Wi-Fi 热力图实战

一、前言

想快速绘制局域室内的 Wi-Fi 信号热力图来找死角或优化 AP 布局?本文给出最简单、可落地的方案:用一台 Raspberry Pi / Linux 笔记本 + 支持扫描的无线网卡,按楼层网格采样信号强度(RSSI),把采样写入 SQLite/CSV,然后用一个短小 Python 脚本生成热力图。步骤直观、脚本简单,适合非专业也能马上跑通的实践。

Wi-Fi 热力图实战

二、适用场景

  • 家庭/办公室排查 Wi-Fi 盲区
  • 优化路由器/AP 放置位置与信道选择
  • 做无线测试并长期保存信号采样数据

三、准备(超级简单)

  • 一台 Linux 设备:Raspberry Pi / Ubuntu 笔记本(需能执行 shell 命令)
  • 无线网卡:内置或 USB,能执行 iw 或 iwlist scan(常见芯片均可用于被动扫描)
  • 软件:iw(或 wireless-tools 包中的 iwlist)、python3、sqlite3、(可选)matplotlib 用于画图

安装示例(Debian/Ubuntu / Raspberry Pi OS):

sudo apt update

sudo apt install -y iw wireless-tools python3 python3-pip sqlite3

# 若要生成图片:

sudo apt install -y python3-matplotlib

Wi-Fi 热力图实战

四、总体流程(3 步)

  1. 规划采样点(在平面图上用格子或以米为单位定位)。
  2. 在每个采样点运行一次简单采样脚本,记录时间、坐标、BSSID/SSID 与 RSSI。
  3. 用聚合脚本把数据写入 SQLite,并用 Python 生成热力图或导出 CSV 供其它工具绘制。

五、采样脚本(最小化实现)

把下面脚本保存为 sample_wifi.sh 并赋可执行权限(在每个采样点运行):

#!/bin/bash

# sample_wifi.sh

# 用法: sudo ./sample_wifi.sh X Y (X,Y 为采样点坐标,整数或浮点)

# 依赖: iw 或 iwlist (脚本尝试用 iw first, fallback to iwlist)

X=$1

Y=$2

OUT_DB=”/opt/wifi_map/wifi_samples.db”

TMP=”/tmp/wifi_scan.$$”

if [ -z “$X” ] || [ -z “$Y” ]; then

echo “Usage: sudo $0 X Y”

exit 1

fi

# 首选使用 iw

if command -v iw >/dev/null 2>&1; then

# 使用 wlan0,可按需修改接口名

IFACE=$(iw dev | awk '/Interface/ {print $2; exit}')

iw dev $IFACE scan > $TMP 2>/dev/null || iw dev $IFACE scan –rescan > $TMP 2>/dev/null

else

# fallback to iwlist

IFACE=$(iwconfig 2>/dev/null | awk '/IEEE/{print $1; exit}')

sudo iwlist $IFACE scan > $TMP 2>/dev/null

fi

TS=$(date +%s)

# 解析并写入 SQLite(使用 sqlite3 CLI,最小依赖)

mkdir -p $(dirname “$OUT_DB”)

sqlite3 “$OUT_DB” “CREATE TABLE IF NOT EXISTS samples(id INTEGER PRIMARY KEY, ts INTEGER, x REAL, y REAL, ssid TEXT, bssid TEXT, rssi INTEGER);”

# 解析iw 输出:查找 BSSID 行和 signal: 行(兼容多种格式)

awk -v ts=”$TS” -v x=”$X” -v y=”$Y” '

/BSS [0-9a-fA-F:]+/ {bssid=$2}

/Address: [0-9a-fA-F:]+/ {bssid=$2}

/ESSID:|SSID:/ {ssid=$0; gsub(/.*ESSID:|”|SSID:/,””,ssid)}

/signal:|Signal level=/ {

s=$0

if (match(s,/(-?[0-9]+) dBm/)) {r=substr(s,RSTART,RLENGTH); gsub(/ dBm/,””,r)}

else if (match(s,/(-?[0-9]+)/-?[0-9]+/)) {split(s,a,”/”); r=a[1]}

else {r=0}

# 输出 SQL 插入行

if (bssid!=””) {

gsub(/'/,”''”,ssid)

printf(“INSERT INTO samples(ts,x,y,ssid,bssid,rssi) VALUES(%d,%f,%f,''%s'',''%s'',%d);
“, ts, x, y, ssid, bssid, r)

}

}

' “$TMP” | sqlite3 “$OUT_DB”

rm -f “$TMP”

echo “Sampled at ($X,$Y) — saved to $OUT_DB”

说明:

  • 运行需 root(或 sudo),由于扫描一般需要特权:sudo ./sample_wifi.sh 2 3。
  • 接口自动识别(若你想指定接口,请把 IFACE 强制设为 wlan0 或 wlan1)。
  • 脚本兼容 iw 与 iwlist 两种输出。结果存到 /opt/wifi_map/wifi_samples.db。

六、聚合与可视化脚本(简单热力图)

把下面小脚本保存为 plot_wifi.py(需要 matplotlib):

#!/usr/bin/env python3

# plot_wifi.py

import sqlite3, numpy as np, matplotlib.pyplot as plt

DB=”/opt/wifi_map/wifi_samples.db”

def top_bssid(conn):

cur=conn.cursor()

cur.execute(“SELECT bssid, COUNT(*) as c FROM samples GROUP BY bssid ORDER BY c DESC LIMIT 1”)

r=cur.fetchone()

return r[0] if r else None

def load_points(conn, bssid):

cur=conn.cursor()

cur.execute(“SELECT x,y,rssi FROM samples WHERE bssid=?”,(bssid,))

return cur.fetchall()

def heatmap(points, bins=50):

xs=[p[0] for p in points]

ys=[p[1] for p in points]

vals=[p[2] for p in points]

# 用加权平均做网格

xi = np.linspace(min(xs), max(xs), bins)

yi = np.linspace(min(ys), max(ys), bins)

X, Y = np.meshgrid(xi, yi)

Z = np.zeros_like(X)

W = np.zeros_like(X)

for x,y,v in zip(xs,ys,vals):

# 找到最近的格子

ix = np.searchsorted(xi, x)-1

iy = np.searchsorted(yi, y)-1

if 0<=ix<bins and 0<=iy<bins:

Z[iy,ix] += v

W[iy,ix] += 1

# 平均

with np.errstate(invalid='ignore'):

Z = np.where(W>0, Z/W, np.nan)

return X,Y,Z

def main():

conn=sqlite3.connect(DB)

bssid = top_bssid(conn)

if not bssid:

print(“No data found in DB.”)

return

pts = load_points(conn, bssid)

if not pts:

print(“No points for BSSID”, bssid)

return

X,Y,Z = heatmap(pts, bins=80)

plt.figure(figsize=(8,6))

plt.title(f”Heatmap for {bssid}”)

plt.xlabel(“X”)

plt.ylabel(“Y”)

cmap = plt.get_cmap('jet')

im = plt.pcolormesh(X, Y, Z, cmap=cmap, shading='auto')

plt.colorbar(im,label='RSSI (dBm)')

plt.scatter([p[0] for p in pts],[p[1] for p in pts], c='k', s=5, alpha=0.6)

plt.savefig(“/opt/wifi_map/wifi_heatmap.png”, dpi=150)

print(“Saved /opt/wifi_map/wifi_heatmap.png”)

plt.close()

if __name__ == “__main__”:

main()

运行生成图片:

python3 plot_wifi.py

# 输出:
/opt/wifi_map/wifi_heatmap.png

说明:

  • 脚本选取出现次数最多的 BSSID(一般是你的目标 AP)做热力图。你也可以修改脚本按 ssid 或指定 bssid 绘图。
  • 网格采样与插值超级简单(加权平均),目的是快速可视化;若要更精细可用插值库(scipy.interpolate)。

七、采样提议(实操小贴士)

  • 规划网格:先在平面图上划定网格(例如每格 1m 或 2m),按格中心采样。
  • 每点多次采样取平均(例如在点上停留 3–5 次运行脚本或手动多次触发)。
  • 记录高度(楼层或不同台面高度会影响信号)时可以把高度作为第三维度或在坐标里标注。
  • 若用手机采样:可以 SSH 到 Pi 并触发 sample_wifi.sh,或把手机采样数据导出成同样格式再导入 SQLite。

八、数据管理与导出

  • 查看最近 20 条样本:

sqlite3 /opt/wifi_map/wifi_samples.db “SELECT datetime(ts,'unixepoch','localtime'),x,y,ssid,bssid,rssi FROM samples ORDER BY ts DESC LIMIT 20;”

  • 导出 CSV(供 Excel / QGIS 用):

sqlite3 -header -csv /opt/wifi_map/wifi_samples.db “SELECT ts,x,y,ssid,bssid,rssi FROM samples;” > /opt/wifi_map/wifi_samples.csv

九、常见问题与排查

  • 接口未被识别或扫描无结果:确认无线接口名并用 iw dev / iwlist wlan0 scan 手动测试。
  • RSSI 格式差异:不同驱动输出不同格式,脚本做基本兼容;若解析出 0 或异常值,请手动查看扫描原始输出并调整 awk/解析逻辑。
  • 权限问题:扫描命令一般需要 root,使用 sudo。
  • 网格覆盖不足:采样点太少会导致热力图不准确,多采样点会更平滑。

十、总结

本文提供一个极简可落地的 Wi-Fi 热力图方案:用小脚本按点采样 RSSI,写入 SQLite,再用小 Python 脚本生成热力图。所有代码短小、依赖少,几条命令就能跑通,适合想快速定位无线死角或优化 AP 布局的读者。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...