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

二、适用场景
- 家庭/办公室排查 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

四、总体流程(3 步)
- 规划采样点(在平面图上用格子或以米为单位定位)。
- 在每个采样点运行一次简单采样脚本,记录时间、坐标、BSSID/SSID 与 RSSI。
- 用聚合脚本把数据写入 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 布局的读者。





