├── .gitignore ├── README.md ├── binding.gyp ├── package.json ├── rtlsdr.js ├── src ├── binding.cpp ├── nodeRtlSdr.cpp └── nodeRtlSdr.h └── test └── rtlsdrTest.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /build 3 | /node_modules 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # node-rtlsdr 3 | 4 | Node wrapper around [rtl-sdr](http://sdr.osmocom.org/trac/wiki/rtl-sdr/). 5 | 6 | Note: this is a working fork of the [node-rtlsdr](https://github.com/joeferner/node-rtlsdr) module. 7 | 8 | You need to install the rtl-sdr libraries first: 9 | 10 | ``` 11 | $ apt-get install librtlsdr0 librtlsdr-dev 12 | ``` 13 | 14 | ## Usage 15 | 16 | 17 | 18 | ``` 19 | $ mkdir node_modules 20 | $ npm install git+https://github.com/pminervini/node-rtlsdr.git 21 | ``` 22 | 23 | ```javascript 24 | var rtlsdr = require('rtlsdr'); 25 | 26 | rtlsdr.getDevices(function (err, devices) { 27 | devices[0].open(function (err, device) { 28 | device.setSampleRate(2048000); 29 | device.setCenterFrequency(99500000); 30 | device.on("data", function (data) { 31 | // process data 32 | console.log(data); 33 | }); 34 | device.start(); 35 | setTimeout(function () { 36 | device.stop(); 37 | }, 1000); 38 | }); 39 | }); 40 | ``` 41 | 42 | Output: 43 | 44 | ``` 45 | Found Rafael Micro R820T tuner 46 | 47 | 48 | 49 | 50 | 51 | 52 | ``` 53 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "nodeRtlsdr", 5 | "sources": [ 6 | "src/binding.cpp", 7 | "src/nodeRtlSdr.cpp" 8 | ], 9 | "conditions" : [ 10 | [ 11 | 'OS!="win"', { 12 | "libraries" : [ 13 | '-lrtlsdr', 14 | ], 15 | } 16 | ], 17 | [ 18 | 'OS=="win"', { 19 | "libraries" : [ 20 | '<(module_root_dir)/gyp/lib/librtlsdr.dll.a' 21 | ] 22 | } 23 | ] 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtlsdr", 3 | "description": "Node bindings for rtl-sdr", 4 | "author": "Joe Ferner ", 5 | "keywords": [ 6 | "sdr", 7 | "rtl", 8 | "rtl-sdr" 9 | ], 10 | "maintainers": [ 11 | { 12 | "name": "Joe Ferner", 13 | "email": "joe.ferner@nearinfinity.com" 14 | } 15 | ], 16 | "bugs": { 17 | "url": "https://github.com/joeferner/node-rtlsdr/issues" 18 | }, 19 | "version": "0.0.1", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/joeferner/node-rtlsdr.git" 23 | }, 24 | "main": "./rtlsdr.js", 25 | "scripts": { 26 | "test": "nodeunit test" 27 | }, 28 | "dependencies": { 29 | "bindings": "~1.0.0" 30 | }, 31 | "devDependencies": { 32 | "nodeunit": "~0.7.4" 33 | }, 34 | "optionalDependencies": {}, 35 | "scripts": { 36 | "install": "node-gyp configure build" 37 | }, 38 | "engines": { 39 | "node": "*" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /rtlsdr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require("util"); 4 | var events = require("events"); 5 | var rtlsdrBindings = require("bindings")("nodeRtlsdr.node"); 6 | 7 | exports.getDevices = function (callback) { 8 | callback = callback || function () {}; 9 | var opts = {}; 10 | opts.toEventEmitter = function (clazz) { 11 | util.inherits(clazz, events.EventEmitter); 12 | }; 13 | rtlsdrBindings.getDevices(opts, callback); 14 | }; 15 | -------------------------------------------------------------------------------- /src/binding.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "nodeRtlSdr.h" 3 | 4 | extern "C" { 5 | void init (v8::Handle target) 6 | { 7 | v8::HandleScope scope; 8 | NODE_SET_METHOD(target, "getDevices", GetDevices); 9 | } 10 | } 11 | 12 | NODE_MODULE(nodeRtlsdr, init); 13 | -------------------------------------------------------------------------------- /src/nodeRtlSdr.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "nodeRtlSdr.h" 3 | #include 4 | 5 | #define DEFAULT_ASYNC_BUF_NUMBER 32 6 | #define DEFAULT_BUF_LENGTH (16 * 16384) 7 | 8 | v8::Persistent g_deviceConstructor; 9 | int g_initialized = false; 10 | 11 | v8::Handle device_open(const v8::Arguments& args); 12 | v8::Handle device_setSampleRate(const v8::Arguments& args); 13 | v8::Handle device_setCenterFrequency(const v8::Arguments& args); 14 | v8::Handle device_start(const v8::Arguments& args); 15 | v8::Handle device_stop(const v8::Arguments& args); 16 | void device_cleanUp(v8::Persistent obj, void *parameter); 17 | static void device_dataCallback(unsigned char *buf, uint32_t len, void *ctx); 18 | 19 | struct DeviceData { 20 | rtlsdr_dev_t *dev; 21 | v8::Persistent v8dev; 22 | }; 23 | 24 | struct DataEventData { 25 | DeviceData* devData; 26 | unsigned char* buf; 27 | int len; 28 | }; 29 | 30 | struct StartData { 31 | DeviceData* devData; 32 | v8::Persistent callback; 33 | }; 34 | 35 | v8::Handle GetDevices(const v8::Arguments& args) { 36 | v8::HandleScope scope; 37 | int deviceCount, i; 38 | char vendor[256], product[256], serial[256], str[1000]; 39 | const char* deviceName; 40 | int err; 41 | v8::Local deviceArray; 42 | v8::Local device; 43 | v8::Handle callbackArgs[2]; 44 | 45 | callbackArgs[0] = v8::Undefined(); 46 | callbackArgs[1] = v8::Undefined(); 47 | 48 | // options 49 | if(!args[0]->IsObject()) { 50 | return scope.Close(v8::ThrowException(v8::Exception::TypeError(v8::String::New("First argument must be an object")))); 51 | } 52 | v8::Local options = args[0]->ToObject(); 53 | 54 | // callback 55 | if(!args[1]->IsFunction()) { 56 | return scope.Close(v8::ThrowException(v8::Exception::TypeError(v8::String::New("Second argument must be a function")))); 57 | } 58 | v8::Local callback = args[1]; 59 | 60 | if(!g_initialized) { 61 | v8::Local t = v8::FunctionTemplate::New(); 62 | t->InstanceTemplate()->SetInternalFieldCount(1); 63 | t->SetClassName(v8::String::NewSymbol("RtlsdrDevice")); 64 | g_deviceConstructor = v8::Persistent::New(t->GetFunction()); 65 | 66 | v8::Handle toEventEmitterArgs[1]; 67 | toEventEmitterArgs[0] = g_deviceConstructor; 68 | v8::Local toEventEmitterFn = options->Get(v8::String::New("toEventEmitter")); 69 | v8::Function::Cast(*toEventEmitterFn)->Call(options, 1, toEventEmitterArgs); 70 | 71 | g_initialized = true; 72 | } 73 | 74 | deviceArray = v8::Array::New(); 75 | deviceCount = rtlsdr_get_device_count(); 76 | for(i = 0; i < deviceCount; i++) { 77 | vendor[0] = '\0'; 78 | product[0] = '\0'; 79 | serial[0] = '\0'; 80 | err = rtlsdr_get_device_usb_strings(i, vendor, product, serial); 81 | if(err) { 82 | sprintf(str, "Could not get device strings"); 83 | callbackArgs[0] = v8::Exception::Error(v8::String::New(str)); 84 | goto getDevicesDone; 85 | } 86 | deviceName = rtlsdr_get_device_name(i); 87 | device = g_deviceConstructor->NewInstance(); 88 | device->Set(v8::String::New("vendor"), v8::String::New(vendor)); 89 | device->Set(v8::String::New("product"), v8::String::New(product)); 90 | device->Set(v8::String::New("serial"), v8::String::New(serial)); 91 | device->Set(v8::String::New("name"), v8::String::New(deviceName)); 92 | device->Set(v8::String::New("index"), v8::Int32::New(i)); 93 | device->Set(v8::String::New("open"), v8::FunctionTemplate::New(device_open)->GetFunction()); 94 | device->Set(v8::String::New("setSampleRate"), v8::FunctionTemplate::New(device_setSampleRate)->GetFunction()); 95 | device->Set(v8::String::New("setCenterFrequency"), v8::FunctionTemplate::New(device_setCenterFrequency)->GetFunction()); 96 | device->Set(v8::String::New("start"), v8::FunctionTemplate::New(device_start)->GetFunction()); 97 | device->Set(v8::String::New("stop"), v8::FunctionTemplate::New(device_stop)->GetFunction()); 98 | deviceArray->Set(i, device); 99 | } 100 | 101 | callbackArgs[1] = deviceArray; 102 | 103 | getDevicesDone: 104 | v8::Function::Cast(*callback)->Call(v8::Context::GetCurrent()->Global(), 2, callbackArgs); 105 | return scope.Close(v8::Undefined()); 106 | } 107 | 108 | v8::Handle device_open(const v8::Arguments& args) { 109 | v8::HandleScope scope; 110 | int deviceIdx; 111 | int err; 112 | DeviceData* deviceData; 113 | rtlsdr_dev_t *dev; 114 | v8::Local device = args.This(); 115 | char str[1000]; 116 | v8::Handle callbackArgs[2]; 117 | 118 | callbackArgs[0] = v8::Undefined(); 119 | callbackArgs[1] = v8::Undefined(); 120 | 121 | // callback 122 | if(!args[0]->IsFunction()) { 123 | return scope.Close(v8::ThrowException(v8::Exception::Error(v8::String::New("First argument must be a function")))); 124 | } 125 | v8::Local callback = args[0]; 126 | 127 | deviceIdx = device->Get(v8::String::New("index"))->ToInt32()->Value(); 128 | err = rtlsdr_open(&dev, deviceIdx); 129 | if(err < 0) { 130 | sprintf(str, "Could not open device err:%d.", err); 131 | callbackArgs[0] = v8::Exception::Error(v8::String::New(str)); 132 | goto deviceOpenDone; 133 | } 134 | 135 | deviceData = new DeviceData(); 136 | deviceData->dev = dev; 137 | device->SetPointerInInternalField(0, deviceData); 138 | deviceData->v8dev = v8::Persistent::New(device); 139 | deviceData->v8dev.MakeWeak(deviceData, device_cleanUp); 140 | deviceData->v8dev.MarkIndependent(); 141 | 142 | callbackArgs[1] = deviceData->v8dev; 143 | 144 | deviceOpenDone: 145 | v8::Function::Cast(*callback)->Call(v8::Context::GetCurrent()->Global(), 2, callbackArgs); 146 | return scope.Close(v8::Undefined()); 147 | } 148 | 149 | v8::Handle device_setSampleRate(const v8::Arguments& args) { 150 | v8::HandleScope scope; 151 | char str[1000]; 152 | v8::Local device = args.This(); 153 | DeviceData* data = (DeviceData*)device->GetPointerFromInternalField(0); 154 | 155 | int sampleRate = args[0]->ToInt32()->Value(); 156 | 157 | int err = rtlsdr_set_sample_rate(data->dev, sampleRate); 158 | if(err < 0) { 159 | sprintf(str, "failed to set sample rate (err: %d)", err); 160 | return scope.Close(v8::ThrowException(v8::Exception::Error(v8::String::New(str)))); 161 | } 162 | 163 | return scope.Close(v8::Undefined()); 164 | } 165 | 166 | v8::Handle device_setCenterFrequency(const v8::Arguments& args) { 167 | v8::HandleScope scope; 168 | char str[1000]; 169 | v8::Local device = args.This(); 170 | DeviceData* data = (DeviceData*)device->GetPointerFromInternalField(0); 171 | 172 | int frequency = args[0]->ToInt32()->Value(); 173 | 174 | int err = rtlsdr_set_center_freq(data->dev, frequency); 175 | if(err < 0) { 176 | sprintf(str, "failed to set frequency (err: %d)", err); 177 | return scope.Close(v8::ThrowException(v8::Exception::Error(v8::String::New(str)))); 178 | } 179 | 180 | return scope.Close(v8::Undefined()); 181 | } 182 | 183 | void EIO_Start(uv_work_t* req) { 184 | StartData* startData = (StartData*)req->data; 185 | 186 | int err = rtlsdr_read_async(startData->devData->dev, device_dataCallback, (void*)startData->devData, DEFAULT_ASYNC_BUF_NUMBER, DEFAULT_BUF_LENGTH); 187 | if(err) { 188 | char str[1000]; 189 | sprintf(str, "failed to start read async (err: %d)", err); 190 | printf("error starting: %s", str); 191 | // TODO callback with error 192 | return; 193 | } 194 | } 195 | 196 | void EIO_StartAfter(uv_work_t* req) { 197 | v8::HandleScope scope; 198 | StartData* startData = (StartData*)req->data; 199 | 200 | // TODO startData->callback.Dispose(); 201 | delete startData; 202 | } 203 | 204 | v8::Handle device_start(const v8::Arguments& args) { 205 | v8::HandleScope scope; 206 | char str[1000]; 207 | v8::Local device = args.This(); 208 | DeviceData* data = (DeviceData*)device->GetPointerFromInternalField(0); 209 | StartData* startData; 210 | 211 | // callback 212 | /* TODO 213 | if(!args[0]->IsFunction()) { 214 | return scope.Close(v8::ThrowException(v8::Exception::TypeError(v8::String::New("First argument must be a function")))); 215 | } 216 | v8::Local callback = args[0]; 217 | */ 218 | 219 | int err = rtlsdr_reset_buffer(data->dev); 220 | if(err) { 221 | sprintf(str, "failed to reset buffer (err: %d)", err); 222 | return scope.Close(v8::ThrowException(v8::Exception::Error(v8::String::New(str)))); 223 | } 224 | 225 | startData = new StartData(); 226 | startData->devData = data; 227 | // TODO startData->callback = v8::Persistent::New(callback); 228 | 229 | uv_work_t* req = new uv_work_t(); 230 | req->data = startData; 231 | uv_queue_work(uv_default_loop(), req, EIO_Start, (uv_after_work_cb) EIO_StartAfter); 232 | 233 | return scope.Close(v8::Undefined()); 234 | } 235 | 236 | v8::Handle device_stop(const v8::Arguments& args) { 237 | v8::HandleScope scope; 238 | v8::Local device = args.This(); 239 | DeviceData* data = (DeviceData*)device->GetPointerFromInternalField(0); 240 | 241 | rtlsdr_cancel_async(data->dev); 242 | 243 | return scope.Close(v8::Undefined()); 244 | } 245 | 246 | void EIO_EmitData(uv_work_t* req) { 247 | 248 | } 249 | 250 | void EIO_EmitDataAfter(uv_work_t* req) { 251 | v8::HandleScope scope; 252 | DataEventData* eventData = (DataEventData*)req->data; 253 | 254 | v8::Handle emitArgs[2]; 255 | emitArgs[0] = v8::String::New("data"); 256 | emitArgs[1] = v8::Local::New(node::Buffer::New((char*)eventData->buf, eventData->len)->handle_); 257 | v8::Function::Cast(*eventData->devData->v8dev->Get(v8::String::New("emit")))->Call(eventData->devData->v8dev, 2, emitArgs); 258 | 259 | delete eventData; 260 | } 261 | 262 | static void device_dataCallback(unsigned char* buf, uint32_t len, void *ctx) { 263 | DeviceData* devData = (DeviceData*)ctx; 264 | DataEventData* eventData = new DataEventData(); 265 | eventData->devData = devData; 266 | eventData->buf = buf; 267 | eventData->len = len; 268 | 269 | uv_work_t* req = new uv_work_t(); 270 | req->data = eventData; 271 | uv_queue_work(uv_default_loop(), req, EIO_EmitData, (uv_after_work_cb) EIO_EmitDataAfter); 272 | } 273 | 274 | void device_cleanUp(v8::Persistent obj, void *parameter) { 275 | DeviceData* data = (DeviceData*)parameter; 276 | 277 | rtlsdr_close(data->dev); 278 | delete data; 279 | } 280 | -------------------------------------------------------------------------------- /src/nodeRtlSdr.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | v8::Handle GetDevices(const v8::Arguments& args); 7 | -------------------------------------------------------------------------------- /test/rtlsdrTest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var rtlsdr = require('../'); 4 | 5 | module.exports = { 6 | "device list, open": function (test) { 7 | rtlsdr.getDevices(function (err, devices) { 8 | if (err) { 9 | test.fail(err); 10 | return test.done(); 11 | } 12 | test.equal(devices.length, 1); 13 | console.log(devices[0]); 14 | devices[0].open(function (err, device) { 15 | if (err) { 16 | test.fail(err); 17 | return test.done(); 18 | } 19 | device.setSampleRate(2048000); 20 | device.setCenterFrequency(99500000); 21 | var dataCount = 0; 22 | device.on("data", function (data) { 23 | dataCount++; 24 | }); 25 | device.start(); 26 | setTimeout(function () { 27 | test.ok(dataCount > 0, "no data reveived"); 28 | device.stop(); 29 | test.done(); 30 | }, 1000); 31 | }); 32 | } 33 | ); 34 | } 35 | }; 36 | --------------------------------------------------------------------------------