esp8266 文件系统 web交互 天气获取 mqtt 串口通信
main.cpp:
#include <FS.h> // 文件系统库
#include <ArduinoJson.h> // Arduino JSON解析库
#include <PubSubClient.h> // MQTT客户端库
#include <ESP8266WebServer.h> // ESP8266 Web服务器库
#include <WiFiManager.h> // WiFi管理库
#include <ESP8266WiFi.h> // ESP8266 WiFi库
#include <WiFiClient.h> // WiFi客户端库
#include <Arduino.h> // Arduino核心库
#include <html.h> // HTML相关库
#include <ArduinoHttpClient.h> // HTTP客户端库
#include <fsrots.h> // 文件系统相关库
FSRots fsA; // 文件系统相关实例
WiFiManager wifi; // WiFi管理实例
int ihead = 0; // 串口接收缓冲区
int wlank = true; // 控制串口读取标志位
char head[256] = ""; // 串口接收头部缓冲区
String head_str = ""; // 串口接收头部缓冲区
//============================================//
// 函数声明
void wlan(); // 换页函数
void tjs(String name); // 串口屏输出函数
void HTTPA(); // Web服务器处理函数
void cls(); // 清除串口缓存函数
void testMQtt(); // 尝试连接MQTT函数
void mqttmsgg(char *topic, byte *payload, unsigned int length); // MQTT消息回调函数
void setup(); // 初始化函数
void loop(); // 主循环函数
void handleFileUpload(); // 文件上传处理函数
void SerialHead(); // 串口头处理函数
//============================================//
ESP8266WebServer server(80); // 创建Web服务器,监听端口80
WiFiClient ClientA; // WiFi客户端实例
// PubSubClient ClientB(ClientA); // MQTT客户端实例,使用ClientA作为网络客户端
String subget; // 存储MQTT订阅主题的字符串
void clientaa();
// 测试HTTP请求函数
// Web服务器处理函数
void HTTPA()
{
String text = server.uri(); // 获取请求的URI
if (text.endsWith("/"))
{ // 如果请求的URI以"/"结束
if (SPIFFS.exists("/index.html"))
{ // 如果文件系统中有主页
File inhtml = SPIFFS.open("/index.html", "r"); // 打开主页文件
server.streamFile(inhtml, "text/html"); // 通过Web服务器流式传输文件
inhtml.close(); // 关闭文件
}
else
{ // 如果没有主页,则使用内置主页
server.send(200, "text/html", html);
}
}
// 其他请求处理逻辑...
}
// 清除串口缓存函数
void cls()
{
while (Serial.available())
{ // 当串口有数据可读时
Serial.read(); // 读取并丢弃,以清空缓存
}
}
// 尝试连接MQTT函数
void testMQtt()
{
if (SPIFFS.exists("/config.txt"))
{ // 如果配置文件存在
File respi = SPIFFS.open("/config.txt", "r"); // 打开配置文件
String cc;
for (int i = 0; i < respi.size(); i++)
{ // 读取文件内容
cc += (char)respi.read();
}
Serial.println(cc);
respi.close();
// 提取配置信息
int indeu = cc.indexOf(";");
int indeu1 = cc.indexOf(";", indeu + 1);
int indeu2 = cc.indexOf(";", indeu1 + 1);
String pat1 = cc.substring(0, indeu);
String pat2 = cc.substring(indeu + 1, indeu1);
String pat3 = cc.substring(indeu1 + 1, indeu2);
String pat4 = pat1 + pat2 + pat3;
Serial.print(pat4.c_str());
}
else
{
Serial.println("没有找到配置文件");
}
}
// MQTT消息回调函数
void mqttmsgg(char *topic, byte *payload, unsigned int length)
{
String msmgg = (String)topic; // 转换主题为字符串
for (int i = 0; i < length; i++)
{ // 处理payload
msmgg += (char)payload[i];
}
Serial.println(msmgg.c_str()); // 输出MQTT消息
}
// 初始化函数
void setup()
{
Serial.begin(9600); // 初始化串口通信
wifi.autoConnect("ESP-8266AUTO"); // 自动连接WiFi
fsA.fsrots(); // 文件系统初始化
if (WiFi.status() != WL_CONNECTED)
{
delay(2500); // 延时2.5秒
}
else
{
if (WiFi.status() != WL_CONNECTED)
{ // 如果WiFi未连接
}
}
if (WiFi.status() == WL_CONNECTED)
{ // 如果WiFi已连接
server.begin(); // 开始Web服务器
server.onNotFound(HTTPA); // 设置HTTPA为未找到页面的处理函数
}
}
// 获取天气信息函数==================================//
void clientaa()
{
HttpClient clienta(ClientA, "weather.cma.cn", 80);
clienta.kUserAgent = "User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
clienta.get("/api/now/53698");
String buf = clienta.responseBody();
Serial.println(buf);
// json数据解析
DynamicJsonDocument doc(1024); // 动态分配内存用于存储JSON数据
deserializeJson(doc, buf); // 将JSON数据反序列化为doc
JsonObject locationobj = doc["data"]["location"].as<JsonObject>(); // 获取JSON对象 data中location
JsonObject nowobj = doc["data"]["now"].as<JsonObject>(); // 获取JSON对象data中now
JsonObject lastUpdateobj = doc["data"]["lastUpdate"]; // 获取JSON对象data中lastUpdate
String strjson = locationobj["path"]; // 获取JSON对象data中location中的path属性
int index1 = strjson.indexOf(","); // 查找第一个逗号的位置
int index2 = strjson.indexOf(",", index1 + 1); // 查找第二个逗号的位置
String part1 = strjson.substring(0, index1); // 截取第一个部分
String part2 = strjson.substring(index1 + 1, index2); // 截取第二个部分
String part3 = strjson.substring(index2 + 1); // 截取第三个部分
Serial.println("国家" + part1); // 输出第一个部分
Serial.println("城市" + part2); // 输出第二个部分
Serial.println("区县" + part3); // 输出第三个部分
Serial.println("温度" + String(nowobj["temperature"]) + "℃");
Serial.println("湿度" + String(nowobj["humidity"]) + "%");
Serial.println("气压" + String(nowobj["pressure"]) + "hPa");
// 获取时间
Serial.println("时间:" + String(lastUpdateobj["lastUpdate"]));
}
//====================================串口数据读取
void SerialHead()
{
int Sh = 0;
head_str.reserve(ihead); // 分配内存
for (Sh; Sh < ihead; Sh++)
{
head_str += String(head[Sh]);
}
if (head_str.endsWith("/天气"))
{
clientaa();
}
if (head_str.endsWith("/MQTT"))
{
testMQtt();
}
cls(); // 清空缓存
ihead = 0;
head_str = "";
wlank = true;
}
// 主循环函数
void loop()
{
if (WiFi.status() == WL_CONNECTED)
{ // 如果WiFi已连接
server.handleClient(); // 处理Web服务器的客户端请求
}
if(wlank==true){
//进入读取模式
if (Serial.available())
{ wlank = false; // 退出循环
while (Serial.available() > 0 && ihead < 255)
{
head[ihead] = Serial.read();
ihead++;
}
head[ihead] = '\0'; // 字符串结束符
if (ihead >= 256)
{
wlank = false; // 退出循环
}
SerialHead(); // 处理数据
}}
delay(200); // 等待200毫秒
}
FSROTS库:
#ifndef FSROTS_H
#define FSROTS_H
#include <FS.h> // 文件系统库
#include <ArduinoJson.h>//json库
#include <ESP8266WebServer.h>//web服务器
/**
* @brief 功能描述:文件系统
* FSRots 自定义名 ; 实例化 fsrots
* 自定义名.fsrots();文件系统初始化
* 自定义名.fsread(文件名);读取文件 返回内容
* 自定义名.getContentType(文件名);获取文件类型 返回内容
* 自定义名.sFlash(); // 获取系统信息并以JSON格式返回
* 自定义名.sDir(); // 获取目录信息并以JSON格式返回
* 自定义名.fsServer(服务器实例); // 处理服务器请求,发送文件或404响应
*
*/
class FSRots
{
private:
FSInfo csd; // 存储文件系统信息的结构体,如总空间、已用空间等
public:
// 初始化文件系统
void fsrots()
{
SPIFFS.begin(); // 开始SPIFFS文件系统,确保文件系统可用
}
// 读取指定名称的文件内容
String fsread(const String &fsname)
{
// 检查文件是否存在
if (SPIFFS.exists(fsname))
{
String fsdata; // 用于存储文件内容的字符串
File file = SPIFFS.open(fsname, "r"); // 以只读模式打开文件
// 循环读取文件内容
while (file.available())
{
fsdata += (char)file.read(); // 读取文件的每一个字节并追加到fsdata中
}
file.close(); // 关闭文件
return fsdata; // 返回文件内容
}
else
{
return "文件不存在"; // 如果文件不存在,返回错误信息
}
}
/**---------------------分割线-------------------------------**/
// 查询系统信息并返回JSON格式的字符串
String sFlash()
{
SPIFFS.info(csd); // 获取文件系统信息并存储在csd结构体中
StaticJsonDocument<256> doc; // 创建一个静态的JSON文档,用于构建JSON数据
// 填充JSON文档
doc["bootv"] = ESP.getBootVersion(); // 获取启动版本
doc["id"] = ESP.getChipId(); // 芯片ID
doc["mac"] = WiFi.macAddress(); // MAC地址
doc["ip"] = WiFi.localIP().toString(); // IP地址
doc["sdall"] = csd.totalBytes; // 总空间
doc["sdused"] = csd.usedBytes; // 已用空间
String output; // 创建一个字符串对象,用于存储序列化后的JSON数据
serializeJson(doc, output); // 将JSON文档序列化为字符串
return output; // 返回序列化后的JSON字符串
}
//查询目录信息
String sDir()
{ DynamicJsonDocument doc(1024);
int i=0;
String output;
Dir dir=SPIFFS.openDir("/");
while (dir.next())
{ i++;
String pastr="dir"+String(i);
doc[pastr]=dir.fileName();
}
serializeJson(doc, output);
return output;
}
/**---------------------分割线-------------------------------**/
// 处理服务器请求
template <typename T>
void fsServer(T &ServerG)
{
String path = ServerG.uri(); // 获取请求的URI
String BackContentType = getContentType(path); // 获取文件类型
size_t lastSlashPos = path.lastIndexOf('/'); // 查找最后一个'/'的位置,用于提取文件名
String fileNameStr =path.substring(lastSlashPos + 1); // 提取文件名
// 检查文件是否存在
if (SPIFFS.exists(fileNameStr))
{
File fileName = SPIFFS.open(fileNameStr, "r"); // 打开文件
ServerG.streamFile(fileName, BackContentType); // 发送文件到客户端
fileName.close(); // 关闭文件
}
else
{
// 如果文件不存在,发送404错误
ServerG.send(404, "text/plain", "File Not Found");
}
}
// 根据文件名获取文件类型
String getContentType(String filename)
{
// 检查文件后缀,返回对应的MIME类型
if (filename.endsWith(".htm"))
return "text/html";
else if (filename.endsWith(".html"))
return "text/html";
else if (filename.endsWith(".css"))
return "text/css";
else if (filename.endsWith(".js"))
return "application/javascript";
else if (filename.endsWith(".png"))
return "image/png";
else if (filename.endsWith(".gif"))
return "image/gif";
else if (filename.endsWith(".jpg"))
return "image/jpeg";
else if (filename.endsWith(".ico"))
return "image/x-icon";
else if (filename.endsWith(".xml"))
return "text/xml";
else if (filename.endsWith(".pdf"))
return "application/x-pdf";
else if (filename.endsWith(".zip"))
return "application/x-zip";
else if (filename.endsWith(".gz"))
return "application/x-gzip";
// 如果文件后缀不匹配任何已知类型,则默认返回纯文本类型
return "text/plain";
}
};
#endif
HTML库:
#ifndef html_h
#define html_h
const char* html=R"(<!DOCTYPE html><html lang='en' id='myhtml1'><head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title id='mytitle'>勇敢向前冲!</title></head><style id='mystyle'> * { box-sizing: border-box; margin: 0; padding: 0; } :root { --t: 10s; } body { width: 100vw; height: 100vh; overflow: hidden; display: grid; grid-template-columns: repeat(3, 1fr); grid-row: 0 1fr; } h1 { text-align: center; margin-top: 30px; grid-column: 1/span 3; grid-row: 1; height: 0; } h3 { position: relative; margin: 0; border: 2px solid black; z-index: 2; background-color: white; text-align: center; } #box1 { visibility: hidden; grid-column: 2; grid-row: 2; border: 2px solid black; width: 80%; left: 10%; height: 75%; overflow: hidden; text-align: center; position: relative; background-color: white; } #box1>table { height: 50%; text-align: center; border-collapse: collapse; width: 100%; } th { position: relative; z-index: 1; border: 1px solid black; background-color: white; } td { height: 10px; position: relative; padding: 0; margin: 0; border-left: 1px solid black; animation: move 20s linear infinite; z-index: -1; } @keyframes move { 0% { transform: translateY(0); } 100% { transform: translateY(-100px); } } #box2 { visibility: visible; /* visibility: hidden; */ /*调试专用*/ grid-column: 2; grid-row: 2; border: 2px solid black; width: 80%; left: 10%; height: 80%; position: relative; background-color: black; } #box2>input { width: 100%; text-align: center; } #box3 { visibility: hidden; /* visibility: visible;调试专用 */ grid-column: 1/span 3; grid-row: 2; border: 2px solid black; overflow: hidden; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: 40% 1fr; } #keyname { grid-column: 1; grid-row: 1; position: relative; top: 0; left: 0; width: 50%; height: 40%; display: grid; grid-template-columns: 1fr; grid-template-rows: repeat(4, 1fr); } #keyname>button { width: 100%; height: 100%; font-size: 2vw; background-color: white; position: relative; transform: rotate(-10deg); z-index: -1; } #keyname2 { grid-column: 3; grid-row: 1; justify-self: end; /* 对齐到网格单元格的右侧 */ align-self: start; /* 对齐到网格单元格的顶部 */ position: relative; top: 0; right: 0; width: 50%; height: 40%; display: grid; grid-template-columns: 1fr; grid-template-rows: repeat(4, 1fr); } #keyname2>button { width: 100%; height: 100%; font-size: 2vw; background-color: white; position: relative; transform: rotate(10deg); z-index: -1; } #game { grid-column: 1/span 3; grid-row: 2; border-bottom: 2vh solid black; position: relative; overflow: hidden; } #car { width: 3vh; height: 3vh; border-radius: 3vh; background-color: black; position: absolute; bottom: 0; } #room { border-radius: 1vh 1vh 0 0; position: absolute; right: 0; width: 3vw; height: 6vh; bottom: 0; background-color: black; } input[name='youname'] { margin-top: 20%; font-size: 2em; background-color: black; width: 100%; border: none; outline: none; color: black; text-overflow: hidden; animation: textshow 3s linear infinite, text var(--t) linear infinite; -webkit-box-reflect: below 0px linear-gradient(transparent, rgb(255, 255, 255)); } @keyframes textshow { 0% { color: aqua; } 50% { color: rgb(255, 255, 255); transform: translateY(-10px); text-shadow: 0 0 10px aqua, 0 0 20px rgba(0, 255, 255, 0.8), 0 0 30px rgba(0, 255, 255, 0.6), 0 0 40px rgba(0, 255, 255, 0.8), 0 0 50px aqua; } 100% { transform: translateY(0px); } } #colorA, #colorB { animation: colorC 10s linear infinite; } @keyframes colorC { 0% { color: rgb(0, 0, 0); } /* 黑色 */ 14.28% { color: rgb(255, 0, 0); } /* 红色 */ 28.56% { color: rgb(255, 255, 0); } /* 黄色 */ 42.84% { color: rgb(255, 127, 0); } /* 橙色 */ 57.12% { color: rgb(0, 255, 0); } /* 绿色 */ 71.4% { color: rgb(75, 0, 130); } /* 靛色 */ 85.68% { color: rgb(0, 0, 255); } /* 蓝色 */ 100% { color: rgb(143, 0, 255); } /* 紫色 */ } input[name='youname']:focus { --t: 100s; } @keyframes text { 0% { transform: translateY(0); } 50% { transform: translateY(-20px); } 100% { transform: translateY(0); } } #box3ts { grid-column: 2; grid-row: 1; width: 100%; height: 100%; }</style><body> <h1>勇敢向前冲</h1> <div id='box1'> <h3>排行榜</h3> <!-- 在此处填入你们熟悉的人姓名 --> <table> <thead> <tr> <th>排名</th> <th>玩家名字</th> <th>当前关卡</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>周文轩</td> <td>第8关</td> </tr> <tr> <td>2</td> <td>王芳芳</td> <td>第6关</td> </tr> <tr> <td>3</td> <td>张伟华</td> <td>第6关</td> </tr> <tr> <td>4</td> <td>刘洋子</td> <td>第5关</td> </tr> <tr> <td>5</td> <td>陈娜丽</td> <td>第3关</td> </tr> <tr> <td>6</td> <td>杨明杰</td> <td>第2关</td> </tr> <tr> <td>7</td> <td>虚位以待</td> <td></td> </tr> <tr> <td>8</td> <td>虚位以待</td> <td></td> </tr> <tr> <td>9</td> <td>虚位以待</td> <td></td> </tr> <tr> <td>10</td> <td>虚位以待</td> <td></td> </tr> </tbody> </table> </div> <div id='box2'> <h3>实名认证</h3> <input type='text' name='youname' placeholder='填入姓名'> <button id='play' style='width: 100%;position:absolute;bottom: 6%;left: 0;'>开始游戏</button> <button id='Box1Show' style='width: 100%;height: 6%; position:absolute;bottom: 0;left: 0;'>排行榜</button> </div> <div id='box3'> <div id='keyname'> <button id='but1'>A</button> <button id='but2'>W</button> <button id='but3'>D</button> <button id='but4'>J</button> </div> <div id='box3ts'> <h3 id='box3ts1'>第1关</h3> <p id='colorA' style='display: inline-block;'>您当前已移动:</p> <p id='numts1' style='display: inline-block;'>0</p> <p id='colorB' style='display: inline-block;'>步</p> </div> <div id='keyname2'> <button id='but5'>A</button> <button id='but6'>W</button> <button id='but7'>D</button> <button id='but8'>J</button> </div> <div id='game'> <div id='car'></div> <div id='room'></div> </div> </div> <script> // 定义七彩色列表 const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; var namea; window.onload = function () { var play = document.getElementById('play'); var Box1Show = document.getElementById('Box1Show'); play.onclick = function () { var youname = document.getElementsByName('youname')[0].value; namea=youname; // 这里填写你女朋友名字 if (youname == '染染') { console.log(youname); var box2 = document.getElementById('box2'); box2.style.visibility = 'hidden'; var box3 = document.getElementById('box3'); box3.style.visibility = 'visible'; document.addEventListener('keypress', function (event) { console.log(event.key); keytest(event.key); }); } else if (youname == '') { alert('不能为空!'); } else { alert('请输入您的真实姓名!'); } } Box1Show.onclick = function () { var box1 = document.getElementById('box1'); box1.style.zIndex = 2; if (box1.style.visibility == 'visible') { box1.style.visibility = 'hidden'; } else { box1.style.visibility = 'visible'; } } } var xa = 0, xb, gamepam = 1; function keytest(code) { var but1 = document.getElementById('but1'); var but2 = document.getElementById('but2'); var but3 = document.getElementById('but3'); var but4 = document.getElementById('but4'); var car = document.getElementById('car'); var numts1 = document.getElementById('numts1'); var box3ts1 = document.getElementById('box3ts1'); if (code === 'a') { if (xa != 0) { xa -= 1; xb = xa + 'vw'; } if (xa < 0) { xa = 0; } console.log('后退' + xb); car.style.transform = 'translateX(' + xb + ')'; butshow(but1); butshow(but5); } if (code === 'w') { butshow(but2); butshow(but6); } if (code === 'd') { butshow(but3); butshow(but7); if (xa < 100) { xa += 1; xb = xa + 'vw'; } if (xa === 97) { xa = 0; xb = xa + 'vw'; gamepam += 1; box3ts1.innerHTML = '第' + gamepam + '关'; gameover(); } console.log('前进' + xb); car.style.transform = 'translateX(' + xb + ')'; numts1.innerHTML = xa; numts1.style.color = colors[Math.floor(Math.random() * colors.length)]; } if (code === 'j') { butshow(but4); butshow(but8); } } function butshow(buts) { buts.style.backgroundColor = colors[Math.floor(Math.random() * colors.length)]; setTimeout(function () { buts.style.backgroundColor = 'white'; }, 500); } //这里放表白网页 function gameover() { var myhtml1=document.getElementById('myhtml1'); myhtml1.innerHTML=`<!DOCTYPE html><html lang='en'><head> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1.0'> <title>女友相册html代码</title> <style> *{ margin: 0; padding: 0; background-color: black; } :root{ --uu: 100px; --rot:-100px; --fu:-200px; } .box{ width: 200px; height: 200px; background-color: transparent; position: relative; top: 30vh; left: 30vw; animation: move 10s infinite; animation-timing-function: linear; transform-style: preserve-3d; /* Preserve 3D space */ } .box div{ position: absolute; width: 200px; height: 200px; border: 2px solid white; } .box:hover{ --uu:200px; --rot:-200px; } @keyframes move{ 0%{ transform: rotateY(0deg) rotateX(-25deg) ; } 100%{ transform: rotateY(360deg) rotateX(-25deg); } } .front { transform: translateZ(var(--uu));}.back { transform: translateZ(var(--rot)) rotateY(180deg);} .right { transform: rotateY(90deg) translateZ(var(--uu));}.left { transform: rotateY(-90deg) translateZ(var(--uu));}.top { transform: rotateX(90deg) translateZ(var(--uu));}.bottom { transform: rotateX(-90deg) translateZ(var(--uu));} </style></head><body> <div class='box'> <div class='front'><img src='img/1.png' ></div> <div class='back'><img src='img/2.png' ></div> <div class='right'><img src='img/3.png' ></div> <div class='left'><img src='img/4.png' ></div> <div class='top'><img src='img/5.png' ></div> <div class='bottom'><img src='img/6.png' ></div> </div></body></html>`; } // //===========调试专用 // box3.style.visibility = 'visible'; // document.addEventListener('keypress', function (event) { // console.log(event.key); // keytest(event.key); // }); </script></body></html>
)";
#endif