toy-Kapi_Rtc/main/weather_api.cc
2026-01-20 16:55:17 +08:00

770 lines
36 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "weather_api.h"
#include <string>
#include <unordered_map>
#include <cctype>
#include <vector>
#include <time.h>
// ESP32 ESP-IDF headers
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_client.h"
#include "esp_log.h"
#include "cJSON.h"
#include "esp_crt_bundle.h"
#include "esp_wifi.h"
#include "nvs.h"
#include "nvs_flash.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#ifdef __cplusplus
}
#endif
#include "zlib.h"
static const char* TAG = "WeatherApi";
// 天气代码映射表 - 将和风天气API的天气代码转换为中文描述
std::unordered_map<std::string, std::string> WeatherApi::WEATHER_CODE_MAP = {
{"100", ""}, {"101", "多云"}, {"102", "少云"}, {"103", "晴间多云"},
{"104", ""}, {"150", ""}, {"151", "多云"}, {"152", "少云"},
{"153", "晴间多云"}, {"300", "阵雨"}, {"301", "强阵雨"}, {"302", "雷阵雨"},
{"303", "强雷阵雨"}, {"304", "雷阵雨伴有冰雹"}, {"305", "小雨"}, {"306", "中雨"},
{"307", "大雨"}, {"308", "极端降雨"}, {"309", "毛毛雨/细雨"}, {"310", "暴雨"},
{"311", "大暴雨"}, {"312", "特大暴雨"}, {"313", "冻雨"}, {"314", "小到中雨"},
{"315", "中到大雨"}, {"316", "大到暴雨"}, {"317", "暴雨到大暴雨"}, {"318", "大暴雨到特大暴雨"},
{"350", "阵雨"}, {"351", "强阵雨"}, {"399", ""}, {"400", "小雪"},
{"401", "中雪"}, {"402", "大雪"}, {"403", "暴雪"}, {"404", "雨夹雪"},
{"405", "雨雪天气"}, {"406", "阵雨夹雪"}, {"407", "阵雪"}, {"408", "小到中雪"},
{"409", "中到大雪"}, {"410", "大到暴雪"}, {"456", "阵雨夹雪"}, {"457", "阵雪"},
{"499", ""}, {"500", "薄雾"}, {"501", ""}, {"502", ""},
{"503", "扬沙"}, {"504", "浮尘"}, {"507", "沙尘暴"}, {"508", "强沙尘暴"},
{"509", "浓雾"}, {"510", "强浓雾"}, {"511", "中度霾"}, {"512", "重度霾"},
{"513", "严重霾"}, {"514", "大雾"}, {"515", "特强浓雾"}, {"900", ""},
{"901", ""}, {"999", "未知"}
};
static std::string MapLangCode(const std::string& lang) {
if (lang.empty()) return "zh";
if (lang == "zh_CN" || lang == "zh" || lang == "zh-Hans") return "zh";
if (lang == "zh_HK" || lang == "zh-TW" || lang == "zh-Hant") return "zh-Hant";
if (lang == "en_US" || lang == "en") return "en";
if (lang == "ja_JP" || lang == "ja") return "ja";
if (lang == "ko_KR" || lang == "ko") return "ko";
if (lang == "fr_FR" || lang == "fr") return "fr";
if (lang == "de_DE" || lang == "de") return "de";
if (lang == "es_ES" || lang == "es") return "es";
return "zh";
}
static bool IsGzipData(const std::string& data) {
if (data.size() < 2) return false;
const unsigned char* p = reinterpret_cast<const unsigned char*>(data.data());
return p[0] == 0x1F && p[1] == 0x8B;
}
static bool GzipDecompress(const std::string& in, std::string& out) {
if (in.empty()) return false;
z_stream strm;
memset(&strm, 0, sizeof(strm));
strm.next_in = (Bytef*)in.data();
strm.avail_in = (uInt)in.size();
int ret = inflateInit2(&strm, 16 + MAX_WBITS);
if (ret != Z_OK) {
ESP_LOGE(TAG, "inflateInit2失败: %d", ret);
return false;
}
std::vector<char> buf(1024);
while (true) {
strm.next_out = (Bytef*)buf.data();
strm.avail_out = (uInt)buf.size();
ret = inflate(&strm, Z_NO_FLUSH);
if (ret == Z_STREAM_END) {
size_t produced = buf.size() - strm.avail_out;
if (produced) out.append(buf.data(), produced);
break;
} else if (ret == Z_OK || ret == Z_BUF_ERROR) {
size_t produced = buf.size() - strm.avail_out;
if (produced) out.append(buf.data(), produced);
continue;
} else {
ESP_LOGE(TAG, "inflate失败: %d", ret);
inflateEnd(&strm);
return false;
}
}
inflateEnd(&strm);
return true;
}
// 缓存条目结构体,包含时间戳信息
typedef struct {
time_t timestamp;// 缓存条目时间戳
} CacheEntry;
// 缓存上限常量
#define MAX_WIFI_CITY_CACHE 5
// WeatherApi构造函数初始化配置参数
WeatherApi::WeatherApi() {
// 硬编码配置参数 - 从get_weather.py文件中提取的配置与小智server保持一致
api_host_ = "kq3aapg9h5.re.qweatherapi.com";
api_key_ = "aa5ec0859c144ac7b33966e25eef5580";
default_location_ = "北京"; // 默认城市设置为北京
kid_ = "T45F5GTR8Y";
project_id_ = "4N855TEVNN";
private_key_ = "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEIA26lz31HoaZV17EjIGcyo9YNGGQ77/gOZU8Chw8wlWq\n-----END PRIVATE KEY-----";
ESP_LOGI(TAG, "初始化天气API配置 - 默认城市: %s", default_location_.c_str());
ESP_LOGI(TAG, "WiFi位置缓存限制已设置为: %d 条", MAX_WIFI_CITY_CACHE);
}
// 生成JWT令牌预留接口当前版本暂不实现完整JWT认证
std::string WeatherApi::GenerateJwtToken() {
ESP_LOGI(TAG, "JWT令牌生成预留接口");
return "";
}
// 封装HTTP GET请求
bool WeatherApi::HttpGet(const std::string& url, const std::string& headers, std::string& response) {
ESP_LOGI(TAG, "HTTP请求: %s", url.c_str());
esp_http_client_config_t config = {};
config.url = url.c_str();
config.method = HTTP_METHOD_GET;
config.transport_type = HTTP_TRANSPORT_OVER_SSL;
#ifdef CONFIG_MBEDTLS_CERTIFICATE_BUNDLE
config.crt_bundle_attach = esp_crt_bundle_attach;
#else
ESP_LOGE(TAG, "证书包未启用,无法进行服务器证书验证。请在 menuconfig 启用 ESP-TLS Certificate Bundle");
#endif
esp_http_client_handle_t client = esp_http_client_init(&config);
if (!client) {
ESP_LOGE(TAG, "HTTP客户端初始化失败");
return false;
}
if (!headers.empty()) {
esp_http_client_set_header(client, "Authorization", headers.c_str());
}
esp_http_client_set_header(client, "User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36");
esp_http_client_set_header(client, "Accept", "application/json");
// esp_http_client_set_header(client, "Accept-Encoding", "identity");// 禁用压缩,防止解压失败
esp_err_t err = esp_http_client_open(client, 0);
bool success = false;
if (err == ESP_OK) {
int64_t headers_len = esp_http_client_fetch_headers(client);
int status_code = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "HTTP状态码: %d, 头长度: %lld", status_code, (long long)headers_len);
if (status_code == 200) {
char buf[512];
int read_len;
int total = 0;
while ((read_len = esp_http_client_read(client, buf, sizeof(buf))) > 0) {
response.append(buf, read_len);
total += read_len;
}
if (IsGzipData(response)) {
std::string decompressed;
if (GzipDecompress(response, decompressed)) {
response.swap(decompressed);
ESP_LOGI(TAG, "GZIP解压成功解压后长度: %d", (int)response.size());
} else {
ESP_LOGE(TAG, "GZIP解压失败保留原始响应");
}
}
success = (total > 0);
ESP_LOGI(TAG, "读取完成,累计长度: %d", total);
if (!success) {
ESP_LOGE(TAG, "读取响应内容失败");
}
} else {
ESP_LOGE(TAG, "HTTP请求失败状态码: %d", status_code);
}
esp_http_client_close(client);
} else {
ESP_LOGE(TAG, "HTTP请求执行失败: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
vTaskDelay(100 / portTICK_PERIOD_MS);
return success;
}
// URL编码函数
std::string UrlEncode(const std::string& str) {
std::string encoded;
for (char c : str) {
if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
encoded += c;
} else {
encoded += '%';
encoded += "0123456789ABCDEF"[static_cast<unsigned char>(c) / 16];
encoded += "0123456789ABCDEF"[static_cast<unsigned char>(c) % 16];
}
}
return encoded;
}
// 获取城市信息
std::string WeatherApi::FetchCityInfo(const std::string& location, const std::string& lang) {
ESP_LOGI(TAG, "[FetchCityInfo] 开始获取城市信息location参数: '%s', 长度: %zu", location.c_str(), location.length());
// 对location参数进行URL编码
std::string encoded_location = UrlEncode(location);
ESP_LOGI(TAG, "[FetchCityInfo] URL编码后: '%s'", encoded_location.c_str());
// 构建城市信息查询URL
std::string q_lang = MapLangCode(lang);
std::string url = "https://" + api_host_ + "/geo/v2/city/lookup?key=" + api_key_ +
"\u0026location=" + encoded_location + "\u0026lang=" + q_lang;
std::string response;
ESP_LOGI(TAG, "[FetchCityInfo] API主机: '%s', API密钥长度: %zu", api_host_.c_str(), api_key_.length());
ESP_LOGI(TAG, "[FetchCityInfo] 查询城市信息URL: '%s'", url.c_str());
// 发送HTTP请求
ESP_LOGI(TAG, "[FetchCityInfo] 开始发送HTTP GET请求");
if (HttpGet(url, "", response)) {
ESP_LOGI(TAG, "[FetchCityInfo] HTTP请求成功响应长度: %zu 字节", response.length());
ESP_LOGD(TAG, "[FetchCityInfo] 响应内容前100字节: '%s'", response.substr(0, std::min(size_t(100), response.length())).c_str());
// 解析JSON响应
cJSON* root = cJSON_Parse(response.c_str());
if (root) {
ESP_LOGI(TAG, "[FetchCityInfo] JSON解析成功");
// 检查响应状态
cJSON* code = cJSON_GetObjectItem(root, "code");
if (code) {
ESP_LOGI(TAG, "[FetchCityInfo] 响应状态码存在: '%s'", code->valuestring);
if (cJSON_IsString(code) && strcmp(code->valuestring, "200") == 0) {
ESP_LOGI(TAG, "[FetchCityInfo] 响应状态码为200成功");
// 获取location数组
cJSON* location_array = cJSON_GetObjectItem(root, "location");
if (location_array) {
ESP_LOGI(TAG, "[FetchCityInfo] location数组存在");
if (cJSON_IsArray(location_array)) {
int array_size = cJSON_GetArraySize(location_array);
ESP_LOGI(TAG, "[FetchCityInfo] location数组大小: %d", array_size);
if (array_size > 0) {
// 获取第一个城市信息
cJSON* first_location = cJSON_GetArrayItem(location_array, 0);
if (first_location) {
ESP_LOGI(TAG, "[FetchCityInfo] 获取到第一个城市信息对象");
// 获取城市ID
cJSON* id = cJSON_GetObjectItem(first_location, "id");
if (id) {
ESP_LOGI(TAG, "[FetchCityInfo] 城市ID字段存在");
if (cJSON_IsString(id)) {
std::string city_id = id->valuestring;
ESP_LOGI(TAG, "[FetchCityInfo] 成功获取城市ID: '%s'", city_id.c_str());
cJSON_Delete(root);
return city_id;
} else {
ESP_LOGE(TAG, "[FetchCityInfo] 城市ID不是字符串类型");
}
} else {
ESP_LOGE(TAG, "[FetchCityInfo] 未能获取城市ID字段");
}
} else {
ESP_LOGE(TAG, "[FetchCityInfo] 未能获取第一个城市信息对象");
}
} else {
ESP_LOGE(TAG, "[FetchCityInfo] location数组为空");
}
} else {
ESP_LOGE(TAG, "[FetchCityInfo] location不是数组类型");
}
} else {
ESP_LOGE(TAG, "[FetchCityInfo] 响应中没有location数组");
}
} else {
ESP_LOGE(TAG, "[FetchCityInfo] 城市信息API返回错误码: '%s'", code->valuestring);
}
} else {
ESP_LOGE(TAG, "[FetchCityInfo] 响应中没有code字段");
}
cJSON_Delete(root);
} else {
ESP_LOGE(TAG, "[FetchCityInfo] 解析城市信息JSON失败");
ESP_LOGD(TAG, "[FetchCityInfo] 失败的JSON响应: '%s'", response.c_str());
}
} else {
ESP_LOGE(TAG, "[FetchCityInfo] HTTP请求失败");
}
ESP_LOGI(TAG, "[FetchCityInfo] 函数结束,返回空字符串");
return "";
}
// 解析并打印IP位置信息- IP查询
static bool ParseAndPrintIpInfo(const std::string& response) {
ESP_LOGI(TAG, "开始解析IP位置信息JSON");
// 解析JSON响应
cJSON* root = cJSON_Parse(response.c_str());
if (!root) {
const char* error_ptr = cJSON_GetErrorPtr();
ESP_LOGE(TAG, "解析IP信息JSON失败: %s", error_ptr ? error_ptr : "未知错误");
return false;
}
// 提取并打印各个字段ip-api.com 格式)
cJSON* status = cJSON_GetObjectItem(root, "status");
cJSON* ip = cJSON_GetObjectItem(root, "query");
cJSON* country = cJSON_GetObjectItem(root, "country");
cJSON* region = cJSON_GetObjectItem(root, "region");
cJSON* regionName = cJSON_GetObjectItem(root, "regionName");
cJSON* city = cJSON_GetObjectItem(root, "city");
cJSON* zip = cJSON_GetObjectItem(root, "zip");
cJSON* lat = cJSON_GetObjectItem(root, "lat");
cJSON* lon = cJSON_GetObjectItem(root, "lon");
cJSON* timezone = cJSON_GetObjectItem(root, "timezone");
cJSON* isp = cJSON_GetObjectItem(root, "isp");
cJSON* org = cJSON_GetObjectItem(root, "org");
cJSON* asn = cJSON_GetObjectItem(root, "as");
// 打印解析结果
ESP_LOGI(TAG, "=============== IP位置信息 ===============");
if (status && cJSON_IsString(status)) {
ESP_LOGI(TAG, "状态: %s", status->valuestring);
}
if (ip && cJSON_IsString(ip)) {
ESP_LOGI(TAG, "IP: %s", ip->valuestring);
}
if (country && cJSON_IsString(country)) {
ESP_LOGI(TAG, "国家: %s", country->valuestring);
}
if (region && cJSON_IsString(region)) {
ESP_LOGI(TAG, "区域代码: %s", region->valuestring);
}
if (regionName && cJSON_IsString(regionName)) {
ESP_LOGI(TAG, "省份: %s", regionName->valuestring);
}
if (city && cJSON_IsString(city)) {
ESP_LOGI(TAG, "城市: %s", city->valuestring);
}
if (zip && cJSON_IsString(zip)) {
ESP_LOGI(TAG, "邮编: %s", zip->valuestring);
}
if (lat && cJSON_IsNumber(lat)) {
ESP_LOGI(TAG, "纬度: %.4f", lat->valuedouble);
}
if (lon && cJSON_IsNumber(lon)) {
ESP_LOGI(TAG, "经度: %.4f", lon->valuedouble);
}
if (timezone && cJSON_IsString(timezone)) {
ESP_LOGI(TAG, "时区: %s", timezone->valuestring);
}
if (isp && cJSON_IsString(isp)) {
ESP_LOGI(TAG, "运营商: %s", isp->valuestring);
}
if (org && cJSON_IsString(org)) {
ESP_LOGI(TAG, "组织: %s", org->valuestring);
}
if (asn && cJSON_IsString(asn)) {
ESP_LOGI(TAG, "AS号: %s", asn->valuestring);
}
ESP_LOGI(TAG, "======================================");
// 释放JSON对象
cJSON_Delete(root);
return true;
}
// 获取IP位置信息ip-api.com
// 自动获取设备当前IP的地理位置信息
std::string WeatherApi::GetIpInfo() {
ESP_LOGI(TAG, "[GetIpInfo] 开始获取IP位置信息");
// 构建IP查询API的URL
std::string url = "http://ip-api.com/json/?lang=zh-CN";
ESP_LOGI(TAG, "[GetIpInfo] 查询URL: %s", url.c_str());
std::string response;
std::string city_info = default_location_; // 默认返回默认位置
// 使用HTTP
esp_http_client_config_t config = {};
config.url = url.c_str();
config.method = HTTP_METHOD_GET;
config.transport_type = HTTP_TRANSPORT_OVER_TCP; // 使用HTTP而非HTTPS
esp_http_client_handle_t client = esp_http_client_init(&config);
if (!client) {
ESP_LOGE(TAG, "[GetIpInfo] HTTP客户端初始化失败");
return city_info;
}
// 设置请求头
esp_http_client_set_header(client, "User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36");
esp_http_client_set_header(client, "Accept-Encoding", "identity");
esp_err_t err = esp_http_client_open(client, 0);
if (err == ESP_OK) {
int64_t headers_len = esp_http_client_fetch_headers(client);
int status_code = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "[GetIpInfo] HTTP状态码: %d, 头长度: %lld", status_code, (long long)headers_len);
if (status_code == 200) {
char buf[512];
int read_len;
while ((read_len = esp_http_client_read(client, buf, sizeof(buf))) > 0) {
response.append(buf, read_len);
}
ESP_LOGI(TAG, "[GetIpInfo] 获取到响应,长度: %zu 字节", response.length());
if (ParseAndPrintIpInfo(response)) {
cJSON* root = cJSON_Parse(response.c_str());
if (root) {
cJSON* city = cJSON_GetObjectItem(root, "city");
cJSON* regionName = cJSON_GetObjectItem(root, "regionName");
if (city && cJSON_IsString(city) && city->valuestring[0] != '\0') {
city_info = city->valuestring;
} else if (regionName && cJSON_IsString(regionName)) {
city_info = regionName->valuestring;
}
cJSON_Delete(root);
}
}
} else {
ESP_LOGE(TAG, "[GetIpInfo] HTTP请求失败状态码: %d", status_code);
}
esp_http_client_close(client);// 关闭HTTP客户端连接释放资源
} else {
ESP_LOGE(TAG, "[GetIpInfo] HTTP请求执行失败: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);// 清理HTTP客户端资源释放内存
vTaskDelay(100 / portTICK_PERIOD_MS);// 延时100ms确保资源释放完成
ESP_LOGI(TAG, "[GetIpInfo] 返回城市信息: %s", city_info.c_str());
return city_info;// 返回提取的城市信息
}
// 获取天气信息主函数
std::string WeatherApi::GetWeather(const std::string& location, const std::string& lang) {
ESP_LOGI(TAG, "[GetWeather] ===== 开始获取天气信息 =====");
ESP_LOGI(TAG, "[GetWeather] 输入参数: location='%s' (长度: %zu), lang='%s'",
location.c_str(), location.length(), lang.c_str());
// 确定查询位置 - 当location为空或为"None"字符串时,使用默认城市
std::string query_location = (location.empty() || location == "None") ? default_location_ : location;
ESP_LOGI(TAG, "[GetWeather] 查询位置: '%s'", query_location.c_str());
// 检查缓存 - 简单的内存缓存
std::string cache_key = "weather_" + query_location;
ESP_LOGI(TAG, "[GetWeather] 缓存键: '%s'", cache_key.c_str());
auto it = weather_cache_.find(cache_key);
if (it != weather_cache_.end()) {
ESP_LOGI(TAG, "[GetWeather] 使用缓存的天气信息: '%s'", query_location.c_str());
ESP_LOGI(TAG, "[GetWeather] ===== 天气信息获取完成 (缓存命中) =====");
return it->second; // 返回缓存的结果
}
ESP_LOGI(TAG, "[GetWeather] 缓存未命中,开始获取天气信息: '%s'", query_location.c_str());
// 获取城市ID
ESP_LOGI(TAG, "[GetWeather] 开始获取城市ID");
std::string city_id = FetchCityInfo(query_location, lang);
ESP_LOGI(TAG, "[GetWeather] FetchCityInfo返回结果: city_id='%s' (长度: %zu)",
city_id.c_str(), city_id.length());
if (city_id.empty()) {
ESP_LOGI(TAG, "[GetWeather] 未找到相关城市信息,请确认城市名称是否正确");
ESP_LOGI(TAG, "[GetWeather] ===== 天气信息获取失败 (城市ID为空) =====");
return "未找到相关城市信息,请确认城市名称是否正确";
}
std::string q_lang = MapLangCode(lang);// 映射语言代码为API需要的格式
std::string weather_url = "https://" + api_host_ + "/v7/weather/now?key=" + api_key_ + "\u0026location=" + city_id + "\u0026lang=" + q_lang;
std::string weather_response;// 存储天气API响应的字符串
ESP_LOGI(TAG, "[GetWeather] 构建天气查询URL: '%s'", weather_url.c_str());
std::string result = "";// 存储最终格式化的天气信息
// 发送天气查询请求
ESP_LOGI(TAG, "[GetWeather] 开始发送天气查询HTTP请求");
if (HttpGet(weather_url, "", weather_response)) {
ESP_LOGI(TAG, "[GetWeather] HTTP请求成功响应长度: %zu 字节", weather_response.length());
// 解析天气JSON响应
ESP_LOGI(TAG, "[GetWeather] 开始解析天气JSON响应");
cJSON* root = cJSON_Parse(weather_response.c_str());
if (root) {
ESP_LOGI(TAG, "[GetWeather] JSON解析成功");
// 检查响应状态
cJSON* code = cJSON_GetObjectItem(root, "code");
if (code && cJSON_IsString(code) && strcmp(code->valuestring, "200") == 0) {
ESP_LOGI(TAG, "[GetWeather] 响应状态码: 200 (成功)");
// 获取天气数据
cJSON* now = cJSON_GetObjectItem(root, "now");
if (now) {
ESP_LOGI(TAG, "[GetWeather] 获取到now字段开始构建天气报告");
// 构建天气报告
result = "前天气情况 (" + query_location + ")\n\n";
// 获取天气描述
cJSON* text = cJSON_GetObjectItem(now, "text");
if (text && cJSON_IsString(text)) {
result += "天气状况: " + std::string(text->valuestring) + "\n";
ESP_LOGI(TAG, "[GetWeather] 天气状况: '%s'", text->valuestring);
} else {
ESP_LOGW(TAG, "[GetWeather] 未能获取天气状况");
}
// 获取温度
cJSON* temp = cJSON_GetObjectItem(now, "temp");
if (temp && cJSON_IsString(temp)) {
result += "当前温度: " + std::string(temp->valuestring) + "摄氏度\n";
ESP_LOGI(TAG, "[GetWeather] 当前温度: '%s'摄氏度", temp->valuestring);
} else {
ESP_LOGW(TAG, "[GetWeather] 未能获取当前温度");
}
// 获取体感温度
cJSON* feelsLike = cJSON_GetObjectItem(now, "feelsLike");
if (feelsLike && cJSON_IsString(feelsLike)) {
result += "体感温度: " + std::string(feelsLike->valuestring) + "摄氏度\n";
ESP_LOGI(TAG, "[GetWeather] 体感温度: '%s'摄氏度", feelsLike->valuestring);
} else {
ESP_LOGW(TAG, "[GetWeather] 未能获取体感温度");
}
// 获取风向风速
cJSON* windDir = cJSON_GetObjectItem(now, "windDir");
cJSON* windScale = cJSON_GetObjectItem(now, "windScale");
if (windDir && cJSON_IsString(windDir) && windScale && cJSON_IsString(windScale)) {
result += "风向风速: " + std::string(windDir->valuestring) + " " +
std::string(windScale->valuestring) + "\n";
ESP_LOGI(TAG, "[GetWeather] 风向风速: '%s' %s级", windDir->valuestring, windScale->valuestring);
} else {
ESP_LOGW(TAG, "[GetWeather] 未能获取风向风速信息");
}
// 获取湿度
cJSON* humidity = cJSON_GetObjectItem(now, "humidity");
if (humidity && cJSON_IsString(humidity)) {
result += "相对湿度: " + std::string(humidity->valuestring) + "%\n";
ESP_LOGI(TAG, "[GetWeather] 相对湿度: '%s'%%", humidity->valuestring);
} else {
ESP_LOGW(TAG, "[GetWeather] 未能获取相对湿度");
}
// 获取气压
cJSON* pressure = cJSON_GetObjectItem(now, "pressure");
if (pressure && cJSON_IsString(pressure)) {
result += "大气压强: " + std::string(pressure->valuestring) + "hPa\n";
ESP_LOGI(TAG, "[GetWeather] 大气压强: '%s'hPa", pressure->valuestring);
} else {
ESP_LOGW(TAG, "[GetWeather] 未能获取大气压强");
}
// 添加更新时间
result += std::string("\n数据更新时间: ") + __DATE__ + " " + __TIME__;
ESP_LOGI(TAG, "[GetWeather] 数据更新时间: %s %s", __DATE__, __TIME__);
} else {
ESP_LOGE(TAG, "[GetWeather] 无法获取当前天气数据(now字段为空)");
result = "无法获取当前天气数据";
}
} else {
const char* error_code = code && cJSON_IsString(code) ? code->valuestring : "未知";
ESP_LOGE(TAG, "[GetWeather] 天气API请求失败错误码: '%s'", error_code);
result = "天气API请求失败错误码: " + std::string(error_code);
}
cJSON_Delete(root);
} else {
ESP_LOGE(TAG, "[GetWeather] 解析天气数据失败,请稍后重试");
result = "解析天气数据失败,请稍后重试";
}
} else {
ESP_LOGE(TAG, "[GetWeather] 网络请求失败,无法获取天气信息");
result = "网络请求失败,无法获取天气信息";
}
// 缓存结果 - 简单实现,实际项目中应考虑缓存过期时间
if (!result.empty()) {
ESP_LOGI(TAG, "[GetWeather] 开始缓存天气结果,当前缓存大小: %zu", weather_cache_.size());
weather_cache_[cache_key] = result;
// 限制缓存大小
if (weather_cache_.size() > 10) {
ESP_LOGI(TAG, "[GetWeather] 缓存大小超过10移除最旧的缓存项");
weather_cache_.erase(weather_cache_.begin());
}
ESP_LOGI(TAG, "[GetWeather] 缓存完成,当前缓存大小: %zu", weather_cache_.size());
}
ESP_LOGI(TAG, "[GetWeather] 天气信息获取完成");
ESP_LOGI(TAG, "[GetWeather] ===== 天气信息获取流程结束 =====");
return result;
}
// 创建全局WeatherApi实例
WeatherApi g_weather_api;
// 实现全局GetWeatherInfo函数用于获取天气信息// 全局函数,供外部调用
std::string GetWeatherInfo(const std::string& location, const std::string& lang) {
ESP_LOGI(TAG, "[GetWeatherInfo] ===== 全局函数调用开始 =====");
ESP_LOGI(TAG, "[GetWeatherInfo] 收到调用请求location='%s' (长度: %zu), lang='%s'",location.c_str(), location.length(), lang.c_str());
// 调用g_weather_api单例的GetWeather方法
ESP_LOGI(TAG, "[GetWeatherInfo] 准备调用g_weather_api.GetWeather");
std::string result = g_weather_api.GetWeather(location, lang);// 调用单例实例的GetWeather方法获取天气信息
ESP_LOGI(TAG, "[GetWeatherInfo] g_weather_api.GetWeather调用完成结果长度: %zu 字节", result.length());
ESP_LOGD(TAG, "[GetWeatherInfo] 返回结果前100字节: '%s'", result.substr(0, std::min(size_t(100), result.length())).c_str());
ESP_LOGI(TAG, "[GetWeatherInfo] ===== 全局函数调用结束 =====");
return result;
}
std::string WeatherApi::GetDefaultLocation() const {
return default_location_;// 返回默认位置
}
// 新增方法自动检测NVS中是否存在当前位置的城市信息并设置当前位置
void WeatherApi::AutoDetectLocation() {
ESP_LOGI(TAG, "[AutoDetectLocation] ===== 开始自动检测位置 =====");
wifi_config_t wc{};// 定义WiFi配置结构体
esp_wifi_get_config(WIFI_IF_STA, &wc);// 获取当前WiFi配置
std::string ssid = std::string(reinterpret_cast<char*>(wc.sta.ssid));// 转换SSID为字符串
wifi_ap_record_t ap{};// 定义AP记录结构体
std::string bssid;// 定义BSSID字符串
if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK) {
char buf[18];// 定义BSSID缓冲区
snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
ap.bssid[0], ap.bssid[1], ap.bssid[2],
ap.bssid[3], ap.bssid[4], ap.bssid[5]);// 格式化BSSID为字符串
bssid.assign(buf);// 赋值给BSSID字符串
}
nvs_handle_t h;// 定义NVS句柄
// 打开NVS命名空间"wifi_city_map",读写模式
if (nvs_open("wifi_city_map", NVS_READWRITE, &h) == ESP_OK) {
auto try_get = [&](const std::string& key)->std::string{
size_t len = 0;
// 尝试获取NVS中存储的城市字符串
if (nvs_get_str(h, key.c_str(), NULL, &len) == ESP_OK && len > 0) {
std::vector<char> buf(len);// 定义缓冲区
// 尝试从NVS获取城市字符串
if (nvs_get_str(h, key.c_str(), buf.data(), &len) == ESP_OK) {
return std::string(buf.data());// 返回城市字符串
}
}
return std::string();// 未找到对应城市,返回空字符串
};
std::string city;// 定义城市字符串
if (!ssid.empty()) {
if (!bssid.empty()) {
city = try_get(ssid + "|" + bssid);// 先尝试SSID+BSSID
}
if (city.empty()) {
city = try_get(ssid);// 如果未找到再尝试SSID
}
}
// 如果仍未从NVS中找到城市信息将调用位置API获取城市信息
if (city.empty()) {
ESP_LOGI(TAG, "[AutoDetectLocation] 未从NVS命中城市信息将调用位置API获取城市信息");
std::string detected = GetIpInfo();// 获取IP位置信息 调用位置查询API获取当前位置的城市信息
if (!detected.empty()) {
default_location_ = detected;// 更新默认城市为检测到的位置
if (!ssid.empty()) {
size_t cache_count = 0;// 初始化缓存条目数量
std::vector<std::string> cache_entries; // 定义缓存条目向量,存储所有键
std::vector<std::string> deletable_entries; // 存储可删除的条目排除当前使用的SSID和SSID+BSSID
// 遍历NVS中的所有键
nvs_iterator_t it = NULL;
esp_err_t res = nvs_entry_find(NULL, "wifi_city_map", NVS_TYPE_STR, &it);// 查找所有字符串类型的条目
while (res == ESP_OK) {
nvs_entry_info_t info; // 定义NVS条目信息结构体
nvs_entry_info(it, &info);// 获取当前迭代器指向的条目信息
std::string key = info.key; // 获取当前条目的键名
cache_entries.push_back(key);// 将键添加到缓存向量中
cache_count++;// 增加缓存条目数量
// 检查是否为当前使用的键,如果不是则添加到可删除列表
std::string current_full_key = ssid;
if (!bssid.empty()) {current_full_key = ssid + "|" + bssid;}// 组合SSID和BSSID作为完整键
// 排除当前正在使用的SSID和SSID+BSSID键
if (key != ssid && key != current_full_key) {deletable_entries.push_back(key);}// 将键添加到可删除列表
res = nvs_entry_next(&it);// 移动到下一个条目
}
nvs_release_iterator(it);// 释放NVS迭代器
// 如果缓存数量超过限制且有可删除的条目,随机删除一个
if (cache_count >= MAX_WIFI_CITY_CACHE && !deletable_entries.empty()) {
ESP_LOGI(TAG, "[AutoDetectLocation] WiFi位置缓存数量(%zu)达到限制(%d),开始随机删除策略",cache_count, MAX_WIFI_CITY_CACHE);
// 随机选择一个条目删除(使用更简单的方式选择索引),ESP32平台可以使用esp_random()获得更好的随机性,但这里保持简单实现
int random_index = (int)(esp_timer_get_time() % deletable_entries.size());// 生成随机索引
const std::string& key_to_delete = deletable_entries[random_index];// 获取随机选择的键名
ESP_LOGI(TAG, "[AutoDetectLocation] 随机删除缓存条目: %s", key_to_delete.c_str());
nvs_erase_key(h, key_to_delete.c_str());// 删除城市字符串键
}
// 保存新的位置信息
if (!bssid.empty()) {
const std::string full_key = ssid + "|" + bssid;// 组合SSID和BSSID作为完整键
nvs_set_str(h, full_key.c_str(), detected.c_str());// 缓存SSID+BSSID到城市
}
nvs_set_str(h, ssid.c_str(), detected.c_str());// 缓存SSID到城市
if (nvs_commit(h) == ESP_OK) {
ESP_LOGI(TAG, "[AutoDetectLocation] 城市信息保存到NVS成功");
} else {
ESP_LOGW(TAG, "[AutoDetectLocation] 城市信息保存到NVS失败");
}
}
ESP_LOGI(TAG, "[AutoDetectLocation] 自动检测到位置: '%s',已更新默认城市", detected.c_str());
} else {
ESP_LOGI(TAG, "[AutoDetectLocation] 位置检测失败或未变化,保持默认城市: '%s'", default_location_.c_str());
}
}
// 如果从NVS命中城市找到了城市信息
else {
default_location_ = city;// 更新默认城市为NVS中命中的城市
ESP_LOGI(TAG, "[AutoDetectLocation] 从NVS命中位置: '%s',已更新默认城市", city.c_str());
}
nvs_close(h);// 关闭NVS句柄
}
// 如果打开NVS检索城市信息失败
else {
std::string detected = GetIpInfo();// 调用位置API获取城市信息
if (!detected.empty()) {
default_location_ = detected;// 更新默认城市为检测到的位置
ESP_LOGI(TAG, "[AutoDetectLocation] 自动检测到位置: '%s',已更新默认城市", detected.c_str());
} else {
ESP_LOGI(TAG, "[AutoDetectLocation] 位置检测失败或未变化,保持默认城市: '%s'", default_location_.c_str());
}
}
ESP_LOGI(TAG, "[AutoDetectLocation] ===== 位置检测完成 =====");
}
// 全局函数:供应用程序网络连接后调用,用于自动检测位置
void AutoDetectAndSetLocation() {
ESP_LOGI(TAG, "[AutoDetectAndSetLocation] 调用全局函数自动检测位置");
g_weather_api.AutoDetectLocation();// 自动检测NVS中是否存在当前位置的城市信息并设置当前位置
}