一、简介
准备用基于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"
#endif
const char* ssid = STASSID;
const char* password = STAPSK;
#ifndef APSSID
#define APSSID "ESPap"
#define APPSK "nodemcu-esp8266"
#endif
const 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_Protocol
const 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 address
while (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 数据包...");
// 将缓冲区中的所有字节设置为0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// 初始化形成NTP请求所需的值
// 有关数据包的详细信息,请参阅上面的URL
packetBuffer[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请求发送到端口123
udp.write(packetBuffer, NTP_PACKET_SIZE);
udp.endPacket();
}
// WiFi列表保存ssid使用的数组
String* arrssid;
// WiFi列表保存信号强度使用的数组
int32_t* rssi;
// 对比出该wifi网络加密类型并返回相应的String值
/*
5:ENC_TYPE_WEP : WEP
2:ENC_TYPE_TKIP: WPA / PSK
4:ENC_TYPE_CCMP: WPA2 / PSK
7:ENC_TYPE_NONE: OPEN
8: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 到 900
strvar += "<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);
}
// 连接成功后打印消息和本地IP
Serial.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());
else
Serial.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/GetWeather
https://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"
}
*/