diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f1d78e0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+*.swp
+.npmignore
+npm-debug.log
+/.project
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index a6cbb3c..0000000
--- a/.npmignore
+++ /dev/null
@@ -1 +0,0 @@
-example.js
diff --git a/README.markdown b/README.markdown
deleted file mode 100644
index a5da08b..0000000
--- a/README.markdown
+++ /dev/null
@@ -1,93 +0,0 @@
-This module lets you bridge the real world to Node.js. Connect to sensors, robots, turn things on and off, take remote measurements. In fact if you find a creative use for this stuff, let me know! I'd be proud to hear of it being taken advantage of.
-
-(made up Javascript code to get your imagination going)
-
- frontdoor.on("open", function() {
- if (alarm.state == "on") {
- alarm.sound();
- hounds.release();
- } else {
- lights.switchOn();
- voice.speak("Welcome home");
- }
- });
-
-Background
-==========
-
-[Digi's xbee modules](http://www.digi.com/xbee) are good for quickly building low power wireless networks.
-
-They can be connected to a computer over RS232 and communicated on using a standard serial port.
-
-Even easier, with something like the [XBee USB Explorer](http://www.sparkfun.com/products/8687) by SparkFun, you can connect to them easily over USB.
-
-This work is inspired by:
-
-* voodootikigod's [serialport module](https://github.com/voodootikigod/node-serialport) (in fact you're going to need this to use this package)
-* "[Building Wireless Sensor Networks](http://shop.oreilly.com/product/9780596807740.do)" by Rob Faludi
-
-Setup
-=====
-
-I have my xbee coordinator radio connected to the computer running Node. Crucially, the coordinator is in xbee's API mode - this is required to allow you to send remote instructions, and so on.
-
-My remote xbee network modules send periodic measurements and I can push them to web browsers, save them in a database, etc.
-
-I can also use this library to send remote commands and query remote xbee modules. For instance, setting a digital output on a remote module could turn a light on, or a motor, or a laser beam - up to you!
-
-How To Use
-==========
-
-Like node-serialport, using this is "pretty easy because it is pretty basic. It provides you with the building block to make great things, it is not a complete solution - just a cog in the (world domination) machine."
-
-To Install
-----------
-
-You'll need serialport as well (this module doesn't depend on it, but it provides a parser so this is the intended use pattern)
-
- npm install serialport
- npm install xbee
-
-To Use
-------
-
-Open a serial port and give the xbee parser as an option:
-
- var serial_xbee = new SerialPort("/dev/ttyUSB0", {
- parser: xbee.packetParser()
- });
-
-Then listen for incoming xbee packets like this:
-
- serial_xbee.on("data", function(data) {
- console.log('xbee data received:', data.type);
- });
-
-(the __data__ object passed has lot more packet-type-dependent properties)
-
-Send remote AT commands (e.g. query remote module, or "release the hounds"):
-
- // execute an AT command on a remote xbee module
- function RemoteAT(cmd, val, remote64, remote16) {
- var atc = new xbee.RemoteATCommand();
- atc.setCommand(cmd);
- atc.commandParameter = val;
- atc.destination64 = remote64;
- atc.destination16 = remote16;
- b = atc.getBytes();
- serial_xbee.write(b);
- //console.log('Wrote bytes to serial port', b);
- }
-
- // simple example: query ATD0 on remote xbee module.
- var remote64 = [0x00,0x13,0xa2,0x00,0x40,0x7a,0x1f,0x95]; // <-- you'll need to replace this with the 64-bit hex address of your module
- var remote16 = [0xff,0xfe]; // <-- put the 16 bit address of remote module here, if known. Otherwise use [0xff, 0xfe]
-
- RemoteAT('D0', null, remote64, remote16);
-
-See __example.js__ for a full working example (you'll need to use your own xbee IDs, though).
-
-Licence
--------
-
- This work by Richard Morrison is licensed under a Creative Commons Attribution-ShareAlike 2.0 UK: England & Wales License. Based on a work at github.com.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..35d2945
--- /dev/null
+++ b/README.md
@@ -0,0 +1,70 @@
+## NOTE!
+Check out [xbee-api](http://github.com/jouz/xbee-api). It separates out the API component and is aimed at being a more solid and tested module for working with XBees. New higher level modules based on [xbee-api](http://github.com/jouz/xbee-api) are in development here: [xbee-stream](http://github.com/jouz/xbee-stream) and here [xbee-nodes](http://github.com/jouz/xbee-nodes).
+
+# SVD-XBEE
+
+[Digi's xbee modules](http://www.digi.com/xbee) are good for quickly building low power wireless networks. They can be used to send/receive text data from serial ports of different devices. XBees can also be used alone for their on board digital and analog I/O capabilities.
+
+**svd-xbee** is a high level, actively maintained fork of Richard Morrison's [node-xbee](http://github.com/mozz100/node-xbee). It talks the ZigBee API with XBee radio modules over serial connections and provides high level abstractions of the XBee's functionality.
+
+### Nutshell
+```javascript
+var XBee = require('svd-xbee');
+
+var xbee = new XBee({
+ port: 'COM3', // replace with yours
+ baudrate: 9600 // 9600 is default
+}).init();
+
+var robot = xbee.addNode([0x00,0x13,0xa2,0x00,0x40,0x61,0xaa,0xe2]);
+
+var robot.on("data", function(data) {
+ console.log("robot>", data);
+ if (data == "ping") robot.send("pong");
+});
+```
+### Features
+
+- Asynchronous architecture
+- Event-based Node Discovery
+- Local and remote AT commands
+- High-level abstraction of common tasks
+- Parse API frames into meaningful objects
+ - Regular text data
+ - Analog & Digital I/O Samples
+ - Modem Status
+ - Transmission Reports
+
+### Installation
+
+ npm install svd-xbee
+
+### Documentation
+
+For documentation, see the [Documentation](https://github.com/jouz/svd-xbee/wiki/Documentation).
+
+### EXAMPLES
+
+See the [examples folder](https://github.com/jouz/svd-xbee/tree/master/examples) in the repository for more examples.
+
+## SUPPORTED XBEE MODELS
+
+Both ZNet 2.5 and ZIGBEE modules should be supported. Since ZIGBEE offers more features and is more robust, you might be interested in upgrading your modules from ZNet 2.5 to ZIGBEE: [upgradingfromznettozb.pdf](ftp://ftp1.digi.com/support/documentation/upgradingfromznettozb.pdf).
+Development is done using Series 2 XBee modules with XB24-ZB (ZIGBEE) firmware. In specific, this document is used as reference: [90000976_M.pdf](http://ftp1.digi.com/support/documentation/90000976_M.pdf "http://ftp1.digi.com/support/documentation/90000976_M.pdf").
+
+
+## MODULE CONFIGURATION
+
+The module communicating with svd-xbee must be set to use an API function set with escape characters enabled (ATAP = 2). Other nodes in the network can be configured however you find it convenient. See [Module Configuration](https://github.com/jouz/svd-xbee/wiki/Module-Configurationi) for more details.
+
+
+## ACKNOWLEDGMENTS
+
+* voodootikigod's [serialport module](https://github.com/voodootikigod/node-serialport) (in fact you're going to need this to use this package)
+* "[Building Wireless Sensor Networks](http://shop.oreilly.com/product/9780596807740.do)" by Rob Faludi
+
+
+## LICENSE
+
+
+> This work by Jan Kolkmeier is licensed under a Creative Commons Attribution-ShareAlike 2.0 UK: England & Wales License. Based on a work at github.com.
diff --git a/example.js b/example.js
deleted file mode 100644
index dcda477..0000000
--- a/example.js
+++ /dev/null
@@ -1,45 +0,0 @@
-var rsp = require("serialport");
-var xbee = require("xbee");
-var SerialPort = rsp.SerialPort; // localize object constructor
-
-// connect to xbee module on /dev/ttyUSB0 using serialport.
-// Pass xbee.packetParser as the parser - that's it
-var serial_xbee = new SerialPort("/dev/ttyUSB0", {
- parser: xbee.packetParser()
-});
-
-// listen for incoming xbee data
-serial_xbee.on("data", function(data) {
- console.log('xbee data received:', data.type);
-});
-
-// execute an AT command on the local xbee module
-function AT(cmd, val) { // e.g. 'ID' or '%V'
- var atc = new xbee.ATCommand();
- atc.setCommand(cmd);
- atc.commandParameter = val;
- b = atc.getBytes();
- serial_xbee.write(b);
- //console.log('Wrote bytes to serial port', b);
-};
-
-// simple example: ATID on local xbee module
-AT('ID');
-
-// execute an AT command on a remote xbee module
-function RemoteAT(cmd, val, remote64, remote16) {
- var atc = new xbee.RemoteATCommand();
- atc.setCommand(cmd);
- atc.commandParameter = val;
- atc.destination64 = remote64;
- atc.destination16 = remote16;
- b = atc.getBytes();
- serial_xbee.write(b);
- //console.log('Wrote bytes to serial port', b);
-}
-
-// simple example: query ATD0 on remote xbee module.
-var remote64 = [0x00,0x13,0xa2,0x00,0x40,0x7a,0x1f,0x95]; // <-- you'll need to replace this with the 64-bit hex address of your module
-var remote16 = [0xff,0xfe]; // <-- put the 16 bit address of remote module here, if known. Otherwise use [0xff, 0xfe]
-
-RemoteAT('D0', null, remote64, remote16);
diff --git a/examples/full/full.js b/examples/full/full.js
new file mode 100644
index 0000000..0d37b76
--- /dev/null
+++ b/examples/full/full.js
@@ -0,0 +1,131 @@
+var util = require('util');
+var XBee = require('../../index.js');
+
+// This parser buffers data, emits chucks
+// seperated by space chars (" ")
+var Parser = require('./parser.js');
+
+var xbee = new XBee.XBee({
+ port: 'COM3', // replace with yours
+ baudrate: 9600 // 9600 is default
+})
+
+// Open COM port, read some parameters from the XBee at once.
+xbee.init();
+
+// Add Node by hand...
+var myNode = xbee.addNode([0x00,0x13,0xa2,0x00,0x40,0x61,0x2f,0xe4], Parser);
+
+// Triggered whenever this node
+// - responds to discovery request
+// - sends identification frame on network association
+// (not necessarily on power up, as node might
+// already be associated)
+myNode.on("discovered", function(node) {
+ console.log("Discovered myNode");
+
+ // We configure pin D4 to sample digital data.
+ myNode.AT("D4", [ 0x03 ], function(err, res, x) {
+ if (err) return console.log("AT Error D4:", err);
+
+ // This configures pin 2 to sample analog data.
+ myNode.AT("D2", [ 0x02 ], function(err, res, x) {
+ if (err) return console.log("AT Error D2:", err);
+
+ // We can put requests in the callback, but generally
+ // but generally parallel requests are fine!
+ myNode.AT("IS", function(err, res) { // Manually query IO Sample
+ if (err) return console.log("AT Error IS:", err);
+ console.log("Queried IO Sample: ", util.inspect(xbee.tools.parseIOSample(res)));
+ });
+ });
+
+ });
+});
+
+// Whenever myNode sends us an IO data sample...
+myNode.on("io", function(io) {
+ console.log("I/O: ", util.inspect(io));
+});
+
+// Whenever myNode sends us text data - since we are using
+// a parser, data will first be processed there before landing
+// here:
+myNode.on("data", function(data) {
+ console.log("Data: ", util.inspect(data));
+ if (data == "ping") myNode.send("pong");
+});
+
+
+// Emitted when .init() is done (COM port open, parameters read)
+xbee.on("initialized", function(params) {
+ console.log("XBee Parameters: %s", util.inspect(params));
+ // Start Node discovery to find currently connected nodes.
+ xbee.discover();
+ console.log("Node discovery starded...");
+
+ // Local Request:
+ xbee.AT("VR", function(err, res) {
+ console.log("Firmware Version:", xbee.tools.bArr2HexStr(res));
+ });
+
+
+ // Remote Requests ...
+ // Enable auto reporting of IO Samples with a 2000ms interval set
+ // Note that this request will most typically fail.
+ // Try uncommenting the line "xbee.discover()", and this should
+ // work fine (if your network is configured properly).
+ // The reason is that remote nodes seem to become unresponsive for
+ // a little while during node discovery.
+ myNode.AT("IR", xbee.tools.dec2bArr(2000), function(err, res) {
+ if (err) return console.log("AT Error:", util.inspect(err));
+ console.log("Auto Reporting Enabled!");
+ });
+});
+
+xbee.on("discoveryEnd", function() {
+ // Discovery is over. If the XBee is an End Device/Router,
+ // you may want to re-issue xbee.discover() later.
+ // For Coordinators this should not be necessary, as
+ // nodes will notify coordinators once they join the PAN,
+ // triggering the "node" event.
+ console.log("...node discovery over");
+});
+
+// Triggered whenever a node is discovered that is not already
+// added. / myNode will not show up here!
+xbee.on("newNodeDiscovered", function(node) {
+ console.log("Node %s discovered", node.remote64.hex);
+ console.log(util.inspect(node));
+
+ node.on("data", function(data) {
+ console.log("%s> %s", node.remote64.hex, util.inspect(data));
+ node.send("pong", function(err, status) {
+ // Transmission successful if err is null
+ });
+ });
+
+ node.on("io", function(sample) {
+ console.log("%s> %s", node.remote64.hex, util.inspect(data));
+ });
+
+ // Here some functions you might find helpful:
+ node.setPinMode("DIO2", "DIGITAL_INPUT");
+ node.setPinMode("DIO3", "DIGITAL_INPUT");
+ node.setPinMode("DIO0", "DIGITAL_OUTPUT_HIGH");
+
+ node.getPinMode("DIO0", function(err, res) {
+ console.log("Pin DIO0 has mode: ", res);
+ });
+
+ // XBee will send a sample whenever one of these pins measure a change
+ node.setChangeDetection([ "DIO2", "DIO3" ]);
+
+ // Manually retrieve a sample
+ node.getSample(function(err, res) {
+ console.log("Res2:", util.inspect(res));
+ });
+
+ // ...or instruct xbee to sample data every 5 seconds:
+ node.setSampleInterval(2000);
+});
diff --git a/examples/full/parser.js b/examples/full/parser.js
new file mode 100644
index 0000000..c9af6f3
--- /dev/null
+++ b/examples/full/parser.js
@@ -0,0 +1,18 @@
+exports = module.exports = function (device) {
+ var delimiter = " ";
+ function DataParser(device) {
+ this.device = device;
+ this.buffer = "";
+ }
+ DataParser.prototype.parse = function(data) {
+ this.buffer += data;
+ var split = this.buffer.indexOf(delimiter);
+ while (split > -1) {
+ this.device.emit('data', this.buffer.slice(0,split));
+ this.buffer = this.buffer.slice(split+delimiter.length);
+ split = this.buffer.indexOf(delimiter);
+ }
+ }
+
+ return new DataParser(device);
+}
diff --git a/examples/nutshell/nutshell.js b/examples/nutshell/nutshell.js
new file mode 100644
index 0000000..6129b43
--- /dev/null
+++ b/examples/nutshell/nutshell.js
@@ -0,0 +1,27 @@
+var util = require('util');
+var XBee = require('../../index.js').XBee;
+
+var xbee = new XBee({
+ port: 'COM5', // replace with yours
+ baudrate: 9600 // 9600 is default
+}).init();
+
+xbee.on("initialized", function(params) {
+ console.log("XBee Parameters: %s", util.inspect(params));
+
+ xbee.discover();
+ console.log("Node discovery starded...");
+});
+
+xbee.on("discoveryEnd", function() {
+ console.log("...node discovery over");
+});
+
+
+// Add Set to your remote node
+var robot = xbee.addNode([0x00,0x13,0xa2,0x00,0x40,0x61,0x2f,0xe4]);
+
+robot.on("data", function(data) {
+ console.log("robot>", data);
+ if (data == "ping") robot.send("pong");
+});
diff --git a/examples/web/index.html b/examples/web/index.html
new file mode 100644
index 0000000..536e333
--- /dev/null
+++ b/examples/web/index.html
@@ -0,0 +1,41 @@
+
+
+
+
+ SVD-XBEE WEB TEST
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/web/web.js b/examples/web/web.js
new file mode 100644
index 0000000..cff0ab4
--- /dev/null
+++ b/examples/web/web.js
@@ -0,0 +1,87 @@
+var util = require('util');
+var XBee = require('../../index.js').XBee;
+var app = require('http').createServer(handler)
+ , io = require('socket.io').listen(app)
+ , fs = require('fs')
+
+app.listen(80);
+
+function handler(req, res) {
+ fs.readFile(__dirname + '/index.html',
+ function (err, data) {
+ if (err) {
+ res.writeHead(500);
+ return res.end('Error loading index.html');
+ }
+
+ res.writeHead(200);
+ res.end(data);
+ });
+}
+
+io.sockets.on('connection', function (socket) {
+});
+
+var xbee = new XBee({
+ port: 'COM3', // replace with yours
+ baudrate: 9600 // 9600 is default
+});
+
+// Add Node by hand...
+var myNode = xbee.addNode([0x00,0x13,0xa2,0x00,0x40,0x61,0x2f,0xe4]);
+
+xbee.on("initialized", function(params) {
+ console.log("XBee Parameters: %s", util.inspect(params));
+ xbee.discover();
+ console.log("Node discovery starded...");
+});
+
+xbee.on("discoveryEnd", function() {
+ console.log("...node discovery over");
+});
+
+xbee.init();
+
+myNode.on("io", function(sample) {
+ console.log("I/O:", io);
+ io.sockets.emit("io", sample);
+});
+
+myNode.on("data", function(data) {
+ console.log("Data:", data);
+ io.sockets.emit("data", data);
+});
+
+myNode.on("discovered", function(node) {
+ console.log("myNode Discovered.");
+
+
+ // Enable Auto Reporting every 2 seconds
+ myNode.AT("IR", xbee.tools.dec2bArr(2000), function(err, res) {
+ if (err) return console.log("AT Error:", util.inspect(err));
+ console.log("Auto Reporting Enabled.");
+ });
+
+ // Configure pin D4 to sample digital data.
+ myNode.AT("D4", [ 0x03 ], function(err, res) {
+ if (err) return console.log("AT Error:", err);
+ console.log("D4 configured.");
+ });
+
+ // Configures pin D2 to sample analog data.
+ myNode.AT("D2", [ 0x02 ], function(err, res) {
+ if (err) return console.log("AT Error:", err);
+ console.log("D2 configured.");
+ });
+});
+
+io.sockets.on('connection', function(socket) {
+ socket.on("msg", function(data) {
+ myNode.send(data);
+ });
+});
+
+// Triggered whenever a node is discovered that is not already
+xbee.on("newNodeDiscovered", function(node) {
+ console.log("Node %s discovered.", node.remote64.hex);
+});
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..dff39ec
--- /dev/null
+++ b/index.js
@@ -0,0 +1,679 @@
+"use strict"; // see: ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
+
+var util = require('util');
+var EventEmitter = require('events').EventEmitter;
+var api = require("./lib/xbee-api.js");
+var serialport = require("serialport");
+var async = require('async');
+var os = require('os');
+
+var C = exports.C = api.Constants;
+var Tools = exports.Tools = api.tools;
+
+function XBee(options) {
+ EventEmitter.call(this);
+
+ // Option Parsing
+ if (typeof options === 'string') {
+ this.options = { port: options };
+ } else {
+ this.options = options;
+ }
+
+ this.tools = Tools; // Remove this, use XBee.Tools instead
+
+ this.data_parser = options.data_parser || undefined;
+
+ this.use_heartbeat = options.use_heartbeat || false;
+ this.heartbeat_packet = options.heartbeat_packet || '```';
+ this.heartbeat_timeout = options.heartbeat_timeout || 8000;
+
+ // How long (in ms) shall we wait before deciding that a transmit hasn't been successful?
+ this.transmit_status_timeout = options.transmit_status_timeout || 1000;
+
+ // maximum number of outbound messages to be waiting for a response
+ this.max_parallel_messages = options.max_parallel_messages || 1;
+
+ if (options.api_mode) api.api_mode = options.api_mode;
+
+ // Current nodes
+ this.nodes = {};
+}
+
+util.inherits(XBee, EventEmitter);
+
+
+XBee.prototype._createNode = function (data) {
+ return new Node(this, data, this.data_parser)
+}
+
+XBee.prototype._makeTask = function (packet) {
+ var self = this;
+ return function Writer(cb) {
+ //console.log("<<< "+util.inspect(packet.data));
+ //console.log("<<< "+packet.data);
+
+ var timeout = setTimeout(function () {
+ cb({ msg: "Never got Transmit status from XBee" });
+ }, self.transmit_status_timeout);
+ self.serial.write(packet.data, function (err, results) {
+ if (err) {
+ cb(err);
+ } else {
+ //console.log(util.inspect(packet.data));
+ //console.log("written data: " + packet.cbid + " : " + results);
+ if (results != packet.data.length) return cb(new Error("Not all bytes written"));
+ self.serial.once(packet.cbid, function (packet) {
+ //console.log("Got Respones: "+packet.cbid);
+ clearTimeout(timeout);
+ var error = null;
+ if (packet.commandStatus !== undefined) {
+ if (packet.commandStatus != C.COMMAND_STATUS.OK) {
+ error = C.COMMAND_STATUS[packet.commandStatus];
+ }
+ packet = packet.commandData;
+ } else if (packet.deliveryStatus !== undefined) {
+ if (packet.deliveryStatus != C.DELIVERY_STATUS.SUCCESS) {
+ error = C.DELIVERY_STATUS[packet.deliveryStatus];
+ }
+ }
+ cb(error, packet);
+ });
+ }
+ });
+ };
+}
+
+XBee.prototype._AT = function (cmd, val, _cb) {
+ // val parameter is optional
+ if (typeof val === 'function') {
+ _cb = val;
+ val = undefined;
+ }
+
+ var frame = new api.ATCommand();
+ frame.setCommand(cmd);
+ frame.commandParameter = val;
+ var cbid = C.FRAME_TYPE.AT_COMMAND_RESPONSE + C.EVT_SEP + frame.frameId;
+ var packet = [this._makeTask({
+ data: frame.getBytes(),
+ cbid: cbid
+ })];
+ this._queue.push({ packets: packet, cb: _cb });
+ return cbid;
+}
+
+// TODO: Merge this up with _AT to save some space
+XBee.prototype._remoteAT = function (cmd, remote64, remote16, val, _cb) {
+ // val parameter is optional
+ if (typeof val === 'function') {
+ _cb = val;
+ val = undefined;
+ }
+
+ var frame = new api.RemoteATCommand();
+ frame.setCommand(cmd);
+ frame.commandParameter = val;
+ frame.destination64 = remote64.dec;
+ frame.destination16 = remote16.dec;
+ var cbid = C.FRAME_TYPE.REMOTE_COMMAND_RESPONSE + C.EVT_SEP + frame.frameId;
+ var packet = [this._makeTask({
+ data: frame.getBytes(),
+ cbid: cbid
+ })];
+ this._queue.push({ packets: packet, cb: _cb });
+ return cbid;
+}
+
+XBee.prototype._handleNodeIdentification = function (node) {
+ if (!this.nodes[node.remote64.hex]) {
+ var node = self.addNode(node.remote64.dec, node.remote16.dec, this.data_parser);
+ //this.nodes[node.remote64.hex] = this._createNode(node);
+ this.emit("newNodeDiscovered", node);
+ } else {
+ // update 16-bit address, as it may change during reconnects.
+ this.nodes[node.remote64.hex].remote16 = node.remote16;
+ this.nodes[node.remote64.hex].id = node.id;
+ this.nodes[node.remote64.hex].emit("discovered", this.nodes[node.remote64.hex]);
+ }
+ this.nodes[node.remote64.hex].connected = true;
+}
+
+XBee.prototype.init = function (cb) {
+ var self = this;
+ // Serial connection to the XBee
+ self.serial = new serialport.SerialPort(self.options.port, {
+ baudrate: self.options.baudrate || 9600,
+ databits: 8,
+ stopbits: 1,
+ parity: 'none',
+ parser: api.packetBuilder()
+ });
+
+ self.serial.on("open", function () {
+ self.readParameters.bind(self)(cb);
+ });
+
+ var exit = function () {
+ self.serial.close(function (err) {
+ if (err) console.log("Error closing port: " + util.inspect(err));
+ process.exit();
+ });
+ }
+
+ if (os.platform() !== 'win32') {
+ process.on('SIGINT', exit);
+ }
+
+
+ /* Frame-specific Handlers */
+
+ // Whenever a node reports with an identification frame.
+ self._onNodeIdentification = function (packet) {
+ var node = parseNodeIdentificationPayload(packet.nodeIdentificationPayload);
+ self._handleNodeIdentification(node);
+ }
+
+ // Modem Status
+ self._onModemStatus = function (packet) {
+ if (packet.status == C.MODEM_STATUS.JOINED_NETWORK) {
+ self.emit("joinedNetwork", packet);
+ } else if (packet.status == C.MODEM_STATUS.HARDWARE_RESET) {
+ self.emit("hardwareReset", packet);
+ } else if (packet.status == C.MODEM_STATUS.WATCHDOG_RESET) {
+ self.emit("watchdogReset", packet);
+ } else if (packet.status == C.MODEM_STATUS.DISASSOCIATED) {
+ self.emit("disassociated", packet);
+ } else if (packet.status == C.MODEM_STATUS.COORDINATOR_STARTED) {
+ self.emit("coordinatorStarted", packet);
+ } else {
+ console.warn("Modem status: ", C.MODEM_STATUS[packet.status]);
+ }
+ }
+
+ // Messages
+ self._onReceivePacket = function (data) {
+ if (!self.nodes[data.remote64.hex]) {
+ var node = self.addNode(data.remote64.dec, data.remote16.dec, self.data_parser);
+ self.emit("newNodeDiscovered", node); // TODO: Should this be a different event?
+ }
+ self.nodes[data.remote64.hex]._onReceivePacket(data);
+ }
+
+ // Data samples (from XBee's I/O)
+ self._onDataSampleRx = function (data) {
+ // Todo: data from local xbee?
+ if (!self.nodes[data.remote64.hex]) {
+ var node = self.addNode(data.remote64.dec, data.remote16.dec, self.data_parser);
+ self.emit("newNodeDiscovered", node); // TODO: Should this be a different event?
+ }
+ self.nodes[data.remote64.hex]._onDataSampleRx(data);
+ }
+
+ self.serial.on(C.FRAME_TYPE.MODEM_STATUS, function(packet) {
+ self._onModemStatus(packet);
+ });
+ self.serial.on(C.FRAME_TYPE.NODE_IDENTIFICATION, function(packet) {
+ self.self._onNodeIdentification(packet);
+ });
+ self.serial.on(C.FRAME_TYPE.ZIGBEE_RECEIVE_PACKET, function(packet) {
+ self.self._onReceivePacket(packet);
+ });
+ self.serial.on(C.FRAME_TYPE.ZIGBEE_IO_DATA_SAMPLE_RX, function(packet) {
+ self.self._onDataSampleRx(packet);
+ });
+
+ self._queue = async.queue(function (task, callback) {
+ async.series(task.packets, function (err, data) {
+ if (typeof task.cb === 'function') task.cb(err, data[data.length - 1]);
+ callback();
+ });
+ }, self.max_parallel_messages);
+
+ return self;
+}
+
+XBee.prototype.readParameters = function (_done_cb) {
+ var self = this;
+
+ // Returns a function that initiates an AT command to
+ // query a configuration parameter's value.
+ // To be passed to an async.parallel.
+ var QF = function (command, val, f) { // Format the result using f
+ f = typeof f !== 'undefined' ? f : function (a) {
+ return a
+ };
+ return function (cb) {
+ self._AT(command, val, function (err, res) {
+ if (!err) {
+ cb(err, f(res));
+ } else {
+ console.error('Error: XBee.readParameters.QF; ', err.msg);
+ }
+ });
+ }
+ }
+
+ var parameters = {
+ panid: QF('ID', undefined, Tools.bArr2HexStr),
+ id: QF('NI', undefined, Tools.bArr2Str),
+ sourceHigh: QF('SH', undefined, Tools.bArr2HexStr),
+ sourceLow: QF('SL', undefined, Tools.bArr2HexStr),
+ nodeDiscoveryTime: QF('NT', undefined, function (a) {
+ return 100 * Tools.bArr2Dec(a);
+ })
+ };
+
+ var done = function (err, results) {
+ if (err) {
+ self.emit("error", new Error("Failure to read XBee params: " + util.inspect(err)));
+ if (typeof _done_cb === 'function') _done_cb(err);
+ }
+ self.parameters = results;
+ self.emit("initialized", self.parameters);
+ if (typeof _done_cb === 'function') _done_cb(null, self.parameters);
+ }
+
+ // Using async to read parameters
+ var res_stop = Object.keys(parameters).length;
+ var results = {};
+ for (var k in parameters) {
+ parameters[k]((function (key) {
+ return function (err, data) {
+ if (err) return done(err, null);
+ results[key] = data;
+ // TODO: Timeout?
+ if (--res_stop === 0) {
+ done(null, results);
+ }
+ }
+ })(k));
+ }
+}
+
+// Add a node by hand.
+XBee.prototype.addNode = function(remote64, remote16, parser) {
+ var self = this;
+ if (!remote16 instanceof Array) {
+ parser = remote16;
+ } else if (!remote16) {
+ remote16 = [0xff,0xfe]; // Unknown
+ }
+
+ var node_data = {
+ remote16: { dec: remote16, hex: Tools.bArr2HexStr(remote16) },
+ remote64: { dec: remote64, hex: Tools.bArr2HexStr(remote64) }
+ };
+
+ var node = self.nodes[node_data.remote64.hex];
+
+ if (!node) {
+ node = self.nodes[node_data.remote64.hex] = self._createNode(node_data);
+ node.connected = false;
+ }
+
+ if (typeof parser === "function")
+ node.parser = parser(node);
+
+ return node;
+}
+
+// Run network discovery. Associated nodes can report in
+// for config.nodeDiscoveryTime ms.
+XBee.prototype.discover = function(cb) {
+ var self = this;
+ var cbid = self._AT('ND');
+ self.serial.on(cbid, function(packet) {
+ var node = parseNodeIdentificationPayload(packet.commandData);
+ self._handleNodeIdentification(node);
+ })
+ setTimeout(function() {
+ if (typeof cb === 'function') cb();
+ self.removeAllListeners(cbid);
+ self.emit("discoveryEnd");
+ }, self.parameters.nodeDiscoveryTime || 6000);
+}
+
+XBee.prototype.broadcast = function(data, cb) {
+ var remote64 = {};
+ var remote16 = {};
+ remote64.dec = [0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff];
+ remote16.dec = [0xff,0xfe];
+ this.send(data, remote64, remote16, cb);
+}
+
+XBee.prototype.send = function(data, remote64, remote16, _cb) {
+ var packets = [];
+ while (data.length > 0) {
+ var frame = new api.TransmitRFData();
+ frame.destination64 = remote64.dec;
+ frame.destination16 = remote16.dec;
+ var length = (C.MAX_PAYLOAD_SIZE < data.length) ? C.MAX_PAYLOAD_SIZE : data.length;
+ frame.RFData = data.slice(0, length);
+ data = data.slice(length);
+ packets.push(this._makeTask({
+ data: frame.getBytes(),
+ cbid: C.FRAME_TYPE.ZIGBEE_TRANSMIT_STATUS + C.EVT_SEP + frame.frameId
+ }));
+ }
+
+ this._queue.push({ packets: packets, cb: _cb });
+}
+
+XBee.prototype.AT = function (cmd, val, _cb) {
+ this._AT(cmd, val, _cb);
+}
+
+XBee.prototype.setChangeDetection = function (pins, cb, remote) {
+ // TODO: this is lazy...
+ var mask = "000000000000".split('');
+ for (var pin in pins) {
+ var _pin = pins[pin];
+ if (typeof _pin == "number") {
+ mask[C.CHANGE_DETECTION.PIN[_pin]] = '1';
+ } else {
+ mask[C.CHANGE_DETECTION[_pin]] = '1';
+ }
+ }
+
+ var val = parseInt(mask.reverse().join(''), 2);
+ val = Tools.dec2bArr(val, 2);
+
+ if (remote)
+ this._remoteAT("IC", remote.remote64, remote.remote16, val, cb);
+ else
+ this._AT("IC", val, cb);
+}
+
+XBee.prototype.setSampleInterval = function (interval, cb, remote) {
+ var _interval = Tools.dec2bArr(interval, 2);
+ if (remote)
+ this._remoteAT("IR", remote.remote64, remote.remote16, _interval, cb);
+ else
+ this._AT("IR", _interval, cb);
+}
+
+XBee.prototype.getSample = function (cb, remote) {
+ var _cb = function (err, res) {
+ if (err) cb(err);
+ else cb(err, parseIOSample(res));
+ }
+
+ if (remote)
+ this._remoteAT("IS", remote.remote64, remote.remote16, _cb);
+ else
+ this._AT("IS", _cb);
+}
+
+
+// Change the mode of a pin.
+// If "pin" is a string,
+// we assume the descriptive name (AO3, etc.)
+// If "pin" is a number
+// we assume the phsical pin is meant (~1-20)
+XBee.prototype.getAnalogPin = function (pin, cb, remote) {
+ var _pin;
+ if (typeof pin == "number")
+ _pin = C.ANALOG_CHANNEL.MASK[C.ANALOG_CHANNEL.PIN[pin]][0]
+ else _pin = pin;
+
+ this.getSample(function (err, res) {
+ if (err) cb(err);
+ else if (res.analogSamples[_pin] == undefined)
+ cb(new Error("XBee not configured to take analog samples on pin " + _pin));
+ else
+ cb(err, res.analogSamples[_pin]);
+ }, remote);
+}
+
+// Change the mode of a pin.
+// If "pin" is a string,
+// we assume the descriptive name (DIO3, etc.)
+// If "pin" is a number
+// we assume the phsical pin is meant (~1-20)
+XBee.prototype.getDigitalPin = function (pin, cb, remote) {
+ var _pin;
+ if (typeof pin == "number")
+ _pin = C.DIGITAL_CHANNEL.MASK[C.DIGITAL_CHANNEL.PIN[pin]][0]
+ else _pin = pin;
+
+ this.getSample(function (err, res) {
+ if (err) cb(err);
+ else if (res.digitalSamples[_pin] == undefined)
+ cb(new Error("XBee not configured to take digital samples on pin " + _pin));
+ else
+ cb(err, res.digitalSamples[_pin]);
+ }, remote);
+}
+
+XBee.prototype.getPinMode = function (pin, mode, cb, remote) {
+ var _pin;
+ if (typeof pin == "number") _pin = C.PIN_COMMAND.PIN[pin];
+ else if (typeof pin == "string" && pin.length != 2) _pin = C.PIN_COMMAND[pin];
+ else _pin = pin;
+ if (_pin == undefined)
+ return cb(new Error("Unknown pin: " + pin));
+
+ var _cb = function (err, res) {
+ if (err) cb(err);
+ else cb(err, res[0]); // Or should we return the name of the mode?
+ };
+
+ if (remote)
+ this._remoteAT(_pin, remote.remote64, remote.remote16, _cb);
+ else
+ this._AT(_pin, _cb);
+}
+
+// Change the mode of a pin.
+// If "pin" is not the two-letter AT command associated with the pin,
+// we assume the descriptive name (DIO3, etc.)
+// If "pin" is a number
+// we assume the phsical pin is meant (~1-20)
+// If "mode" is a number
+// we assume it is the byte parameter
+// If "mode" is a string
+// we assume descriptive mode (DISABLED, DIGITAL_INPUT, ...) see constants.js
+XBee.prototype.setPinMode = function (pin, mode, cb, remote) {
+ var _pin, _mode;
+ if (typeof pin == "number") _pin = C.PIN_COMMAND.PIN[pin];
+ else if (typeof pin == "string" && pin.length != 2) _pin = C.PIN_COMMAND[pin];
+ else _pin = pin;
+ if (_pin == undefined || C.PIN_MODE[_pin] == undefined)
+ return cb(new Error("Unknown pin: " + pin));
+ if (typeof mode == "string") {
+ _mode = C.PIN_MODE[_pin][mode];
+ } else _mode = mode;
+ if (_mode == undefined || C.PIN_MODE[_pin][mode] == undefined)
+ return cb(new Error("Unknown mode " + mode + " for pin " + pin));
+ _mode = parseInt(_mode); // Make sure mode is dec
+
+ if (remote)
+ this._remoteAT(_pin, remote.remote64, remote.remote16, [_mode], cb);
+ else
+ this._AT(_pin, [_mode], cb);
+}
+
+XBee.prototype.setDestinationNode = function (dest, cb, remote) {
+ cb("Not Implemented Yet");
+}
+
+exports.XBee = XBee;
+
+
+function Node(xbee, params, data_parser) {
+ EventEmitter.call(this);
+ this.xbee = xbee;
+ this.remote16 = params.remote16;
+ this.remote64 = params.remote64;
+ this.id = params.id || "";
+ this.deviceType = params.deviceType || -1;
+ this.buffer = "";
+ if (typeof data_parser === 'function')
+ this.parser = data_parser(this);
+ this.timeout = {};
+ this.connected = true;
+ this.refreshTimeout();
+}
+
+util.inherits(Node, EventEmitter);
+
+Node.prototype._onReceivePacket = function (packet) {
+
+// the original code used Buffer.toString('ascii')
+// but ascii is 7 bit only, so the most significant bit of each value got trahsed.
+// this is not elegant, but it works
+// TODO: refactor data string creation from buffer
+ var data = "";
+ for(var i = 0; i < packet.rawData.length; i+= 1) {
+ data += String.fromCharCode(packet.rawData[i]);
+ }
+
+ if (this.xbee.use_heartbeat) {
+ this.refreshTimeout();
+ if (data === this.xbee.heartbeat_packet) return;
+ }
+
+ if (this.parser !== undefined) this.parser.parse(data);
+ else this.emit('data', data, packet);
+}
+
+Node.prototype._onDataSampleRx = function(packet) {
+ var sample = parseIOSample(packet.ioSample);
+ this.emit('io', sample, packet);
+ if (this.xbee.use_heartbeat) {
+ this.refreshTimeout();
+ }
+}
+
+Node.prototype.timeoutOccured = function () {
+ this.connected = false;
+ this.emit('disconnect');
+}
+
+Node.prototype.refreshTimeout = function () {
+ clearTimeout(this.timeout);
+ this.timeout = setTimeout(this.timeoutOccured.bind(this), this.xbee.heartbeat_timeout);
+ if (!this.connected) {
+ this.connected = true;
+ // todo other stuff
+ }
+}
+
+Node.prototype.send = function (data, cb) {
+ this.xbee.send(data, this.remote64, this.remote16, cb);
+}
+
+Node.prototype.AT = function (cmd, val, cb) {
+ // val parameter is optional
+ if (typeof val === "function") {
+ // use val as the callback in this case
+ this.xbee._remoteAT(cmd, this.remote64, this.remote16, val);
+ } else {
+ this.xbee._remoteAT(cmd, this.remote64, this.remote16, val, cb);
+ }
+}
+
+Node.prototype.isCoordinator = function () {
+ return this.deviceType === C.DEVICE_TYPES.COORDINATOR;
+}
+
+Node.prototype.isRouter = function () {
+ return this.deviceType === C.DEVICE_TYPES.ROUTER;
+}
+
+Node.prototype.isEndDevice = function () {
+ return this.deviceType === C.DEVICE_TYPES.END_DEVICE
+}
+
+Node.prototype.setChangeDetection = function (pins, cb) {
+ this.xbee.setChangeDetection(pins, cb, this);
+}
+
+Node.prototype.setSampleInterval = function (interval, cb) {
+ this.xbee.setSampleInterval(interval, cb, this);
+}
+
+Node.prototype.getSample = function (cb) {
+ this.xbee.getSample(cb, this);
+};
+
+Node.prototype.setPinMode = function (pin, mode, cb) {
+ this.xbee.setPinMode(pin, mode, cb, this);
+}
+
+Node.prototype.getAnalogPin = function (pin, cb) {
+ this.xbee.getAnalogPin(pin, cb, this);
+}
+
+Node.prototype.getDigitalPin = function (pin, cb) {
+ this.xbee.getDigitalPin(pin, cb, this);
+}
+
+Node.prototype.setDestinationNode = function (dest, cb) {
+ this.xbee.setDestinationNode(dest, cb);
+}
+
+exports.Node = Node;
+
+var parseIOSample = exports.parseIOSample = function (sample) {
+ var res = {
+ digitalSamples: {},
+ analogSamples: {}
+ }
+
+ var numSamples = sample.splice(0, 1)[0];
+ var mskD = sample.splice(0, 2);
+ mskD = (mskD[0] << 8) | mskD[1];
+ var mskA = sample.splice(0, 1)[0];
+
+ if (mskD > 0) {
+ var digitalSamples = sample.splice(0, 2);
+ var valD = (digitalSamples[0] << 8) | digitalSamples[1];
+ for (var bit in C.DIGITAL_CHANNELS.MASK) {
+ if ((mskD & (1 << bit)) >> bit) {
+ res.digitalSamples[C.DIGITAL_CHANNELS.MASK[bit][0]] = (valD & (1 << bit)) >> bit;
+ }
+ }
+ }
+
+ if (mskA > 0) {
+ var analogSamples = sample.splice(0);
+ var sampleNr = 0;
+ for (var bit in C.ANALOG_CHANNELS.MASK) {
+ if ((mskA & (1 << bit)) >> bit) {
+ var valA = (analogSamples[sampleNr * 2] << 8) | analogSamples[sampleNr * 2 + 1];
+ // Convert to mV
+ res.analogSamples[C.ANALOG_CHANNELS.MASK[bit][0]] = (valA * 1200) / 1023;
+ sampleNr++;
+ }
+ }
+ }
+
+ return res;
+}
+
+var parseAddress = exports.parseAddress = function (dec) {
+ return {
+ dec: dec,
+ hex: Tools.bArr2HexStr(dec)
+ }
+}
+
+var parseNodeIdentificationPayload = exports.parseNodeIdentificationPayload = function (payload) {
+ var res = {}
+ res.id = "";
+
+ res.remote16 = parseAddress(payload.splice(0, 2));
+ res.remote64 = parseAddress(payload.splice(0, 8));
+ while (payload[0] != 0x00) res.id += String.fromCharCode(payload.splice(0, 1)[0]);
+ payload.splice(0, 1); // Read 0x00 away
+ res.remoteParent16 = parseAddress(payload.splice(0, 2));
+ res.deviceType = payload.splice(0, 1)[0];
+ res.sourceEvent = payload.splice(0, 1)[0];
+ res.nodeIdentificationPayload = payload.splice(0);
+ // res.status = payload.splice(0, 1)[0];
+ // ...
+
+ return res;
+}
diff --git a/lib/constants.js b/lib/constants.js
new file mode 100644
index 0000000..5fe45d8
--- /dev/null
+++ b/lib/constants.js
@@ -0,0 +1,349 @@
+exports = module.exports;
+exports.START_BYTE = 0x7E;
+exports.ESCAPE = 0x7D;
+exports.XOFF = 0x13;
+exports.XON = 0x11;
+exports.EVT_SEP = "_";
+exports.MAX_PAYLOAD_SIZE = 74;
+
+var ft = exports.FRAME_TYPE = {};
+var diss = exports.DISCOVERY_STATUS = {};
+var dels = exports.DELIVERY_STATUS = {};
+var coms = exports.COMMAND_STATUS = {};
+var ms = exports.MODEM_STATUS = {};
+var ro = exports.RECEIVE_OPTIONS = {};
+var dt = exports.DEVICE_TYPES = {};
+
+var dc = exports.DIGITAL_CHANNELS = { MASK: {}, PIN:{} };
+var ac = exports.ANALOG_CHANNELS = { MASK: {}, PIN:{} };
+var pr = exports.PULLUP_RESISTOR = { MASK: {}, PIN:{} };
+var ic = exports.CHANGE_DETECTION = { MASK: {}, PIN:{} };
+var pm = exports.PIN_MODE = {};
+var pc = exports.PIN_COMMAND = { PIN:{} };
+
+// Device Type
+dt.COORDINATOR = 0x00;
+dt[0x00] = "COORDINATOR";
+dt.ROUTER = 0x01;
+dt[0x01] = "ROUTER";
+dt.END_DEVICE = 0x02;
+dt[0x02] = "END_DEVICE";
+
+// Frame Type
+ft.AT_COMMAND = 0x08;
+ft[0x08] = "AT Command (0x08)";
+ft.AT_COMMAND_QUEUE_PARAMETER_VALUE = 0x09;
+ft[0x09] = "AT Command - Queue Parameter Value (0x09)";
+ft.ZIGBEE_TRANSMIT_REQUEST = 0x10;
+ft[0x10] = "ZigBee Transmit Request (0x10)";
+ft.EXPLICIT_ADDRESSING_ZIGBEE_COMMAND_FRAME = 0x11;
+ft[0x11] = "Explicit Addressing ZigBee Command Frame (0x11)";
+ft.REMOTE_COMMAND_REQUEST = 0x17;
+ft[0x17] = "Remote Command Request (0x17)";
+ft.CREATE_SOURCE_ROUTE = 0x21;
+ft[0x21] = "Create Source Route (0x21)";
+ft.AT_COMMAND_RESPONSE = 0x88;
+ft[0x88] = "AT Command Response (0x88)";
+ft.MODEM_STATUS = 0x8A;
+ft[0x8A] = "Modem Status (0x8A)";
+ft.ZIGBEE_TRANSMIT_STATUS = 0x8B;
+ft[0x8B] = "ZigBee Transmit Status (0x8B)";
+ft.ZIGBEE_RECEIVE_PACKET = 0x90;
+ft[0x90] = "ZigBee Receive Packet (AO=0) (0x90)";
+ft.ZIGBEE_EXPLICIT_RX = 0x91;
+ft[0x91] = "ZigBee Explicit Rx Indicator (AO=1) (0x91)";
+ft.ZIGBEE_IO_DATA_SAMPLE_RX = 0x92;
+ft[0x92] = "ZigBee IO Data Sample Rx Indicator (0x92)";
+ft.XBEE_SENSOR_READ = 0x94;
+ft[0x94] = "XBee Sensor Read Indicator (AO=0) (0x94)";
+ft.NODE_IDENTIFICATION = 0x95;
+ft[0x95] = "Node Identification Indicator (AO=0) (0x95)";
+ft.REMOTE_COMMAND_RESPONSE = 0x97;
+ft[0x97] = "Remote Command Response (0x97)";
+ft.OTA_FIRMWARE_UPDATE_STATUS = 0xA0;
+ft[0xA0] = "Over-the-Air Firmware Update Status (0xA0)";
+ft.ROUTE_RECORD = 0xA1;
+ft[0xA1] = "Route Record Indicator (0xA1)";
+ft.MTO_ROUTE_REQUEST = 0xA3;
+ft[0xA3] = "Many-to-One Route Request Indicator (0xA3)";
+
+// Modem Status
+ms.HARDWARE_RESET = 0x00;
+ms[0x00] = "Hardware Reset";
+ms.WATCHDOG_RESET = 0x01;
+ms[0x01] = "Watchdog timer reset";
+ms.JOINED_NETWORK = 0x02;
+ms[0x02] = "Joined network";
+ms.DISASSOCIATED = 0x03;
+ms[0x03] = "Disassociated";
+ms.COORDINATOR_STARTED = 0x06;
+ms[0x06] = "Coordinator started";
+ms.SECURITY_KEY_UPDATED = 0x07;
+ms[0x07] = "Network security key was updated";
+ms.VOLTAGE_SUPPLY_LIMIT_EXCEEDED = 0x0D;
+ms[0x0D] = "Voltage supply limit exceeded";
+ms.CONFIGURATION_CHANGED_DURING_JOIN = 0x11;
+ms[0x11] = "Modem Configuration changed while join in progress";
+ms.STACK_ERROR = 0x80;
+ms[0x80] = "Stack Error";
+
+// Command Status
+coms.OK = 0x00;
+coms[0x00] = "OK (0x00)";
+coms.ERROR = 0x01;
+coms[0x01] = "ERROR (0x01)";
+coms.INVALID_COMMAND = 0x02;
+coms[0x02] = "Invalid Command (0x02)";
+coms.INVALID_PARAMETER = 0x03;
+coms[0x03] = "Invalid Command (0x03)";
+coms.REMOTE_CMD_TRANS_FAILURE = 0x04;
+coms[0x04] = "Remote Command Transmission Failed (0x04)";
+
+// Delivery Status
+dels.SUCCESS = 0x00;
+dels[0x00] = "Success (0x00)";
+dels.MAC_ACK_FALIURE = 0x01;
+dels[0x01] = "MAC ACK Failure (0x01)";
+dels.CA_FAILURE = 0x02;
+dels[0x02] = "CA Failure (0x02)";
+dels.INVALID_DESTINATION_ENDPOINT = 0x15;
+dels[0x15] = "Invalid destination endpoint (0x15)";
+dels.NETWORK_ACK_FAILURE = 0x21;
+dels[0x21] = "Network ACK Failure (0x21)";
+dels.NOT_JOINED_TO_NETWORK = 0x22;
+dels[0x22] = "Not Joined to Network (0x22)";
+dels.SELF_ADDRESSED = 0x23;
+dels[0x23] = "Self-addressed (0x23)";
+dels.ADDRESS_NOT_FOUND = 0x24;
+dels[0x24] = "Address Not Found (0x24)";
+dels.ROUTE_NOT_FOUND = 0x25;
+dels[0x25] = "Route Not Found (0x25)";
+dels.BROADCAST_SOURCE_FAILED = 0x26;
+dels[0x26] = "Broadcast source failed to hear a neighbor relay the message (0x26)";
+dels.INVALID_BINDING_TABLE_INDEX = 0x2B;
+dels[0x2B] = "Invalid binding table index (0x2B)";
+dels.RESOURCE_ERROR = 0x2C;
+dels[0x2C] = "Resource error lack of free buffers, timers, etc. (0x2C)";
+dels.ATTEMPTED_BROADCAST_WITH_APS_TRANS = 0x2D;
+dels[0x2D] = "Attempted broadcast with APS transmission (0x2D)";
+dels.ATTEMPTED_BROADCAST_WITH_APS_TRANS_EE0 = 0x2D;
+dels[0x2E] = "Attempted unicast with APS transmission, but EE=0 (0x2E)";
+dels.RESOURCE_ERROR_B = 0x32;
+dels[0x32] = "Resource error lack of free buffers, timers, etc. (0x32)";
+dels.DATA_PAYLOAD_TOO_LARGE = 0x74;
+dels[0x74] = "Data payload too large (0x74)";
+dels.INDIRECT_MESSAGE_UNREQUESTED = 0x75;
+dels[0x75] = "Indirect message unrequested (0x75)";
+
+// Discovery Status
+diss.NO_DISCOVERY_OVERHEAD = 0x00;
+diss[0x00] = "No Discovery Overhead (0x00)";
+diss.ADDRESS_DISCOVERY = 0x01;
+diss[0x01] = "Address Discovery (0x01)";
+diss.ROUTE_DISCOVERY = 0x02;
+diss[0x02] = "Route Discovery (0x02)";
+diss.ADDRESS_AND_ROUTE_DISCOVERY = 0x03;
+diss[0x03] = "Address and Route (0x03)";
+diss.EXTENDED_TIMEOUT_DISCOVERY = 0x40;
+diss[0x40] = "Extended Timeout Discovery (0x40)";
+
+// Receive Options
+ro.PACKET_ACKNOWLEDGED = 0x01;
+ro[0x01] = "Packet Acknowledged (0x01)";
+ro.PACKET_WAS_BROADCAST = 0x02;
+ro[0x02] = "Packet was a broadcast packet (0x02)";
+ro.PACKET_ENCRYPTED = 0x20;
+ro[0x20] = "Packet encrypted with APS encryption (0x20)";
+ro.PACKET_SENT_FROM_END_DEVICE = 0x40;
+ro[0x40] = "Packet was sent from an end device (if known) (0x40)";
+
+
+
+//
+// Digital Channel Mask/Pins
+//
+// Map mask to name
+dc.MASK[0] = ["DIO0", "AD0"];
+dc.MASK[1] = ["DIO1", "AD1"];
+dc.MASK[2] = ["DIO2", "AD2"];
+dc.MASK[3] = ["DIO3", "AD3"];
+dc.MASK[4] = ["DIO4"];
+dc.MASK[5] = ["DIO5", "ASSOCIATE"];
+dc.MASK[6] = ["DIO6", "RTS"];
+dc.MASK[7] = ["DIO7", "CTS"];
+dc.MASK[10] = ["DIO10", "RSSI"];
+dc.MASK[11] = ["DIO11", "PWM"];
+dc.MASK[12] = ["DIO12", "CD"];
+// Map pin/name to mask
+ac.PIN[20] = dc.DIO0 = dc.AD0 = 0;
+ac.PIN[19] = dc.DIO1 = dc.AD1 = 1;
+ac.PIN[18] = dc.DIO2 = dc.AD2 = 2;
+ac.PIN[17] = dc.DIO3 = dc.AD3 = 3;
+ac.PIN[11] = dc.DIO4 = 4;
+ac.PIN[15] = dc.DIO5 = dc.ASSOCIATE = 5;
+ac.PIN[16] = dc.DIO6 = dc.RTS = 6;
+ac.PIN[12] = dc.DIO7 = dc.CTS = 7;
+ac.PIN[6] = dc.DIO10 = dc.RSSI = 10;
+ac.PIN[7] = dc.DIO11 = dc.PWM = 11;
+ac.PIN[4] = dc.DIO12 = dc.CD = 12;
+
+//
+// Analog Channel Mask/Pins
+//
+// Map mask to name
+ac.MASK[0] = ["AD0", "DIO0" ];
+ac.MASK[1] = ["AD1", "DIO1" ];
+ac.MASK[2] = ["AD2", "DIO2" ];
+ac.MASK[3] = ["AD3", "DIO3" ];
+ac.MASK[7] = ["SUPPLY"];
+// map pin/name to mask
+ac.PIN[20] = ac.AD0 = ac.DIO0 = 0;
+ac.PIN[19] = ac.AD1 = ac.DIO1 = 1;
+ac.PIN[18] = ac.AD2 = ac.AD3 = 3;
+ac.PIN[17] = ac.SUPPLY = 7; // 17 True?
+
+
+//
+// Pullup-enable Mask/Pins
+//
+// Map mask to name
+pr.MASK[0] = ["DIO4"];
+pr.MASK[1] = ["DIO3", "AD3"];
+pr.MASK[2] = ["DIO2", "AD2"];
+pr.MASK[3] = ["DIO1", "AD1"];
+pr.MASK[4] = ["DIO0", "AD0"];
+pr.MASK[5] = ["DIO6", "RTS"];
+pr.MASK[6] = ["DIO8", "DTR", "SLEEP_REQUEST"];
+pr.MASK[7] = ["DIN", "CONFIG"];
+pr.MASK[8] = ["DIO5", "ASSOCIATE"];
+pr.MASK[9] = ["DIO9", "ON"];
+pr.MASK[10] = ["DIO12"];
+pr.MASK[11] = ["DIO10", "RSSI", "PWM0"];
+pr.MASK[12] = ["DIO11", "PWM1"];
+pr.MASK[13] = ["DIO7", "CTS"];
+// Map pin/name to maks
+pr.PIN[11] = pr.DIO4 = 0;
+pr.PIN[17] = pr.AD3 = pr.DIO3 = 1;
+pr.PIN[18] = pr.AD2 = pr.DIO2 = 2;
+pr.PIN[19] = pr.AD1 = pr.DIO1 = 3;
+pr.PIN[20] = pr.AD0 = pr.DIO0 = 4;
+pr.PIN[16] = pr.RTS = pr.DIO6 = 5;
+pr.PIN[9] = pr.DIO8 = pr.DTR = pr.SLEEP_REQUEST = 6;
+pr.PIN[3] = pr.DIN = pr.CONFIG = 7;
+pr.PIN[15] = pr.ASSOCIATE = pr.DIO5 = 8;
+pr.PIN[13] = pr.ON = pr.SLEEP = pr.DIO9 = 9;
+pr.PIN[4] = pr.DIO12 = 10;
+pr.PIN[6] = pr.PWM0 = pr.RSSI = pr.DIO10 = 11;
+pr.PIN[7] = pr.PWM1 = pr.DIO11 = 12;
+pr.PIN[12] = pr.CTS = pr.DIO7 = 13;
+
+
+//
+// Change Reporting Mask/Pins
+//
+// Map mask to name
+ic.MASK[0] = ["DIO0"];
+ic.MASK[1] = ["DIO1"];
+ic.MASK[2] = ["DIO2"];
+ic.MASK[3] = ["DIO3"];
+ic.MASK[4] = ["DIO4"];
+ic.MASK[5] = ["DIO5"];
+ic.MASK[6] = ["DIO6"];
+ic.MASK[7] = ["DIO7"];
+ic.MASK[8] = ["DIO8"];
+ic.MASK[9] = ["DIO9"];
+ic.MASK[10] = ["DIO10"];
+ic.MASK[11] = ["DIO11"];
+// Map pin/name to mask
+ic.PIN[20] = ic.DIO0 = 0;
+ic.PIN[19] = ic.DIO1 = 1;
+ic.PIN[18] = ic.DIO2 = 2;
+ic.PIN[17] = ic.DIO3 = 3;
+ic.PIN[11] = ic.DIO4 = 4;
+ic.PIN[15] = ic.DIO5 = 5;
+ic.PIN[16] = ic.DIO6 = 6;
+ic.PIN[12] = ic.DIO7 = 7;
+ic.PIN[9] = ic.DIO8 = 8;
+ic.PIN[13] = ic.DIO9 = 9;
+ic.PIN[6] = ic.DIO10 = 10;
+ic.PIN[7] = ic.DIO11 = 11;
+
+
+//
+// Pin Modes
+//
+pm.P2 = pm.P1 = {
+ UNMONITORED_INPUT: 0x00,
+ DIGITAL_INPUT: 0x03,
+ DIGITAL_OUTPUT_LOW: 0x04,
+ DIGITAL_OUTPUT_HIGH: 0x05
+}
+
+pm.P0 = {
+ DISABLED: 0x00,
+ RSSI_PWM: 0x01,
+ DIGITAL_INPUT: 0x03,
+ DIGITAL_OUTPUT_LOW: 0x04,
+ DIGITAL_OUTPUT_HIGH: 0x05
+};
+
+pm.D4 = {
+ DISABLED: 0x00,
+ DIGITAL_INPUT: 0x03,
+ DIGITAL_OUTPUT_LOW: 0x04,
+ DIGITAL_OUTPUT_HIGH: 0x05
+};
+
+pm.D7 = {
+ DISABLED: 0x00,
+ CTS_FLOW_CTRL: 0x01,
+ DIGITAL_INPUT: 0x03,
+ DIGITAL_OUTPUT_LOW: 0x04,
+ DIGITAL_OUTPUT_HIGH: 0x05,
+ RS485_TX_LOW: 0x06,
+ RS485_TX_HIGH: 0x07
+};
+
+pm.D5 = {
+ DISABLED: 0x00,
+ ASSOC_LED: 0x01,
+ DIGITAL_INPUT: 0x03,
+ DIGITAL_OUTPUT_LOW: 0x04,
+ DIGITAL_OUTPUT_HIGH: 0x05
+};
+
+pm.D6 = {
+ DISABLED: 0x00,
+ RTS_FLOW_CTRL: 0x01,
+ DIGITAL_INPUT: 0x03,
+ DIGITAL_OUTPUT_LOW: 0x04,
+ DIGITAL_OUTPUT_HIGH: 0x05
+};
+
+pm.D0 = pm.D1 = pm.D2 = pm.D3 = {
+ DISABLED: 0x00,
+ NODE_ID_ENABLED: 0x01, // Only valid for D0!
+ ANALOG_INPUT: 0x02,
+ DIGITAL_INPUT: 0x03,
+ DIGITAL_OUTPUT_LOW: 0x04,
+ DIGITAL_OUTPUT_HIGH: 0x05
+};
+
+for (var pin in pm) {
+ for (var key in pm[pin]) {
+ pm[pin][pm[pin][key]] = key;
+ }
+}
+
+pc.PIN[6] = pc.PWM0 = pc.DIO10 = pc.RSSIM = "P0";
+pc.PIN[7] = pc.DIO11 = pc.PWM1 = "P1";
+pc.PIN[4] = pc.DIO12 = "P2";
+pc.PIN[12] = pc.DIO7 = pc.CTS = "D7";
+pc.PIN[16] = pc.DIO6 = "D6";
+pc.PIN[20] = pc.AD0 = pc.DIO0 = "D0";
+pc.PIN[19] = pc.AD1 = pc.DIO1 = "D1";
+pc.PIN[18] = pc.AD2 = pc.DIO2 = "D2";
+pc.PIN[17] = pc.AD3 = pc.DIO3 = "D3";
+pc.PIN[11] = pc.DIO4 = "D4";
+pc.PIN[15] = pc.DIO5 = pc.ASSOC = "D5";
+
diff --git a/lib/xbee-api.js b/lib/xbee-api.js
new file mode 100644
index 0000000..a156d10
--- /dev/null
+++ b/lib/xbee-api.js
@@ -0,0 +1,566 @@
+var Buffer = require('buffer').Buffer;
+var util = require('util');
+
+var C = exports.Constants = require('./constants.js');
+exports = module.exports;
+
+var tools = exports.tools = {};
+
+
+// Converts a single decimal value to a Hex string, padding with leading zeros as required.
+// e.g. 100 would become '64' and 8 would become '08'
+// params d - decimal value to be converted
+// padding - maximum number of leading zeros to add to the left side of the string
+tools.dec2Hex = function(d, padding) {
+ var hex = Number(d).toString(16);
+ padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;
+
+ while (hex.length < padding) {
+ hex = "0" + hex;
+ }
+
+ return hex;
+}
+
+// Converts a byte array to a Hex string.
+// e.g. The 8 byte XBee 64 bit address [0x00, 0x13, 0xa2, 0x00, 0x40, 0x8b, 0x94, 0x37] would become '0013a200408b9437'
+// params a - byte array to be converted
+tools.bArr2HexStr = function(a) {
+ var s = '';
+ for(i in a) {
+ s += tools.dec2Hex(a[i]);
+ }
+ return s;
+}
+
+tools.bArr2Str = function(a) {
+ var s = '';
+ for(i in a) {
+ s += String.fromCharCode(a[i]);
+ }
+ return s;
+}
+
+// Converts a byte array like [3, 21] into a decimal value.
+// e.g. [3,21] --> 3 * 256 + 21 = 789
+// params a - byte array to convert
+tools.bArr2Dec = function(a) {
+ var r = 0;
+ for (var i = 0; i < a.length; i++) {
+ var power = a.length - i - 1;
+ r += a[i] * Math.pow(256,power);
+ }
+ return r
+}
+
+// Converts a decimal number to into an array of 2 byte values.
+// params a - decimal number to convert
+// m - length of array to return
+tools.dec2bArr = function(a, m) {
+ var r = [];
+ while (a > 0 || r.length < m) {
+ r.unshift(a & 0xff);
+ a = a >> 8;
+ }
+ return r;
+}
+
+// Converts a Hex string (typically an XBee address) into an array of 2 byte values.
+// e.g. The XBee 64 bit address '0013a200408b9437' would become [0x00, 0x13, 0xa2, 0x00, 0x40, 0x8b, 0x94, 0x37]
+// params str - Hex string to be converted, should contain hex characters pairs
+tools.hexStr2bArr = function(str) {
+ return str.match(/../g).map( function(chars){ return parseInt(chars,16) });
+}
+
+
+// module-level variable for storing a frameId.
+// Gets incremented by 1 each time it's used, so that you can
+// tell which responses relate to which XBee commands
+var frameId = 0x30;
+
+// use API mode 2 (escape special chars) by default
+exports.api_mode = 2;
+
+function incrementFrameId() {
+ frameId++;
+ frameId %= 255;
+ if (frameId == 0) frameId = 1; // 0x00 means: no response expected
+ return frameId;
+}
+
+// constructor for an outgoing Packet.
+var Packet = function() {
+ this.frameId = incrementFrameId();
+};
+
+// call getBytes to get a JS array of byte values, ready to send down the serial port
+Packet.prototype.getBytes = function() {
+ // build a JS array to hold the bytes
+ var packetdata = [C.START_BYTE];
+
+ // calculate the length bytes. First, get the entire payload by calling the internal function
+ var payload = this.getPayload();
+
+ // least significant length byte is easy
+ var len_lsb = payload.length % 256;
+
+ // if payload length is greater than 255, have to calculate the more significant byte...
+ if (payload.length > 255) {
+ var len_msb = payload.length >>> 8;
+ } else {
+ //...otherwise the MSB is zero
+ var len_msb = 0;
+ }
+
+ // add the length bytes to our growing packet array
+ packetdata.push(len_msb);
+ packetdata.push(len_lsb);
+
+ // now calculate checksum, meanwhile pushing each byte from the payload onto the packet array
+ var running_total = 0;
+
+ for(var j = 0; j < payload.length; j++) {
+ packetdata.push(payload[j]);
+ running_total += payload[j];
+ }
+
+ checksum = 255 - (running_total % 256);
+
+ // finally append the checksum byte and return the packet as a JS array
+ packetdata.push(checksum);
+
+ // Escape characters that need to be escaped (if using API mode 2 (the default)).
+ // could be shorter:
+ var res = [packetdata[0]];
+ for (var p = 1; p 0 && b == C.ESCAPE) {
+ escape_next = true;
+ continue;
+ }
+
+ if (escape_next) {
+ b = 0x20 ^ b;
+ escape_next = false;
+ escaped = true;
+ }
+
+ packpos += 1;
+
+ // Detect start of packet, ONLY if this byte wasn't an escaped 0x7E (C.START_BYTE). The start
+ // delimeter will only be escaped if it is within the body of a packet, thus we shouldn't
+ // start a new packet if this value was escaped.
+ if (b == C.START_BYTE && !escaped) {
+ packpos = 0;
+ packlen = 0;
+ running_total = 0;
+ checksum = -1;
+ packet = [];
+ escape_next = false;
+ }
+
+ escaped = false;
+
+ if (packpos == 1) packlen += b << 8; // most significant bit of the length
+ if (packpos == 2) packlen += b; // least significant bit of the length
+ if ((packlen > 0) && (packpos > 2)) {
+ if (packet.length < packlen) {
+ packet.push(b);
+ running_total += b;
+ } else {
+ checksum = b;
+ }
+ }
+
+
+ // Packet is complete. Parse & Emit
+ if ((packlen > 0) && (packet.length == packlen) && (packpos == packlen + 3)) {
+ // There will still be a checksum byte. Currently this is ignored
+ if (!checksum === 255 - (running_total % 256)) {
+ console.log("CHECKSUM_MISMATCH");
+ } else {
+ var parser = new PacketParser(packet)
+ var json = parser.parse();
+ //console.log("FRAME: %s", C.FRAME_TYPE[json.ft]);
+ if (json.not_implemented) {
+ console.log("FRAME TYPE NOT IMPLEMENTED: %s", C.FRAME_TYPE[json.ft]);
+ } else {
+ var evt = json.ft;
+ if ([C.FRAME_TYPE.ZIGBEE_TRANSMIT_STATUS,
+ C.FRAME_TYPE.REMOTE_COMMAND_RESPONSE,
+ C.FRAME_TYPE.AT_COMMAND_RESPONSE].indexOf(json.ft) >= 0) {
+ evt += C.EVT_SEP+json.frameId;
+ }
+ else if ( C.FRAME_TYPE.ZIGBEE_EXPLICIT_RX == json.ft && C.RECEIVE_OPTIONS.PACKET_ACKNOWLEDGED == json.receiveOptions ) {
+ var txn = json.profileId == 0 ? json.rawData[0] : json.rawData[1];
+ emitter.emit(evt, json); // emit C.FRAME_TYPE.ZIGBEE_EXPLICIT_RX event without txn. Some devices use PACKET_ACKNOWLEDGED when sending events for bound endpoints
+ evt += C.EVT_SEP+txn;
+ }
+ //console.log(">>> "+C.FRAME_TYPE[json.ft]+" "+evt+" "+json.rawData);
+ emitter.emit(evt, json);
+ }
+ }
+ }
+ }
+ };
+}
+
+// Packet Parser Class. Used to parse packets if they are known
+var PacketParser = function(p) {
+ this.json = {
+ ft: p.splice(0,1)[0],
+ }
+
+ // Used as pointer to the object data is parsed into
+ this.write = this.json;
+ this.payload = p;
+}
+
+PacketParser.prototype.parse = function() {
+ if (false) { // TODO: Debug option
+ this.json.desc = C.FRAME_TYPES[this.json.ft];
+ }
+
+ this.frames[this.json.ft].parse(this);
+
+ return this.json;
+}
+
+PacketParser.prototype.readAddr = function(name, length) {
+ var dec = this.payload.splice(0, length);
+ this.write[name] = { dec: dec, hex: tools.bArr2HexStr(dec) }
+ return this;
+}
+
+PacketParser.prototype.readByte = function(name, length) {
+ if (typeof length === 'number')
+ this.write[name] = this.payload.splice(0,length);
+ else this.write[name] = this.payload.splice(0,1)[0];
+ return this;
+}
+
+PacketParser.prototype.readInt = function(name, length) {
+ var bytes = [];
+ if (typeof length === 'number')
+ bytes = this.payload.splice(0,length);
+ else bytes = this.payload.splice(0,1)[0];
+ var val = 0;
+ for (var i = 0; i < bytes.length; ++i) {
+ val = val << 8;
+ val += bytes[i];
+ }
+ this.write[name] = val;
+
+ return this;
+}
+
+PacketParser.prototype.readAddr64 = function(name) {
+ return this.readAddr(name, 8);
+}
+
+PacketParser.prototype.readAddr16 = function(name) {
+ return this.readAddr(name, 2);
+}
+
+PacketParser.prototype.readString = function(name, length) {
+ this.write[name] = "";
+ if (typeof length === 'number') {
+ for (var i = 0; i < length; i++)
+ this.write[name] += String.fromCharCode(this.payload.splice(0,1)[0]);
+ } else {
+ while(this.payload[0] != 0x00) {
+ this.write[name] += String.fromCharCode(this.payload.splice(0,1)[0]);
+ }
+ this.payload.splice(0,1); // Read 0x00 away
+ }
+ return this;
+}
+
+PacketParser.prototype.collectPayload = function(name) {
+ this.write[name] = this.payload.splice(0);
+ return this;
+}
+
+var frames = PacketParser.prototype.frames = {};
+
+frames[C.FRAME_TYPE.NODE_IDENTIFICATION] = {
+ parse: function(parser) {
+ parser
+ .readAddr64('sender64')
+ .readAddr16('sender16')
+ .readByte('recieveOptions')
+ .collectPayload('nodeIdentificationPayload');
+ }
+};
+
+frames[C.FRAME_TYPE.ZIGBEE_TRANSMIT_STATUS] = {
+ parse: function(parser) {
+ parser
+ .readByte('frameId')
+ .readAddr16('remote16')
+ .readByte('transmitRetryCount')
+ .readByte('deliveryStatus')
+ .readByte('discoveryStatus')
+ }
+};
+
+frames[C.FRAME_TYPE.AT_COMMAND_RESPONSE] = {
+ parse: function(parser) {
+ parser
+ .readByte('frameId')
+ .readString('command', 2)
+ .readByte('commandStatus')
+ .collectPayload('commandData');
+ }
+};
+
+frames[C.FRAME_TYPE.REMOTE_COMMAND_RESPONSE] = {
+ parse: function(parser) {
+ parser
+ .readByte('frameId')
+ .readAddr16('remote16')
+ .readAddr64('remote64')
+ .readString('command', 2)
+ .readByte('commandStatus')
+ .collectPayload('commandData');
+ }
+};
+
+frames[C.FRAME_TYPE.ZIGBEE_RECEIVE_PACKET] = {
+ parse: function(parser) {
+ parser
+ .readAddr64('remote64')
+ .readAddr16('remote16')
+ .readByte('receiveOptions')
+ .collectPayload('rawData');
+ }
+};
+
+frames[C.FRAME_TYPE.MODEM_STATUS] = {
+ parse: function(parser) {
+ parser
+ .readByte('status');
+ }
+}
+
+frames[C.FRAME_TYPE.ZIGBEE_IO_DATA_SAMPLE_RX] = {
+ parse: function(parser) {
+ parser
+ .readAddr64('remote64')
+ .readAddr16('remote16')
+ .readByte('receiveOptions')
+ .collectPayload('ioSample');
+ }
+};
+
+
+// Added by Warren
+frames[C.FRAME_TYPE.ZIGBEE_EXPLICIT_RX] = {
+ parse: function(parser) {
+ parser
+ .readAddr64('remote64')
+ .readAddr16('remote16')
+ .readByte('sourceEndpoint')
+ .readByte('destEndpoint')
+ .readInt('clusterId', 2)
+ .readInt('profileId', 2)
+ .readByte('receiveOptions')
+ .collectPayload('rawData');
+ }
+};
+
+// Added by Warren
+frames[C.FRAME_TYPE.ROUTE_RECORD] = {
+ parse: function(parser) {
+ parser
+ .readAddr64('remote64')
+ .readAddr16('remote16')
+ .readByte('receiveOptions')
+ .readByte('numAddresses') // number of addresses in the source route
+ .collectPayload('rawData'); // each address is a pair of bytes
+ }
+};
+
+
+// Unsupported Frame Types
+for (key in C.FRAME_TYPE) {
+ var val = C.FRAME_TYPE[key];
+ if (typeof val === 'number') {
+ if (!frames[val]) {
+ frames[val] = {
+ parse: function(parser) {
+ parser.json.not_implemented = true;
+ }
+ }
+ } else {
+ //
+ }
+ }
+}
diff --git a/package.json b/package.json
index 5467759..e31eb9a 100644
--- a/package.json
+++ b/package.json
@@ -1,13 +1,45 @@
-{ "name" : "xbee",
- "version" : "0.0.4",
- "description" : "Node talks to xbee radios through serialport",
- "author": "Richard Morrison ",
- "main": "./xbee",
- "keywords": ["xbee", "serialport", "robots", "sensors", "automation", "control"],
- "homepage": "https://github.com/mozz100/node-xbee",
- "repository": {
- "type" : "git",
- "url" : "git://github.com/mozz100/node-xbee.git"
- }
-
+ {
+ "name": "svd-xbee",
+ "version": "0.3.6",
+ "description": "A more high level fork of Richard Morrison's node-xbee",
+ "author": {
+ "name": "Jan Kolkmeier",
+ "email": "jankolkmeier@gmail.com"
+ },
+ "maintainers": "Jan Kolkmeier ",
+ "contributors": [
+ {
+ "name": "Jan Kolkmeier",
+ "email": "jankolkmeier@gmail.com"
+ },
+ {
+ "name": "Richard Morrison",
+ "email": "richard@rmorrison.net"
+ },
+ {
+ "name": "msealand"
+ }
+ ],
+ "main": "./index.js",
+ "keywords": [
+ "xbee",
+ "serialport",
+ "robots",
+ "sensors",
+ "automation",
+ "control"
+ ],
+ "homepage": "https://github.com/jouz/svd-xbee",
+ "dependencies": {
+ "serialport": "1.1.x",
+ "async": "0.1.x"
+ },
+ "engines": {
+ "node": "0.6.x | 0.8.x | 0.10.x"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/lowi-yeah/svd-xbee"
+ },
+ "readmeFilename": "README.md"
}
diff --git a/xbee.js b/xbee.js
deleted file mode 100644
index 8908e60..0000000
--- a/xbee.js
+++ /dev/null
@@ -1,420 +0,0 @@
-var Buffer = require('buffer').Buffer;
-var sys = require('sys');
-
-function decimalToHex(d, padding) {
- var hex = Number(d).toString(16);
- padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;
-
- while (hex.length < padding) {
- hex = "0" + hex;
- }
-
- return hex;
-}
-
-function byteArrayToHexString(a) {
- var s = '';
- for(var i = 0; i < a.length; i++) {
- s += decimalToHex(a[i]);
- }
- return s;
-}
-
-// module-level variable for storing a frameId.
-// Gets incremented by 1 each time it's used, so that you can
-// tell which responses relate to which XBee commands
-var frameId = 0x00;
-
-function incrementFrameId() {
- // increment frameId and make sure it's <=255
- frameId += 1;
- frameId %= 256;
- return frameId;
-}
-
-// Define some useful XBee constants
-exports.START_BYTE = 0x7e; // start of every XBee packet
-
-// Frame Types
-exports.FT_DATA_SAMPLE_RX = 0x92; // I/O data sample packet received
-exports.FT_AT_COMMAND = 0x08; // AT command (local)
-exports.FT_AT_RESPONSE = 0x88; // AT response (local)
-exports.FT_REMOTE_AT_COMMAND = 0x17; // AT command (to remote radio)
-exports.FT_REMOTE_AT_RESPONSE = 0x97; // AT response (from remote radio)
-exports.FT_TRANSMIT_RF_DATA = 0x10; // Transmit RF data
-exports.FT_TRANSMIT_ACKNOWLEDGED = 0x8b; // TX response
-exports.FT_RECEIVE_RF_DATA = 0x90; // RX received
-
-// Bitmasks for I/O pins
-var digiPinsByte1 = {
- D10: 4,
- D11: 8,
- D12: 16
-};
-var digiPinsByte2 = {
- D0: 1,
- D1: 2,
- D2: 4,
- D3: 8,
- D4: 16,
- D5: 32,
- D6: 64,
- D7: 128
-};
-var analogPins = {
- A0: 1,
- A1: 2,
- A2: 4,
- A3: 8,
- supply: 128
-};
-
-// constructor for an outgoing Packet.
-var Packet = function() {
- this.frameId = incrementFrameId();
-};
-
-// call getBytes to get a JS array of byte values, ready to send down the serial port
-Packet.prototype.getBytes = function() {
- // build a JS array to hold the bytes
- var packetdata = [exports.START_BYTE];
-
- // calculate the length bytes. First, get the entire payload by calling the internal function
- var payload = this.getPayload();
-
- // least significant length byte is easy
- var len_lsb = payload.length % 256;
-
- // if payload length is greater than 255, have to calculate the more significant byte...
- if (payload.length > 255) {
- var len_msb = payload.length >>> 8;
- } else {
- //...otherwise the MSB is zero
- var len_msb = 0;
- }
-
- // add the length bytes to our growing packet array
- packetdata.push(len_msb);
- packetdata.push(len_lsb);
-
- // now calculate checksum, meanwhile pushing each byte from the payload onto the packet array
- var running_total = 0;
-
- for(var j = 0; j < payload.length; j++) {
- packetdata.push(payload[j]);
- running_total += payload[j];
- }
-
- checksum = 255 - (running_total % 256);
-
- // finally append the checksum byte and return the packet as a JS array
- packetdata.push(checksum);
-
- return packetdata;
-}
-
-Packet.prototype.getPayload = function() {
- // this function is overridden by subclasses
- return this.payload;
-}
-
-exports.Packet = Packet;
-
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-// ~~~~~~~~~~~~~~~~~~~~ OUTGOING XBEE PACKETS ~~~~~~~~~~~~~~~~~~~~
-// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-// ATCommand is for setting/reading AT registers on the local XBee node.
-var ATCommand = function() {
- this.frameId = incrementFrameId();
-};
-sys.inherits(ATCommand, Packet);
-
-ATCommand.prototype.setCommand = function(strCmd) {
- // take the ascii command and save it internally as byte values command0 and command1
- this.command0 = strCmd.charCodeAt(0);
- this.command1 = strCmd.charCodeAt(1);
-}
-
-ATCommand.prototype.getPayload = function() {
- // Returns a JS array of byte values
- // which form the payload of an AT command packet.
- // Uses command0, command1 and commandParameter to build the payload.
-
- // begin with the frame type and frame ID
- var payload = [exports.FT_AT_COMMAND, this.frameId];
-
- // add two bytes to identify which AT command is being used
- payload.push(this.command0);
- payload.push(this.command1);
-
- // this.commandParameter can either be undefined (to query an AT register), or an array (to set an AT register)
- if (this.commandParameter) {
- for(var j=0; j 0) && (packpos > 2) && (packet.length < packlen)) {
- packet.push(b);
- }
-
- // emit the packet when it's fully built. packlen + 3 = position of final byte
- if ((packlen > 0) && (packet.length == packlen) && (packpos == packlen + 3)) {
- // translate the packet into a JS object before emitting it
- emitter.emit("data", packetToJS(packet));
- }
-
- // there will still be a checksum byte. Currently this is ignored
- if ((packlen > 0) && (packet.length == packlen) && (packpos > packlen + 3)) {
- // ignore checksum for now
- }
- }
- };
-}
-
-function packetToJS(packet) {
- // given an array of byte values, return a JS object representing the packet
- // the array of bytes excludes the start bit and the length bits (these are not collected by the serial parser funciton)
-
- // So, the first byte in the packet is the frame type identifier.
- if (packet[0]== exports.FT_AT_RESPONSE) {
- return {
- type: 'AT Response',
- frameId: packet[1],
- command: String.fromCharCode(packet[2]) + String.fromCharCode(packet[3]), // translate bytes back to ASCII
- commandStatus: (packet[4] == 0) ? 'OK' : packet[4],
- commandData: packet.slice(4),
- bytes: packet
- }
- } else if (packet[0] == exports.FT_REMOTE_AT_RESPONSE) {
- return {
- type: 'Remote AT Response',
- frameId: packet[1],
- remote64: {dec: packet.slice(2,10), hex: byteArrayToHexString(packet.slice(2,10))},
- remote16: {dec: packet.slice(10,12), hex: byteArrayToHexString(packet.slice(10,12))},
- command: String.fromCharCode(packet[12]) + String.fromCharCode(packet[13]),
- commandStatus: (packet[14] == 0) ? 'OK' : packet[14],
- commandData: packet.slice(15),
- bytes: packet
- }
- } else if(packet[0] == exports.FT_RECEIVE_RF_DATA) {
- p = {
- type: 'RF Data',
- remote64: {dec: packet.slice(1,9), hex: byteArrayToHexString(packet.slice(1,9))},
- remote16: {dec: packet.slice(9,11), hex: byteArrayToHexString(packet.slice(9,11))},
- receiveOptions: packet[11],
- raw_data: packet.slice(12),
- data: "",
- bytes: packet
- }
- // build ascii from raw_data
- for(i in p.raw_data) {
- p.data += String.fromCharCode(p.raw_data[i]);
- }
- return p
- } else if (packet[0] == exports.FT_DATA_SAMPLE_RX) {
- s = {
- type: 'Data Sample',
- remote64: {dec: packet.slice(1,9), hex: byteArrayToHexString(packet.slice(1,9))},
- remote16: {dec: packet.slice(9,11), hex: byteArrayToHexString(packet.slice(9,11))},
- receiveOptions: packet[11],
- numSamples: packet[12], // apparently always set to 1
- digitalChannelMask: packet.slice(13,15),
- analogChannelMask: packet[15],
- bytes: packet
- }
- // Bit more work to do on an I/O data sample.
- // First check s.digitalChannelMask - are there any digital samples?
- if (s.digitalChannelMask[0] + s.digitalChannelMask[1] > 0) {
- // digital channel mask indicates that digital samples are present, so they
- // are in the bytes 16 and 17.
- s.digitalSamples = packet.slice(16,18);
- // Now check whether any analog samples are present
- if (s.analogChannelMask > 0) {
- s.analogSamples = packet.slice(18);
- }
- } else {
- // no digital samples. There might still be analog samples...
- if (s.analogChannelMask > 0) {
- s.analogSamples = packet.slice(16);
- }
- }
-
- // translate digital samples into JS for easier handling
- s['samples'] = {}
-
- if (s.digitalChannelMask[0] + s.digitalChannelMask[1] > 0) { // if digital samples present,
- // run through the first bitmask for digital pins, i.e. digiPinsByte1
- for (x in digiPinsByte1) {
- // On first iteration, for example, x = 'D10', digiPinsByte1[x] = 4.
- // OK. So, is there a sample for this pin? Check the digital channel mask.
- if (s.digitalChannelMask[0] & digiPinsByte1[x]) {
- // There is a sample for this pin. So, AND the sample byte and the bitmask,
- // and turn the result into a boolean.
- // On the first iteration, for example, this sets s['D10'] = 1
- // if the bitwise AND of the first byte of the digital sample with 4 is > 0
- s['samples'][x] = ((s.digitalSamples[0] & digiPinsByte1[x]) > 0) ? 1 : 0;
- }
- }
- // do the same thing for the second load of digital inputs
- for (x in digiPinsByte2) {
- if (s.digitalChannelMask[1] & digiPinsByte2[x]) {
- s['samples'][x] = ((s.digitalSamples[1] & digiPinsByte2[x]) > 0) ? 1 : 0;
- }
- }
- }
-
- // Also translate analog samples into JS
- // The analog channel mask indicates which pins are enabled as analog channels.
- if (s.analogChannelMask > 0) {
- var sampleIndex = 0;
- for (x in analogPins) {
- // on first iteration, for example, x = 'A0', analogPins[x] = 1
- if (s.analogChannelMask & analogPins[x]) {
- s['samples'][x] = 256*s.analogSamples[sampleIndex*2]+s.analogSamples[1+sampleIndex*2];
- sampleIndex += 1;
- }
- }
- }
- return s;
- } else {
- // The first byte of the packet indicates it's an as-yet unknown frame type.
- // In this case, just return the bytes.
- return packet;
- }
-}