#include "weather_api.h" #include #include #include #include #include // 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 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(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 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(c) / 16]; encoded += "0123456789ABCDEF"[static_cast(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(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 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 cache_entries; // 定义缓存条目向量,存储所有键 std::vector 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中是否存在当前位置的城市信息,并设置当前位置 }