竹壳天气时钟(二)第二阶段任务已完成

一、简介

准备用基于esp8266的nodemcu开发板做一个天气时钟。
一步一步记录代码编写过程。

  竹壳天气时钟

  Bamboo shell weather clock

  使用基于esp8266的NodeMCU制作。

  计划用竹子做最后成品的外壳,所以才有了这个名称。

  第一阶段任务:

  1.开启混合模式,使用webserver进行WiFi设置;

  2.如果连接WiFi12秒后还没有连接成功就返回连接失败;

  3.web页面可以输入ssid和密码连接WiFi,如果已经连接就显示获取到的IP地址;

  4.web页面可以手动扫描WiFi网络并显示出扫描到的网络列表;

  5.WiFi网络列表页面可以输入密码并点击连接按钮连接网络;

  6.连接成功后web页面显示连接成功的提示并显示获取到的IP地址。

  2024年9月19日开始---2024年10月10日完成

  第二阶段任务:

  1.连接网络时间服务器获取当前时间;

  2.把当前时间输出到串口;

控制台串口输出截图
控制台串口输出截图

  3.通过网络获取当前天气信息;

  4.把当前天气信息输出到串口。

  2024年10月10日开始---2024年10月13日完成

网页访问截图
网页访问截图

  第三阶段任务:

  1.尝试用旧手机上的显示器;

  2.旧手机显示器如果用不了就买一个2.4英寸的tft显示器;

  3.把时间和天气显示到显示器。

  未开始

  第四阶段任务:

  1.增加温湿度模块并更新显示数据;

  2.尝试添加喇叭做报时和报天气功能;

  3.尝试添加语音控制模块;

  4.尝试添加日历提醒功能;

  5.尝试添加农历。

  未开始

  第五阶段任务:

  1.增加电池和充电模块并连接好电路方便安装外壳;

  2.用竹子做好外壳。

  未开始

二、我目前在使用的云服务器推荐

学Linux不搞个云服务器始终感觉不爽!
要稳定性、安全性、不差钱的可以使用阿里、腾讯等大厂的云服务器。
本人穷屌丝一枚,所以我用的是免费的“三丰云”,同时提供"免费虚拟主机"和“免费云服务器”产品,有兴趣的可以试一下。
“三丰云”我已经用了一段时间,感觉还是很不错的,速度快也很稳定。
三丰云 https://www.sanfengyun.com 链接。
大家可以点击前往查看是否需要。

三、代码

期间还做了一个串口控制命令,这个小功能后面都不会用到,所以没有写进任务列表。

/*竹壳天气时钟Bamboo shell weather clock使用基于esp8266的NodeMCU制作。计划用竹子做最后成品的外壳,所以才有了这个名称。第一阶段任务:1.开启混合模式,使用webserver进行WiFi设置;2.如果连接WiFi12秒后还没有连接成功就返回连接失败;3.web页面可以输入ssid和密码连接WiFi,如果已经连接就显示获取到的IP地址;4.web页面可以手动扫描WiFi网络并显示出扫描到的网络列表;5.WiFi网络列表页面可以输入密码并点击连接按钮连接网络;6.连接成功后web页面显示连接成功的提示并显示获取到的IP地址。2024年9月19日开始---2024年10月10日完成第二阶段任务:1.连接网络时间服务器获取当前时间;2.把当前时间输出到串口;3.通过网络获取当前天气信息;4.把当前天气信息输出到串口。2024年10月10日开始---2024年10月13日完成第三阶段任务:1.尝试用旧手机上的显示器;2.旧手机显示器如果用不了就买一个2.4英寸的tft显示器;3.把时间和天气显示到显示器。未开始第四阶段任务:1.增加温湿度模块并更新显示数据;2.尝试添加喇叭做报时和报天气功能;3.尝试添加语音控制模块;4.尝试添加日历提醒功能;5.尝试添加农历。未开始第五阶段任务:1.增加电池和充电模块并连接好电路方便安装外壳;2.用竹子做好外壳。未开始
*/
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <WiFiUdp.h>
#include <TimeLib.h>
#include <DNSServer.h>
#include <ArduinoJson.h>
#include <WiFiClientSecure.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>#include "certs.h"#ifndef STASSID
#define STASSID "ssid"
#define STAPSK "password"
#endifconst char* ssid = STASSID;
const char* password = STAPSK;#ifndef APSSID
#define APSSID "ESPap"
#define APPSK "nodemcu-esp8266"
#endifconst char* apssid = APSSID;
const char* appassword = APPSK;const char* host = "api.oioweb.cn";  // 天气API的域名
const uint16_t port = 443;           // 天气API的端口X509List cert(cert_ZeroSSL_ECC_Domain_Secure_Site_CA);  // https请求需要用到的证书time_t wthr_update_time = 0;                             // 更新时间
String wthr_update_local_time = "";                      // 本地时间
bool wthr_status = false;                                // 更新状态
const char* wthr_result_city_City;                       // 当前城市
const char* wthr_result_city_Carrier;                    // 网络运营商
const char* wthr_result_city_UserIp;                     // 公网IP地址
const char* wthr_result_condition_day_weather;           // 白天天气
String wthr_result_condition_day_wind_direction;         // 白天风向
const char* wthr_result_condition_day_wind_power;        // 白天风力
const char* wthr_result_condition_max_degree;            // 最高温度
const char* wthr_result_condition_min_degree;            // 最低温度
const char* wthr_result_condition_night_weather;         // 晚上天气
const char* wthr_result_condition_night_wind_direction;  // 晚上风向
const char* wthr_result_condition_night_wind_power;      // 晚上风力
int wthr_result_condition_aqi_aqi;                       // 空气指数
const char* wthr_result_condition_aqi_aqi_name;          // 空气质量
const char* wthr_result_condition_aqi_co;                // 一氧化碳
const char* wthr_result_condition_aqi_no2;               // 氮氧化物
const char* wthr_result_condition_aqi_o3;                // 臭氧
const char* wthr_result_condition_aqi_pm10;              // PM10
const char* wthr_result_condition_aqi_pm2_5;             // PM2.5
const char* wthr_result_condition_aqi_so2;               // 二氧化硫// 网络校时的相关配置
// 关于NTP时间服务器及其通信所需消息的更多信息,
// 看http://en.wikipedia.org/wiki/Network_Time_Protocolconst char* ntpServerName = "ntp1.aliyun.com";  //NTP服务器,使用阿里云
int timeZone = 8;                               //时区设置,采用北京时间unsigned int localPort = 2390;  // 本地端口,用于监听UDP数据包const int NTP_PACKET_SIZE = 48;      // NTP时间在消息的前48个字节里
byte packetBuffer[NTP_PACKET_SIZE];  // 输入输出包的缓冲区WiFiUDP udp;  // UDP实例,用于发送和接收UDP数据包void digitalClockDisplay() {// 打印时间到串口Serial.println(getTimeNow("str"));
}
// 返回中文星期
String getDayOfWeek(int dayOfWeek) {switch (dayOfWeek) {case 2:return "星期一";break;case 3:return "星期二";break;case 4:return "星期三";break;case 5:return "星期四";break;case 6:return "星期五";break;case 7:return "星期六";break;case 1:return "星期日";break;default:return "未知";break;}
}
String getDigits(int digits) {if (digits < 10) return "0" + String(digits);else return String(digits);
}String getTimeNow(String var) {String gtn = "";if (var == "str") {gtn = String(year()) + "年" + getDigits(month()) + "月" + getDigits(day()) + "日 " + getDigits(hour()) + "点" + getDigits(minute()) + "分" + getDigits(second()) + "秒 " + getDayOfWeek(weekday());}if (var == "now") {gtn = String(now() - 8 * 3600);}return gtn;
}/*-------- NTP code ----------*/
time_t getNtpTime() {IPAddress ntpServerIP;  // NTP server's ip addresswhile (udp.parsePacket() > 0);  // 丢弃之前接收到的任何数据包Serial.println("发送NTP请求");// 获取服务器IP地址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("收到 NTP 响应");// 读取收到的前48个字节的UDP数据包到缓冲区udp.read(packetBuffer, NTP_PACKET_SIZE);unsigned long secsSince1900;// 将从第40个字节开始的四个字节转换为一个无符号长整型数字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("时间同步成功 \n 10分钟后再次同步");setSyncInterval(600);// Unix时间是从1970.1.1开始,NTP时间是从1900.1.1开始。// 把获取到的NTP时间减掉这70年的秒数,然后加上时区间差异的秒数。// 就把NTP的UTC时间转换成了Unix的本地时间。return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;}}Serial.println("没有收到 NTP 响应 :-( \n 6秒后再次同步");if (WiFi.status() != WL_CONNECTED) setSyncInterval(3600);else setSyncInterval(6);return 0;  // 如果没有获取到时间就返回 0
}// 向给定地址的时间服务器发送NTP请求
void sendNTPpacket(IPAddress& address) {Serial.println("发送 NTP 数据包...");// 将缓冲区中的所有字节设置为0memset(packetBuffer, 0, NTP_PACKET_SIZE);// 初始化形成NTP请求所需的值// 有关数据包的详细信息,请参阅上面的URLpacketBuffer[0] = 0b11100011;  // LI,版本,模式packetBuffer[1] = 0;           // 层次, 或时钟类型packetBuffer[2] = 6;           // 轮询间隔packetBuffer[3] = 0xEC;        // 对等时钟精度// 根延迟和根色散为零的8个字节packetBuffer[12] = 49;packetBuffer[13] = 0x4E;packetBuffer[14] = 49;packetBuffer[15] = 52;// 现在,所有NTP字段都已给定值// 您可以发送一个请求时间戳的数据包:udp.beginPacket(address, 123);  // NTP请求发送到端口123udp.write(packetBuffer, NTP_PACKET_SIZE);udp.endPacket();
}// WiFi列表保存ssid使用的数组
String* arrssid;
// WiFi列表保存信号强度使用的数组
int32_t* rssi;// 对比出该wifi网络加密类型并返回相应的String值
/*5:ENC_TYPE_WEP : WEP2:ENC_TYPE_TKIP: WPA / PSK4:ENC_TYPE_CCMP: WPA2 / PSK7:ENC_TYPE_NONE: OPEN8:ENC_TYPE_AUTO: WPA / WPA2 / PSK
*/
String transEncryptionType(uint8_t encryptionType) {switch (int(encryptionType)) {case (ENC_TYPE_WEP):return "WEP";case (ENC_TYPE_TKIP):return "WPA_PSK";case (ENC_TYPE_CCMP):return "WPA2_PSK";case (ENC_TYPE_NONE):return "OPEN";case (ENC_TYPE_AUTO):return "WPA_WPA2_PSK";default:return (String(int(encryptionType)));}
}// WiFi扫描函数
void wsf() {uint8_t encryptionType;uint8_t* bssid;int32_t channel;bool hidden;int scanResult;Serial.println(F("开始WiFi扫描..."));scanResult = WiFi.scanNetworks(/*async=*/false, /*hidden=*/true);arrssid = new String[scanResult];rssi = new int32_t[scanResult];if (scanResult == 0) {Serial.println(F("找不到WiFi网络"));} else if (scanResult > 0) {Serial.printf(PSTR("扫描出的网络数量: %d\n"), scanResult);// 打印扫描出的WiFi列表for (int8_t i = 0; i < scanResult; i++) {WiFi.getNetworkInfo(i, arrssid[i], encryptionType, rssi[i], bssid, channel, hidden);if (hidden) arrssid[i] = "隐藏";// 获取扩展信息const bss_info* bssInfo = WiFi.getScanInfoByIndex(i);String phyMode;const char* wps = "";if (bssInfo) {phyMode.reserve(12);phyMode = F("802.11");String slash;if (bssInfo->phy_11b) {phyMode += 'b';slash = '/';}if (bssInfo->phy_11g) {phyMode += slash + 'g';slash = '/';}if (bssInfo->phy_11n) {phyMode += slash + 'n';}if (bssInfo->wps) {wps = PSTR("WPS");}}Serial.printf(PSTR("  %02d: [CH %02d] [%02X:%02X:%02X:%02X:%02X:%02X] %ddBm %-12s %c %-11s %3S %s\n"), i, channel, bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], rssi[i], transEncryptionType(encryptionType), hidden ? 'H' : 'V', phyMode.c_str(), wps, arrssid[i].c_str());yield();}} else {Serial.printf(PSTR("WiFi扫描出现错误 %d"), scanResult);}
}ESP8266WebServer server(80);// HTML代码压缩网址:https://www.sojson.com/jshtml.html
String str = "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\"content=\"width=device-width, initial-scale=1.0\"><meta http-equiv=\"X-UA-Compatible\"content=\"ie=edge\">";/****************************************************** 函数名称:handleRoot()* 函数说明:客户端请求回调函数* 参数说明:无
******************************************************/
void handleRoot() {String strvar = "";strvar += "<title>Yale的ESP8266--配置WiFi</title></head><body>";strvar += "<form name=\"my\"><div align=\"right\">";strvar += "<input type=\"button\"value=\"扫描\"onclick=\"wscan()\"></div><br><div align=\"center\">";// 或者 <a href=\"/HandleScan\"class=\"button\">扫描</a><style>.button{display:inline-block;padding:10px 20px;background-color:#f2f2f2;text-decoration:none;border:1px solid#ddd;margin:10px 0;font-size:15px;cursor:pointer}.button:hover{background-color:#ddd}</style>strvar += "WiFi名称:<input type=\"text\"name=\"s\"placeholder=\"请输入您WiFi的名称\"id=\"aa\"><br><br>";strvar += "WiFi密码:<input type=\"text\"name=\"p\"placeholder=\"请输入您WiFi的密码\"id=\"bb\"><br><br></div>";strvar += "<div align=\"center\"><input type=\"button\"align=\"center\"value=\"连接\"onclick=\"wifi()\"></div>";strvar += "</form><br><br>";if (WiFi.status() == WL_CONNECTED) {IPAddress ipaddr = WiFi.localIP();strvar += "WiFi 已连接成功<br>IP 地址:" + ipaddr.toString();if (timeStatus() != timeNotSet) {// font-weight 属性值:normal=400 | bold=700 | 100 到 900strvar += "<p id=\"timeLabel\"style=\"color:blue;font-weight:900;\">" + getTimeNow("now") + "</p>";if (wthr_status) {String const_char_str = wthr_result_city_City;String wthr_str = "<ul><li>当前城市:" + const_char_str;const_char_str = wthr_result_city_Carrier;wthr_str += " 网络运营商:" + const_char_str;const_char_str = wthr_result_city_UserIp;wthr_str += "</li><li>当前公网IP:" + const_char_str;const_char_str = wthr_result_condition_day_weather;wthr_str += "</li><li>今天天气情况:</li><li><ul><li>白天:" + const_char_str;const_char_str = wthr_result_condition_day_wind_direction;wthr_str += " " + const_char_str;const_char_str = wthr_result_condition_day_wind_power;wthr_str += " " + const_char_str + "级";const_char_str = wthr_result_condition_night_weather;wthr_str += "</li><li>晚上:" + const_char_str;const_char_str = wthr_result_condition_night_wind_direction;wthr_str += " " + const_char_str;const_char_str = wthr_result_condition_night_wind_power;wthr_str += " " + const_char_str + "级";const_char_str = wthr_result_condition_max_degree;wthr_str += "</li><li>温度:最高" + const_char_str + "℃ 最低";const_char_str = wthr_result_condition_min_degree;wthr_str += const_char_str + "℃";const_char_str = wthr_result_condition_aqi_aqi;wthr_str += "</li></ul><li>空气指数情况:</li><li><ul><li>空气指数:" + const_char_str;const_char_str = wthr_result_condition_aqi_aqi_name;wthr_str += " " + const_char_str;const_char_str = wthr_result_condition_aqi_co;wthr_str += "</li><li>CO:" + const_char_str;const_char_str = wthr_result_condition_aqi_no2;wthr_str += " CO₂:" + const_char_str;wthr_str += " SO₂:" + const_char_str;const_char_str = wthr_result_condition_aqi_o3;wthr_str += " O₃:" + const_char_str;const_char_str = wthr_result_condition_aqi_pm10;wthr_str += "</li><li>PM10:" + const_char_str;const_char_str = wthr_result_condition_aqi_pm2_5;wthr_str += " PM2.5:" + const_char_str;const_char_str = wthr_result_condition_aqi_so2;wthr_str += "</li></ul><li>更新时间:" + wthr_update_local_time + "</li></ul>";strvar += wthr_str;}strvar += "<script language=\"javascript\">";strvar += "function stringToUnsignedLong(str){const bigIntValue=BigInt(str);return bigIntValue.toString();}";strvar += "var dateElement=document.getElementById(\"timeLabel\");var nowUL=stringToUnsignedLong(dateElement.innerHTML);";strvar += "function secondsToDate(seconds){const date=new Date(0);date.setSeconds(seconds);return date;}";strvar += "function updateDate(){nowUL++;var now=secondsToDate(nowUL);var dayOfWeek=now.getDay();var dayOfWeekName=[\"星期日\",\"星期一\",\"星期二\",\"星期三\",\"星期四\",\"星期五\",\"星期六\"];";strvar += "dateElement.innerHTML=\"NodeMCU当前系统时间:<br>\"+now.toLocaleString()+\" \"+dayOfWeekName[dayOfWeek];}";strvar += "window.onload=function(){setInterval(updateDate, 1000);};";strvar += "</script>";}}strvar += "<ul><li>上次复位原因:" + ESP.getResetReason() + "</li><li>闪存真实大小:" + ESP.getFlashChipRealSize() + "</li><li>闪存大小:" + ESP.getFlashChipSize() + "</li><li>固件大小:" + ESP.getSketchSize() + "</li><li>固件可用空间:" + ESP.getFreeSketchSpace() + "</li><li>闪存运行频率:" + ESP.getFlashChipSpeed() + "</li><li>CPU运行频率:" + ESP.getCpuFreqMHz() + "</li><li>可用内存:" + ESP.getFreeHeap() + "</li></ul>";strvar += "<script language=\"javascript\">";strvar += "function wscan(){window.location.href=\"/HandleScan\";}";strvar += "function wifi(){var ssid=my.s.value;var password=bb.value;";//strvar += "var xmlhttp=new XMLHttpRequest();xmlhttp.open(\"GET\",\"/HandleVal?ssid=\"+ssid+\"&password=\"+password,true);xmlhttp.send()}";strvar += "window.location.href=\"/HandleVal?ssid=\"+ssid+\"&password=\"+password;}";strvar += "</script></body></html>";server.send(200, "text/html", str + strvar);
}/****************************************************** 函数名称:HandleVal()* 函数说明:对客户端请求返回值处理* 参数说明:无
******************************************************/
void HandleVal() {String wifis = server.arg("ssid");      //从JavaScript发送的数据中找ssid的值String wifip = server.arg("password");  //从JavaScript发送的数据中找password的值// first = false;if (connectwifi(wifis, wifip)) {IPAddress ipaddr = WiFi.localIP();server.send(200, "text/html", str + "<title>Yale的ESP8266--连接WiFi</title></head><body>WiFi 连接成功<br>IP 地址:" + ipaddr.toString() + "<br><a href=\"/\">返回首页</a></body></html>");setSyncInterval(6);} else {server.send(200, "text/html", str + "<title>Yale的ESP8266--连接WiFi</title></head><body>WiFi 连接失败 <br><a href=\"/\">返回首页</a></body></html>");}
}/****************************************************** 函数名称:HandleScan()* 函数说明:对客户端请求返回值处理* 参数说明:无
******************************************************/
void HandleScan() {Serial.println("执行HandleScan方法");wsf();int wifinum = 0;while (arrssid[wifinum] != "\0") {wifinum++;}String strvar = "";strvar += "<title>Yale的ESP8266--WiFi扫描</title></head><body>";strvar += "<form name=\"my\"><div align=\"right\">";strvar += "<input type=\"button\"value=\"重新扫描\"onclick=\"wscan()\"></div><br>";strvar += "一共扫描到" + String(wifinum) + "个网络。<br>";strvar += "<table width=\"100%\"border=\"1\">";for (int i = 0; i < wifinum; i++) {strvar += "<tr><td align=\"center\"colspan=3>第" + String(i + 1) + "个网络</td>";strvar += "<tr><td align=\"center\">WiFi名称</td><td colspan=2><input type=\"text\"name=\"s" + String(i) + "\"placeholder=\"" + arrssid[i] + "\"value=\"" + arrssid[i] + "\"id=\"ips" + String(i) + "\"></td></tr>";strvar += "<tr><td align=\"center\">信号强度</td><td colspan=2>" + String(rssi[i]) + "dBm</td>";strvar += "<tr><td align=\"center\">可用操作</td><td><input type=\"password\"name=\"p" + String(i) + "\"placeholder=\"请输入您WiFi的密码\"id=\"ipp" + String(i) + "\"></td>";strvar += "<td align=\"center\"><input type=\"button\"value=\"连接\"onclick=\"cw" + String(i) + "()\"></td></tr>";}strvar += "</table>";strvar += "</form><script language=\"javascript\">";strvar += "function wscan(){window.location.href=\"/HandleScan\";}";for (int i = 0; i < wifinum; i++) {strvar += "function cw" + String(i) + "(){var ssid=ips" + String(i) + ".value;var password=ipp" + String(i) + ".value;";strvar += "window.location.href=\"/HandleVal?ssid=\"+ssid+\"&password=\"+password;}";}strvar += "</script></body></html>";server.send(200, "text/html", str + strvar);
}/****************************************************** 函数名称:handleNotFound()* 函数说明:响应失败函数* 参数说明:无
******************************************************/
void handleNotFound() {digitalWrite(LED_BUILTIN, 0);String message = "找不到文件\n\n";message += "URI: ";message += server.uri();message += "\nMethod: ";message += (server.method() == HTTP_GET) ? "GET" : "POST";message += "\nArguments: ";message += server.args();message += "\n";for (uint8_t i = 0; i < server.args(); i++) {message += " " + server.argName(i) + ": " + server.arg(i) + "\n";}server.send(404, "text/plain", message);digitalWrite(LED_BUILTIN, 1);
}void configap() {Serial.println("开始配置AP...");// 设置WiFi模式为混合模式WiFi.mode(WIFI_AP_STA);WiFi.softAP(apssid, appassword);IPAddress myIP = WiFi.softAPIP();Serial.print("AP IP 地址: ");Serial.println(myIP);server.on("/", handleRoot);server.on("/HandleVal", HTTP_GET, HandleVal);server.on("/HandleScan", HTTP_GET, HandleScan);server.onNotFound(handleNotFound);  //请求失败回调函数server.begin();Serial.println("HTTP 服务已启动");if (!connectwifi(ssid, password)) {Serial.println();Serial.println("WiFi 连接失败");}
}bool connectwifi(String s, String p) {Serial.println();Serial.print("连接到:");Serial.println(s);// 连接WiFi//if (first) WiFi.mode(WIFI_STA);WiFi.begin(s, p);// 连接过程中打印出读秒的数字int i = 1;Serial.print("连接WiFi已用时间:");while (WiFi.status() != WL_CONNECTED) {Serial.print(String(i) + "秒 ");i++;if (i > 12) {return false;}delay(1000);}// 连接成功后打印消息和本地IPSerial.println();Serial.println("WiFi 连接成功");Serial.print("IP 地址: ");Serial.println(WiFi.localIP());return true;
}void setup() {// put your setup code here, to run once:pinMode(LED_BUILTIN, OUTPUT);digitalWrite(LED_BUILTIN, HIGH);// 打印信息Serial.begin(115200);delay(9000);Serial.println();Serial.println("nodeMCU开始第一次配置");configap();Serial.println("启动 UDP");udp.begin(localPort);Serial.print("本地端口: ");Serial.println(udp.localPort());Serial.println("等待时间同步");setSyncProvider(getNtpTime);  // 设置时间同步函数// setSyncInterval(600);         // 设置每10分钟更新一次if (WiFi.status() != WL_CONNECTED) setSyncInterval(3600);// 定时器中断,每10秒更新一次时间// TimerLib.setInterval_s(timed_function, 10);// NTPClient和uTimerLib有冲突,分别单独使用都没问题,一起使用就会导致ESP8266崩溃重启randomSeed(millis());
}bool mf = true;void loop() {// put your main code here, to run repeatedly:if (mf) {Serial.println();Serial.println("nodeMCU进入主循环");mf = false;}if (Serial.available() > 0) {int temp = Serial.read();Serial.println(temp);}server.handleClient();digitalWrite(LED_BUILTIN, HIGH);delay(300);digitalWrite(LED_BUILTIN, LOW);if (timeStatus() != timeNotSet) {//每隔2个小时更新一次天气if ((!(wthr_status) | (((now()) - wthr_update_time) > 7200)) & (WiFi.status() == WL_CONNECTED)) {httpsRequestJson();}}
}// 缓冲区大小
#define BUFFER_SIZE 5// 缓冲区和计数器
char buffer[BUFFER_SIZE];
int bufferIndex = 0;// 设置串口接收中断处理函数
void serialEvent() {while (Serial.available() > 0) {char inChar = Serial.read();if (inChar == '\n') {            // 如果是换行符,表示数据结束buffer[bufferIndex] = '\0';    // 添加字符串结束标记String data = String(buffer);  // 将缓冲区内容转换为字符串// 处理接收到的字符串数据so(data);bufferIndex = 0;  // 重置缓冲区索引} else if (bufferIndex <= 3) {buffer[bufferIndex++] = inChar;  // 否则添加到缓冲区}}
}int findIndexInArray(String value) {int index = 7;String srry[index];srry[0] = "help";srry[1] = "time";srry[2] = "ip";srry[3] = "ap";srry[4] = "wthr";srry[5] = "h";srry[6] = "heap";for (int i = 0; i < index; i++) {if (srry[i] == value) {return i;  // 找到值,返回下标}}return -1;  // 没有找到值,返回-1
}void so(String data) {switch (findIndexInArray(data)) {case 0:Serial.println();Serial.println("help:显示命令列表");Serial.println("ap:显示AP的IP地址");Serial.println("time:显示当前系统时间");Serial.println("wthr:显示获取到的天气信息");Serial.println("ip:显示从接入点获取到的IP地址");Serial.println("h:立即更新天气");Serial.println("heap:显示NodeMCU状态");break;case 1:Serial.println();Serial.println(getTimeNow("str"));break;case 2:Serial.println();if (WiFi.status() == WL_CONNECTED)Serial.println("从接入点获取到的IP地址是:" + WiFi.localIP().toString());elseSerial.println("WiFi未连接成功,请用WiFi连接" + String(APSSID) + "接入点,密码:" + String(APPSK) + "然后通过浏览器访问" + WiFi.softAPIP().toString() + "配置WiFi");break;case 3:Serial.println();Serial.println(String(APSSID) + "的IP地址是:" + WiFi.softAPIP().toString());break;case 4:Serial.println();Serial.println("开发中");break;case 5:httpsRequestJson();break;case 6:Serial.print("上次复位原因:");Serial.println(ESP.getResetReason());Serial.print("闪存真实大小:");Serial.println(ESP.getFlashChipRealSize());Serial.print("闪存大小:");Serial.println(ESP.getFlashChipSize());Serial.print("固件大小:");Serial.println(ESP.getSketchSize());Serial.print("固件可用空间:");Serial.println(ESP.getFreeSketchSpace());Serial.print("闪存运行频率:");Serial.println(ESP.getFlashChipSpeed());Serial.print("CPU运行频率:");Serial.println(ESP.getCpuFreqMHz());Serial.print("可用内存:");Serial.println(ESP.getFreeHeap());break;default:Serial.println();Serial.println("\"" + data + "\" 未知命令");Serial.println("请键入 \"help\" 查询命令列表");break;}
}void httpsRequestJson() {WiFiClientSecure client;Serial.print("Connecting to ");Serial.println(host);client.setX509Time(now());client.setTrustAnchors(&cert);client.setFingerprint(fingerprint___oioweb_cn);if (!client.connect(host, port)) {Serial.println("Connection failed");return;}String url = "/api/weather/GetWeather";Serial.print("Requesting URL: ");Serial.println(url);client.print(String("GET ") + url + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: BuildFailureDetectorESP8266\r\n" + "Connection: close\r\n\r\n");Serial.println("Request sent");String line = client.readStringUntil('\n');Serial.print("status_response: ");Serial.println(line);// 使用find跳过HTTP响应头if (client.find("\r\n\r\n")) {Serial.println("Found Header End. Start Parsing.");}wthr_update_local_time = getTimeNow("str");wthr_update_time = now();DynamicJsonDocument doc(256);deserializeJson(doc, client);int json_code = doc["code"].as<int>();if (json_code != 200) {Serial.println("更新失败。code: " + String(json_code));wthr_status = false;return;} else Serial.println("更新成功。code: " + String(json_code) + "\n开始解析数据...");wthr_status = true;JsonObject json_result = doc["result"];JsonObject json_result_city = json_result["city"];wthr_result_city_City = json_result_city["City"];wthr_result_city_Carrier = json_result_city["Carrier"];wthr_result_city_UserIp = json_result_city["UserIp"];JsonObject json_result_condition = json_result["condition"];wthr_result_condition_day_weather = json_result_condition["day_weather"];const char* jrcdwd = json_result_condition["day_wind_direction"];wthr_result_condition_day_wind_direction = jrcdwd;wthr_result_condition_day_wind_power = json_result_condition["day_wind_power"];wthr_result_condition_max_degree = json_result_condition["max_degree"];wthr_result_condition_min_degree = json_result_condition["min_degree"];wthr_result_condition_night_weather = json_result_condition["night_weather"];wthr_result_condition_night_wind_direction = json_result_condition["night_wind_direction"];wthr_result_condition_night_wind_power = json_result_condition["night_wind_power"];JsonObject json_result_condition_aqi = json_result_condition["aqi"];wthr_result_condition_aqi_aqi = json_result_condition_aqi["aqi"].as<int>();wthr_result_condition_aqi_aqi_name = json_result_condition_aqi["aqi_name"];wthr_result_condition_aqi_co = json_result_condition_aqi["co"];wthr_result_condition_aqi_no2 = json_result_condition_aqi["no2"];wthr_result_condition_aqi_o3 = json_result_condition_aqi["o3"];wthr_result_condition_aqi_pm10 = json_result_condition_aqi["pm10"];wthr_result_condition_aqi_pm2_5 = json_result_condition_aqi["pm2.5"];wthr_result_condition_aqi_so2 = json_result_condition_aqi["so2"];String const_char_str = wthr_result_city_City;String wthr_str = "现在是北京时间:" + getTimeNow("str") + "\n当前城市:" + const_char_str;const_char_str = wthr_result_city_Carrier;wthr_str += " 网络运营商:" + const_char_str;const_char_str = wthr_result_city_UserIp;wthr_str += "\n当前公网IP:" + const_char_str;const_char_str = wthr_result_condition_day_weather;wthr_str += "\n今天天气情况:\n  白天:" + const_char_str;const_char_str = wthr_result_condition_day_wind_direction;wthr_str += " " + const_char_str;const_char_str = wthr_result_condition_day_wind_power;wthr_str += " " + const_char_str + "级";const_char_str = wthr_result_condition_night_weather;wthr_str += "\n  晚上:" + const_char_str;const_char_str = wthr_result_condition_night_wind_direction;wthr_str += " " + const_char_str;const_char_str = wthr_result_condition_night_wind_power;wthr_str += " " + const_char_str + "级";const_char_str = wthr_result_condition_max_degree;wthr_str += "\n  温度:最高" + const_char_str + "℃ / 最低";const_char_str = wthr_result_condition_min_degree;wthr_str += const_char_str + "℃";const_char_str = wthr_result_condition_aqi_aqi;wthr_str += "\n空气指数情况:\n  空气指数:" + const_char_str;const_char_str = wthr_result_condition_aqi_aqi_name;wthr_str += " " + const_char_str;const_char_str = wthr_result_condition_aqi_co;wthr_str += "\n  CO:" + const_char_str;const_char_str = wthr_result_condition_aqi_no2;wthr_str += " CO₂:" + const_char_str;wthr_str += " SO₂:" + const_char_str;const_char_str = wthr_result_condition_aqi_o3;wthr_str += " O₃:" + const_char_str;const_char_str = wthr_result_condition_aqi_pm10;wthr_str += "\n  PM10:" + const_char_str;const_char_str = wthr_result_condition_aqi_pm2_5;wthr_str += "  PM2.5:" + const_char_str;const_char_str = wthr_result_condition_aqi_so2;wthr_str += "\n更新时间:" + wthr_update_local_time;Serial.println(wthr_str);Serial.println("Closing connection");
}/*OpenWeather 提供分钟级实时预报,拥有一定数量的免费调用次数;AccuWeather 凭借其悠久历史和专业度在业界享有较高声誉;和风天气API 对非商业用户提供无限制的免费服务,并且数据较为全面;心知天气API 对个人开发者友好,且数据更新较快;高德天气API 适合对实时天气和简单预报有需求的开发者。https://api.oioweb.cn/api/weather/GetWeatherhttps://api.oioweb.cn/doc/weather/GetWeather{"code": 200,"result": {"city": {"Country": "中国","Province": "四川","City": "成都","Carrier": "移动","UserIp": "117.176.185.90"},"condition": {"day_weather": "小雨","day_weather_short": "小雨","day_wind_direction": "南风","day_wind_power": "1","max_degree": "20","min_degree": "15","night_weather": "阴","night_weather_short": "阴","night_wind_direction": "南风","night_wind_power": "1","aqi": {"aqi": 22,"aqi_level": 1,"aqi_name": "优","co": "6","no2": "10","o3": "15","pm10": "21","pm2.5": "21","so2": "1","update_time": "202410112300"}},"forecast": []},"msg": "success"}
*/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/56112.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025推荐选题|基于MVC的农业病虫害防治平台的设计与实现

作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参与学生毕业答辩指导&#xff0c;…

Golang | Leetcode Golang题解之第477题汉明距离总和

题目&#xff1a; 题解&#xff1a; func totalHammingDistance(nums []int) (ans int) {n : len(nums)for i : 0; i < 30; i {c : 0for _, val : range nums {c val >> i & 1}ans c * (n - c)}return }

tkinter库的应用小示例:文本编辑器

tkinter库的应用小示例&#xff1a;文本编辑器 要 求&#xff1a; 创建一个文本编辑器&#xff0c;功能包括&#xff0c;创建、打开、编辑、保存文件。一个Button小组件&#xff0c;命名为btn_open,用于打开要编辑的文件&#xff0c;一个Button小组件&#xff0c;命名为btn_s…

【Ubuntu】“Linux版PhotoShop”绘图软件的安装和汉化

【Ubuntu】“Linux版PhotoShop”绘图软件的安装和汉化 零、前言 最近换了Linux系统&#xff0c;但是写教程做PPT的时候还是得用到绘图软件&#xff0c;上网一查&#xff0c;总结对比之后发现Krita比较好用&#xff0c;故此讲解一下如何安装和汉化Krita。 壹、安装 安装很简…

Unity中搜索不到XR Interaction Toolkit包解决方法

问题&#xff1a; 针对Unity版本2020.3在中PackageManager可能搜素不到XR Interaction Toolkit包 在Package Manager中未显示XR Interaction Toolkit包 解决方法&#xff1a; Package manager左上角&#xff0c;点加号&#xff0c;选择 Add package from git URL..&#xff0c;…

Mysql(2)—SQL语法详解(通俗易懂)

一、关于SQL 1.1 简介 SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;是一种用于管理关系型数据库的标准编程语言。它主要用于数据的查询、插入、更新和删除等操作。SQL最初在1970年代由IBM的研究人员开发&#xff0c;旨在处理关系数据模型…

Pytorch基础:设置随机种子

相关阅读 Pytorch基础https://blog.csdn.net/weixin_45791458/category_12457644.html?spm1001.2014.3001.5482 有时候&#xff0c;如果需要代码在多个运行中具有可重复性&#xff0c;可以通过以下方式来设置随机种子&#xff1a; import torch import numpy as np import r…

qt+opengl 实现纹理贴图,平移旋转,绘制三角形,方形

1 首先qt 已经封装了opengl&#xff0c;那么我们就可以直接用了&#xff0c;这里面有三个函数需要继承 virtual void initializeGL() override; virtual void resizeGL(int w,int h) override; virtual void paintGL() override; 这三个函数是实现opengl的重要函数。 2 我们…

E: Unable to locate package:无法定位包的完美解决方法 ️

博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客&#x1f466;&#x1f3fb; 《java 面试题大全》 《java 专栏》 &#x1f369;惟余辈才疏学浅&#xff0c;临摹之作或有不妥之处&#xff0c;还请读者海涵指正。☕&#x1f36d; 《MYSQL从入门到精通》数据库是开…

LabVIEW提高开发效率技巧----点阵图(XY Graph)

在LabVIEW开发中&#xff0c;点阵图&#xff08;XY Graph&#xff09; 是一种强大的工具&#xff0c;尤其适用于需要实时展示大量数据的场景。通过使用点阵图&#xff0c;开发人员能够将实时数据可视化&#xff0c;帮助用户更直观地分析数据变化。 1. 点阵图的优势 点阵图&…

树莓派应用--AI项目实战篇来啦-17.YOLOv8目标检测-安全帽检测

1. YOLOv8介绍 YOLOv8是Ultralytics公司2023年推出的Yolo系列目标检测算法&#xff0c;可以用于图像分类、物体检测和实例分割等任务。YOLOv8作为YOLO系列算法的最新成员&#xff0c;在损失函数、Anchor机制、样本分配策略等方面进行了全面优化和创新。这些改进不仅提高了模型的…

长芯微LSPGD1系列带气嘴DIP8封装集成表压传感器完全替代松下ADP51B62替代ADP51B62,成本更低!

描述 LSPGD1是长芯微针对家电医疗等市场推出的经过校准的表压传感器系列产品。该系列产品采用高性能信号调理芯片对MEMS压阻芯体输出进行温度和压力的校准和补偿&#xff0c;保证性能和可靠性的同时对封装进行了集成&#xff0c;易于使用。LSPGD1系列集成压力传感器可选量程为…

Java多线程之死锁(死锁产生条件、手写简单死锁程序、破坏死锁)(面试常有)

目录 一、死锁。 &#xff08;1&#xff09;实际生活"死锁"情景。 &#xff08;2&#xff09;程序中举例。 &#xff08;3&#xff09;死锁产生必要的条件。 <1> 互斥使用。 <2> 不可抢占。 <3> 请求和保持。 <4> 循环等待。 &#xff08;4&…

iOS 14 自定义画中画悬浮窗 Custom AVPictureInPictureController 实现方案

iOS 14&#xff0c;基于 AVPictureInPictureController&#xff0c;实现自定义画中画&#xff0c;涵盖所有功能与难点。 市面上的各种悬浮钟和提词器的原理都是基于此。 Demo源码在文末。 使用 iOS 画中画的要求&#xff1a; 真机&#xff0c;不能使用模拟器&#xff1b;iO…

starrocks-删除表字段

1、背景 之前做了个大宽表&#xff0c;将近100个字段&#xff0c;但是后来发现很多字段在实际生产上都没有用到&#xff0c;并且随着数据量的增加&#xff0c;给集群的存储以及消费任务的解析带来了比较大的压力。所以决定对字段做删除处理。 当前的表是使用routine load任务从…

hadoop全分布式搭建(三台虚拟机,一个主节点,两个从节点)

根据尚硅谷哔哩哔哩视频搭建&#xff1a;bilibili.com/video/BV1Qp4y1n7EN/ 安装虚拟机教程可参考&#xff1a;VMware虚拟机 安装 Centos7(linux)&#xff08;新手超详细教程&#xff09;_vmware安装centos7教程-CSDN博客 集群配置如下&#xff1a; 一、先配置一台虚拟机hadoo…

【计算机网络 - 基础问题】每日 3 题(三十八)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

【华为HCIP实战课程七】OSPF邻居关系排错MTU问题,网络工程师

一、MTU MUT默认1500,最大传输单元,一致性检测 [R3-GigabitEthernet0/0/1]mtu 1503//更改R3的MTU为1503 查看R3和SW1之间的OSPF邻居关系正常: 默认华为设备没有开启MTU一致性检测! [R3-GigabitEthernet0/0/1]ospf mtu-enable //手动开启MTU检测 [SW1-Vlanif30]ospf mtu…

PCL点云处理之求法向量

求法向量干什么&#xff1f;将点渲染成面 1、一个点垂直于一个曲线的切线叫法线 2、在点云中取一块区域&#xff0c;用最小二乘将区域中的点云拟合成一个面&#xff08;贴合在曲面上的一个切面&#xff09;在相近的区域计算出n个这样的面&#xff0c;用这个面求出法向量&#…

第十五届蓝桥杯C++B组省赛

文章目录 1.握手问题解题思路1&#xff08;组合数学&#xff09;解题思路2&#xff08;暴力枚举&#xff09; 2.小球反弹做题思路 3.好数算法思路&#xff08;暴力解法&#xff09;---不会超时 4.R格式算法思路 5.宝石组合算法思路---唯一分解定理 6.数字接龙算法思路----DFS 7…