這一篇,博主將教你們怎麼去實現一個簡易版本的天氣助手。
先來一個博主已經實現功能的圖片,以下:html
本篇須要用到如下知識點:git
往心知天氣平臺請求當地城市天氣狀況,並在OLED上顯示天氣圖標、溫度值以及城市名稱;github
引腳鏈接:json
把如下代碼燒錄進NodeMcu:api
/** * 日期:2019/05/25 * 功能:OLED顯示天氣屏 * 做者:單片機菜鳥 **/ #include <Arduino.h> #include <ESP8266WiFi.h> #include <ArduinoJson.h> #include <U8g2lib.h> #ifdef U8X8_HAVE_HW_SPI #include <SPI.h> #endif #ifdef U8X8_HAVE_HW_I2C #include <Wire.h> #endif #define LED D4 #define DEBUG //是否開啓debug功能 #ifdef DEBUG #define DebugPrintln(message) Serial.println(message) #else #define DebugPrintln(message) #endif #ifdef DEBUG #define DebugPrint(message) Serial.print(message) #else #define DebugPrint(message) #endif #define WEATHER_CODE_DAY_SUN "0" //晴(國內城市白天晴) #define WEATHER_CODE_NIGHT_SUN "1" //晴(國內城市夜晚晴) #define WEATHER_CODE_DAY_SUN1 "2" //晴(國外城市白天晴) #define WEATHER_CODE_NIGHT_SUN2 "3" //晴(國外城市夜晚晴) #define WEATHER_CODE_CLOUDY "4" //多雲 #define WEATHER_CODE_DAY_PARTLY_CLOUDY "5" //白天晴間多雲 #define WEATHER_CODE_NIGHT_PARTLY_CLOUDY "6" //夜晚晴間多雲 #define WEATHER_CODE_DAY_MOSTLY_CLOUDY "7" //白天大部多雲 #define WEATHER_CODE_NIGHT_MOSTLY_CLOUDY "8" //夜晚大部多雲 #define WEATHER_CODE_OVERCAST "9" //陰 #define WEATHER_CODE_SHOWER "10" //陣雨 #define WEATHER_CODE_THUNDERSHOWER "11" //雷陣雨 #define WEATHER_CODE_THUNDERSHOWER_WITH_HAIL "12" //雷陣雨伴有冰雹 #define WEATHER_CODE_LIGHT_RAIN "13" //小雨 #define WEATHER_CODE_MODERATE_RAIN "14" //中雨 #define WEATHER_CODE_HEAVY_RAIN "15" //大雨 #define WEATHER_CODE_STORM "16" //暴雨 #define WEATHER_CODE_HEAVY_STORM "17" //大暴雨 #define WEATHER_CODE_SEVERE_STORM "18" //特大暴雨 #define WEATHER_CODE_ICE_RAIN "19" //凍雨 #define WEATHER_CODE_SLEET "20" //雨夾雪 #define WEATHER_CODE_SNOW_FLURRY "21" //陣雪 #define WEATHER_CODE_LIGHT_SNOW "22" //小雪 #define WEATHER_CODE_MODERATE_SNOW "23" //中雪 #define WEATHER_CODE_HEAVY_SNOW "24" //大雪 #define WEATHER_CODE_SNOW_STORM "25" //暴雪 #define SUN_DAY 0 #define SUN_NIGHT 1 #define SUN_CLOUD 2 #define CLOUD 3 #define RAIN 4 #define THUNDER 5 //聲明方法 bool autoConfig(); void smartConfig(); bool sendRequest(const char* host, const char* cityid, const char* apiKey); bool skipResponseHeaders(); void readReponseContent(char* content, size_t maxSize); void stopConnect(); void clrEsp8266ResponseBuffer(void); bool parseUserData(char* content, struct UserData* userData); void drawWeatherSymbol(u8g2_uint_t x, u8g2_uint_t y, uint8_t symbol); void drawWeather(uint8_t symbol, int degree); const unsigned long BAUD_RATE = 115200;// serial connection speed const unsigned long HTTP_TIMEOUT = 5000; // max respone time from server const size_t MAX_CONTENT_SIZE = 500; // max size of the HTTP response const char* host = "api.seniverse.com"; const char* APIKEY = "wcmquevztdy1jpca"; //API KEY const char* city = "guangzhou"; const char* language = "zh-Hans";//zh-Hans 簡體中文 會顯示亂碼 int flag = HIGH;//默認當前滅燈 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); WiFiClient client; char response[MAX_CONTENT_SIZE]; char endOfHeaders[] = "\r\n\r\n"; long lastTime = 0; // 請求服務間隔 long Delay = 20000; // 咱們要今後網頁中提取的數據的類型 struct UserData { char city[16];//城市名稱 char weather_code[4];//天氣現象code(多雲...) char temp[5];//溫度 }; /** * @Desc 初始化操做 */ void setup() { Serial.begin(BAUD_RATE); pinMode(LED,OUTPUT); digitalWrite(LED, HIGH); WiFi.disconnect(); if(!autoConfig()){ smartConfig(); DebugPrint("Connecting to WiFi");//寫幾句提示,哈哈 while (WiFi.status() != WL_CONNECTED) { //這個函數是wifi鏈接狀態,返回wifi連接狀態 delay(500); DebugPrint("."); } } delay(1000); digitalWrite(LED, LOW); DebugPrintln("IP address: "); DebugPrintln(WiFi.localIP());//WiFi.localIP()返回8266得到的ip地址 lastTime = millis(); u8g2.begin(); u8g2.enableUTF8Print(); //使能軟件看門狗的觸發間隔 ESP.wdtEnable(5000); } /** * @Desc 主函數 */ void loop() { while (!client.connected()){ if (!client.connect(host, 80)){ flag = !flag; digitalWrite(LED, flag); delay(500); //喂狗 ESP.wdtFeed(); } } if(millis()-lastTime>=Delay){ //每間隔20s左右調用一次 lastTime = millis(); if (sendRequest(host, city, APIKEY) && skipResponseHeaders()) { clrEsp8266ResponseBuffer(); readReponseContent(response, sizeof(response)); UserData userData; if (parseUserData(response, &userData)) { showWeather(&userData); } } } //喂狗 ESP.wdtFeed(); } /** * 自動鏈接20s 超過以後自動進入SmartConfig模式 */ bool autoConfig(){ WiFi.mode(WIFI_AP_STA); //設置esp8266 工做模式 WiFi.begin(); delay(2000);//剛啓動模塊的話 延時穩定一下 DebugPrintln("AutoConfiging ......"); for(int index=0;index<10;index++){ int wstatus = WiFi.status(); if (wstatus == WL_CONNECTED){ DebugPrintln("AutoConfig Success"); DebugPrint("SSID:"); DebugPrintln(WiFi.SSID().c_str()); DebugPrint("PSW:"); DebugPrintln(WiFi.psk().c_str()); return true; }else{ DebugPrint("."); delay(500); flag = !flag; digitalWrite(LED, flag); } } DebugPrintln("AutoConfig Faild!"); return false; } /** * 開啓SmartConfig功能 */ void smartConfig() { WiFi.mode(WIFI_STA); delay(1000); DebugPrintln("Wait for Smartconfig"); // 等待配網 WiFi.beginSmartConfig(); while (1){ DebugPrint("."); delay(200); flag = !flag; digitalWrite(LED, flag); if (WiFi.smartConfigDone()){ //smartconfig配置完畢 DebugPrintln("SmartConfig Success"); DebugPrint("SSID:"); DebugPrintln(WiFi.SSID().c_str()); DebugPrint("PSW:"); DebugPrintln(WiFi.psk().c_str()); WiFi.mode(WIFI_AP_STA); //設置esp8266 工做模式 WiFi.setAutoConnect(true); // 設置自動鏈接 break; } } } /** * @發送請求指令 */ bool sendRequest(const char* host, const char* cityid, const char* apiKey) { // We now create a URI for the request //心知天氣 String GetUrl = "/v3/weather/now.json?key="; GetUrl += apiKey; GetUrl += "&location="; GetUrl += city; GetUrl += "&language="; GetUrl += language; // This will send the request to the server client.print(String("GET ") + GetUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"); DebugPrintln("create a request:"); DebugPrintln(String("GET ") + GetUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n"); delay(1000); return true; } /** * @Desc 跳過 HTTP 頭,使咱們在響應正文的開頭 */ bool skipResponseHeaders() { // HTTP headers end with an empty line bool ok = client.find(endOfHeaders); if (!ok) { DebugPrintln("No response or invalid response!"); } return ok; } /** * @Desc 從HTTP服務器響應中讀取正文 */ void readReponseContent(char* content, size_t maxSize) { size_t length = client.readBytes(content, maxSize); delay(100); DebugPrintln("Get the data from Internet!"); content[length] = 0; DebugPrintln(content); DebugPrintln("Read data Over!"); client.flush();//這句代碼須要加上 否則會發現每隔一次client.find會失敗 } // 關閉與HTTP服務器鏈接 void stopConnect() { client.stop(); } void clrEsp8266ResponseBuffer(void){ memset(response, 0, MAX_CONTENT_SIZE); //清空 } bool parseUserData(char* content, struct UserData* userData) { // -- 根據咱們須要解析的數據來計算JSON緩衝區最佳大小 // 若是你使用StaticJsonBuffer時才須要 // const size_t BUFFER_SIZE = 1024; // 在堆棧上分配一個臨時內存池 // StaticJsonBuffer<BUFFER_SIZE> jsonBuffer; // -- 若是堆棧的內存池太大,使用 DynamicJsonBuffer jsonBuffer 代替 DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.parseObject(content); if (!root.success()) { Serial.println("JSON parsing failed!"); return false; } //複製咱們感興趣的字符串 strcpy(userData->city, root["results"][0]["location"]["name"]); strcpy(userData->weather_code, root["results"][0]["now"]["code"]); strcpy(userData->temp, root["results"][0]["now"]["temperature"]); // -- 這不是強制複製,你可使用指針,由於他們是指向「內容」緩衝區內,因此你須要確保 // 當你讀取字符串時它仍在內存中 return true; } /** * 根據天氣接口返回的數據判斷顯示 */ void showWeather(struct UserData* userData){ if(strcmp(userData->weather_code,WEATHER_CODE_DAY_SUN) == 0 || strcmp(userData->weather_code,WEATHER_CODE_DAY_SUN1) == 0){ drawWeather(SUN_DAY,userData->temp,userData->city); }else if(strcmp(userData->weather_code,WEATHER_CODE_NIGHT_SUN) == 0 || strcmp(userData->weather_code,WEATHER_CODE_NIGHT_SUN2) == 0 ){ drawWeather(SUN_NIGHT,userData->temp,userData->city); }else if(strcmp(userData->weather_code,WEATHER_CODE_DAY_PARTLY_CLOUDY) == 0 || strcmp(userData->weather_code,WEATHER_CODE_NIGHT_PARTLY_CLOUDY) == 0 ){ drawWeather(SUN_CLOUD,userData->temp,userData->city); }else if(strcmp(userData->weather_code,WEATHER_CODE_CLOUDY) == 0 || strcmp(userData->weather_code,WEATHER_CODE_DAY_MOSTLY_CLOUDY) == 0 || strcmp(userData->weather_code,WEATHER_CODE_NIGHT_MOSTLY_CLOUDY) == 0 || strcmp(userData->weather_code,WEATHER_CODE_OVERCAST) == 0){ drawWeather(CLOUD,userData->temp,userData->city); }else if(strcmp(userData->weather_code,WEATHER_CODE_SHOWER) == 0 || strcmp(userData->weather_code,WEATHER_CODE_LIGHT_RAIN) == 0 || strcmp(userData->weather_code,WEATHER_CODE_MODERATE_RAIN) == 0 || strcmp(userData->weather_code,WEATHER_CODE_HEAVY_RAIN) == 0 || strcmp(userData->weather_code,WEATHER_CODE_STORM) == 0 || strcmp(userData->weather_code,WEATHER_CODE_HEAVY_STORM) == 0 || strcmp(userData->weather_code,WEATHER_CODE_SEVERE_STORM) == 0){ drawWeather(RAIN,userData->temp,userData->city); }else if(strcmp(userData->weather_code,WEATHER_CODE_THUNDERSHOWER) == 0 || strcmp(userData->weather_code,WEATHER_CODE_THUNDERSHOWER_WITH_HAIL) == 0){ drawWeather(THUNDER,userData->temp,userData->city); }else{ drawWeather(CLOUD,userData->temp,userData->city); } } void drawWeather(uint8_t symbol, char* degree,char* city) { DebugPrintln(city); u8g2.clearBuffer(); // clear the internal memory //繪製天氣符號 drawWeatherSymbol(0, 48, symbol); //繪製溫度 u8g2.setFont(u8g2_font_logisoso32_tf); u8g2.setCursor(48+3, 42); u8g2.print(degree); u8g2.print("°C"); // requires enableUTF8Print() u8g2.setFont(u8g2_font_unifont_t_chinese3); u8g2_uint_t strWidth = u8g2.getUTF8Width(city); u8g2_uint_t displayWidth = u8g2.getDisplayWidth(); u8g2.setCursor(displayWidth - strWidth - 5, 60); u8g2.print(city); u8g2.sendBuffer(); // transfer internal memory to the display } /** * 繪製天氣符號 */ void drawWeatherSymbol(u8g2_uint_t x, u8g2_uint_t y, uint8_t symbol) { // fonts used: // u8g2_font_open_iconic_embedded_6x_t // u8g2_font_open_iconic_weather_6x_t // encoding values, see: https://github.com/olikraus/u8g2/wiki/fntgrpiconic switch(symbol) { case SUN_DAY://太陽 u8g2.setFont(u8g2_font_open_iconic_weather_6x_t); u8g2.drawGlyph(x, y, 69); break; case SUN_NIGHT://太陽 u8g2.setFont(u8g2_font_open_iconic_weather_6x_t); u8g2.drawGlyph(x, y, 66); break; case SUN_CLOUD://晴間多雲 u8g2.setFont(u8g2_font_open_iconic_weather_6x_t); u8g2.drawGlyph(x, y, 65); break; case CLOUD://多雲 u8g2.setFont(u8g2_font_open_iconic_weather_6x_t); u8g2.drawGlyph(x, y, 64); break; case RAIN://下雨 u8g2.setFont(u8g2_font_open_iconic_weather_6x_t); u8g2.drawGlyph(x, y, 67); break; case THUNDER://打雷 u8g2.setFont(u8g2_font_open_iconic_embedded_6x_t); u8g2.drawGlyph(x, y, 67); break; } }
等待幾秒以後,模塊自動進入配網模式,請使用博主的app進行配置,配置成功後就會每隔一段時間去請求天氣接口,而後處理顯示在OLED。服務器
實驗結果:網絡
本篇博文內容簡單,主要是整合了esp8266和oled的應用,爲了優化請求網絡以及顯示時間,讀者可自行加入鬧鐘功能,具體請參考 玩轉 RTC時鐘庫 + DS3231app