├── .gitignore ├── LICENSE ├── README.md ├── browser.js ├── index.js ├── lib ├── r820t.js ├── rtl2832u.js ├── rtlcom.js ├── rtlsdr.js ├── usb.js └── web-usb.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 📡 rtlsdr.js 2 | 3 | Turn your Realtek RTL2832U based device into an SDR (Software Defined Radio) receiver using JavaScript. 4 | 5 | Supports [Node.js](https://nodejs.org/) and [WebUSB](https://wicg.github.io/webusb/) compatible browsers. 6 | 7 | ## Requirements 8 | 9 | ### Hardware 10 | 11 | * Realtek RTL2832U based USB adapter with R820T tuner chip 12 | 13 | ### Software 14 | 15 | * [Node.js](https://nodejs.org/) 8.x or later + [node-usb requirements](https://github.com/tessel/node-usb#installation) 16 | * [Web browser that supports WebUSB](https://caniuse.com/#feat=webusb) 17 | 18 | ## Setup 19 | 20 | ### Node.js 21 | 22 | Install: `npm i rtlsdrjs` 23 | 24 | Require: `const RtlSdr = require('rtlsdrjs');` 25 | 26 | ### Browser 27 | 28 | Download a `rtlsdr.js` build from a [release](https://github.com/sandeepmistry/rtlsdrjs/releases). 29 | 30 | Include `rtlsdr.js` in you HTML page: `` 31 | 32 | ## Usage 33 | 34 | ```javascript 35 | let readSamples = true; 36 | 37 | async function start() { 38 | // 39 | // request a device 40 | // - displays prompt in browser 41 | // - selects first device in Node.js 42 | // 43 | // RtlSdr.getDevices() can be used to get a list of all RTL SDR's attached to system 44 | // 45 | const sdr = await RtlSdr.requestDevice(); 46 | 47 | // 48 | // open the device 49 | // 50 | // supported options are: 51 | // - ppm: frequency correction factor, in parts per million (defaults to 0) 52 | // - gain: optional gain in dB, auto gain is used if not specified 53 | // 54 | await sdr.open({ 55 | ppm: 0.5 56 | }); 57 | 58 | // 59 | // set sample rate and center frequency in Hz 60 | // - returns the actual values set 61 | // 62 | const actualSampleRate = await sdr.setSampleRate(2000000); 63 | const actualCenterFrequency = await sdr.setCenterFrequency(1090000000); 64 | 65 | // 66 | // reset the buffer 67 | // 68 | await sdr.resetBuffer(); 69 | 70 | while (readSamples) { 71 | // 72 | // read some samples 73 | // - returns an ArrayBuffer with the specified number of samples, 74 | // data is interleaved in IQ format 75 | // 76 | const samples = await sdr.readSamples(16 * 16384); 77 | 78 | // 79 | // process the samples ... 80 | // 81 | } 82 | } 83 | 84 | ``` 85 | 86 | ## Acknowledgements 87 | 88 | This library is based on [Jacobo Tarrío's (@jtarrio](https://github.com/jtarrio)) work in Google's [Radio Receiver Chrome app](https://github.com/google/radioreceiver), which used work from the [RTL-SDR project](http://sdr.osmocom.org/trac/wiki/rtl-sdr). The Chrome USB API layer has been replaced with [node-usb](https://github.com/tessel/node-usb) on Node.js and [WebUSB](https://wicg.github.io/webusb/) when running in the browser. 89 | 90 | ## License 91 | 92 | Apache 2.0 93 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | window.RtlSdr = require('./lib/rtlsdr'); 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/rtlsdr'); 2 | -------------------------------------------------------------------------------- /lib/r820t.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | var RtlCom = require('./rtlcom'); 16 | 17 | var CMD = RtlCom.CMD; 18 | 19 | /** 20 | * Operations on the R820T tuner chip. 21 | * @param {RtlCom} com The RTL communications object. 22 | * @param {number} xtalFreq The frequency of the oscillator crystal. 23 | * @constructor 24 | */ 25 | function R820T(com, xtalFreq) { 26 | 27 | /** 28 | * Initial values for registers 0x05-0x1f. 29 | */ 30 | var REGISTERS = [0x83, 0x32, 0x75, 0xc0, 0x40, 0xd6, 0x6c, 0xf5, 0x63, 0x75, 31 | 0x68, 0x6c, 0x83, 0x80, 0x00, 0x0f, 0x00, 0xc0, 0x30, 0x48, 32 | 0xcc, 0x60, 0x00, 0x54, 0xae, 0x4a, 0xc0]; 33 | 34 | /** 35 | * Configurations for the multiplexer in different frequency bands. 36 | */ 37 | var MUX_CFGS = [ 38 | [0, 0x08, 0x02, 0xdf], 39 | [50, 0x08, 0x02, 0xbe], 40 | [55, 0x08, 0x02, 0x8b], 41 | [60, 0x08, 0x02, 0x7b], 42 | [65, 0x08, 0x02, 0x69], 43 | [70, 0x08, 0x02, 0x58], 44 | [75, 0x00, 0x02, 0x44], 45 | [90, 0x00, 0x02, 0x34], 46 | [110, 0x00, 0x02, 0x24], 47 | [140, 0x00, 0x02, 0x14], 48 | [180, 0x00, 0x02, 0x13], 49 | [250, 0x00, 0x02, 0x11], 50 | [280, 0x00, 0x02, 0x00], 51 | [310, 0x00, 0x41, 0x00], 52 | [588, 0x00, 0x40, 0x00] 53 | ]; 54 | 55 | /** 56 | * A bit mask to reverse the bits in a byte. 57 | */ 58 | var BIT_REVS = [0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe, 59 | 0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf]; 60 | 61 | /** 62 | * Whether the PLL in the tuner is locked. 63 | */ 64 | var hasPllLock = false; 65 | 66 | /** 67 | * Shadow registers 0x05-0x1f, for setting values using masks. 68 | */ 69 | var shadowRegs; 70 | 71 | 72 | /** 73 | * Initializes the tuner. 74 | */ 75 | async function init() { 76 | await initRegisters(REGISTERS); 77 | await initElectronics(); 78 | } 79 | 80 | /** 81 | * Sets the tuner's frequency. 82 | * @param {number} freq The frequency to tune to. 83 | * @return {number} The actual tuned frequency. 84 | */ 85 | async function setFrequency(freq) { 86 | await setMux(freq); 87 | return await setPll(freq); 88 | } 89 | 90 | /** 91 | * Stops the tuner. 92 | */ 93 | async function close() { 94 | await writeEach([ 95 | [0x06, 0xb1, 0xff], 96 | [0x05, 0xb3, 0xff], 97 | [0x07, 0x3a, 0xff], 98 | [0x08, 0x40, 0xff], 99 | [0x09, 0xc0, 0xff], 100 | [0x0a, 0x36, 0xff], 101 | [0x0c, 0x35, 0xff], 102 | [0x0f, 0x68, 0xff], 103 | [0x11, 0x03, 0xff], 104 | [0x17, 0xf4, 0xff], 105 | [0x19, 0x0c, 0xff] 106 | ]); 107 | } 108 | 109 | /** 110 | * Initializes all the components of the tuner. 111 | */ 112 | async function initElectronics() { 113 | await writeEach([ 114 | [0x0c, 0x00, 0x0f], 115 | [0x13, 49, 0x3f], 116 | [0x1d, 0x00, 0x38] 117 | ]); 118 | var filterCap = await calibrateFilter(true); 119 | await writeEach([ 120 | [0x0a, 0x10 | filterCap, 0x1f], 121 | [0x0b, 0x6b, 0xef], 122 | [0x07, 0x00, 0x80], 123 | [0x06, 0x10, 0x30], 124 | [0x1e, 0x40, 0x60], 125 | [0x05, 0x00, 0x80], 126 | [0x1f, 0x00, 0x80], 127 | [0x0f, 0x00, 0x80], 128 | [0x19, 0x60, 0x60], 129 | [0x1d, 0xe5, 0xc7], 130 | [0x1c, 0x24, 0xf8], 131 | [0x0d, 0x53, 0xff], 132 | [0x0e, 0x75, 0xff], 133 | [0x05, 0x00, 0x60], 134 | [0x06, 0x00, 0x08], 135 | [0x11, 0x38, 0x08], 136 | [0x17, 0x30, 0x30], 137 | [0x0a, 0x40, 0x60], 138 | [0x1d, 0x00, 0x38], 139 | [0x1c, 0x00, 0x04], 140 | [0x06, 0x00, 0x40], 141 | [0x1a, 0x30, 0x30], 142 | [0x1d, 0x18, 0x38], 143 | [0x1c, 0x24, 0x04], 144 | [0x1e, 0x0d, 0x1f], 145 | [0x1a, 0x20, 0x30] 146 | ]); 147 | } 148 | 149 | /** 150 | * Sets the tuner to automatic gain. 151 | */ 152 | async function setAutoGain() { 153 | await writeEach([ 154 | [0x05, 0x00, 0x10], 155 | [0x07, 0x10, 0x10], 156 | [0x0c, 0x0b, 0x9f] 157 | ]); 158 | } 159 | 160 | /** 161 | * Sets the tuner's manual gain. 162 | * @param {number} gain The tuner's gain, in dB. 163 | */ 164 | async function setManualGain(gain) { 165 | var step = 0; 166 | if (gain <= 15) { 167 | step = Math.round(1.36 + gain * (1.1118 + gain * (-0.0786 + gain * 0.0027))); 168 | } else { 169 | step = Math.round(1.2068 + gain * (0.6875 + gain * (-0.01011 + gain * 0.0001587))); 170 | } 171 | if (step < 0) { 172 | step = 0; 173 | } else if (step > 30) { 174 | step = 30; 175 | } 176 | var lnaValue = Math.floor(step / 2); 177 | var mixerValue = Math.floor((step - 1) / 2); 178 | await writeEach([ 179 | [0x05, 0x10, 0x10], 180 | [0x07, 0x00, 0x10], 181 | [0x0c, 0x08, 0x9f], 182 | [0x05, lnaValue, 0x0f], 183 | [0x07, mixerValue, 0x0f] 184 | ]); 185 | } 186 | 187 | /** 188 | * Calibrates the filters. 189 | * @param {boolean} firstTry Whether this is the first try to calibrate. 190 | */ 191 | async function calibrateFilter(firstTry) { 192 | await writeEach([ 193 | [0x0b, 0x6b, 0x60], 194 | [0x0f, 0x04, 0x04], 195 | [0x10, 0x00, 0x03] 196 | ]); 197 | await setPll(56000000); 198 | if (!hasPllLock) { 199 | throw new Error("PLL not locked -- cannot tune to the selected frequency."); 200 | return; 201 | } 202 | await writeEach([ 203 | [0x0b, 0x10, 0x10], 204 | [0x0b, 0x00, 0x10], 205 | [0x0f, 0x00, 0x04] 206 | ]); 207 | var data = await readRegBuffer(0x00, 5); 208 | var arr = new Uint8Array(data); 209 | var filterCap = arr[4] & 0x0f; 210 | if (filterCap == 0x0f) { 211 | filterCap = 0; 212 | } 213 | if (filterCap != 0 && firstTry) { 214 | return await calibrateFilter(false); 215 | } else { 216 | return (filterCap); 217 | } 218 | } 219 | 220 | /** 221 | * Sets the multiplexer's frequency. 222 | * @param {number} freq The frequency to set. 223 | */ 224 | async function setMux(freq) { 225 | var freqMhz = freq / 1000000; 226 | for (var i = 0; i < MUX_CFGS.length - 1; ++i) { 227 | if (freqMhz < MUX_CFGS[i + 1][0]) { 228 | break; 229 | } 230 | } 231 | var cfg = MUX_CFGS[i]; 232 | await writeEach([ 233 | [0x17, cfg[1], 0x08], 234 | [0x1a, cfg[2], 0xc3], 235 | [0x1b, cfg[3], 0xff], 236 | [0x10, 0x00, 0x0b], 237 | [0x08, 0x00, 0x3f], 238 | [0x09, 0x00, 0x3f] 239 | ]); 240 | } 241 | 242 | /** 243 | * Sets the PLL's frequency. 244 | * @param {number} freq The frequency to set. 245 | */ 246 | async function setPll(freq) { 247 | var pllRef = Math.floor(xtalFreq); 248 | await writeEach([ 249 | [0x10, 0x00, 0x10], 250 | [0x1a, 0x00, 0x0c], 251 | [0x12, 0x80, 0xe0] 252 | ]); 253 | var divNum = Math.min(6, Math.floor(Math.log(1770000000 / freq) / Math.LN2)); 254 | var mixDiv = 1 << (divNum + 1); 255 | var data = await readRegBuffer(0x00, 5); 256 | var arr = new Uint8Array(data); 257 | var vcoFineTune = (arr[4] & 0x30) >> 4; 258 | if (vcoFineTune > 2) { 259 | --divNum; 260 | } else if (vcoFineTune < 2) { 261 | ++divNum; 262 | } 263 | await writeRegMask(0x10, divNum << 5, 0xe0); 264 | var vcoFreq = freq * mixDiv; 265 | var nint = Math.floor(vcoFreq / (2 * pllRef)); 266 | var vcoFra = vcoFreq % (2 * pllRef); 267 | if (nint > 63) { 268 | hasPllLock = false; 269 | return; 270 | } 271 | var ni = Math.floor((nint - 13) / 4); 272 | var si = (nint - 13) % 4; 273 | await writeEach([ 274 | [0x14, ni + (si << 6), 0xff], 275 | [0x12, vcoFra == 0 ? 0x08 : 0x00, 0x08] 276 | ]); 277 | var sdm = Math.min(65535, Math.floor(32768 * vcoFra / pllRef)); 278 | await writeEach([ 279 | [0x16, sdm >> 8, 0xff], 280 | [0x15, sdm & 0xff, 0xff] 281 | ]); 282 | await getPllLock(true); 283 | await writeRegMask(0x1a, 0x08, 0x08); 284 | var actualFreq = 2 * pllRef * (nint + sdm / 65536) / mixDiv; 285 | return (actualFreq); 286 | } 287 | 288 | /** 289 | * Checks whether the PLL has achieved lock. 290 | * @param {boolean} firstTry Whether this is the first try to achieve lock. 291 | */ 292 | async function getPllLock(firstTry) { 293 | var data = await readRegBuffer(0x00, 3); 294 | var arr = new Uint8Array(data); 295 | if (arr[2] & 0x40) { 296 | hasPllLock = true; 297 | return; 298 | } 299 | if (firstTry) { 300 | await writeRegMask(0x12, 0x60, 0xe0); 301 | return await getPllLock(false); 302 | } else { 303 | hasPllLock = false; 304 | return; 305 | } 306 | } 307 | 308 | /** 309 | * Sets the initial values of the 0x05-0x1f registers. 310 | * @param {Array.} regs The values for the registers. 311 | */ 312 | async function initRegisters(regs) { 313 | shadowRegs = new Uint8Array(regs); 314 | var cmds = []; 315 | for (var i = 0; i < regs.length; ++i) { 316 | cmds.push([CMD.I2CREG, 0x34, i + 5, regs[i]]); 317 | } 318 | await com.writeEach(cmds); 319 | } 320 | 321 | /** 322 | * Reads a series of registers into a buffer. 323 | * @param {number} addr The first register's address to read. 324 | * @param {number} length The number of registers to read. 325 | * @return {ArrayBuffer} An ArrayBuffer with the data. 326 | */ 327 | async function readRegBuffer(addr, length) { 328 | var data = await com.i2c.readRegBuffer(0x34, addr, length); 329 | var buf = new Uint8Array(data); 330 | for (var i = 0; i < buf.length; ++i) { 331 | var b = buf[i]; 332 | buf[i] = (BIT_REVS[b & 0xf] << 4) | BIT_REVS[b >> 4]; 333 | } 334 | return (buf.buffer); 335 | } 336 | 337 | /** 338 | * Writes a masked value into a register. 339 | * @param {number} addr The address of the register to write into. 340 | * @param {number} value The value to write. 341 | * @param {number} mask A mask that specifies which bits to write. 342 | */ 343 | async function writeRegMask(addr, value, mask) { 344 | var rc = shadowRegs[addr - 5]; 345 | var val = (rc & ~mask) | (value & mask); 346 | shadowRegs[addr - 5] = val; 347 | await com.i2c.writeRegister(0x34, addr, val); 348 | } 349 | 350 | /** 351 | * Perform the write operations given in the array. 352 | * @param {Array.>} array The operations. 353 | */ 354 | async function writeEach(array) { 355 | for (var index = 0; index < array.length; index++) { 356 | var line = array[index]; 357 | await writeRegMask(line[0], line[1], line[2]); 358 | } 359 | } 360 | 361 | return { 362 | init: init, 363 | setFrequency: setFrequency, 364 | setAutoGain: setAutoGain, 365 | setManualGain: setManualGain, 366 | close: close 367 | }; 368 | } 369 | 370 | /** 371 | * Checks if the R820T tuner is present. 372 | * @param {RtlCom} com The RTL communications object. 373 | * @return {boolean} A boolean that tells whether the tuner is present. 374 | */ 375 | R820T.check = async function (com) { 376 | var data = await com.i2c.readRegister(0x34, 0); 377 | return (data == 0x69); 378 | }; 379 | 380 | module.exports = R820T; 381 | -------------------------------------------------------------------------------- /lib/rtl2832u.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All rights reserved. 2 | // Copyright 2018 Sandeep Mistry All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | var R820T = require('./r820t'); 17 | var RtlCom = require('./rtlcom'); 18 | 19 | var CMD = RtlCom.CMD; 20 | var BLOCK = RtlCom.BLOCK; 21 | var REG = RtlCom.REG; 22 | 23 | /** 24 | * Operations on the RTL2832U demodulator. 25 | * @param {ConnectionHandle} conn The USB connection handle. 26 | * @param {number} ppm The frequency correction factor, in parts per million. 27 | * @param {number=} opt_gain The optional gain in dB. If unspecified or null, sets auto gain. 28 | * @constructor 29 | */ 30 | function RTL2832U(conn, ppm, opt_gain) { 31 | 32 | /** 33 | * Frequency of the oscillator crystal. 34 | */ 35 | var XTAL_FREQ = 28800000; 36 | 37 | /** 38 | * Tuner intermediate frequency. 39 | */ 40 | var IF_FREQ = 3570000; 41 | 42 | /** 43 | * The number of bytes for each sample. 44 | */ 45 | var BYTES_PER_SAMPLE = 2; 46 | 47 | /** 48 | * Communications with the demodulator via USB. 49 | */ 50 | var com = new RtlCom(conn); 51 | 52 | /** 53 | * The tuner used by the dongle. 54 | */ 55 | var tuner; 56 | 57 | /** 58 | * Initialize the demodulator. 59 | */ 60 | async function open() { 61 | await com.writeEach([ 62 | [CMD.REG, BLOCK.USB, REG.SYSCTL, 0x09, 1], 63 | [CMD.REG, BLOCK.USB, REG.EPA_MAXPKT, 0x0200, 2], 64 | [CMD.REG, BLOCK.USB, REG.EPA_CTL, 0x0210, 2] 65 | ]); 66 | await com.iface.claim(); 67 | await com.writeEach([ 68 | [CMD.REG, BLOCK.SYS, REG.DEMOD_CTL_1, 0x22, 1], 69 | [CMD.REG, BLOCK.SYS, REG.DEMOD_CTL, 0xe8, 1], 70 | [CMD.DEMODREG, 1, 0x01, 0x14, 1], 71 | [CMD.DEMODREG, 1, 0x01, 0x10, 1], 72 | [CMD.DEMODREG, 1, 0x15, 0x00, 1], 73 | [CMD.DEMODREG, 1, 0x16, 0x0000, 2], 74 | [CMD.DEMODREG, 1, 0x16, 0x00, 1], 75 | [CMD.DEMODREG, 1, 0x17, 0x00, 1], 76 | [CMD.DEMODREG, 1, 0x18, 0x00, 1], 77 | [CMD.DEMODREG, 1, 0x19, 0x00, 1], 78 | [CMD.DEMODREG, 1, 0x1a, 0x00, 1], 79 | [CMD.DEMODREG, 1, 0x1b, 0x00, 1], 80 | [CMD.DEMODREG, 1, 0x1c, 0xca, 1], 81 | [CMD.DEMODREG, 1, 0x1d, 0xdc, 1], 82 | [CMD.DEMODREG, 1, 0x1e, 0xd7, 1], 83 | [CMD.DEMODREG, 1, 0x1f, 0xd8, 1], 84 | [CMD.DEMODREG, 1, 0x20, 0xe0, 1], 85 | [CMD.DEMODREG, 1, 0x21, 0xf2, 1], 86 | [CMD.DEMODREG, 1, 0x22, 0x0e, 1], 87 | [CMD.DEMODREG, 1, 0x23, 0x35, 1], 88 | [CMD.DEMODREG, 1, 0x24, 0x06, 1], 89 | [CMD.DEMODREG, 1, 0x25, 0x50, 1], 90 | [CMD.DEMODREG, 1, 0x26, 0x9c, 1], 91 | [CMD.DEMODREG, 1, 0x27, 0x0d, 1], 92 | [CMD.DEMODREG, 1, 0x28, 0x71, 1], 93 | [CMD.DEMODREG, 1, 0x29, 0x11, 1], 94 | [CMD.DEMODREG, 1, 0x2a, 0x14, 1], 95 | [CMD.DEMODREG, 1, 0x2b, 0x71, 1], 96 | [CMD.DEMODREG, 1, 0x2c, 0x74, 1], 97 | [CMD.DEMODREG, 1, 0x2d, 0x19, 1], 98 | [CMD.DEMODREG, 1, 0x2e, 0x41, 1], 99 | [CMD.DEMODREG, 1, 0x2f, 0xa5, 1], 100 | [CMD.DEMODREG, 0, 0x19, 0x05, 1], 101 | [CMD.DEMODREG, 1, 0x93, 0xf0, 1], 102 | [CMD.DEMODREG, 1, 0x94, 0x0f, 1], 103 | [CMD.DEMODREG, 1, 0x11, 0x00, 1], 104 | [CMD.DEMODREG, 1, 0x04, 0x00, 1], 105 | [CMD.DEMODREG, 0, 0x61, 0x60, 1], 106 | [CMD.DEMODREG, 0, 0x06, 0x80, 1], 107 | [CMD.DEMODREG, 1, 0xb1, 0x1b, 1], 108 | [CMD.DEMODREG, 0, 0x0d, 0x83, 1] 109 | ]); 110 | 111 | var xtalFreq = Math.floor(XTAL_FREQ * (1 + ppm / 1000000)); 112 | await com.i2c.open(); 113 | var found = await R820T.check(com); 114 | if (found) { 115 | tuner = new R820T(com, xtalFreq); 116 | } 117 | if (!tuner) { 118 | throw new Error('Sorry, your USB dongle has an unsupported tuner chip. ' + 119 | 'Only the R820T chip is supported.'); 120 | return; 121 | } 122 | var multiplier = -1 * Math.floor(IF_FREQ * (1<<22) / xtalFreq); 123 | await com.writeEach([ 124 | [CMD.DEMODREG, 1, 0xb1, 0x1a, 1], 125 | [CMD.DEMODREG, 0, 0x08, 0x4d, 1], 126 | [CMD.DEMODREG, 1, 0x19, (multiplier >> 16) & 0x3f, 1], 127 | [CMD.DEMODREG, 1, 0x1a, (multiplier >> 8) & 0xff, 1], 128 | [CMD.DEMODREG, 1, 0x1b, multiplier & 0xff, 1], 129 | [CMD.DEMODREG, 1, 0x15, 0x01, 1] 130 | ]) 131 | await tuner.init(); 132 | await setGain(opt_gain); 133 | await com.i2c.close(); 134 | } 135 | 136 | /** 137 | * Sets the requested gain. 138 | * @param {number|null|undefined} gain The gain in dB, or null/undefined 139 | * for automatic gain. 140 | */ 141 | async function setGain(gain) { 142 | if (gain == null) { 143 | await tuner.setAutoGain(); 144 | } else { 145 | await tuner.setManualGain(gain); 146 | } 147 | } 148 | 149 | /** 150 | * Set the sample rate. 151 | * @param {number} rate The sample rate, in samples/sec. 152 | * @return {number} The sample rate that was actually set as its first parameter. 153 | */ 154 | async function setSampleRate(rate) { 155 | var ratio = Math.floor(XTAL_FREQ * (1 << 22) / rate); 156 | ratio &= 0x0ffffffc; 157 | var realRate = Math.floor(XTAL_FREQ * (1 << 22) / ratio); 158 | var ppmOffset = -1 * Math.floor(ppm * (1 << 24) / 1000000); 159 | await com.writeEach([ 160 | [CMD.DEMODREG, 1, 0x9f, (ratio >> 16) & 0xffff, 2], 161 | [CMD.DEMODREG, 1, 0xa1, ratio & 0xffff, 2], 162 | [CMD.DEMODREG, 1, 0x3e, (ppmOffset >> 8) & 0x3f, 1], 163 | [CMD.DEMODREG, 1, 0x3f, ppmOffset & 0xff, 1] 164 | ]); 165 | await resetDemodulator(); 166 | return realRate; 167 | } 168 | 169 | /** 170 | * Resets the demodulator. 171 | */ 172 | async function resetDemodulator() { 173 | await com.writeEach([ 174 | [CMD.DEMODREG, 1, 0x01, 0x14, 1], 175 | [CMD.DEMODREG, 1, 0x01, 0x10, 1] 176 | ]); 177 | } 178 | 179 | /** 180 | * Tunes the device to the given frequency. 181 | * @param {number} freq The frequency to tune to, in Hertz. 182 | * @return {number} The actual tuned frequency. 183 | */ 184 | async function setCenterFrequency(freq) { 185 | await com.i2c.open(); 186 | var actualFreq = await tuner.setFrequency(freq + IF_FREQ); 187 | await com.i2c.close(); 188 | return (actualFreq - IF_FREQ); 189 | } 190 | 191 | /** 192 | * Resets the sample buffer. Call this before starting to read samples. 193 | */ 194 | async function resetBuffer() { 195 | await com.writeEach([ 196 | [CMD.REG, BLOCK.USB, REG.EPA_CTL, 0x0210, 2], 197 | [CMD.REG, BLOCK.USB, REG.EPA_CTL, 0x0000, 2] 198 | ]); 199 | } 200 | 201 | /** 202 | * Reads a block of samples off the device. 203 | * @param {number} length The number of samples to read. 204 | * @return {ArrayBuffer} An ArrayBuffer containing the read samples, which you 205 | * can interpret as pairs of unsigned 8-bit integers; the first one is 206 | * the sample's I value, and the second one is its Q value. 207 | */ 208 | async function readSamples(length) { 209 | return await com.bulk.readBuffer(length * BYTES_PER_SAMPLE); 210 | } 211 | 212 | /** 213 | * Stops the demodulator. 214 | */ 215 | async function close() { 216 | await com.i2c.open(); 217 | await tuner.close(); 218 | await com.i2c.close(); 219 | await com.iface.release(); 220 | } 221 | 222 | return { 223 | open: open, 224 | setSampleRate: setSampleRate, 225 | setCenterFrequency: setCenterFrequency, 226 | resetBuffer: resetBuffer, 227 | readSamples: readSamples, 228 | close: close 229 | }; 230 | } 231 | 232 | module.exports = RTL2832U; 233 | -------------------------------------------------------------------------------- /lib/rtlcom.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Google Inc. All rights reserved. 2 | // Copyright 2018 Sandeep Mistry All rights reserved. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | /** 17 | * Low-level communications with the RTL2832U-based dongle. 18 | * @param {ConnectionHandle} conn The USB connection handle. 19 | * @constructor 20 | */ 21 | function RtlCom(conn) { 22 | 23 | /** 24 | * Whether to log all USB transfers. 25 | */ 26 | var VERBOSE = false; 27 | 28 | /** 29 | * Set in the control messages' index field for write operations. 30 | */ 31 | var WRITE_FLAG = 0x10; 32 | 33 | /** 34 | * Writes a buffer into a dongle's register. 35 | * @param {number} block The register's block number. 36 | * @param {number} reg The register number. 37 | * @param {ArrayBuffer} buffer The buffer to write. 38 | */ 39 | async function writeRegBuffer(block, reg, buffer) { 40 | await writeCtrlMsg(reg, block | WRITE_FLAG, buffer); 41 | } 42 | 43 | /** 44 | * Reads a buffer from a dongle's register. 45 | * @param {number} block The register's block number. 46 | * @param {number} reg The register number. 47 | * @param {number} length The length in bytes of the buffer to read. 48 | * @return {ArrayBuffer} The read buffer. 49 | */ 50 | async function readRegBuffer(block, reg, length) { 51 | return await readCtrlMsg(reg, block, length); 52 | } 53 | 54 | /** 55 | * Writes a value into a dongle's register. 56 | * @param {number} block The register's block number. 57 | * @param {number} reg The register number. 58 | * @param {number} value The value to write. 59 | * @param {number} length The width in bytes of this value. 60 | */ 61 | async function writeReg(block, reg, value, length) { 62 | await writeCtrlMsg(reg, block | WRITE_FLAG, numberToBuffer(value, length)); 63 | } 64 | 65 | /** 66 | * Reads a value from a dongle's register. 67 | * @param {number} block The register's block number. 68 | * @param {number} reg The register number. 69 | * @param {number} length The width in bytes of the value to read. 70 | * @return {number} The decoded value. 71 | */ 72 | async function readReg(block, reg, length) { 73 | return bufferToNumber(await readCtrlMsg(reg, block, length)); 74 | } 75 | 76 | /** 77 | * Writes a masked value into a dongle's register. 78 | * @param {number} block The register's block number. 79 | * @param {number} reg The register number. 80 | * @param {number} value The value to write. 81 | * @param {number} mask The mask for the value to write. 82 | */ 83 | async function writeRegMask(block, reg, value, mask) { 84 | if (mask == 0xff) { 85 | await writeReg(block, reg, value, 1); 86 | } else { 87 | var old = await readReg(block, reg, 1); 88 | value &= mask; 89 | old &= ~mask; 90 | value |= old; 91 | await writeReg(block, reg, value, 1); 92 | } 93 | } 94 | 95 | /** 96 | * Reads a value from a demodulator register. 97 | * @param {number} page The register page number. 98 | * @param {number} addr The register's address. 99 | * @return {number} The decoded value. 100 | */ 101 | async function readDemodReg(page, addr) { 102 | return await readReg(page, (addr << 8) | 0x20, 1); 103 | } 104 | 105 | /** 106 | * Writes a value into a demodulator register. 107 | * @param {number} page The register page number. 108 | * @param {number} addr The register's address. 109 | * @param {number} value The value to write. 110 | * @param {number} len The width in bytes of this value. 111 | */ 112 | async function writeDemodReg(page, addr, value, len) { 113 | await writeRegBuffer(page, (addr << 8) | 0x20, numberToBuffer(value, len, true)); 114 | return await readDemodReg(0x0a, 0x01); 115 | } 116 | 117 | /** 118 | * Opens the I2C repeater. 119 | */ 120 | async function openI2C() { 121 | await writeDemodReg(1, 1, 0x18, 1); 122 | } 123 | 124 | /** 125 | * Closes the I2C repeater. 126 | */ 127 | async function closeI2C() { 128 | await writeDemodReg(1, 1, 0x10, 1); 129 | } 130 | 131 | /** 132 | * Reads a value from an I2C register. 133 | * @param {number} addr The device's address. 134 | * @param {number} reg The register number. 135 | */ 136 | async function readI2CReg(addr, reg) { 137 | await writeRegBuffer(BLOCK.I2C, addr, new Uint8Array([reg]).buffer); 138 | return await readReg(BLOCK.I2C, addr, 1); 139 | } 140 | 141 | /** 142 | * Writes a value to an I2C register. 143 | * @param {number} addr The device's address. 144 | * @param {number} reg The register number. 145 | * @param {number} value The value to write. 146 | * @param {number} len The width in bytes of this value. 147 | */ 148 | async function writeI2CReg(addr, reg, value) { 149 | await writeRegBuffer(BLOCK.I2C, addr, new Uint8Array([reg, value]).buffer); 150 | } 151 | 152 | /** 153 | * Reads a buffer from an I2C register. 154 | * @param {number} addr The device's address. 155 | * @param {number} reg The register number. 156 | * @param {number} len The number of bytes to read. 157 | */ 158 | async function readI2CRegBuffer(addr, reg, len) { 159 | await writeRegBuffer(BLOCK.I2C, addr, new Uint8Array([reg]).buffer); 160 | return await readRegBuffer(BLOCK.I2C, addr, len); 161 | } 162 | 163 | /** 164 | * Writes a buffer to an I2C register. 165 | * @param {number} addr The device's address. 166 | * @param {number} reg The register number. 167 | * @param {ArrayBuffer} buffer The buffer to write. 168 | */ 169 | async function writeI2CRegBuffer(addr, reg, buffer) { 170 | var data = new Uint8Array(buffer.byteLength + 1); 171 | data[0] = reg; 172 | data.set(new Uint8Array(buffer), 1); 173 | await writeRegBuffer(BLOCK.I2C, addr, data.buffer); 174 | } 175 | 176 | /** 177 | * Decodes a buffer as a little-endian number. 178 | * @param {ArrayBuffer} buffer The buffer to decode. 179 | * @return {number} The decoded number. 180 | */ 181 | function bufferToNumber(buffer) { 182 | var len = buffer.byteLength; 183 | var dv = new DataView(buffer); 184 | if (len == 0) { 185 | return null; 186 | } else if (len == 1) { 187 | return dv.getUint8(0); 188 | } else if (len == 2) { 189 | return dv.getUint16(0, true); 190 | } else if (len == 4) { 191 | return dv.getUint32(0, true); 192 | } 193 | throw 'Cannot parse ' + len + '-byte number'; 194 | } 195 | 196 | /** 197 | * Encodes a number into a buffer. 198 | * @param {number} value The number to encode. 199 | * @param {number} len The number of bytes to encode into. 200 | * @param {boolean=} opt_bigEndian Whether to use a big-endian encoding. 201 | */ 202 | function numberToBuffer(value, len, opt_bigEndian) { 203 | var buffer = new ArrayBuffer(len); 204 | var dv = new DataView(buffer); 205 | if (len == 1) { 206 | dv.setUint8(0, value); 207 | } else if (len == 2) { 208 | dv.setUint16(0, value, !opt_bigEndian); 209 | } else if (len == 4) { 210 | dv.setUint32(0, value, !opt_bigEndian); 211 | } else { 212 | throw 'Cannot write ' + len + '-byte number'; 213 | } 214 | return buffer; 215 | } 216 | 217 | /** 218 | * Sends a USB control message to read from the device. 219 | * @param {number} value The value field of the control message. 220 | * @param {number} index The index field of the control message. 221 | * @param {number} length The number of bytes to read. 222 | */ 223 | async function readCtrlMsg(value, index, length) { 224 | var ti = { 225 | 'requestType': 'vendor', 226 | 'recipient': 'device', 227 | 'direction': 'in', 228 | 'request': 0, 229 | 'value': value, 230 | 'index': index, 231 | 'length': Math.max(8, length) 232 | }; 233 | try { 234 | var data = await conn.controlTransfer(ti); 235 | data = data.slice(0, length); 236 | if (VERBOSE) { 237 | console.log('IN value 0x' + value.toString(16) + ' index 0x' + 238 | index.toString(16)); 239 | console.log(' read -> ' + dumpBuffer(data)); 240 | } 241 | 242 | return data; 243 | } catch (error) { 244 | var msg = 'USB read failed (value 0x' + value.toString(16) + 245 | ' index 0x' + index.toString(16) + '), message="' + error.message + '"'; 246 | }; 247 | } 248 | 249 | /** 250 | * Sends a USB control message to write to the device. 251 | * @param {number} value The value field of the control message. 252 | * @param {number} index The index field of the control message. 253 | * @param {ArrayBuffer} buffer The buffer to write to the device. 254 | */ 255 | async function writeCtrlMsg(value, index, buffer) { 256 | var ti = { 257 | 'requestType': 'vendor', 258 | 'recipient': 'device', 259 | 'direction': 'out', 260 | 'request': 0, 261 | 'value': value, 262 | 'index': index, 263 | 'data': buffer 264 | }; 265 | try { 266 | await conn.controlTransfer(ti); 267 | if (VERBOSE) { 268 | console.log('OUT value 0x' + value.toString(16) + ' index 0x' + 269 | index.toString(16) + ' data ' + dumpBuffer(buffer)); 270 | } 271 | } catch (error) { 272 | var msg = 'USB write failed (value 0x' + value.toString(16) + 273 | ' index 0x' + index.toString(16) + ' data ' + dumpBuffer(buffer) + 274 | ') message="' + 275 | error.message + '"'; 276 | throw msg; 277 | }; 278 | } 279 | 280 | /** 281 | * Does a bulk transfer from the device. 282 | * @param {number} length The number of bytes to read. 283 | * @return {ArrayBuffer} The received buffer. 284 | */ 285 | async function readBulk(length) { 286 | var ti = { 287 | 'direction': 'in', 288 | 'endpoint': 1, 289 | 'length': length 290 | }; 291 | try { 292 | var data = await conn.bulkTransfer(ti); 293 | if (VERBOSE) { 294 | console.log('IN BULK requested ' + length + ' received ' + data.byteLength); 295 | } 296 | return data; 297 | } catch(error) { 298 | var msg = 'USB bulk read failed (length 0x' + length.toString(16) + 299 | '), error="' + 300 | error.message + '"'; 301 | throw msg; 302 | } 303 | } 304 | 305 | /** 306 | * Claims the USB interface. 307 | */ 308 | async function claimInterface() { 309 | await conn.claimInterface(0); 310 | } 311 | 312 | /** 313 | * Releases the USB interface. 314 | */ 315 | async function releaseInterface() { 316 | await conn.releaseInterface(0); 317 | } 318 | 319 | /** 320 | * Performs several write operations as specified in an array. 321 | * @param {Array.>} array The operations to perform. 322 | */ 323 | async function writeEach(array) { 324 | for (var index = 0; index < array.length; index++) { 325 | var line = array[index]; 326 | if (line[0] == CMD.REG) { 327 | await writeReg(line[1], line[2], line[3], line[4]); 328 | } else if (line[0] == CMD.REGMASK) { 329 | await writeRegMask(line[1], line[2], line[3], line[4]); 330 | } else if (line[0] == CMD.DEMODREG) { 331 | await writeDemodReg(line[1], line[2], line[3], line[4]); 332 | } else if (line[0] == CMD.I2CREG) { 333 | await writeI2CReg(line[1], line[2], line[3]); 334 | } else { 335 | throw 'Unsupported operation [' + line + ']'; 336 | } 337 | } 338 | } 339 | 340 | /** 341 | * Returns a string representation of a buffer. 342 | * @param {ArrayBuffer} buffer The buffer to display. 343 | * @return {string} The string representation of the buffer. 344 | */ 345 | function dumpBuffer(buffer) { 346 | var bytes = []; 347 | var arr = new Uint8Array(buffer); 348 | for (var i = 0; i < arr.length; ++i) { 349 | bytes.push('0x' + arr[i].toString(16)); 350 | } 351 | return '[' + bytes + ']'; 352 | } 353 | 354 | 355 | return { 356 | writeRegister: writeReg, 357 | readRegister: readReg, 358 | writeRegMask: writeRegMask, 359 | demod: { 360 | readRegister: readDemodReg, 361 | writeRegister: writeDemodReg 362 | }, 363 | i2c: { 364 | open: openI2C, 365 | close: closeI2C, 366 | readRegister: readI2CReg, 367 | writeRegister: writeI2CReg, 368 | readRegBuffer: readI2CRegBuffer 369 | }, 370 | bulk: { 371 | readBuffer: readBulk 372 | }, 373 | iface: { 374 | claim: claimInterface, 375 | release: releaseInterface 376 | }, 377 | writeEach: writeEach 378 | }; 379 | } 380 | 381 | /** 382 | * Commands for writeEach. 383 | */ 384 | var CMD = { 385 | REG: 1, 386 | REGMASK: 2, 387 | DEMODREG: 3, 388 | I2CREG: 4 389 | }; 390 | 391 | /** 392 | * Register blocks. 393 | */ 394 | var BLOCK = { 395 | DEMOD: 0x000, 396 | USB: 0x100, 397 | SYS: 0x200, 398 | I2C: 0x600 399 | }; 400 | 401 | /** 402 | * Device registers. 403 | */ 404 | var REG = { 405 | SYSCTL: 0x2000, 406 | EPA_CTL: 0x2148, 407 | EPA_MAXPKT: 0x2158, 408 | DEMOD_CTL: 0x3000, 409 | DEMOD_CTL_1: 0x300b 410 | }; 411 | 412 | RtlCom.CMD = CMD; 413 | RtlCom.BLOCK = BLOCK; 414 | RtlCom.REG = REG; 415 | 416 | module.exports = RtlCom; 417 | -------------------------------------------------------------------------------- /lib/rtlsdr.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sandeep Mistry All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const usb = require('./usb'); 16 | const RTL2832U = require('./rtl2832u'); 17 | 18 | const FILTERS = [ 19 | { 20 | vendorId: 0x0bda, 21 | productId: 0x2832 22 | }, 23 | { 24 | vendorId: 0x0bda, 25 | productId: 0x2838 26 | } 27 | ]; 28 | 29 | function RtlSdr(usbDevice) { 30 | this._usbDevice = usbDevice; 31 | this._rtl2832u = null; 32 | } 33 | 34 | RtlSdr.prototype.open = async function(options) { 35 | await this._usbDevice.open(); 36 | await this._usbDevice.selectConfiguration(1); 37 | 38 | this._rtl2832u = new RTL2832U(this._usbDevice, options.ppm || 0, options.gain || null); 39 | 40 | await this._rtl2832u.open(); 41 | }; 42 | 43 | RtlSdr.prototype.setSampleRate = async function(sampleRate) { 44 | return await this._rtl2832u.setSampleRate(sampleRate); 45 | }; 46 | 47 | RtlSdr.prototype.setCenterFrequency = async function(centerFrequency) { 48 | return await this._rtl2832u.setCenterFrequency(centerFrequency); 49 | }; 50 | 51 | RtlSdr.prototype.resetBuffer = async function() { 52 | await this._rtl2832u.resetBuffer(); 53 | }; 54 | 55 | RtlSdr.prototype.readSamples = async function(length) { 56 | return await this._rtl2832u.readSamples(length); 57 | }; 58 | 59 | RtlSdr.prototype.close = async function() { 60 | await this._rtl2832u.close(); 61 | await this._usbDevice.close(); 62 | }; 63 | 64 | RtlSdr.requestDevice = async function() { 65 | let usbDevice = await usb.requestDevice(FILTERS); 66 | 67 | return new RtlSdr(usbDevice); 68 | }; 69 | 70 | RtlSdr.getDevices = async function() { 71 | let usbDevices = await usb.getDevices(FILTERS); 72 | 73 | const sdrs = []; 74 | 75 | usbDevices.forEach((usbDevice) => { 76 | sdrs.push(new RtlSdr(usbDevice)); 77 | }); 78 | 79 | return sdrs; 80 | }; 81 | 82 | module.exports = RtlSdr; 83 | -------------------------------------------------------------------------------- /lib/usb.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sandeep Mistry All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const usb = require('usb'); 16 | 17 | function USB(device) { 18 | this._device = device; 19 | } 20 | 21 | USB.prototype.open = async function() { 22 | return new Promise((resolve) => { 23 | this._device.open(); 24 | 25 | resolve(); 26 | }); 27 | }; 28 | 29 | USB.prototype.selectConfiguration = async function(configuration) { 30 | return new Promise((resolve, reject) => { 31 | this._device.setConfiguration(configuration, (err) => { 32 | if (err) { 33 | return reject(err); 34 | } 35 | 36 | resolve(); 37 | }); 38 | }); 39 | }; 40 | 41 | USB.prototype.claimInterface = async function(interface) { 42 | return new Promise((resolve) => { 43 | this._device.interface(interface).claim(); 44 | this._interface = interface; 45 | 46 | resolve(); 47 | }); 48 | }; 49 | 50 | USB.prototype.releaseInterface = async function(interface) { 51 | return new Promise((resolve) => { 52 | this._device.interface(interface).release((error) => { 53 | if (error) { 54 | return reject(error); 55 | } 56 | 57 | resolve(); 58 | }); 59 | }); 60 | }; 61 | 62 | USB.prototype.controlTransfer = async function(ti) { 63 | return new Promise((resolve, reject) => { 64 | let requestType = 0; 65 | let lengthOrData = ti.length ? ti.length : Buffer.from(ti.data); 66 | 67 | if (ti.requestType === 'vendor') { 68 | requestType |= usb.LIBUSB_REQUEST_TYPE_VENDOR; 69 | } 70 | 71 | if (ti.recipient === 'device') { 72 | requestType |= usb.LIBUSB_RECIPIENT_DEVICE; 73 | } 74 | 75 | if (ti.direction === 'out') { 76 | requestType |= usb.LIBUSB_ENDPOINT_OUT; 77 | } else if (ti.direction === 'in') { 78 | requestType |= usb.LIBUSB_ENDPOINT_IN; 79 | } 80 | 81 | this._device.controlTransfer(requestType, ti.request, ti.value, ti.index, lengthOrData, (err, data) => { 82 | if (err) { 83 | return reject(err); 84 | } 85 | 86 | if (data) { 87 | return resolve(Uint8Array.from(data).buffer); 88 | } 89 | 90 | resolve(); 91 | }); 92 | }); 93 | }; 94 | 95 | USB.prototype.bulkTransfer = async function(ti) { 96 | return new Promise((resolve, reject) => { 97 | this._device.interface(this._interface).endpoints[ti.endpoint - 1].transfer(ti.length, (err, data) => { 98 | if (err) { 99 | return reject(err); 100 | } 101 | 102 | resolve(data.buffer); 103 | }); 104 | }); 105 | }; 106 | 107 | USB.prototype.close = async function() { 108 | return new Promise((resolve) => { 109 | this._device.close(resolve); 110 | }); 111 | }; 112 | 113 | USB.requestDevice = async function(filters) { 114 | return new Promise((resolve, reject) => { 115 | for (let i = 0; i < filters.length; i++) { 116 | const filter = filters[i]; 117 | 118 | const usbDevice = usb.findByIds(filter.vendorId, filter.productId); 119 | 120 | if (usbDevice) { 121 | return resolve(new USB(usbDevice)); 122 | } 123 | } 124 | 125 | reject(new Error('No devices found!')); 126 | }); 127 | }; 128 | 129 | USB.getDevices = async function(filters) { 130 | return new Promise((resolve) => { 131 | const usbDevices = usb.getDeviceList(); 132 | const devices = []; 133 | 134 | usbDevices.forEach((usbDevice) => { 135 | filters.forEach((filter) => { 136 | if (filter.vendorId === usbDevice.deviceDescriptor.idVendor && 137 | filter.productId === usbDevice.deviceDescriptor.idProduct) { 138 | devices.push(new USB(usbDevice)); 139 | } 140 | }); 141 | }); 142 | 143 | resolve(devices); 144 | }); 145 | }; 146 | 147 | module.exports = USB; 148 | -------------------------------------------------------------------------------- /lib/web-usb.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Sandeep Mistry All rights reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | function USB(device) { 16 | this._device = device; 17 | } 18 | 19 | USB.prototype.open = async function() { 20 | await this._device.open(); 21 | }; 22 | 23 | USB.prototype.selectConfiguration = async function(configuration) { 24 | await this._device.selectConfiguration(configuration); 25 | }; 26 | 27 | USB.prototype.claimInterface = async function(interface) { 28 | await this._device.claimInterface(interface); 29 | }; 30 | 31 | USB.prototype.releaseInterface = async function(interface) { 32 | await this._device.releaseInterface(interface); 33 | }; 34 | 35 | USB.prototype.controlTransfer = async function(ti) { 36 | if (ti.direction === 'out') { 37 | await this._device.controlTransferOut(ti, ti.data); 38 | } else if (ti.direction === 'in') { 39 | const result = await this._device.controlTransferIn(ti, ti.length); 40 | 41 | return result.data.buffer; 42 | } 43 | }; 44 | 45 | USB.prototype.bulkTransfer = async function(ti) { 46 | const result = await this._device.transferIn(ti.endpoint, ti.length); 47 | 48 | return result.data.buffer; 49 | }; 50 | 51 | USB.prototype.close = async function() { 52 | await this._device.close(); 53 | }; 54 | 55 | USB.requestDevice = async function(filters) { 56 | const usbDevice = await navigator.usb.requestDevice({ 57 | filters: filters 58 | }); 59 | 60 | return new USB(usbDevice); 61 | }; 62 | 63 | USB.getDevices = async function(filters, callback) { 64 | const usbDevices = navigator.usb.getDevices(); 65 | const devices = []; 66 | 67 | usbDevices.forEach((usbDevice) => { 68 | filters.forEach((filter) => { 69 | if (filter.vendorId === usbDevice.vendorId && filter.productId === usbDevice.productId) { 70 | devices.push(new USB(usbDevice)); 71 | } 72 | }); 73 | }); 74 | 75 | return devices; 76 | }; 77 | 78 | module.exports = USB; 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rtlsdrjs", 3 | "version": "0.0.0", 4 | "description": "Turn your Realtek RTL2832U based device into an SDR receiver using JavaScript", 5 | "main": "index.js", 6 | "directories": { 7 | "lib": "lib" 8 | }, 9 | "dependencies": { 10 | "usb": "^1.3.1" 11 | }, 12 | "devDependencies": { 13 | "browserify": "^16.1.1" 14 | }, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1", 17 | "build": "browserify --bare -o build/Release/rtlsdr.js browser.js" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/sandeepmistry/rtlsdrjs.git" 22 | }, 23 | "keywords": [ 24 | "SDR", 25 | "Software", 26 | "Defined", 27 | "Radio", 28 | "Realtek", 29 | "RTL2832U", 30 | "receiver" 31 | ], 32 | "author": "Sandeep Mistry", 33 | "license": "Apache-2.0", 34 | "bugs": { 35 | "url": "https://github.com/sandeepmistry/rtlsdrjs/issues" 36 | }, 37 | "homepage": "https://github.com/sandeepmistry/rtlsdrjs#readme", 38 | "browser": { 39 | "./lib/usb.js": "./lib/web-usb.js" 40 | } 41 | } 42 | --------------------------------------------------------------------------------