From 44f33cb8796f5ad9b934ad8426a3ec5516add6d7 Mon Sep 17 00:00:00 2001 From: Nathan Vander Wilt Date: Sat, 21 Dec 2013 21:35:25 -0600 Subject: [PATCH 01/16] quick attempt to test on Tessel, still hits https://github.com/tessel/beta/issues/48 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs-o-mentation: much of it / but not all / of it. quick fixes during initial test attempts on device use tessel.sleep instead of spinloop attempted workaround for https://github.com/tessel/beta/issues/72, now giving Bytecode compilation error… fix queue-async include (no improvement on https://github.com/tessel/beta/issues/73), put back relative include too just to start back over on https://github.com/tessel/beta/issues/72 start workaround for https://github.com/tessel/beta/issues/62 attempt to workaround https://github.com/tessel/beta/issues/73 by including queue-async directly but still triggers trouble a working version of queue-async cleaner workaround for https://github.com/tessel/beta/issues/73, closes https://github.com/tessel/beta/issues/72 quick workaround for https://github.com/tessel/beta/issues/74 remove ancient log wrap Tessel SPI with asymetric read/write convenience turn on debug workaround for SPI not setting CSN workaround for https://github.com/tessel/beta/issues/85 (corrupts queue.js internal state and causes procesing to just stop) fix isBuffer check to handle null workaround https://github.com/tessel/beta/issues/86 workaround for https://github.com/tessel/beta/issues/87 better note for https://github.com/tessel/beta/issues/89 tessel compiler not liking switch statement tonight, see https://github.com/tessel/beta/issues/181 workaround for https://github.com/tessel/beta/issues/198 workaround for https://github.com/tessel/beta/issues/199 add workaround for https://github.com/tessel/beta/issues/200 add workaround for https://github.com/tessel/beta/issues/201 make response log slightly less confusing apparently SPI returns an array instead of buffer, workaround https://github.com/tessel/beta/issues/203 (although I'm not sure if this was hurting anything but debug log readability) log written data in hex too for easier debugging workaround for https://github.com/tessel/beta/issues/202 workaround https://github.com/tessel/beta/issues/204 typo in comment workaround https://github.com/tessel/beta/issues/206 with own binary parse remove workaround for https://github.com/tessel/beta/issues/73 remove workaround for https://github.com/tessel/beta/issues/74 clean up tessel wait a bit (also baby step back to merging this and main lib) move test code out of library and back into test.js actually, split out tessel test code into own file start testing read/write pipes, with tiny stream.Duplex stub add workaround for https://github.com/tessel/beta/issues/207 add note about next bug…, https://github.com/tessel/beta/issues/209 attempted workaround for some tessel number formatting stuff, but see https://github.com/tessel/beta/issues/213 workaround Tessel number handling, https://github.com/tessel/beta/issues/213 https://github.com/tessel/beta/issues/206 https://github.com/tessel/beta/issues/209 don't inject tessel module anymore implement Tessel version of setCE, clean up some bad variable usage better logging for TX show sleep in debug mode on tessel make fake stream appear ever-eager for more data reads/writes better execCommand logging better polling strategy on Tessel: instead of polling status, at least poll IRQ pin add passive listen mode to test script re-enable plain UTF-8 buffer toString sample/test code to listen to same stuff as https://github.com/natevw/greenhouse/blob/master/greenhub.cpp#L42 + https://github.com/natevw/rooflux/blob/greenhouse/display.html#L65 remove workaround for https://github.com/tessel/beta/issues/89, .activeChipSelect is now an internal method --- README.md | 48 ++---------- index.js | 209 +++++++++++++++++++++++++++++++++++++------------ magicnums.js | 11 ++- package.json | 5 ++ test-tessel.js | 78 ++++++++++++++++++ 5 files changed, 257 insertions(+), 94 deletions(-) create mode 100644 test-tessel.js diff --git a/README.md b/README.md index 53f2bb4..148bb35 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,10 @@ # 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/). +nRF24L01+ driver library for node.js on the Raspberry Pi (etc.) -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. +Not to be confused with [node-rf24](https://github.com/natevw/node-rf24) which is an unfinished (and broken by recent V8/libuv changes) wrapper around the RasPi port of the C++ [RF24 library](https://github.com/stanleyseow/RF24). -## 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. +This module is implemented in pure JavaScript, on top of native [SPI bindings](https://github.com/natevw/pi-spi). ## Installation @@ -18,7 +14,7 @@ In contrast, *this* module is implemented in pure JavaScript on top of native [S ## Usage -[Streams](https://github.com/substack/stream-handbook#readme)! +Streams! ``` var radio = require('nrf').connect(spiDev, cePin, irqPin); @@ -32,7 +28,7 @@ radio.begin(function () { The nRF24L01+ radios provide "logic pipes" which can be used as node.js streams. These are opened for a given receiver address according to their primary direction. However, since the transceiver hardware supports sending data payloads with its acknowlegement packets, the both primary directions provide duplex streams — acknowlegement payload data can be read from a `'tx'` stream if the `ackPayloads` option is set true, and written to any `'rx'` stream. -> **TBD**: expand this section ["non"-stream usage, pipe options, optional callbacks, buffering and splitting/joining streams from 32-byte chunks, etc.] +> **TBD**: expand this section ["non"-stream usage, pipe options, optional callbacks, etc.] ## API @@ -59,51 +55,21 @@ The nRF24L01+ radios provide "logic pipes" which can be used as node.js streams. * `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.openPipe(mode, addr, opts)` — Returns a stream representing a "data pipe" on the radio. See pipe details section below. +* `radio.openPipe(mode, addr, opts)` — Returns a stream. **TBD**: … * `radio.end(cb)` — Closes all pipes and powers down the radio. Callback is optional. -#### 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.) - -* The `addr` parameter is simply the aforementioned address of the data pipe, usually as a 5 byte buffer. As a shorthand, you can also pass raw numbers e.g. `0xEF` for addresses, but note that the most significant nibble must have bit(s) set for this to work as expected — a literal `0x0000000A` in your source code will get processed as the invalid `Buffer("a", 'hex')` rather than a 3-byte address. - -* For `'rx'` mode pipes things are a little more complicated. The nRF24 chip supports listening simultaneously for up to 6 data channel pipes, *but* four of these logical channel address assignments must differ in only one byte from the first address. Also, the sixth address slot will be temporarily "borrowed" whenever any `'tx'`-mode pipe needs to listen for an acknowlegement packet. Basically for `'rx'` pipes, pass a 3, 4 or 5 byte `Buffer` the first time you call it, and it will be assigned a hardware pipe number automatically. Subsequent calls should ideally be single-byte `Buffers` only, representing the least significant byte in the address of up to four more pipes. If you open another pipe with a 3/4/5-byte address instead (or additionally), be aware that you may miss packets in certain situations. For example if you first open a pipe with address `0x123456`, you could also listen for `0x57`through `0x5A`. You could also open one last `'rx'` pipe with address `0x998877` — but if there were open `'tx'` pipes as well, and any of them needed to listen for acknowlegements, you could end up occasionally missing transmissions to this sixth address. [**TBD** diagram of "slots"?] - -* Finally, via the `opts` parameter you can set a fixed payload `size` (in bytes, defaults to `'auto'`) or disable auto-acknowlegement with `autoAck:false` (defaults to `true`). Note that if you want to disable auto-acknowlegment, you *must* also set a payload size — for some reason these are linked in the nRF24 feature set. - -* For `'tx'` pipes, the `opts` parameter also lets you provide individual `retryCount`, `retryDelay`, `txPower` options instead of using the `radio.autoRetransmit` and `radio.transmitPower` methods; if you do this you should provide values for *every* `'tx`' pipe you open, to make sure the hardware configuration gets updated between each different transmission. [**TBD**: what is `ackPayloads` option supposed to do for `'tx`` pipes?] - -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 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.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. When the power is off the transceiver hardware uses little power, but takes a little while longer to enter any useful mode. This is determined automatically whenever `radio.openPipe()` so it is typically not necessary 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 - -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`: - - sudo modprobe -r spi_bcm2708 - sudo modprobe spi_bcm2708 - -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 > **TBD**: [BSD-2-Clause template] diff --git a/index.js b/index.js index 5995dd7..c318016 100644 --- a/index.js +++ b/index.js @@ -2,10 +2,41 @@ 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) {} + +// WORKAROUND: https://github.com/tessel/beta/issues/199 +console.warn = console.error.bind(console); + +// WORKAROUND: https://github.com/tessel/beta/issues/62 +if (!stream.Duplex) { + var events = require('events'); + + stream.Duplex = function () { + events.EventEmitter.call(this); + this.on('newListener', function (evtName) { + if (evtName === 'data') this._read(); + }.bind(this)); + }; + util.inherits(stream.Duplex, events.EventEmitter); + + stream.Duplex.prototype.write = function (data) { + this._write(data); + return true; + }; + + stream.Duplex.prototype.push = function (data) { + this.emit('data', data); + return true; + }; +} + + + function forEachWithCB(fn, cb) { var process = q(1); this.forEach(function (d) { process.defer(fn, d); }); @@ -23,16 +54,65 @@ function _extend(obj) { function _nop() {} // used when a cb is not provided -exports.connect = function (spi,ce,irq) { - var _spi = spi, _ce = ce, _irq = irq; // only for printDetails! +// TODO: remove when https://github.com/tessel/beta/issues/209 resolved +function Buffer_workaround(str, fmt) { + if (!fmt) return Buffer(str); + else if (fmt === 'hex') { // avoid https://github.com/tessel/beta/issues/211 + if (str.length % 2) throw /*Type*/Error("Invalid hex string"); + var arr = []; + for (var i = 0; i < str.length; i += 2) { + // also workaround https://github.com/tessel/beta/issues/206 + var n = Number('0x'+str.slice(i, i+2)); + arr.push(n); + } + return Buffer(arr); + } else { + throw Error("Not implemented: buffer conversion from "+fmt); + } +} + + +// TODO: remove when https://github.com/tessel/beta/issues/202 resolved +var _origBuffString = Buffer.prototype.toString; +Buffer.prototype.toString = function (fmt) { + if (!fmt) return _origBuffString.call(this); + else if (fmt === 'hex') return Array.prototype.slice.call(this, 0).map(function (n) { + return (0x100+n).toString(16).slice(1); + }).join(''); + else throw Error("Not implemented: buffer conversion to "+fmt); +} + + +exports.connect = function (port) { + var _spi = "Tessel", _ce = "builtin", _irq = "-"; // only for printDetails! var nrf = new events.EventEmitter(), - spi = SPI.initialize(spi), - ce = GPIO.connect(ce), - irq = (arguments.length > 2) && GPIO.connect(irq); + spi = new port.SPI({chipSelect:port.gpio(1)}), + ce = port.gpio(2), + irq = port.gpio(3); + + // Tessel's transfer always returns as much data as sent + spi._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) { + spi.activeChipSelect(false); + if (e) cb(e); // WORKAROUND: https://github.com/tessel/beta/issues/203 + else cb(null, Buffer(d).slice(0,readLen)); + }); + }; + nrf._T = _extend({}, _m.TIMING, {pd2stby:4500}); // may need local override of pd2stby - nrf.blockMicroseconds = function (us) { + nrf.blockMicroseconds = (tessel) ? function (us) { + tessel.sleep(us); + if (nrf._debug) console.log("slept for "+us+"µs."); + } : function (us) { // NOTE: setImmediate/process.nextTick too slow (especially on Pi) so we just spinloop for µs var start = process.hrtime(); while (1) { @@ -47,7 +127,6 @@ exports.connect = function (spi,ce,irq) { cb = data || _nop; data = 0; } - if (nrf._debug) console.log('execCommand', cmd, data); var cmdByte; if (typeof cmd === 'string') { @@ -73,8 +152,10 @@ exports.connect = function (spi,ce,irq) { readLen = data; } - spi.transfer(writeBuf, readLen && readLen+1, function (e,d) { - if (nrf._debug && readLen) console.log(' - exec read:', d); + if (nrf._debug) console.log('execCommand', cmd, readLen, writeBuf); + + spi._nrf_transfer(writeBuf, readLen && readLen+1, function (e,d) { + if (nrf._debug && readLen) console.log(' - got:', d); if (e) return cb(e); else return cb(null, d && Array.prototype.reverse.call(d.slice(1))); }); @@ -84,10 +165,11 @@ exports.connect = function (spi,ce,irq) { var registersNeeded = Object.create(null); list.forEach(function (mnem) { var _r = _m.REGISTER_MAP[mnem]; - if (!_r) return console.warn("Skipping uknown mnemonic '"+mnem+"'!"); - if (_r.length === 1) _r.push(0,8); + // WORKAROUND: https://github.com/tessel/beta/issues/200 + if (!Boolean(_r)) return console.warn("Skipping uknown mnemonic '"+mnem+"'!"); + if (_r.length === 1) _r.push(0), _r.push(8); - var reg = _r[0], + var reg = _r[0]+'', // WORKAROUND: https://github.com/tessel/beta/issues/201 howManyBits = _r[2] || 1, iq = registersNeeded[reg] || (registersNeeded[reg] = {arr:[]}); iq.len = (howManyBits / 8 >> 0) || 1; @@ -112,7 +194,10 @@ exports.connect = function (spi,ce,irq) { // TODO: execCommand always reads register 0x07 but we're not optimizing for that // TODO: we could probably also eliminate re-fetch of 0x07 during IRQ processing var iq = registersNeeded[reg]; - reg = +reg; + //reg = +reg; + // WORKAROUND: https://github.com/tessel/beta/issues/204 + reg = parseInt(reg, 10); + nrf.execCommand(['R_REGISTER',reg], iq.len, function (e,d) { if (e) return cb(e); iq.arr.forEach(function (mnem) { @@ -120,7 +205,7 @@ exports.connect = function (spi,ce,irq) { states[mnem] = (d[0] & m.mask) >> m.rightmostBit; }); if (iq.solo) states[iq.solo] = d; - cb(); + cb(null, '-'); // workaround for https://github.com/tessel/beta/issues/85 }); } forEachWithCB.call(Object.keys(registersNeeded), processInquiryForRegister, function (e) { @@ -136,7 +221,10 @@ exports.connect = function (spi,ce,irq) { var registersNeeded = registersForMnemonics(Object.keys(vals)); function processInquiryForRegister(reg, cb) { var iq = registersNeeded[reg]; - reg = +reg; // was string key, now convert back to number + //reg = +reg; // was string key, now convert back to number + // WORKAROUND: https://github.com/tessel/beta/issues/204 + reg = parseInt(reg, 10); + // if a register is "full" we can simply overwrite, otherwise we must read+merge // NOTE: high bits in RF_CH/PX_PW_Pn are *reserved*, i.e. technically need merging if (!iq.arr.length || iq.arr[0]==='RF_CH' || iq.arr[0].indexOf('RX_PW_P')===0) { @@ -149,6 +237,9 @@ exports.connect = function (spi,ce,irq) { settlingNeeded = 0; if (iq.solo) val = vals[iq.solo]; // TODO: refactor so as not to fetch in the first place! iq.arr.forEach(function (mnem) { + // WORKAROUND: https://github.com/tessel/beta/issues/87 + if (typeof vals[mnem] === 'boolean') vals[mnem] = (vals[mnem]) ? 1 : 0; + var m = maskForMnemonic(mnem); if (mnem === 'PWR_UP') { var rising = !(d[0] & m.mask) && vals[mnem]; @@ -160,17 +251,30 @@ exports.connect = function (spi,ce,irq) { val &= ~m.mask; // clear current value val |= (vals[mnem] << m.rightmostBit) & m.mask; }); - if (val !== d[0] || reg === _statusReg) nrf.execCommand(['W_REGISTER', reg], [val], function () { + if (val !== d[0] || reg === _statusReg) nrf.execCommand(['W_REGISTER', reg], [val], function (e,d) { if (settlingNeeded) nrf.blockMicroseconds(settlingNeeded); // see p.24 - cb.apply(this, arguments); + // WORKAROUND: https://github.com/tessel/beta/issues/207 + //cb.apply(this, arguments); + cb.call(this, e,d); }); - else cb(null); // don't bother writing if value hasn't changed (unless status, which clears bits) + else cb(null, '-'); // don't bother writing if value hasn't changed (unless status, which clears bits) }); } forEachWithCB.call(Object.keys(registersNeeded), processInquiryForRegister, cb); }; - nrf.setCE = function (state, block) { + nrf.setCE = (tessel) ? function (state, block) { + if (typeof state === 'string') { + ce.mode('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) + } : function (state, block) { if (typeof state === 'string') ce.mode(state); else ce.value(state); if (nrf._debug) console.log("Set CE "+state+"."); @@ -215,19 +319,10 @@ exports.connect = function (spi,ce,irq) { else cb(null, '1Mbps'); }); } else { - switch (val) { - case '1Mbps': - val = {RF_DR_LOW:false,RF_DR_HIGH:false}; - break; - case '2Mbps': - val = {RF_DR_LOW:false,RF_DR_HIGH:true}; - break; - case '250kbps': - val = {RF_DR_LOW:true,RF_DR_HIGH:false}; - break; - default: - throw Error("dataRate must be one of '1Mbps', '2Mbps', or '250kbps'."); - } + if (val === '1Mbps') val = {RF_DR_LOW:false,RF_DR_HIGH:false}; + else if (val === '2Mbps') val = {RF_DR_LOW:false,RF_DR_HIGH:true}; + else if (val == '250kbps') val = {RF_DR_LOW:true,RF_DR_HIGH:false}; + else throw Error("dataRate must be one of '1Mbps', '2Mbps', or '250kbps'."); nrf.setStates(val, cb); } return this; @@ -255,19 +350,10 @@ exports.connect = function (spi,ce,irq) { else cb(null, 1); }); } else { - switch (val) { - case 0: - val = {EN_CRC:false,CRCO:0}; - break; - case 1: - val = {EN_CRC:true,CRCO:0}; - break; - case 2: - val = {EN_CRC:true,CRCO:1}; - break; - default: - throw Error("crcBytes must be 1, 2, or 0."); - } + if (val === 0) val = {EN_CRC:false,CRCO:0}; + else if (val === 1) val = {EN_CRC:true,CRCO:0}; + else if (val === 2) val = {EN_CRC:true,CRCO:1}; + else throw Error("crcBytes must be 1, 2, or 0."); nrf.setStates(val, cb); } return this; @@ -387,9 +473,20 @@ 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 + // TODO: use hardware interrupt https://github.com/tessel/beta/issues/216 + irq.mode('input'); + var prevIdle = true; + irqListener = setInterval(function () { + var idle = irq.read(); + //if (nrf._debug) console.log("polled irq, idle:", idle); + if (prevIdle && !idle) nrf._checkStatus(true); + prevIdle = idle; + }, 0); // (minimum 4ms is a looong time if hoping to quickly stream data!) } 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 +497,7 @@ 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 clearInterval(irqListener); irqOn = false; }; @@ -414,7 +511,7 @@ exports.connect = function (spi,ce,irq) { nrf.setCE('low','stby2a'); var clearIRQ = {RX_DR:true, TX_DS:true, MAX_RT:true}, features = {EN_DPL:true, EN_ACK_PAY:true, EN_DYN_ACK:true}; - nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x00},clearIRQ,features), function (e) { + nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x00},clearIRQ,features), function (e,d) { if (e) return nrf.emit('error', e); nrf._irqOn(); // NOTE: on before any pipes to facilite lower-level sendPayload use ready = true; @@ -450,8 +547,13 @@ exports.connect = function (spi,ce,irq) { } } nrf.openPipe = function (rx_tx, addr, opts) { + if (tessel && typeof addr === 'number') throw Error( + "Please don't use hex literal addresses on Tessel, " + +"see https://github.com/tessel/beta/issues/213" + ); if (!ready) throw Error("Radio .begin() must be finished before a pipe can be opened."); if (typeof addr === 'number') addr = Buffer(addr.toString(16), 'hex'); + else if (typeof addr == 'string') addr = Buffer_workaround(addr, 'hex'); opts || (opts = {}); var pipe; @@ -473,8 +575,11 @@ exports.connect = function (spi,ce,irq) { txQ.active = true; d.pipe._tx(d.data, function () { try { + // NOTE: skipping https://github.com/tessel/beta/issues/207 workaround since should only have 1 argument… d.cb.apply(this, arguments); - } finally { + //} finally { + // WORKAROUND: https://github.com/tessel/beta/issues/198 (not quite equivalent, this drops exception!) + } catch (e) {} if (1) { delete txQ.active; nrf._nudgeTX(); } @@ -652,7 +757,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 () { 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..a1e7c7a 100644 --- a/package.json +++ b/package.json @@ -27,5 +27,10 @@ "pi-spi": "~0.8.1", "queue-async": "~1.0.4", "pi-pins": "^1.0.0" + }, + // prevent tessel from including pi files + "hardware": { + "pi-spi": false, + "pi-pins": false } } diff --git a/test-tessel.js b/test-tessel.js new file mode 100644 index 0000000..2146f11 --- /dev/null +++ b/test-tessel.js @@ -0,0 +1,78 @@ +var tessel = require('tessel'), + NRF24 = require("./index"), + nrf = NRF24.connect(tessel.port('a')), + pipes = ['F0F0F0F0E1', 'F0F0F0F0D2'], + role = 'listen'; +//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 From d05aa3ed0735ba6ea32e42bcc20e7afb2a05c721 Mon Sep 17 00:00:00 2001 From: johnnyman727 Date: Sat, 3 May 2014 07:17:23 -0700 Subject: [PATCH 02/16] Removed deprecated GPIO/SPI commands to get it running again --- index.js | 14 ++++++-------- test-tessel.js | 4 ++-- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index c318016..0ed11bd 100644 --- a/index.js +++ b/index.js @@ -9,9 +9,6 @@ try { tessel = require('tessel'); } catch (e) {} -// WORKAROUND: https://github.com/tessel/beta/issues/199 -console.warn = console.error.bind(console); - // WORKAROUND: https://github.com/tessel/beta/issues/62 if (!stream.Duplex) { var events = require('events'); @@ -83,7 +80,7 @@ Buffer.prototype.toString = function (fmt) { } -exports.connect = function (port) { +exports.use = function (port) { var _spi = "Tessel", _ce = "builtin", _irq = "-"; // only for printDetails! var nrf = new events.EventEmitter(), spi = new port.SPI({chipSelect:port.gpio(1)}), @@ -100,9 +97,9 @@ exports.connect = function (port) { } spi.transfer(writeBuf, function (e,d) { - spi.activeChipSelect(false); + // spi.activeChipSelect(false); if (e) cb(e); // WORKAROUND: https://github.com/tessel/beta/issues/203 - else cb(null, Buffer(d).slice(0,readLen)); + else cb(null, d); }); }; @@ -265,7 +262,7 @@ exports.connect = function (port) { nrf.setCE = (tessel) ? function (state, block) { if (typeof state === 'string') { - ce.mode('input'); + ce.input(); if (state === 'high') state = true; else if (state === 'low') state = false; else throw Error("Unsupported setCE mode: "+state); @@ -479,7 +476,7 @@ exports.connect = function (port) { } else if (irq) { // hybrid mode: polling, but of IRQ pin instead of nrf status // TODO: use hardware interrupt https://github.com/tessel/beta/issues/216 - irq.mode('input'); + irq.input(); var prevIdle = true; irqListener = setInterval(function () { var idle = irq.read(); @@ -513,6 +510,7 @@ exports.connect = function (port) { features = {EN_DPL:true, EN_ACK_PAY:true, EN_DYN_ACK:true}; nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x00},clearIRQ,features), function (e,d) { if (e) return nrf.emit('error', e); + console.log('reset!'); nrf._irqOn(); // NOTE: on before any pipes to facilite lower-level sendPayload use ready = true; nrf.emit('ready'); diff --git a/test-tessel.js b/test-tessel.js index 2146f11..b2a715e 100644 --- a/test-tessel.js +++ b/test-tessel.js @@ -1,8 +1,8 @@ var tessel = require('tessel'), NRF24 = require("./index"), - nrf = NRF24.connect(tessel.port('a')), + nrf = NRF24.use(tessel.port('a')), pipes = ['F0F0F0F0E1', 'F0F0F0F0D2'], - role = 'listen'; + role = 'ping'; //nrf._debug = true; //nrf.printDetails(); From b593c1f30799b3ad37f85018028ba7127d769bcd Mon Sep 17 00:00:00 2001 From: johnnyman727 Date: Sat, 3 May 2014 08:08:11 -0700 Subject: [PATCH 03/16] moved to gpio interripts --- index.js | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/index.js b/index.js index 0ed11bd..13b7f44 100644 --- a/index.js +++ b/index.js @@ -71,6 +71,7 @@ function Buffer_workaround(str, fmt) { // TODO: remove when https://github.com/tessel/beta/issues/202 resolved var _origBuffString = Buffer.prototype.toString; + Buffer.prototype.toString = function (fmt) { if (!fmt) return _origBuffString.call(this); else if (fmt === 'hex') return Array.prototype.slice.call(this, 0).map(function (n) { @@ -79,14 +80,17 @@ Buffer.prototype.toString = function (fmt) { else throw Error("Not implemented: buffer conversion to "+fmt); } +exports.use = function(hardware, callback) { + return new nrf(hardware); +} -exports.use = function (port) { +function nrf(hardware, callback) { var _spi = "Tessel", _ce = "builtin", _irq = "-"; // only for printDetails! var nrf = new events.EventEmitter(), - spi = new port.SPI({chipSelect:port.gpio(1)}), - ce = port.gpio(2), - irq = port.gpio(3); - + spi = new hardware.SPI({chipSelect:hardware.gpio(1)}), + ce = hardware.gpio(2), + irq = hardware.gpio(3); + nrf._debug = true; // Tessel's transfer always returns as much data as sent spi._nrf_transfer = function (writeBuf, readLen, cb) { if (readLen > writeBuf.length) { @@ -207,6 +211,7 @@ exports.use = function (port) { } forEachWithCB.call(Object.keys(registersNeeded), processInquiryForRegister, function (e) { if (nrf._debug) console.log('gotStates', states, e); + console.log('got states', states); cb(e,states); }); }; @@ -476,14 +481,7 @@ exports.use = function (port) { } else if (irq) { // hybrid mode: polling, but of IRQ pin instead of nrf status // TODO: use hardware interrupt https://github.com/tessel/beta/issues/216 - irq.input(); - var prevIdle = true; - irqListener = setInterval(function () { - var idle = irq.read(); - //if (nrf._debug) console.log("polled irq, idle:", idle); - if (prevIdle && !idle) nrf._checkStatus(true); - prevIdle = idle; - }, 0); // (minimum 4ms is a looong time if hoping to quickly stream data!) + irq.watch('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? @@ -495,7 +493,7 @@ exports.use = function (port) { nrf._irqOff = function () { if (!irqOn) return; else if (irq && !tessel) irq.removeListener('fall', irqListener); - else clearInterval(irqListener); + else irq.unwatch('fall'); irqOn = false; }; @@ -510,7 +508,6 @@ exports.use = function (port) { features = {EN_DPL:true, EN_ACK_PAY:true, EN_DYN_ACK:true}; nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x00},clearIRQ,features), function (e,d) { if (e) return nrf.emit('error', e); - console.log('reset!'); nrf._irqOn(); // NOTE: on before any pipes to facilite lower-level sendPayload use ready = true; nrf.emit('ready'); @@ -545,10 +542,6 @@ exports.use = function (port) { } } nrf.openPipe = function (rx_tx, addr, opts) { - if (tessel && typeof addr === 'number') throw Error( - "Please don't use hex literal addresses on Tessel, " - +"see https://github.com/tessel/beta/issues/213" - ); if (!ready) throw Error("Radio .begin() must be finished before a pipe can be opened."); if (typeof addr === 'number') addr = Buffer(addr.toString(16), 'hex'); else if (typeof addr == 'string') addr = Buffer_workaround(addr, 'hex'); From c22c2a3cc843ff77b12db712e5ac411b403fb20b Mon Sep 17 00:00:00 2001 From: jialiya Date: Thu, 8 May 2014 15:27:30 -0700 Subject: [PATCH 04/16] adding example code to work with RF24 lib --- examples/RF24-pingpair.js | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 examples/RF24-pingpair.js diff --git a/examples/RF24-pingpair.js b/examples/RF24-pingpair.js new file mode 100644 index 0000000..023f6c6 --- /dev/null +++ b/examples/RF24-pingpair.js @@ -0,0 +1,68 @@ +/* + * These are settings designed 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("../"), + nrf = NRF24.use(tessel.port('a')), + pipes = ['F0F0F0F0E1', 'F0F0F0F0D2'], + role = 'pong'; // swap this to pong if you want to wait for receive + +nrf._debug = false; + +nrf.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}) + .begin(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(500) + */ + + 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 > 500 )" + */ + + var rx = nrf.openPipe('rx', pipes[0], {size: 8}); + tx = nrf.openPipe('tx', pipes[0], {size: 8}); + 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 From 943d95587a3ae5af8c7b8bbcc03b5f4b136b52b1 Mon Sep 17 00:00:00 2001 From: jialiya Date: Wed, 21 May 2014 12:12:06 -0700 Subject: [PATCH 05/16] removed most workarounds --- examples/RF24-pingpair.js | 13 ++- index.js | 169 ++++++++++++++++---------------------- 2 files changed, 79 insertions(+), 103 deletions(-) diff --git a/examples/RF24-pingpair.js b/examples/RF24-pingpair.js index 023f6c6..34af84a 100644 --- a/examples/RF24-pingpair.js +++ b/examples/RF24-pingpair.js @@ -1,18 +1,17 @@ /* - * These are settings designed to work out of the box with + * 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("../"), nrf = NRF24.use(tessel.port('a')), - pipes = ['F0F0F0F0E1', 'F0F0F0F0D2'], - role = 'pong'; // swap this to pong if you want to wait for receive + pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2], + role = 'ping'; // swap this to pong if you want to wait for receive nrf._debug = false; +// nrf._debug = true; nrf.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = 2476MHz .transmitPower('PA_MAX') // set the transmit power to max @@ -25,7 +24,7 @@ nrf.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = /* * 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(500) + * the delay(20) needs to be swapped out with delay(2000) */ var tx = nrf.openPipe('tx', pipes[1]), // transmit address F0F0F0F0D2 @@ -49,7 +48,7 @@ nrf.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = * 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 > 500 )" + * change to "if (millis() - started_waiting_at > 2000 )" */ var rx = nrf.openPipe('rx', pipes[0], {size: 8}); diff --git a/index.js b/index.js index 13b7f44..25b1192 100644 --- a/index.js +++ b/index.js @@ -9,31 +9,6 @@ try { tessel = require('tessel'); } catch (e) {} -// WORKAROUND: https://github.com/tessel/beta/issues/62 -if (!stream.Duplex) { - var events = require('events'); - - stream.Duplex = function () { - events.EventEmitter.call(this); - this.on('newListener', function (evtName) { - if (evtName === 'data') this._read(); - }.bind(this)); - }; - util.inherits(stream.Duplex, events.EventEmitter); - - stream.Duplex.prototype.write = function (data) { - this._write(data); - return true; - }; - - stream.Duplex.prototype.push = function (data) { - this.emit('data', data); - return true; - }; -} - - - function forEachWithCB(fn, cb) { var process = q(1); this.forEach(function (d) { process.defer(fn, d); }); @@ -50,36 +25,6 @@ function _extend(obj) { function _nop() {} // used when a cb is not provided - -// TODO: remove when https://github.com/tessel/beta/issues/209 resolved -function Buffer_workaround(str, fmt) { - if (!fmt) return Buffer(str); - else if (fmt === 'hex') { // avoid https://github.com/tessel/beta/issues/211 - if (str.length % 2) throw /*Type*/Error("Invalid hex string"); - var arr = []; - for (var i = 0; i < str.length; i += 2) { - // also workaround https://github.com/tessel/beta/issues/206 - var n = Number('0x'+str.slice(i, i+2)); - arr.push(n); - } - return Buffer(arr); - } else { - throw Error("Not implemented: buffer conversion from "+fmt); - } -} - - -// TODO: remove when https://github.com/tessel/beta/issues/202 resolved -var _origBuffString = Buffer.prototype.toString; - -Buffer.prototype.toString = function (fmt) { - if (!fmt) return _origBuffString.call(this); - else if (fmt === 'hex') return Array.prototype.slice.call(this, 0).map(function (n) { - return (0x100+n).toString(16).slice(1); - }).join(''); - else throw Error("Not implemented: buffer conversion to "+fmt); -} - exports.use = function(hardware, callback) { return new nrf(hardware); } @@ -87,10 +32,10 @@ exports.use = function(hardware, callback) { function nrf(hardware, callback) { var _spi = "Tessel", _ce = "builtin", _irq = "-"; // only for printDetails! var nrf = new events.EventEmitter(), - spi = new hardware.SPI({chipSelect:hardware.gpio(1)}), - ce = hardware.gpio(2), - irq = hardware.gpio(3); - nrf._debug = true; + spi = new hardware.SPI({chipSelect:hardware.digital[1], chipSelectActive: 0}), + ce = hardware.digital[2], + irq = hardware.digital[3]; + // nrf._debug = true; // Tessel's transfer always returns as much data as sent spi._nrf_transfer = function (writeBuf, readLen, cb) { if (readLen > writeBuf.length) { @@ -164,18 +109,23 @@ function nrf(hardware, callback) { function registersForMnemonics(list) { var registersNeeded = Object.create(null); + var count = 0; list.forEach(function (mnem) { var _r = _m.REGISTER_MAP[mnem]; - // WORKAROUND: https://github.com/tessel/beta/issues/200 - if (!Boolean(_r)) return console.warn("Skipping uknown mnemonic '"+mnem+"'!"); + // console.log("mnem", mnem, _m.REGISTER_MAP[mnem]); + if (!_r) return console.warn("Skipping uknown mnemonic '"+mnem+"'!"); if (_r.length === 1) _r.push(0), _r.push(8); - var reg = _r[0]+'', // WORKAROUND: https://github.com/tessel/beta/issues/201 + var reg = _r[0], howManyBits = _r[2] || 1, iq = registersNeeded[reg] || (registersNeeded[reg] = {arr:[]}); iq.len = (howManyBits / 8 >> 0) || 1; + if (howManyBits < 8) iq.arr.push(mnem); else iq.solo = mnem; + + // console.log("reg", reg, "howManyBits", howManyBits, "iq", iq, "registersNeeded[reg]", registersNeeded[reg], "iq.len", iq.len, registersNeeded); + // console.log("reg", reg, "registersNeeded[reg]", registersNeeded[reg], registersNeeded); }); return registersNeeded; } @@ -191,13 +141,12 @@ function nrf(hardware, callback) { nrf.getStates = function (list, cb) { var registersNeeded = registersForMnemonics(list), states = Object.create(null); + // console.log("registers needed", list, registersNeeded); function processInquiryForRegister(reg, cb) { // TODO: execCommand always reads register 0x07 but we're not optimizing for that // TODO: we could probably also eliminate re-fetch of 0x07 during IRQ processing var iq = registersNeeded[reg]; - //reg = +reg; - // WORKAROUND: https://github.com/tessel/beta/issues/204 - reg = parseInt(reg, 10); + reg = +reg; nrf.execCommand(['R_REGISTER',reg], iq.len, function (e,d) { if (e) return cb(e); @@ -206,12 +155,11 @@ function nrf(hardware, callback) { states[mnem] = (d[0] & m.mask) >> m.rightmostBit; }); if (iq.solo) states[iq.solo] = d; - cb(null, '-'); // workaround for https://github.com/tessel/beta/issues/85 + cb(); }); } forEachWithCB.call(Object.keys(registersNeeded), processInquiryForRegister, function (e) { if (nrf._debug) console.log('gotStates', states, e); - console.log('got states', states); cb(e,states); }); }; @@ -223,9 +171,7 @@ function nrf(hardware, callback) { var registersNeeded = registersForMnemonics(Object.keys(vals)); function processInquiryForRegister(reg, cb) { var iq = registersNeeded[reg]; - //reg = +reg; // was string key, now convert back to number - // WORKAROUND: https://github.com/tessel/beta/issues/204 - reg = parseInt(reg, 10); + reg = +reg; // was string key, now convert back to number // if a register is "full" we can simply overwrite, otherwise we must read+merge // NOTE: high bits in RF_CH/PX_PW_Pn are *reserved*, i.e. technically need merging @@ -239,9 +185,8 @@ function nrf(hardware, callback) { settlingNeeded = 0; if (iq.solo) val = vals[iq.solo]; // TODO: refactor so as not to fetch in the first place! iq.arr.forEach(function (mnem) { - // WORKAROUND: https://github.com/tessel/beta/issues/87 - if (typeof vals[mnem] === 'boolean') vals[mnem] = (vals[mnem]) ? 1 : 0; - + vals[mnem] = (vals[mnem] << 0); // convert boolean to number + var m = maskForMnemonic(mnem); if (mnem === 'PWR_UP') { var rising = !(d[0] & m.mask) && vals[mnem]; @@ -255,9 +200,7 @@ function nrf(hardware, callback) { }); if (val !== d[0] || reg === _statusReg) nrf.execCommand(['W_REGISTER', reg], [val], function (e,d) { if (settlingNeeded) nrf.blockMicroseconds(settlingNeeded); // see p.24 - // WORKAROUND: https://github.com/tessel/beta/issues/207 - //cb.apply(this, arguments); - cb.call(this, e,d); + cb.apply(this, arguments); }); else cb(null, '-'); // don't bother writing if value hasn't changed (unless status, which clears bits) }); @@ -384,6 +327,7 @@ function nrf(hardware, callback) { // caller must know pipe and provide its params! nrf.readPayload = function (opts, cb) { + console.log("reading payload"); if (!cb) cb = _nop; if (opts.width === 'auto') nrf.execCommand('R_RX_PL_WID', 1, function (e,d) { if (e) return finish(e); @@ -458,6 +402,7 @@ function nrf(hardware, callback) { if (checking && !irq) return; // avoid simultaneous checks unless latest triggered by real IRQ else checking = true; nrf.getStates(['RX_P_NO','TX_DS','MAX_RT','RX_DR'], function (e,d) { + checking = false; if (e) nrf.emit('error', e); else if (d.RX_DR && d.RX_P_NO === 0x07) setTimeout(function () { @@ -481,6 +426,7 @@ function nrf(hardware, callback) { } else if (irq) { // hybrid mode: polling, but of IRQ pin instead of nrf status // TODO: use hardware interrupt https://github.com/tessel/beta/issues/216 + console.log("watching for IRQ"); irq.watch('fall', irqListener); } else { console.warn("Recommend use with IRQ pin, fallback handling is suboptimal."); @@ -506,7 +452,7 @@ function nrf(hardware, callback) { nrf.setCE('low','stby2a'); var clearIRQ = {RX_DR:true, TX_DS:true, MAX_RT:true}, features = {EN_DPL:true, EN_ACK_PAY:true, EN_DYN_ACK:true}; - nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x00},clearIRQ,features), function (e,d) { + nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x03},clearIRQ,features), function (e,d) { if (e) return nrf.emit('error', e); nrf._irqOn(); // NOTE: on before any pipes to facilite lower-level sendPayload use ready = true; @@ -542,22 +488,28 @@ function nrf(hardware, callback) { } } nrf.openPipe = function (rx_tx, addr, opts) { + console.log("open pipe hit"); if (!ready) throw Error("Radio .begin() must be finished before a pipe can be opened."); if (typeof addr === 'number') addr = Buffer(addr.toString(16), 'hex'); - else if (typeof addr == 'string') addr = Buffer_workaround(addr, 'hex'); + else if (typeof addr == 'string') addr = Buffer(addr, 'hex'); + opts || (opts = {}); var pipe; if (rx_tx === 'rx') { var s = slotForAddr(addr); + console.log("creating rx pipe"); pipe = new PRX(s, addr, opts); + console.log("new rx pipe"); rxPipes.push(pipe); } else if (rx_tx === 'tx') { pipe = new PTX(addr, opts); + console.log("new tx pipe"); txPipes.push(pipe); } else { throw Error("Unknown pipe mode '"+rx_tx+"', must be 'rx' or 'tx'."); } + console.log("done with open pipe"); return pipe; }; nrf._nudgeTX = function () { @@ -566,11 +518,8 @@ function nrf(hardware, callback) { txQ.active = true; d.pipe._tx(d.data, function () { try { - // NOTE: skipping https://github.com/tessel/beta/issues/207 workaround since should only have 1 argument… d.cb.apply(this, arguments); - //} finally { - // WORKAROUND: https://github.com/tessel/beta/issues/198 (not quite equivalent, this drops exception!) - } catch (e) {} if (1) { + } finally { delete txQ.active; nrf._nudgeTX(); } @@ -588,9 +537,11 @@ function nrf(hardware, callback) { var s = {}, n = pipe; // TODO: what if ack'ed TX already in progress and n=0? + + var hack = {}; // workaround for https://github.com/tessel/beta/issues/314 if (addr.length > 1) s['AW'] = addr.length - 2; if (opts._primRX) { - s['PRIM_RX'] = true; + hack['PRIM_RX'] = true; if (pipe === 0) rxP0 = this; if (opts.autoAck) nrf._prevSender = null; // make sure TX doesn't skip setup } @@ -604,15 +555,20 @@ function nrf(hardware, callback) { s['ENAA_P'+n] = true; // must be set for DPL (…not sure why) s['DPL_P'+n] = true; } else { + console.log("RX_PW", 'RX_PW_P'+n, this._size); s['RX_PW_P'+n] = this._size; s['ENAA_P'+n] = opts.autoAck; s['DPL_P'+n] = false; } - nrf.setStates(s, function (e) { - if (opts._primRX) nrf.setCE(true,'stby2a'); - if (e) this.emit('error', e); - else this.emit('ready'); // TODO: eliminate need to wait for this (setup on first _rx/_tx?) + console.log("PxX", s, hack); + nrf.setStates(hack, function(e){ + nrf.setStates(s, function (e) { + if (opts._primRX) nrf.setCE(true,'stby2a'); + if (e) this.emit('error', e); + else this.emit('ready'); // TODO: eliminate need to wait for this (setup on first _rx/_tx?) + }.bind(this)); }.bind(this)); + var irqHandler = this._rx.bind(this); nrf.addListener('interrupt', irqHandler); @@ -642,13 +598,20 @@ function nrf(hardware, callback) { } if (this._sendOpts.ack) { if (rxP0) rxP0._pipe = -1; // HACK: avoid the pipe-0 PRX from reading our ack payload - s['RX_ADDR_P0'] = this._addr; + // s['RX_ADDR_P0'] = this._addr; + // s['RXADDRP0'] = this._addr; + // s['RX_ADDR_P0'] = s['TX_ADDR']; + var temp_buf = new Buffer(5); + s['RX_ADDR_P0'] = temp_buf.copy(this._addr); + console.log("_tx", s['RX_ADDR_P0'], s, this._addr); if ('retryCount' in this.opts) s['ARC'] = this.opts.retryCount; if ('retryDelay' in this.opts) s['ARD'] = this.opts.retryDelay/250 - 1; // TODO: shouldn't this be overrideable regardless of _sendOpts.ack?? if ('txPower' in this.opts) s['RF_PWR'] = _m.TX_POWER.indexOf(this.opts.txPower); } + console.log("before set state"); } + nrf.setStates(s, function (e) { // (± fine to call with no keys) if (e) return cb(e); var sendOpts = _extend({},this._sendOpts); @@ -662,6 +625,7 @@ function nrf(hardware, callback) { } if (this._sendOpts.ack && rxP0) { s['RX_ADDR_P0'] = rxP0._addr; + console.log("sendOpts.ack", s['RX_ADDR_P0']); rxP0._pipe = 0; } nrf.setStates(s, cb); @@ -670,9 +634,14 @@ function nrf(hardware, callback) { }.bind(this)); }; PxX.prototype._rx = function (d) { - if (d.RX_P_NO !== this._pipe) return; - if (!this._wantsRead) return; // NOTE: this could starve other RX pipes! - + if (d.RX_P_NO !== this._pipe){ + // console.log("some bad pipe", d.RX_P_NO); + return; + } + if (!this._wantsRead) { + // console.log("no wantsRead"); + return; // NOTE: this could starve other RX pipes! + } nrf.readPayload({width:this._size}, function (e,d) { if (e) this.emit('error', e); else this._wantsRead = this.push(d); @@ -680,6 +649,7 @@ function nrf(hardware, callback) { }.bind(this)); }; PxX.prototype._read = function () { + console.log("_read called"); this._wantsRead = true; nrf._checkStatus(false); }; @@ -691,7 +661,8 @@ function nrf(hardware, callback) { }; function PTX(addr,opts) { - opts = _extend({size:'auto',autoAck:true,ackPayloads:false}, opts); + // opts = _extend({size:'auto',autoAck:true,ackPayloads:false}, opts); + opts._enableRX = (opts.autoAck || opts.ackPayloads); PxX.call(this, 0, addr, opts); _extend(this._sendOpts, {ack:opts._enableRX}); @@ -699,7 +670,7 @@ function nrf(hardware, callback) { util.inherits(PTX, PxX); function PRX(pipe, addr, opts) { - opts = _extend({size:'auto',autoAck:true}, opts); + // opts = _extend({size:'auto',autoAck:true}, opts); opts._primRX = opts._enableRX = true; PxX.call(this, pipe, addr, opts); _extend(this._sendOpts, {ack:false, asAckTo:pipe}); @@ -733,12 +704,15 @@ function nrf(hardware, callback) { _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) { + // 'CONFIG', + nrf.getStates(['EN_AA','EN_RXADDR','RF_CH','RF_SETUP','DYNPD','FEATURE'], function (e,d) { + + // EN_AA','EN_RXADDR are missing...? 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)); console.log("RF_SETUP:\t",_h(d.RF_SETUP)); - console.log("CONFIG:\t\t",_h(d.CONFIG)); + // console.log("CONFIG:\t\t",_h(d.CONFIG)); console.log("DYNPD/FEATURE:\t",_h(d.DYNPD),_h(d.FEATURE)); nrf.getStates(['RF_DR_LOW','RF_DR_HIGH','EN_CRC','CRCO','RF_PWR'], function (e,d) { var isPlus = false, @@ -768,7 +742,10 @@ function nrf(hardware, callback) { }); }); }); - function _h(n) { return (Buffer.isBuffer(n)) ? '0x'+n.toString('hex') : '0x'+n.toString(16); } + function _h(n) { + // console.log("n", 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); }); From c08477be2942edd7ed3087937fcace80f42cd3d1 Mon Sep 17 00:00:00 2001 From: jialiya Date: Wed, 21 May 2014 17:30:00 -0700 Subject: [PATCH 06/16] removed all workarounds --- examples/RF24-pingpair.js | 2 +- index.js | 265 +++++++++++++++++++------------------- 2 files changed, 136 insertions(+), 131 deletions(-) diff --git a/examples/RF24-pingpair.js b/examples/RF24-pingpair.js index 34af84a..c6c0db0 100644 --- a/examples/RF24-pingpair.js +++ b/examples/RF24-pingpair.js @@ -10,7 +10,7 @@ var tessel = require('tessel'), pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2], role = 'ping'; // swap this to pong if you want to wait for receive -nrf._debug = false; +// nrf._debug = false; // nrf._debug = true; nrf.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = 2476MHz diff --git a/index.js b/index.js index 25b1192..da2629f 100644 --- a/index.js +++ b/index.js @@ -25,54 +25,103 @@ function _extend(obj) { function _nop() {} // used when a cb is not provided -exports.use = function(hardware, callback) { - return new nrf(hardware); +exports.use = function(hardware, ce, irq, callback) { + + // if we just have hardware assume it's a tessel + if (arguments.length <= 2){ + return new nrf('tessel', hardware, arguments[1]); + } else if (arguments.length >= 3){ + // if we have everything assume it's an RPi + return new nrf('pi', hardware, ce, irq, callback); + } + } -function nrf(hardware, callback) { - var _spi = "Tessel", _ce = "builtin", _irq = "-"; // only for printDetails! - var nrf = new events.EventEmitter(), +function nrf(type, hardware) { + var _spi = type; + var _ce, _irq, ce, irq, spi, callback; + var nrf = new events.EventEmitter(); + + if (type == 'tessel') { + _ce = "builtin"; + _irq = "builtin"; + callback = arguments[2]; spi = new hardware.SPI({chipSelect:hardware.digital[1], chipSelectActive: 0}), ce = hardware.digital[2], irq = hardware.digital[3]; - // nrf._debug = true; - // Tessel's transfer always returns as much data as sent - spi._nrf_transfer = function (writeBuf, readLen, cb) { - if (readLen > writeBuf.length) { - var tmpBuff = Buffer(readLen); - tmpBuff.fill(0); - writeBuf.copy(tmpBuff); - writeBuf = tmpBuff; + + // 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) } - - spi.transfer(writeBuf, function (e,d) { - // spi.activeChipSelect(false); - if (e) cb(e); // WORKAROUND: https://github.com/tessel/beta/issues/203 - else cb(null, d); - }); - }; - - - nrf._T = _extend({}, _m.TIMING, {pd2stby:4500}); // may need local override of pd2stby - - nrf.blockMicroseconds = (tessel) ? function (us) { - tessel.sleep(us); - if (nrf._debug) console.log("slept for "+us+"µs."); - } : 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; + + } else if (type == 'pi'){ + spi = hardware; + ce = arguments[2]; + irq = arguments[3]; + callback = arguments[4]; + + nrf._transfer = function(buff, len, cb) { + spi.transfer(buff, len, cb); } - if (nrf._debug) console.log("blocked for "+us+"µs."); - }; + + 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') { cb = data || _nop; data = 0; } + if (nrf._debug) console.log('execCommand', cmd, data); var cmdByte; if (typeof cmd === 'string') { @@ -98,37 +147,31 @@ function nrf(hardware, callback) { readLen = data; } - if (nrf._debug) console.log('execCommand', cmd, readLen, writeBuf); - - spi._nrf_transfer(writeBuf, readLen && readLen+1, function (e,d) { - if (nrf._debug && readLen) console.log(' - got:', 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); - var count = 0; list.forEach(function (mnem) { var _r = _m.REGISTER_MAP[mnem]; - // console.log("mnem", mnem, _m.REGISTER_MAP[mnem]); if (!_r) return console.warn("Skipping uknown mnemonic '"+mnem+"'!"); - if (_r.length === 1) _r.push(0), _r.push(8); + if (_r.length === 1) _r.push(0,8); var reg = _r[0], howManyBits = _r[2] || 1, iq = registersNeeded[reg] || (registersNeeded[reg] = {arr:[]}); iq.len = (howManyBits / 8 >> 0) || 1; - if (howManyBits < 8) iq.arr.push(mnem); else iq.solo = mnem; - - // console.log("reg", reg, "howManyBits", howManyBits, "iq", iq, "registersNeeded[reg]", registersNeeded[reg], "iq.len", iq.len, registersNeeded); - // console.log("reg", reg, "registersNeeded[reg]", registersNeeded[reg], registersNeeded); }); return registersNeeded; } + function maskForMnemonic(mnem) { var _r = _m.REGISTER_MAP[mnem], @@ -141,13 +184,11 @@ function nrf(hardware, callback) { nrf.getStates = function (list, cb) { var registersNeeded = registersForMnemonics(list), states = Object.create(null); - // console.log("registers needed", list, registersNeeded); function processInquiryForRegister(reg, cb) { // TODO: execCommand always reads register 0x07 but we're not optimizing for that // TODO: we could probably also eliminate re-fetch of 0x07 during IRQ processing var iq = registersNeeded[reg]; reg = +reg; - nrf.execCommand(['R_REGISTER',reg], iq.len, function (e,d) { if (e) return cb(e); iq.arr.forEach(function (mnem) { @@ -163,6 +204,7 @@ function nrf(hardware, callback) { cb(e,states); }); }; + var _statusReg = _m.REGISTER_MAP['STATUS'][0]; nrf.setStates = function (vals, cb) { @@ -172,7 +214,6 @@ function nrf(hardware, callback) { function processInquiryForRegister(reg, cb) { var iq = registersNeeded[reg]; reg = +reg; // was string key, now convert back to number - // if a register is "full" we can simply overwrite, otherwise we must read+merge // NOTE: high bits in RF_CH/PX_PW_Pn are *reserved*, i.e. technically need merging if (!iq.arr.length || iq.arr[0]==='RF_CH' || iq.arr[0].indexOf('RX_PW_P')===0) { @@ -185,8 +226,6 @@ function nrf(hardware, callback) { settlingNeeded = 0; if (iq.solo) val = vals[iq.solo]; // TODO: refactor so as not to fetch in the first place! iq.arr.forEach(function (mnem) { - vals[mnem] = (vals[mnem] << 0); // convert boolean to number - var m = maskForMnemonic(mnem); if (mnem === 'PWR_UP') { var rising = !(d[0] & m.mask) && vals[mnem]; @@ -198,33 +237,17 @@ function nrf(hardware, callback) { val &= ~m.mask; // clear current value val |= (vals[mnem] << m.rightmostBit) & m.mask; }); - if (val !== d[0] || reg === _statusReg) nrf.execCommand(['W_REGISTER', reg], [val], function (e,d) { + if (val !== d[0] || reg === _statusReg) nrf.execCommand(['W_REGISTER', reg], [val], function () { if (settlingNeeded) nrf.blockMicroseconds(settlingNeeded); // see p.24 cb.apply(this, arguments); }); - else cb(null, '-'); // don't bother writing if value hasn't changed (unless status, which clears bits) + else cb(null); // don't bother writing if value hasn't changed (unless status, which clears bits) }); } forEachWithCB.call(Object.keys(registersNeeded), processInquiryForRegister, cb); }; + - nrf.setCE = (tessel) ? 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) - } : 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); @@ -264,10 +287,19 @@ function nrf(hardware, callback) { else cb(null, '1Mbps'); }); } else { - if (val === '1Mbps') val = {RF_DR_LOW:false,RF_DR_HIGH:false}; - else if (val === '2Mbps') val = {RF_DR_LOW:false,RF_DR_HIGH:true}; - else if (val == '250kbps') val = {RF_DR_LOW:true,RF_DR_HIGH:false}; - else throw Error("dataRate must be one of '1Mbps', '2Mbps', or '250kbps'."); + switch (val) { + case '1Mbps': + val = {RF_DR_LOW:false,RF_DR_HIGH:false}; + break; + case '2Mbps': + val = {RF_DR_LOW:false,RF_DR_HIGH:true}; + break; + case '250kbps': + val = {RF_DR_LOW:true,RF_DR_HIGH:false}; + break; + default: + throw Error("dataRate must be one of '1Mbps', '2Mbps', or '250kbps'."); + } nrf.setStates(val, cb); } return this; @@ -295,10 +327,19 @@ function nrf(hardware, callback) { else cb(null, 1); }); } else { - if (val === 0) val = {EN_CRC:false,CRCO:0}; - else if (val === 1) val = {EN_CRC:true,CRCO:0}; - else if (val === 2) val = {EN_CRC:true,CRCO:1}; - else throw Error("crcBytes must be 1, 2, or 0."); + switch (val) { + case 0: + val = {EN_CRC:false,CRCO:0}; + break; + case 1: + val = {EN_CRC:true,CRCO:0}; + break; + case 2: + val = {EN_CRC:true,CRCO:1}; + break; + default: + throw Error("crcBytes must be 1, 2, or 0."); + } nrf.setStates(val, cb); } return this; @@ -327,7 +368,6 @@ function nrf(hardware, callback) { // caller must know pipe and provide its params! nrf.readPayload = function (opts, cb) { - console.log("reading payload"); if (!cb) cb = _nop; if (opts.width === 'auto') nrf.execCommand('R_RX_PL_WID', 1, function (e,d) { if (e) return finish(e); @@ -402,7 +442,6 @@ function nrf(hardware, callback) { if (checking && !irq) return; // avoid simultaneous checks unless latest triggered by real IRQ else checking = true; nrf.getStates(['RX_P_NO','TX_DS','MAX_RT','RX_DR'], function (e,d) { - checking = false; if (e) nrf.emit('error', e); else if (d.RX_DR && d.RX_P_NO === 0x07) setTimeout(function () { @@ -425,8 +464,6 @@ function nrf(hardware, callback) { irq.addListener('fall', irqListener); } else if (irq) { // hybrid mode: polling, but of IRQ pin instead of nrf status - // TODO: use hardware interrupt https://github.com/tessel/beta/issues/216 - console.log("watching for IRQ"); irq.watch('fall', irqListener); } else { console.warn("Recommend use with IRQ pin, fallback handling is suboptimal."); @@ -439,7 +476,8 @@ function nrf(hardware, callback) { nrf._irqOff = function () { if (!irqOn) return; else if (irq && !tessel) irq.removeListener('fall', irqListener); - else irq.unwatch('fall'); + else if (tessel) irq.unwatch('fall'); + else clearInterval(irqListener); irqOn = false; }; @@ -452,7 +490,7 @@ function nrf(hardware, callback) { nrf.setCE('low','stby2a'); var clearIRQ = {RX_DR:true, TX_DS:true, MAX_RT:true}, features = {EN_DPL:true, EN_ACK_PAY:true, EN_DYN_ACK:true}; - nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x03},clearIRQ,features), function (e,d) { + nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x00},clearIRQ,features), function (e) { if (e) return nrf.emit('error', e); nrf._irqOn(); // NOTE: on before any pipes to facilite lower-level sendPayload use ready = true; @@ -488,28 +526,21 @@ function nrf(hardware, callback) { } } nrf.openPipe = function (rx_tx, addr, opts) { - console.log("open pipe hit"); if (!ready) throw Error("Radio .begin() must be finished before a pipe can be opened."); if (typeof addr === 'number') addr = Buffer(addr.toString(16), 'hex'); - else if (typeof addr == 'string') addr = Buffer(addr, 'hex'); - opts || (opts = {}); var pipe; if (rx_tx === 'rx') { var s = slotForAddr(addr); - console.log("creating rx pipe"); pipe = new PRX(s, addr, opts); - console.log("new rx pipe"); rxPipes.push(pipe); } else if (rx_tx === 'tx') { pipe = new PTX(addr, opts); - console.log("new tx pipe"); txPipes.push(pipe); } else { throw Error("Unknown pipe mode '"+rx_tx+"', must be 'rx' or 'tx'."); } - console.log("done with open pipe"); return pipe; }; nrf._nudgeTX = function () { @@ -537,11 +568,9 @@ function nrf(hardware, callback) { var s = {}, n = pipe; // TODO: what if ack'ed TX already in progress and n=0? - - var hack = {}; // workaround for https://github.com/tessel/beta/issues/314 if (addr.length > 1) s['AW'] = addr.length - 2; if (opts._primRX) { - hack['PRIM_RX'] = true; + s['PRIM_RX'] = true; if (pipe === 0) rxP0 = this; if (opts.autoAck) nrf._prevSender = null; // make sure TX doesn't skip setup } @@ -555,20 +584,15 @@ function nrf(hardware, callback) { s['ENAA_P'+n] = true; // must be set for DPL (…not sure why) s['DPL_P'+n] = true; } else { - console.log("RX_PW", 'RX_PW_P'+n, this._size); s['RX_PW_P'+n] = this._size; s['ENAA_P'+n] = opts.autoAck; s['DPL_P'+n] = false; } - console.log("PxX", s, hack); - nrf.setStates(hack, function(e){ - nrf.setStates(s, function (e) { - if (opts._primRX) nrf.setCE(true,'stby2a'); - if (e) this.emit('error', e); - else this.emit('ready'); // TODO: eliminate need to wait for this (setup on first _rx/_tx?) - }.bind(this)); + nrf.setStates(s, function (e) { + if (opts._primRX) nrf.setCE(true,'stby2a'); + if (e) this.emit('error', e); + else this.emit('ready'); // TODO: eliminate need to wait for this (setup on first _rx/_tx?) }.bind(this)); - var irqHandler = this._rx.bind(this); nrf.addListener('interrupt', irqHandler); @@ -598,20 +622,13 @@ function nrf(hardware, callback) { } if (this._sendOpts.ack) { if (rxP0) rxP0._pipe = -1; // HACK: avoid the pipe-0 PRX from reading our ack payload - // s['RX_ADDR_P0'] = this._addr; - // s['RXADDRP0'] = this._addr; - // s['RX_ADDR_P0'] = s['TX_ADDR']; - var temp_buf = new Buffer(5); - s['RX_ADDR_P0'] = temp_buf.copy(this._addr); - console.log("_tx", s['RX_ADDR_P0'], s, this._addr); + s['RX_ADDR_P0'] = this._addr; if ('retryCount' in this.opts) s['ARC'] = this.opts.retryCount; if ('retryDelay' in this.opts) s['ARD'] = this.opts.retryDelay/250 - 1; // TODO: shouldn't this be overrideable regardless of _sendOpts.ack?? if ('txPower' in this.opts) s['RF_PWR'] = _m.TX_POWER.indexOf(this.opts.txPower); } - console.log("before set state"); } - nrf.setStates(s, function (e) { // (± fine to call with no keys) if (e) return cb(e); var sendOpts = _extend({},this._sendOpts); @@ -625,7 +642,6 @@ function nrf(hardware, callback) { } if (this._sendOpts.ack && rxP0) { s['RX_ADDR_P0'] = rxP0._addr; - console.log("sendOpts.ack", s['RX_ADDR_P0']); rxP0._pipe = 0; } nrf.setStates(s, cb); @@ -634,14 +650,9 @@ function nrf(hardware, callback) { }.bind(this)); }; PxX.prototype._rx = function (d) { - if (d.RX_P_NO !== this._pipe){ - // console.log("some bad pipe", d.RX_P_NO); - return; - } - if (!this._wantsRead) { - // console.log("no wantsRead"); - return; // NOTE: this could starve other RX pipes! - } + if (d.RX_P_NO !== this._pipe) return; + if (!this._wantsRead) return; // NOTE: this could starve other RX pipes! + nrf.readPayload({width:this._size}, function (e,d) { if (e) this.emit('error', e); else this._wantsRead = this.push(d); @@ -649,7 +660,6 @@ function nrf(hardware, callback) { }.bind(this)); }; PxX.prototype._read = function () { - console.log("_read called"); this._wantsRead = true; nrf._checkStatus(false); }; @@ -661,8 +671,7 @@ function nrf(hardware, callback) { }; function PTX(addr,opts) { - // opts = _extend({size:'auto',autoAck:true,ackPayloads:false}, opts); - + opts = _extend({size:'auto',autoAck:true,ackPayloads:false}, opts); opts._enableRX = (opts.autoAck || opts.ackPayloads); PxX.call(this, 0, addr, opts); _extend(this._sendOpts, {ack:opts._enableRX}); @@ -670,7 +679,7 @@ function nrf(hardware, callback) { util.inherits(PTX, PxX); function PRX(pipe, addr, opts) { - // opts = _extend({size:'auto',autoAck:true}, opts); + opts = _extend({size:'auto',autoAck:true}, opts); opts._primRX = opts._enableRX = true; PxX.call(this, pipe, addr, opts); _extend(this._sendOpts, {ack:false, asAckTo:pipe}); @@ -704,15 +713,12 @@ function nrf(hardware, callback) { _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) ); - // 'CONFIG', - nrf.getStates(['EN_AA','EN_RXADDR','RF_CH','RF_SETUP','DYNPD','FEATURE'], function (e,d) { - - // EN_AA','EN_RXADDR are missing...? + 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)); console.log("RF_SETUP:\t",_h(d.RF_SETUP)); - // console.log("CONFIG:\t\t",_h(d.CONFIG)); + console.log("CONFIG:\t\t",_h(d.CONFIG)); console.log("DYNPD/FEATURE:\t",_h(d.DYNPD),_h(d.FEATURE)); nrf.getStates(['RF_DR_LOW','RF_DR_HIGH','EN_CRC','CRCO','RF_PWR'], function (e,d) { var isPlus = false, @@ -743,7 +749,6 @@ function nrf(hardware, callback) { }); }); function _h(n) { - // console.log("n", n); return (Buffer.isBuffer(n)) ? '0x'+n.toString('hex') : '0x'+n.toString(16); } }; From 4699eb12669237e23677e05ccea2c7d4c8c4faf8 Mon Sep 17 00:00:00 2001 From: jialiya Date: Wed, 21 May 2014 23:31:19 -0700 Subject: [PATCH 07/16] new proposed API --- examples/RF24-pingpair.js | 96 +++++++++++++++++++-------------------- index.js | 55 ++++++++++++++++++++-- 2 files changed, 98 insertions(+), 53 deletions(-) diff --git a/examples/RF24-pingpair.js b/examples/RF24-pingpair.js index c6c0db0..a25bd1d 100644 --- a/examples/RF24-pingpair.js +++ b/examples/RF24-pingpair.js @@ -6,62 +6,62 @@ var tessel = require('tessel'), NRF24 = require("../"), - nrf = NRF24.use(tessel.port('a')), pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2], - role = 'ping'; // swap this to pong if you want to wait for receive + role = 'pong'; // swap this to pong if you want to wait for receive -// nrf._debug = false; -// nrf._debug = true; - -nrf.channel(0x4c) // set the RF channel to 76. Frequency = 2400 + RF_CH [MHz] = 2476MHz +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}) - .begin(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) - */ + .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 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], {size: 8}); - 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); - }); - } - }); + 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/index.js b/index.js index da2629f..b115705 100644 --- a/index.js +++ b/index.js @@ -9,6 +9,8 @@ try { tessel = require('tessel'); } catch (e) {} +var nrfOpts = {}; + function forEachWithCB(fn, cb) { var process = q(1); this.forEach(function (d) { process.defer(fn, d); }); @@ -25,18 +27,62 @@ function _extend(obj) { function _nop() {} // used when a cb is not provided -exports.use = function(hardware, ce, irq, callback) { +function nrfWrapper(){} +nrfWrapper.use =function(hardware, ce, irq, callback) { + var nrfObj; // if we just have hardware assume it's a tessel if (arguments.length <= 2){ - return new nrf('tessel', hardware, arguments[1]); + nrfObj = new nrf('tessel', hardware, arguments[1]); } else if (arguments.length >= 3){ // if we have everything assume it's an RPi - return new nrf('pi', hardware, ce, irq, callback); + nrfObj = new nrf('pi', hardware, ce, irq, callback); } + // 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'); + }); + }) + + 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, callback; @@ -494,9 +540,8 @@ function nrf(type, hardware) { 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'); + if (cb) cb(); }); - if (cb) nrf.once('ready', cb); }; nrf.end = function (cb) { var pipes = txPipes.concat(rxPipes); From 099727029ab0b23de5c7f8f78e017f11ae9cc34b Mon Sep 17 00:00:00 2001 From: jialiya Date: Thu, 22 May 2014 00:11:07 -0700 Subject: [PATCH 08/16] making callback optional --- index.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index b115705..a0e2b71 100644 --- a/index.js +++ b/index.js @@ -33,10 +33,11 @@ nrfWrapper.use =function(hardware, ce, irq, callback) { var nrfObj; // if we just have hardware assume it's a tessel if (arguments.length <= 2){ - nrfObj = new nrf('tessel', hardware, arguments[1]); + 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, callback); + nrfObj = new nrf('pi', hardware, ce, irq); } // go through and apply all options forEachWithCB.call(Object.keys(nrfOpts), function(key, cb){ @@ -45,6 +46,7 @@ nrfWrapper.use =function(hardware, ce, irq, callback) { // on finish emit ready nrfObj.begin(function(){ nrfObj.emit('ready'); + callback && callback(); }); }) @@ -85,13 +87,12 @@ module.exports = nrfWrapper; function nrf(type, hardware) { var _spi = type; - var _ce, _irq, ce, irq, spi, callback; + var _ce, _irq, ce, irq, spi; var nrf = new events.EventEmitter(); if (type == 'tessel') { _ce = "builtin"; _irq = "builtin"; - callback = arguments[2]; spi = new hardware.SPI({chipSelect:hardware.digital[1], chipSelectActive: 0}), ce = hardware.digital[2], irq = hardware.digital[3]; @@ -133,7 +134,6 @@ function nrf(type, hardware) { spi = hardware; ce = arguments[2]; irq = arguments[3]; - callback = arguments[4]; nrf._transfer = function(buff, len, cb) { spi.transfer(buff, len, cb); @@ -540,8 +540,11 @@ function nrf(type, hardware) { 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'); if (cb) cb(); }); + // .use now emits and handles .ready + // if (cb) nrf.once('ready', cb); }; nrf.end = function (cb) { var pipes = txPipes.concat(rxPipes); From c559b27345138401828d52a6ec7354db206cc001 Mon Sep 17 00:00:00 2001 From: jialiya Date: Thu, 22 May 2014 00:46:01 -0700 Subject: [PATCH 09/16] fixing RPi example to work with new API --- test.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) 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]), From fa7c2f381c2f8739b6d61a1b1ab21f970a11b210 Mon Sep 17 00:00:00 2001 From: jialiya Date: Thu, 22 May 2014 01:11:25 -0700 Subject: [PATCH 10/16] fixing example in readme --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 148bb35..1707caa 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # node-nrf -nRF24L01+ driver library for node.js on the Raspberry Pi (etc.) +nRF24L01+ driver library for node.js on platforms like the [Raspberry Pi](http://en.wikipedia.org/wiki/Raspberry_Pi) and [others](http://tessel.io/). -Not to be confused with [node-rf24](https://github.com/natevw/node-rf24) which is an unfinished (and broken by recent V8/libuv changes) wrapper around the RasPi port of the C++ [RF24 library](https://github.com/stanleyseow/RF24). +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. -This module is implemented in pure JavaScript, on top of native [SPI bindings](https://github.com/natevw/pi-spi). +## 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 @@ -14,12 +18,15 @@ This module is implemented in pure JavaScript, on top of native [SPI bindings](h ## Usage -Streams! +[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 @@ -28,7 +35,7 @@ radio.begin(function () { The nRF24L01+ radios provide "logic pipes" which can be used as node.js streams. These are opened for a given receiver address according to their primary direction. However, since the transceiver hardware supports sending data payloads with its acknowlegement packets, the both primary directions provide duplex streams — acknowlegement payload data can be read from a `'tx'` stream if the `ackPayloads` option is set true, and written to any `'rx'` stream. -> **TBD**: expand this section ["non"-stream usage, pipe options, optional callbacks, etc.] +> **TBD**: expand this section ["non"-stream usage, pipe options, optional callbacks, buffering and splitting/joining streams from 32-byte chunks, etc.] ## API @@ -55,21 +62,51 @@ The nRF24L01+ radios provide "logic pipes" which can be used as node.js streams. * `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.openPipe(mode, addr, opts)` — Returns a stream. **TBD**: … +* `radio.openPipe(mode, addr, opts)` — Returns a stream representing a "data pipe" on the radio. See pipe details section below. * `radio.end(cb)` — Closes all pipes and powers down the radio. Callback is optional. +#### 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.) + +* The `addr` parameter is simply the aforementioned address of the data pipe, usually as a 5 byte buffer. As a shorthand, you can also pass raw numbers e.g. `0xEF` for addresses, but note that the most significant nibble must have bit(s) set for this to work as expected — a literal `0x0000000A` in your source code will get processed as the invalid `Buffer("a", 'hex')` rather than a 3-byte address. + +* For `'rx'` mode pipes things are a little more complicated. The nRF24 chip supports listening simultaneously for up to 6 data channel pipes, *but* four of these logical channel address assignments must differ in only one byte from the first address. Also, the sixth address slot will be temporarily "borrowed" whenever any `'tx'`-mode pipe needs to listen for an acknowlegement packet. Basically for `'rx'` pipes, pass a 3, 4 or 5 byte `Buffer` the first time you call it, and it will be assigned a hardware pipe number automatically. Subsequent calls should ideally be single-byte `Buffers` only, representing the least significant byte in the address of up to four more pipes. If you open another pipe with a 3/4/5-byte address instead (or additionally), be aware that you may miss packets in certain situations. For example if you first open a pipe with address `0x123456`, you could also listen for `0x57`through `0x5A`. You could also open one last `'rx'` pipe with address `0x998877` — but if there were open `'tx'` pipes as well, and any of them needed to listen for acknowlegements, you could end up occasionally missing transmissions to this sixth address. [**TBD** diagram of "slots"?] + +* Finally, via the `opts` parameter you can set a fixed payload `size` (in bytes, defaults to `'auto'`) or disable auto-acknowlegement with `autoAck:false` (defaults to `true`). Note that if you want to disable auto-acknowlegment, you *must* also set a payload size — for some reason these are linked in the nRF24 feature set. + +* For `'tx'` pipes, the `opts` parameter also lets you provide individual `retryCount`, `retryDelay`, `txPower` options instead of using the `radio.autoRetransmit` and `radio.transmitPower` methods; if you do this you should provide values for *every* `'tx`' pipe you open, to make sure the hardware configuration gets updated between each different transmission. [**TBD**: what is `ackPayloads` option supposed to do for `'tx`` pipes?] + +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 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.addressWidth(width, cb)` — Set (or read, when no value is provided) the receiver address width used. 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 determined automatically whenever `radio.openPipe()` so it is typically not necessary 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 + +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`: + + sudo modprobe -r spi_bcm2708 + sudo modprobe spi_bcm2708 + +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 > **TBD**: [BSD-2-Clause template] From ea8271b8934f747296f4ac1488519e9ee77bbc6d Mon Sep 17 00:00:00 2001 From: jialiya Date: Fri, 23 May 2014 00:22:17 -0700 Subject: [PATCH 11/16] tessel to tessel example --- examples/RF24-pingpair.js | 2 +- examples/nrf24.js | 58 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 examples/nrf24.js diff --git a/examples/RF24-pingpair.js b/examples/RF24-pingpair.js index a25bd1d..2c50d51 100644 --- a/examples/RF24-pingpair.js +++ b/examples/RF24-pingpair.js @@ -7,7 +7,7 @@ var tessel = require('tessel'), NRF24 = require("../"), pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2], - role = 'pong'; // swap this to pong if you want to wait for receive + role = 'ping'; // 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 diff --git a/examples/nrf24.js b/examples/nrf24.js new file mode 100644 index 0000000..3f59b1e --- /dev/null +++ b/examples/nrf24.js @@ -0,0 +1,58 @@ +/* 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 From edf8494e576d1457a6d6edf2617c7706ef3634cc Mon Sep 17 00:00:00 2001 From: jialiya Date: Fri, 23 May 2014 00:32:07 -0700 Subject: [PATCH 12/16] ping pong whatever --- examples/nrf24.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nrf24.js b/examples/nrf24.js index 3f59b1e..f8253be 100644 --- a/examples/nrf24.js +++ b/examples/nrf24.js @@ -6,7 +6,7 @@ var tessel = require('tessel'), NRF24 = require("../"), pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2], - role = 'pong'; // swap this to pong if you want to wait for receive + role = 'ping'; // 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 From a97b3690d06e666800140ac723dbf915cfd6068f Mon Sep 17 00:00:00 2001 From: Tim Cameron Ryan Date: Fri, 23 May 2014 00:42:45 -0700 Subject: [PATCH 13/16] Adds elementary test case. --- package.json | 13 +++++++--- test/test.js | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++ testsuite.js | 10 ++++++++ 3 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 test/test.js create mode 100755 testsuite.js diff --git a/package.json b/package.json index a1e7c7a..5fb9f12 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "nRF24L01 driver library", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "./testsuite.js" }, "repository": { "type": "git", @@ -28,9 +28,16 @@ "queue-async": "~1.0.4", "pi-pins": "^1.0.0" }, - // prevent tessel from including pi files "hardware": { "pi-spi": false, - "pi-pins": false + "pi-pins": false, + "tape": false, + "tap": false, + "shelljs": false + }, + "devDependencies": { + "tape": "~2.3.2", + "tap": "git+https://github.com/tcr/node-tap.git#3cb76e", + "shelljs": "~0.3.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 diff --git a/testsuite.js b/testsuite.js new file mode 100755 index 0000000..1153802 --- /dev/null +++ b/testsuite.js @@ -0,0 +1,10 @@ +#!/usr/bin/env node + +require('shelljs/global'); + +var port = process.env.ACCEL_PORT || 'A'; +var cmd = './node_modules/.bin/tap --timeout 90 -e "tessel run {} ' + port + '" test/*.js'; + +// execute +cd(__dirname) +process.exit(exec(cmd).code); From 62d2a1d8fd0bfd6e6f16a84b2f4c390ced6a2f42 Mon Sep 17 00:00:00 2001 From: Tim Cameron Ryan Date: Fri, 23 May 2014 18:23:50 -0700 Subject: [PATCH 14/16] Replaces tests with tinytap. --- package.json | 11 +++-------- testsuite.js | 10 ---------- 2 files changed, 3 insertions(+), 18 deletions(-) delete mode 100755 testsuite.js diff --git a/package.json b/package.json index 5fb9f12..d4c2f91 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "nRF24L01 driver library", "main": "index.js", "scripts": { - "test": "./testsuite.js" + "test": "tinytap -e 'tessel run {}' test/*.js" }, "repository": { "type": "git", @@ -30,14 +30,9 @@ }, "hardware": { "pi-spi": false, - "pi-pins": false, - "tape": false, - "tap": false, - "shelljs": false + "pi-pins": false }, "devDependencies": { - "tape": "~2.3.2", - "tap": "git+https://github.com/tcr/node-tap.git#3cb76e", - "shelljs": "~0.3.0" + "tinytap": "~0.0.2" } } diff --git a/testsuite.js b/testsuite.js deleted file mode 100755 index 1153802..0000000 --- a/testsuite.js +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env node - -require('shelljs/global'); - -var port = process.env.ACCEL_PORT || 'A'; -var cmd = './node_modules/.bin/tap --timeout 90 -e "tessel run {} ' + port + '" test/*.js'; - -// execute -cd(__dirname) -process.exit(exec(cmd).code); From 90233ff2d23eaadb93c87a2d8f4dd79ddea2eba4 Mon Sep 17 00:00:00 2001 From: Frijol Date: Wed, 28 May 2014 05:46:31 -0700 Subject: [PATCH 15/16] updated example to match style of other examples --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a1e7c7a..9a604d5 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "queue-async": "~1.0.4", "pi-pins": "^1.0.0" }, - // prevent tessel from including pi files + "hardware": { "pi-spi": false, "pi-pins": false From e6b0715d5009d10f8ea2d079ff61f4ef88f9743b Mon Sep 17 00:00:00 2001 From: Frijol Date: Wed, 28 May 2014 05:46:31 -0700 Subject: [PATCH 16/16] updated example to match style of other examples --- examples/nrf.js | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 examples/nrf.js diff --git a/examples/nrf.js b/examples/nrf.js new file mode 100644 index 0000000..b8c22cb --- /dev/null +++ b/examples/nrf.js @@ -0,0 +1,68 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +/********************************************* +This nRF24 example requires two nRF24 modules +(and ideally two Tessels). Put one Tessel + +nRF24 module on "ping" mode and the other +pair on "pong" mode to make them send +information back and forth. +*********************************************/ + +var tessel = require('tessel'); +var NRF24 = require('../'); // Replace '../' with 'rf-nrf24' in your own code +var pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2]; + +var role = 'ping'; // 'ping' to send; 'pong' to receive + +// Set up NRF +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; + +// Wait for the module to connect +nrf.on('ready', function () { + setTimeout(function(){ + nrf.printDetails(); + }, 5000); + + if (role === 'ping') { + console.log('PING out'); + // If set to 'ping' mode, send data + 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 buff = new Buffer(4); // set buff len of 8 for compat with maniac bug's RF24 lib + buff.fill(0); + buff.writeUInt32BE(n++); + console.log("Sending", n); + tx.write(buff); + }, 5e3); // transmit every 5 seconds + }); + rx.on('data', function (data) { + console.log("Got response back:", data); + }); + } else { + console.log("PONG back"); + // If set to 'pong' mode, receive data + var rx = nrf.openPipe('rx', pipes[0], {size: 4}); + tx = nrf.openPipe('tx', pipes[1], {autoAck: false}); + rx.on('data', function (data) { + console.log("Got data, will respond", data); + tx.write(data); + }); + tx.on('error', function (err) { + console.warn("Error sending reply.", err); + }); + } +}); + +// hold this process open +process.ref(); diff --git a/package.json b/package.json index a1e7c7a..9a604d5 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "queue-async": "~1.0.4", "pi-pins": "^1.0.0" }, - // prevent tessel from including pi files + "hardware": { "pi-spi": false, "pi-pins": false