├── .gitignore ├── README.md ├── index.js ├── magicnums.js ├── package.json ├── test-details.js ├── test.js └── test2.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | ._* 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-nrf 2 | 3 | nRF24L01+ driver library for node.js on platforms like the [Raspberry Pi](http://en.wikipedia.org/wiki/Raspberry_Pi) and [others](http://tessel.io/). 4 | 5 | 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. 6 | 7 | ## See also? 8 | 9 | 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). 10 | 11 | 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. 12 | 13 | 14 | ## Installation 15 | 16 | `npm install nrf` 17 | 18 | 19 | ## Usage 20 | 21 | [Streams](https://github.com/substack/stream-handbook#readme)! 22 | 23 | ``` 24 | var radio = require('nrf').connect(spiDev, cePin, irqPin); 25 | radio.channel(0x4c).dataRate('1Mbps').crcBytes(2).autoRetransmit({count:15, delay:4000}); 26 | radio.begin(function () { 27 | var rx = radio.openPipe('rx', 0xF0F0F0F0E1), 28 | tx = radio.openPipe('tx', 0xF0F0F0F0D2); 29 | rx.pipe(tx); // echo back everything 30 | }); 31 | ``` 32 | 33 | 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. 34 | 35 | > **TBD**: expand this section ["non"-stream usage, pipe options, optional callbacks, buffering and splitting/joining streams from 32-byte chunks, etc.] 36 | 37 | 38 | ## API 39 | 40 | ### Initialization 41 | 42 | * `var radio = nrf.connect(spiDev, cePin, irqPin)` — Initialize a radio object using the given hardware pinouts. Under Linux, `spiDev` is a device like "/dev/spidev0.0" and must be accessible by your process's user. `cePin` and `irqPin` are GPIO port numbers (`irqPin` is optional but highly recommended — without it the library must resort to polling which is slower and more processor/power intensive) and these GPIO ports must also be accessible by your process's user. This does essentially no communication with (and no configuration of) the radio; use the configuration methods below and `radio.begin()` to set up. 43 | 44 | * `radio.reset(cb)` — Resets the transciever to its default settings and flushes its transmit/receive queues. Most of this (i.e. queue flushing and low-level settings) is done by `.begin()` and so calling reset is *not* necessary if the five transceiver configuration options below (channel/dataRate/transmitPower/crcBytes/autoRetransmit) are being written anyway. 45 | 46 | ### Transceiver configuration 47 | 48 | * `radio.channel(num, cb)` — Set (or read, when no value is provided) the radio frequency channel. Callback is optional. This must be the same on all transceivers that wish to communicate. Default is `0x02`. 49 | 50 | * `radio.dataRate(rate, cb)` — Set (or read, when no value is provided) the channel data rate. Callback is optional. This must be the same on all transeivers that wish to communicate. Must be one of `['250kbps', '1Mbps','2Mbps']`. Default is `'2Mbps'`. 51 | 52 | * `radio.transmitPower(rate, cb)` — Set (or read, when no value is provided) the RF output power. Callback is optional. Must be one of `['PA_MIN', 'PA_LOW', 'PA_HIGH', 'PA_MAX']`. Default is `'PA_MAX'`. 53 | 54 | * `radio.crcBytes(numBytes, cb)` — Set (or read, when no rate is provided) the size of packet checksums. Callback is optional. This must be the same on all transeivers that wish to communicate. Choose `1` or `2` bytes, or `0` to disable CRC checksums. Default is `1`. 55 | 56 | * `radio.autoRetransmit(opts, cb)` — Set (or read, when no value is provided) the packet retransmission parameters. Callback is optional. Provide a dictionary with one or two keys: `delay` to set the retry spacing in microseconds (will be rounded to a multiple of 250µs) and `count` to set the maximum number of retries. (See the datasheet for the minimum delay necessary based on data rate and packet size.) Default is `{delay:250,count:3}`. 57 | 58 | ### Sending/receiving 59 | 60 | * `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.) 61 | 62 | * `radio.openPipe(mode, addr, opts)` — Returns a stream representing a "data pipe" on the radio. See pipe details section below. 63 | 64 | * `radio.end(cb)` — Closes all pipes and powers down the radio. Callback is optional. 65 | 66 | #### Pipe details 67 | 68 | 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. 69 | 70 | * 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.) 71 | 72 | * 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. 73 | 74 | * 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"?] 75 | 76 | * 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. 77 | 78 | * 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?] 79 | 80 | 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. 81 | 82 | 83 | ### Low-level methods 84 | 85 | 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™. 86 | 87 | * `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`. 88 | 89 | * `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`. 90 | 91 | > **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)` 92 | 93 | 94 | ## Troubleshooting 95 | 96 | ### node-nrf (or pi-spi) not working after using C++ RF24 library 97 | 98 | 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`: 99 | 100 | sudo modprobe -r spi_bcm2708 101 | sudo modprobe spi_bcm2708 102 | 103 | or 104 | 105 | sudo modprobe -r spi_bcm2835 106 | sudo modprobe spi_bcm2835 107 | 108 | See [this comment](https://github.com/natevw/node-nrf/issues/1#issuecomment-32395546) for a bit more discussion. 109 | 110 | ### TBD: gather more advice (or link to a wiki page?) 111 | 112 | ## License 113 | 114 | > **TBD**: [BSD-2-Clause template] 115 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var q = require('queue-async'), 2 | fifo = require('fifolock'), 3 | stream = require('stream'), 4 | util = require('util'), 5 | events = require('events'), 6 | SPI = require('pi-spi'), 7 | GPIO = require('pi-pins'), 8 | _m = require("./magicnums"); 9 | 10 | var mutex = fifo(); // HACK: avoid https://github.com/natevw/node-nrf/commit/8d80dabde1026e949f4eb4ea6d25624cbf3c70ec 11 | function forEachWithCB(fn, cb) { cb = mutex.TRANSACTION_WRAPPER(cb, function () { 12 | var process = q(1); 13 | this.forEach(function (d) { process.defer(fn, d); }); 14 | process.awaitAll(cb); 15 | }.bind(this)); } 16 | 17 | function _extend(obj) { 18 | for (var i = 1, len = arguments.length; i < len; i++) { 19 | var nxt = arguments[i]; 20 | Object.keys(nxt).forEach(function (k) { obj[k] = nxt[k]; }); 21 | } 22 | return obj; 23 | } 24 | 25 | function _nop() {} // used when a cb is not provided 26 | 27 | 28 | exports.connect = function (spi,ce,irq) { 29 | var _spi = spi, _ce = ce, _irq = irq; // only for printDetails! 30 | var nrf = new events.EventEmitter(), 31 | spi = SPI.initialize(spi), 32 | ce = GPIO.connect(ce), 33 | irq = (arguments.length > 2) && GPIO.connect(irq); 34 | 35 | nrf._T = _extend({}, _m.TIMING, {pd2stby:4500}); // may need local override of pd2stby 36 | 37 | nrf.blockMicroseconds = function (us) { 38 | // NOTE: setImmediate/process.nextTick too slow (especially on Pi) so we just spinloop for µs 39 | var start = process.hrtime(); 40 | while (1) { 41 | var diff = process.hrtime(start); 42 | if (diff[0] * 1e9 + diff[1] >= us*1e3) break; 43 | } 44 | if (nrf._debug) console.log("blocked for "+us+"µs."); 45 | }; 46 | 47 | nrf.execCommand = function (cmd, data, cb) { // (can omit data, or specify readLen instead) 48 | if (typeof data === 'function' || typeof data === 'undefined') { 49 | cb = data || _nop; 50 | data = 0; 51 | } 52 | if (nrf._debug) console.log('execCommand', cmd, data); 53 | 54 | var cmdByte; 55 | if (typeof cmd === 'string') { 56 | cmdByte = _m.COMMANDS[cmd]; 57 | } else if (Array.isArray(cmd)) { 58 | cmdByte = _m.COMMANDS[cmd[0]] | cmd[1]; 59 | } else cmdByte = cmd; 60 | 61 | var writeBuf, 62 | readLen = 0; 63 | if (Buffer.isBuffer(data)) { 64 | writeBuf = Buffer(data.length+1); 65 | writeBuf[0] = cmdByte; 66 | Array.prototype.reverse.call(data); // data is LSByte to MSByte, see p.50 67 | data.copy(writeBuf,1); 68 | Array.prototype.reverse.call(data); // leave data how caller had. 69 | } else if (Array.isArray(data)) { 70 | data.reverse(); 71 | writeBuf = Buffer([cmdByte].concat(data)); 72 | data.reverse(); 73 | } else { 74 | writeBuf = Buffer([cmdByte]); 75 | readLen = data; 76 | } 77 | 78 | spi.transfer(writeBuf, readLen && readLen+1, function (e,d) { 79 | if (nrf._debug && readLen) console.log(' - exec read:', d); 80 | if (e) return cb(e); 81 | else return cb(null, d && Array.prototype.reverse.call(d.slice(1))); 82 | }); 83 | }; 84 | 85 | function registersForMnemonics(list) { 86 | var registersNeeded = Object.create(null); 87 | list.forEach(function (mnem) { 88 | var _r = _m.REGISTER_MAP[mnem]; 89 | if (!_r) return console.warn("Skipping uknown mnemonic '"+mnem+"'!"); 90 | if (_r.length === 1) _r.push(0,8); 91 | 92 | var reg = _r[0], 93 | howManyBits = _r[2] || 1, 94 | iq = registersNeeded[reg] || (registersNeeded[reg] = {arr:[]}); 95 | iq.len = (howManyBits / 8 >> 0) || 1; 96 | if (howManyBits < 8) iq.arr.push(mnem); 97 | else iq.solo = mnem; 98 | }); 99 | return registersNeeded; 100 | } 101 | 102 | function maskForMnemonic(mnem) { 103 | var _r = _m.REGISTER_MAP[mnem], 104 | howManyBits = _r[2] || 1, 105 | rightmostBit = _r[1], 106 | mask = 0xFF >> (8 - howManyBits) << rightmostBit; 107 | return {mask:mask, rightmostBit:rightmostBit}; 108 | } 109 | 110 | nrf.getStates = function (list, cb) { 111 | var registersNeeded = registersForMnemonics(list), 112 | states = Object.create(null); 113 | function processInquiryForRegister(reg, cb) { 114 | // TODO: execCommand always reads register 0x07 but we're not optimizing for that 115 | // TODO: we could probably also eliminate re-fetch of 0x07 during IRQ processing 116 | var iq = registersNeeded[reg]; 117 | reg = +reg; 118 | nrf.execCommand(['R_REGISTER',reg], iq.len, function (e,d) { 119 | if (e) return cb(e); 120 | iq.arr.forEach(function (mnem) { 121 | var m = maskForMnemonic(mnem); 122 | states[mnem] = (d[0] & m.mask) >> m.rightmostBit; 123 | }); 124 | if (iq.solo) states[iq.solo] = d; 125 | cb(); 126 | }); 127 | } 128 | forEachWithCB.call(Object.keys(registersNeeded), processInquiryForRegister, function (e) { 129 | if (nrf._debug) console.log('gotStates', states, e); 130 | cb(e,states); 131 | }); 132 | }; 133 | 134 | var _statusReg = _m.REGISTER_MAP['STATUS'][0]; 135 | nrf.setStates = function (vals, cb) { 136 | if (nrf._debug) console.log('setStates', vals); 137 | if (!cb) cb = _nop; 138 | var registersNeeded = registersForMnemonics(Object.keys(vals)); 139 | function processInquiryForRegister(reg, cb) { 140 | var iq = registersNeeded[reg]; 141 | reg = +reg; // was string key, now convert back to number 142 | // if a register is "full" we can simply overwrite, otherwise we must read+merge 143 | // NOTE: high bits in RF_CH/PX_PW_Pn are *reserved*, i.e. technically need merging 144 | if (!iq.arr.length || iq.arr[0]==='RF_CH' || iq.arr[0].indexOf('RX_PW_P')===0) { 145 | var val = vals[iq.solo || iq.arr[0]], 146 | buf = (Buffer.isBuffer(val)) ? val : [val]; 147 | nrf.execCommand(['W_REGISTER', reg], buf, cb); 148 | } else nrf.execCommand(['R_REGISTER', reg], 1, function (e,d) { 149 | if (e) return cb(e); 150 | var val = d[0], 151 | settlingNeeded = 0; 152 | if (iq.solo) val = vals[iq.solo]; // TODO: refactor so as not to fetch in the first place! 153 | iq.arr.forEach(function (mnem) { 154 | var m = maskForMnemonic(mnem); 155 | if (mnem === 'PWR_UP') { 156 | var rising = !(d[0] & m.mask) && vals[mnem]; 157 | if (rising) settlingNeeded = Math.max(settlingNeeded, nrf._T.pd2stby); 158 | } else if (mnem === 'PRIM_RX') { 159 | var changing = !(d[0] & m.mask) !== !vals[mnem]; 160 | if (changing) settlingNeeded = Math.max(settlingNeeded, nrf._T.stby2a); 161 | } 162 | val &= ~m.mask; // clear current value 163 | val |= (vals[mnem] << m.rightmostBit) & m.mask; 164 | }); 165 | if (val !== d[0] || reg === _statusReg) nrf.execCommand(['W_REGISTER', reg], [val], function () { 166 | if (settlingNeeded) nrf.blockMicroseconds(settlingNeeded); // see p.24 167 | cb.apply(this, arguments); 168 | }); 169 | else cb(null); // don't bother writing if value hasn't changed (unless status, which clears bits) 170 | }); 171 | } 172 | forEachWithCB.call(Object.keys(registersNeeded), processInquiryForRegister, cb); 173 | }; 174 | 175 | nrf.setCE = function (state, block) { 176 | if (typeof state === 'string') ce.mode(state); 177 | else ce.value(state); 178 | if (nrf._debug) console.log("Set CE "+state+"."); 179 | if (block) nrf.blockMicroseconds(nrf._T[block]); // (assume ce changed TX/RX mode) 180 | }; 181 | nrf.pulseCE = function (block) { 182 | nrf.setCE(true,'hce'); 183 | nrf.setCE(false,block); 184 | }; 185 | 186 | // ✓ low level interface (execCommand, getStates, setStates, pulseCE, 'interrupt') 187 | // ✓ mid level interface (channel, dataRate, power, crcBytes, autoRetransmit{count,delay}) 188 | // ✓ high level PRX (addrs) 189 | // ✓ high level PTX (addr) 190 | // - test! 191 | // - document 192 | 193 | 194 | nrf.powerUp = function (val, cb) { 195 | if (typeof val === 'function' || typeof val === 'undefined') { 196 | cb = val || _nop; 197 | nrf.getStates(['PWR_UP'], function (e,d) { cb(e, d && !!d.PWR_UP); }); 198 | } else nrf.setStates({PWR_UP:val}, cb); 199 | return this; 200 | }; 201 | 202 | nrf.channel = function (val, cb) { 203 | if (typeof val === 'function' || typeof val === 'undefined') { 204 | cb = val || _nop; 205 | nrf.getStates(['RF_CH'], function (e,d) { cb(e, d && d.RF_CH); }); 206 | } else nrf.setStates({RF_CH:val}, cb); 207 | return this; 208 | }; 209 | 210 | nrf.dataRate = function (val, cb) { 211 | if (typeof val === 'function' || typeof val === 'undefined') { 212 | cb = val || _nop; 213 | nrf.getStates(['RF_DR_LOW', 'RF_DR_HIGH'], function (e,d) { 214 | if (e) return cb(e); 215 | else if (d.RF_DR_LOW) cb(null, '250kbps'); 216 | else if (d.RF_DR_HIGH) cb(null, '2Mbps'); 217 | else cb(null, '1Mbps'); 218 | }); 219 | } else { 220 | switch (val) { 221 | case '1Mbps': 222 | val = {RF_DR_LOW:false,RF_DR_HIGH:false}; 223 | break; 224 | case '2Mbps': 225 | val = {RF_DR_LOW:false,RF_DR_HIGH:true}; 226 | break; 227 | case '250kbps': 228 | val = {RF_DR_LOW:true,RF_DR_HIGH:false}; 229 | break; 230 | default: 231 | throw Error("dataRate must be one of '1Mbps', '2Mbps', or '250kbps'."); 232 | } 233 | nrf.setStates(val, cb); 234 | } 235 | return this; 236 | }; 237 | 238 | nrf.transmitPower = function (val, cb) { 239 | if (typeof val === 'function' || typeof val === 'undefined') { 240 | cb = val || _nop; 241 | nrf.getStates(['RF_PWR'], function (e,d) { cb(e, d && _m.TX_POWER[d.RF_PWR]); }); 242 | } else { 243 | val = _m.TX_POWER.indexOf(val); 244 | if (val === -1) throw Error("Radio power must be 'PA_MIN', 'PA_LOW', 'PA_HIGH' or 'PA_MAX'."); 245 | nrf.setStates({RF_PWR:val}, cb); 246 | } 247 | return this; 248 | }; 249 | 250 | nrf.crcBytes = function (val, cb) { 251 | if (typeof val === 'function' || typeof val === 'undefined') { 252 | cb = val || _nop; 253 | nrf.getStates(['EN_CRC, CRCO'], function (e,d) { 254 | if (e) return cb(e); 255 | else if (!d.EN_CRC) cb(null, 0); 256 | else if (d.CRCO) cb(null, 2); 257 | else cb(null, 1); 258 | }); 259 | } else { 260 | switch (val) { 261 | case 0: 262 | val = {EN_CRC:false,CRCO:0}; 263 | break; 264 | case 1: 265 | val = {EN_CRC:true,CRCO:0}; 266 | break; 267 | case 2: 268 | val = {EN_CRC:true,CRCO:1}; 269 | break; 270 | default: 271 | throw Error("crcBytes must be 1, 2, or 0."); 272 | } 273 | nrf.setStates(val, cb); 274 | } 275 | return this; 276 | }; 277 | 278 | nrf.addressWidth = function (val, cb) { 279 | if (typeof val === 'function' || typeof val === 'undefined') { 280 | cb = val || _nop; 281 | nrf.getStates(['AW'], function (e,d) { cb(e, d && d.AW+2); }); 282 | } else nrf.setStates({AW:val-2}, cb); 283 | return this; 284 | }; 285 | 286 | nrf.autoRetransmit = function (val, cb) { 287 | if (typeof val === 'function' || typeof val === 'undefined') { 288 | cb = val || _nop; 289 | nrf.getStates(['ARD, ARC'], function (e,d) { cb(e, d && {count:d.ARC,delay:250*(1+d.ARD)}); }); 290 | } else { 291 | var states = {}; 292 | if ('count' in val) states['ARC'] = val.count; 293 | if ('delay' in val) states['ARD'] = val.delay/250 - 1; 294 | nrf.setStates(states, cb); 295 | } 296 | return this; 297 | }; 298 | 299 | // caller must know pipe and provide its params! 300 | nrf.readPayload = function (opts, cb) { 301 | if (!cb) cb = _nop; 302 | if (opts.width === 'auto') nrf.execCommand('R_RX_PL_WID', 1, function (e,d) { 303 | if (e) return finish(e); 304 | var width = d[0]; 305 | if (width > 32) nrf.execCommand('FLUSH_RX', function (e,d) { 306 | finish(new Error("Invalid dynamic payload size, receive queue flushed.")); // per R_RX_PL_WID details, p.51 307 | }); else read(width); 308 | }); else read(opts.width); 309 | 310 | function read(width) { 311 | nrf.execCommand('R_RX_PAYLOAD', width, finish); 312 | } 313 | 314 | function finish(e,d) { // see footnote c, p.62 315 | if (opts.leaveStatus) cb(e,d); 316 | else nrf.setStates({RX_DR:true,TX_DS:false,MAX_RT:false}, function (e2) { 317 | cb(e||e2,d); 318 | }); 319 | } 320 | }; 321 | 322 | // caller must set up any prerequisites (i.e. TX addr) and ensure no other send is pending 323 | nrf.sendPayload = function (data, opts, cb) { 324 | if (!cb) cb = _nop; 325 | if (data.length > 32) throw Error("Maximum packet size exceeded. Smaller writes, Dash!"); 326 | nrf._prevSender = null; // help PxX setup again if user sends data directly 327 | 328 | var cmd; 329 | if ('asAckTo' in opts) { 330 | cmd = ['W_ACK_PAYLOAD',opts.asAckTo]; 331 | } else if (opts.ack) { 332 | cmd = 'W_TX_PAYLOAD'; 333 | } else { 334 | cmd = 'W_TX_PD_NOACK'; 335 | } 336 | nrf.execCommand(cmd, data, function (e) { 337 | if (e) return cb(e); 338 | if (!opts.ceHigh) nrf.pulseCE('pece2csn'); 339 | // TODO: if _sendOpts.asAckTo we won't get MAX_RT interrupt — how to prevent a blocked TX FIFO? (see p.33) 340 | nrf.once('interrupt', function (d) { 341 | if (d.MAX_RT) nrf.execCommand('FLUSH_TX', function (e) { // see p.56 342 | finish(new Error("Packet timeout, transmit queue flushed.")); 343 | }); 344 | else if (!d.TX_DS) console.warn("Unexpected IRQ during transmit phase!"); 345 | else finish(); 346 | 347 | function finish(e) { // clear our interrupts, leaving RX_DR 348 | nrf.setStates({TX_DS:true,MAX_RT:true,RX_DR:false}, function () { 349 | cb(e||null); 350 | }); 351 | } 352 | }); 353 | }); 354 | }; 355 | 356 | nrf.reset = function (states, cb) { 357 | if (typeof states === 'function' || typeof states === 'undefined') { 358 | cb = states || _nop; 359 | states = _m.REGISTER_DEFAULTS; 360 | } 361 | nrf.setCE('low','stby2a'); 362 | q(1) 363 | .defer(nrf.execCommand, 'FLUSH_TX') 364 | .defer(nrf.execCommand, 'FLUSH_RX') 365 | .defer(nrf.setStates, states) 366 | .await(cb); 367 | }; 368 | 369 | var checking = false; 370 | nrf._checkStatus = function (irq) { 371 | if (nrf._debug) console.log("_checkStatus, irq =", irq, "checking =", checking); 372 | if (checking && !irq) return; // avoid simultaneous checks unless latest triggered by real IRQ 373 | else checking = true; 374 | nrf.getStates(['RX_P_NO','TX_DS','MAX_RT','RX_DR'], function (e,d) { 375 | checking = false; 376 | if (e) nrf.emit('error', e); 377 | else if (d.RX_DR && d.RX_P_NO === 0x07) setTimeout(function () { 378 | // HACK: chip seems to assert RX_DR a while before setting RX_P_NO, so poll if necessary 379 | // TODO: this may actually just happen until we reset RX_DR (maybe FLUSH_RX or similar unsyncs?) 380 | // see also note on top of datasheet p.52 about status register updated *during* IRQ transmission 381 | if (nrf._debug) console.warn("- weird status, checking again -"); 382 | nrf._checkStatus(false); 383 | }, 0); 384 | else if (irq || d.RX_P_NO !== 0x07 || d.TX_DS || d.MAX_RT) nrf.emit('interrupt', d); 385 | }); 386 | }; 387 | 388 | var irqListener = nrf._checkStatus.bind(nrf,true), 389 | irqOn = false; 390 | nrf._irqOn = function () { 391 | if (irqOn) return; 392 | else if (irq) { 393 | irq.mode('in'); 394 | irq.addListener('fall', irqListener); 395 | } else { 396 | console.warn("Recommend use with IRQ pin, fallback handling is suboptimal."); 397 | irqListener = setInterval(function () { // TODO: clear interval when there are no listeners? 398 | if (nrf.listeners('interrupt').length) nrf._checkStatus(false); 399 | }, 0); // (minimum 4ms is a looong time if hoping to quickly stream data!) 400 | } 401 | irqOn = true; 402 | }; 403 | nrf._irqOff = function () { 404 | if (!irqOn) return; 405 | else if (irq) irq.removeListener('fall', irqListener); 406 | else clearInterval(irqListener); 407 | irqOn = false; 408 | }; 409 | 410 | var ready = false, 411 | txQ = [], 412 | txPipes = [], 413 | rxPipes = [] 414 | rxP0 = null; 415 | nrf.begin = function (cb) { 416 | nrf.setCE('low','stby2a'); 417 | var clearIRQ = {RX_DR:true, TX_DS:true, MAX_RT:true}, 418 | features = {EN_DPL:true, EN_ACK_PAY:true, EN_DYN_ACK:true}; 419 | nrf.reset(_extend({PWR_UP:true, PRIM_RX:false, EN_RXADDR:0x00},clearIRQ,features), function (e) { 420 | if (e) return nrf.emit('error', e); 421 | nrf._irqOn(); // NOTE: on before any pipes to facilite lower-level sendPayload use 422 | ready = true; 423 | nrf.emit('ready'); 424 | }); 425 | if (cb) nrf.once('ready', cb); 426 | }; 427 | nrf.end = function (cb) { 428 | var pipes = txPipes.concat(rxPipes); 429 | pipes.forEach(function (pipe) { pipe.close(); }); 430 | txPipes.length = rxPipes.length = txQ.length = 0; 431 | ready = false; 432 | nrf._irqOff(); 433 | nrf.setCE(false,'stby2a'); 434 | nrf.setStates({PWR_UP:false}, function (e) { 435 | if (e) nrf.emit('error', e); 436 | else if (cb) cb(); 437 | }); 438 | }; 439 | function slotForAddr(addr) { 440 | var slot = Array(6), aw = Math.max(3,Math.min(addr.length, 5)); 441 | rxPipes.forEach(function (pipe) { slot[pipe._pipe] = pipe._addr; }); 442 | if (slot[1]) aw = slot[1].length; // address width already determined 443 | if (addr.length === 1) { // find a place in last four pipes 444 | for (var i = 2; i < 6; ++i) if (!slot[i]) return i; 445 | throw Error("No more final-byte listener addresses available!"); 446 | } else if (addr.length === aw) { // use pipe 1 or 0 447 | if (!slot[1]) return 1; 448 | else if (!slot[0]) return 0; // NOTE: using pipe 0 has caveats! 449 | else throw Error("No more "+aw+"-byte listener addresses available!"); 450 | } else { 451 | throw Error("Address 0x"+addr.toString(16)+" is of unsuitable width for use."); 452 | } 453 | } 454 | nrf.openPipe = function (rx_tx, addr, opts) { 455 | if (!ready) throw Error("Radio .begin() must be finished before a pipe can be opened."); 456 | if (typeof addr === 'number') addr = Buffer(addr.toString(16), 'hex'); 457 | opts || (opts = {}); 458 | 459 | var pipe; 460 | if (rx_tx === 'rx') { 461 | var s = slotForAddr(addr); 462 | pipe = new PRX(s, addr, opts); 463 | rxPipes.push(pipe); 464 | } else if (rx_tx === 'tx') { 465 | pipe = new PTX(addr, opts); 466 | txPipes.push(pipe); 467 | } else { 468 | throw Error("Unknown pipe mode '"+rx_tx+"', must be 'rx' or 'tx'."); 469 | } 470 | return pipe; 471 | }; 472 | nrf._nudgeTX = function () { 473 | if (txQ.active || !txQ.length) return; 474 | var d = txQ.shift(); 475 | txQ.active = true; 476 | d.pipe._tx(d.data, function () { 477 | try { 478 | d.cb.apply(this, arguments); 479 | } finally { 480 | delete txQ.active; 481 | nrf._nudgeTX(); 482 | } 483 | }); 484 | }; 485 | 486 | function PxX(pipe, addr, opts) { // base for PTX/PRX 487 | stream.Duplex.call(this,{highWaterMark:64}); 488 | this.opts = opts; 489 | this._pipe = pipe; 490 | this._addr = addr; 491 | this._size = opts.size; 492 | this._wantsRead = false; 493 | this._sendOpts = {}; 494 | 495 | var s = {}, 496 | n = pipe; // TODO: what if ack'ed TX already in progress and n=0? 497 | if (addr.length > 1) s['AW'] = addr.length - 2; 498 | if (opts._primRX) { 499 | s['PRIM_RX'] = true; 500 | if (pipe === 0) rxP0 = this; 501 | if (opts.autoAck) nrf._prevSender = null; // make sure TX doesn't skip setup 502 | } 503 | if (opts._enableRX) { 504 | s['RX_ADDR_P'+n] = addr; // TODO: AFAICT only opts._primRX should do this 505 | s['ERX_P'+n] = true; 506 | } else { 507 | s['ERX_P'+n] = false; 508 | } 509 | if (opts.size === 'auto') { 510 | s['ENAA_P'+n] = true; // must be set for DPL (…not sure why) 511 | s['DPL_P'+n] = true; 512 | } else { 513 | s['RX_PW_P'+n] = this._size; 514 | s['ENAA_P'+n] = opts.autoAck; 515 | s['DPL_P'+n] = false; 516 | } 517 | nrf.setStates(s, function (e) { 518 | if (opts._primRX) nrf.setCE(true,'stby2a'); 519 | if (e) this.emit('error', e); 520 | else this.emit('ready'); // TODO: eliminate need to wait for this (setup on first _rx/_tx?) 521 | }.bind(this)); 522 | 523 | var irqHandler = this._rx.bind(this); 524 | nrf.addListener('interrupt', irqHandler); 525 | this.once('close', function () { 526 | nrf.removeListener('interrupt', irqHandler); 527 | }); 528 | } 529 | util.inherits(PxX, stream.Duplex); 530 | PxX.prototype._write = function (buff, _enc, cb) { 531 | txQ.push({pipe:this,data:buff,cb:cb}); 532 | nrf._nudgeTX(); 533 | }; 534 | PxX.prototype._tx = function (data, cb) { // see p.75 535 | var s = {}; 536 | if (this._sendOpts.asAckTo) { 537 | // no config is needed 538 | } else if (nrf._prevSender === this) { 539 | if (rxPipes.length) { 540 | nrf.setCE('low'); // this or PWR_UP:0 are the only ways out of RX mode acc to p.22 541 | s['PRIM_RX'] = false; 542 | } 543 | } else { 544 | s['TX_ADDR'] = this._addr; 545 | if (rxPipes.length) { 546 | nrf.setCE('low'); 547 | s['PRIM_RX'] = false; 548 | } 549 | if (this._sendOpts.ack) { 550 | if (rxP0) rxP0._pipe = -1; // HACK: avoid the pipe-0 PRX from reading our ack payload 551 | s['RX_ADDR_P0'] = this._addr; 552 | if ('retryCount' in this.opts) s['ARC'] = this.opts.retryCount; 553 | if ('retryDelay' in this.opts) s['ARD'] = this.opts.retryDelay/250 - 1; 554 | // TODO: shouldn't this be overrideable regardless of _sendOpts.ack?? 555 | if ('txPower' in this.opts) s['RF_PWR'] = _m.TX_POWER.indexOf(this.opts.txPower); 556 | } 557 | } 558 | nrf.setStates(s, function (e) { // (± fine to call with no keys) 559 | if (e) return cb(e); 560 | var sendOpts = _extend({},this._sendOpts); 561 | //if (rxPipes.length) sendOpts.ceHigh = true; // PRX will already have CE high 562 | nrf.sendPayload(data, sendOpts, function (e) { 563 | if (e) return cb(e); 564 | var s = {}; // NOTE: if another TX is waiting, switching to RX is a waste… 565 | if (rxPipes.length && !this._sendOpts.asAckTo) { 566 | nrf.setCE('high'); 567 | s['PRIM_RX'] = true; 568 | } 569 | if (this._sendOpts.ack && rxP0) { 570 | s['RX_ADDR_P0'] = rxP0._addr; 571 | rxP0._pipe = 0; 572 | } 573 | nrf.setStates(s, cb); 574 | }.bind(this)); 575 | if (!rxPipes.length) nrf._prevSender = this; // we might avoid setting state next time 576 | }.bind(this)); 577 | }; 578 | PxX.prototype._rx = function (d) { 579 | if (d.RX_P_NO !== this._pipe) return; 580 | if (!this._wantsRead) return; // NOTE: this could starve other RX pipes! 581 | 582 | nrf.readPayload({width:this._size}, function (e,d) { 583 | if (e) this.emit('error', e); 584 | else this._wantsRead = this.push(d); 585 | nrf._checkStatus(false); // see footnote c, p.63 586 | }.bind(this)); 587 | }; 588 | PxX.prototype._read = function () { 589 | this._wantsRead = true; 590 | nrf._checkStatus(false); 591 | }; 592 | PxX.prototype.close = function () { 593 | if (rxP0 === this) rxP0 = null; 594 | // TODO: also update CE and RX_EN registers accordingly 595 | this.push(null); 596 | this.emit('close'); 597 | }; 598 | 599 | function PTX(addr,opts) { 600 | opts = _extend({size:'auto',autoAck:true,ackPayloads:false}, opts); 601 | opts._enableRX = (opts.autoAck || opts.ackPayloads); 602 | PxX.call(this, 0, addr, opts); 603 | _extend(this._sendOpts, {ack:opts._enableRX}); 604 | } 605 | util.inherits(PTX, PxX); 606 | 607 | function PRX(pipe, addr, opts) { 608 | opts = _extend({size:'auto',autoAck:true}, opts); 609 | opts._primRX = opts._enableRX = true; 610 | PxX.call(this, pipe, addr, opts); 611 | _extend(this._sendOpts, {ack:false, asAckTo:pipe}); 612 | } 613 | util.inherits(PRX, PxX); 614 | 615 | 616 | nrf.printStatus = function () { // for debugging 617 | nrf.getStates(['RX_DR','TX_DS','MAX_RT','RX_P_NO','TX_FULL'], function (e,d) { 618 | if (e) throw e; 619 | else console.log(irq.value() ? 'no-irq' : '-IRQ-', d); 620 | }); 621 | }; 622 | 623 | nrf.printDetails = function (cb) { // for debugging, mimic e.g. https://github.com/stanleyseow/RF24/blob/master/librf24-rpi/librf24/RF24.cpp#L318 624 | if (!cb) cb = _nop; 625 | console.log("SPI device:\t",_spi); 626 | //console.log("SPI speed:\t",'?'); 627 | console.log("CE GPIO:\t",_ce); 628 | console.log("IRQ GPIO:\t",_irq); 629 | nrf.getStates(['STATUS','RX_DR','TX_DS','MAX_RT','RX_P_NO','TX_FULL'], function (e,d) { 630 | if (e) throw e; 631 | console.log("STATUS:\t\t",_h(d.STATUS[0]),'RX_DR='+d.RX_DR,'TX_DS='+d.TX_DS,'MAX_RT='+d.MAX_RT,'RX_P_NO='+d.RX_P_NO,'TX_FULL='+d.TX_FULL); 632 | nrf.getStates(['RX_ADDR_P0','RX_ADDR_P1','RX_ADDR_P2','RX_ADDR_P3','RX_ADDR_P4','RX_ADDR_P5','TX_ADDR'], function (e,d) { 633 | 634 | console.log("RX_ADDR_P0–1:\t",_h(d.RX_ADDR_P0),_h(d.RX_ADDR_P1)); 635 | console.log("RX_ADDR_P2–5:\t",_h(d.RX_ADDR_P2),_h(d.RX_ADDR_P3),_h(d.RX_ADDR_P4),_h(d.RX_ADDR_P5)); 636 | console.log("TX_ADDR:\t",_h(d.TX_ADDR)); 637 | nrf.getStates(['RX_PW_P0','RX_PW_P1','RX_PW_P2','RX_PW_P3','RX_PW_P4','RX_PW_P5'], function (e,d) { 638 | console.log("RX_PW_P0–5:\t", 639 | _h(d.RX_PW_P0),_h(d.RX_PW_P1),_h(d.RX_PW_P2), 640 | _h(d.RX_PW_P3),_h(d.RX_PW_P4),_h(d.RX_PW_P5) 641 | ); 642 | nrf.getStates(['EN_AA','EN_RXADDR','RF_CH','RF_SETUP','CONFIG','DYNPD','FEATURE'], function (e,d) { 643 | console.log("EN_AA:\t\t",_h(d.EN_AA)); 644 | console.log("EN_RXADDR:\t",_h(d.EN_RXADDR)); 645 | console.log("RF_CH:\t\t",_h(d.RF_CH)); 646 | console.log("RF_SETUP:\t",_h(d.RF_SETUP)); 647 | console.log("CONFIG:\t\t",_h(d.CONFIG)); 648 | console.log("DYNPD/FEATURE:\t",_h(d.DYNPD),_h(d.FEATURE)); 649 | nrf.getStates(['RF_DR_LOW','RF_DR_HIGH','EN_CRC','CRCO','RF_PWR'], function (e,d) { 650 | var isPlus = false, 651 | pwrs = ('compat') ? _m.TX_POWER : ["-18dBm","-12dBm","-6dBm","0dBm"]; 652 | if (d.RF_DR_LOW) { // if set, we already know and don't need to check by toggling 653 | isPlus = true; 654 | logFinalDetails(); 655 | } else nrf.setStates({RF_DR_LOW:true}, function () { 656 | nrf.getStates(['RF_DR_LOW'], function (e,d2) { 657 | // (non-plus chips hold this bit zero even after settting) 658 | if (d2.RF_DR_LOW) isPlus = true; 659 | // …then set back to original (false) value again 660 | nrf.setStates({RF_DR_LOW:false}, function () { 661 | logFinalDetails(); 662 | }); 663 | }); 664 | }); 665 | function logFinalDetails() { 666 | console.log("Data Rate:\t", (d.RF_DR_LOW) ? "250kbps" : ((d.RF_DR_HIGH) ? "2Mbps" : "1Mbps")); 667 | console.log("Model:\t\t", (isPlus) ? "nRF24L01+" : "nRF24L01"); 668 | console.log("CRC Length:\t", (d.EN_CRC) ? ((d.CRCO) ? "16 bits" : "8 bits") : "Disabled"); 669 | console.log("PA Power:\t", pwrs[d.RF_PWR]); 670 | cb(); 671 | } 672 | }); 673 | }); 674 | }); 675 | }); 676 | }); 677 | function _h(n) { return (Buffer.isBuffer(n)) ? '0x'+n.toString('hex') : '0x'+n.toString(16); } 678 | }; 679 | 680 | nrf.on('interrupt', function (d) { if (nrf._debug) console.log("IRQ.", d); }); 681 | 682 | return nrf; 683 | } -------------------------------------------------------------------------------- /magicnums.js: -------------------------------------------------------------------------------- 1 | function _b(v) { return parseInt(v.replace(' ',''),2); } 2 | 3 | exports.COMMANDS = { 4 | R_REGISTER: _b('0000 0000'), 5 | W_REGISTER: _b('0010 0000'), 6 | R_RX_PAYLOAD: _b('0110 0001'), 7 | W_TX_PAYLOAD: _b('1010 0000'), 8 | FLUSH_TX: _b('1110 0001'), 9 | FLUSH_RX: _b('1110 0010'), 10 | REUSE_TX_PL: _b('1110 0011'), 11 | R_RX_PL_WID: _b('0110 0000'), 12 | W_ACK_PAYLOAD: _b('1010 1000'), 13 | W_TX_PD_NOACK: _b('1011 0000'), 14 | NOP: _b('1111 1111') 15 | }; 16 | 17 | exports.REGISTER_MAP = { 18 | // mnemonic addr,bit[,width] 19 | /* CONFIG */ 20 | CONFIG: [0x00], 21 | MASK_RX_DR: [0x00,6], 22 | MASK_TX_DS: [0x00,5], 23 | MASK_MAX_RT: [0x00,4], 24 | EN_CRC: [0x00,3], 25 | CRCO: [0x00,2], 26 | PWR_UP: [0x00,1], 27 | PRIM_RX: [0x00,0], 28 | /* EN_AA */ 29 | EN_AA: [0x01], 30 | ENAA_P5: [0x01,5], 31 | ENAA_P4: [0x01,4], 32 | ENAA_P3: [0x01,3], 33 | ENAA_P2: [0x01,2], 34 | ENAA_P1: [0x01,1], 35 | ENAA_P0: [0x01,0], 36 | /* EN_RXADDR */ 37 | EN_RXADDR: [0x02], 38 | ERX_P5: [0x02,5], 39 | ERX_P4: [0x02,4], 40 | ERX_P3: [0x02,3], 41 | ERX_P2: [0x02,2], 42 | ERX_P1: [0x02,1], 43 | ERX_P0: [0x02,0], 44 | /* SETUP_AW */ 45 | SETUP_AW: [0x03], 46 | AW: [0x03,0,2], 47 | /* SETUP_RETR */ 48 | SETUP_RETR: [0x04], 49 | ARD: [0x04,4,4], 50 | ARC: [0x04,0,4], 51 | /* RF_CH */ 52 | RF_CH: [0x05,0,7], 53 | /* RF_SETUP */ 54 | RF_SETUP: [0x06], 55 | CONT_WAVE: [0x06,7], 56 | RF_DR_LOW: [0x06,5], 57 | PLL_LOCK: [0x06,4], 58 | RF_DR_HIGH: [0x06,3], 59 | RF_PWR: [0x06,1,2], 60 | LNA_HCURR: [0x06,0], // NOTE: this is obsolete on the nRF24L01+ model 61 | /* STATUS */ 62 | STATUS: [0x07], 63 | RX_DR: [0x07,6], 64 | TX_DS: [0x07,5], 65 | MAX_RT: [0x07,4], 66 | RX_P_NO: [0x07,1,3], 67 | TX_FULL: [0x07,0], 68 | /* OBSERVE_TX */ 69 | OBSERVE_TX: [0x08], 70 | PLOS_CNT: [0x08,4,4], 71 | ARC_CNT: [0x08,0,4], 72 | /* RPD */ 73 | RPD: [0x09,0], 74 | /* ADDR */ 75 | RX_ADDR_P0: [0x0A,0,40], 76 | RX_ADDR_P1: [0x0B,0,40], 77 | RX_ADDR_P2: [0x0C,0,8], 78 | RX_ADDR_P3: [0x0D,0,8], 79 | RX_ADDR_P4: [0x0E,0,8], 80 | RX_ADDR_P5: [0x0F,0,8], 81 | TX_ADDR: [0x10,0,40], 82 | /* RX_PW_Pn */ 83 | RX_PW_P0: [0x11,0,6], 84 | RX_PW_P1: [0x12,0,6], 85 | RX_PW_P2: [0x13,0,6], 86 | RX_PW_P3: [0x14,0,6], 87 | RX_PW_P4: [0x15,0,6], 88 | RX_PW_P5: [0x16,0,6], 89 | /* FIFO_STATUS */ 90 | FIFO_STATUS: [0x17], 91 | TX_REUSE: [0x17,6], 92 | TX_FULL: [0x17,5], 93 | TX_EMPTY: [0x17,4], 94 | RX_FULL: [0x17,1], 95 | RX_EMPTY: [0x17,0], 96 | /* DYNPD */ 97 | DYNPD: [0x1C], 98 | DPL_P5: [0x1C,5], 99 | DPL_P4: [0x1C,4], 100 | DPL_P3: [0x1C,3], 101 | DPL_P2: [0x1C,2], 102 | DPL_P1: [0x1C,1], 103 | DPL_P0: [0x1C,0], 104 | /* FEATURE */ 105 | FEATURE: [0x1D], 106 | EN_DPL: [0x1D,2], 107 | EN_ACK_PAY: [0x1D,1], 108 | EN_DYN_ACK: [0x1D,0] 109 | }; 110 | 111 | exports.REGISTER_DEFAULTS = { 112 | CONFIG: _b('0000 1000'), 113 | EN_AA: _b('0011 1111'), 114 | EN_RXADDR: _b('0000 0011'), 115 | SETUP_AW: _b('0000 0011'), 116 | SETUP_RETR: _b('0000 0011'), 117 | RF_CH: _b('0000 0010'), 118 | RF_SETUP: _b('0000 1111'), 119 | STATUS: _b('0111 1110'), 120 | RX_ADDR_P0: Buffer("E7E7E7E7E7", 'hex'), 121 | RX_ADDR_P1: Buffer("C2C2C2C2C2", 'hex'), 122 | RX_ADDR_P2: 0xC3, 123 | RX_ADDR_P3: 0xC4, 124 | RX_ADDR_P4: 0xC5, 125 | RX_ADDR_P5: 0xC6, 126 | TX_ADDR: Buffer("E7E7E7E7E7", 'hex'), 127 | DYNPD: _b('0000 0000'), 128 | FEATURE: _b('0000 0000'), 129 | }; 130 | 131 | exports.TIMING = { 132 | pd2stby: 150, // NOTE: varies dep. on crystal configuration, see p.24/p.19 133 | stby2a: 130, 134 | hce: 10, 135 | pece2csn: 4 136 | }; 137 | 138 | exports.TX_POWER = ['PA_MIN', 'PA_LOW', 'PA_HIGH', 'PA_MAX']; 139 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nrf", 3 | "version": "0.8.3", 4 | "description": "nRF24L01 driver library", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/natevw/node-nrf.git" 12 | }, 13 | "keywords": [ 14 | "nrf24l01", 15 | "rf24", 16 | "wireless", 17 | "transceiver", 18 | "raspi" 19 | ], 20 | "author": "Nathan Vander Wilt", 21 | "license": "BSD-2-Clause", 22 | "homepage": "https://github.com/natevw/node-nrf", 23 | "bugs": { 24 | "url": "https://github.com/natevw/node-nrf/issues" 25 | }, 26 | "dependencies": { 27 | "fifolock": "^1.0.0", 28 | "pi-pins": "^1.0.0", 29 | "pi-spi": "^1.0.0", 30 | "queue-async": "~1.0.4" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-details.js: -------------------------------------------------------------------------------- 1 | var NRF24 = require("./index"), 2 | spiDev = process.argv[2] || "/dev/spidev0.0", 3 | cePin = +process.argv[3] || 24, 4 | irqPin = +process.argv[4] || 25; 5 | 6 | var nrf = (irqPin > 0) ? 7 | NRF24.connect(spiDev, cePin, irqPin) : 8 | NRF24.connect(spiDev, cePin); 9 | nrf.printDetails(); -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | // see https://gist.github.com/natevw/5789019 for pins 2 | 3 | var NRF24 = require("./index"), 4 | spiDev = "/dev/spidev0.0", 5 | cePin = 24, irqPin = 25, //var ce = require("./gpio").connect(cePin) 6 | pipes = [0xF0F0F0F0E1, 0xF0F0F0F0D2], 7 | role = 'ping'; 8 | 9 | var stream = require('stream'), 10 | util = require('util'); 11 | function CountStream(ms) { 12 | stream.Readable.call(this); 13 | this._n = 0; 14 | } 15 | util.inherits(CountStream, stream.Readable); 16 | CountStream.prototype._read = function () { 17 | console.log("Piping out", this._n); 18 | var b = new Buffer(4); 19 | b.writeUInt32BE(this._n++, 0); 20 | this.push(b); 21 | }; 22 | 23 | var nrf = NRF24.connect(spiDev, cePin, irqPin); 24 | //nrf._debug = true; 25 | nrf.channel(0x4c).transmitPower('PA_MAX').dataRate('1Mbps').crcBytes(2).autoRetransmit({count:15, delay:4000}).begin(function () { 26 | if (role === 'ping') { 27 | console.log("PING out"); 28 | var tx = nrf.openPipe('tx', pipes[0]), 29 | rx = nrf.openPipe('rx', pipes[1]); 30 | var count = 0; 31 | rx.on('data', function (d) { 32 | console.log("Got response back:", d.readUInt32BE(0)); 33 | }); 34 | tx.on('ready', function () { // NOTE: hoping to get rid of need to wait for "ready" 35 | (new CountStream).pipe(tx); 36 | }); 37 | } else { 38 | console.log("PONG back"); 39 | var rx = nrf.openPipe('rx', pipes[0]), 40 | tx = nrf.openPipe('tx', pipes[1]); 41 | rx.on('data', function (d) { 42 | console.log("Got data, will respond", d.readUInt32BE(0)); 43 | tx.write(d); 44 | }); 45 | tx.on('error', function (e) { 46 | console.warn("Error sending reply.", e); 47 | }); 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /test2.js: -------------------------------------------------------------------------------- 1 | // see https://gist.github.com/natevw/5789019 for pins 2 | 3 | var pipes = [0xF1F0F0F0E1, 0xF1F0F0F0D2]; 4 | var radios = [ 5 | {spiDev:"/dev/spidev0.0", cePin:24, irqPin:25}, 6 | {spiDev:"/dev/spidev0.1", cePin:23} 7 | ]; 8 | 9 | 10 | var NRF24 = require("./index"), 11 | queue = require('queue-async'); 12 | 13 | var q = queue(); 14 | radios.forEach(function (radio) { 15 | radio._interface = (radio.irqPin) ? 16 | NRF24.connect(radio.spiDev, radio.cePin, radio.irqPin) : 17 | NRF24.connect(radio.spiDev, radio.cePin); 18 | q.defer(setupRadio, radio._interface); 19 | }); 20 | function setupRadio(radio, cb) { 21 | radio.channel(0x4c).dataRate('1Mbps').crcBytes(2); 22 | radio.transmitPower('PA_MAX').autoRetransmit({count:15, delay:4000}); 23 | radio.begin(function (e) { cb(e, radio); }); 24 | } 25 | q.awaitAll(function (e,d) { 26 | if (e) throw e; 27 | 28 | d[0]._debug = true; 29 | 30 | var tx = d[1].openPipe('tx', pipes[0]), 31 | rx = d[0].openPipe('rx', pipes[0]), 32 | rx2 = d[0].openPipe('rx', pipes[1]); 33 | 34 | tx.on('ready', function () { 35 | tx.write("NARF!"); 36 | tx.write("Hello?"); 37 | tx.write("blah blah blah"); 38 | tx.write("the number 4"); 39 | setInterval(tx.write.bind(tx, "beep"), 2e3); 40 | //setInterval(tx.write.bind(tx, "boop"), 2e3); 41 | }); 42 | rx.on('data', function (d) { 43 | console.log("Got data:", d.toString()); 44 | }); 45 | 46 | // RX - no ack 47 | // TX - no ack 48 | 49 | // RX - ack payloads 50 | // TX - ack payloads 51 | }); --------------------------------------------------------------------------------