一、平台简介
Wio Terminal的运行速度为 120MHz (最高可达200MHz), 4MB 外部闪存和 192KB RAM。
Wio Terminal自身配有一个2.4英寸 LCD屏幕, 板载IMU(LIS3DHTR),麦克风,蜂鸣器,microSD卡槽,光传感器和940nm红外发射器。
除了这些它还有两个用于Grove生态系统的多功能Grove接口和兼容Raspberry pi的40个GPIO引脚,用于支持更多附加组件。
无线模块由 Realtek RTL8720DN 提供技术支持,双频 2.4 GHz/5 GHz Wi-Fi (802.11 a/b/g/n),BL/BLE 5.0。
可以方便的使用Arduino,MicroPython,ArduPy等方式进行开发。
二、任务介绍
本次活动完成的任务是任务二,制作一个自动联网的天气预报仪,在设计界面显示温湿度、天气情况、空气质量以及未来三天内的天气变化。
三、代码讲解
1、初始化
对系统进行初始化,同时对外输出初始化状态,完成联网等操作。
const char *ssid = "YourWifi"; //填入SSID
const char *password = "YourPassword"; //填入Wifi密码
void setup()
{
tft.init();
tft.setRotation(3); //旋转屏幕0-3
tft.fillScreen(TFT_BLACK);
tft.drawString("RTL8720 Firmware Version", (320 - tft.textWidth("RTL8720 Firmware Version")) / 2, 100);
tft.drawString(rpc_system_version(), (320 - tft.textWidth(rpc_system_version())) / 2, 116);
tft.drawString("Initializing", (320 - tft.textWidth("Initializing")) / 2, 132);
Serial.begin(115200);
for (uint8_t t = 2; t > 0; t--)
{
Serial.printf("[SETUP] WAIT %d...\n", t);
Serial.flush();
delay(1000);
}
//Wifi初始化
Serial.printf("RTL8720 Firmware Version: %s\n", rpc_system_version());
tft.drawString("Connecting to Wifi", (320 - tft.textWidth("Connecting to Wifi")) / 2, 116);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
Serial.print(".");
// wait 1 second for re-trying
delay(1000);
}
Serial.print("WiFi Connected\n");
tft.drawString("WiFi Connected", (320 - tft.textWidth("WiFi Connected")) / 2, 116);
while (!SD.begin(SDCARD_SS_PIN, SDCARD_SPI))//初始化SD卡,读取卡内的字体文件
{
Serial.println("SD card error!\n");
while (1)
;
}
delay(1000);
pinMode(WIO_KEY_A, INPUT);
pinMode(WIO_KEY_B, INPUT);
//payload = jsonStr;//调试用
getData();
Serial.println(payload);
tft.drawString("Get Data OK", (320 - tft.textWidth("Get Data OK")) / 2, 216);
drawUI();
decodeJSON();
}
2、获取API数据
本次设计使用的是由itboy.com提供的免费天气预报API(免费天气API,天气JSON API,不限次数获取十五天的天气预报 —技术博客 (sojson.com)),API的调用非常简单,只要拼接在地址 “http://t.weather.itboy.net/api/weather/city/+
city_code” 后面即可。city_code可以在 CityCode 中查询获得。
使用http.GET();读取API数据。
String Citycode = ""; //查询所在区域的城市编码填入
//https://github.com/baichengzhou/weather.api/blob/master/src/main/resources/citycode-2019-08-23.json
void getData()
{
// wait for WiFi connection
if ((WiFi.status() == WL_CONNECTED))
{
HTTPClient http;
Serial.print("[HTTP] begin...\n");
Serial.println("http://t.weather.itboy.net/api/weather/city/" + Citycode);
// configure traged server and url
http.begin("http://t.weather.itboy.net/api/weather/city/" + Citycode); //HTTP
Serial.print("[HTTP] GET...\n");
// start connection and send HTTP header
int httpCode = http.GET();
// httpCode will be negative on error
if (httpCode > 0)
{
// HTTP header has been send and Server response header has been handled
Serial.printf("[HTTP] GET... code: %d\n", httpCode);
// file found at server
if (httpCode == HTTP_CODE_OK)
{
payload = http.getString();
}
}
else
{
Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
}
http.end();
}
else
{
Serial.println("Connect Wifi First");
}
}
3、解析JSON并显示
使用ArduinoJson Assistant自动生成解析json的代码,然后在代码中加入其他内容实现输出。
void decodeJSON()
{
forecastCon = 0;
//JSON解析
DynamicJsonDocument doc(6144);
DeserializationError error = deserializeJson(doc, payload);
if (error)
{
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
const char *time = doc["time"];
Serial.println(time);
if (modeUI == 0) //当天天气
{
JsonObject cityInfo = doc["cityInfo"];
const char *cityInfo_city = cityInfo["city"];
const char *cityInfo_parent = cityInfo["parent"];
const char *cityInfo_updateTime = cityInfo["updateTime"];
tft.loadFont("simhei24");
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(cityInfo_parent, 38, 216);
tft.drawString(cityInfo_city, 138, 216);
tft.drawString(cityInfo_updateTime, 238, 216);
tft.unloadFont();
JsonObject data = doc["data"];
const char *data_shidu = data["shidu"];
int data_pm25 = data["pm25"];
int data_pm10 = data["pm10"];
const char *data_quality = data["quality"];
const char *data_wendu = data["wendu"];
const char *data_ganmao = data["ganmao"];
for (JsonObject data_forecast_item : data["forecast"].as<JsonArray>())
{
const char *data_forecast_item_date = data_forecast_item["date"];
const char *data_forecast_item_high = data_forecast_item["high"];
const char *data_forecast_item_low = data_forecast_item["low"];
const char *data_forecast_item_ymd = data_forecast_item["ymd"];
const char *data_forecast_item_sunrise = data_forecast_item["sunrise"];
const char *data_forecast_item_sunset = data_forecast_item["sunset"];
int data_forecast_item_aqi = data_forecast_item["aqi"];
const char *data_forecast_item_fx = data_forecast_item["fx"];
const char *data_forecast_item_fl = data_forecast_item["fl"];
const char *data_forecast_item_type = data_forecast_item["type"];
int length;
char *data_forecast_item_fx_char;
length = strlen(data_forecast_item_fx);
data_forecast_item_fx_char = new char[length + 1];
strcpy(data_forecast_item_fx_char, data_forecast_item_fx);
char *data_forecast_item_fl_char;
length = strlen(data_forecast_item_fl);
data_forecast_item_fl_char = new char[length + 1];
strcpy(data_forecast_item_fl_char, data_forecast_item_fl);
tft.loadFont("qweather-icons50");
if (strncmp(data_forecast_item_type, "晴", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf101", 35, 46);
else if (strncmp(data_forecast_item_type, "多云", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf102", 35, 46);
else if (strncmp(data_forecast_item_type, "阴", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf105", 35, 46);
else if (strncmp(data_forecast_item_type, "阵雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf10a", 35, 46);
else if (strncmp(data_forecast_item_type, "雷阵雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf10c", 35, 46);
else if (strncmp(data_forecast_item_type, "雨夹雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf124", 35, 46);
else if (strncmp(data_forecast_item_type, "小雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf10f", 35, 46);
else if (strncmp(data_forecast_item_type, "中雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf110", 35, 46);
else if (strncmp(data_forecast_item_type, "大雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf111", 35, 46);
else if (strncmp(data_forecast_item_type, "暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf112", 35, 46);
else if (strncmp(data_forecast_item_type, "大暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf115", 35, 46);
else if (strncmp(data_forecast_item_type, "特大暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf116", 35, 46);
else if (strncmp(data_forecast_item_type, "阵雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf12d", 35, 46);
else if (strncmp(data_forecast_item_type, "小雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf120", 35, 46);
else if (strncmp(data_forecast_item_type, "中雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf121", 35, 46);
else if (strncmp(data_forecast_item_type, "大雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf122", 35, 46);
else if (strncmp(data_forecast_item_type, "暴雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf123", 35, 46);
else if (strncmp(data_forecast_item_type, "小雨-中雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf118", 35, 46);
else if (strncmp(data_forecast_item_type, "中雨-大雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf119", 35, 46);
else if (strncmp(data_forecast_item_type, "大雨-暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf11a", 35, 46);
else if (strncmp(data_forecast_item_type, "暴雨-大暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf11b", 35, 46);
else if (strncmp(data_forecast_item_type, "大暴雨-特大暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf11c", 35, 46);
else if (strncmp(data_forecast_item_type, "小雪-中雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf128", 35, 46);
else if (strncmp(data_forecast_item_type, "中雪-大雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf129", 35, 46);
else if (strncmp(data_forecast_item_type, "大雪-暴雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf12a", 35, 46);
tft.unloadFont();
tft.loadFont("simhei24");
tft.drawString(data_forecast_item_type, (120 - tft.textWidth(data_forecast_item_type)) / 2, 106);
tft.drawString(data_forecast_item_ymd, 0, 162);
tft.drawString(strcat(data_forecast_item_fx_char, data_forecast_item_fl_char), 120, 190);
tft.drawString(data_forecast_item_high, 120, 50);
tft.drawString(data_forecast_item_low, 220, 50);
//tft.unloadFont();
break;
}
//tft.loadFont("simhei24");
//tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString("温度", 120, 22);
tft.drawString(data_wendu, 220, 22);
tft.drawString("PM2.5", 120, 78);
tft.drawNumber(data_pm25, 220, 78);
tft.drawString("PM10", 120, 106);
tft.drawNumber(data_pm10, 220, 106);
tft.drawString("空气质量", 120, 134);
tft.drawString(data_quality, 220, 134);
tft.drawString("湿度", 120, 162);
tft.drawString(data_shidu, 220, 162);
tft.unloadFont();
}
else if (modeUI == 1) //天气预报
{
forecastCon = 0;
JsonObject data = doc["data"];
for (JsonObject data_forecast_item : data["forecast"].as<JsonArray>())
{
if ((forecastCon++) == 0)
{
continue;
}
const char *data_forecast_item_date = data_forecast_item["date"];
const char *data_forecast_item_high = data_forecast_item["high"];
const char *data_forecast_item_low = data_forecast_item["low"];
const char *data_forecast_item_ymd = data_forecast_item["ymd"];
const char *data_forecast_item_sunrise = data_forecast_item["sunrise"];
const char *data_forecast_item_sunset = data_forecast_item["sunset"];
int data_forecast_item_aqi = data_forecast_item["aqi"];
const char *data_forecast_item_fx = data_forecast_item["fx"];
const char *data_forecast_item_fl = data_forecast_item["fl"];
const char *data_forecast_item_type = data_forecast_item["type"];
if (forecastCon == 2 || forecastCon == 4)
{
tft.setTextColor(TFT_WHITE, TFT_GREY);
}
else
{
tft.setTextColor(TFT_WHITE, TFT_BLACK);
}
tft.loadFont("qweather-icons50");
if (strncmp(data_forecast_item_type, "晴", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf101", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "多云", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf102", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "阴", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf105", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "阵雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf10a", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "雷阵雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf10c", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "雨夹雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf124", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "小雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf10f", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "中雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf110", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "大雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf111", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf112", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "大暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf115", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "特大暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf116", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "阵雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf12d", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "小雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf120", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "中雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf121", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "大雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf122", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "暴雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf123", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "小雨-中雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf118", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "中雨-大雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf119", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "大雨-暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf11a", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "暴雨-大暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf11b", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "大暴雨-特大暴雨", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf11c", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "小雪-中雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf128", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "中雪-大雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf129", 5, 4 + (forecastCon - 2) * 80);
else if (strncmp(data_forecast_item_type, "大雪-暴雪", sizeof(data_forecast_item_type)) == 0)
tft.drawString("\uf12a", 5, 4 + (forecastCon - 2) * 80);
tft.unloadFont();
int length;
char *data_forecast_item_fx_char;
length = strlen(data_forecast_item_fx);
data_forecast_item_fx_char = new char[length + 1];
strcpy(data_forecast_item_fx_char, data_forecast_item_fx);
char *data_forecast_item_fl_char;
length = strlen(data_forecast_item_fl);
data_forecast_item_fl_char = new char[length + 1];
strcpy(data_forecast_item_fl_char, data_forecast_item_fl);
tft.loadFont("simhei24");
tft.drawString(data_forecast_item_ymd, 60, 4 + (forecastCon - 2) * 80);
tft.drawString(data_forecast_item_high, 200, 4 + (forecastCon - 2) * 80);
tft.drawString(data_forecast_item_sunrise, 60, 30 + (forecastCon - 2) * 80);
tft.drawString(data_forecast_item_sunset, 60 + tft.textWidth(data_forecast_item_ymd) - tft.textWidth(data_forecast_item_sunset), 30 + (forecastCon - 2) * 80);
tft.drawString(data_forecast_item_low, 200, 30 + (forecastCon - 2) * 80);
tft.drawString(data_forecast_item_type, (60 - tft.textWidth(data_forecast_item_type)) / 2, 56 + (forecastCon - 2) * 80);
tft.drawString(strcat(data_forecast_item_fx_char, data_forecast_item_fl_char), 60, 56 + (forecastCon - 2) * 80);
tft.drawString("AQI:", 200, 56 + (forecastCon - 2) * 80);
tft.drawNumber(data_forecast_item_aqi, 260, 56 + (forecastCon - 2) * 80);
tft.unloadFont();
if (forecastCon >= 4)
{
break;
}
}
}
////////////////////////////////////////////////////////////////////////
}
四、实物展示
初始化页面,显示当前的WiFi模块固件版本号,和WiFi连接情况。
初始化完成后,自动获取一次天气预报,并显示在屏幕上,包括温度湿度,PM2.5,PM10等信息,按一下右边按键,即可立即更新天气,由于天气API是每八小时更新一次,所以无需频繁更新。
按一下中间按键,即可在当天详细天气显示和未来三天天气预报之间进行切换。显示内容包括天气类型,日出日落时间,AQI指数。
五、源文件使用方法
将vlw文件夹内的文件复制到一张小于16g的SD卡中,SD卡需要使用FAT格式,将ino文件中内容进行对应修改。
const char *ssid = ""; //填入SSID
const char *password = ""; //填入Wifi密码
String Citycode = ""; //查询所在区域的城市编码填入
//https://github.com/baichengzhou/weather.api/blob/master/src/main/resources/citycode-2019-08-23.json
使用ArduinoIDE对修改后的源文件进行编译,烧录即可使用。
五、不足与展望
没有使用GUI绘制一个漂亮的界面,界面相对简单,未能将字体文件整合写入Flash中,需要使用SD卡,由于对ArduinoJSON的理解不够,JSON处理的方式效率较低。
后续可以基于LVGL实现一个UI界面,优化操作,将字体整合写入Flash,优化JSON解析效率