Upgrade Playlist Features
This commit is contained in:
185
main/boards/genjutech-s3-1.54tft/WEATHER_CLOCK_README.md
Normal file
185
main/boards/genjutech-s3-1.54tft/WEATHER_CLOCK_README.md
Normal 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
|
||||
|
||||
@@ -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_
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "genjutech-s3-1.54tft",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "genjutech-s3-1.54tft",
|
||||
"sdkconfig_append": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
456
main/boards/genjutech-s3-1.54tft/idle_screen.cc
Normal file
456
main/boards/genjutech-s3-1.54tft/idle_screen.cc
Normal 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
|
||||
118
main/boards/genjutech-s3-1.54tft/idle_screen.h
Normal file
118
main/boards/genjutech-s3-1.54tft/idle_screen.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
3545
main/boards/genjutech-s3-1.54tft/ui_font_font48Seg.c
Normal file
3545
main/boards/genjutech-s3-1.54tft/ui_font_font48Seg.c
Normal file
File diff suppressed because it is too large
Load Diff
349
main/boards/genjutech-s3-1.54tft/ui_helpers.c
Normal file
349
main/boards/genjutech-s3-1.54tft/ui_helpers.c
Normal 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
|
||||
154
main/boards/genjutech-s3-1.54tft/ui_helpers.h
Normal file
154
main/boards/genjutech-s3-1.54tft/ui_helpers.h
Normal 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
|
||||
107
main/boards/genjutech-s3-1.54tft/ui_img_xiaozhi_48_png.c
Normal file
107
main/boards/genjutech-s3-1.54tft/ui_img_xiaozhi_48_png.c
Normal 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
|
||||
365
main/boards/genjutech-s3-1.54tft/weather_service.cc
Normal file
365
main/boards/genjutech-s3-1.54tft/weather_service.cc
Normal 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");
|
||||
}
|
||||
}
|
||||
|
||||
46
main/boards/genjutech-s3-1.54tft/weather_service.h
Normal file
46
main/boards/genjutech-s3-1.54tft/weather_service.h
Normal 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_;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user