Upgrade Playlist Features

This commit is contained in:
2025-12-09 17:20:01 +08:00
parent 577990de69
commit 8bd2780688
683 changed files with 91812 additions and 81260 deletions

View File

@@ -0,0 +1,365 @@
/**
* @file weather_service.cc
* @brief Weather API service implementation
* @version 1.0
*/
#include "weather_service.h"
#include <esp_log.h>
#include <esp_http_client.h>
#include <cJSON.h>
#include <time.h>
static const char *TAG = "WeatherService";
WeatherService::WeatherService() {
city_code_ = "101010100"; // Default: Beijing
auto_detect_enabled_ = false;
}
WeatherService::~WeatherService() {
}
void WeatherService::Initialize(const std::string& city_code) {
if (city_code.empty()) {
// Auto-detect city code by IP
ESP_LOGI(TAG, "Auto-detecting city code by IP address...");
if (AutoDetectCityCode()) {
ESP_LOGI(TAG, "Auto-detected city code: %s", city_code_.c_str());
auto_detect_enabled_ = true;
} else {
ESP_LOGW(TAG, "Failed to auto-detect city code, using default: Beijing (101010100)");
city_code_ = "101010100";
auto_detect_enabled_ = false;
}
} else {
city_code_ = city_code;
auto_detect_enabled_ = false;
ESP_LOGI(TAG, "Weather service initialized with city code: %s", city_code_.c_str());
}
}
bool WeatherService::AutoDetectCityCode() {
// Use weather.com.cn IP geolocation API
time_t now;
time(&now);
char url[256];
snprintf(url, sizeof(url), "http://wgeo.weather.com.cn/ip/?_=%ld", (long)now);
ESP_LOGI(TAG, "Fetching city code from: %s", url);
// Allocate response buffer
char *buffer = (char*)malloc(2048);
if (!buffer) {
ESP_LOGE(TAG, "Failed to allocate buffer");
return false;
}
memset(buffer, 0, 2048);
// Configure HTTP client with larger buffer
esp_http_client_config_t config = {};
config.url = url;
config.timeout_ms = 15000;
config.buffer_size = 2048; // Important: increase buffer size
config.buffer_size_tx = 1024;
config.disable_auto_redirect = false;
config.max_redirection_count = 3;
esp_http_client_handle_t client = esp_http_client_init(&config);
if (!client) {
ESP_LOGE(TAG, "Failed to init HTTP client");
free(buffer);
return false;
}
// Set headers
esp_http_client_set_header(client, "User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
esp_http_client_set_header(client, "Referer", "http://www.weather.com.cn/");
esp_http_client_set_header(client, "Accept", "*/*");
bool success = false;
// Open connection and read
esp_err_t err = esp_http_client_open(client, 0);
if (err == ESP_OK) {
int content_length = esp_http_client_fetch_headers(client);
int status_code = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "HTTP Status = %d, Content-Length = %d", status_code, content_length);
if (status_code == 200 || status_code == 302) {
// Read response data
int total_read = 0;
int read_len;
while (total_read < 2047) {
read_len = esp_http_client_read(client, buffer + total_read, 2047 - total_read);
if (read_len <= 0) {
break;
}
total_read += read_len;
}
buffer[total_read] = '\0';
ESP_LOGI(TAG, "Read %d bytes from API", total_read);
if (total_read > 0) {
// Print first 200 chars for debugging
char preview[201];
int preview_len = (total_read > 200) ? 200 : total_read;
memcpy(preview, buffer, preview_len);
preview[preview_len] = '\0';
ESP_LOGI(TAG, "Response preview: %s", preview);
std::string response(buffer);
// Try multiple parsing patterns
size_t id_pos = response.find("id=\"");
if (id_pos != std::string::npos) {
// Pattern: id="101010100" (JavaScript variable with double quotes)
size_t start = id_pos + 4; // Skip id="
size_t end = response.find("\"", start);
if (end != std::string::npos && end > start) {
city_code_ = response.substr(start, end - start);
ESP_LOGI(TAG, "✅ Detected city code: %s", city_code_.c_str());
success = true;
}
} else if ((id_pos = response.find("id='")) != std::string::npos) {
// Pattern: id='101010100' (JavaScript variable with single quotes)
size_t start = id_pos + 4;
size_t end = response.find("'", start);
if (end != std::string::npos && end > start) {
city_code_ = response.substr(start, end - start);
ESP_LOGI(TAG, "✅ Detected city code: %s", city_code_.c_str());
success = true;
}
} else if ((id_pos = response.find("id\":\"")) != std::string::npos) {
// Pattern: "id":"101010100" (JSON format)
size_t start = id_pos + 5;
size_t end = response.find("\"", start);
if (end != std::string::npos && end > start) {
city_code_ = response.substr(start, end - start);
ESP_LOGI(TAG, "✅ Detected city code: %s", city_code_.c_str());
success = true;
}
} else {
ESP_LOGW(TAG, "❌ City code pattern not found in response");
}
} else {
ESP_LOGW(TAG, "❌ No data read from API");
}
} else {
ESP_LOGW(TAG, "❌ HTTP status: %d", status_code);
}
esp_http_client_close(client);
} else {
ESP_LOGE(TAG, "❌ Failed to connect: %s", esp_err_to_name(err));
}
esp_http_client_cleanup(client);
free(buffer);
return success;
}
void WeatherService::SetWeatherCallback(std::function<void(const WeatherData&)> callback) {
weather_callback_ = callback;
}
void WeatherService::FetchWeather() {
ESP_LOGI(TAG, "Fetching weather data for city: %s", city_code_.c_str());
// Construct URL
time_t now;
time(&now);
char url[256];
snprintf(url, sizeof(url), "http://d1.weather.com.cn/weather_index/%s.html?_=%ld",
city_code_.c_str(), (long)now);
// Configure HTTP client (no event handler, read directly)
esp_http_client_config_t config = {};
config.url = url;
config.timeout_ms = 10000;
esp_http_client_handle_t client = esp_http_client_init(&config);
// Set User-Agent header
esp_http_client_set_header(client, "User-Agent",
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38");
esp_http_client_set_header(client, "Referer", "http://www.weather.com.cn/");
// Open connection
esp_err_t err = esp_http_client_open(client, 0);
if (err == ESP_OK) {
// Read response
int content_length = esp_http_client_fetch_headers(client);
int status_code = esp_http_client_get_status_code(client);
ESP_LOGI(TAG, "HTTP GET Status = %d, Content-Length = %d", status_code, content_length);
if (status_code == 200) {
// Allocate buffer (use 8KB if content_length unknown or too large)
int buffer_size = (content_length > 0 && content_length < 8192) ? content_length + 1 : 8192;
char *buffer = (char*)malloc(buffer_size);
if (buffer) {
int total_read = 0;
int read_len;
// Read all data
while ((read_len = esp_http_client_read(client, buffer + total_read, buffer_size - total_read - 1)) > 0) {
total_read += read_len;
if (total_read >= buffer_size - 1) {
break;
}
}
buffer[total_read] = '\0';
ESP_LOGI(TAG, "Read %d bytes of weather data", total_read);
std::string response(buffer);
ParseWeatherData(response);
if (weather_callback_) {
weather_callback_(last_weather_data_);
}
free(buffer);
} else {
ESP_LOGE(TAG, "Failed to allocate buffer for response");
}
} else {
ESP_LOGW(TAG, "HTTP request returned status code: %d", status_code);
}
} else {
ESP_LOGE(TAG, "Failed to open HTTP connection: %s", esp_err_to_name(err));
}
esp_http_client_close(client);
esp_http_client_cleanup(client);
}
std::string WeatherService::ExtractJsonValue(const std::string& json, const std::string& key) {
size_t key_pos = json.find("\"" + key + "\":");
if (key_pos == std::string::npos) {
return "";
}
size_t value_start = json.find("\"", key_pos + key.length() + 3);
if (value_start == std::string::npos) {
return "";
}
value_start++;
size_t value_end = json.find("\"", value_start);
if (value_end == std::string::npos) {
return "";
}
return json.substr(value_start, value_end - value_start);
}
void WeatherService::ParseWeatherData(const std::string& response) {
ESP_LOGI(TAG, "Parsing weather data...");
try {
// Extract dataSK JSON section
size_t sk_start = response.find("dataSK =");
size_t sk_end = response.find(";var dataZS");
if (sk_start != std::string::npos && sk_end != std::string::npos) {
std::string dataSK = response.substr(sk_start + 8, sk_end - sk_start - 8);
// Parse JSON using cJSON
cJSON *root = cJSON_Parse(dataSK.c_str());
if (root) {
cJSON *city = cJSON_GetObjectItem(root, "cityname");
cJSON *temp = cJSON_GetObjectItem(root, "temp");
cJSON *humidity = cJSON_GetObjectItem(root, "SD");
cJSON *weather = cJSON_GetObjectItem(root, "weather");
cJSON *wind_dir = cJSON_GetObjectItem(root, "WD");
cJSON *wind_speed = cJSON_GetObjectItem(root, "WS");
cJSON *aqi = cJSON_GetObjectItem(root, "aqi");
if (city && cJSON_IsString(city)) {
last_weather_data_.city_name = city->valuestring;
}
if (temp && cJSON_IsString(temp)) {
last_weather_data_.temperature = temp->valuestring;
}
if (humidity && cJSON_IsString(humidity)) {
last_weather_data_.humidity = humidity->valuestring;
}
if (weather && cJSON_IsString(weather)) {
last_weather_data_.weather_desc = weather->valuestring;
}
if (wind_dir && cJSON_IsString(wind_dir)) {
last_weather_data_.wind_direction = wind_dir->valuestring;
}
if (wind_speed && cJSON_IsString(wind_speed)) {
last_weather_data_.wind_speed = wind_speed->valuestring;
}
if (aqi && cJSON_IsNumber(aqi)) {
last_weather_data_.aqi = aqi->valueint;
// Determine AQI description
if (last_weather_data_.aqi > 200) {
last_weather_data_.aqi_desc = "重度";
} else if (last_weather_data_.aqi > 150) {
last_weather_data_.aqi_desc = "中度";
} else if (last_weather_data_.aqi > 100) {
last_weather_data_.aqi_desc = "轻度";
} else if (last_weather_data_.aqi > 50) {
last_weather_data_.aqi_desc = "";
} else {
last_weather_data_.aqi_desc = "";
}
}
cJSON_Delete(root);
}
}
// Extract forecast data (f section)
size_t fc_start = response.find("\"f\":[");
size_t fc_end = response.find(",{\"fa", fc_start);
if (fc_start != std::string::npos && fc_end != std::string::npos) {
std::string dataFC = response.substr(fc_start + 5, fc_end - fc_start - 5);
cJSON *root = cJSON_Parse(dataFC.c_str());
if (root) {
cJSON *temp_low = cJSON_GetObjectItem(root, "fd");
cJSON *temp_high = cJSON_GetObjectItem(root, "fc");
if (temp_low && cJSON_IsString(temp_low)) {
last_weather_data_.temp_low = temp_low->valuestring;
}
if (temp_high && cJSON_IsString(temp_high)) {
last_weather_data_.temp_high = temp_high->valuestring;
}
cJSON_Delete(root);
}
}
last_weather_data_.last_update_time = esp_timer_get_time() / 1000000;
ESP_LOGI(TAG, "Weather parsed: City=%s, Temp=%s℃, Humidity=%s, AQI=%d(%s)",
last_weather_data_.city_name.c_str(),
last_weather_data_.temperature.c_str(),
last_weather_data_.humidity.c_str(),
last_weather_data_.aqi,
last_weather_data_.aqi_desc.c_str());
} catch (...) {
ESP_LOGE(TAG, "Failed to parse weather data");
}
}