成品图
一、实现功能
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(); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具