成品圖

一、實現功能
1.顯示當地城市
2.顯示當天城市的溫度和濕度
3.顯示當天的風向和風級
4.顯示當天的空氣質量
5.顯示時間
6.顯示動態圖
二、設計與實現
1.分光棱鏡
(1)實物圖

(2)原理
a.偏振分光棱鏡是通過在直角棱鏡的斜面鍍制多層膜結構,然后膠合成一個立方體結構,利用光線以布魯斯特角入射時P偏振光透射率為1而S偏振光透射率小於1的性質,在光線以布魯斯特角多次通過多層膜結構以后,達到使的P偏振分量完全透過,而絕大部分S偏振分量反射(至少90%以上)的一個光學元件。
2.利用ntp服務來獲取時間
(1)ntp服務是什么?
a.NTP服務全稱:network time protocol 網絡時間協議
b.NTP服務為手機,電腦提高精准,可靠的時間服務
c.我們使用的是國內阿里雲服務:ntp.aliyun.com
3.利用http協議來請求中國天氣網並獲取相關天氣
(1)http 就是超文本傳輸協議,全拼是 HyperText Transfer Protocol,它是指從客戶端到
服務器端的請求消息,簡單的講 http 超文本傳輸協議就是定義了瀏覽器向互聯網上的服
務器請求數據的規則以及服務器該以什么樣的格式把數據傳遞給瀏覽器。
首先我們需要創建一個 HttpClient 對象,然后創建一個 HttpGet 對象(這里我們是
Get 請求,所以是 HttpGet 對象),並設置 url 地址,注意此地址也必須是一個 Get 請求
地址,然后使用 httpClient 客戶端對象發送請求,獲取響應。獲取響應的響應碼,如
果響應碼等於 200 時,則證明請求響應成功,httpclient.getString 將響應結果轉化成 String
類型字符串(實際上是一個 json 字符串);使用 JSON 對象工具(fastjson 提供)將 json
串轉化成 json 對象,獲取此 json 對象中 key 為 data 的值,即是我們 url 地址中的請求結
果。因我們 url 地址得到的結果是一個 list,即將其轉化為 list集合輸出。
(2)當我們向中國天氣網進行請求

(3)中國天氣網會向我們發送一個json包數據,而arduino庫中有解析json包的兩種庫

三、代碼分析











四、系統后台演示

完整json包(截取了一部分)

五、完整代碼
后期會上傳到github中
#include <HTTPClient.h> #include "ArduinoJson.h" #include <TimeLib.h> #include <WiFi.h> #include <WiFiUdp.h> #include <TFT_eSPI.h> #include <SPI.h> #include <TJpg_Decoder.h> #include <EEPROM.h> #include "number.h" #include "weathernum.h" #define LCD_BL_PIN 5 #define LCD_BL_PWM_CHANNEL 0 //-----------如手動配網,修改此處""內的信息--------------- char ssid[] = "BJ"; //WIFI名稱 char pswd[] = "88888888"; //WIFI密碼 //---------------------------------------------------- #include "font/ZdyLwFont_20.h" #include "img/misaka.h" #include "img/qr.h" #include "img/temperature.h" #include "img/humidity.h" #include "img/pangzi/i0.h" #include "img/pangzi/i1.h" #include "img/pangzi/i2.h" #include "img/pangzi/i3.h" #include "img/pangzi/i4.h" #include "img/pangzi/i5.h" #include "img/pangzi/i6.h" #include "img/pangzi/i7.h" #include "img/pangzi/i8.h" #include "img/pangzi/i9.h" TFT_eSPI tft = TFT_eSPI(); // 引腳請自行配置tft_espi庫中的 User_Setups文件夾中 setup24_st7789.h文件 TFT_eSprite clk = TFT_eSprite(&tft); /*** Component objects ***/ Number dig; WeatherNum wrat; uint32_t targetTime = 0; uint16_t bgColor = 0x0000;//背景為黑 String cityCode = "101280701"; //天氣城市代碼 int tempnum = 0; //溫度百分比 int huminum = 0; //濕度百分比 int tempcol = 0xffff; int humicol = 0xffff; //NTP服務器 static const char ntpServerName[] = "ntp6.aliyun.com"; const int timeZone = 8; //時區-東八區 WiFiUDP Udp; unsigned int localPort = 8000; // local port to listen for UDP packets float duty = 0;/**/ time_t getNtpTime(); void digitalClockDisplay(); void printDigits(int digits); String num2str(int digits);/**/ void sendNTPpacket(IPAddress &address); //UI bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap) { if ( y >= tft.height() ) return 0; tft.pushImage(x, y, w, h, bitmap); // Return 1 to decode next block return 1; } byte loadNum = 6; void loading(byte delayTime)//繪制進度條 { clk.setColorDepth(8); clk.createSprite(200, 50);//創建窗口 clk.fillSprite(0x0000); //填充率 clk.drawRoundRect(0, 0, 200, 16, 8, 0xFFFF); //空心圓角矩形 clk.fillRoundRect(3, 3, loadNum, 10, 5, 0xFFFF); //實心圓角矩形 clk.setTextDatum(CC_DATUM); //設置文本數據 clk.setTextColor(TFT_GREEN, 0x0000); clk.drawString("Connecting to WiFi", 100, 40, 2); clk.pushSprite(20, 110); //窗口位置 clk.deleteSprite(); loadNum += 1; delay(delayTime); } void humidityWin() { clk.setColorDepth(8); huminum = huminum / 2; clk.createSprite(52, 6); //創建窗口 clk.fillSprite(0x0000); //填充率 clk.drawRoundRect(0, 0, 52, 6, 3, 0xFFFF); //空心圓角矩形 起始位x,y,長度,寬度,圓弧半徑,顏色 clk.fillRoundRect(1, 1, huminum, 4, 2, humicol); //實心圓角矩形 clk.pushSprite(45, 222); //窗口位置 clk.deleteSprite(); } void tempWin() { clk.setColorDepth(8); clk.createSprite(52, 6); //創建窗口 clk.fillSprite(0x0000); //填充率 clk.drawRoundRect(0, 0, 52, 6, 3, 0xFFFF); //空心圓角矩形 起始位x,y,長度,寬度,圓弧半徑,顏色 clk.fillRoundRect(1, 1, tempnum, 4, 2, tempcol); //實心圓角矩形 clk.pushSprite(45, 192); //窗口位置 clk.deleteSprite(); } void SmartConfig(void)//微信配網 { WiFi.mode(WIFI_STA); //設置STA模式 //tft.pushImage(0, 0, 240, 240, qr); TJpgDec.drawJpg(0, 0, qr, sizeof(qr)); //顯示二維碼 Serial.println("\r\nWait for Smartconfig..."); //打印log信息 WiFi.beginSmartConfig(); //開始SmartConfig,等待手機端發出用戶名和密碼 while (1) { Serial.print("."); delay(100); // wait for a second if (WiFi.smartConfigDone())//配網成功,接收到SSID和密碼 { Serial.println("SmartConfig Success"); Serial.printf("SSID:%s\r\n", WiFi.SSID().c_str()); Serial.printf("PSW:%s\r\n", WiFi.psk().c_str()); break; } } loadNum = 194; } void setup() { Serial.begin(115200); ledcSetup(LCD_BL_PWM_CHANNEL, 5000, 8); ledcAttachPin(LCD_BL_PIN, LCD_BL_PWM_CHANNEL); duty = constrain(0.1, 0, 0.1); duty = 1 - duty; ledcWrite(LCD_BL_PWM_CHANNEL, (int)(duty * 255)); tft.begin(); /* TFT init */ tft.setRotation(4); /* mirror 屏幕方向 4為鏡像 6為正向*/ tft.fillScreen(0x0000); tft.setTextColor(TFT_BLACK, bgColor); targetTime = millis() + 1000; Serial.print("正在連接WIFI "); Serial.println(ssid); WiFi.begin(); TJpgDec.setJpgScale(1); TJpgDec.setSwapBytes(true); TJpgDec.setCallback(tft_output); while (WiFi.status() != WL_CONNECTED) { loading(70); if (loadNum >= 194) { SmartConfig(); break; } } delay(10); while (loadNum < 194) //讓動畫走完 { loading(1); } Serial.print("本地IP: "); Serial.println(WiFi.localIP()); Serial.println("啟動UDP"); Udp.begin(localPort); Serial.println("等待同步..."); setSyncProvider(getNtpTime); setSyncInterval(300); TJpgDec.drawJpg(0, 0, misaka, sizeof(misaka)); //顯示logo delay(200); getCityCode(); //獲取城市代碼 tft.fillScreen(TFT_BLACK);//清屏 getCityWeater();//獲取城市天氣 TJpgDec.drawJpg(15, 183, temperature, sizeof(temperature)); //溫度圖標 TJpgDec.drawJpg(15, 213, humidity, sizeof(humidity)); //濕度圖標 } time_t prevDisplay = 0; // 顯示時間 unsigned long weaterTime = 0; void loop() { if (now() != prevDisplay) { prevDisplay = now(); digitalClockDisplay(); } if (millis() - weaterTime > 300000) { //5分鍾更新一次天氣 weaterTime = millis(); getCityWeater(); } scrollBanner(); imgAnim(); } // 發送HTTP請求並且將服務器響應通過串口輸出 void getCityCode() { String URL = "http://wgeo.weather.com.cn/ip/?_=" + String(now()); //創建 HTTPClient 對象 HTTPClient httpClient; //配置請求地址。此處也可以不使用端口號和PATH而單純的 httpClient.begin(URL); //設置請求頭中的User-Agent httpClient.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"); httpClient.addHeader("Referer", "http://www.weather.com.cn/"); //啟動連接並發送HTTP請求 int httpCode = httpClient.GET(); Serial.print("Send GET request to URL: "); Serial.println(URL); //如果服務器響應OK則從服務器獲取響應體信息並通過串口輸出 if (httpCode == HTTP_CODE_OK) { String str = httpClient.getString(); Serial.println(str); int aa = str.indexOf("id="); if (aa > -1) { //cityCode = str.substring(aa+4,aa+4+9).toInt(); cityCode = str.substring(aa + 4, aa + 4 + 9); Serial.println(cityCode); getCityWeater(); } else { Serial.println("獲取城市代碼失敗"); } } else { Serial.println("請求城市代碼錯誤:"); Serial.println(httpCode); } //關閉ESP32與服務器連接 httpClient.end(); } // 獲取城市天氣 void getCityWeater() { //String URL = "http://d1.weather.com.cn/dingzhi/" + cityCode + ".html?_="+String(now());//新 String URL = "http://d1.weather.com.cn/weather_index/" + cityCode + ".html?_=" + String(now()); //原來 //創建 HTTPClient 對象 HTTPClient httpClient; httpClient.begin(URL); //設置請求頭中的User-Agent httpClient.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"); httpClient.addHeader("Referer", "http://www.weather.com.cn/"); //啟動連接並發送HTTP請求 int httpCode = httpClient.GET(); Serial.println("正在獲取天氣數據"); Serial.println(URL); //如果服務器響應OK則從服務器獲取響應體信息並通過串口輸出 if (httpCode == HTTP_CODE_OK) { String str = httpClient.getString(); int indexStart = str.indexOf("weatherinfo\":"); int indexEnd = str.indexOf("};var alarmDZ"); String jsonCityDZ = str.substring(indexStart + 13, indexEnd); Serial.println(jsonCityDZ); indexStart = str.indexOf("dataSK ="); indexEnd = str.indexOf(";var dataZS"); String jsonDataSK = str.substring(indexStart + 8, indexEnd); Serial.println(jsonDataSK); indexStart = str.indexOf("\"f\":["); indexEnd = str.indexOf(",{\"fa"); String jsonFC = str.substring(indexStart + 5, indexEnd); Serial.println(jsonFC); Serial.println("--------"); Serial.println(str); Serial.println("--------"); weatherData(&jsonCityDZ, &jsonDataSK, &jsonFC); Serial.println("獲取成功"); } else { Serial.println("請求城市天氣錯誤:"); Serial.print(httpCode); } //關閉ESP32與服務器連接 httpClient.end(); } String scrollText[7]; //int scrollTextWidth = 0; //天氣信息寫到屏幕上 void weatherData(String *cityDZ, String *dataSK, String *dataFC) { //解析第一段JSON DynamicJsonDocument doc(1024); deserializeJson(doc, *dataSK); JsonObject sk = doc.as<JsonObject>(); //TFT_eSprite clkb = TFT_eSprite(&tft); /***繪制相關文字***/ clk.setColorDepth(8); clk.loadFont(ZdyLwFont_20); //溫度 clk.createSprite(58, 24); clk.fillSprite(bgColor); clk.setTextDatum(CC_DATUM); clk.setTextColor(TFT_WHITE, bgColor); clk.drawString(sk["temp"].as<String>() + "℃", 28, 13); clk.pushSprite(100, 184); clk.deleteSprite(); tempnum = sk["temp"].as<int>(); tempnum = tempnum + 10; if (tempnum < 10) tempcol = 0x00FF; else if (tempnum < 28) tempcol = 0x0AFF; else if (tempnum < 34) tempcol = 0x0F0F; else if (tempnum < 41) tempcol = 0xFF0F; else if (tempnum < 49) tempcol = 0xF00F; else { tempcol = 0xF00F; tempnum = 50; } tempWin(); //濕度 clk.createSprite(58, 24); clk.fillSprite(bgColor); clk.setTextDatum(CC_DATUM); clk.setTextColor(TFT_WHITE, bgColor); clk.drawString(sk["SD"].as<String>(), 28, 13); //clk.drawString("100%",28,13); clk.pushSprite(100, 214); clk.deleteSprite(); //String A = sk["SD"].as<String>(); huminum = atoi((sk["SD"].as<String>()).substring(0, 2).c_str()); if (huminum > 90) humicol = 0x00FF; else if (huminum > 70) humicol = 0x0AFF; else if (huminum > 40) humicol = 0x0F0F; else if (huminum > 20) humicol = 0xFF0F; else humicol = 0xF00F; humidityWin(); //城市名稱 clk.createSprite(94, 30); clk.fillSprite(bgColor); clk.setTextDatum(CC_DATUM); clk.setTextColor(TFT_WHITE, bgColor); clk.drawString(sk["cityname"].as<String>(), 44, 16); clk.pushSprite(15, 15); clk.deleteSprite(); //PM2.5空氣指數 uint16_t pm25BgColor = tft.color565(156, 202, 127); //優 String aqiTxt = "優"; int pm25V = sk["aqi"]; if (pm25V > 200) { pm25BgColor = tft.color565(136, 11, 32); //重度 aqiTxt = "重度"; } else if (pm25V > 150) { pm25BgColor = tft.color565(186, 55, 121); //中度 aqiTxt = "中度"; } else if (pm25V > 100) { pm25BgColor = tft.color565(242, 159, 57); //輕 aqiTxt = "輕度"; } else if (pm25V > 50) { pm25BgColor = tft.color565(247, 219, 100); //良 aqiTxt = "良"; } clk.createSprite(56, 24); clk.fillSprite(bgColor); clk.fillRoundRect(0, 0, 50, 24, 4, pm25BgColor); clk.setTextDatum(CC_DATUM); clk.setTextColor(0x0000); clk.drawString(aqiTxt, 25, 13); clk.pushSprite(104, 18); clk.deleteSprite(); scrollText[0] = "實時天氣 " + sk["weather"].as<String>(); scrollText[1] = "空氣質量 " + aqiTxt; scrollText[2] = "風向 " + sk["WD"].as<String>() + sk["WS"].as<String>(); //scrollText[6] = atoi((sk["weathercode"].as<String>()).substring(1,3).c_str()) ; //天氣圖標 wrat.printfweather(170, 15, atoi((sk["weathercode"].as<String>()).substring(1, 3).c_str())); //左上角滾動字幕 //解析第二段JSON deserializeJson(doc, *cityDZ); JsonObject dz = doc.as<JsonObject>(); //Serial.println(sk["ws"].as<String>()); //橫向滾動方式 //String aa = "今日天氣:" + dz["weather"].as<String>() + ",溫度:最低" + dz["tempn"].as<String>() + ",最高" + dz["temp"].as<String>() + " 空氣質量:" + aqiTxt + ",風向:" + dz["wd"].as<String>() + dz["ws"].as<String>(); //scrollTextWidth = clk.textWidth(scrollText); //Serial.println(aa); scrollText[3] = "今日" + dz["weather"].as<String>(); deserializeJson(doc, *dataFC); JsonObject fc = doc.as<JsonObject>(); scrollText[4] = "最低溫度" + fc["fd"].as<String>() + "℃"; scrollText[5] = "最高溫度" + fc["fc"].as<String>() + "℃"; //Serial.println(scrollText[0]); clk.unloadFont(); } int currentIndex = 0; int prevTime = 0; TFT_eSprite clkb = TFT_eSprite(&tft); void scrollBanner() { if (millis() - prevTime > 2333) { //2.333秒切換一次 if (scrollText[currentIndex]) { clkb.setColorDepth(8); clkb.loadFont(ZdyLwFont_20); for (int pos = 24; pos > 0 ; pos--) { scrollTxt(pos); } clkb.deleteSprite(); clkb.unloadFont(); if (currentIndex >= 5) { currentIndex = 0; //回第一個 } else { currentIndex += 1; //准備切換到下一個 } //Serial.println(currentIndex); } prevTime = millis(); } } void scrollTxt(int pos) { clkb.createSprite(150, 30); clkb.fillSprite(bgColor); clkb.setTextWrap(false); clkb.setTextDatum(CC_DATUM); clkb.setTextColor(TFT_WHITE, bgColor); clkb.drawString(scrollText[currentIndex], 74, pos + 16); clkb.pushSprite(10, 45); } void imgAnim() { int x = 160, y = 160, dt = 29; TJpgDec.drawJpg(x, y, i0, sizeof(i0)); delay(dt); TJpgDec.drawJpg(x, y, i1, sizeof(i1)); delay(dt); TJpgDec.drawJpg(x, y, i2, sizeof(i2)); delay(dt); TJpgDec.drawJpg(x, y, i3, sizeof(i3)); delay(dt); TJpgDec.drawJpg(x, y, i4, sizeof(i4)); delay(dt); TJpgDec.drawJpg(x, y, i5, sizeof(i5)); delay(dt); TJpgDec.drawJpg(x, y, i6, sizeof(i6)); delay(dt); TJpgDec.drawJpg(x, y, i7, sizeof(i7)); delay(dt); TJpgDec.drawJpg(x, y, i8, sizeof(i8)); delay(dt); TJpgDec.drawJpg(x, y, i9, sizeof(i9)); delay(dt); } void digitalClockDisplay() { int timey = 82; dig.printfO3660(20, timey, hour() / 10); dig.printfO3660(60, timey, hour() % 10); dig.printfO3660(101, timey, minute() / 10); dig.printfO3660(141, timey, minute() % 10); dig.printfW1830(182, timey + 30, second() / 10); dig.printfW1830(202, timey + 30, second() % 10); /***日期****/ clk.setColorDepth(8); clk.loadFont(ZdyLwFont_20); //星期 clk.createSprite(58, 30); clk.fillSprite(bgColor); clk.setTextDatum(CC_DATUM); clk.setTextColor(TFT_WHITE, bgColor); clk.drawString(week(), 29, 16); clk.pushSprite(102, 150); clk.deleteSprite(); //月日 clk.createSprite(95, 30); clk.fillSprite(bgColor); clk.setTextDatum(CC_DATUM); clk.setTextColor(TFT_WHITE, bgColor); clk.drawString(monthDay(), 49, 16); clk.pushSprite(5, 150); clk.deleteSprite(); clk.unloadFont(); /***日期****/ } //星期 String week() { String wk[7] = {"日", "一", "二", "三", "四", "五", "六"}; String s = "周" + wk[weekday() - 1]; return s; } //月日 String monthDay() { String s = String(month()); s = s + "月" + day() + "日"; return s; } /*-------- NTP code ----------*/ const int NTP_PACKET_SIZE = 48; // NTP時間在消息的前48字節中 byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets time_t getNtpTime() { IPAddress ntpServerIP; // NTP server's ip address while (Udp.parsePacket() > 0) ; // discard any previously received packets Serial.println("Transmit NTP Request"); // get a random server from the pool WiFi.hostByName(ntpServerName, ntpServerIP); Serial.print(ntpServerName); Serial.print(": "); Serial.println(ntpServerIP); sendNTPpacket(ntpServerIP); uint32_t beginWait = millis(); while (millis() - beginWait < 1500) { int size = Udp.parsePacket(); if (size >= NTP_PACKET_SIZE) { Serial.println("Receive NTP Response"); Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer unsigned long secsSince1900; // convert four bytes starting at location 40 to a long integer secsSince1900 = (unsigned long)packetBuffer[40] << 24; secsSince1900 |= (unsigned long)packetBuffer[41] << 16; secsSince1900 |= (unsigned long)packetBuffer[42] << 8; secsSince1900 |= (unsigned long)packetBuffer[43]; //Serial.println(secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR); return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; } } Serial.println("No NTP Response :-("); return 0; // 無法獲取時間時返回0 } // 向NTP服務器發送請求 //https://blog.csdn.net/qq_41868901/article/details/104841528參考代碼 //https://blog.csdn.net/qq_41868901/article/details/104225816 void sendNTPpacket(IPAddress &address) { // set all bytes in the buffer to 0 memset(packetBuffer, 0, NTP_PACKET_SIZE); // Initialize values needed to form NTP request // (see URL above for details on the packets) packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion packetBuffer[12] = 49; packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; // all NTP fields have been given values, now // you can send a packet requesting a timestamp: Udp.beginPacket(address, 123); //NTP requests are to port 123 Udp.write(packetBuffer, NTP_PACKET_SIZE); Udp.endPacket(); }
