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,185 @@
# 天气时钟界面说明
## 📋 概述
已成功将Arduino版本的MiniTV天气时钟UI移植到ESP-IDF LVGL环境中。新的天气时钟界面完全替换了原来的简单时钟界面提供更丰富的信息显示。
## 🎨 界面布局
参考Arduino版本新界面采用240x240分区布局
```
┌─────────────────────────────────┐
│ 天气信息滚动 │ 城市名称 │ 顶部 (0-34px)
├─────────────────────────────────┤
│ │
│ 时:分 秒 │ 中部 (35-165px)
│ (大字体) (小字体) │
│ │
├─────┬─────────────┬─────────────┤
│ AQI │ 湿度图标 │ 温度图标 │ 底部上 (166-200px)
│空气 │ 💧XX% │ 🌡XX℃ │
├─────┼─────────────┼─────────────┤
│周X │ XX月XX日 │ (空白) │ 底部下 (200-240px)
└─────┴─────────────┴─────────────┘
```
## ✨ 新增功能
### 1. 天气数据显示
- ✅ 城市名称
- ✅ 实时温度
- ✅ 湿度百分比
- ✅ 空气质量指数AQI和等级
- ✅ 最高/最低温度
- ✅ 风向和风速
### 2. 动态信息滚动
顶部滚动区域每2.5秒切换显示:
- 实时天气状况
- 空气质量描述
- 风向风速信息
- 今日天气概况
- 最低/最高温度
### 3. AQI颜色编码
空气质量指数自动显示对应颜色:
- 🟢 优 (0-50): 绿色
- 🟡 良 (51-100): 黄色
- 🟠 轻度污染 (101-150): 橙色
- 🟣 中度污染 (151-200): 紫红色
- 🔴 重度污染 (200+): 深红色
### 4. 自动更新
- ⏰ 时间:每秒更新
- 🌤️ 天气每10分钟更新一次
## 📁 新增文件
1. **idle_screen.h** - 天气时钟UI头文件重写
2. **idle_screen.cc** - 天气时钟UI实现重写
3. **weather_service.h** - 天气API服务头文件
4. **weather_service.cc** - 天气API服务实现
## 🔧 修改文件
1. **genjutech-s3-1.54tft.cc**
- 添加 `SpiLcdDisplayEx` 类扩展
- 集成天气服务
- 添加 `InitWeatherService()` 方法
- 自动启动天气更新任务
## 🌐 天气API
使用中国天气网API
- 城市代码自动检测:`http://wgeo.weather.com.cn/ip/`
- 天气数据获取:`http://d1.weather.com.cn/weather_index/{城市代码}.html`
### 🎯 城市代码配置(两种方式)
#### 方式1自动检测推荐
**默认已启用**WiFi连接后会根据IP地址自动获取所在城市
`genjutech-s3-1.54tft.cc``InitWeatherService()` 中:
```cpp
self->weather_service_.Initialize(""); // 空字符串 = 自动检测
```
**优点**
- ✅ 无需手动配置
- ✅ 根据实际位置显示天气
- ✅ 设备移动到其他城市会自动适应
- ✅ 失败时自动回退到北京
#### 方式2手动指定城市代码
如果你想固定显示某个城市的天气,可以指定城市代码:
```cpp
self->weather_service_.Initialize("101280601"); // 指定:深圳
```
**常用城市代码:**
- 北京:`101010100`
- 上海:`101020100`
- 广州:`101280101`
- 深圳:`101280601`
- 成都:`101270101`
- 杭州:`101210101`
- 武汉:`101200101`
- 西安:`101110101`
- 青岛:`101120201`
- 南京:`101190101`
- 重庆:`101040100`
- 天津:`101030100`
**优点**
- ✅ 可查看其他城市天气
- ✅ 不受网络IP地址影响
## 🎯 使用说明
### 1. 编译
```bash
idf.py build
```
### 2. 烧录
```bash
idf.py flash monitor
```
### 3. 配置城市
修改 `genjutech-s3-1.54tft.cc` 中的城市代码后重新编译烧录。
## 🔄 与原有功能的集成
- ✅ 保留语音交互功能
- ✅ 保留闹钟功能
- ✅ 闹钟触发时在时钟界面上显示
- ✅ 设备空闲时自动显示天气时钟
- ✅ 语音交互时自动切换到聊天界面
## 🐛 故障排查
### 天气数据不显示
1. 检查WiFi连接状态
2. 检查城市代码是否正确
3. 查看串口日志中的天气API响应
### 界面显示异常
1. 确认LVGL初始化正常
2. 检查显示锁是否正确使用
3. 查看内存使用情况
## 📝 注意事项
1. 天气更新需要WiFi连接
2. 首次获取天气数据需等待5秒WiFi连接时间
3. 天气API可能受网络状况影响
4. 建议在WiFi稳定环境下使用
## 🎨 设计理念
- **简洁实用**:一屏显示所有关键信息
- **视觉清晰**:分区明确,信息层次分明
- **动态更新**:滚动显示更多天气详情
- **色彩编码**AQI用颜色直观表示空气质量
## 🚀 未来扩展
可能的扩展方向:
- [ ] 支持多城市切换
- [ ] 添加未来天气预报
- [ ] 自定义UI主题和颜色
- [ ] 添加天气图标/动画
- [ ] 支持更多天气数据源
---
**移植时间**: 2025-01-10
**原始参考**: Arduino MiniTV Weather Clock by DIY攻城狮
**实现版本**: ESP-IDF + LVGL

View File

@@ -1,43 +1,46 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14
#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
#define AUDIO_CODEC_PA_PIN GPIO_NUM_21
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_9
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define BUILTIN_LED_GPIO GPIO_NUM_1
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_42
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41
#define DISPLAY_SDA GPIO_NUM_3
#define DISPLAY_SCL GPIO_NUM_4
#define DISPLAY_DC GPIO_NUM_5
#define DISPLAY_CS GPIO_NUM_6
#define DISPLAY_RES GPIO_NUM_7
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_SWAP_XY false
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define BACKLIGHT_INVERT false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14
#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
#define AUDIO_CODEC_PA_PIN GPIO_NUM_21
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_9
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define BUILTIN_LED_GPIO GPIO_NUM_1
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_42
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41
#define DISPLAY_SDA GPIO_NUM_3
#define DISPLAY_SCL GPIO_NUM_4
#define DISPLAY_DC GPIO_NUM_5
#define DISPLAY_CS GPIO_NUM_6
#define DISPLAY_RES GPIO_NUM_7
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_SWAP_XY false
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define BACKLIGHT_INVERT false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
/**< use new idle screen of xiaozhi */
#define IDLE_SCREEN_HOOK 1
#endif // _BOARD_CONFIG_H_

View File

@@ -1,9 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "genjutech-s3-1.54tft",
"sdkconfig_append": []
}
]
{
"target": "esp32s3",
"builds": [
{
"name": "genjutech-s3-1.54tft",
"sdkconfig_append": []
}
]
}

View File

@@ -1,259 +1,437 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "led/single_led.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include <esp_efuse_table.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "power_save_timer.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#define TAG "GenJuTech_s3_1_54TFT"
class SparkBotEs8311AudioCodec : public Es8311AudioCodec {
private:
public:
SparkBotEs8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true)
: Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate,
mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {}
void EnableOutput(bool enable) override {
if (enable == output_enabled_) {
return;
}
if (enable) {
Es8311AudioCodec::EnableOutput(enable);
} else {
// Nothing todo because the display io and PA io conflict
}
}
};
class GenJuTech_s3_1_54TFT : public WifiBoard {
private:
// i2c_master_bus_handle_t display_i2c_bus_;
Button boot_button_;
Button volume_up_button_;
Button volume_down_button_;
LcdDisplay* display_;
i2c_master_bus_handle_t codec_i2c_bus_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
void InitializePowerManager() {
power_manager_ = new PowerManager(GPIO_NUM_16);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
// 第一个参数不为 -1 时,进入睡眠会关闭音频输入
power_save_timer_ = new PowerSaveTimer(240, 60);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
});
power_save_timer_->SetEnabled(true);
}
void InitializeCodecI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
}
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SDA;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_SCL;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
// boot_button_.OnPressDown([this]() {
// Application::GetInstance().StartListening();
// });
// boot_button_.OnPressUp([this]() {
// Application::GetInstance().StopListening();
// });
volume_up_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
power_save_timer_->WakeUp();
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
power_save_timer_->WakeUp();
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
}
void InitializeSt7789Display() {
gpio_config_t config = {
.pin_bit_mask = (1ULL << DISPLAY_RES),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&config));
gpio_set_level(DISPLAY_RES, 0);
vTaskDelay(20);
gpio_set_level(DISPLAY_RES, 1);
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS;
io_config.dc_gpio_num = DISPLAY_DC;
io_config.spi_mode = 3;
io_config.pclk_hz = 80 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片ST7789
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_RES;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
panel_config.bits_per_pixel = 16;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
}
public:
GenJuTech_s3_1_54TFT() :
boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
ESP_LOGI(TAG, "Initializing GenJuTech S3 1.54 Board");
InitializePowerManager();
InitializePowerSaveTimer();
InitializeCodecI2c();
InitializeSpi();
InitializeButtons();
InitializeSt7789Display();
GetBacklight()->RestoreBrightness();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static SparkBotEs8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR);
return &audio_codec;
}
virtual Display *GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
static bool last_discharging = false;
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = power_manager_->GetBatteryLevel();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(GenJuTech_s3_1_54TFT);
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "led/single_led.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#include <esp_efuse_table.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "power_save_timer.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "alarm_manager.h" // 用于检测和停止闹钟
#if IDLE_SCREEN_HOOK
#include "idle_screen.h"
#include "weather_service.h"
#endif
#define TAG "GenJuTech_s3_1_54TFT"
#if IDLE_SCREEN_HOOK
LV_FONT_DECLARE(font_puhui_20_4);
// Extended SpiLcdDisplay with idle screen support
class SpiLcdDisplayEx : public SpiLcdDisplay {
public:
SpiLcdDisplayEx(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
int width, int height, int offset_x, int offset_y,
bool mirror_x, bool mirror_y, bool swap_xy) :
SpiLcdDisplay(panel_io, panel,
width, height, offset_x, offset_y,
mirror_x, mirror_y, swap_xy) {
DisplayLockGuard lock(this);
lv_obj_set_style_pad_left(status_bar_, 20, 0);
lv_obj_set_style_pad_right(status_bar_, 20, 0);
}
virtual void OnStateChanged() override {
DisplayLockGuard lock(this);
auto& app = Application::GetInstance();
auto device_state = app.GetDeviceState();
switch (device_state) {
case kDeviceStateIdle:
ESP_LOGI(TAG, "hide xiaozhi, show idle screen");
if (!lv_obj_has_flag(container_, LV_OBJ_FLAG_HIDDEN)) {
lv_obj_add_flag(container_, LV_OBJ_FLAG_HIDDEN);
}
_lcdScnIdle.ui_showScreen(true);
break;
case kDeviceStateListening:
case kDeviceStateConnecting:
case kDeviceStateSpeaking:
ESP_LOGI(TAG, "show xiaozhi, hide idle screen");
_lcdScnIdle.ui_showScreen(false);
if (lv_obj_has_flag(container_, LV_OBJ_FLAG_HIDDEN)) {
lv_obj_clear_flag(container_, LV_OBJ_FLAG_HIDDEN);
}
break;
default:
break;
}
}
virtual void OnClockTimer() override {
DisplayLockGuard lock(this);
_lcdScnIdle.ui_update(); // update screen every 1s
}
void IdleScrSetupUi() {
DisplayLockGuard lock(this); // ← 必须加锁LVGL不是线程安全的
ESP_LOGI(TAG, "IdleScrSetupUi()");
// Get ThemeColors from current theme
ThemeColors theme_colors;
theme_colors.background = lv_color_hex(0x000000);
theme_colors.text = lv_color_hex(0xFFFFFF);
theme_colors.border = lv_color_hex(0x444444);
theme_colors.chat_background = lv_color_hex(0x111111);
theme_colors.user_bubble = lv_color_hex(0x0078D4);
theme_colors.assistant_bubble = lv_color_hex(0x2D2D2D);
theme_colors.system_bubble = lv_color_hex(0x1A1A1A);
theme_colors.system_text = lv_color_hex(0xFFFFFF);
theme_colors.low_battery = lv_color_hex(0xFF0000);
_lcdScnIdle.ui_init(&theme_colors);
}
void UpdateTheme() {
DisplayLockGuard lock(this); // ← 必须加锁!
ThemeColors theme_colors;
theme_colors.background = lv_color_hex(0x000000);
theme_colors.text = lv_color_hex(0xFFFFFF);
theme_colors.border = lv_color_hex(0x444444);
theme_colors.chat_background = lv_color_hex(0x111111);
theme_colors.user_bubble = lv_color_hex(0x0078D4);
theme_colors.assistant_bubble = lv_color_hex(0x2D2D2D);
theme_colors.system_bubble = lv_color_hex(0x1A1A1A);
theme_colors.system_text = lv_color_hex(0xFFFFFF);
theme_colors.low_battery = lv_color_hex(0xFF0000);
_lcdScnIdle.ui_updateTheme(&theme_colors);
}
// Override alarm display methods
virtual void ShowAlarmOnIdleScreen(const char* alarm_message) override {
DisplayLockGuard lock(this);
ESP_LOGI(TAG, "ShowAlarmOnIdleScreen: %s", alarm_message);
_lcdScnIdle.ui_showAlarmInfo(alarm_message);
// Make sure idle screen is visible
if (!_lcdScnIdle.ui_shown) {
_lcdScnIdle.ui_showScreen(true);
}
// Hide xiaozhi interface
if (!lv_obj_has_flag(container_, LV_OBJ_FLAG_HIDDEN)) {
lv_obj_add_flag(container_, LV_OBJ_FLAG_HIDDEN);
}
}
virtual void HideAlarmOnIdleScreen() override {
DisplayLockGuard lock(this);
ESP_LOGI(TAG, "HideAlarmOnIdleScreen");
_lcdScnIdle.ui_hideAlarmInfo();
}
void InitWeatherService() {
ESP_LOGI(TAG, "Initializing weather service");
// Start weather update task
xTaskCreate([](void* param) {
auto* self = static_cast<SpiLcdDisplayEx*>(param);
ESP_LOGI(TAG, "Weather update task started");
// Wait 5 seconds for WiFi to connect
vTaskDelay(pdMS_TO_TICKS(5000));
// Auto-detect city code by IP (leave empty string for auto-detect)
// Or specify a city code like "101010100" for Beijing
self->weather_service_.Initialize(""); // Empty = auto-detect
// Set callback to update UI when weather data is received
self->weather_service_.SetWeatherCallback([self](const WeatherData& weather) {
DisplayLockGuard lock(self);
self->_lcdScnIdle.ui_updateWeather(weather);
});
// Fetch weather immediately after initialization
self->weather_service_.FetchWeather();
// Continue updating weather every 10 minutes
while (true) {
vTaskDelay(pdMS_TO_TICKS(600000)); // 10 minutes
self->weather_service_.FetchWeather();
}
}, "weather_task", 8192, this, 5, NULL); // Increased stack size for HTTP operations
}
private:
IdleScreen _lcdScnIdle;
WeatherService weather_service_;
};
#endif // IDLE_SCREEN_HOOK
class SparkBotEs8311AudioCodec : public Es8311AudioCodec {
private:
public:
SparkBotEs8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true)
: Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate,
mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {}
void EnableOutput(bool enable) override {
if (enable == output_enabled_) {
return;
}
if (enable) {
Es8311AudioCodec::EnableOutput(enable);
} else {
// Nothing todo because the display io and PA io conflict
}
}
};
class GenJuTech_s3_1_54TFT : public WifiBoard {
private:
// i2c_master_bus_handle_t display_i2c_bus_;
Button boot_button_;
Button volume_up_button_;
Button volume_down_button_;
#if IDLE_SCREEN_HOOK
SpiLcdDisplayEx* display_;
#else
LcdDisplay* display_;
#endif
i2c_master_bus_handle_t codec_i2c_bus_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
void InitializePowerManager() {
power_manager_ = new PowerManager(GPIO_NUM_16);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
// 第一个参数不为 -1 时,进入睡眠会关闭音频输入
power_save_timer_ = new PowerSaveTimer(240, 60);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
});
power_save_timer_->SetEnabled(true);
}
void InitializeCodecI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
}
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SDA;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_SCL;
buscfg.quadwp_io_num = GPIO_NUM_NC;
buscfg.quadhd_io_num = GPIO_NUM_NC;
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
// 如果有闹钟正在播放,优先停止闹钟,而不是切换界面
auto& alarm_manager = AlarmManager::GetInstance();
auto active_alarms = alarm_manager.GetActiveAlarms();
if (!active_alarms.empty()) {
ESP_LOGI(TAG, "Boot button pressed during alarm, stopping alarm");
for (const auto& alarm : active_alarms) {
alarm_manager.StopAlarm(alarm.id);
}
return; // 不切换界面
}
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
// boot_button_.OnPressDown([this]() {
// Application::GetInstance().StartListening();
// });
// boot_button_.OnPressUp([this]() {
// Application::GetInstance().StopListening();
// });
volume_up_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto codec = GetAudioCodec();
auto volume = codec->output_volume() + 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_up_button_.OnLongPress([this]() {
power_save_timer_->WakeUp();
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
});
volume_down_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto codec = GetAudioCodec();
auto volume = codec->output_volume() - 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
});
volume_down_button_.OnLongPress([this]() {
power_save_timer_->WakeUp();
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
});
}
void InitializeSt7789Display() {
gpio_config_t config = {
.pin_bit_mask = (1ULL << DISPLAY_RES),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&config));
gpio_set_level(DISPLAY_RES, 0);
vTaskDelay(20);
gpio_set_level(DISPLAY_RES, 1);
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS;
io_config.dc_gpio_num = DISPLAY_DC;
io_config.spi_mode = 3;
io_config.pclk_hz = 80 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片ST7789
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_RES;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
panel_config.bits_per_pixel = 16;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
#if IDLE_SCREEN_HOOK
display_ = new SpiLcdDisplayEx(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
#else
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
#endif
}
public:
GenJuTech_s3_1_54TFT() :
boot_button_(BOOT_BUTTON_GPIO),
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
ESP_LOGI(TAG, "Initializing GenJuTech S3 1.54 Board");
InitializePowerManager();
InitializePowerSaveTimer();
InitializeCodecI2c();
InitializeSpi();
InitializeButtons();
InitializeSt7789Display();
GetBacklight()->RestoreBrightness();
#if IDLE_SCREEN_HOOK
auto* display_ex = static_cast<SpiLcdDisplayEx*>(display_);
display_ex->IdleScrSetupUi();
display_ex->InitWeatherService();
#endif
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static SparkBotEs8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR);
return &audio_codec;
}
virtual Display *GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
static bool last_discharging = false;
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = power_manager_->GetBatteryLevel();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(GenJuTech_s3_1_54TFT);

View File

@@ -0,0 +1,456 @@
/**
* @file idle_screen.cc
* @brief Weather Clock Idle Screen Implementation
* @version 2.0
*/
#include "config.h"
#include "application.h"
#include "idle_screen.h"
#include "ui_helpers.h"
#include "display/lcd_display.h"
#include <esp_log.h>
#include <time.h>
#include <sys/time.h>
#if IDLE_SCREEN_HOOK
LV_FONT_DECLARE(font_puhui_20_4);
LV_FONT_DECLARE(ui_font_font48Seg);
LV_IMG_DECLARE(ui_img_xiaozhi_48_png);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-enum-enum-conversion"
static const char *TAG = "WeatherClock";
// Helper function to format time
static void get_time_string(char* hour_min_buf, char* second_buf, char* week_buf, char* date_buf) {
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
// Format hour:minute (HH:MM)
snprintf(hour_min_buf, 16, "%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min);
// Format second (SS)
snprintf(second_buf, 8, "%02d", timeinfo.tm_sec);
// Format week (周X)
const char* week_names[] = {"", "", "", "", "", "", ""};
snprintf(week_buf, 16, "周%s", week_names[timeinfo.tm_wday]);
// Format date (MM月DD日)
snprintf(date_buf, 32, "%02d月%02d日", timeinfo.tm_mon + 1, timeinfo.tm_mday);
}
IdleScreen::IdleScreen() {
ui_screen = NULL;
ui_main_container = NULL;
ui_scroll_container = NULL;
ui_scroll_label = NULL;
ui_city_label = NULL;
ui_time_container = NULL;
ui_time_hour_min = NULL;
ui_time_second = NULL;
ui_xiaozhi_icon = NULL;
ui_info_container = NULL;
ui_aqi_container = NULL;
ui_aqi_label = NULL;
ui_temp_label = NULL;
ui_temp_icon_label = NULL;
ui_humid_label = NULL;
ui_humid_icon_label = NULL;
ui_date_container = NULL;
ui_week_label = NULL;
ui_date_label = NULL;
ui_alarm_info_label = NULL;
ui_shown = false;
current_scroll_index = 0;
last_scroll_time = 0;
p_theme = NULL;
}
IdleScreen::~IdleScreen() {
ui_destroy();
}
void IdleScreen::ui_init(ThemeColors *p_current_theme) {
auto screen = lv_screen_active();
p_theme = p_current_theme;
ESP_LOGI(TAG, "Initializing weather clock UI");
// Main screen container
ui_screen = lv_obj_create(screen);
lv_obj_remove_flag(ui_screen, LV_OBJ_FLAG_SCROLLABLE);
lv_obj_set_size(ui_screen, LV_HOR_RES, LV_VER_RES);
lv_obj_set_style_bg_color(ui_screen, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_border_width(ui_screen, 0, 0);
lv_obj_set_style_pad_all(ui_screen, 0, 0);
// Main container
ui_main_container = lv_obj_create(ui_screen);
lv_obj_remove_style_all(ui_main_container);
lv_obj_set_size(ui_main_container, 240, 240);
lv_obj_set_align(ui_main_container, LV_ALIGN_CENTER);
lv_obj_remove_flag(ui_main_container, (lv_obj_flag_t)(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE));
lv_obj_set_style_bg_color(ui_main_container, lv_color_hex(0xFFFFFF), 0);
lv_obj_set_style_bg_opa(ui_main_container, LV_OPA_COVER, 0);
createTopSection();
createMiddleSection();
createBottomSection();
// Alarm info label (initially hidden)
ui_alarm_info_label = lv_label_create(ui_main_container);
lv_obj_set_width(ui_alarm_info_label, 220);
lv_obj_set_height(ui_alarm_info_label, LV_SIZE_CONTENT);
lv_obj_set_pos(ui_alarm_info_label, 10, 80);
lv_label_set_long_mode(ui_alarm_info_label, LV_LABEL_LONG_WRAP);
lv_label_set_text(ui_alarm_info_label, "");
lv_obj_set_style_text_align(ui_alarm_info_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_style_text_font(ui_alarm_info_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_alarm_info_label, lv_color_hex(0xFF0000), 0);
lv_obj_add_flag(ui_alarm_info_label, LV_OBJ_FLAG_HIDDEN);
// Draw divider lines
static lv_point_precise_t line_points1[] = {{0, 34}, {240, 34}};
static lv_point_precise_t line_points2[] = {{150, 0}, {150, 34}};
static lv_point_precise_t line_points3[] = {{0, 166}, {240, 166}};
static lv_point_precise_t line_points4[] = {{60, 166}, {60, 200}};
static lv_point_precise_t line_points5[] = {{160, 166}, {160, 200}};
lv_obj_t* line1 = lv_line_create(ui_main_container);
lv_line_set_points(line1, line_points1, 2);
lv_obj_set_style_line_color(line1, lv_color_hex(0x000000), 0);
lv_obj_set_style_line_width(line1, 1, 0);
lv_obj_t* line2 = lv_line_create(ui_main_container);
lv_line_set_points(line2, line_points2, 2);
lv_obj_set_style_line_color(line2, lv_color_hex(0x000000), 0);
lv_obj_set_style_line_width(line2, 1, 0);
lv_obj_t* line3 = lv_line_create(ui_main_container);
lv_line_set_points(line3, line_points3, 2);
lv_obj_set_style_line_color(line3, lv_color_hex(0x000000), 0);
lv_obj_set_style_line_width(line3, 1, 0);
lv_obj_t* line4 = lv_line_create(ui_main_container);
lv_line_set_points(line4, line_points4, 2);
lv_obj_set_style_line_color(line4, lv_color_hex(0x000000), 0);
lv_obj_set_style_line_width(line4, 1, 0);
lv_obj_t* line5 = lv_line_create(ui_main_container);
lv_line_set_points(line5, line_points5, 2);
lv_obj_set_style_line_color(line5, lv_color_hex(0x000000), 0);
lv_obj_set_style_line_width(line5, 1, 0);
// Hide by default
lv_obj_add_flag(ui_screen, LV_OBJ_FLAG_HIDDEN);
ESP_LOGI(TAG, "Weather clock UI initialized");
}
void IdleScreen::createTopSection() {
// Top section container (0-34px height)
ui_scroll_container = lv_obj_create(ui_main_container);
lv_obj_remove_style_all(ui_scroll_container);
lv_obj_set_size(ui_scroll_container, 148, 32);
lv_obj_set_pos(ui_scroll_container, 2, 2);
lv_obj_remove_flag(ui_scroll_container, LV_OBJ_FLAG_SCROLLABLE);
// Scrolling weather info label
ui_scroll_label = lv_label_create(ui_scroll_container);
lv_obj_set_width(ui_scroll_label, 144);
lv_label_set_long_mode(ui_scroll_label, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_label_set_text(ui_scroll_label, "正在获取天气信息...");
lv_obj_set_style_text_font(ui_scroll_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_scroll_label, lv_color_hex(0x000000), 0);
lv_obj_align(ui_scroll_label, LV_ALIGN_LEFT_MID, 0, 0);
// City name label
ui_city_label = lv_label_create(ui_main_container);
lv_obj_set_width(ui_city_label, 88);
lv_label_set_text(ui_city_label, "北京");
lv_obj_set_style_text_font(ui_city_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_city_label, lv_color_hex(0x000000), 0);
lv_obj_set_style_text_align(ui_city_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_pos(ui_city_label, 152, 8);
}
void IdleScreen::createMiddleSection() {
// Middle section container (35-165px, height 130px)
ui_time_container = lv_obj_create(ui_main_container);
lv_obj_remove_style_all(ui_time_container);
lv_obj_set_size(ui_time_container, 240, 130);
lv_obj_set_pos(ui_time_container, 0, 35);
lv_obj_remove_flag(ui_time_container, LV_OBJ_FLAG_SCROLLABLE);
// Large time display (HH:MM) - using ui_font_font48Seg (already in project)
ui_time_hour_min = lv_label_create(ui_time_container);
lv_label_set_text(ui_time_hour_min, "12:34");
lv_obj_set_style_text_font(ui_time_hour_min, &ui_font_font48Seg, 0);
lv_obj_set_style_text_color(ui_time_hour_min, lv_color_hex(0x000000), 0);
lv_obj_align(ui_time_hour_min, LV_ALIGN_CENTER, -20, -25);
// Small second display (SS)
ui_time_second = lv_label_create(ui_time_container);
lv_label_set_text(ui_time_second, "56");
lv_obj_set_style_text_font(ui_time_second, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_time_second, lv_color_hex(0x000000), 0);
lv_obj_align(ui_time_second, LV_ALIGN_CENTER, 65, -25);
// Rotating Xiaozhi icon (位置在时间下方)
ui_xiaozhi_icon = lv_img_create(ui_time_container);
lv_img_set_src(ui_xiaozhi_icon, &ui_img_xiaozhi_48_png);
lv_obj_align(ui_xiaozhi_icon, LV_ALIGN_CENTER, 0, 35);
lv_img_set_pivot(ui_xiaozhi_icon, 24, 24); // 设置旋转中心点 (48/2 = 24)
// Add rotation animation (使用局部变量LVGL会自动管理)
lv_anim_t anim;
lv_anim_init(&anim);
lv_anim_set_var(&anim, ui_xiaozhi_icon);
lv_anim_set_exec_cb(&anim, (lv_anim_exec_xcb_t)lv_img_set_angle);
lv_anim_set_values(&anim, 0, 3600); // 0-360度 (LVGL使用0.1度单位)
lv_anim_set_time(&anim, 3000); // 3秒转一圈
lv_anim_set_repeat_count(&anim, LV_ANIM_REPEAT_INFINITE);
lv_anim_start(&anim);
ESP_LOGI(TAG, "Xiaozhi rotation animation started");
}
void IdleScreen::createBottomSection() {
// Info container (166-200px, height 34px)
ui_info_container = lv_obj_create(ui_main_container);
lv_obj_remove_style_all(ui_info_container);
lv_obj_set_size(ui_info_container, 240, 34);
lv_obj_set_pos(ui_info_container, 0, 166);
lv_obj_remove_flag(ui_info_container, LV_OBJ_FLAG_SCROLLABLE);
// AQI container (0-60px)
ui_aqi_container = lv_obj_create(ui_info_container);
lv_obj_set_size(ui_aqi_container, 50, 24);
lv_obj_set_pos(ui_aqi_container, 5, 5);
lv_obj_set_style_radius(ui_aqi_container, 4, 0);
lv_obj_set_style_bg_color(ui_aqi_container, lv_color_hex(0x9CCA7F), 0); // Default: 优
lv_obj_set_style_bg_opa(ui_aqi_container, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(ui_aqi_container, 0, 0);
lv_obj_set_style_pad_all(ui_aqi_container, 0, 0);
ui_aqi_label = lv_label_create(ui_aqi_container);
lv_label_set_text(ui_aqi_label, "");
lv_obj_set_style_text_font(ui_aqi_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_aqi_label, lv_color_hex(0xFFFFFF), 0);
lv_obj_center(ui_aqi_label);
// Humidity icon and label (middle section)
ui_humid_icon_label = lv_label_create(ui_info_container);
lv_label_set_text(ui_humid_icon_label, "湿"); // 湿度
lv_obj_set_style_text_font(ui_humid_icon_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_humid_icon_label, lv_color_hex(0x0000FF), 0);
lv_obj_set_pos(ui_humid_icon_label, 85, 8);
ui_humid_label = lv_label_create(ui_info_container);
lv_label_set_text(ui_humid_label, "65%");
lv_obj_set_style_text_font(ui_humid_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_humid_label, lv_color_hex(0x000000), 0);
lv_obj_set_pos(ui_humid_label, 110, 8);
// Temperature icon and label (160-240px)
ui_temp_icon_label = lv_label_create(ui_info_container);
lv_label_set_text(ui_temp_icon_label, ""); // 温度
lv_obj_set_style_text_font(ui_temp_icon_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_temp_icon_label, lv_color_hex(0xFF0000), 0);
lv_obj_set_pos(ui_temp_icon_label, 162, 8);
ui_temp_label = lv_label_create(ui_info_container);
lv_label_set_text(ui_temp_label, "25℃");
lv_obj_set_style_text_font(ui_temp_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_temp_label, lv_color_hex(0x000000), 0);
lv_obj_set_pos(ui_temp_label, 182, 8);
// Date container (200-240px, height 40px)
ui_date_container = lv_obj_create(ui_main_container);
lv_obj_remove_style_all(ui_date_container);
lv_obj_set_size(ui_date_container, 240, 34);
lv_obj_set_pos(ui_date_container, 0, 200);
lv_obj_remove_flag(ui_date_container, LV_OBJ_FLAG_SCROLLABLE);
// Week label (0-60px)
ui_week_label = lv_label_create(ui_date_container);
lv_label_set_text(ui_week_label, "周一");
lv_obj_set_style_text_font(ui_week_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_week_label, lv_color_hex(0x000000), 0);
lv_obj_set_style_text_align(ui_week_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_pos(ui_week_label, 5, 8);
// Date label (61-160px)
ui_date_label = lv_label_create(ui_date_container);
lv_label_set_text(ui_date_label, "01月01日");
lv_obj_set_style_text_font(ui_date_label, &font_puhui_20_4, 0);
lv_obj_set_style_text_color(ui_date_label, lv_color_hex(0x000000), 0);
lv_obj_set_style_text_align(ui_date_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_set_pos(ui_date_label, 70, 8);
}
void IdleScreen::ui_destroy() {
if (ui_screen) {
// Stop animation before deleting objects
if (ui_xiaozhi_icon) {
lv_anim_delete(ui_xiaozhi_icon, NULL);
}
lv_obj_delete(ui_screen);
ui_screen = NULL;
}
}
void IdleScreen::ui_showScreen(bool showIt) {
if (!ui_screen) return;
if (showIt) {
lv_obj_remove_flag(ui_screen, LV_OBJ_FLAG_HIDDEN);
ui_shown = true;
} else {
lv_obj_add_flag(ui_screen, LV_OBJ_FLAG_HIDDEN);
ui_shown = false;
}
}
void IdleScreen::ui_update() {
if (!ui_screen || !ui_shown) return;
// Update time display
char hour_min_buf[16], second_buf[8], week_buf[16], date_buf[32];
get_time_string(hour_min_buf, second_buf, week_buf, date_buf);
if (ui_time_hour_min) {
lv_label_set_text(ui_time_hour_min, hour_min_buf);
}
if (ui_time_second) {
lv_label_set_text(ui_time_second, second_buf);
}
if (ui_week_label) {
lv_label_set_text(ui_week_label, week_buf);
}
if (ui_date_label) {
lv_label_set_text(ui_date_label, date_buf);
}
// Update scroll text every 2.5 seconds
updateScrollText();
}
void IdleScreen::updateScrollText() {
if (scroll_texts.empty()) return;
uint32_t current_time = lv_tick_get();
if (current_time - last_scroll_time > 2500) { // 2.5 seconds
current_scroll_index = (current_scroll_index + 1) % scroll_texts.size();
if (ui_scroll_label) {
lv_label_set_text(ui_scroll_label, scroll_texts[current_scroll_index].c_str());
}
last_scroll_time = current_time;
}
}
void IdleScreen::ui_updateTheme(ThemeColors *p_current_theme) {
p_theme = p_current_theme;
// Weather clock uses fixed white background and black text
// Theme colors are not applied to maintain Arduino design
}
void IdleScreen::ui_updateWeather(const WeatherData& weather) {
ESP_LOGI(TAG, "Updating weather data");
// Update city name
if (ui_city_label) {
lv_label_set_text(ui_city_label, weather.city_name.c_str());
}
// Update temperature
if (ui_temp_label) {
std::string temp_text = weather.temperature + "";
lv_label_set_text(ui_temp_label, temp_text.c_str());
}
// Update humidity
if (ui_humid_label) {
lv_label_set_text(ui_humid_label, weather.humidity.c_str());
}
// Update AQI
if (ui_aqi_label) {
lv_label_set_text(ui_aqi_label, weather.aqi_desc.c_str());
updateAQIColor(weather.aqi);
}
// Update scroll texts
std::vector<std::string> texts;
texts.push_back("实时天气 " + weather.weather_desc);
texts.push_back("空气质量 " + weather.aqi_desc);
texts.push_back("风向 " + weather.wind_direction + weather.wind_speed);
texts.push_back("今日天气 " + weather.weather_desc);
texts.push_back("最低温度 " + weather.temp_low + "");
texts.push_back("最高温度 " + weather.temp_high + "");
ui_setScrollText(texts);
}
void IdleScreen::ui_setScrollText(const std::vector<std::string>& texts) {
scroll_texts = texts;
current_scroll_index = 0;
last_scroll_time = lv_tick_get();
if (!texts.empty() && ui_scroll_label) {
lv_label_set_text(ui_scroll_label, texts[0].c_str());
}
}
void IdleScreen::updateAQIColor(int aqi) {
if (!ui_aqi_container) return;
lv_color_t color;
if (aqi > 200) {
// 重度污染 - 深红色
color = lv_color_hex(0x880B20);
} else if (aqi > 150) {
// 中度污染 - 紫红色
color = lv_color_hex(0xBA3779);
} else if (aqi > 100) {
// 轻度污染 - 橙色
color = lv_color_hex(0xF29F39);
} else if (aqi > 50) {
// 良 - 黄色
color = lv_color_hex(0xF7DB64);
} else {
// 优 - 绿色
color = lv_color_hex(0x9CCA7F);
}
lv_obj_set_style_bg_color(ui_aqi_container, color, 0);
}
void IdleScreen::ui_showAlarmInfo(const char* alarm_message) {
if (!ui_alarm_info_label) return;
ESP_LOGI(TAG, "Showing alarm info: %s", alarm_message);
lv_label_set_text(ui_alarm_info_label, alarm_message);
lv_obj_remove_flag(ui_alarm_info_label, LV_OBJ_FLAG_HIDDEN);
}
void IdleScreen::ui_hideAlarmInfo() {
if (!ui_alarm_info_label) return;
ESP_LOGI(TAG, "Hiding alarm info");
lv_obj_add_flag(ui_alarm_info_label, LV_OBJ_FLAG_HIDDEN);
}
#pragma GCC diagnostic pop
#endif // IDLE_SCREEN_HOOK

View File

@@ -0,0 +1,118 @@
/**
* @file idle_screen.h
* @brief Weather Clock Idle Screen for GenJuTech S3 1.54TFT
* @brief Inspired by Arduino MiniTV weather clock project
* @version 2.0
* @date 2025-01-10
*/
#pragma once
#include "config.h"
#if IDLE_SCREEN_HOOK
#include <lvgl.h>
#include <string>
#include <vector>
// Theme color structure (copied from lcd_display.h to avoid circular dependency)
struct ThemeColors {
lv_color_t background;
lv_color_t text;
lv_color_t chat_background;
lv_color_t user_bubble;
lv_color_t assistant_bubble;
lv_color_t system_bubble;
lv_color_t system_text;
lv_color_t border;
lv_color_t low_battery;
};
// Weather data structure
struct WeatherData {
std::string city_name; // 城市名称
std::string temperature; // 当前温度
std::string humidity; // 湿度
std::string weather_desc; // 天气描述
std::string wind_direction; // 风向
std::string wind_speed; // 风速
int aqi; // 空气质量指数
std::string aqi_desc; // 空气质量描述
std::string temp_low; // 最低温度
std::string temp_high; // 最高温度
uint32_t last_update_time; // 最后更新时间
};
class IdleScreen
{
public:
IdleScreen();
~IdleScreen();
public:
void ui_init(ThemeColors *p_current_theme);
void ui_destroy();
void ui_showScreen(bool showIt);
void ui_update(); // Called every second to update time
void ui_updateTheme(ThemeColors *p_current_theme);
// Weather related methods
void ui_updateWeather(const WeatherData& weather);
void ui_setScrollText(const std::vector<std::string>& texts);
// Alarm display methods
void ui_showAlarmInfo(const char* alarm_message);
void ui_hideAlarmInfo();
public:
bool ui_shown; // UI shown or not
private:
void createTopSection(); // 顶部:滚动信息 + 城市
void createMiddleSection(); // 中间:时间显示区域
void createBottomSection(); // 底部:温湿度 + 日期
void updateScrollText(); // 更新滚动文字
void updateAQIColor(int aqi); // 更新空气质量颜色
private:
// Main containers
lv_obj_t* ui_screen;
lv_obj_t* ui_main_container;
// Top section - Weather scroll and city
lv_obj_t* ui_scroll_container;
lv_obj_t* ui_scroll_label;
lv_obj_t* ui_city_label;
// Middle section - Time display
lv_obj_t* ui_time_container;
lv_obj_t* ui_time_hour_min; // 时:分 (大字体)
lv_obj_t* ui_time_second; // 秒 (小字体)
lv_obj_t* ui_xiaozhi_icon; // 旋转小智图标
// Bottom upper section - AQI, Temperature, Humidity
lv_obj_t* ui_info_container;
lv_obj_t* ui_aqi_container;
lv_obj_t* ui_aqi_label;
lv_obj_t* ui_temp_label;
lv_obj_t* ui_temp_icon_label;
lv_obj_t* ui_humid_label;
lv_obj_t* ui_humid_icon_label;
// Bottom lower section - Week and Date
lv_obj_t* ui_date_container;
lv_obj_t* ui_week_label;
lv_obj_t* ui_date_label;
// Alarm info (overlays on screen when alarm triggers)
lv_obj_t* ui_alarm_info_label;
// Scroll text management
std::vector<std::string> scroll_texts;
int current_scroll_index;
uint32_t last_scroll_time;
// Theme
ThemeColors* p_theme;
};
#endif // IDLE_SCREEN_HOOK

View File

@@ -1,186 +1,186 @@
#pragma once
#include <vector>
#include <functional>
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
esp_timer_handle_t timer_handle_;
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> adc_values_;
uint32_t battery_level_ = 0;
bool is_charging_ = false;
bool is_low_battery_ = false;
int ticks_ = 0;
const int kBatteryAdcInterval = 60;
const int kBatteryAdcDataCount = 3;
const int kLowBatteryLevel = 20;
adc_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
bool new_charging_status = gpio_get_level(charging_pin_) == 1;
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (on_charging_status_changed_) {
on_charging_status_changed_(is_charging_);
}
ReadBatteryAdcData();
return;
}
// 如果电池电量数据不足,则读取电池电量数据
if (adc_values_.size() < kBatteryAdcDataCount) {
ReadBatteryAdcData();
return;
}
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
ticks_++;
if (ticks_ % kBatteryAdcInterval == 0) {
ReadBatteryAdcData();
}
}
void ReadBatteryAdcData() {
int adc_value;
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_4, &adc_value));
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{1280, 0},
{1334, 20},
{1388, 40},
{1442, 60},
{1496, 80},
{1550, 100}
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
// 初始化充电引脚
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << charging_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(arg);
self->CheckBatteryStatus();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "battery_check_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
// 初始化 ADC
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_2,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_4, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_handle_);
}
}
bool IsCharging() {
// 如果电量已经满了,则不再显示充电中
if (battery_level_ == 100) {
return false;
}
return is_charging_;
}
bool IsDischarging() {
// 没有区分充电和放电,所以直接返回相反状态
return !is_charging_;
}
uint8_t GetBatteryLevel() {
return battery_level_;
}
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};
#pragma once
#include <vector>
#include <functional>
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
esp_timer_handle_t timer_handle_;
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> adc_values_;
uint32_t battery_level_ = 0;
bool is_charging_ = false;
bool is_low_battery_ = false;
int ticks_ = 0;
const int kBatteryAdcInterval = 60;
const int kBatteryAdcDataCount = 3;
const int kLowBatteryLevel = 20;
adc_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
bool new_charging_status = gpio_get_level(charging_pin_) == 1;
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (on_charging_status_changed_) {
on_charging_status_changed_(is_charging_);
}
ReadBatteryAdcData();
return;
}
// 如果电池电量数据不足,则读取电池电量数据
if (adc_values_.size() < kBatteryAdcDataCount) {
ReadBatteryAdcData();
return;
}
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
ticks_++;
if (ticks_ % kBatteryAdcInterval == 0) {
ReadBatteryAdcData();
}
}
void ReadBatteryAdcData() {
int adc_value;
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_4, &adc_value));
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{1280, 0},
{1334, 20},
{1388, 40},
{1442, 60},
{1496, 80},
{1550, 100}
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
// 初始化充电引脚
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << charging_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(arg);
self->CheckBatteryStatus();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "battery_check_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
// 初始化 ADC
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_2,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_4, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_handle_);
}
}
bool IsCharging() {
// 如果电量已经满了,则不再显示充电中
if (battery_level_ == 100) {
return false;
}
return is_charging_;
}
bool IsDischarging() {
// 没有区分充电和放电,所以直接返回相反状态
return !is_charging_;
}
uint8_t GetBatteryLevel() {
return battery_level_;
}
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,349 @@
// This file was generated by SquareLine Studio
// SquareLine Studio version: SquareLine Studio 1.5.2
// LVGL version: 9.2.2
// Project name: weather0
#include "config.h"
#if IDLE_SCREEN_HOOK
#include "ui_helpers.h"
void _ui_bar_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_BAR_PROPERTY_VALUE_WITH_ANIM) lv_bar_set_value(target, val, LV_ANIM_ON);
if(id == _UI_BAR_PROPERTY_VALUE) lv_bar_set_value(target, val, LV_ANIM_OFF);
}
void _ui_basic_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_BASIC_PROPERTY_POSITION_X) lv_obj_set_x(target, val);
if(id == _UI_BASIC_PROPERTY_POSITION_Y) lv_obj_set_y(target, val);
if(id == _UI_BASIC_PROPERTY_WIDTH) lv_obj_set_width(target, val);
if(id == _UI_BASIC_PROPERTY_HEIGHT) lv_obj_set_height(target, val);
}
void _ui_dropdown_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_DROPDOWN_PROPERTY_SELECTED) lv_dropdown_set_selected(target, val);
}
void _ui_image_set_property(lv_obj_t * target, int id, uint8_t * val)
{
if(id == _UI_IMAGE_PROPERTY_IMAGE) lv_image_set_src(target, val);
}
void _ui_label_set_property(lv_obj_t * target, int id, const char * val)
{
if(id == _UI_LABEL_PROPERTY_TEXT) lv_label_set_text(target, val);
}
void _ui_roller_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_ROLLER_PROPERTY_SELECTED_WITH_ANIM) lv_roller_set_selected(target, val, LV_ANIM_ON);
if(id == _UI_ROLLER_PROPERTY_SELECTED) lv_roller_set_selected(target, val, LV_ANIM_OFF);
}
void _ui_slider_set_property(lv_obj_t * target, int id, int val)
{
if(id == _UI_SLIDER_PROPERTY_VALUE_WITH_ANIM) lv_slider_set_value(target, val, LV_ANIM_ON);
if(id == _UI_SLIDER_PROPERTY_VALUE) lv_slider_set_value(target, val, LV_ANIM_OFF);
}
void _ui_screen_change(lv_obj_t ** target, lv_screen_load_anim_t fademode, int spd, int delay,
void (*target_init)(void))
{
if(*target == NULL)
target_init();
lv_screen_load_anim(*target, fademode, spd, delay, false);
}
void _ui_screen_delete(lv_obj_t ** target)
{
if(*target == NULL) {
lv_obj_delete(*target);
target = NULL;
}
}
void _ui_arc_increment(lv_obj_t * target, int val)
{
int old = lv_arc_get_value(target);
lv_arc_set_value(target, old + val);
lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0);
}
void _ui_bar_increment(lv_obj_t * target, int val, int anm)
{
int old = lv_bar_get_value(target);
lv_bar_set_value(target, old + val, anm);
}
void _ui_slider_increment(lv_obj_t * target, int val, int anm)
{
int old = lv_slider_get_value(target);
lv_slider_set_value(target, old + val, anm);
lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0);
}
void _ui_keyboard_set_target(lv_obj_t * keyboard, lv_obj_t * textarea)
{
//lv_keyboard_set_textarea(keyboard, textarea);
}
void _ui_flag_modify(lv_obj_t * target, int32_t flag, int value)
{
if(value == _UI_MODIFY_FLAG_TOGGLE) {
if(lv_obj_has_flag(target, flag)) lv_obj_remove_flag(target, flag);
else lv_obj_add_flag(target, flag);
}
else if(value == _UI_MODIFY_FLAG_ADD) lv_obj_add_flag(target, flag);
else lv_obj_remove_flag(target, flag);
}
void _ui_state_modify(lv_obj_t * target, int32_t state, int value)
{
if(value == _UI_MODIFY_STATE_TOGGLE) {
if(lv_obj_has_state(target, state)) lv_obj_remove_state(target, state);
else lv_obj_add_state(target, state);
}
else if(value == _UI_MODIFY_STATE_ADD) lv_obj_add_state(target, state);
else lv_obj_remove_state(target, state);
}
void _ui_textarea_move_cursor(lv_obj_t * target, int val)
{
if(val == UI_MOVE_CURSOR_UP) lv_textarea_cursor_up(target);
if(val == UI_MOVE_CURSOR_RIGHT) lv_textarea_cursor_right(target);
if(val == UI_MOVE_CURSOR_DOWN) lv_textarea_cursor_down(target);
if(val == UI_MOVE_CURSOR_LEFT) lv_textarea_cursor_left(target);
lv_obj_add_state(target, LV_STATE_FOCUSED);
}
void scr_unloaded_delete_cb(lv_event_t * e)
{
lv_obj_t ** var = lv_event_get_user_data(e);
lv_obj_delete(*var);
(*var) = NULL;
}
void _ui_opacity_set(lv_obj_t * target, int val)
{
lv_obj_set_style_opa(target, val, 0);
}
void _ui_anim_callback_free_user_data(lv_anim_t * a)
{
lv_free(a->user_data);
a->user_data = NULL;
}
void _ui_anim_callback_set_x(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_x(usr->target, v);
}
void _ui_anim_callback_set_y(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_y(usr->target, v);
}
void _ui_anim_callback_set_width(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_width(usr->target, v);
}
void _ui_anim_callback_set_height(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_height(usr->target, v);
}
void _ui_anim_callback_set_opacity(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_obj_set_style_opa(usr->target, v, 0);
}
void _ui_anim_callback_set_image_zoom(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_image_set_scale(usr->target, v);
}
void _ui_anim_callback_set_image_angle(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
lv_image_set_rotation(usr->target, v);
}
void _ui_anim_callback_set_image_frame(lv_anim_t * a, int32_t v)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
usr->val = v;
if(v < 0) v = 0;
if(v >= usr->imgset_size) v = usr->imgset_size - 1;
lv_image_set_src(usr->target, usr->imgset[v]);
}
int32_t _ui_anim_callback_get_x(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_x_aligned(usr->target);
}
int32_t _ui_anim_callback_get_y(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_y_aligned(usr->target);
}
int32_t _ui_anim_callback_get_width(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_width(usr->target);
}
int32_t _ui_anim_callback_get_height(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_height(usr->target);
}
int32_t _ui_anim_callback_get_opacity(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_obj_get_style_opa(usr->target, 0);
}
int32_t _ui_anim_callback_get_image_zoom(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_image_get_scale(usr->target);
}
int32_t _ui_anim_callback_get_image_angle(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return lv_image_get_rotation(usr->target);
}
int32_t _ui_anim_callback_get_image_frame(lv_anim_t * a)
{
ui_anim_user_data_t * usr = (ui_anim_user_data_t *)a->user_data;
return usr->val;
}
void _ui_arc_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix)
{
char buf[_UI_TEMPORARY_STRING_BUFFER_SIZE];
lv_snprintf(buf, sizeof(buf), "%s%d%s", prefix, (int)lv_arc_get_value(src), postfix);
lv_label_set_text(trg, buf);
}
void _ui_slider_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix)
{
char buf[_UI_TEMPORARY_STRING_BUFFER_SIZE];
lv_snprintf(buf, sizeof(buf), "%s%d%s", prefix, (int)lv_slider_get_value(src), postfix);
lv_label_set_text(trg, buf);
}
void _ui_checked_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * txt_on, const char * txt_off)
{
if(lv_obj_has_state(src, LV_STATE_CHECKED)) lv_label_set_text(trg, txt_on);
else lv_label_set_text(trg, txt_off);
}
void _ui_spinbox_step(lv_obj_t * target, int val)
{
// if(val > 0) lv_spinbox_increment(target);
// else lv_spinbox_decrement(target);
// lv_obj_send_event(target, LV_EVENT_VALUE_CHANGED, 0);
}
void _ui_switch_theme(int val)
{
#ifdef UI_THEME_ACTIVE
ui_theme_set(val);
#endif
}
#endif // IDLE_SCREEN_HOOK

View File

@@ -0,0 +1,154 @@
// This file was generated by SquareLine Studio
// SquareLine Studio version: SquareLine Studio 1.5.2
// LVGL version: 9.2.2
// Project name: weather0
#ifndef _SLS_UI_HELPERS_H
#define _SLS_UI_HELPERS_H
#include "config.h"
#if IDLE_SCREEN_HOOK
#ifdef __cplusplus
extern "C" {
#endif
#include <lvgl.h>
#define _UI_TEMPORARY_STRING_BUFFER_SIZE 32
#define _UI_BAR_PROPERTY_VALUE 0
#define _UI_BAR_PROPERTY_VALUE_WITH_ANIM 1
void _ui_bar_set_property(lv_obj_t * target, int id, int val);
#define _UI_BASIC_PROPERTY_POSITION_X 0
#define _UI_BASIC_PROPERTY_POSITION_Y 1
#define _UI_BASIC_PROPERTY_WIDTH 2
#define _UI_BASIC_PROPERTY_HEIGHT 3
void _ui_basic_set_property(lv_obj_t * target, int id, int val);
#define _UI_DROPDOWN_PROPERTY_SELECTED 0
void _ui_dropdown_set_property(lv_obj_t * target, int id, int val);
#define _UI_IMAGE_PROPERTY_IMAGE 0
void _ui_image_set_property(lv_obj_t * target, int id, uint8_t * val);
#define _UI_LABEL_PROPERTY_TEXT 0
void _ui_label_set_property(lv_obj_t * target, int id, const char * val);
#define _UI_ROLLER_PROPERTY_SELECTED 0
#define _UI_ROLLER_PROPERTY_SELECTED_WITH_ANIM 1
void _ui_roller_set_property(lv_obj_t * target, int id, int val);
#define _UI_SLIDER_PROPERTY_VALUE 0
#define _UI_SLIDER_PROPERTY_VALUE_WITH_ANIM 1
void _ui_slider_set_property(lv_obj_t * target, int id, int val);
void _ui_screen_change(lv_obj_t ** target, lv_screen_load_anim_t fademode, int spd, int delay,
void (*target_init)(void));
void _ui_screen_delete(lv_obj_t ** target);
void _ui_arc_increment(lv_obj_t * target, int val);
void _ui_bar_increment(lv_obj_t * target, int val, int anm);
void _ui_slider_increment(lv_obj_t * target, int val, int anm);
void _ui_keyboard_set_target(lv_obj_t * keyboard, lv_obj_t * textarea);
#define _UI_MODIFY_FLAG_ADD 0
#define _UI_MODIFY_FLAG_REMOVE 1
#define _UI_MODIFY_FLAG_TOGGLE 2
void _ui_flag_modify(lv_obj_t * target, int32_t flag, int value);
#define _UI_MODIFY_STATE_ADD 0
#define _UI_MODIFY_STATE_REMOVE 1
#define _UI_MODIFY_STATE_TOGGLE 2
void _ui_state_modify(lv_obj_t * target, int32_t state, int value);
#define UI_MOVE_CURSOR_UP 0
#define UI_MOVE_CURSOR_RIGHT 1
#define UI_MOVE_CURSOR_DOWN 2
#define UI_MOVE_CURSOR_LEFT 3
void _ui_textarea_move_cursor(lv_obj_t * target, int val)
;
void scr_unloaded_delete_cb(lv_event_t * e);
void _ui_opacity_set(lv_obj_t * target, int val);
/** Describes an animation*/
typedef struct _ui_anim_user_data_t {
lv_obj_t * target;
lv_image_dsc_t ** imgset;
int32_t imgset_size;
int32_t val;
} ui_anim_user_data_t;
void _ui_anim_callback_free_user_data(lv_anim_t * a);
void _ui_anim_callback_set_x(lv_anim_t * a, int32_t v);
void _ui_anim_callback_set_y(lv_anim_t * a, int32_t v);
void _ui_anim_callback_set_width(lv_anim_t * a, int32_t v);
void _ui_anim_callback_set_height(lv_anim_t * a, int32_t v);
void _ui_anim_callback_set_opacity(lv_anim_t * a, int32_t v);
void _ui_anim_callback_set_image_zoom(lv_anim_t * a, int32_t v);
void _ui_anim_callback_set_image_angle(lv_anim_t * a, int32_t v);
void _ui_anim_callback_set_image_frame(lv_anim_t * a, int32_t v);
int32_t _ui_anim_callback_get_x(lv_anim_t * a);
int32_t _ui_anim_callback_get_y(lv_anim_t * a);
int32_t _ui_anim_callback_get_width(lv_anim_t * a);
int32_t _ui_anim_callback_get_height(lv_anim_t * a);
int32_t _ui_anim_callback_get_opacity(lv_anim_t * a);
int32_t _ui_anim_callback_get_image_zoom(lv_anim_t * a);
int32_t _ui_anim_callback_get_image_angle(lv_anim_t * a);
int32_t _ui_anim_callback_get_image_frame(lv_anim_t * a);
void _ui_arc_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix);
void _ui_slider_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * prefix, const char * postfix);
void _ui_checked_set_text_value(lv_obj_t * trg, lv_obj_t * src, const char * txt_on, const char * txt_off);
void _ui_spinbox_step(lv_obj_t * target, int val)
;
void _ui_switch_theme(int val)
;
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* _SLS_UI_HELPERS_H */
#endif // IDLE_SCREEN_HOOK

View File

@@ -0,0 +1,107 @@
// This file was generated by SquareLine Studio
// SquareLine Studio version: SquareLine Studio 1.5.2
// LVGL version: 9.2.2
// Project name: weather0
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl.h"
#endif
#ifndef LV_ATTRIBUTE_MEM_ALIGN
#define LV_ATTRIBUTE_MEM_ALIGN
#endif
#include "config.h"
#if IDLE_SCREEN_HOOK
// IMAGE DATA: assets/xiaozhi_48.png
const LV_ATTRIBUTE_MEM_ALIGN uint8_t ui_img_xiaozhi_48_png_data[] = {
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x3B,0xEF,0xBD,0x8D,0x9F,0x0B,0xDF,0x1B,0xDF,0x23,0xDF,0x23,0xDF,0x23,0xDF,0x23,0xDF,0x1B,0xDF,0x1B,0xDF,0x23,0xDF,0x23,0xDF,0x23,0xDF,0x23,0xDF,0x1B,0x9F,0x0B,0xBD,0x8D,0x3B,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x3B,0xEF,
0x5C,0xBE,0xFF,0x23,0xBF,0x13,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xBF,0x13,0xBF,0x13,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xBF,0x13,0xFF,0x23,0x5C,0xBE,0x3B,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0xFC,0xDE,0x3B,0xEF,0x7C,0xBE,0xDF,0x1B,0xBF,0x13,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xBF,0x13,0xBF,0x13,0xDF,0x1B,0xDF,0x1B,0xDF,0x1B,0xBF,0x13,0xDF,0x1B,0x7C,0xBE,0x3B,0xEF,
0xFC,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xDF,0x3B,0xEF,0x7C,0xC6,0x1F,0x2C,0xDF,0x1B,0xFF,0x23,0xFF,0x23,0xDF,0x1B,0xDF,0x1B,0xFF,0x23,0xFF,0x23,0xDF,0x1B,0x1F,0x2C,0x7C,0xC6,0x3B,0xEF,0x1C,0xDF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3B,0xEF,0x7C,0xBE,0xDF,0x1B,0xBF,0x0B,0xDF,0x13,0xBF,0x13,0xBF,0x13,0xDF,0x13,0xBF,0x0B,0xDF,0x1B,0x7C,0xBE,0x3B,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0xFC,0xDE,0x3B,0xEF,0x7C,0xBE,0xDF,0x1B,0x9F,0x0B,0xBF,0x13,0xBF,0x13,0x9F,0x0B,0xDF,0x1B,0x7C,0xBE,0x3B,0xEF,0xFC,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3B,0xEF,0x5C,0xB6,0x5E,0x3C,0xBF,0x13,0xBF,0x13,0x5E,0x3C,0x5C,0xB6,0x3B,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xDF,0x3B,0xEF,0x5B,0xF7,0x7E,0x44,0x7E,0x44,0x5B,0xF7,0x3B,0xEF,0x1C,0xDF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xDC,0xD6,0xDC,0xD6,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3B,0xE7,0x3B,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x5D,0xEF,0x5D,0xEF,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x5D,0xEF,0x5D,0xEF,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0xFB,0xDE,0xB6,0xB5,0xB6,0xB5,0xFB,0xDE,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0xFB,0xDE,0xB6,0xB5,0xB6,0xB5,0xFB,0xDE,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0x18,0xC6,
0xE7,0x39,0x20,0x00,0x20,0x00,0xE7,0x39,0x18,0xC6,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0x18,0xC6,0xE7,0x39,0x20,0x00,0x20,0x00,0xE7,0x39,0x18,0xC6,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xC7,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC7,0x39,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xC7,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xC7,0x39,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x5D,0xEF,0xB6,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB6,0xB5,0x5D,0xEF,0xFB,0xDE,0xFB,0xDE,0x5D,0xEF,0xB6,0xB5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xB6,0xB5,0x5D,0xEF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x5D,0xEF,0xD7,0xBD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD7,0xBD,0x5D,0xEF,0xFB,0xDE,0xFB,0xDE,0x5D,0xEF,0xD7,0xBD,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD7,0xBD,0x5D,0xEF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xC7,0x39,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0x39,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xC7,0x39,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE7,0x39,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0x18,0xC6,0xC7,0x39,0x61,0x08,0x61,0x08,0xE7,0x39,0x18,0xC6,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x5D,0xEF,0x18,0xC6,0xC7,0x39,0x61,0x08,0x61,0x08,0xE7,0x39,
0x18,0xC6,0x5D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x5D,0xEF,0xFB,0xDE,0x96,0xB5,0x96,0xB5,0xFB,0xDE,0x5D,0xEF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x5D,0xEF,0xFB,0xDE,0x96,0xB5,0x96,0xB5,0xFB,0xDE,0x5D,0xEF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x7D,0xEF,0xBE,0xF7,0x9E,0xF7,0x3C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x3C,0xE7,0x9E,0xF7,0xBE,0xF7,0x7D,0xEF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xDB,0xDE,
0x96,0xB5,0x79,0xCE,0x3C,0xE7,0x5D,0xEF,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x5D,0xEF,0x3C,0xE7,0x79,0xCE,0x96,0xB5,0xDB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x9A,0xD6,0xEF,0x7B,0x4D,0x6B,0x92,0x94,0xB6,0xB5,0x9A,0xD6,0x1C,0xE7,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x1C,0xE7,0x9A,0xD6,0xB6,0xB5,0x92,0x94,0x4D,0x6B,0xEF,0x7B,
0x9A,0xD6,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xDB,0xDE,0xB6,0xB5,0x51,0x8C,0x8E,0x73,0x8E,0x73,0xCF,0x7B,0x30,0x84,0x92,0x94,0x92,0x94,0x30,0x84,0xCF,0x7B,0x8E,0x73,0x8E,0x73,0x51,0x8C,0xB6,0xB5,0xDB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0xBA,0xD6,0xF7,0xBD,0x34,0xA5,0xD3,0x9C,0x92,0x94,0x92,0x94,0xD3,0x9C,0x34,0xA5,0xF7,0xBD,0xBA,0xD6,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x3C,0xE7,0x5D,0xEF,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x3C,0xE7,0x5D,0xEF,0x3C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,
0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xDB,0xDE,0xFB,0xDE,0x1C,0xE7,0x1C,0xE7,0x1C,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
//alpha channel data:
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xA1,0xA1,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x29,0xDE,0xDE,0x29,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2C,0xEC,0xEC,0x2C,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xAA,0xAA,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA5,0xA5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA6,0xA6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xA3,0xA3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x12,0x21,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1B,0xAF,0xAF,0x1B,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x21,0x12,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x54,0xD5,0xDF,0xDD,0xDD,0xDD,0xDD,0xDD,0xDD,0xDD,0xDB,0xF4,0xF4,0xDB,0xDD,0xDD,0xDD,0xDD,0xDD,0xDD,0xDD,0xDF,0xD5,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xDB,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDA,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xEE,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x13,0xEC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEC,0x13,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0xEF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0x14,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0xDA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xD9,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x53,0xD5,0xDE,0xDA,0xE7,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE6,0xDA,0xDE,0xD4,0x53,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x1F,0x25,0xAE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAD,0x25,0x1F,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x14,0x13,0x13,0x13,0x13,0x0F,0x03,0x17,
0xDC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDC,0x17,0x03,0x0F,0x13,0x13,0x13,0x13,0x14,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x74,0xE6,0xEE,0xEC,0xEC,0xEC,0xEC,0xEC,0xE0,0x90,0xDE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDE,
0x8E,0xDF,0xED,0xEC,0xEC,0xEC,0xEC,0xEE,0xE7,0x77,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xEF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFA,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xEE,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x9E,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xF6,0xB1,0xE2,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE3,0xB3,0xF6,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0xFB,0x9B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x35,0x3C,0x3A,0x3A,0x3A,0x3A,0x3B,0x2E,0x22,
0xDC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xDC,0x22,0x2E,0x3B,0x3A,0x3A,0x3A,0x3A,0x3C,0x35,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x21,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,
0x21,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0xE0,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,
0xE1,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE1,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x22,0xE5,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE5,
0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0A,0xAE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xAE,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5D,0xB5,0xB4,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB3,0xB4,0xB5,0x5D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
const lv_image_dsc_t ui_img_xiaozhi_48_png = {
.header.w = 48,
.header.h = 48,
.data_size = sizeof(ui_img_xiaozhi_48_png_data),
.header.cf = LV_COLOR_FORMAT_NATIVE_WITH_ALPHA,
.header.magic = LV_IMAGE_HEADER_MAGIC,
.data = ui_img_xiaozhi_48_png_data
};
#endif // IDLE_SCREEN_HOOK

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");
}
}

View File

@@ -0,0 +1,46 @@
/**
* @file weather_service.h
* @brief Weather API service for fetching weather data
* @version 1.0
* @date 2025-01-10
*/
#pragma once
#include <string>
#include <functional>
#include "idle_screen.h"
class WeatherService {
public:
WeatherService();
~WeatherService();
// Initialize weather service with city code (optional, will auto-detect if empty)
void Initialize(const std::string& city_code = ""); // Empty: auto-detect
// Auto-detect city code by IP address
bool AutoDetectCityCode();
// Fetch weather data (async)
void FetchWeather();
// Set callback for when weather data is updated
void SetWeatherCallback(std::function<void(const WeatherData&)> callback);
// Get last fetched weather data
const WeatherData& GetLastWeatherData() const { return last_weather_data_; }
// Get current city code
const std::string& GetCityCode() const { return city_code_; }
private:
void ParseWeatherData(const std::string& response);
std::string ExtractJsonValue(const std::string& json, const std::string& key);
private:
std::string city_code_;
WeatherData last_weather_data_;
std::function<void(const WeatherData&)> weather_callback_;
bool auto_detect_enabled_;
};