去年夏天,我和妻子卖掉了我们所有的东西,带着我们的两条狗搬到了夏威夷。这里的一切都和我们想象的一样:美丽的阳光、温暖的沙滩、凉爽的海浪——应有尽有。我们也遇到了一些我们没有预料到的事情:WiFi 问题。
现在,这不是夏威夷的问题。这仅限于我们租住的公寓。我们住在一个与房东公寓相连的单间公寓里。房租的一部分包括免费互联网!耶!但是,所谓的互联网是由房东公寓里的 WiFi 路由器提供的。呜!
老实说,它工作得还算可以。勉强可以。好吧,它工作得不太好,我也不确定为什么。路由器实际上就在墙的另一边,但是我们的信号断断续续,并且在保持连接方面遇到了一些麻烦。在家里,我们的 WiFi 路由器的信号穿过了许多墙壁和一些楼层。当然,它覆盖的面积比我们居住的 600 平方英尺的公寓还要大!
在这种情况下,一个优秀的科技爱好者会做什么?当然是调查!
幸运的是,我们在搬到这里之前卖掉的“我们所有的东西”不包括我们的 Raspberry Pi Zero W。如此小巧!如此便携!当然,我把它带到了夏威夷。我的好主意是使用 Pi 及其内置的 WiFi 适配器,用 Go 编写一个小程序来测量从路由器接收到的 WiFi 信号强度,并显示该输出。我打算让它超级简单、快速和粗糙,稍后再担心如何改进它。我只是想知道 WiFi 到底是怎么回事,该死的!
在 Google 上搜索一分钟,找到一个相对有用的 Go 包,用于处理 WiFi,mdlayher/wifi。听起来很有希望!
获取有关 WiFi 接口的信息
我的计划是查询 WiFi 接口统计信息并返回信号强度,所以我需要找到设备上的接口。幸运的是,mdlayher/wifi 包有一个查询它们的方法,所以我可以通过创建一个名为 main.go
的文件来做到这一点
package main
import (
"fmt"
"github.com/mdlayher/wifi"
)
func main() {
c, err := wifi.New()
defer c.Close()
if err != nil {
panic(err)
}
interfaces, err := c.Interfaces()
for _, x := range interfaces {
fmt.Printf("%+v\n", x)
}
}
那么,这里发生了什么?导入它之后,mdlayher/wifi 模块可以在 main 函数中使用,以创建一个新的 Client(类型为 *Client
)。新的客户端(名为 c
)然后可以使用 c.Interfaces()
获取系统上接口的列表。然后它可以循环遍历 Interface 指针的切片并打印有关它们的信息。
通过在 %+v
中添加“+”,它也会打印 *Interface
结构中字段的名称,这有助于我识别我看到的内容,而无需查阅文档。
运行上面的代码会提供我的机器上 WiFi 接口的列表
&{Index:0 Name: HardwareAddr:5c:5f:67:f3:0a:a7 PHY:0 Device:3 Type:P2P device Frequency:0}
&{Index:3 Name:wlp2s0 HardwareAddr:5c:5f:67:f3:0a:a7 PHY:0 Device:1 Type:station Frequency:2412}
请注意,MAC 地址 HardwareAddr
对于两行是相同的,这意味着这是相同的物理硬件。这通过 PHY: 0
得到确认。Go wifi 模块的文档 指出 PHY
是接口所属的物理设备。
第一个接口没有名称,并且是 TYPE:P2P
。第二个接口名为 wpl2s0
,是 TYPE:Station
。wifi 模块文档列出了 不同类型的接口 并描述了它们是什么。根据文档,“P2P”类型表示“接口是点对点客户端网络中的设备”。我相信,如果我错了,请在评论中纠正我,这个接口用于 WiFi Direct,这是一种允许两个 WiFi 设备在没有中间接入点的情况下连接的标准。
“Station”类型表示“接口是具有控制接入点的客户端设备托管基本服务集 (BSS) 的一部分。” 这是大多数人习惯的无线设备的标准功能——作为连接到接入点的客户端。这是用于测试 WiFi 质量的接口。
从接口获取 Station 信息
使用此信息,我可以更新接口上的循环以检索我要查找的信息
for _, x := range interfaces {
if x.Type == wifi.InterfaceTypeStation {
// c.StationInfo(x) returns a slice of all
// the staton information about the interface
info, err := c.StationInfo(x)
if err != nil {
fmt.Printf("Station err: %s\n", err)
}
for _, x := range info {
fmt.Printf("%+v\n", x)
}
}
}
首先,它检查 x.Type
(接口类型)是否为 wifi.InterfaceTypeStation
——Station 接口(这是本练习中唯一重要的类型)。这是一个不幸的命名冲突——接口“类型”不是 Golang 意义上的“类型”。事实上,我在这里做的是一个名为 InterfaceType
的 Go type
,用于表示接口的类型。哇,这花了我一分钟才弄清楚!
因此,假设接口是正确的类型,可以使用 c.StationInfo(x)
使用客户端 StationInfo()
方法来检索 Station 信息,以获取有关接口 x
的信息。
这会返回 *StationInfo
指针的切片。我不确定为什么会有一个切片。也许接口可以有多个 StationInfo 响应?无论如何,我可以循环遍历切片并使用相同的 +%v
技巧来打印 StationInfo 结构的键和值。
运行上面的代码返回
&{HardwareAddr:70:5a:9e:71:2e:d4 Connected:17m10s Inactive:1.579s ReceivedBytes:2458563 TransmittedBytes:1295562 ReceivedPackets:6355 TransmittedPackets:6135 ReceiveBitrate:2000000 TransmitBitrate:43300000 Signal:-79 TransmitRetries:2306 TransmitFailed:4 BeaconLoss:2}
我感兴趣的是“Signal”以及可能的“TransmitFailed”和“BeaconLoss”。信号以 dBm(或分贝毫瓦)为单位报告。
一个小插曲:如何读取 WiFi dBm
根据 MetaGeek
- –30 是最佳信号强度——既不现实也不必要
- –67 非常好;适用于需要可靠数据包传输的应用,例如流媒体
- –70 还可以,最低可靠数据包传输,适用于电子邮件和网页
- –80 很差,绝对基本的连接,不可靠的数据包传输
- –90 不可用,接近“噪声底限”
请注意,dBm 是对数刻度:-60 比 -30 低 1,000 倍
将其变成真正的“扫描器”
因此,看看我上面的信号:–79。糟糕,不太好。但是这个单一的结果并没有特别大的帮助。这只是一个时间点的参考,并且仅对 WiFi 网络适配器在那个时刻所在的特定物理空间有效。更有用的是连续读取,这样就可以看到信号随着 Raspberry Pi 的移动而发生的变化。可以再次调整 main 函数来实现这一点
var i *wifi.Interface
for _, x := range interfaces {
if x.Type == wifi.InterfaceTypeStation {
// Loop through the interfaces, and assign the station
// to var x
// We could hardcode the station by name, or index,
// or hardwareaddr, but this is more portable, if less efficient
i = x
break
}
}
for {
// c.StationInfo(x) returns a slice of all
// the staton information about the interface
info, err := c.StationInfo(i)
if err != nil {
fmt.Printf("Station err: %s\n", err)
}
for _, x := range info {
fmt.Printf("Signal: %d\n", x.Signal)
}
time.Sleep(time.Second)
}
首先,我命名一个类型为 *wifi.Interface
的变量 i
。由于它在循环之外,我可以使用它来存储接口信息。在循环内部创建的任何变量都无法在该循环范围之外访问。
然后,我可以将循环分成两个。第一个循环遍历 c.Interfaces()
返回的接口,如果该接口是 Station 类型,则将其存储在之前创建的 i
变量中并跳出循环。
第二个循环是一个无限循环,所以它会一遍又一遍地运行,直到我按下 Ctrl+C 结束程序。此循环获取该接口信息并检索 Station 信息(如前所述),并打印出信号信息。然后它休眠一秒钟,再次运行,一遍又一遍地打印信号信息,直到我退出。
所以,运行它
[chris@marvin wifi-monitor]$ go run main.go
Signal: -81
Signal: -81
Signal: -79
Signal: -81
哎呀。不太好。
绘制公寓地图
至少这些信息很有用。使用附加的屏幕或 E Ink 显示屏和电池(或长长的延长线),我可以带着 Pi 在公寓里走动,并绘制出死角在哪里。
剧透警告:由于房东的接入点在隔壁的公寓里,我的大死角是从单间公寓厨房区域的冰箱发出的锥形区域……冰箱与房东的公寓共用一堵墙!
我认为用《龙与地下城》的行话来说,这是一个“静默之锥”。或者至少是一个“网络差之锥”。
无论如何,这段代码可以直接在 Raspberry Pi 上使用 go build -o wifi_scanner
编译,生成的二进制文件 wifi_scanner
可以与任何其他 ARM 设备(相同版本)共享。或者,它可以在具有 ARM 设备正确库的常规系统上编译。
祝您 Pi 扫描愉快!愿您的 WiFi 路由器不在您的冰箱后面!您可以在 我的 GitHub 仓库 中找到用于此项目的代码。
评论已关闭。