按照小安派AiPi-Eyes天气站思路,在ESP32 S3上实现获取天气情况。
一、在ESP32 S3实现
1、main.c
建立2个TASK
void app_main(void)
{
//lvgl初始化
xTaskCreate(guiTask, "guiTask", 1024 * 6, NULL, 5, NULL);
//wifi初始化、socket、json处理task
custom_init();
}
2、guiTask()
lVGL初始化
void guiTask(void *pvParameter)
{
xGuiSemaphore = xSemaphoreCreateMutex();
static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
static lv_disp_drv_t disp_drv; // contains callback functions
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_set_direction(PIN_NUM_RD, GPIO_MODE_OUTPUT);
gpio_set_level(PIN_NUM_RD, 1);
backlight_ledc_init();
ESP_LOGI(TAG, "Initialize Intel 8080 bus");
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.dc_gpio_num = PIN_NUM_DC,
.wr_gpio_num = PIN_NUM_PCLK,
.data_gpio_nums = {
PIN_NUM_DATA0,
PIN_NUM_DATA1,
PIN_NUM_DATA2,
PIN_NUM_DATA3,
PIN_NUM_DATA4,
PIN_NUM_DATA5,
PIN_NUM_DATA6,
PIN_NUM_DATA7,
},
.bus_width = 8,
.max_transfer_bytes = LCD_V_RES * 220 * sizeof(uint16_t),
.psram_trans_align = PSRAM_DATA_ALIGNMENT,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = PIN_NUM_CS,
.pclk_hz = LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 10,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.on_color_trans_done = notify_lvgl_flush_ready,
.user_ctx = &disp_drv,
.lcd_cmd_bits = LCD_CMD_BITS,
.lcd_param_bits = LCD_PARAM_BITS,
.flags.swap_color_bytes = true,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
ESP_LOGI(TAG, "Install LCD driver of ili9225");
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = PIN_NUM_RST,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9225(io_handle, &panel_config, &panel_handle));
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
// alloc draw buffers used by LVGL
// it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized
lv_color_t *buf1 = NULL;
lv_color_t *buf2 = NULL;
buf1 = heap_caps_malloc(LCD_V_RES * 50 * sizeof(lv_color_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
buf2 = heap_caps_malloc(LCD_V_RES * 50 * sizeof(lv_color_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
assert(buf1);
assert(buf2);
ESP_LOGI(TAG, "buf1@%p, buf2@%p", buf1, buf2);
// initialize LVGL draw buffers
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, LCD_V_RES * 50);
ESP_LOGI(TAG, "Register display driver to LVGL");
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = LCD_H_RES;
disp_drv.ver_res = LCD_V_RES;
disp_drv.flush_cb = lvgl_flush_cb;
disp_drv.draw_buf = &disp_buf;
disp_drv.user_data = panel_handle;
// lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
lv_disp_drv_register(&disp_drv);
ESP_LOGI(TAG, "Install LVGL tick timer");
// Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
const esp_timer_create_args_t lvgl_tick_timer_args = {
.callback = &increase_lvgl_tick,
.name = "lvgl_tick"};
esp_timer_handle_t lvgl_tick_timer = NULL;
ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, LVGL_TICK_PERIOD_MS * 1000));
ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 8191));
// Update duty to apply the new value
ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0));
// First to print one frame
lv_timer_handler();
lv_obj_t *screen = lv_obj_create(NULL);
lv_obj_set_style_bg_color(screen,lv_color_hex(0x000000),LV_PART_MAIN);
lv_scr_load(screen);
static lv_style_t style;
lv_style_init(&style);
lv_style_set_bg_color(&style, lv_color_make(0xFF, 0xff, 0xff));
lv_style_set_bg_opa(&style,LV_OPA_10);
lv_style_set_border_color(&style, lv_color_make(0xFF, 0x00, 0xff));
lv_style_set_text_color(&style,lv_color_make(0x00, 0x00, 0xff));
label1=lv_label_create(lv_scr_act());
lv_obj_add_style(label1, &style, 0);
lv_obj_set_pos(label1, 10, 10); /*Set its position*/
lv_obj_set_size(label1, 160, 32); /*Set its size*/
lv_label_set_text(label1, "Weather");
lv_obj_set_style_text_color(label1, lv_color_make(0xff, 0x00, 0x00), LV_PART_MAIN|LV_STATE_DEFAULT);
//城市
label_city=lv_label_create(lv_scr_act());
lv_obj_set_pos(label_city, 10, 50); /*Set its position*/
lv_obj_set_size(label_city, 100, 32); /*Set its size*/
lv_obj_set_style_text_color(label_city, lv_color_make(0xff, 0xff, 0xff), LV_PART_MAIN|LV_STATE_DEFAULT);
//温度
label_tem=lv_label_create(lv_scr_act());
lv_obj_set_pos(label_tem, 120, 50); /*Set its position*/
lv_obj_set_size(label_tem, 60, 32); /*Set its size*/
lv_obj_set_style_text_color(label_tem, lv_color_make(0xff, 0xff, 0xff), LV_PART_MAIN|LV_STATE_DEFAULT);
//天气
label_wea_img=lv_label_create(lv_scr_act());
lv_obj_set_pos(label_wea_img, 10, 90); /*Set its position*/
lv_obj_set_size(label_wea_img, 160, 32); /*Set its size*/
lv_obj_set_style_text_color(label_wea_img, lv_color_make(0xff, 0xff, 0xff), LV_PART_MAIN|LV_STATE_DEFAULT);
//湿度
label_humidity=lv_label_create(lv_scr_act());
lv_obj_set_pos(label_humidity, 80, 90); /*Set its position*/
lv_obj_set_size(label_humidity, 60, 32); /*Set its size*/
lv_obj_set_style_text_color(label_humidity, lv_color_make(0x00, 0xff, 0x00), LV_PART_MAIN|LV_STATE_DEFAULT);
//日期
label_date=lv_label_create(lv_scr_act());
lv_obj_set_pos(label_date, 20, 130); /*Set its position*/
lv_obj_set_size(label_date, 170, 32); /*Set its size*/
lv_obj_set_style_text_color(label_date, lv_color_make(0x00, 0x00, 0xff), LV_PART_MAIN|LV_STATE_DEFAULT);
ESP_LOGI(TAG, "LVGL interface init OK!");
while (1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY))
{
lv_timer_handler();
xSemaphoreGive(xGuiSemaphore);
}
}
free(buf1);
free(buf2);
vTaskDelete(NULL);
}
3、void custom_init(void)
建立queue处理TASK
建立定时,1个小时执行一次重新获取天气信息
执行exmple_connect(),连接WIFI
向queue模拟发送获得IP成功,触发queue下一步操作
void custom_init(void)
{
ESP_ERROR_CHECK( nvs_flash_init() );
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect());
queue = xQueueCreate(1, 1024*2);
xTaskCreate(queue_task, "queue task", 1024*6, NULL, 2, NULL);
http_timers = xTimerCreate("http_timers", pdMS_TO_TICKS(1000), pdTRUE, 0, http_hour_requst_time);
vTaskDelay(4000 / portTICK_PERIOD_MS);
static char* queue_buff;
queue_buff = pvPortMalloc(128);
memset(queue_buff, 0, 128);
sprintf(queue_buff, "{\"ip\":{\"IP\":\"%s\"}}","11.11.11.11");
//前面example_connect()不可控,运行到这里应该已经获得ip了,向QUEUE模拟发送一个ip已经获得的消息,触发执行后面的程序
xQueueSend(queue, queue_buff, portMAX_DELAY);
vPortFree(queue_buff);
}
4、static void queue_task(void* arg)
根据进入QUEUE的信息,执行不同的任务,一个守护task
static int cjson__analysis_type(char* json_data)
{
cJSON* root = cJSON_Parse(json_data);
//ESP_LOGI(TAG, "json_data:%s",json_data);
if (root==NULL) {
printf("[%s] is not json\r\n", __func__);
return 0;
}
cJSON* wifi = cJSON_GetObjectItem(root, "WiFi");
if (wifi) {
cJSON_Delete(root);
return 1;
}
cJSON* ip = cJSON_GetObjectItem(root, "ip");
if (ip) {
cJSON_Delete(root);
return 2;
}
cJSON* weather = cJSON_GetObjectItem(root, "weather");
if (weather) {
cJSON_Delete(root);
return 3;
}
cJSON_Delete(root);
return 0;
}
static void queue_task(void* arg)
{
char* queue_buff = NULL;
queue_buff = pvPortMalloc(1024*2);
while (1)
{
vTaskDelay(pdMS_TO_TICKS(100));
memset(queue_buff, 0, 1024*2);
xQueueReceive(queue, queue_buff, portMAX_DELAY);
switch (cjson__analysis_type(queue_buff))
{
case 0:
printf("queue_buff:%s\r\n",queue_buff);
break;
case 1: //wifi
break;
case 2: //ip
if (https_Handle!=NULL) {
vTaskDelete(https_Handle);
}
xTaskCreate(&http_get_task, "http_get_task", 4096, NULL, 5, &https_Handle);
break;
case 3: //weather
vTaskSuspend(https_Handle);
cjson_get_weather(queue_buff);
break;
default:
break;
}
}
vPortFree(queue_buff);
}
5、static void http_hour_requst_time(TimerHandle_t timer),
每1个小时重新执行一次获取天气信息
static void http_hour_requst_time(TimerHandle_t timer)
{
if (timers_http>=60*60) {
//LOG_I("Timed to http update,start https request");
vTaskResume(https_Handle);
timers_http = 0;
}
else {
timers_http++;
}
}
6、static void http_get_task(void *pvParameters)
向网站发送API获取信息,返回天气信息入QUEUE。
#define WEB_SERVER "v1.yiketianqi.com"
#define WEB_PORT "80"
#define WEB_PATH "/api?unescape=1&version=v61&appid=替换成自己的ID&appsecret=替换成自己的KEY"
static const char *REQUEST = "GET " WEB_PATH " HTTP/1.0\r\n"
"Host: "WEB_SERVER":"WEB_PORT"\r\n"
"User-Agent: esp-idf/1.0 esp32\r\n"
"\r\n";
static char* https_get_data(const char* https_request_data)
{
char* request_data = https_request_data;
printf("https_get_data:\r\n%s\r\n",request_data);
static char* https_data;
https_data = pvPortMalloc(1024*2);
memset(https_data, 0, 1024*2);
request_data += 2;
char* date = pvPortMalloc(64);
char* request_value = strtok(request_data, "\n");
for (size_t i = 0; i < 13; i++)
{
printf("[%d]%s\r\n", i,request_value);
if (i==2) strcpy(date, request_value);
if(i==12) strcpy(https_data, request_value);
memset(request_value, 0, strlen(request_value));
request_value = strtok(NULL, "\n");
}
vPortFree(date);
return https_data;
}
static void http_get_task(void *pvParameters)
{
const struct addrinfo hints = {
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
struct addrinfo *res;
struct in_addr *addr;
int s, r;
char recv_buf[64];
static char* buff;
char* queue_buff = NULL;
buff = pvPortMalloc(2*1024);
memset(buff, 0, 2*1024);
while(1) {
int err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);
if(err != 0 || res == NULL) {
ESP_LOGE(TAG, "DNS lookup failed err=%d res=%p", err, res);
vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
}
/* Code to print the resolved IP.
Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code */
addr = &((struct sockaddr_in *)res->ai_addr)->sin_addr;
ESP_LOGI(TAG, "DNS lookup succeeded. IP=%s", inet_ntoa(*addr));
s = socket(res->ai_family, res->ai_socktype, 0);
if(s < 0) {
ESP_LOGE(TAG, "... Failed to allocate socket.");
freeaddrinfo(res);
vTaskDelay(1000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... allocated socket");
if(connect(s, res->ai_addr, res->ai_addrlen) != 0) {
ESP_LOGE(TAG, "... socket connect failed errno=%d", errno);
close(s);
freeaddrinfo(res);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... connected");
freeaddrinfo(res);
if (write(s, REQUEST, strlen(REQUEST)) < 0) {
ESP_LOGE(TAG, "... socket send failed");
close(s);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... socket send success");
struct timeval receiving_timeout;
receiving_timeout.tv_sec = 5;
receiving_timeout.tv_usec = 0;
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,
sizeof(receiving_timeout)) < 0) {
ESP_LOGE(TAG, "... failed to set socket receiving timeout");
close(s);
vTaskDelay(4000 / portTICK_PERIOD_MS);
continue;
}
ESP_LOGI(TAG, "... set socket receiving timeout success");
/* Read HTTP response */
do {
bzero(recv_buf, sizeof(recv_buf));
r = read(s, recv_buf, sizeof(recv_buf)-1);
strncat(buff, recv_buf, r); //
for(int i = 0; i < r; i++) {
putchar(recv_buf[i]);
}
} while(r > 0);
ESP_LOGI(TAG, "... done reading from socket. Last read return=%d errno=%d.", r, errno);
close(s);
queue_buff = pvPortMalloc(1024*3);
memset(queue_buff, 0, 1024*3);
sprintf(queue_buff, "{\"weather\":%s}", https_get_data(buff));
xQueueSend(queue, queue_buff, portMAX_DELAY);
xTimerStart(http_timers, portMAX_DELAY);
vPortFree(buff);
vTaskDelay(50/portTICK_PERIOD_MS);
for(int countdown = 10; countdown >= 0; countdown--) {
ESP_LOGI(TAG, "%d... ", countdown);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
ESP_LOGI(TAG, "Starting again!");
}
}
7、void cjson_get_weather(char* weather_data)
处理json,并在LCD显示。
void cjson_get_weather(char* weather_data)
{
cJSON * item = NULL;//cjson对象
cJSON* root = cJSON_Parse(weather_data );
root= cJSON_GetObjectItem(root, "weather");
if (!root)
{
printf("Error before: [%s]\n",cJSON_GetErrorPtr());
}
else
{
item = cJSON_GetObjectItem(root, "cityEn"); //城市
lv_label_set_text(label_city, item->valuestring);
item = cJSON_GetObjectItem(root, "tem"); //温度
lv_label_set_text(label_tem, item->valuestring);
item = cJSON_GetObjectItem(root, "wea_img"); //wea
lv_label_set_text(label_wea_img, item->valuestring);
item = cJSON_GetObjectItem(root, "humidity"); //wea
lv_label_set_text(label_humidity, item->valuestring);
item = cJSON_GetObjectItem(root, "date");
lv_label_set_text(label_date, item->valuestring);
}
cJSON_free(item);
}
二、说明
1、利用了example中的example_connect()函数实现wifi连接,需要做一下配置:
CMakeLists.txt增加:
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
idf.py menuconfig 设置SSID和password
2、生成的BIN大于1M,Partition Table中选择“Single factory app(large)”,可以支持到1.5M