ESP32 M5 超级问卷星:轻松收集数据
时间:2022-12-07 01:30:01
作者:默
不知不觉中,疫情持续了 3 近年来,门卫王叔叔非常苦恼,因为领导要求所有进入单位的人员和车辆按照防疫要求登记。登记内容包括车牌号、姓名、性别、体温、联系电话、被访人及其进入单位的时间 等。叔叔年纪大了,有时力不是很好,注册经常卡住,每次卡住。他每次都要问来访者很长一段时间。后来的来访者看着不知所措的王叔叔也很无助。毕竟,叔叔是你叔叔,不能催促他。他只能诚实地排队。叔叔自己也发现了这个问题,所以他找到了我,想让我给他一个想法,想办法解决他目前的问题,提高他的工作效率。
在一些社区和医院,作者也发现了与王叔叔相似的情况:
社区保安需要登记来访者和车辆;
医院服务的大白和志愿者需要登记家庭住址、身份证号码、近期外出情况等信息;
临近假期,一些大学辅导员也要登记学生的假期安排(留校或回家)。
这些问题有一个共同的特点:即属于表格统计,只有简单的文本信息,传统信息输入由相关人员打印纸质表格,费时费力,遇到问题需要找到人员相关信息,从大量纸质表格搜索信息,这种古老而传统的数字方式无疑非常落后。作为实用型 Maker 针对这些痛点,我开发了M5 超级问卷星”这个项目,通过在线的方式将数据进行录入,并且支持在线搜索与查看,以及将数据导出为 Excel 表格。
让我们先这个项目的演示视频。在这段视频中,我们以王叔叔的需求为例:
M5超级问卷星名的起源
M5 来源于基于 ESP32 的 M5core2 开发板,问卷星代表问卷调查表统计设备,就像前几期分享的一样 M5 Server X 一样,它们同属于网络服务器的范畴,故名 M5 超级问卷星。运用 M5 超级问卷星,我们可以在线收集任何文本数据,在线查看和导出 Excel 表格,如防疫登记表、离校意向表、商品销售清单等,农林或生物系的学生可以用它来记录一些对照试验表。 M5 还有很多超级问卷星可以做到,只要有表格,就会有用武之地。
预期目标和功能
在线提交表单数据
提交数据反馈
在线数据查看
在线数据检索
在线数据导出为 Excel 表格
SD 加载卡配置文件
二维码显示服务网站
所用硬件
M5 Core2
M5Core2特点
基于 ESP32 开发,支持 WiFi、蓝牙;
16M闪存,8M PSRAM;
内置扬声器、电源指示灯、振动电机、RTC,I2S 功率放大器、电容式触摸屏、电源键、复位键;
TF 卡插槽(支持最大 16GB);
配备电源管理芯片的内置锂电池;
内置独立小板 6 轴 IMU,PDM 麦克风;
M-Bus 总线插座。
程序设计
开始详细说明程序设计过程。
开发环境
我们使用 Aduino IDE 软件编写项目程序,开发板选择 M5Stack-Core2.至于怎么在 Arduino 中配置 ESP32 本文本文介绍范围内的开发环境,请自行查阅相关资料。
程序思路
为了实现我们的预期目标,我们首先绘制功能性思维导图,然后根据思维导图逐步实现 M5 超级问卷星的程序设计。
下面具体讨论一下 M5 如何实现超级问卷星的子功能?
获取提交参数
在前期 M5 Server X 在项目中,我们使用构建路径参数来区分每个服务。本期我们将通过请求参数获取网页表单的数据,如下所示。
#include #include #include #include constchar*ssid="xxxxxx"; constchar*password="xxxxxx"; AsyncWebServerserver(80); voidsetup(){ Serial.begin(115200); WiFi.begin(ssid,password); while(WiFi.status()!=WL_CONNECTED){ delay(1000); Serial.println("ConnectingtoWiFi.."); } Serial.println(WiFi.localIP()); server.on("/",HTTP_GET,[](AsyncWebServerRequest*request){ intparamsNr=request->params();///获得提交参数的数量 Serial.println(paramsNr); for(inti=0;igetParam(i); Serial.print("Paramname:"); Serial.println(p->name()); Serial.print("Paramvalue:"); Serial.println(p->value()); Serial.println("------"); } request->send(200,"text/plain","messagereceived");//反馈网页 }); server.begin(); } voidloop(){ }
上传程序,打开串口监视器,我们观察到路由器 M5 Core2 分配的 IP 地址是192.168.1.24
,记住地址。
我们通过浏览器访问http://192.168.1.24/?name=小明&gender=男
, 浏览器返回数据如下
此时,串口监视器返回数据如下:
这里有两个参数,分别是 name(姓名)与 gender(性别),它们的价值是小明和男性。在这里,您可以添加其他参数,如年龄参数,浏览器访问链接再次观察串口输出,找到规则,理解访问参数的意义。
表单输入
以上示例,我们通过参数请求获得了提交的数据,那么我们如何更简单、更方便地提交不同的参数呢?网页上有一种叫做表格提交的方法,让我们提交这些数据。让我们以王叔叔的需求为例 HTML 脚本输入数据。
#include #include #include #include constchar*ssid="xxxxxx"; constchar*password="xxxxxx"; constcharindex_home[]PROGMEM=R"rawliteal(
来访登记
)rawliteral";
AsyncWebServer server(80);
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi..");
}
Serial.println(WiFi.localIP());
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
int paramsNr = request->params();
Serial.println(paramsNr);
for (int i = 0; i < paramsNr; i++) {
AsyncWebParameter* p = request->getParam(i);
Serial.print("Param name: ");
Serial.println(p->name());
Serial.print("Param value: ");
Serial.println(p->value());
Serial.println("------");
}
request->send(200, "text/html", index_home);
});
server.begin();
}
void loop() {
}
上传程序,访问 M5core2 对应的 IP 得到如下图所示网页:
打开串口监视器,随机输入一些信息观察串口的数据输出,在这里我们仅需要关注几个地方,网页标题通过 title 标签指定,某一参数输入由 input 标签决定,例如我们要实现年龄输入,只需替换为下列程序:
年龄
这里我们可以通过 w3school在线网页编辑器 实时修改并预览,修改后效果如下:
对于固定格式的数据,如性别、年级等可参考上面的性别例子在网页编辑器中编辑并预览,相信你一定能轻松搞定。
数据可视化
现在我们实现了任意数据表单的输入,那么我们要如何显示数据呢,下面我直接给出网页代码:
来访者记录
来访者记录
序号
车牌号码
访客姓名
性别
体温
联系电话
被访人
进入时间
1
湘P·RBBZG
邱勤
男
36
1845646545
小宝
2022/4/28 14:56
2
晋B·MIVYF
简馨
女
36
1845646548
马克
12022/4/28 15:56
将上面的网页代码复制到w3school在线网页编辑器我们得到如下的表格:
在这个网页中我们点击搜索,填写关键字可以在一定范围内搜索数据,例如我们输入 P 效果如下:
点击导出表格将会下载文件“来访者记录表格.xls”,我们打开该 Excel 文件效果如下:
对比 Excel表格与在线表格,可知两者信息以及格式一致。值得注意的是,即使我们输入错误的信息也会被录入,如上图中的访客 简馨 输入的进入时间有误。在这里篇幅有限我们并没有验证,对于有格式要求的信息如邮箱,电话号码等,有需要的读者可以自行学习前端知识改进上面的网页代码以达到自己的要求。
对于上面的HTML文件进行分析,我们可以知道关键的几项数据如下:
来访者记录
序号
车牌号码
访客姓名
性别
体温
联系电话
被访人
进入时间
1
湘P·RBBZG
邱勤
男
36
1845646545
小宝
2022/4/28 14:56
2
晋B·MIVYF
简馨
女
36
1845646548
马克
12022/4/28 15:56
将上面的表格的数据替换或者增加数据项,并在网页编辑器中编辑并预览你发现了什么规律,想要实现下图中的表格样式又该如何改呢,这个问题留给大家进行思考。
在这个例子中,我们发现,一旦表格的名称与表头信息固定,那么整个表也就随之确定了。其中序号作为必要的数据项放到表格的第一位,为表格排序与统计数据个数,观察 html 表格可知其的最小重复单位如下:
2 //固定序号
晋B·MIVYF
简馨
女
36
1845646548
马克
12022/4/28 15:56
按照最小重复单元,我们增加数据项与重复单元,在网页编辑器中进行增删改,实时修改并预览,深刻理解 html 表格。
表单输入自动添加到表格
前面我们通过串口监视器观察到了表单数据的提交,那么我们要如何将表单提交的数据添加到表里呢?对于一条数据对应的就是上面的最小重复单元,那么我们可以这样做,对每一条提交的表单数据都将其构造为下面的形式
data1 //固定序号
data2 //第1个提交参数
data3 //第2个提交参数
……
dataN //第N个提交参数
这里我们不需要考虑每一个数据的数据类型到底是整数,小数还是字符串,我们将其构造后将其视为一个字符串即可。对于多条数据,那么我们如何保存它呢?在 C 语言中我们知道保存一个数据我们可以用变量,保存多个数据我们用数组,那么问题来了我们并不知道我们提交的数据有多少条,数组要求我们使用要先定义数组长度,但这里我们的长度显然是无法预知的。那么有没有可变长度的数组呢,答案是肯定的,我们的解决办法是用链表,至于链表是什么大家可以自己百度了解一下,下面我给出一个链表的简单使用例子。
#include
QList myList;//声明一个字符串类型的链表
void setup(){
Serial.begin(115200);
Serial.println(myList.size());//第一次打印链表长度
for (int i = 1; i <= 10; i = i++) {//逐次添加随机数到链表
myList.push_back(String((random(1, 100))));
}
Serial.println(myList.size());//添加数据后再次打印链表长度
for (int i = 1; i <= 10; i++) {//循环打印链表的每一项
Serial.println(myList.at((i - 1)));
}
myList.clear();//清除链表
Serial.println(myList.size());//再次打印链表长度
}
void loop(){
}
上传程序,打开串口监视器结果如下:
关于链表还有一个注意事项,那就是如果你访问了不存在的数据项,例如我们这里值添加了 10 项数据,但你访问了第 11 项数据,那就会导致程序崩溃,开发板会不断重启,你可以试试这种情况看是否如此,为了避免这种情况的发生我们要适当利用链表长度获取函数来规避这个问题。下面我直接给出表单提交数据放入链表的例子:
String table = " \n";
for (int i = 1; i <= paramsNr + 1; i++) {
table = String(table) + String(String(" ") + String("data") + String(i) + String(" \n"));
}
table = String(table) + String(" \n");
Serial.println(table);
table.replace("data1", String(myList.size() + 1));
for (int i = 0; i < paramsNr; i++) {
AsyncWebParameter* p = request->getParam(i);
table.replace(String("data") + String(i + 2), String(p->value()));
}
myList.push_back(table);
Serial.println(myList.at((myList.size() - 1)));
表格数据替换
现在我们已经能够获取表单数据并写入链表了,那么我们如何将链表的每一项数据都放入数据查看的 html 网页了呢,我们可以这样做 ,将原表格网页的数据项用一个占位符表示,链表的所有数据项用一个 for 循环全部连接到一起组成一个长字符串,再把这个字符串替换原来的占位符,那么就可以得到完整的 html 表格文件了。实现的方法如下:
String Tabular_data_variable = "";//声明一个变量用来存放所有链表数据
for (int i = 0; i < myList.size(); i++) {//获取链表长度巧妙利用for循环获取所有数据进行拼接
Tabular_data_variable = String(Tabular_data_variable) + String(myList.at(i));
delay(0);//延时函数必须要,否则当链表长度较大时可能会导致看门狗超时重启
}
html = index_data;//将数据表格html赋值为原始的数据表格字符串(含占位符)
html.replace("Tabular_data", Tabular_data_variable);//将占位符Tabular_data替换为有效数据
提交表单交互
到这里如果你提交了表单数据,你会发现没有一个交互效果 ,我们并不知道表单数据是否提交成功以及数据提交是否有缺失,当数据不完整时表单数据不应该加入到链表里,只有提交的所有数据参数都不为空字符串时证明数据有效,才能添加到链表,交互的网页代码如下,这是一个提交成功和失败都通用的网页,当然你也可以自己写一个交互网页:
登记成功
信息登记成功!
秒后回到主页
代码效果如下:
域名解析
我们可以通过串口监视器获取设备的 IP 地址进行访问,但是路由器的分配的 IP 地址是变化的,这点很不方便,当然你也可以登录路由器后台给 M5Core2 分配一个静态 IP。我们这里采取域名解析的方式通过给 M5Core2 分配一个本地域名,这样我们不需要知道IP地址也能方便的 访问设备,实现代码如下:
#include
void setup() {
Serial.begin(115200);
if (!MDNS.begin("M5Core2")) {//自定义域名
Serial.println("Error setting up MDNS responder!");
}
MDNS.addService("http", "tcp", 80); //启用DNS服务
}
void loop() {
}
通过域名解析,我们只要和设备在同一局域网内,访问 http://m5core2 就能访问相应的服务了。
读取SD卡文件
本项目是一个通用型的项目,如果我们将网页文件进行固定那么就丧失了灵活性,因此我们将网络信息以及网页代码等配置文件放入 SD 卡内,从 SD 中去加载所有服务。SD 卡使用的简单示例如下:
#include "FS.h"
#include
#include
SPIClass sdSPI(VSPI); //定义SD卡软SPI管脚
#define SD_MISO 38
#define SD_MOSI 23
#define SD_SCLK 18
#define SD_CS 4
String readFile(fs::FS &fs, const char * path) { //读取SD卡指定路径文件
File file = fs.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
}
String data = "";
while (file.available()) {
data = String(data) + String(char(file.read()));
}
file.close();
return data;
}
void setup() {
Serial.begin(115200);
sdSPI.begin(SD_SCLK, SD_MISO, SD_MOSI, SD_CS); //初始化SD卡SPI
if (!SD.begin(SD_CS, sdSPI)) {
Serial.println("Card Mount Failed");
return;
}
Serial.println(readFile(SD, "/admin.txt"));
}
void loop() {
}
在这里我们可以直接输入 TXT 或者 HTML 文件的路径,便可读取该文件,这里我们读取了 SD 卡根目录下的 admin.txt 文件,该文件作为配置文件用来保存网络信息与一个二维码的数据,该文件内容如下:
{
"ssid": "ChinaNet-5678",
"password": "1234567890",
"qr": "http://m5core2/"
}
M5 超级问卷星网页逻辑
M5 超级问卷星服务框架如下:
M5 超级问卷星通过区分根目录与 /data 路径来呈现不同的网页内容,其中访问域名或IP进入表单数据提交页,域名或 IP 加 /data 路径进入数据查看与导出页面。
服务响应逻辑
M5 超级问卷星服务响应的逻辑如下,访问根目录返回 Home_page.html 页面用于表单提交,当表单提交数据时有两种情况,当提交表单所有数据都不为空返回 success.html 页面,当提交表单某些数据为空时返回 mistake.html,当访问 /data 路径时返回 index_data.html 页面用于查看或导出数据。
配置文件结构
SD 卡配置文件主要由 admin.txt、Home_page.html、success.html、mistake.html、index_data.html 这四个文件构成 ,配置文件详情示意图如下:
细节优化
M5 超级问卷星是一个用于表格数据收集的项目,它集数据库与服务器于一身,不依赖于任何第三方服务,为了交互体验更好,还应当有些许动态提示功能,例如是否有 SD 卡,是否有网络等、我们可以显示一些图像文字和播放音效来进行提示,屏幕上应当显示一个用于外网或者本地的二维码地址,用户直接扫码浏览器打开就可以提交数据,关于这些功能的实现可以参考往期《DIY掌上POS机,或许是最小的收银POS机了!》与《超便利!教你用ESP32 开发板 DIY 掌上服务器》这两篇教程,其中有功能实现详细的描述,这里就不再赘述,可通过链接进行查看。
程序下载
以上就是M5 超级问卷星的项目介绍,如果你不想下载IDE只想体验该项目,那么你可以访问 https://docs.m5stack.com/zh_CN/download 根据你自己的系统下载M5Burner烧录工具进行安装,打开软件按照下面的步骤进行烧录体验,其中SD卡网页模板与配置文件请通过本教程附件进行下载,直接解压到的SD卡修改网络信息即可体验。
使用说明
烧录固件;
将附件提供的模板解压到 SD 卡;
打开 admin.txt 文件修改网络信息(不需要外网访问二维码地址可以不改,如果有使用内网穿透请填写外网地址);
SD 卡放入 M5core2 并重启设备;
等待 M5core2 初始化并进入二维码显示页面(若初始化错误请按照屏幕显示与语音提示使用正确配置文件或更换 SD 卡);
访问 http://m5core2/ 按演示视频提交表单数据(体验提交成功与失败两种情况);
访问 http://m5core2/data 查看数据并导出 Excel 查看;
尝试修改模板文件自定义表单文件与数据表格重复上述步骤理解本项目。
总结
根据上面的理论基础我们便能完成 M5 超级问卷星的项目制作了,其中具体实现细节由于篇幅限制,这里就不再讨论,大家可以通过附件下载程序源代码进行查看,其中必要的程序说明已经注释,对此项目有任何建议或者疑问均可评论区留言。使用 M5 超级问卷星你能够轻松利用表单收集任意文本信息并且导出为 Excel 表格,如果你会网页前端知识你还可以定制属于自己的网页样式满足个性化,与问卷星不同这个是你个人私有的服务器且可以灵活定制各种功能(数据分类汇总,表格可视化等),如果你想要定制或者有其他项目需求请联系我,如果你有好玩的创意也可以评论区留言,或许下期你的创意就会得到实现,我也会@你来见证你的好创意。
我是默,我们下期见。
代码下载
关注本公众号“铁熊玩创客”,回复“M5问卷星”获取完整代码。
欢迎转发朋友圈。如需转载,请注明出处和原作者。
点个赞与在看支持一下吧 ↓↓↓