几年前,我看到了Thomas Watson的精彩演讲,他在演讲中谈到了他是如何构建AirplaneJS的,这是一个网络应用程序,可以从飞机上接收ADS-B无线电信号,并在浏览器的地图上实时绘制它们。我不知道这在 JavaScript 中是可能的,所以我开始研究它。
我玩弄了这个项目,并开始想知道是否有办法可以将它推得更远一些。考虑到 AirplaneJS 在服务器上使用 Node.js,我决定进行实验,看看是否可以使用 Web USB 使其工作,从而将其变成仅前端的项目。几周前我终于让它工作了🥳所以决定写下来。
我还在学习很多关于 USB 协议以及如何解码 ADS-B 信号的知识,所以这篇文章不会深入探讨这个主题。
在我开始之前,这里有一个演示:
这是演示站点的链接
RTL-SDR 加密狗
该项目的主要组件包括Web USB API、RTL-SDR 加密狗 + 天线和一些 JavaScript 代码。
如今,大多数飞机都广播 ADS-B 数据,即自动相关监视广播。这是一种监视技术,允许飞机在不被询问的情况下广播其飞行状态,这意味着它不需要飞行员的输入。
此数据指示飞机的位置、纬度、经度、速度、代码等。
他们会定期将此数据传输给空中交通管制员,但是,使用带有天线的软件定义无线电加密狗,您可以拦截这些消息以制作您自己的飞机雷达系统📡。
您可以购买或制造不同类型的天线,但我个人使用的是偶极天线。
由于数据以 1090 MHz 的频率广播,因此偶极子的大小需要使用以下公式计算:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-name-color)">In</span> <span style="color:var(--syntax-name-color)">feet</span><span style="color:var(--syntax-text-color)">:</span>
<span style="color:var(--syntax-name-color)">Total</span> <span style="color:var(--syntax-name-color)">length</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-literal-color)">468</span> <span style="color:var(--syntax-error-color)">/</span> <span style="color:var(--syntax-name-color)">Frequency</span> <span style="color:var(--syntax-declaration-color)">in</span> <span style="color:var(--syntax-name-color)">MHz</span> <span style="color:var(--syntax-comment-color)">// Gives the total lengh of the dipole</span>
<span style="color:var(--syntax-name-color)">Length</span> <span style="color:var(--syntax-declaration-color)">of</span> <span style="color:var(--syntax-name-color)">each</span> <span style="color:var(--syntax-name-color)">branch</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">Total</span> <span style="color:var(--syntax-name-color)">length</span> <span style="color:var(--syntax-error-color)">/</span> <span style="color:var(--syntax-literal-color)">2</span>
<span style="color:var(--syntax-error-color)">---</span>
<span style="color:var(--syntax-name-color)">In</span> <span style="color:var(--syntax-name-color)">cm</span><span style="color:var(--syntax-text-color)">:</span>
<span style="color:var(--syntax-name-color)">Total</span> <span style="color:var(--syntax-name-color)">length</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-literal-color)">14264</span> <span style="color:var(--syntax-error-color)">/</span> <span style="color:var(--syntax-name-color)">Frequency</span> <span style="color:var(--syntax-declaration-color)">in</span> <span style="color:var(--syntax-name-color)">MHz</span> <span style="color:var(--syntax-comment-color)">// Gives the total lengh of the dipole</span>
<span style="color:var(--syntax-name-color)">Length</span> <span style="color:var(--syntax-declaration-color)">of</span> <span style="color:var(--syntax-name-color)">each</span> <span style="color:var(--syntax-name-color)">branch</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">Total</span> <span style="color:var(--syntax-name-color)">length</span> <span style="color:var(--syntax-error-color)">/</span> <span style="color:var(--syntax-literal-color)">2</span>
</code></span></span></span>
如果更简单,可以使用在线计算器。
对于这个项目,它给了我大约 13 厘米的总长度,所以偶极子的每个部分应该是大约 6.5 厘米。
通过 Web USB 连接到加密狗
Web USB API是一种浏览器 API,可让您通过USB 与插入计算机的设备进行连接和通信。
要使用它,您需要首先检查您的浏览器是否支持它。您可以使用以下代码执行此操作:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-declaration-color)">if</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-text-color)">navigator</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">usb</span><span style="color:var(--syntax-text-color)">){</span>
<span style="color:var(--syntax-name-color)">console</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">log</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">The Web USB API is supported!</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-declaration-color)">else</span> <span style="color:var(--syntax-text-color)">{</span>
<span style="color:var(--syntax-name-color)">console</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">log</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">I'm afraid this won't work</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">}</span>
</code></span></span></span>
然后,您需要指明您希望应用程序连接到哪个设备。您可以列出所有当前连接的设备,也可以指定一个过滤器,在其中传递设备的供应商 ID 和产品 ID。
在 Mac 上,您可以通过将设备插入计算机、单击苹果图标 > 关于本机 > 系统报告并查看硬件 > USB 下的列表来找到这些 ID。
上图表明供应商 ID 是0x0bda
,产品 ID 是0x2838
,所以我可以在代码中使用它。
出于安全原因,Web USB API 需要由用户交互触发,所以我在屏幕上有一个按钮来启动连接过程。
第一个调用方法navigator.usb
是requestDevice
:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-name-color)">button</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">onclick</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">()</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-text-color)">{</span>
<span style="color:var(--syntax-text-color)">navigator</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">usb</span>
<span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">requestDevice</span><span style="color:var(--syntax-text-color)">({</span>
<span style="color:var(--syntax-name-color)">filters</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-text-color)">[</span>
<span style="color:var(--syntax-text-color)">{</span>
<span style="color:var(--syntax-name-color)">vendorId</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-literal-color)">0x0bda</span><span style="color:var(--syntax-text-color)">,</span>
<span style="color:var(--syntax-name-color)">productId</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-literal-color)">0x2838</span><span style="color:var(--syntax-text-color)">,</span>
<span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-text-color)">],</span>
<span style="color:var(--syntax-text-color)">})</span>
<span style="color:var(--syntax-text-color)">};</span>
</code></span></span></span>
它返回一个带有在列表中选择的设备的承诺。接下来的步骤是打开与此设备的会话,选择配置并声明接口:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">then</span><span style="color:var(--syntax-text-color)">((</span><span style="color:var(--syntax-name-color)">device</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-name-color)">device</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">open</span><span style="color:var(--syntax-text-color)">());</span>
<span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">then</span><span style="color:var(--syntax-text-color)">(()</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-name-color)">device</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">selectConfiguration</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-literal-color)">1</span><span style="color:var(--syntax-text-color)">))</span>
<span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">then</span><span style="color:var(--syntax-text-color)">(()</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-name-color)">device</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">claimInterface</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-literal-color)">0</span><span style="color:var(--syntax-text-color)">))</span>
</code></span></span></span>
配置和接口是特定于设备的。我在网上找不到关于我正在使用的特定设备的信息,所以我尝试了不同的值,直到它起作用......
成功执行这些步骤后,应连接设备。现在,需要设置频率和采样率。
为此,我依靠Sandeep Mistry的rtlsdrjs库。如果你决定用它来创建一个类似的项目,我用它作为频率(对于 1090MHz)和采样率。1090000000
2000000
最后,该transferIn
方法在从设备接收到数据时执行。
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">transferIn</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-literal-color)">1</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-literal-color)">256000</span><span style="color:var(--syntax-text-color)">)</span>
<span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">then</span><span style="color:var(--syntax-text-color)">((</span><span style="color:var(--syntax-name-color)">result</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-text-color)">{</span>
<span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">data</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">new</span> <span style="color:var(--syntax-text-color)">Uint8Array</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">result</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">data</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">buffer</span><span style="color:var(--syntax-text-color)">);</span>
<span style="color:var(--syntax-name-color)">console</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">log</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">data</span><span style="color:var(--syntax-text-color)">);</span>
<span style="color:var(--syntax-text-color)">})</span>
</code></span></span></span>
此代码返回如下所示的原始数据:
处理和格式化后,它看起来像这样:
处理原始 ADS-B 数据
该项目的这一部分很大程度上依赖于现有的工作。我需要更深入地研究 S 模式通信协议如何工作以解码数据,但本指南解码 S 模式和 ADS-B 信号似乎是一个非常好的开始。
到目前为止,据我了解,ADS-B 消息有两个主要部分,一个前导码和一个数据块。数据块由 112 位组成,分为 5 个部分:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>+----------+----------+-------------+------------------------+-----------+
| DF (5) | CA (3) | ICAO (24) | ME (56) | PI (24) |
+----------+----------+-------------+------------------------+-----------+
</code></span></span></span>
- DF:下行格式
- CA:转发器能力
- ICAO 飞机地址
- 我:留言
- PI:奇偶校验 ID
位 33-88 专用于消息。无需过多介绍,本节的前 5 位表示类型代码。如果此类型代码在 1 和 4 之间,ADS-B 消息是飞机识别消息,如果类型代码在 5-8 之间,则消息是表面位置消息,等等。
这个类型代码很重要,因为它表明了消息的其余部分涉及。
十六进制格式的原始 ADS-B 消息示例如下所示:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-literal-color)">8</span><span style="color:var(--syntax-name-color)">D4840D6202CC371C32CE0576098</span>
</code></span></span></span>
在二进制中,它转换为:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-literal-color)">1000110101001000010000001101011000100000001011001100001101110001110000110010110011100000010101110110000010011000</span>
</code></span></span></span>
如果您正在关注,您可能会猜到这是多少位......
...
...
...
112!
如果我们使用前面显示的表格,我们可以将此二进制文件拆分为相应的部分:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>+--------+--------+--------------+-------------------------+-------------------+
| DF (5) | CA (3) | ICAO (24) | ME (56) | PI (24) |
+--------+--------+--------------+-------------------------+-------------------+
| 10001 | 101 | 010010000100 | 00100000001011001100001 | 01010111011000001 |
| | | 000011010110 | 10111000111000011001011 | 0011000 |
| | | 000011010110 | 0011100000 | |
| | | | | |
+--------+--------+--------------+-------------------------+-------------------+
</code></span></span></span>
它使它更容易阅读,但现在我们也可以将它们中的每一个转换为十进制:
<span style="color:#171717"><span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>+--------+--------+--------------+-------------------------+-------------------+
| DF (5) | CA (3) | ICAO (24) | ME (56) | PI (24) |
+--------+--------+--------------+-------------------------+-------------------+
| 17 | 5 | 4735190 | [4]... | ... |
+--------+--------+--------------+-------------------------+-------------------+
</code></span></span></span>
消息的前 5 位给出十进制值 4,这意味着这是一个飞机识别消息。从那里,如果我理解得很好,消息的其余部分应该包含有关飞机类别及其呼号的数据,根据我找到的这张表。
使用这些数据,您可以使用planespotters.net等在线工具并搜索 ICAO 代码以获取有关您正在跟踪的飞机的更多信息。
处理完所有这些数据后,结果是一个 JSON 对象,其中包含高度、纬度、经度等信息。
如果需要,请查看repo 。
最终设置
最后,这就是我的设置:
——
这篇文章不是深入探讨,但希望它有意义!
我真的非常高兴我能够使用 Web USB 让这个项目工作,这是我多年来一直想做的事情!
我仍然需要了解更多关于它的信息,但我有一些其他的想法,我想围绕相同的技术构建项目,我非常兴奋!潜入我一无所知的事情只需要很多时间😅...