├── .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 |
--------------------------------------------------------------------------------