物联网不求人-自制ESP32微型NAS

在这里插入图片描述

物联网不求人-自制ESP32微型NAS

近期我所在的创客交流群不少老师都在提到如何使用ESP32CAM摄像头与ESP32如何使用Webserver远程控制,恰好有位老师咨询过我一个循迹小车竞技类的比赛,其中一个任务是采集指定地点的实时图像(即到达指定任务地点后拍摄一张图片保存),考虑到不少物联网项目都有保存与采集数据的需求,如研究一天气温或者光照变化等需要采集一天的气温或者光照数据。DFRobot有一款串口数据记录器的设备,其能够通过串口保存txt文本或者csv文件并在手机等设备进行数据查看同时还可以通过Excel进行数据分析,但其必须串口有线连接、不能保存图像,数据分析也必须要拷贝数据到其他设备上进行数据整理,同时多个主控需要保存数据时也较为不便,针对物联网场景下的数据保存与分析实际需求,于是便催生了ESP32微型NAS的这个项目。可以用它充当简单的网络服务器保存数据与采集图像,同时开放API以实现灵活的自定义需求与预览文件,如在线数据分析,自定义控制页面等。在这个项目当中我们将会学到ESP32的SD卡的操作与ESP32摄像头使用以及ESP32充当Webserver的应用方法,现在让我们一起来开始ESP32微型NAS的学习吧。

视频演示

下面让我们来看看自制ESP32微型NAS的演示视频,该视频当中演示了ESP32微型NAS的主要功能如拍照保存,在线SD卡文件管理功能(上传文件,修改文件,预览文件,下载文件),其中预览文件功能可以让我们自己编写html代码实现数据分析,智能家居控制,网络查询等。

ESP32微NAS应用

所用硬件

在这里插入图片描述

AIcam Pro是齐护机器人推出的一款人工智能学习的开发板,内置了摄像头,板载按钮,TFT等模块。将常见的百度,旷视以及腾讯等人工智能平台进行了整合提供了统一的接入方式,简化了人工智能的使用方法,为广大师生与爱好者提供了一种人工智能学习的简单方法。本教程便是以AIcam Pro为例进行二次开发,其思路可用于任意ESP系列开发板。

硬件特点

  • 基于 ESP32 开发,支持 WiFi,蓝牙
  • TF卡拓展
  • 状态指示灯
  • OV3660摄像头
  • 240x240 TFT
  • 两路拓展接口
  • 外部供电与电源管理
  • 音频放大电路

本教程所需准备

软硬件准备

  • Arduino IDE(或者齐护教育版Mixly内置IDE)
  • 一块齐护AIcam Pro开发板;
  • 一张SD卡(不超过32GB且为高速卡);
  • 齐护固件上传工具

关于开发环境搭建不在本文讨论范围,大家自行百度或者访问对应官网进行相关开发软件的下载与安装

自制ESP32微型NAS思维导图

在这里插入图片描述

上面思维导图中阐述了自制ESP32微型NAS的基本功能,下面将围绕该思维导图进行讲解,带大家了解从零开始了解AIcam Pro微型NAS的技术实现原理,以上所有库文件均已包含在齐护教育版Mixly,其他开发环境请GitHub自行下载相关库。

相关技术原理分析

SD卡相关操作

SD卡数据读

这里我们读取数据泛指可阅读的文件格式如txt、html、js、css等,该类文件读取直接返回文本内容。读取该类文件的核心代码如下

String Read_SD_card(const char * path) {
  File file = SD.open(path);
  if (!file) {
    Serial.println("Failed to open file for reading");
    while (true) {
      delay(1000);
    }
  }
  String data = "";
  while (file.available()) {
    data = String(data) + String(char(file.read()));
  }
  file.close();
  return data;
}

该函数指定一个文件路径读取该文件并将文本内容作为字符串返回,其中文件路径为/test.txt表示根目录下的test.txt文件,/test/test.txt表示test目录下的test.txt文件,file.read()函数返回的是ASCII码,这里我们用char()方法将ASCII码转换为我们可以阅读的字符,此外我们还用到了一个技巧在这个函数当中我们先声明一个局部变量data,利用file.available()函数的特点利用while循环读取文件内容并拼接到data字符串,最后当数据不可读时跳出循环关闭文件并返回data,最后我们便得到的文件内容的完整字符串。利用该函数我们可以读取某些配置文件如网页模版,网络配置以及摄像头配置等信息对系统进行初始化。

SD卡数据写

这里我们的数据写也泛指可阅读的文件格式,文件写有两种方式一种是文件的覆盖写入,写入相同路径新文件时将覆盖原文件;另一种是追加写入,写入相同路径新文件时将在原文件内容后面追加内容,两种方式都会自动创建文件。下面是一个文件写入的例子。

File file = SD.open("/test.txt", FILE_APPEND); //FILE_APPEND表示追加,FILE_WRITE表示覆盖
if (!file) {
  Serial.println("无法打开文件");
  return;
} else {
  file.println("Hello World"); //file.println表示换行写入,file.print表示不换行
  file.close();
}

利用这两种写入方式,我们可以创建API记录数据或者修改预览的相关文件,数据收集主要用到的也是此函数实现数据记录的目的。

判断文件是否存在

我们读取或者修改文件时需要先判断该文件是否存在,若处理的相关文件不存在时会出现无法读取或者开发板重启等错误,因此我们需要使用SD.exists(path)函数判断文件或者目录是否存在,文件存在返回true反之返回false,其中path为文件的路径如"/test.txt"表示根目录下test.txt文件。该函数搭配上面的SD卡数据读可以实现一些特殊效果,例如程序初始化时为默认配置,若检测到某个配置文件存在时使用配置文件的配置去覆盖默认配置从而达到个性化配置的目的。

删除文件

删除文件用到了SD.remove(path)函数,该函数返回bool值,删除成功返回true反之返回false,该函数加上Webserver可以让我们在线删除某些文件,其中path为文件路径,可以为任意格式的文件。

遍历目录

某些时候我们需要获取根目录或者指定目录下的所有文件名,例如演示视频中的NAS主页显示了所有文件,其中相关文件的文件大小,文件名称,预览下载等相关操作都需要获取对应的文件名称,因此我们需要一次性获取目录下的所有文件名,这里就用到了遍历目录,相关代码如下

void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
  Serial.printf("Listing directory: %s\r\n", dirname);

  File root = fs.open(dirname);
  if (!root) {
    Serial.println("- failed to open directory");
    return;
  }
  if (!root.isDirectory()) {
    Serial.println(" - not a directory");
    return;
  }

  File file = root.openNextFile();
  while (file) {
    if (file.isDirectory()) {
      Serial.print("  DIR : ");
      Serial.println(file.name()); //打印子目录
      if (levels) {
        listDir(fs, file.name(), levels - 1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name()); //打印文件名称
      Serial.print("\tSIZE: ");
      Serial.println(file.size()); //打印文件大小
    }
    file = root.openNextFile();
  }
}

例如执行listDir(SD, “/”, 0);将打印根目录的所有文件目录与文件名,利用遍历功能我们利用html模板便可生成含有文件名称的特定代码用于展示文件名,文件大小以及各种功能的UI与接口地址等。

创建目录

若我们将所有的文件全部都放置到根目录中,这会导致文件的展示混乱,同一个类型的文件或者具备某一类功能的关联文件应该放到一个子目录中增加文件系统的可读性,例如演示视频当中的保存拍照图片到指定的目录进行汇总,创建目录的函数为SD.mkdir(Path),如SD.mkdir(“/test”)表示在根目录下创建test目录。

Webserver使用相关

静态网页托管

下面我们看看Webserver的一个简单例子,代码如下

#include <WiFi.h>
#include <ESPAsyncWebServer.h>

AsyncWebServer server(80); //80端口启用Webserver服务

const char index_html [] PROGMEM = R"rawliteral( //Webserver响应页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Webserver测试</title>
</head>
<body>
<h1>你好世界</h1>
<h1>Hello World</h1>
</body>
</html>
)rawliteral";

void setup() {
  Serial.begin(9600);
  WiFi.begin("********", "********"); //连接wifi
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("Local IP:");
  Serial.print(WiFi.localIP()); //打印IP地址

  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) { //网页路径为/方式为get请求,HTTP_POST为post方式
        request->send(200, "text/html", index_html);
  });

  server.begin(); //开始Webserver服务
}

void loop() {

}

从上面的的代码中我们可以发现ESP32 Webserver服务使用较为简单,主要由3部分构成第一是访问的路径,第二是请求方式(GET或者POST),第三是响应。我们访问联网后的设备IP地址可得到如下响应

在这里插入图片描述

在这里我们仅展示了基础的网页响应,没有添加CSS与JS样式,在网页响应中HTML提供了网页的结构和内容,CSS负责样式和外观,JS则为网页添加了交互和动态效果。它们三者相互配合,共同构成了一个完整的网页,要想更好的使用Webserver了解一些网页基础知识是必要的,这个大家可以自行去相关网站了解并学习。

获取请求参数

除了在代码中添加响应字符串之外我们还可以使用SD卡中的文件作为响应例如request->send(SD, “/index.html”, “text/html”);表示将SD卡根目录中的index.html文件作为响应发送到网页,其中响应的类型为text/html,利用这种方式可以在SD卡中放入任意html网页代码,若我们响应的html中存在CSS与JS样式,那么我们需要使用绝对引用的方式引用SD卡里的样式文件,为此我们需要在get或者post请求中加入filename参数,该参数表示响应的文件路径,这里我们以get方式为例,获取路径/file参数filename代码如下

  server.on("/file", HTTP_GET, [](AsyncWebServerRequest *request){
  String filename = "";
  if (request->hasParam("filename")) { //判断是否存在路径参数filename
  filename = request->getParam("filename")->value();
  request->send(200, "text/plain", filename); //返回filename参数提交的值
  }else {
  request->send(200, "text/plain", "no filename"); //返回no filename表示参数
  }
  });

下图所示展示了get请求方式的参数请求与响应

在这里插入图片描述

对于get请求方式提交参数有一个缺点,那就是所有的参数以明文的方式在链接中显示出来了,有数据泄露的风险,对于某些敏感数据,我们不希望其明文出现,这时我们可以使用post请求通过表单的形式提交,通过表单我们提交的参数不仅可以是字符串还可以是文件,且post请求没有提交数据量的限制可以用来提交大量的数据,表单提交字符串类型参数例子如下

server.on("/superhtml", HTTP_POST, handleForm);

其中handleForm为处理函数函数如下

void handleForm(AsyncWebServerRequest *request) {
  // 检查请求方法是否为POST
  if (request->method() == HTTP_POST) {
    // 获取表单字段的值
    String path = "";
    String filename = "";
    String Inquire = "";

    filename = request->arg("filename");
    Inquire = request->arg("Inquire");
    path = request->arg("path");

    Serial.println(filename);
    Serial.println(Inquire);
    Serial.println(path);

    request->send(200, "text/html", "ok");
  }
}

下图所示展示了post请求方式表单参数提交与响应

在这里插入图片描述

网络响应代码与响应类型

在上面我们注意到了响应的请求中都有一个200字样,那么这个数字有什么特殊含义呢?这些数字称为网络响应代码,在网络请求中通过不同的响应代码代表不同的含义。例如这里的200表示请求成功,服务器成功处理并响应了客户端的请求。有些时候我们访问网页返回页面带404字样代表请求的资源不存在或者已删除。这些状态码可以让我们知道服务器的状态,除了这里介绍的两种状态码之外还有其他状态码,感兴趣的小伙伴可以网上查阅相关资料。我们在请求网络资源的时候只有返回正确的文件类型才可以正常响应请求,否则便会出现错误响应或者响应不能被浏览器渲染等。下面介绍几种常见响应数据类型

  1. 文本/字符串(Text/String):普通的文本响应,通常是以文本形式返回给客户端,如HTML(text/html)、纯文本(text/plain)、JSON(application/json)等。
  2. 图像(Image):响应是图像文件,如JPEG(image/jpeg)、PNG(image/png)、GIF(image/gif)等的二进制数据。
  3. 音频(Audio):响应是音频文件,如MP3(audio/mpeg)、WAV等的二进制数据。
  4. 视频(Video):响应是视频文件,如MP4(video/mp4)、AVI等的二进制数据。
  5. PDF(PDF):响应是PDF(application/pdf)文件的二进制数据。
文件上传

ESP32微型NAS的文件是存储到SD卡的,想要上传文件到SD卡最简单的方法是使用读卡器插入电脑上传文件,但文件上传完成以后需要将SD卡插回ESP32并重启,这就导致了服务中断的问题,为了解决这个问题,我们需要提供在线上传文件的方式,其API接口程序如下:

server.on("/upload", HTTP_POST, [](AsyncWebServerRequest * request) {
  AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "OK");
  response->addHeader("Access-Control-Allow-Origin", "*");
  request->send(200, "text/html", "ok");
}, [](AsyncWebServerRequest * request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
  filename = "/" + filename;
  static File file;
  if (!index) {
    Serial.printf("UploadStart: %s\n", filename.c_str());
    SD.remove(filename);
    file = SD.open(filename, FILE_WRITE);
    if (!file) {
      Serial.println("Failed to open file for writing");
      return;
    }
  }
  if (file) {
    file.write(data, len);
  }
  if (final) {
    Serial.printf("UploadEnd: %s, %u B\n", filename.c_str(), index + len);
    if (file) {
      file.close();
    }
  }
});

文件上传API接口上传图片效果如下:

在这里插入图片描述

摄像头拍照

ESP32常见的摄像头模组有OV2640与OV3660等,这里仅介绍拍照的核心代码,忽略引脚定义及摄像头初始化后代码如下

String Photograph(String path) {
  camera_fb_t * fb = NULL;
  fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    return "Camera capture failed";
  }

  File file = SD.open(path , FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing");
    return "no";
  }

  file.write(fb->buf, fb->len);
  file.close();
  esp_camera_fb_return(fb);
  return "ok";
}

该代码比较简单这里就不解释代码了,上面的函数实现了一个指定文件存放路径拍照保存到SD卡的功能。与前面介绍的Webserver相结合可以还可以实现演示视频中API接口获取图像或者指定路径拍照等功能,此处篇幅有限,相关代码可以在附录代码中查阅。

json字符串解析

json是一种轻量级的数据交换格式,常用于数据传输和配置文件中,其具有简洁轻量易拓展的特点,利用json我们可以向SD卡写入一些配置文件,通过判断该配置文件是否存在并读取配置文件解析出其中的联网信息或者摄像头设置等信息,从而让配置文件替代默认的系统配置实现个性化的设置,下面我们来看看如何解析json格式的字符串,第一步我们先打开ArduinoJson Assistant网站,打开网站后按下图所示进行解析

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

TFT中文显示

TFT显示文字有着展示系统状态与丰富人机交互的作用,其中文显示的相关程序如下

#include <TFT_eSPI.h>
#include <SPI.h>
#include <tft_Font_20.h>//20号中文字体

TFT_eSPI tft = TFT_eSPI();

void setup() {
  tft.init();//TFT初始化
  tft.fillScreen(0x0000);//清屏为黑色
  tft.setTextColor(0xFCCC, 0x0000);//设置文本显示前景色与背景色
  tft.loadFont(tft_Font_20);//加载字体
  tft.drawString("你好世界", 0, 0);//显示中文
  tft.unloadFont();//释放字体
}

void loop() {

}

TFT二维码显示

为了确保设备离线情况下也能使用手机等设备进行NAS访问,因此设备的AP热点将会一直开启,受由手机二维码分享WiFi网络启发,这里我们将自动生成设备热点的网络信息二维码方便其他设备进行扫码连接,动态生成设备AP热点分享二维码例子如下,使用手机扫一扫即可连接设备访问192.168.4.1即可进入设备主界面

String strURL = "WIFI:T:WPA;P:mac;S:AIcam_mac;H:false;"; //WiFi分享信息
strURL.replace("mac", mac); //将WiFi分享信息中mac字符串替换为实际mac地址

char x[500];
strURL.toCharArray(x, 500);
Serial.println (x);
QRCode qrcode;
uint8_t qrcodeData[qrcode_getBufferSize(6)];
qrcode_initText(&qrcode, qrcodeData, 6, 0, x);
uint8_t x0 = 17; //横坐标偏移
uint8_t y0 = 17; //纵坐标偏移
uint8_t Zoom = 5; //缩放倍数
Serial.println (qrcode.size);
for (uint8_t y = 0; y < qrcode.size; y++) {
  for (uint8_t x = 0; x < qrcode.size; x++) {
    if (qrcode_getModule(&qrcode, x, y)) {
      tft.fillRect(Zoom * x + x0, Zoom * y + y0, Zoom, Zoom, 0x0000);
    } else {
      tft.fillRect(Zoom * x + x0, Zoom * y + y0, Zoom, Zoom, 0xFFFF);
    }
  }
}

其他显示设备如OLED等均可修改使用此二维码生成程序通过绘制填充矩形的方式绘制任意二维码。

程序整合

到这里我们已经介绍完了本项目的基础原理与技术路线,本项目是一个对SD卡文件操作与Webserver深度结合的案例,演示视频中的所有功能本质上都是html对Webserver开放API的利用,而API又通过操作SD卡或者摄像头模组相关的函数从而实现各种功能,如下方的NAS主页其原理便是遍历SD卡根目录获取其所有的文件名称与文件目录根据其类型分别赋予其不同html元素实现进入目录与各种文件预览编辑等功能,受限于篇幅,更多关于ESP32微NAS的程序方面的介绍请在本文附录下载资源包进行查看与学习,本教程仅阐述其技术原理。
在这里插入图片描述

体验与烧录程序

如果你你不想理会复杂查询代码想要快速体验本项目那么只需要使用齐护固件上传工具上传附录固件,复制SD卡模板文件到SD卡并修改admin.txt配置文件按照屏幕提示进行使用即可。

在这里插入图片描述

总结

本教程将SD卡的操作与Webserver相结合实现了一个简单的NAS功能,值得注意的是该项目主要是给单片机用的“NAS”,用来记录一些传感器数据与图像,主要用途是当做一个特殊的带图像界面的物联网数据记录仪,通过自定义的html代码与API的结合可以实现诸如数据可视化数据分析以及图床等功能。对于可联网设备可以通过API方式访问设备,其他不能自主联网的设备如UNO,Nano等则可以使用串口通讯进行连接(此部分自行编写,附录参考程序仅包含作为NAS的基础功能)。与SD卡的操作类似ESP系列的文件系统也有与SD卡类似的操作函数,因此本文介绍的方法同样适用于SPIFFS,对于本项目更加详细的使用方法与应用案例介绍可以通过附录文件进行查阅,文中介绍的方法适用于ESP系列的各种开发板,例如原来原来很火的太空人的桌面摆件,可利用文中的方法方便的配置网络,GIF动画与各种显示图标等实现实现丰富的个性化设置。文中的各种API在线使用依赖html等前端知识,因此只有掌握必要的前端相关知识才可以随心所欲的使用本项目制作出各种实用的个性化功能,如将视频流与Webserver API结合的视频流小车控制(GET请求将按钮或滑块值发送到串口);JavaScript DOM技术的在线图像采集(保存图片并动态生成图片预览);数据可视化(不同数据的可视化展示与曲线图像下载);自定义模板查询网页与自定义智能家居控制页等。它带来了无限可能,而这仅仅取决于你的想象力。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

peien268

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值