ESP32 获取地理位置:Wi-Fi网络定位
1. 引言
你是否想过让ESP32知道自己在哪里?在室外,GPS模块是首选;但在室内或高楼林立的城市,GPS信号可能很差,这时Wi-Fi网络定位就成了一个低成本、易实现的替代方案。它的原理很简单:ESP32扫描周围的Wi-Fi热点,将热点的MAC地址和信号强度发给云服务商,服务商根据全球Wi-Fi热点数据库估算出设备的大致位置,并以经纬度返回。
2. 准备工作
硬件
-
ESP32开发板
-
Micro-USB数据线
-
电脑
软件
-
Arduino IDE(已配置好ESP32开发环境
-
需要安装的库(Arduino IDE会自动提示安装,或手动安装):
-
ArduinoJson(用于解析JSON数据)
-
获取云服务商API密钥
我们以腾讯位置服务为例(国内访问快,文档齐全):
-
访问腾讯位置服务官网,注册并登录。
-
进入控制台 → 应用管理 → 我的应用,点击“创建应用”。
-
填写应用名称(如“ESP32定位”),应用类型选择“其他”。
-
创建成功后,进入应用详情,点击“添加Key”,选择WebService API,输入Key名称,完成创建。
-
复制生成的Key(一串字母数字组合),后面代码中需要用到。
注意:腾讯位置服务的网络定位接口是免费的,但有一定调用限额,个人学习完全够用。
3. 硬件连接
无需任何额外连接! ESP32的Wi-Fi模块已集成在板载,我们只需给ESP32供电即可。
4. 编写代码
以下代码已通过测试,你只需要替换其中的Wi-Fi信息和腾讯Key即可。请确保所有引号都是英文半角。
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
// ==================== 配置区域 ====================
// 替换成你的Wi-Fi账号和密码(用于让ESP32联网查询)
const char* WIFI_SSID = "wifi名";
const char* WIFI_PASSWORD = "密码";
// 替换成你在腾讯位置服务申请的Key
const String TENCENT_KEY = "你的Key,密钥";
// 设备的唯一ID,可以自定义,例如 "esp32_device_01"
const String DEVICE_ID = "my_esp32_locator";
// ================================================
// 上报的最多Wi-Fi数量
const int MAX_WIFI_REPORT = 8;
// 存储扫描结果的全局变量
int networksFound = 0;
String wifi_macs[MAX_WIFI_REPORT];
int wifi_rssis[MAX_WIFI_REPORT];
void setup() {
Serial.begin(115200);
Serial.println("系统启动...");
// 1. 初始化Wi-Fi,用于扫描和后续联网查询
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
Serial.println("开始扫描Wi-Fi热点...");
// 2. 扫描Wi-Fi热点
networksFound = WiFi.scanNetworks();
if (networksFound == 0) {
Serial.println("未扫描到任何Wi-Fi热点!");
return;
} else {
Serial.printf("扫描到 %d 个热点\n", networksFound);
// 收集MAC地址和RSSI,并按信号强度排序(只保留最强的若干个)
// 这是一个简化处理:实际应用中你可以按信号强度排序
int reportCount = min(networksFound, MAX_WIFI_REPORT);
for (int i = 0; i < reportCount; i++) {
wifi_macs[i] = WiFi.BSSIDstr(i);
wifi_rssis[i] = WiFi.RSSI(i);
Serial.printf(" %d: MAC=%s, RSSI=%d\n", i, wifi_macs[i].c_str(), wifi_rssis[i]);
}
}
// 3. 连接到Wi-Fi网络(用于调用API)
Serial.printf("正在连接Wi-Fi (%s)...\n", WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi已连接!");
// 4. 调用定位API
getLocationFromTencent();
// 5. 可选:深度睡眠或结束
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
Serial.println("定位完成,系统进入深度睡眠...");
// 这里可以添加进入睡眠的代码,比如 esp_deep_sleep_start();
}
void loop() {
// 本示例只在setup中运行一次,loop保持为空
}
void getLocationFromTencent() {
HTTPClient http;
String url = "https://apis.map.qq.com/ws/location/v1/network";
// 构造JSON请求体
DynamicJsonDocument doc(2048);
doc["key"] = TENCENT_KEY;
doc["device_id"] = DEVICE_ID;
JsonArray wifiList = doc.createNestedArray("wifiinfo");
int reportCount = min(networksFound, MAX_WIFI_REPORT);
for (int i = 0; i < reportCount; i++) {
JsonObject wifi = wifiList.createNestedObject();
wifi["mac"] = wifi_macs[i];
wifi["rssi"] = wifi_rssis[i];
}
String requestBody;
serializeJson(doc, requestBody);
Serial.println("请求数据: " + requestBody);
http.begin(url);
http.addHeader("Content-Type", "application/json");
http.setTimeout(15000); // 设置超时时间15秒
int httpCode = http.POST(requestBody);
if (httpCode > 0) {
String response = http.getString();
Serial.printf("HTTP响应码: %d\n", httpCode);
Serial.println("响应内容: " + response);
// 解析返回的JSON
parseResponse(response);
} else {
Serial.printf("HTTP请求失败,错误: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
void parseResponse(String response) {
DynamicJsonDocument doc(1024);
DeserializationError error = deserializeJson(doc, response);
if (error) {
Serial.print("JSON解析失败: ");
Serial.println(error.c_str());
return;
}
int status = doc["status"];
if (status == 0) {
float lat = doc["result"]["location"]["lat"];
float lng = doc["result"]["location"]["lng"];
float accuracy = doc["result"]["accuracy"]; // 定位精度半径,单位:米
Serial.println("========== 定位成功 ==========");
Serial.print("纬度: "); Serial.println(lat, 6);
Serial.print("经度: "); Serial.println(lng, 6);
Serial.print("精度范围: "); Serial.print(accuracy); Serial.println(" 米");
Serial.println("==============================");
} else {
String message = doc["message"];
Serial.printf("定位失败,错误码: %d, 信息: %s\n", status, message.c_str());
}
}
5. 代码解释
5.1 Wi-Fi扫描
-
WiFi.scanNetworks()扫描周围的Wi-Fi热点,返回找到的数量。 -
WiFi.BSSIDstr(i)获取第i个热点的MAC地址,格式如"xx:xx:xx:xx:xx:xx"。 -
WiFi.RSSI(i)获取信号强度(负值,越接近0信号越强)。 -
我们只取信号最强的8个热点上报,因为太多也无益,且API有长度限制。
5.2 连接互联网
-
使用你自己的Wi-Fi账号让ESP32连上网,这样才能访问腾讯的API。
5.3 构建请求
-
使用ArduinoJson库构造一个JSON对象,包含key、device_id和wifiinfo数组。
-
wifiinfo数组中每个元素包含mac和rssi。
5.4 发送HTTPS POST请求
-
请求地址:
https://apis.map.qq.com/ws/location/v1/network -
设置Content-Type为
application/json,发送JSON数据。
5.5 解析响应
-
返回的JSON中包含
status(0表示成功)、result.location.lat/lng(经纬度)、accuracy(精度半径,单位米)。 -
我们将其打印到串口。
6. 上传与测试
-
在Arduino IDE中,将代码中的
WIFI_SSID、WIFI_PASSWORD、TENCENT_KEY替换为真实值。 -
选择正确的开发板(如“ESP32 Dev Module”)和端口。
-
点击“上传”按钮,等待编译并烧录完成。
-
打开串口监视器(波特率115200),你会看到如下输出:
系统启动... 开始扫描Wi-Fi热点... 扫描到 10 个热点 0: MAC=xxxx, RSSI=-51 1: MAC=xxxx, RSSI=-55 2: MAC=xxxx, RSSI=-59 3: MAC=xxxx, RSSI=-68 4: MAC=xxxx, RSSI=-72 5: MAC=xxxx, RSSI=-73 6: MAC=xxxx, RSSI=-77 7: MAC=xxxx, RSSI=-90 正在连接Wi-Fi (xxxxx)... ..... Wi-Fi已连接! 请求数据: {"key":"HZ7BZ-CRJEL-YHPPV-MABFI-7TYYH-LAFTQ","device_id":"my_esp32_locator","wifiinfo":[{"mac":"1C:E6:39:BB:C0:EC","rssi":-51},{"mac":"78:C1:A7:CA:D3:59","rssi":-55},{"mac":"94:BE:09:74:AF:1D","rssi":-59},{"mac":"FC:D7:33:97:2B:38","rssi":-68},{"mac":"82:16:0F:FA:62:13","rssi":-72},{"mac":"82:16:0F:FA:62:21","rssi":-73},{"mac":"5C:54:6D:F4:E5:F0","rssi":-77},{"mac":"48:3F:E9:63:A7:88","rssi":-90}]} HTTP响应码: 200 响应内容: {"status":0,"message":"Success","request_id":"xxxx","result":{"address":"江苏省苏州市XXXXX","location":{"latitude":xx.xxxxxx,"longitude":xxx.xxxxxx,"altitude":0,"accuracy":150},"ad_info":{"adcode":"xxxxx","nation":"中国","province":"江苏省","city":"苏州市","district":"xxx"}}}
7. 结果分析
得到了一个经纬度坐标和一个精度值(accuracy)。精度值表示估算位置有68%的概率落在以该点为圆心、精度值为半径的圆内。在本例中,精度为150米。
你可以将这个坐标与手机指南针或地图App上的实际位置对比,可能会发现几十米到几百米的偏差。这是正常现象,原因包括:
-
Wi-Fi定位依赖服务商的数据库,数据库可能不完整或不精确。
-
你周围Wi-Fi热点稀疏,导致估算误差大。
-
信号波动影响RSSI值,从而影响定位结果。
8. 常见问题及解决
Q1: 扫描不到任何Wi-Fi
A: 检查ESP32天线是否正常,尝试将ESP32放在路由器附近。如果还是扫描不到,可能是硬件问题。
Q2: HTTP请求失败,返回-1或其它错误码
A: 检查Wi-Fi连接是否成功,API密钥是否正确,网络是否通畅。可以尝试ping一下腾讯的API域名(apis.map.qq.com)测试网络。
Q3: 返回错误码“320”或“311”
A: 腾讯API错误码:
-
320:缺少key参数 → 检查
TENCENT_KEY是否已定义。 -
311:key格式错误 → 确认Key是否正确复制。
-
其他错误码可参考腾讯官方文档。
Q4: 定位精度很差,偏差几公里
A: Wi-Fi定位的精度受环境制约。如果周围Wi-Fi热点很少,服务商可能会根据你的IP地址进行粗略定位,误差可能达数公里。建议在Wi-Fi密集的市区测试。
9. 扩展应用
有了经纬度,你可以做很多有趣的事:
-
OLED显示:将坐标显示在0.96寸OLED屏上。
-
云端上传:通过MQTT或HTTP将坐标发送到自己的服务器,结合地图API(如高德、百度)实现实时追踪。
-
地理围栏:判断设备是否进入或离开某个区域,触发报警。
-
结合GPS:当GPS信号弱时自动切换到Wi-Fi定位。
10. 总结
本教程带你用ESP32和腾讯位置服务实现了Wi-Fi网络定位,无需额外硬件,成本低,适合室内外粗略定位。虽然精度无法与GPS媲美,但在许多物联网场景中已足够。你可以根据自己的需求调整代码,进一步开发出更多有趣的应用。
更多推荐


所有评论(0)