Funpack12:Wio Terminal实现一个天气预报仪
Funpack12,Wio Terminal,任务二,天气预报仪
标签
嵌入式系统
wio terminal
天气预报仪
wakojosin
更新2021-12-21
1313
1. 介绍用本板卡最终实现了什么功能
用板卡实现了题目2中的功能:通过WIFI获取天气预报数据,然后在屏幕上显示天气情况、温湿度、空气质量及未来三天的天气情况。通过五向开关可以切换不同的城市。
2. 各功能对应的主要代码片段及说明
依赖的库
Seeed_Arduino_rpcWiFi、Seeed_Arduino_rpcUnified、Seeed_Arduino_mbedtls、Seeed_Arduino_FS、Seeed_Arduino_SFUD、ArduinoJson、lvgl等,在此感谢开源的大佬们提供这么好用的工具。
代码说明中有一定的删减,完成代码看附件。

2.1 显示交互初始化

显示使用的LVGL图形库,显示接口及按键接口如下:
// lvgl显示接口
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p ) {
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

    tft.startWrite();
    tft.setAddrWindow( area->x1, area->y1, w, h );
    for(int y=area->y1; y <= area->y2; y++) {
        for(int x=area->x1; x <= area->x2; x++) {
            uint16_t c = color_p->full;
            tft.writeColor(c, 1);
            color_p ++;
        }
    }
    tft.endWrite();

    lv_disp_flush_ready(disp);
}
// 获取按键按下的ID
static int32_t button_get_pressed_id(void) {
    if(!digitalRead(WIO_5S_UP)) {
        return 0;
    } else if(!digitalRead(WIO_5S_LEFT)) {
        return 1;
    } else if(!digitalRead(WIO_5S_RIGHT)) {
        return 2;
    } else if(!digitalRead(WIO_5S_DOWN)) {
        return 3;
    } else if(!digitalRead(WIO_5S_PRESS)) {
        return 4;
    }
    return -1;
}
// lvgl按键接口
void read_button(lv_indev_drv_t * indev, lv_indev_data_t * data) {
    int32_t btn_act;

    btn_act = button_get_pressed_id();
    if(btn_act >= 0) {
        data->btn_id = btn_act;
        data->state = LV_INDEV_STATE_PR;
    } else {
        data->state = LV_INDEV_STATE_REL;
    }
}
2.2 初始化图形库并且创建显示标签
void lvgl_init() {
  lv_init(); //初始化图形库
  lv_disp_draw_buf_init(&lv_draw_buf, lv_buf, NULL, LV_HOR_RES_MAX * 10); //初始化缓存
  lv_disp_drv_init(&lv_disp_drv);
  lv_disp_drv.hor_res = LV_HOR_RES_MAX;
  lv_disp_drv.ver_res = LV_VER_RES_MAX;
  lv_disp_drv.flush_cb = my_disp_flush;
  lv_disp_drv.draw_buf = &lv_draw_buf;
  lv_disp_drv_register(&lv_disp_drv); //注册显示器
  /*Initialize the touch pad*/
  lv_indev_drv_init(&indev_drv);
  indev_drv.type = LV_INDEV_TYPE_BUTTON;
  indev_drv.read_cb = read_button;
  indev_btn = lv_indev_drv_register(&indev_drv); //注册按键
  static const lv_point_t btn_points[] = {
    {LV_HOR_RES_MAX / 2, 10}, //up
    {10, LV_VER_RES_MAX / 2}, //left
    {LV_HOR_RES_MAX - 10, LV_VER_RES_MAX / 2}, //right
    {LV_HOR_RES_MAX / 2, LV_VER_RES_MAX - 10}, //down
    {LV_HOR_RES_MAX / 2, LV_VER_RES_MAX / 2} //ok
  };
  lv_indev_set_button_points(indev_btn, btn_points); //映射按键到坐标
  // create day object.
  day00_obj = lv_obj_create(lv_scr_act());
  // 设置背景颜色
  lv_obj_set_style_bg_color(day00_obj, lv_palette_main(LV_PALETTE_RED), 0);
  // 设置字体颜色
  lv_obj_set_style_text_color(day00_obj, lv_color_white(), 0);
  // 设置对象大小
  lv_obj_set_size(day00_obj, LV_HOR_RES_MAX/2, LV_VER_RES_MAX/2);
  // 设置对象位置
  lv_obj_set_pos(day00_obj, 0, 0);
  day01_obj = lv_obj_create(lv_scr_act());
  lv_obj_set_style_bg_color(day01_obj, lv_palette_main(LV_PALETTE_ORANGE), 0);
  //略,同day00_obj
  day10_obj = lv_obj_create(lv_scr_act());
  lv_obj_set_style_bg_color(day10_obj, lv_palette_main(LV_PALETTE_BLUE), 0);
  //略,同day00_obj
  day11_obj = lv_obj_create(lv_scr_act());
  lv_obj_set_style_bg_color(day11_obj, lv_palette_main(LV_PALETTE_GREEN), 0);
  //略,同day00_obj
  // create label
  // 在day00_obj对象内创建标签
  day00_label = lv_label_create(day00_obj);
  // 设置字库
  lv_obj_set_style_text_font(day00_label, &simfang_14, 0);
  // 设置标签长文本模式
  lv_label_set_long_mode(day00_label, LV_LABEL_LONG_WRAP);
  // 设置对齐方式
  lv_obj_set_align(day00_label, LV_ALIGN_CENTER);
  // 设置标签大小,小于父对象尺寸
  lv_obj_set_size(day00_label, LV_HOR_RES_MAX/2 + lv_obj_get_content_width(day00_obj), LV_VER_RES_MAX/2 + lv_obj_get_content_height(day00_obj));
  // 设置显示内容
  lv_label_set_text(day00_label, "day00 label");
  // 下面与day00_label类似,略
  // create button
  lv_obj_t *btn_left = lv_btn_create(lv_scr_act());
  lv_obj_add_event_cb(btn_left, btn_event_cb, LV_EVENT_ALL, (void *)BTN_LEFT_CODE);
  lv_obj_set_pos(btn_left, 0, LV_VER_RES_MAX / 2 - 10);
  lv_obj_set_size(btn_left, 20, 20);
  lv_obj_set_style_opa(btn_left, LV_OPA_10, 0);
  lv_obj_set_style_bg_color(btn_left, lv_palette_main(LV_PALETTE_GREY), 0);
  lv_obj_t *btn_right = lv_btn_create(lv_scr_act());
  lv_obj_add_event_cb(btn_right, btn_event_cb, LV_EVENT_ALL, (void *)BTN_RIGHT_CODE);
  lv_obj_set_pos(btn_right, LV_HOR_RES_MAX - 20, LV_VER_RES_MAX / 2 - 10);
  //略,同btn_left
  lv_obj_t *btn_up = lv_btn_create(lv_scr_act());
  lv_obj_add_event_cb(btn_up, btn_event_cb, LV_EVENT_ALL, (void *)BTN_UP_CODE);
  lv_obj_set_pos(btn_up, LV_HOR_RES_MAX / 2 - 10, 0);
  //略,同btn_left
  lv_obj_t *btn_down = lv_btn_create(lv_scr_act());
  lv_obj_add_event_cb(btn_down, btn_event_cb, LV_EVENT_ALL, (void *)BTN_DOWN_CODE);
  lv_obj_set_pos(btn_down, LV_HOR_RES_MAX / 2 - 10, LV_VER_RES_MAX - 20);
  //略,同btn_left
  
}
2.3 初始化函数
void setup() {
    // put your setup code here, to run once:
    Serial.begin(115200); // 串口初始化
    while(!Serial); // 等待串口准备好
    button_init(); // 按键初始化
    lvgl_init(); // lvgl图形库初始化
    tft.begin(); // LCD初始化
    tft.setRotation(3); // 设置LCD显示方向
    xTaskCreate(lv_task, "lv_task", 2048, NULL, 10, NULL); // 创建图形库处理任务
    WiFi.mode(WIFI_STA); // 设置WIFI模式
    WiFi.disconnect();
}
2.4 主循环
void loop() {
    // put your main code here, to run repeatedly:
    static uint32_t last_millis = 0;
    // 检查是否连接WIFI
    if(!WiFi.isConnected()) {
        const static char*wifi_name[] = {"OW06", "PD06"};
        const static char*wifi_pass[] = {NULL};
        uint32_t wifi_index = 0;
        last_millis = millis();
        do {
            lv_label_set_text_fmt(day00_label, "connect to %s ...", wifi_name[wifi_index]);
            if(((sizeof(wifi_name)/sizeof(wifi_name[0])) == (sizeof(wifi_pass)/sizeof(wifi_pass[0]))) && wifi_pass[0]) {
                // 使用wifi_pass定义的密码
                WiFi.begin(wifi_name[wifi_index], wifi_pass[wifi_index]);
            } else {
                // 使用统一密码
                WiFi.begin(wifi_name[wifi_index], "xxxxxxxx");
            }
            // 连接超时,切换WIFI
            if(millis() - last_millis >= 1500) {
                last_millis = millis();
                if(++wifi_index >= (sizeof(wifi_name)/sizeof(wifi_name[0]))) {
                    wifi_index = 0;
                }
            }
        } while(!WiFi.isConnected());
        lv_label_set_text_fmt(day00_label, "connected!\n%s", WiFi.localIP().toString().c_str());
        last_millis = 0;
    }
    String city;
    String humi;
    String quality;
    String date[4]; 
    String weather[4]; 
    String temperature[4];
    // 周期性的请求天气数据
    if((!last_millis) || (millis() - last_millis >= 600000)) { //10min
        if(get_weather(city_list[city_code_index].code + city_code_subindex, city, humi, quality, date, weather, temperature)) {
            last_millis = millis();
            // 获取成功,显示天气数据
            String msg = "城市: " + city + ", " + weather[0] + "\n" + String(date[0]) + "," + "\n空气质量:" + quality + "\n温度:" + temperature[0] + "\n湿度:" + humi;
            lv_label_set_text(day00_label, msg.c_str());
            msg = String(date[1]) + "," + weather[1] + "\n温度:" + temperature[1];
            lv_label_set_text(day01_label, msg.c_str());
            msg = String(date[2]) + "," + weather[2] + "\n温度:" + temperature[2];
            lv_label_set_text(day10_label, msg.c_str());
            msg = String(date[3]) + "," + weather[3] + "\n温度:" + temperature[3];
            lv_label_set_text(day11_label, msg.c_str());
        } else {
            vTaskDelay(1000 / portTICK_PERIOD_MS);
        }
    }
    vTaskDelay(10 / portTICK_PERIOD_MS);
}
2.5 获取天气接口
String request_weather_http(String code) {
    WiFiClient client;
    /*http://t.weather.itboy.net/api/weather/city/101210101
    */
    if(!client.connect("t.weather.itboy.net", 80, 5000)) { // 连接服务器
        Serial.println("connect to host failed!");
    } else {
        String msg = "GET /api/weather/city/" + code + " HTTP/1.1\n";
        msg += "Host: t.weather.itboy.net\n";
        msg += "\n";
        client.print(msg);
        time_t begin_time = millis();
        while(!client.available() && ((millis() - begin_time) < 2000)); // 等待数据返回
        if(client.available()) {
            char status[32] = {0};
            client.readBytesUntil('\r', status, sizeof(status));
            if (strcmp(status + 9, "200 OK") != 0) { // 检查返回码
                Serial.print(F("Unexpected response: "));
                Serial.println(status);
                client.stop();
                return String("");
            }
            if(!client.find((char *)"\r\n\r\n")) { // 检查HTTP完整性
                Serial.println(F("Invalid response"));
                client.stop();
                return String("");
            }
            String line = client.readString(); //读取http数据
            client.stop();
            if(!line.startsWith("{")) {
                int index = line.indexOf("\r\n{"); //跳过无用数据
                if(index == -1) {
                    Serial.println("invalid json data:");
                    Serial.println(line);
                    return String("");
                }
                line.remove(0, index + 2);
            }
            return line;
        }
    }
    return String("");
}
bool get_weather(int code, String &city, String &humi, String &quality, String *odate, String *oweather, String *otemperature) {
    assert(odate != NULL);
    assert(oweather != NULL);
    assert(otemperature != NULL);
    String line = request_weather_http(String(code));
    if(line.compareTo("")) {
        DynamicJsonDocument doc(10240);
        DeserializationError error = deserializeJson(doc, line);
        if (error) {
            Serial.print(F("deserializeJson() failed: "));
            Serial.println(error.f_str());
            Serial.println(line);
            return false;
        }
        JsonObject obj = doc.as<JsonObject>();
        String sta = obj["status"];
        if(sta == "200") {
            JsonObject wdata = doc["data"].as<JsonObject>();
            city = doc["cityInfo"]["city"].as<String>();
            humi = wdata["shidu"].as<String>();
            quality = wdata["quality"].as<String>();
            JsonObject cast_0 = wdata["forecast"][0].as<JsonObject>();
            JsonObject cast_1 = wdata["forecast"][1].as<JsonObject>();
            JsonObject cast_2 = wdata["forecast"][2].as<JsonObject>();
            JsonObject cast_3 = wdata["forecast"][3].as<JsonObject>();
            odate[0] = cast_0["ymd"].as<String>();
            odate[1] = cast_1["ymd"].as<String>();
            odate[2] = cast_2["ymd"].as<String>();
            odate[3] = cast_3["ymd"].as<String>();
            oweather[0] = cast_0["type"].as<String>();
            oweather[1] = cast_1["type"].as<String>();
            oweather[2] = cast_2["type"].as<String>();
            oweather[3] = cast_3["type"].as<String>();
            otemperature[0] = cast_0["high"].as<String>() + String("/") + cast_0["low"].as<String>();
            otemperature[1] = cast_1["high"].as<String>() + String("/") + cast_1["low"].as<String>();
            otemperature[2] = cast_2["high"].as<String>() + String("/") + cast_2["low"].as<String>();
            otemperature[3] = cast_3["high"].as<String>() + String("/") + cast_3["low"].as<String>();
            return true;
        } else {
            Serial.println("status code err!" + sta);
            return false;
        }
    }
    return false;
}
2.6 按键功能
通过按键可以切换不同的城市,城市通过city_list管理。
int city_code_index = 0;
int city_code_subindex = 0;
struct CityList {
    int code; //城市代码
    int max_num; //可查询的区县数量
};
struct CityList city_list[] = {
    {101210101, 8}, // 杭州
    {101210501, 5}, // 绍兴
};
void btn_event_cb(lv_event_t * e) {
    int id = (int)lv_event_get_user_data(e);
    switch (e->code) {
    case LV_EVENT_CLICKED:
        if(id == BTN_RIGHT_CODE) {
            city_code_subindex ++;
            if(city_code_subindex >= city_list[city_code_index].max_num) {
                city_code_subindex = 0;
            }
        } else if(id == BTN_LEFT_CODE) {
            city_code_subindex --;
            if(city_code_subindex < 0) {
                city_code_subindex = city_list[city_code_index].max_num - 1;
            }
        } else if(id == BTN_UP_CODE) {
            city_code_index ++;
            if(city_code_index >= (int)(sizeof(city_list)/sizeof(city_list[0]))) {
                city_code_index = 0;
                city_code_subindex = 0;
            }
        } else if(id == BTN_DOWN_CODE) {
            city_code_index --;
            if(city_code_index < 0) {
                city_code_index = sizeof(city_list)/sizeof(city_list[0]) - 1;
            }
            city_code_subindex = 0;
        }
        last_millis = 0;
        break;
    default:
        break;
    }
}
3. 功能展示

下面是获取到天气数据之后的截图:

FoCqxzwopEEEUf7RYUGryYphuJgq

4. 对本活动的心得体会(包括意见或建议)
本次活动主要使用了pio进行开发,平台上分享有各种库,大大加快了开发速度,提升了开发效率。关于硬件方面,这个产品真的挺好看的,接口也丰富,DIY的好工具,非常棒。最后感谢举办方提供的这次活动。
附件下载
firmware.bin
firmware.elf
wio_terminal.zip
源码
团队介绍
个人团队
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号