内容介绍
内容介绍
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. 功能展示
下面是获取到天气数据之后的截图:
4. 对本活动的心得体会(包括意见或建议)
本次活动主要使用了pio进行开发,平台上分享有各种库,大大加快了开发速度,提升了开发效率。关于硬件方面,这个产品真的挺好看的,接口也丰富,DIY的好工具,非常棒。最后感谢举办方提供的这次活动。
附件下载
firmware.bin
firmware.elf
wio_terminal.zip
源码
团队介绍
个人团队
评论
0 / 100
查看更多