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

news2024/11/25 23:30:52
一、简介

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

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

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

相关文章

【吊打面试官系列-微服务面试题】单片,SOA 和微服务架构有什么区别?

大家好&#xff0c;我是锋哥。今天分享关于【单片&#xff0c;SOA 和微服务架构有什么区别&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 单片&#xff0c;SOA 和微服务架构有什么区别&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 …

基于SSM框架学籍管理系统的设计与实现

我 | 在这里 ⭐ 全栈开发攻城狮、全网10W粉丝、2022博客之星后端领域Top1、专家博主。 &#x1f393;擅长 指导毕设 | 论文指导 | 系统开发 | 毕业答辩 | 系统讲解等。已指导60位同学顺利毕业 ✈️个人公众号&#xff1a;乡下小哥编程。回复 Java全套视频教程 或 前端全套视频教…

DataStore存储数据+加上加密

如果没有使用SP来存储数据&#xff0c;而是用datastore的话 但是datastore存储是个文件&#xff0c;所以我们需要再加密。 先展示没有加密的存储方式&#xff0c;然后再结合上一节的加密&#xff0c;再将存储的数据进行加密 使用datastore存储数据 添加依赖库 implementat…

心血之作!独家原创改进班翠鸟优化算法IPKO!2024年新算法!效果极佳!

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ ​历经数月&#xff0c;今天又给小伙伴们带了一期独…

IRP学习理解

IRP是Windows内核中的一种非常重要的数据结构。上层应用程序与底层驱动程序通信时&#xff0c;应用程序会发出I/O请求&#xff0c;操作系统将相应的I/O请求转换成相应的IRP&#xff0c;不同的IRP会根据类型被分派到不同的派遣例程中进行处理。 irp相当于R3下的消息&#xff0c…

【文档智能】文本文字识别、公式识别、表格文字识别核心算法及思路及实践-DBNet、CRNN、TrOCR

前言 OCR技术作为文档智能解析链路中的核心组件之一&#xff0c;贯穿整个技术链路&#xff0c;包括&#xff1a;文字识别、表格文字识别、公式识别&#xff0c;参看下面这张架构图&#xff1a; 前期介绍了很多关于文档智能解析相关核心技术及思路&#xff0c;本着连载的目的&a…

特斯拉“We, Robot“发布会:Robotaxi亮相,马斯克兑现承诺

在加利福尼亚州伯班克的华纳兄弟电影制片厂&#xff0c;特斯拉举办了以"We&#xff0c; Robot"为主题的发布会&#xff0c;这场活动吸引了全球科技爱好者的目光。特斯拉在此次发布会上展示了其在自动驾驶领域的最新突破&#xff0c;特别是公司CEO埃隆马斯克多年来承诺…

麒麟桌面版v10 SP1以docker方式安装达梦数据库

安装docker 0.切换root用户&#xff08;可以不切换&#xff0c;但要注意权限问题&#xff0c;我是用root&#xff09; ymym-pc:~/桌面$ whoami ym ymym-pc:~/桌面$ sudo -i rootym-pc:~# whoami root rootym-pc:~# 1.查看系统版本 [rootlocalhost opt]# cat /etc/os-release…

AI知识库如何提升服装电商的运营效率

随着人工智能技术的飞速发展&#xff0c;AI知识库在服装电商领域的应用日益广泛。AI知识库作为一个集成了海量数据、通过高级算法进行智能分析和处理的信息系统&#xff0c;正在深刻改变服装电商的运营模式和效率。本文将详细阐述AI知识库在商品信息管理、库存管理、订单处理等…

C语言题目练习4

这一篇博客继续在算法题的海洋里面遨游~ 链表的中间结点 链表的中间结点&#xff1a; https://leetcode.cn/problems/middle-of-the-linked-list/description/ 这个题我们可以怎么办呢&#xff1f;这里依然提供两个思路 思路1 既然是中间结点我们是不是可以直接第一次循环求…

MySQL从入门到跑路

SQL语言 SQL&#xff08;Structured Query Language&#xff0c;结构化查询语言&#xff09;是用于管理和操作关系数据库的一种标准编程语言。 SQL分类&#xff1a; DDL(Data Definition Language)&#xff1a;数据定义语言&#xff0c;用于操作数据库、表、字段&#xff0c…

天气API接口调用

天气API接口&#xff1a; 天气API接口是一种用于获取实时或预报天气信息的应用程序编程接口&#xff08;API&#xff09;。开发者可以使用这种接口在他们的应用程序或网站上集成天气查询功能&#xff0c;比如查询某个地区的当前温度、降水量、风速等数据。 通常&#xff0c;你…

Django ORM 进行基础 CRUD 操作(创建、读取、更新、删除)

Django ORM 进行基础 CRUD 操作&#xff08;创建、读取、更新、删除&#xff09; Django 是一个流行的 Python web 框架&#xff0c;提供了很多实用的功能来帮助开发者快速构建 web 应用程序。Django 的 ORM&#xff08;Object-Relational Mapping&#xff09;是其强大的数据库…

Java_ EE (网络编程)

网络编程基本概念: 计算机网络计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备&#xff0c;通过通信线路连接起来&#xff0c;在网络操作系统&#xff0c;网络管理软件及网络通信协议的管理和协调下&#xff0c;实现资源共享和信息传递的计算机系统。从其…

【C++算法】双指针

目录 一、快乐数&#xff1a; 二、有效三角形的个数&#xff1a; 三、盛最多水的容器&#xff1a; 四、复写0&#xff1a; 五、三数之和&#xff1a; 总结&#xff1a; 一、快乐数&#xff1a; 题目出处&#xff1a; 202. 快乐数 - 力扣&#xff08;LeetCode&#xff09…

spring task的使用场景

spring task 简介 spring task 是spring自带的任务调度框架按照约定的时间执行某个方法的工具&#xff0c;类似于闹钟 应用场景 cron表达式 周和日两者必定有一个是问号 简单案例

基于java的企业车辆管理系统设计与实现(论文+源码)-kaic

摘 要 随着经济的日益增长,车辆作为最重要的交通工具,在企事业单位中得以普及,单位的车辆数目已经远远不止简单的几辆,与此同时就产生了车辆资源的合理分配使用问题。现有的车辆管理系统存在着不足之处&#xff0c;例如系统不够稳定&#xff0c;功能不够全面。因此&#xff0c…

Python基础之转义字符

字符串转义 转义是指在字符前加一个 \ \ n 则表示原来的字符n不代表字符n 赋予了一个新的含义 变成了一个换行符 print(wu\nzj\njing) \n 会解释为一个换行符 s "wu\"zj" 双引号是用来创建一个字符串的效果 加上\后就变成了一个字符双引号 它就是一个…

如何解决 Open /etc/postfix/main.cf: Permission denied ?

最近我的 Postfix 邮件系统无法发送电子邮件&#xff0c;报错内容&#xff1a;Open /etc/postfix/main.cf: Permission denied 经过一番调查&#xff0c;我能够解决这个问题。 日志文件中发现的错误如下&#xff1a; Jun 27 12:51:02 tecadmin postfix/postfix-script[11764]…

AI大模型开发架构设计(11)——AI 大模型与提示词工程助力职场典型案例场景实战

文章目录 AI 大模型与提示词工程助力职场典型案例场景实战1 AI大模型全局架构实战剖析AI大模型常见术语AI 大模型全局架构 2 Prompt Engineering 整体应用场景剖析Prompt 提示词的三个层次Prompt 提示词的经典模板如何让 Prompt 提示词做的更好?如何让 Prompt 提示词自动优化改…