一、简介
准备用基于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"}
*/