作者:默

不知不觉疫情已持续 3 年了,最近门卫王大爷很苦恼,因为领导要求所有进入单位的人员与车辆按照防疫要求进行登记,登记的内容有来访人员的车牌号、姓名、性别、体温、联系电话、被访人及其进入单位的时间 等。大爷年纪大了,有些时候记性也不太好,登记时经常会卡壳,每次都要向来访人员询问老半天,后面来访的人员看着不知所措的王大爷也有些无奈,毕竟大爷就是你大爷,又不能催促,只能老老实实排队等待了。大爷自己也发现了这个问题,于是就找到我,想让我给他出出主意,想一个好办法来解决他现在的问题,提高他的工作效率。

在一些小区和医院等场合,笔者也发现了和王大爷相似的情况:

  • 小区保安需要登记来访人员与车辆;

  • 医院服务的大白与志愿者们需要登记来院所有人员的家庭住址、身份证号、近期外出情况等信息;

  • 临近假期,某些大学辅导员们也要登记学生假期安排情况(留校或回家)。

这些问题都有一个共同的特点:那就是都属于表格统计,仅有简单的文本类信息,传统的这类信息录入都是靠打印纸质表格由相关人员逐一填写,费时费力,遇到问题需要查找某个人员相关信息时,都要从大量的纸质表格中去检索信息,这种古老而传统的方式在数字化的今天无疑是十分落后的。作为实用型 Maker 的我,针对这些痛点问题,我开发了“M5 超级问卷星”这个项目,通过在线的方式将数据进行录入,并且支持在线搜索与查看,以及将数据导出为 Excel 表格。

下面让我们先来看看这个项目的演示视频。该视频中,我们以王大爷的需求为例:

M5 超级问卷星名称的由来

M5 来源于基于 ESP32 的 M5core2 开发板,问卷星代表它是一个问卷调查表格统计类设备,正如前几期分享的 M5 Server X 一样,它们同属于网络服务器的范畴,故名 M5 超级问卷星。运用 M5 超级问卷星,我们能够将任何文本类数据进行在线收集,并在线查看与导出 Excel 表格,例如防疫登记表、离校意向表、商品出售清单等,农林或生物系学生可以将它用来记录一些对照试验的表格,当然 M5 超级问卷星能做的还有很多,只要有表格的地方都会有它的用武之地。

预期目标及功能

  • 在线提交表单数据

  • 数据提交反馈

  • 在线数据查看

  • 在线数据检索

  • 在线数据导出为 Excel 表格

  • SD 卡配置文件加载

  • 二维码显示服务网址

所用硬件

  • M5 Core2

M5Core2特点

  • 基于 ESP32 开发,支持 WiFi、蓝牙;

  • 16 M 闪存,8 M PSRAM;

  • 内置扬声器,电源指示灯,震动马达,RTC,I2S 功放,电容式触摸屏,电源键,复位键;

  • TF 卡插槽(支持最大 16GB);

  • 内置锂电池,配备电源管理芯片;

  • 独立小板内置 6 轴 IMU,PDM 麦克风;

  • M-Bus 总线插座。

程序设计

下面开始详细讲解程序设计过程。

开发环境

我们使用 Aduino IDE 软件来编写本项目的程序,开发板选择 M5Stack-Core2。至于如何在 Arduino 中配置 ESP32 的开发环境,不在本文的介绍范围,请自行查阅相关资料。

程序思路

为了达到我们的预期目标,我们先绘制功能的思维导图,再根据思维导图逐步实现 M5 超级问卷星的程序设计。

下面我们将具体讨论 M5 超级问卷星各个子功能是如何实现的。

获取提交参数

在前期 M5 Server  X 项目中我们采用构造路径参数的方式来区分每个服务,本期我们将采用请求参数的方式获取来自网页表单的数据,示例如下。

#include <WiFi.h>
#include <FS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>const char* ssid = "xxxxxx";
const char* password = "xxxxxx";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/plain", "message received");//反馈网页});server.begin();
}void loop() {}

上传程序,打开串口监视器,我们观察到路由器给 M5 Core2 分配的 IP 地址是 192.168.1.24,记住该地址。

我们通过浏览器访问 http://192.168.1.24/?name=小明&gender=男, 发现浏览器返回数据如下

此时串口监视器返回数据如下:

这里我们有两个参数,分别是 name(姓名)与 gender(性别),它们的值分别是“小明”与“男”。在这里你可以添加其他参数,例如加上年龄等参数,浏览器访问链接再次观察串口输出,寻找规律,理解访问参数的意义。

表单输入

上面的示例,我们通过参数请求的方式获取了提交的数据,那么我们如何更加简单方便的去提交不同参数呢?在网页中有一个叫表单提交的方法可以让我们去提交这个数据,下面我们来以王大爷的需求为例通过编写 HTML 脚本来进行数据的输入。示例如下:

#include <WiFi.h>
#include <FS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>const char* ssid = "xxxxxx";
const char* password = "xxxxxx";const char index_home[] PROGMEM = R"rawliteral(
<html><head><meta charset="utf-8"><title>来访登记</title><meta name="Generator" content="vsCode"><meta name="Author" content="x"><meta name="Keywords" content=""><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"><meta http-equiv="X-UA-Compatible" content="IE=edge chrome=1"><meta name="referrer" content="never"><meta name="format-detection" content="telephone=no,email=no,address=no"><meta name="renderer" content="webkit"><meta name="Description" content=""><style type="text/css">*{margin: 0;padding: 0;}ul,li{list-style:none;}a{text-decoration:none;}input{outline-style:none;border: 0px;}#form1{margin-top:20px;width:80%;margin:2px auto;}#form1 form span{color:#999;}.row { margin-top: 25px;height: 50px;line-height: 26px;border-bottom: 1px solid #c7c6c6;width:100%; }.input { position: relative;width: 100%;height: 26px; }.input input { display: block;width: 100%;height: 26px;border:none;background: none;outline:none; }.input label { position: absolute;top:0;left:0;height: 26px;line-height: 26px;font-size: 14px;color: #999; }.select select{display:block;width:100%;border:none;outline:none;}input[type=date]::-webkit-inner-spin-button { visibility: hidden; }.button{width:100%;height:40px;background: #ff961e;outline: none;margin:20px 0 30px 0;}.button button{border:0px;background-color:transparent;color:white;width: 100%;height:40px;margin:0 auto;font-size:17px;
}</style></head><body><div id="form1"><form action=""><br><div class="row "><div class="input"><span>车牌号码</span><input type="text" placeholder="例如:苏N·OQN64" name="number_plate" value=""></div></div><div class="row "><div class="input"><span>访客姓名</span><input type="text" placeholder="例如:蓟芊" name="name" value=""></div></div><div class="row"><div class="select"><span>性别</span><br><select name="gender"><option value="男">男</option><option value="女">女</option></select><br></div></div>            <div class="row "><div class="input"><span>体温</span><input type="text" placeholder="例如:37.5" name="body_temperature" value=""></div></div>            <div class="row "><div class="input"><span>联系电话</span><br><input type="text" placeholder="例如:18831015911" name="phone_number" value=""></div></div><div class="row"><div class="input"><span>被访人</span><br><input type="text" placeholder="例如:白允" name="interviewee" value=""></div></div><div class="row"><div class="input"><span>进入时间</span><br><input type="text" placeholder="例如:2022/4/28 14:56" name="Entry_time" value=""></div></div>           <div class="button"><button type="submit">提交信息</button></div></form></div></body>
</html>)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 标签决定,例如我们要实现年龄输入,只需替换为下列程序:

<div class="row "><div class="input"><span>年龄</span><input type="text" placeholder="例如:18" name="age" value=""></div>
</div>

这里我们可以通过 w3school在线网页编辑器 实时修改并预览,修改后效果如下:

对于固定格式的数据,如性别、年级等可参考上面的性别例子在网页编辑器中编辑并预览,相信你一定能轻松搞定。

数据可视化

现在我们实现了任意数据表单的输入,那么我们要如何显示数据呢,下面我直接给出网页代码:

<!DOCTYPE html>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><title>来访者记录</title><style type="text/css">#myInput {background-image: url('https://static.runoob.com/images/mix/searchicon.png'); /* 搜索按钮 */background-position: 10px 12px; /* 定位搜索按钮 */background-repeat: no-repeat; /* 不重复图片 */width: 100%;font-size: 16px;padding: 12px 20px 12px 40px;border: 1px solid #ddd;margin-bottom: 12px; }#output {width: 100%;height: 100vh;margin-top: 20px;}#table {border: solid 1px #565656;border-collapse: collapse;font-family: helvetica,serif;font-size: 10pt;width: 180mm;}#table th {border: solid 1px #565656;background-color: #9a7f5b;color: #eee;text-align: center;padding: 5px;}#table td {border: solid 1px #565656;text-align: center;padding: 2px 5px;}</style>
</head>
<body>
<input type="text" id="myInput" onkeyup="myFunction()" placeholder="搜索..."><!-- 设置border="1"以显示表格框线 -->
<table align="center" id="table" border="1"><caption><span style="font-size:20px;">来访者记录</span></caption><thead><tr><th>序号</th><th>车牌号码</th><th>访客姓名</th><th>性别</th><th>体温</th><th>联系电话</th><th>被访人</th><th>进入时间</th></tr></thead><tbody><tr><td>1</td><td>湘P·RBBZG</td><td>邱勤</td><td>男</td><td>36</td><td>1845646545</td><td>小宝</td><td>2022/4/28 14:56</td></td><tr><td>2</td><td>晋B·MIVYF</td><td>简馨</td><td>女</td><td>36</td><td>1845646548</td><td>马克</td><td>12022/4/28 15:56</td></td></tbody>
</table>
<div style="text-align:center"><a>导出表格</a>                     </div><script>// 使用outerHTML属性获取整个table元素的HTML代码(包括<table>标签),然后包装成一个完整的HTML文档,设置charset为urf-8以防止中文乱码var html = "<html><head><meta charset='utf-8' /></head><body>" + document.getElementsByTagName("table")[0].outerHTML + "</body></html>";// 实例化一个Blob对象,其构造函数的第一个参数是包含文件内容的数组,第二个参数是包含文件类型属性的对象var blob = new Blob([html], { type: "application/vnd.ms-excel" });var a = document.getElementsByTagName("a")[0];// 利用URL.createObjectURL()方法为a元素生成blob URLa.href = URL.createObjectURL(blob);// 设置文件名a.download = "来访者记录表格.xls";</script><script>
function myFunction() {// 声明变量var input, filter, table, tr, td, i;input = document.getElementById("myInput");filter = input.value.toUpperCase();table = document.getElementById("table");tr = table.getElementsByTagName("tr");// 循环表格每一行,查找匹配项for (i = 0; i < tr.length; i++) {td = tr[i].getElementsByTagName("td")[1];//("td")[1]这里的1指表头的第2列数据,可根据自己需求改为其他列,例如学号或者车牌号等列,该列一般选择为唯一的数据列if (td) {if (td.innerHTML.toUpperCase().indexOf(filter) > -1) {tr[i].style.display = "";} else {tr[i].style.display = "none";}} }
}</script>
</body>
</html>

将上面的网页代码复制到w3school在线网页编辑器我们得到如下的表格:

在这个网页中我们点击搜索,填写关键字可以在一定范围内搜索数据,例如我们输入 P 效果如下:

点击导出表格将会下载文件“来访者记录表格.xls”,我们打开该 Excel 文件效果如下:

对比 Excel表格与在线表格,可知两者信息以及格式一致。值得注意的是,即使我们输入错误的信息也会被录入,如上图中的访客 简馨 输入的进入时间有误。在这里篇幅有限我们并没有验证,对于有格式要求的信息如邮箱,电话号码等,有需要的读者可以自行学习前端知识改进上面的网页代码以达到自己的要求。

对于上面的HTML文件进行分析,我们可以知道关键的几项数据如下:

<table align="center" id="table" border="1"><caption><span style="font-size:20px;">来访者记录</span></caption><thead><tr><th>序号</th><th>车牌号码</th><th>访客姓名</th><th>性别</th><th>体温</th><th>联系电话</th><th>被访人</th><th>进入时间</th></tr></thead><tbody><tr><td>1</td><td>湘P·RBBZG</td><td>邱勤</td><td>男</td><td>36</td><td>1845646545</td><td>小宝</td><td>2022/4/28 14:56</td></td><tr><td>2</td><td>晋B·MIVYF</td><td>简馨</td><td>女</td><td>36</td><td>1845646548</td><td>马克</td><td>12022/4/28 15:56</td></tr></tbody>
</table>

将上面的表格的数据替换或者增加数据项,并在网页编辑器中编辑并预览你发现了什么规律,想要实现下图中的表格样式又该如何改呢,这个问题留给大家进行思考。

在这个例子中,我们发现,一旦表格的名称与表头信息固定,那么整个表也就随之确定了。其中序号作为必要的数据项放到表格的第一位,为表格排序与统计数据个数,观察 html 表格可知其的最小重复单位如下:

<tr><td>2</td>//固定序号<td>晋B·MIVYF</td><td>简馨</td><td>女</td><td>36</td><td>1845646548</td><td>马克</td><td>12022/4/28 15:56</td></tr>

按照最小重复单元,我们增加数据项与重复单元,在网页编辑器中进行增删改,实时修改并预览,深刻理解 html 表格。

表单输入自动添加到表格

前面我们通过串口监视器观察到了表单数据的提交,那么我们要如何将表单提交的数据添加到表里呢?对于一条数据对应的就是上面的最小重复单元,那么我们可以这样做,对每一条提交的表单数据都将其构造为下面的形式

<tr><td>data1</td>//固定序号<td>data2</td>//第1个提交参数<td>data3</td>//第2个提交参数……<td>dataN</td>//第N个提交参数</tr>

这里我们不需要考虑每一个数据的数据类型到底是整数,小数还是字符串,我们将其构造后将其视为一个字符串即可。对于多条数据,那么我们如何保存它呢?在 C 语言中我们知道保存一个数据我们可以用变量,保存多个数据我们用数组,那么问题来了我们并不知道我们提交的数据有多少条,数组要求我们使用要先定义数组长度,但这里我们的长度显然是无法预知的。那么有没有可变长度的数组呢,答案是肯定的,我们的解决办法是用链表,至于链表是什么大家可以自己百度了解一下,下面我给出一个链表的简单使用例子。

#include <QList.h>QList<String> 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 = "    <tr>\n";
for (int i = 1; i <= paramsNr + 1; i++) {table = String(table) + String(String("     <td>") + String("data") + String(i) + String("</td>\n"));
}
table = String(table) + String("    </tr>\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替换为有效数据

提交表单交互

到这里如果你提交了表单数据,你会发现没有一个交互效果 ,我们并不知道表单数据是否提交成功以及数据提交是否有缺失,当数据不完整时表单数据不应该加入到链表里,只有提交的所有数据参数都不为空字符串时证明数据有效,才能添加到链表,交互的网页代码如下,这是一个提交成功和失败都通用的网页,当然你也可以自己写一个交互网页:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>登记成功</title>
<script type="text/javascript">var num=6;function redirect(){num--;document.getElementById("num").innerHTML=num;if(num<0){document.getElementById("num").innerHTML=0;location.href="/";//回到根目录}}setInterval("redirect()", 1000);
</script>
</head><body onLoad="redirect();">
<div class="b"><p><h1>信息登记成功!</h1><h1><span id="num"></span>秒后回到主页</h1></p></div>
</body>
</html>

代码效果如下:

域名解析

我们可以通过串口监视器获取设备的 IP 地址进行访问,但是路由器的分配的 IP 地址是变化的,这点很不方便,当然你也可以登录路由器后台给 M5Core2 分配一个静态 IP。我们这里采取域名解析的方式通过给 M5Core2 分配一个本地域名,这样我们不需要知道IP地址也能方便的 访问设备,实现代码如下:

#include <ESPmDNS.h>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 <SD.h>
#include <SD_MMC.h>SPIClass sdSPI(VSPI); //定义SD卡软SPI管脚
#define SD_MISO     38
#define SD_MOSI     23
#define SD_SCLK     18
#define SD_CS       4String 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卡SPIif (!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卡修改网络信息即可体验。

使用说明

  1. 烧录固件;

  2. 将附件提供的模板解压到 SD 卡;

  3. 打开 admin.txt 文件修改网络信息(不需要外网访问二维码地址可以不改,如果有使用内网穿透请填写外网地址);

  4. SD 卡放入 M5core2 并重启设备;

  5. 等待 M5core2 初始化并进入二维码显示页面(若初始化错误请按照屏幕显示与语音提示使用正确配置文件或更换 SD 卡);

  6. 访问 http://m5core2/ 按演示视频提交表单数据(体验提交成功与失败两种情况);

  7. 访问 http://m5core2/data 查看数据并导出 Excel 查看;

  8. 尝试修改模板文件自定义表单文件与数据表格重复上述步骤理解本项目。

总结

根据上面的理论基础我们便能完成 M5 超级问卷星的项目制作了,其中具体实现细节由于篇幅限制,这里就不再讨论,大家可以通过附件下载程序源代码进行查看,其中必要的程序说明已经注释,对此项目有任何建议或者疑问均可评论区留言。使用 M5 超级问卷星你能够轻松利用表单收集任意文本信息并且导出为 Excel 表格,如果你会网页前端知识你还可以定制属于自己的网页样式满足个性化,与问卷星不同这个是你个人私有的服务器且可以灵活定制各种功能(数据分类汇总,表格可视化等),如果你想要定制或者有其他项目需求请联系我,如果你有好玩的创意也可以评论区留言,或许下期你的创意就会得到实现,我也会@你来见证你的好创意。

我是默,我们下期见。

代码下载

关注本公众号“铁熊玩创客”,回复“M5问卷星”获取完整代码。


欢迎转发朋友圈。如需转载,请注明出处和原作者。

点个在看支持一下吧 ↓↓↓

ESP32 M5 超级问卷星:轻松收集数据相关推荐

  1. SPSS打开问卷星下载的数据乱码解决办法

    一.用SPSS打开SPSS数据乱码显示如图: 二.直接双击打开SPSS,不要选择打开某个数据文件,点击编辑→选项→语言,点击选择语言环境的书写系统(默认的那个)→点击确定,关闭SPSS.  三.再双击 ...

  2. 问卷:问卷星文本导出数据的多选题,排序题的处理拆分

    问卷星问卷如果选择的是文本导出而非序号导出,多选题和排序题会用 | 分割符号隔开各个选项 以后应该不会用文本导出的数据了,用序号数据 首先要找出要处理的选择题的选项有哪些 然后判断选项是否出现 mul ...

  3. 问卷星问卷数据怎么快速导入SPSSAU?

    最近收到小伙伴询问问卷星导入的问卷数据怎么编码? 现在的问卷调查,很多都是通过网络问卷的方式进行,问卷星是一个专业的在线问卷调查.测评投票平台,如果你的问卷正好是在问卷星网站发放,填答,回收数据,那太 ...

  4. 你还在用问卷星?微信制作调查问卷他来了。

    问卷星,金数据之类的调查问卷工具用到最后发现都是要收费了,但是大部分人根本不想付费去制作问卷调查. 免费的问卷调查工具:使用直接再微信小程序搜索"创建问卷" 今天要说的是这个问卷调 ...

  5. 自动填写个人信息(问卷星)

    文章目录 1. 问题背景 2. 自动填写方法 3. 测试结果 1. 问题背景 公司通过问卷星收集个人相关信息,学校通过问卷星开展电影抢票,传统手动输入文字信息已不能满足高效.快速填写问卷的强烈需求.为 ...

  6. 如何设计问卷,才能收集到高质量的客户体验数据?

    1997年的初夏,农夫山泉董事长钟睒晱(shǎn)眉头紧锁地坐在办公桌前,他要为公司即将推出的农夫山泉矿泉水选一句主广告语,但无法在"农夫山泉有点甜"."好水喝出健康来& ...

  7. 可爱的 Python: 使用 mechanize 和 Beautiful Soup 轻松收集 Web 数据

    可爱的 Python: 使用 mechanize 和 Beautiful Soup 轻松收集 Web 数据 使用 Python 工具简化 Web 站点数据的提取和组织 David Mertz, Ph. ...

  8. java问卷导入excel,将Excel数据直接上传到问卷星

    一.什么样的数据可以上传 1.如果您已经有了一批报名者的数据,需要使用到问卷星的签到功能.可以将整理好的报名者数据,上传到系统中. 2.如果您有一批数据需要针对个人公开,可以先将数据上传到问卷星,再设 ...

  9. Python问卷星批量填写,支持数据自定义分布

    Python问卷星调查问卷批量填写,支持树自定义分布 脚本环境 python3+基本python网络生态库 方法 self.wjxNumber改成填写的数量 self.wjxdata改成自己的数据分布 ...

  10. python与网页交互_可爱的 Python: 使用 mechanize 和 Beautiful Soup 轻松收集 Web 数据

    可爱的 Python 使用 mechanize 和 Beautiful Soup 轻松收集 Web 数据 使用 Python 工具简化 Web 站点数据的提取和组织 David Mertz 2010 ...

最新文章

  1. 平面广告设计和Web设计的差别
  2. MongoDB探索之路(二)——系统设计之CRUD
  3. html表格数据循环展示,MVC在View中循环数据在table中显示
  4. BZOJ-1005-明明的烦恼
  5. [swustoj 856] Huge Tree
  6. git revert 回滚代码至上一版本
  7. 电脑上没有tts信息服务器,TTS——让你的电脑会说话-win7 tts
  8. 如何用css写出一个三角形
  9. opensatck 分布式路由模式DVR部署
  10. 微信小程序|做一个底部评论视图
  11. Win10系统把桌面变成苹果iOS界面的小技巧
  12. VisionPro基础篇(一): VisionPro界面介绍
  13. (转)四旋翼飞行器基本知识
  14. 纯前端JS实现一个登记照改换底色背景色功能
  15. 计算机专业先考研先上班,如何准备考研工作?
  16. python中正负号怎么表示_[转载]python中整数除法的正负号
  17. 基于JavaScript的简易计算器(可处理连续加减乘除运算)
  18. python如何调用函数计算出成绩的不及格率_(求excle公式:95分以上为优、94分-85分为良、84分-75分为中、74分-60分为合格、60分以下为不合格)excel成绩及格率...
  19. 代码随想录回溯算法——重新安排行程
  20. 【学习】MybatisPlus + ShardingSphere 分表对象使用updateById方法自动补齐分表属性

热门文章

  1. 邮件群发怎么发?解密邮件群发软件小技巧
  2. 实现写邮箱html页面,用html写的简单的邮箱登陆界面
  3. MarkDown学习手册
  4. 启发式搜索--八数码问题
  5. 水务信息化数据整合系统方案分析
  6. sqlite3用法详解
  7. Unity解析XML文件
  8. depot_tools
  9. 卡巴6kis最新激活码
  10. 我的未来式计算机简谱,我的未来式简谱-爱情公寓歌曲-孙世彦曲谱