去年夏天,我和妻子卖掉了我们的财产,带着我们的两只狗搬到了夏威夷。这里有美丽的阳光、温暖的沙滩、凉爽的海浪以及您能想到的一切。我们还有一些意想不到的事情:WiFi问题。但这不是夏威夷的问题,而是我们出租公寓的问题。我们住在房东公寓隔墙的单身公寓里。我们的租赁协议中包含免费上网!伟大的!不过,它是由房东公寓的WiFi提供的,哇……老实说,它工作得很好……嗯?好吧,我承认没有达到应有的水平,也不知道问题出在哪里。路由器明明在墙的另一边,但是我们的信号很不稳定,经常自动断开。在家里,我们的WiFi路由器发出的信号会穿过几层墙壁和地板。事实上,它占地面积比我们住的600平方英尺(约55平方米)的公寓还要大。遇到这种情况,好的技术人员会怎么做?既然想知道为什么,那当然要开始调查了!幸运的是,我们在搬家之前没有出售RaspberryPiZeroW。它是如此小巧便携!当然是我随身带的。我有一个好主意:用Go编写一个小程序,通过树莓派及其内置的WiFi适配器测量并显示从路由器接收到的WiFi信号。打算先简单快速的实现,后面再考虑优化。好麻烦!我现在只想知道这个WiFi是怎么回事!经过一些谷歌搜索,我发现了一个有用的Go包mdlayher/wifi专门用于WiFi相关操作,听起来很有前途!获取WiFi接口的信息我的计划是查询WiFi接口的统计信息并返回信号强度,所以需要先在设备上找到该接口。幸运的是,mdlayher/wifi包有一个查询它们的方法,所以我可以创建一个main.go来完成它,如下所示:c,err:=wifi.New()延迟c.Close()iferr!=nil{panic(err)}interfaces,err:=c.Interfaces()for_,x:=rangeinterfaces{fmt.Printf("%+v\n",x)}}让我们看看上面的代码做了什么!首先是导入依赖包。导入后,我可以使用mdlayher/wifi模块在main函数中创建一个新的客户端(类型*Client)。接下来,只需调用这个新客户端(变量名为c)的c.Interfaces()方法即可获取系统中的接口列表。然后,我可以遍历包含接口指针的切片(可变长度数组)并打印出它们的具体信息。注意到%+v中的+了吗?这意味着该程序详细地打印了*Interface结构中的属性名称,这有助于我识别我所看到的内容,而无需查阅文档。运行上面的代码后,我得到了机器上的WiFi接口列表:&{Index:0Name:HardwareAddr:5c:5f:67:f3:0a:a7PHY:0Device:3Type:P2PdeviceFrequency:0}&{Index:3Name:wlp2s0HardwareAddr:5c:5f:67:f3:0a:a7PHY:0Device:1Type:stationFrequency:2412}注意MAC地址(HardwareAddr)是相同,这意味着它们是相同的物理硬件。也可以用PHY确认:0。参考Go的wifi模块文档,PHY是指接口所属的物理设备。第一个接口没有名字,类型是TYPE:P2P。第二个接口称为wpl2s0,其类型为TYPE:Station。wifi模块的文档列出了不同类型的接口及其用途。根据文档,P2P(Peer-to-PeerTransfer)类型表示“该接口属于对等客户端网络中的设备”。我认为这个接口的目的是WiFiDirect,这是一个允许两个WiFi设备直接连接而无需中间接入点的标准。站(基站)类型表示“接口是由具有控制接入点的客户端设备管理的基本服务集(BSS)的一部分”。这是无线设备熟悉的标准功能:作为客户端连接到网络接入点。这是测试WiFi质量的重要接口。使用接口获取站信息使用此信息,我可以修改迭代接口的代码以获取我需要的信息:for_,x:=rangeinterfaces{ifx.Type==wifi.InterfaceTypeStation{//c.StationInfo(x)returnsasliceofall//thestatoninformationabouttheinterfaceinfo,err:=c.StationInfo(x)iferr!=nil{fmt.Printf("Stationerr:%s\n",err)复制代码}for_,x:=rangeinfo{fmt.Printf("%+v\n",x)}}}首先,这个程序检查x.Type(接口类型)是否为wifi.InterfaceTypeStation,即基站接口(也是本练习中涵盖的唯一类型)。不幸的是名称冲突,这个接口“类型”不是Golang中的“类型”。事实上,我在这里使用一个名为interfaceType的Go类型来表示接口类型。ew,我花了一分钟才弄明白!然后,假设接口的类型是正确的,我们可以调用c.StationInfo(x)来获取基站信息,而StationInfo()方法可以得到这个接口x的信息。这将返回一个包含*StationInfo指针的切片。不知道这里为什么要用slice,可能是因为接口可能会返回多个StationInfo吧?无论哪种方式,我都可以遍历切片并使用前面提到的+%v技巧来格式化和打印StationInfo结构的属性名称和值。运行上述程序后,我得到以下输出:&{HardwareAddr:70:5a:9e:71:2e:d4Connected:17m10sInactive:1.579sReceivedBytes:2458563TransmittedBytes:1295562ReceivedPackets:6355TransmittedPackets:6135ReceiveBitrate:20000TransmitBitrate:43300000Signal:-79TransmitRetries:2306TransmitFailed:4BeaconLoss:2}我对Signal部分感兴趣,可能还有TransmitFailed和BeaconLoss(信标丢失)部分。信号强度以dBm(分贝-毫瓦)为单位报告。小科普:如何读懂WiFidBm根据MetaGeek的说法:-30是最优的,但既不现实也没有必要-67很好,适合需要可靠数据包传输的应用程序,例如流媒体-70还不错,它是可靠数据包传输的底线,适合电子邮件和网页浏览-80很差,只是基本连接,数据包传输不可靠-90不可用,接近“本底噪声”注意:dBm在对数刻度上,-60是1000倍低于-30。让它成为真正的“扫描仪”所以,查看上面的输出显示我的信号:-79。哇,感觉不太好。然而,单看这个结果并没有多大帮助。它只能为某个时间点提供参考,仅在WiFi网络适配器处于特定物理空间的某个时刻有效。连续读数会更有用,在它的帮助下,我们观察了Pi移动时信号的变化。我可以再次修改主要功能来实现这一点。vari*wifi.Interfacefor_,x:=rangeinterfaces{ifx.Type==wifi.InterfaceTypeStation{//遍历接口,并将站点//分配给varx//我们可以按名称对站点进行硬编码,orindex,//orhardwareaddr,butthisismoreportable,iflessefficienti=xbreak}}for{//c.StationInfo(x)返回所有的切片//关于接口信息的站点信息,错误:=c.StationInfo(i)iferr!=nil{fmt.Printf("Stationerr:%s\n",err)}for_,x:=rangeinfo{fmt.Printf("信号:%d\n",x.Signal)}time.Sleep(time.Second)}首先,我将一个类型为wifi.Interface的变量命名为i。由于它在循环范围之外,我可以用它来存储接口信息。在循环内创建的任何变量都无法在该循环范围外访问。然后,我可以将这个循环一分为二。第一个遍历c.Interfaces()返回的接口切片,如果元素是Station类型,则将其存储在先前创建的变量i中,然后跳出循环。第二个循环是一个无限循环,它会一直运行,直到我按Ctrl+C结束程序。和以前一样,这个循环在内部获取接口信息,检索基站信息,并打印出信号信息。然后它休眠一秒钟,再次运行,重复打印信号信息,直到我退出。运行上述程序后,我得到以下输出:[chris@marvinwifi-monitor]$gorunmain.goSignal:-81Signal:-81Signal:-79Signal:-81哇,这感觉很糟糕。映射公寓信号分布无论如何,知道这些信息总比不知道好。通过将显示器或电子墨水屏幕连接到RaspberryPi并将其插入电源,我可以在公寓周围移动它并绘制出信号盲点的位置。剧透警告:由于房东的接入点在隔壁公寓,所以对我来说最大的死角就是公寓厨房冰箱顶点的圆锥形区域……也就是房东公寓旁边有一堵墙!我觉得如果用《龙与地下城》里的俚语,就是“ConeofSilence”。或者至少是一个“贫困互联网锥”。总之,这段代码可以直接在树莓派上运行gobuild-owifi_scanner编译,生成的二进制文件wifi_scanner可以在其他类似的ARM设备上运行。或者,也可以在常规系统上使用适用于ARM设备的正确库对其进行编译。扫描愉快!希望您的WiFi路由器不在冰箱后面!您可以在我的GitHub存储库中找到用于该项目的代码。
