diff --git a/README.md b/README.md
index 53f2bb4..53111e3 100644
--- a/README.md
+++ b/README.md
@@ -1,29 +1,28 @@
-# node-nrf
-
-nRF24L01+ driver library for node.js on platforms like the [Raspberry Pi](http://en.wikipedia.org/wiki/Raspberry_Pi) and [others](http://tessel.io/).
+# nrf24
+nRF24L01+ driver library for node.js on platforms like the [Raspberry Pi](http://en.wikipedia.org/wiki/Raspberry_Pi) and [others](http://tessel.io/). The hardware documentation for this module can be found [here](https://github.com/tessel/hardware/blob/master/modules-overview.md#nrf24).
Making this inexpensive radio chip easy to use from node.js helps bridge the wider Internet with small/cheap "things" — other embedded devices like [Arduino](http://arduino.cc/), [Teensy](http://www.pjrc.com/teensy/), good ol'fashioned [AVR chips](https://www.sparkfun.com/products/11232), … — where the costs of WiFi/Bluetooth/Zigbee radios can quickly add up! This fulfills a critical dependency of my [Microstates](https://github.com/natevw/microstates) idea, for just one example.
-## See also?
+If you run into any issues you can ask for support on the [nrf24 Module Forums](http://forums.tessel.io/category/nrf24).
+###See Also
Not to be confused with [node-rf24](https://github.com/natevw/node-rf24) which was/is an unfinished (and broken by recent V8 and libuv changes) wrapper around the RasPi port of the C++ [RF24 library](https://github.com/stanleyseow/RF24).
-In contrast, *this* module is implemented in pure JavaScript on top of native [SPI bindings](https://github.com/natevw/pi-spi). It also provides a cleaner, node-friendly interface.
-
-
-## Installation
+In contrast, *this* module is implemented in pure JavaScript on top of native [SPI bindings](https://github.com/natevw/pi-spi). It provides a cleaner, node-friendly interface.
+###Installation
`npm install nrf`
-
-## Usage
-
+###Usage
[Streams](https://github.com/substack/stream-handbook#readme)!
-```
-var radio = require('nrf').connect(spiDev, cePin, irqPin);
-radio.channel(0x4c).dataRate('1Mbps').crcBytes(2).autoRetransmit({count:15, delay:4000});
-radio.begin(function () {
+```js
+var radio = require('nrf')
+ .channel(0x4c).dataRate('1Mbps')
+ .crcBytes(2).autoRetransmit({count:15, delay:4000})
+ .use(spiDev, cePin, irqPin);
+
+radio.on('ready', function () {
var rx = radio.openPipe('rx', 0xF0F0F0F0E1),
tx = radio.openPipe('tx', 0xF0F0F0F0D2);
rx.pipe(tx); // echo back everything
@@ -34,38 +33,104 @@ The nRF24L01+ radios provide "logic pipes" which can be used as node.js streams.
> **TBD**: expand this section ["non"-stream usage, pipe options, optional callbacks, buffering and splitting/joining streams from 32-byte chunks, etc.]
+###Example
+```js
+/*********************************************
+Tessel to tessel requires 2 nrf24 modules
+(and ideally two tessels) put one tessel+nrf
+on "ping" mode and another one on "pong" mode.
+*********************************************/
+
+var tessel = require('tessel'),
+ NRF24 = require("nrf"),
+ pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2],
+ role = 'pong'; // swap this to pong if you want to wait for receive
+
+var nrf = NRF24.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = 2476MHz
+ .transmitPower('PA_MAX') // set the transmit power to max
+ .dataRate('1Mbps')
+ .crcBytes(2) // 2 byte CRC
+ .autoRetransmit({count:15, delay:4000})
+ .use(tessel.port['A']);
+
+nrf._debug = false;
+
+nrf.on('ready', function () {
+ setTimeout(function(){
+ nrf.printDetails();
+ }, 5000);
+
+ if (role === 'ping') {
+ console.log("PING out");
+
+ var tx = nrf.openPipe('tx', pipes[0], {autoAck: false}), // transmit address F0F0F0F0D2
+ rx = nrf.openPipe('rx', pipes[1], {size: 4}); // receive address F0F0F0F0D2
+ tx.on('ready', function () {
+ var n = 0;
+ setInterval(function () {
+ var b = new Buffer(4); // set buff len of 8 for compat with maniac bug's RF24 lib
+ b.fill(0);
+ b.writeUInt32BE(n++);
+ console.log("Sending", n);
+ tx.write(b);
+ }, 5e3); // transmit every 5 seconds
+ });
+ rx.on('data', function (d) {
+ console.log("Got response back:", d);
+ });
+ } else {
+ console.log("PONG back");
+ var rx = nrf.openPipe('rx', pipes[0], {size: 4});
+ tx = nrf.openPipe('tx', pipes[1], {autoAck: false});
+ rx.on('data', function (d) {
+ console.log("Got data, will respond", d);
+ tx.write(d);
+ });
+ tx.on('error', function (e) {
+ console.warn("Error sending reply.", e);
+ });
+ }
+});
-## API
-
-### Initialization
-
-* `var radio = nrf.connect(spiDev, cePin, irqPin)` — Initialize a radio object using the given hardware pinouts. Under Linux, `spiDev` is a device like "/dev/spidev0.0" and must be accessible by your process's user. `cePin` and `irqPin` are GPIO port numbers (`irqPin` is optional but highly recommended — without it the library must resort to polling which is slower and more processor/power intensive) and these GPIO ports must also be accessible by your process's user. This does essentially no communication with (and no configuration of) the radio; use the configuration methods below and `radio.begin()` to set up.
-
-* `radio.reset(cb)` — Resets the transciever to its default settings and flushes its transmit/receive queues. Most of this (i.e. queue flushing and low-level settings) is done by `.begin()` and so calling reset is *not* necessary if the five transceiver configuration options below (channel/dataRate/transmitPower/crcBytes/autoRetransmit) are being written anyway.
-
-### Transceiver configuration
+// hold this process open
+process.ref();
+```
-* `radio.channel(num, cb)` — Set (or read, when no value is provided) the radio frequency channel. Callback is optional. This must be the same on all transceivers that wish to communicate. Default is `0x02`.
+###API
+####Initialization
+ # var radio = nrf.connect( spiDev, cePin, irqPin) Initialize a radio object using the given hardware pinouts\\. Under Linux, spiDev is a device like "/dev/spidev0\\.0" and must be accessible by your process's user\\. cePin and irqPin are GPIO port numbers (irqPin is optional but highly recommended — without it the library must resort to polling which is slower and more processor/power intensive) and these GPIO ports must also be accessible by your process's user\\. This does essentially no communication with (and no configuration of) the radio; use the configuration methods below and radio\\.begin()
+ to set up.
-* `radio.dataRate(rate, cb)` — Set (or read, when no value is provided) the channel data rate. Callback is optional. This must be the same on all transeivers that wish to communicate. Must be one of `['250kbps', '1Mbps','2Mbps']`. Default is `'2Mbps'`.
+ # radio.reset( cb) — Resets the transciever to its default settings and flushes its transmit/receive queues\\. Most of this (i\\.e\\. queue flushing and low\\-level settings) is done by \\.begin() and so calling reset is \\*not\\* necessary if the five transceiver configuration options below (channel/dataRate/transmitPower/crcBytes/autoRetransmit )
+ are being written anyway.
-* `radio.transmitPower(rate, cb)` — Set (or read, when no value is provided) the RF output power. Callback is optional. Must be one of `['PA_MIN', 'PA_LOW', 'PA_HIGH', 'PA_MAX']`. Default is `'PA_MAX'`.
+####Transceiver configuration
+ # radio.channel( num, cb) — Set (or read, when no value is provided )
+ the radio frequency channel. Callback is optional. This must be the same on all transceivers that wish to communicate. Default is 0x02.
-* `radio.crcBytes(numBytes, cb)` — Set (or read, when no rate is provided) the size of packet checksums. Callback is optional. This must be the same on all transeivers that wish to communicate. Choose `1` or `2` bytes, or `0` to disable CRC checksums. Default is `1`.
+ # radio.dataRate( rate, cb) Set (or read, when no value is provided )
+ the channel data rate. Callback is optional. This must be the same on all transeivers that wish to communicate. Must be one of ['250kbps', '1Mbps','2Mbps']. Default is '2Mbps'.
-* `radio.autoRetransmit(opts, cb)` — Set (or read, when no value is provided) the packet retransmission parameters. Callback is optional. Provide a dictionary with one or two keys: `delay` to set the retry spacing in microseconds (will be rounded to a multiple of 250µs) and `count` to set the maximum number of retries. (See the datasheet for the minimum delay necessary based on data rate and packet size.) Default is `{delay:250,count:3}`.
+ # radio.transmitPower( rate, cb) Set (or read, when no value is provided )
+ the RF output power. Callback is optional. Must be one of ['PA_MIN', 'PA_LOW', 'PA_HIGH', 'PA_MAX']. Default is 'PA_MAX'.
-### Sending/receiving
+ # radio.crcBytes( numBytes, cb) Set (or read, when no rate is provided )
+ the size of packet checksums. Callback is optional. This must be the same on all transeivers that wish to communicate. Choose 1 or 2 bytes, or 0 to disable CRC checksums. Default is 1.
-* `radio.begin(cb)` — Powers up the radio, configures its pipes, and prepares the library to handle actual payload transmission/receipt. Callback is optional, but if not provided you should not attempt to open pipes until the 'ready' event is emitted. (The configuration methods above may be called at any time before/after this method.)
+ # radio.autoRetransmit( opts, cb) Set (or read, when no value is provided) the packet retransmission parameters\\. Callback is optional\\. Provide a dictionary with one \*or\* two keys: delay to set the retry spacing in microseconds (will be rounded to a multiple of 250µs) and count to set the maximum number of retries. (See the datasheet for the minimum delay necessary based on data rate and packet size. )
+ Default is {delay`:250`,count:3}.
-* `radio.openPipe(mode, addr, opts)` — Returns a stream representing a "data pipe" on the radio. See pipe details section below.
+####Sending/receiving
+ # radio.begin( cb) Powers up the radio, configures its pipes, and prepares the library to handle actual payload transmission/receipt\\. Callback is optional, but if not provided you should not attempt to open pipes until the 'ready' event is emitted. (The configuration methods above may be called at any time before/after this method. )
-* `radio.end(cb)` — Closes all pipes and powers down the radio. Callback is optional.
+ # radio.openPipe( mode, addr, opts )
+ Returns a stream representing a "data pipe" on the radio. See pipe details section below.
-#### Pipe details
+ # radio.end( cb )
+ Closes all pipes and powers down the radio. Callback is optional.
-The nRF24 radios use "logical channels" for communications within a physical channel. Basically a pipe address is sent ahead of every data transmission on a particular channel (frequency); a receiver of the "same pipe" listens for this address and upon detecting a match attempts to process the data packet which follows. The transceiver hardware can be configured for automatic acknowlegdment/retransmission of received/unacknowleged packets (respectively). The `radio.openPipe(mode, addr, opts)` method returns a standard node.js Duplex stream interface wrapping these hardware features.
+#####Pipe details
+The nRF24 radios use "logical channels" for communications within a physical channel. Basically a pipe address is sent ahead of every data transmission on a particular channel (frequency); a receiver of the "same pipe" listens for this address and upon detecting a match attempts to process the data packet which follows. The transceiver hardware can be configured for automatic acknowlegdment/retransmission of received/unacknowleged packets (respectively).The `radio.openPipe(mode, addr, opts)` method returns a standard node.js Duplex stream interface wrapping these hardware features.
* The `mode` parameter to `radio.openPipe` must be `'tx'` or `'rx'` and determines the primary behavior of the radio data pipe. Because acknowlegement packets can include arbitary payloads, a data pipe of either mode can be used for *both* receiving and sending. The main difference is that an `'rx'` pipe is always listening, but can only send data in acknowlegement to incoming packets; conversely [inversely? contrapositively?] a `'tx'` pipe can only receive a single packet of data (sent within a brief window) after each of its own successful transmissions. (See `options` documentation below.)
@@ -80,20 +145,20 @@ The nRF24 radios use "logical channels" for communications within a physical cha
Note that, while you can `.pipe()` to these streams as any other, `node-nrf` will not split data into packets for you, and will get upset if passed more than 32 bytes of data! Make sure all your `write`s to the stream fit the necessary MTU; **TBD** I imagine the common "transfer an arbitrarily large stream of data" case could be handled by a simple [object mode?] transform stream, find or provide a recommended module.
-### Low-level methods
-
+####Low-level methods
Effective use of these methods requires proficiency with both the library internals and the transceiver's data sheet documentation. They are exposed only because What's The Worst That Could Happen™.
-* `radio.powerUp(boolState, cb)` — Set (or read, when no value is provided) the power status of the radio. Callback is optional. When the power is off the transceiver hardware uses little power, but takes a little while longer to enter any useful mode. This is set `true` by `radio.begin()` and `false` by `radio.end()` so it is typically not necessary when using the main API. Default is `false`.
+ # radio.powerUp( boolState, cb) Set (or read, when no value is provided) the power status of the radio\\. Callback is optional\\. When the power is off the transceiver hardware uses little power, but takes a little while longer to enter any useful mode\\. This is set true by radio\\.begin() and false by radio\\.end()
+ so it is typically not necessary when using the main API. Default is false.
-* `radio.addressWidth(width, cb)` — Set (or read, when no value is provided) the receiver address width used. Callback is optional. The address width is determined automatically whenever `radio.openPipe()` is used so it is not normally necessary to call this when using the main API. Choose `3`, `4` or `5` bytes (this library also supports setting `2`, at your own risk). Default is `5`.
+ # radio.addressWidth( width, cb) Set (or read, when no value is provided) the receiver address width used\\. Callback is optional\\. The address width is determined automatically whenever radio\\.openPipe() is used so it is not normally necessary to call this when using the main API\\. Choose 3, 4 or 5 bytes (this library also supports setting 2, at your own risk )
+. Default is 5.
> **TBD**: `radio.execCommand(cmd,data,cb)` / `radio.getStates(list,cb)` / `radio.setStates(vals, cb)` / `radio.setCE(state, block)` / `radio.pulseCE(block)` / `radio.reset(states, cb)` / `radio.blockMicroseconds(us)` / `radio.readPayload(opts, cb)` / `radio.sendPayload(data, opts, cb)`
-## Troubleshooting
-
-### node-nrf (or pi-spi) not working after using C++ RF24 library
+###Troubleshooting
+####node-nrf (or pi-spi) not working after using C++ RF24 library
The C++ [RF24 library for RasPi](https://github.com/stanleyseow/RF24/) toggles the SPI chip select pin manually, which breaks the Linux SPI driver. Reload it to fix, before using `node-nrf`:
@@ -102,8 +167,10 @@ The C++ [RF24 library for RasPi](https://github.com/stanleyseow/RF24/) toggles t
See [this comment](https://github.com/natevw/node-nrf/issues/1#issuecomment-32395546) for a bit more discussion.
-### TBD: gather more advice (or link to a wiki page?)
-
-## License
+###Further Examples
+* [NRF Ping Pair](https://github.com/tessel/rf-nrf24/blob/master/examples/RF24-pingpair.js). These are settings for Tessel to work out of the box with
+ maniacbug's [RF24 pingpair example](https://github.com/maniacbug/RF24/blob/07a4bcf425d91c99105dbdbad0226296c7cd3a93/examples/pingpair/pingpair.pde)
+ Useful for bridging an Arduino + nRF24 to Tessel + nRF24.
-> **TBD**: [BSD-2-Clause template]
+###License
+MIT or Apache 2.0, at your option
diff --git a/examples/RF24-pingpair.js b/examples/RF24-pingpair.js
new file mode 100644
index 0000000..1edd2ee
--- /dev/null
+++ b/examples/RF24-pingpair.js
@@ -0,0 +1,73 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+/*********************************************
+These are settings for Tessel to work out of
+the box with maniacbug's RF24 pingpair example
+(https://github.com/maniacbug/RF24/blob/
+07a4bcf425d91c99105dbdbad0226296c7cd3a93/
+examples/pingpair/pingpair.pde) Useful for
+bridging an Arduino + nRF24 to Tessel + nRF24.
+*********************************************/
+
+var tessel = require('tessel'),
+ NRF24 = require("../"),
+ pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2],
+ role = 'pong'; // swap this to pong if you want to wait for receive
+
+var nrf = NRF24.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = 2476MHz
+ .transmitPower('PA_MAX') // set the transmit power to max
+ .dataRate('1Mbps')
+ .crcBytes(2) // 2 byte CRC
+ .autoRetransmit({count:15, delay:4000})
+ .use(tessel.port['A']);
+
+nrf._debug = false;
+
+nrf.on('ready', function () {
+ if (role === 'ping') {
+ console.log("PING out");
+ /*
+ * The Arduino pong code needs to have its timeout changed. On line #205
+ * https://github.com/maniacbug/RF24/blob/07a4bcf425d91c99105dbdbad0226296c7cd3a93/examples/pingpair/pingpair.pde#L205
+ * the delay(20) needs to be swapped out with delay(2000)
+ */
+
+ var tx = nrf.openPipe('tx', pipes[1]), // transmit address F0F0F0F0D2
+ rx = nrf.openPipe('rx', pipes[1], {size: 8}); // receive address F0F0F0F0D2
+ tx.on('ready', function () { // NOTE: hoping to get rid of need to wait for "ready"
+ var n = 0;
+ setInterval(function () {
+ var b = new Buffer(8); // set buff len of 8 for compat with maniac bug's RF24 lib
+ b.fill(0);
+ b.writeUInt32BE(n++, 4); // offset by 4 because our buffer length is 8 bytes
+ console.log("Sending", n);
+ tx.write(b);
+ }, 5e3); // transmit every 5 seconds
+ });
+ rx.on('data', function (d) {
+ console.log("Got response back:", d.readUInt32BE(4)); //offset by 4 again
+ });
+ } else {
+ console.log("PONG back");
+ /*
+ * The Arduino ping code needs to have its timeout changed. On line #161
+ * https://github.com/maniacbug/RF24/blob/07a4bcf425d91c99105dbdbad0226296c7cd3a93/examples/pingpair/pingpair.pde#L161
+ * instead of "if (millis() - started_waiting_at > 200 )"
+ * change to "if (millis() - started_waiting_at > 2000 )"
+ */
+
+ var rx = nrf.openPipe('rx', pipes[0], {size: 8});
+ tx = nrf.openPipe('tx', pipes[0], {autoAck: false});
+ rx.on('data', function (d) {
+ console.log("Got data, will respond", d);
+ tx.write(d);
+ });
+ tx.on('error', function (e) {
+ console.warn("Error sending reply.", e);
+ });
+ }
+});
+
+// hold this process open
+process.ref();
\ No newline at end of file
diff --git a/examples/nrf24.js b/examples/nrf24.js
new file mode 100644
index 0000000..e151833
--- /dev/null
+++ b/examples/nrf24.js
@@ -0,0 +1,62 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+/*********************************************
+Tessel to tessel requires 2 nrf24 modules
+(and ideally two tessels) put one tessel+nrf
+on "ping" mode and another one on "pong" mode.
+*********************************************/
+
+var tessel = require('tessel'),
+ NRF24 = require("../"),
+ pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2],
+ role = 'pong'; // swap this to pong if you want to wait for receive
+
+var nrf = NRF24.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = 2476MHz
+ .transmitPower('PA_MAX') // set the transmit power to max
+ .dataRate('1Mbps')
+ .crcBytes(2) // 2 byte CRC
+ .autoRetransmit({count:15, delay:4000})
+ .use(tessel.port['A']);
+
+nrf._debug = false;
+
+nrf.on('ready', function () {
+ setTimeout(function(){
+ nrf.printDetails();
+ }, 5000);
+
+ if (role === 'ping') {
+ console.log("PING out");
+
+ var tx = nrf.openPipe('tx', pipes[0], {autoAck: false}), // transmit address F0F0F0F0D2
+ rx = nrf.openPipe('rx', pipes[1], {size: 4}); // receive address F0F0F0F0D2
+ tx.on('ready', function () {
+ var n = 0;
+ setInterval(function () {
+ var b = new Buffer(4); // set buff len of 8 for compat with maniac bug's RF24 lib
+ b.fill(0);
+ b.writeUInt32BE(n++);
+ console.log("Sending", n);
+ tx.write(b);
+ }, 5e3); // transmit every 5 seconds
+ });
+ rx.on('data', function (d) {
+ console.log("Got response back:", d);
+ });
+ } else {
+ console.log("PONG back");
+ var rx = nrf.openPipe('rx', pipes[0], {size: 4});
+ tx = nrf.openPipe('tx', pipes[1], {autoAck: false});
+ rx.on('data', function (d) {
+ console.log("Got data, will respond", d);
+ tx.write(d);
+ });
+ tx.on('error', function (e) {
+ console.warn("Error sending reply.", e);
+ });
+ }
+});
+
+// hold this process open
+process.ref();
\ No newline at end of file
diff --git a/index.js b/index.js
index 5995dd7..ba76c49 100644
--- a/index.js
+++ b/index.js
@@ -2,10 +2,15 @@ var q = require('queue-async'),
stream = require('stream'),
util = require('util'),
events = require('events'),
- SPI = require('pi-spi'),
- GPIO = require('pi-pins'),
_m = require("./magicnums");
+var tessel;
+try {
+ tessel = require('tessel');
+} catch (e) {}
+
+var nrfOpts = {};
+
function forEachWithCB(fn, cb) {
var process = q(1);
this.forEach(function (d) { process.defer(fn, d); });
@@ -22,25 +27,140 @@ function _extend(obj) {
function _nop() {} // used when a cb is not provided
+function nrfWrapper(){}
-exports.connect = function (spi,ce,irq) {
- var _spi = spi, _ce = ce, _irq = irq; // only for printDetails!
- var nrf = new events.EventEmitter(),
- spi = SPI.initialize(spi),
- ce = GPIO.connect(ce),
- irq = (arguments.length > 2) && GPIO.connect(irq);
-
- nrf._T = _extend({}, _m.TIMING, {pd2stby:4500}); // may need local override of pd2stby
-
- nrf.blockMicroseconds = function (us) {
- // NOTE: setImmediate/process.nextTick too slow (especially on Pi) so we just spinloop for µs
- var start = process.hrtime();
- while (1) {
- var diff = process.hrtime(start);
- if (diff[0] * 1e9 + diff[1] >= us*1e3) break;
+nrfWrapper.use =function(hardware, ce, irq, callback) {
+ var nrfObj;
+ // if we just have hardware assume it's a tessel
+ if (arguments.length <= 2){
+ callback = arguments[1];
+ nrfObj = new nrf('tessel', hardware);
+ } else if (arguments.length >= 3){
+ // if we have everything assume it's an RPi
+ nrfObj = new nrf('pi', hardware, ce, irq);
+ }
+ // go through and apply all options
+ forEachWithCB.call(Object.keys(nrfOpts), function(key, cb){
+ nrfObj[key](nrfOpts[key], cb);
+ }, function(){
+ // on finish emit ready
+ nrfObj.begin(function(){
+ nrfObj.emit('ready');
+ callback && callback();
+ });
+ })
+
+ return nrfObj;
+}
+
+nrfWrapper.channel = function(val){
+ nrfOpts.channel = val;
+ return this;
+}
+
+nrfWrapper.dataRate = function(val){
+ nrfOpts.dataRate = val;
+ return this;
+}
+
+nrfWrapper.transmitPower = function(val){
+ nrfOpts.transmitPower = val;
+ return this;
+}
+
+nrfWrapper.crcBytes = function(val){
+ nrfOpts.crcBytes = val;
+ return this;
+}
+
+nrfWrapper.addressWidth = function(val){
+ nrfOpts.addressWidth = val;
+ return this;
+}
+
+nrfWrapper.autoRetransmit = function(val){
+ nrfOpts.autoRetransmit = val;
+ return this;
+}
+
+module.exports = nrfWrapper;
+
+function nrf(type, hardware) {
+ var _spi = type;
+ var _ce, _irq, ce, irq, spi;
+ var nrf = new events.EventEmitter();
+ nrf._debug = true;
+ if (type == 'tessel') {
+ _ce = "builtin";
+ _irq = "builtin";
+ spi = new hardware.SPI({chipSelect:hardware.digital[0], chipSelectActive: 0}),
+ ce = hardware.digital[1],
+ irq = hardware.digital[2];
+
+ // Tessel's transfer always returns as much data as sent
+ nrf._transfer = function (writeBuf, readLen, cb) {
+ if (readLen > writeBuf.length) {
+ var tmpBuff = Buffer(readLen);
+ tmpBuff.fill(0);
+ writeBuf.copy(tmpBuff);
+ writeBuf = tmpBuff;
+ }
+
+ spi.transfer(writeBuf, function (e,d) {
+ if (e) cb(e);
+ else cb(null, d);
+ });
+ };
+
+ nrf.blockMicroseconds = function (us) {
+ tessel.sleep(us);
+ if (nrf._debug) console.log("slept for "+us+"µs.");
+ };
+
+ nrf.setCE = function (state, block) {
+ if (typeof state === 'string') {
+ ce.input();
+ if (state === 'high') state = true;
+ else if (state === 'low') state = false;
+ else throw Error("Unsupported setCE mode: "+state);
+ }
+ if (state) ce.high();
+ else ce.low();
+ if (nrf._debug) console.log("Set CE "+state+".");
+ if (block) nrf.blockMicroseconds(nrf._T[block]); // (assume ce changed TX/RX mode)
}
- if (nrf._debug) console.log("blocked for "+us+"µs.");
- };
+
+ } else if (type == 'pi'){
+ spi = hardware;
+ ce = arguments[2];
+ irq = arguments[3];
+
+ nrf._transfer = function(buff, len, cb) {
+ spi.transfer(buff, len, cb);
+ }
+
+ nrf.blockMicroseconds = function (us) {
+ // NOTE: setImmediate/process.nextTick too slow (especially on Pi) so we just spinloop for µs
+ var start = process.hrtime();
+ while (1) {
+ var diff = process.hrtime(start);
+ if (diff[0] * 1e9 + diff[1] >= us*1e3) break;
+ }
+ if (nrf._debug) console.log("blocked for "+us+"µs.");
+ };
+
+ nrf.setCE = function (state, block) {
+ if (typeof state === 'string') ce.mode(state);
+ else ce.value(state);
+ if (nrf._debug) console.log("Set CE "+state+".");
+ if (block) nrf.blockMicroseconds(nrf._T[block]); // (assume ce changed TX/RX mode)
+ };
+
+ } else {
+ throw "Error: nRF can only be used with the Pi or the Tessel at the moment";
+ }
+
+ nrf._T = _extend({}, _m.TIMING, {pd2stby:4500}); // may need local override of pd2stby
nrf.execCommand = function (cmd, data, cb) { // (can omit data, or specify readLen instead)
if (typeof data === 'function' || typeof data === 'undefined') {
@@ -73,12 +193,13 @@ exports.connect = function (spi,ce,irq) {
readLen = data;
}
- spi.transfer(writeBuf, readLen && readLen+1, function (e,d) {
+ nrf._transfer(writeBuf, readLen && readLen+1, function (e,d) {
if (nrf._debug && readLen) console.log(' - exec read:', d);
if (e) return cb(e);
else return cb(null, d && Array.prototype.reverse.call(d.slice(1)));
});
};
+
function registersForMnemonics(list) {
var registersNeeded = Object.create(null);
@@ -96,6 +217,7 @@ exports.connect = function (spi,ce,irq) {
});
return registersNeeded;
}
+
function maskForMnemonic(mnem) {
var _r = _m.REGISTER_MAP[mnem],
@@ -128,6 +250,7 @@ exports.connect = function (spi,ce,irq) {
cb(e,states);
});
};
+
var _statusReg = _m.REGISTER_MAP['STATUS'][0];
nrf.setStates = function (vals, cb) {
@@ -169,13 +292,8 @@ exports.connect = function (spi,ce,irq) {
}
forEachWithCB.call(Object.keys(registersNeeded), processInquiryForRegister, cb);
};
+
- nrf.setCE = function (state, block) {
- if (typeof state === 'string') ce.mode(state);
- else ce.value(state);
- if (nrf._debug) console.log("Set CE "+state+".");
- if (block) nrf.blockMicroseconds(nrf._T[block]); // (assume ce changed TX/RX mode)
- };
nrf.pulseCE = function (block) {
nrf.setCE(true,'hce');
nrf.setCE(false,block);
@@ -387,9 +505,12 @@ exports.connect = function (spi,ce,irq) {
irqOn = false;
nrf._irqOn = function () {
if (irqOn) return;
- else if (irq) {
+ else if (irq && !tessel) {
irq.mode('in');
irq.addListener('fall', irqListener);
+ } else if (irq) {
+ // hybrid mode: polling, but of IRQ pin instead of nrf status
+ irq.on('fall', irqListener);
} else {
console.warn("Recommend use with IRQ pin, fallback handling is suboptimal.");
irqListener = setInterval(function () { // TODO: clear interval when there are no listeners?
@@ -400,7 +521,8 @@ exports.connect = function (spi,ce,irq) {
};
nrf._irqOff = function () {
if (!irqOn) return;
- else if (irq) irq.removeListener('fall', irqListener);
+ else if (irq && !tessel) irq.removeListener('fall', irqListener);
+ else if (tessel) irq.removeListener('fall', irqListener);
else clearInterval(irqListener);
irqOn = false;
};
@@ -418,9 +540,11 @@ exports.connect = function (spi,ce,irq) {
if (e) return nrf.emit('error', e);
nrf._irqOn(); // NOTE: on before any pipes to facilite lower-level sendPayload use
ready = true;
- nrf.emit('ready');
+ // nrf.emit('ready');
+ if (cb) cb();
});
- if (cb) nrf.once('ready', cb);
+ // .use now emits and handles .ready
+ // if (cb) nrf.once('ready', cb);
};
nrf.end = function (cb) {
var pipes = txPipes.concat(rxPipes);
@@ -637,7 +761,7 @@ exports.connect = function (spi,ce,irq) {
_h(d.RX_PW_P0),_h(d.RX_PW_P1),_h(d.RX_PW_P2),
_h(d.RX_PW_P3),_h(d.RX_PW_P4),_h(d.RX_PW_P5)
);
- nrf.getStates(['EN_AA','EN_RXADDR','RF_CH','RF_SETUP','CONFIG','DYNPD','FEATURE'], function (e,d) {
+ nrf.getStates(['CONFIG','EN_AA','EN_RXADDR','RF_CH','RF_SETUP','DYNPD','FEATURE'], function (e,d) {
console.log("EN_AA:\t\t",_h(d.EN_AA));
console.log("EN_RXADDR:\t",_h(d.EN_RXADDR));
console.log("RF_CH:\t\t",_h(d.RF_CH));
@@ -652,7 +776,7 @@ exports.connect = function (spi,ce,irq) {
logFinalDetails();
} else nrf.setStates({RF_DR_LOW:true}, function () {
nrf.getStates(['RF_DR_LOW'], function (e,d2) {
- // (non-plus chips hold this bit zero even after settting)
+ // (non-plus chips hold this bit zero even after setting)
if (d2.RF_DR_LOW) isPlus = true;
// …then set back to original (false) value again
nrf.setStates({RF_DR_LOW:false}, function () {
@@ -672,7 +796,9 @@ exports.connect = function (spi,ce,irq) {
});
});
});
- function _h(n) { return (Buffer.isBuffer(n)) ? '0x'+n.toString('hex') : '0x'+n.toString(16); }
+ function _h(n) {
+ return (Buffer.isBuffer(n)) ? '0x'+n.toString('hex') : '0x'+n.toString(16);
+ }
};
nrf.on('interrupt', function (d) { if (nrf._debug) console.log("IRQ.", d); });
diff --git a/magicnums.js b/magicnums.js
index 959b6ed..acb3c1a 100644
--- a/magicnums.js
+++ b/magicnums.js
@@ -1,4 +1,13 @@
-function _b(v) { return parseInt(v.replace(' ',''),2); }
+//function _b(v) { return parseInt(v.replace(' ',''),2); }
+// WORKAROUND: https://github.com/tessel/beta/issues/206
+function _b(v) {
+ if (v.length !== 9) throw Error("Proper conversion not implemented for this size string!");
+ var n = 0;
+ Array.prototype.forEach.call(v.replace(' ',''), function (l, i) {
+ if (l === '1') n += 1 << (7 - i);
+ });
+ return n;
+}
exports.COMMANDS = {
R_REGISTER: _b('0000 0000'),
diff --git a/package.json b/package.json
index 6f5a00c..4885a32 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,10 @@
{
- "name": "nrf",
- "version": "0.8.1",
+ "name": "rf-nrf24",
+ "version": "0.8.3",
"description": "nRF24L01 driver library",
"main": "index.js",
"scripts": {
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "tinytap -e 'tessel run {}' test/*.js"
},
"repository": {
"type": "git",
@@ -27,5 +27,12 @@
"pi-spi": "~0.8.1",
"queue-async": "~1.0.4",
"pi-pins": "^1.0.0"
+ },
+ "hardware": {
+ "pi-spi": false,
+ "pi-pins": false
+ },
+ "devDependencies": {
+ "tinytap": "~0.0.2"
}
}
diff --git a/test-tessel.js b/test-tessel.js
new file mode 100644
index 0000000..b2a715e
--- /dev/null
+++ b/test-tessel.js
@@ -0,0 +1,78 @@
+var tessel = require('tessel'),
+ NRF24 = require("./index"),
+ nrf = NRF24.use(tessel.port('a')),
+ pipes = ['F0F0F0F0E1', 'F0F0F0F0D2'],
+ role = 'ping';
+//nrf._debug = true;
+//nrf.printDetails();
+
+nrf.channel(0x4c).transmitPower('PA_MAX').dataRate('1Mbps').crcBytes(2).autoRetransmit({count:15, delay:4000}).begin(function () {
+ if (role === 'listen') {
+ // HACK: listen for "ambient" broadcast i.e. https://github.com/natevw/greenhouse/blob/master/config.h#L5
+ var rx = nrf.openPipe('rx', pipes[0], {autoAck:false});
+ rx.on('data', function (d) {
+ Array.prototype.reverse.call(d); // WORKAROUND: https://github.com/natevw/node-nrf/issues/3
+ console.log("******** Got data ********", d);
+
+ if (d.slice(0,4).toString() === 'aqua') {
+ //printf("Received broadcast: now=%u switchAugerCount=%u remoteAugerCount=%u waterTemp=%u humidity=%u airTemp=%u nc=%u\n", …)
+ var info = {
+ now: d.readUInt32LE(1*4),
+ switchAugerCount: d.readUInt32LE(2*4),
+ remoteAugerCount: d.readUInt32LE(3*4),
+ waterTempC: waterTemp(d.readUInt32LE(4*4)),
+ humidity: d.readUInt32LE(5*4),
+ airTempC: airTemp(d.readUInt32LE(6*4)),
+ nc: powerStatus(d.readUInt32LE(7*4))
+ };
+ info.waterTempF = c2f(info.waterTempC);
+ info.airTempF = c2f(info.airTempC);
+ console.log(info);
+
+ // pinched from https://github.com/natevw/rooflux/blob/greenhouse/display.html#L65
+ function c2f(c) { return 1.8 * c + 32; }
+ function waterTemp(b) {
+ var sign = (b & 0xf800) ? -1 : 1;
+ return sign * (b & ~0xf800) / (1 << 4);
+ }
+ function airTemp(b) {
+ return (b/1024*3.3-0.5)*100;
+ }
+ function powerStatus(b) {
+ if (b === 0) return "Normal";
+ else if (b > 1024) return "Bogus data…";
+ else if (b > 300) return "On battery!";
+ else return "Unknown: "+b;
+ }
+ }
+ });
+ } else if (role === 'ping') {
+ console.log("PING out");
+ var tx = nrf.openPipe('tx', pipes[0]),
+ rx = nrf.openPipe('rx', pipes[1]);
+ tx.on('ready', function () { // NOTE: hoping to get rid of need to wait for "ready"
+ // (new CountStream).pipe(tx);
+ var n = 0;
+ setInterval(function () {
+ console.log("Sending", n);
+ var b = new Buffer(4);
+ b.writeUInt32BE(n++, 0);
+ tx.write(b);
+ }, 1e3);
+ });
+ rx.on('data', function (d) {
+ console.log("Got response back:", d.readUInt32BE(0));
+ });
+ } else {
+ console.log("PONG back");
+ var rx = nrf.openPipe('rx', pipes[0]),
+ tx = nrf.openPipe('tx', pipes[1]);
+ rx.on('data', function (d) {
+ console.log("Got data, will respond", d.readUInt32BE(0));
+ tx.write(d);
+ });
+ tx.on('error', function (e) {
+ console.warn("Error sending reply.", e);
+ });
+ }
+});
\ No newline at end of file
diff --git a/test.js b/test.js
index daa355f..71635e5 100644
--- a/test.js
+++ b/test.js
@@ -2,6 +2,8 @@
var NRF24 = require("./index"),
spiDev = "/dev/spidev0.0",
+ SPI = require('pi-spi'),
+ GPIO = require('pi-pins'),
cePin = 24, irqPin = 25, //var ce = require("./gpio").connect(cePin)
pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2],
role = 'ping';
@@ -20,9 +22,15 @@ CountStream.prototype._read = function () {
this.push(b);
};
-var nrf = NRF24.connect(spiDev, cePin, irqPin);
+var nrf = NRF24.channel(0x4c)
+ .transmitPower('PA_MAX')
+ .dataRate('1Mbps')
+ .crcBytes(2)
+ .autoRetransmit({count:15, delay:4000})
+ .use(SPI.initialize(spiDev), GPIO.connect(cePin), GPIO.connect(irqPin));
+
//nrf._debug = true;
-nrf.channel(0x4c).transmitPower('PA_MAX').dataRate('1Mbps').crcBytes(2).autoRetransmit({count:15, delay:4000}).begin(function () {
+nrf.on('ready', function () {
if (role === 'ping') {
console.log("PING out");
var tx = nrf.openPipe('tx', pipes[0]),
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..bca10a4
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,69 @@
+/* tessel to tessel
+ * requires 2 nrf24 modules (and ideally two tessels)
+ * put one tessel+nrf on "ping" mode and another one on "pong" mode
+ */
+
+var tessel = require('tessel'),
+ NRF24 = require("../"),
+ pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2];
+
+console.log('1..3');
+
+function go (port, role) {
+ var nrf = NRF24.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = 2476MHz
+ .transmitPower('PA_MAX') // set the transmit power to max
+ .dataRate('1Mbps')
+ .crcBytes(2) // 2 byte CRC
+ .autoRetransmit({count:15, delay:4000})
+ .use(port);
+
+ nrf._debug = false;
+
+ var sendack = false, resack = false;
+ nrf.on('ready', function () {
+ if (role === 'ping') {
+ console.log("# PING out");
+
+ var tx = nrf.openPipe('tx', pipes[0], {autoAck: false}), // transmit address F0F0F0F0D2
+ rx = nrf.openPipe('rx', pipes[1], {size: 4}); // receive address F0F0F0F0D2
+ tx.on('ready', function () {
+ var n = 0;
+ setImmediate(function loop () {
+ var b = new Buffer(4); // set buff len of 8 for compat with maniac bug's RF24 lib
+ b.fill(0);
+ b.writeUInt32BE(n++);
+ console.log("# sending", n);
+ !sendack && console.log('ok - sending');
+ sendack = true;
+ tx.write(b);
+ setTimeout(loop, 5e3)
+ }); // transmit every 5 seconds
+ });
+ rx.on('data', function (d) {
+ console.log("# got response back:", d);
+ console.log('ok - responded');
+ process.exit(0);
+ });
+ } else {
+ console.log("# PONG back");
+ var rx = nrf.openPipe('rx', pipes[0], {size: 4});
+ tx = nrf.openPipe('tx', pipes[1], {autoAck: false});
+ rx.on('data', function (d) {
+ console.log("# got data, will respond", d);
+ !resack && console.log('ok - responding');
+ resack = true;
+ tx.write(d);
+ });
+ tx.on('error', function (e) {
+ console.log("not ok - Error sending reply.", e);
+ process.exit(1);
+ });
+ }
+ });
+}
+
+// hold this process open
+process.ref();
+
+go(tessel.port['B'], 'ping');
+go(tessel.port['GPIO'], 'pong');
\ No newline at end of file