1 介绍

暑假里老师给布置了个任务,希望能够避开云端,使用移动端来获取 WeMos D1 上传感器的数据。
那么这里大致的思路就是:

  • 通过串口获取传感器的数据;
  • 在WeMos D1上开启服务以供移动端访问
  • 编写移动端APP

2 系统设计与实现

2.1 系统整体架构设计

本系统主要以WeMos D1为核心,在其上开启Socket服务,将通过串口获得的传感器数据,提供给移动端。

2.2 WeMos D1端

俗话说便宜没好货,WeMos D1这块板子就符合这个道理,在博主拿到这块板子和传感器的时候,就发现了一个问题——板子的硬件串口就只有一个,而且这个串口是另有用途的,所以不能用来获取传感器的数据,因此在这里我们需要用到软串口。所幸的是,在Arduino IDE这个编程环境中,已经给我们提供了软串口1的库,我们只需要像硬件串口一样对其他引脚进行操作便可。对软串口不懂的朋友,可以看下注释1的链接。
这里博主选用了引脚0和引脚16来作为软串口的RX和TX,如图:

为了让移动端能够点对点连接上WeMos D1,这里WeMos D1需要开启AP模式,以使得移动端连接其WiFi构建局域网。
接着使用Arduino IDE中ESP8266的库函数2来开启Socket服务。不知道怎么开启的,请看注释2的链接。

2.3 移动端

移动端的构建非常简单,就是一个很普通的Socket客户端。
其布局如图所示:

因为移动端博主还有其它用途,所以这个布局有点多余的功能,各位看客要写的话,是不需要那么多的按钮的。
这里IP和Port在代码中已经有默认输入,所以不输入直接点击采集数据即可。

3 代码

3.1 获取JY-901加速度数据

在购买JY-901九轴加速度传感器时,店家随了一份传感器资料过来,其中有一份用Arduino UNOR3获取JY-901串口数据的代码,该代码使用Arduino IDE进行编写,首先看工程文件“JY901Serial.ino”:

#include <Wire.h>
#include <JY901.h>
/*
Test on Uno R3.
JY901   UnoR3
TX <---> 0(Rx)
*/
void setup()
{Serial.begin(9600);
}void loop()
{//print received data. Data was received in serialEvent;Serial.print("Time:20");Serial.print(JY901.stcTime.ucYear);Serial.print("-");Serial.print(JY901.stcTime.ucMonth);Serial.print("-");Serial.print(JY901.stcTime.ucDay);Serial.print(" ");Serial.print(JY901.stcTime.ucHour);Serial.print(":");Serial.print(JY901.stcTime.ucMinute);Serial.print(":");Serial.println((float)JY901.stcTime.ucSecond+(float)JY901.stcTime.usMiliSecond/1000);Serial.print("Acc:");Serial.print((float)JY901.stcAcc.a[0]/32768*16);Serial.print(" ");Serial.print((float)JY901.stcAcc.a[1]/32768*16);Serial.print(" ");Serial.println((float)JY901.stcAcc.a[2]/32768*16);Serial.print("Gyro:");Serial.print((float)JY901.stcGyro.w[0]/32768*2000);Serial.print(" ");Serial.print((float)JY901.stcGyro.w[1]/32768*2000);Serial.print(" ");Serial.println((float)JY901.stcGyro.w[2]/32768*2000);Serial.print("Angle:");Serial.print((float)JY901.stcAngle.Angle[0]/32768*180);Serial.print(" ");Serial.print((float)JY901.stcAngle.Angle[1]/32768*180);Serial.print(" ");Serial.println((float)JY901.stcAngle.Angle[2]/32768*180);Serial.print("Mag:");Serial.print(JY901.stcMag.h[0]);Serial.print(" ");Serial.print(JY901.stcMag.h[1]);Serial.print(" ");Serial.println(JY901.stcMag.h[2]);Serial.print("Pressure:");Serial.print(JY901.stcPress.lPressure);Serial.print(" ");Serial.println((float)JY901.stcPress.lAltitude/100);Serial.print("DStatus:");Serial.print(JY901.stcDStatus.sDStatus[0]);Serial.print(" ");Serial.print(JY901.stcDStatus.sDStatus[1]);Serial.print(" ");Serial.print(JY901.stcDStatus.sDStatus[2]);Serial.print(" ");Serial.println(JY901.stcDStatus.sDStatus[3]);Serial.print("Longitude:");Serial.print(JY901.stcLonLat.lLon/10000000);Serial.print("Deg");Serial.print((double)(JY901.stcLonLat.lLon % 10000000)/1e5);Serial.print("m Lattitude:");Serial.print(JY901.stcLonLat.lLat/10000000);Serial.print("Deg");Serial.print((double)(JY901.stcLonLat.lLat % 10000000)/1e5);Serial.println("m");Serial.print("GPSHeight:");Serial.print((float)JY901.stcGPSV.sGPSHeight/10);Serial.print("m GPSYaw:");Serial.print((float)JY901.stcGPSV.sGPSYaw/10);Serial.print("Deg GPSV:");Serial.print((float)JY901.stcGPSV.lGPSVelocity/1000);Serial.println("km/h");Serial.println("");delay(500);
}
/*
SerialEvent occurs whenever a new data comes in the hardware serial RX. This routine is run between each time loop() runs, so using delay inside loop can delay response. Multiple bytes of data may be available.
*/
void serialEvent()
{while (Serial.available()) {JY901.CopeSerialData(Serial.read()); //Call JY901 data cope function}
}

阅读这么一个工程文件,第一步先看setup函数,只有一个以波特率9600打开串口的操作,第二步loop函数,以本博客要获取的加速度数据为例:

Serial.print("Acc:");Serial.print((float)JY901.stcAcc.a[0]/32768*16);Serial.print(" ");Serial.print((float)JY901.stcAcc.a[1]/32768*16);Serial.print(" ");Serial.println((float)JY901.stcAcc.a[2]/32768*16);

可看到loop函数中都是输出对象JY901中数据的语句,由此推测JY901中存放的便是传感器获取到的数据。而后第三步serialEvent函数,这个函数的作用是获取串口数据,并用函数CopeSerialData对串口数据进行处理并存入对象JY901中,注释中也提到,serialEvent函数在loop函数循环时,同步运行。
下面是函数CopeSerialData源码:

void CJY901 ::CopeSerialData(unsigned char ucData)
{static unsigned char ucRxBuffer[250];static unsigned char ucRxCnt = 0;    ucRxBuffer[ucRxCnt++]=ucData;if (ucRxBuffer[0]!=0x55) {ucRxCnt=0;return;}if (ucRxCnt<11) {return;}else{switch(ucRxBuffer[1]){case 0x50: memcpy(&stcTime,&ucRxBuffer[2],8);break;case 0x51:  memcpy(&stcAcc,&ucRxBuffer[2],8);break;case 0x52:   memcpy(&stcGyro,&ucRxBuffer[2],8);break;case 0x53:  memcpy(&stcAngle,&ucRxBuffer[2],8);break;case 0x54: memcpy(&stcMag,&ucRxBuffer[2],8);break;case 0x55:   memcpy(&stcDStatus,&ucRxBuffer[2],8);break;case 0x56:   memcpy(&stcPress,&ucRxBuffer[2],8);break;case 0x57: memcpy(&stcLonLat,&ucRxBuffer[2],8);break;case 0x58:    memcpy(&stcGPSV,&ucRxBuffer[2],8);break;}ucRxCnt=0;}
}

因此只要能够从串口读到传感器数据,并用CopeSerialData函数对串口数据进行处理,即可获得需要的加速度数据。

3.2 在WeMos D1端启动服务

WeMos D1端代码解释请看注释,代码如下:

#include<ESP8266WiFi.h>
#include<SoftwareSerial.h>#define AP_SSID     "ESP8266WiFi"
#define AP_PSW      "88888888"
#define SERVER_MAX  1IPAddress ip(192,168,4,22);
IPAddress gateway(192,168,4,9);
IPAddress subnet(255,255,255,0);
SoftwareSerial mySerial(0,16);//软串口定义,引脚0表示RX,引脚16表示TXWiFiServer server(8000);//定义socket服务,端口8000
WiFiClient clients[SERVER_MAX];//管理与socket服务相连的客户端
struct Acceleration
{short acc[3];//因为只需要加速度传感器,所以不必如传感器资料里一样定义一个类,只需定义一个代表加速度的结构体即可short Time;
}acceleration;void setup() {mySerial.begin(9600);//开启热点,移动移动端连接WiFi,与WeMos D1构建局域网WiFi.mode(WIFI_AP);//修改模式为APWiFi.softAPConfig(ip, gateway, subnet);//设置AP相关网络参数//启动socket服务server.begin();server.setNoDelay(true);//关闭小包合并包功能,不会延时发送数据
}void loop() {uint8_t i;if(server.hasClient()){//如果有新客户端连接,释放客户端列表中失效客户端,并将新客户端加入for(i=0;i<SERVER_MAX;i++){if(!clients[i]||!clients[i].connected()){if(clients[i]){clients[i].stop();}clients[i]=server.available();break;}}//若客户端列表已满,则拒绝新客户端连接if(i==SERVER_MAX){WiFiClient _client=server.available();_client.stop();}}if(mySerial.available()){//读取串口数据,并使用CopeSerialData函数进行处理if(CopeSerialData(mySerial.read())){//将获取到的加速度数据经过处理,通过socket发送给移动端//在要发送的数据前后分别加上begin和end,以供移动端更容易的识别数据头尾char sbuf0[10]="begin ",sbuf1[10],sbuf2[10],sbuf3[10],sbuf4[10]="end\n";//将加速度类型由浮点型转化为字符串dtostrf((float)acceleration.acc[0]/32768*16,2,2,sbuf1);dtostrf((float)acceleration.acc[1]/32768*16,2,2,sbuf2);dtostrf((float)acceleration.acc[2]/32768*16,2,2,sbuf3);//将加速度数据依次发送给每一个连接的客户端for(i=0;i<SERVER_MAX;i++) {if(clients[i]&&clients[i].connected()) {clients[i].write(sbuf0, 6);clients[i].write(sbuf1, Judge(sbuf1));clients[i].write(sbuf2, Judge(sbuf2));clients[i].write(sbuf3, Judge(sbuf3));clients[i].write(sbuf4, 4);delay(1);}}}}
}bool CopeSerialData(unsigned char ucData){static unsigned char ucRxBuffer[250];static unsigned char ucRxCnt = 0;ucRxBuffer[ucRxCnt++]=ucData;//JY-901传感器规定有效数据由0x55开始if(ucRxBuffer[0]!=0x55){ucRxCnt = 0;return false;}//规定每11个数据为一次有效数据if(ucRxCnt<11){return false;}else{switch(ucRxBuffer[1]){//只需要加速度数据,因此只需判断0x51即可,若要获取别的数据,请看上面完整的CopeSerialData函数源码case 0x51:memcpy(&acceleration,&ucRxBuffer[2],8);break;}ucRxCnt=0;return true;}
}int Judge(char str[]){//计算socket连接中发送消息的长度,并在消息后面补上一个空格int len;for(len=0;len<10;len++){if(str[len]=='-'||(str[len]>='0'&&str[len]<='9')||str[len]=='.'){continue;}else{break;}}str[len]=' ';return len+1;
}

3.3 移动端获取数据

移动端最主要的就是一个socket连接问题,首先是socket建立:

protected void SocketConnect() {ip = inputIP.getText().toString();port = inputPort.getText().toString();//WeMos D1的IP地址和端口一开始就有默认值,但也可由用户自行输入if (!IsIPPortLegal()) {Toast.makeText(MainActivity.this, "填写不正确,已使用默认IP和端口", Toast.LENGTH_SHORT).show();ip = "192.168.4.22";port = "8000";}//Android开发中socket连接必须要以线程的方式才可以进行Thread thread = new Thread("Connect") {@Overridepublic void run() {super.run();if (!socketStatus) {try {socket = new Socket(ip, Integer.parseInt(port));if (socket != null) {//socket建立成功inputStream = socket.getInputStream();outputStream = socket.getOutputStream();//socketStatus是一个标志,避免socket还未连接就用socket获取数据的错误socketStatus = true;}} catch (IOException e) {//socket建立失败e.printStackTrace();}}}};thread.start();
}

接着就是利用socket获取数据:

protected void SocketGetData() {//同样要新建一个线程Thread thread = new Thread("GetData") {@Overridepublic void run() {super.run();//判断socket是否已经连接while(!socketStatus){};if (socketStatus) {try {//获取数据int len = 0;byte[] buf = new byte[1024];String tmp = "";int count = 0;while (getDataStatus && ((len = inputStream.read(buf)) != -1)) {String _tmp = new String(buf, 0, len);tmp += _tmp;count++;if (count == 10) {DealWithInputStream(tmp);count = 0;tmp = "";}}} catch (IOException e) {e.printStackTrace();}}}};thread.start();
}protected void DealWithInputStream(String tmp) {//按照与WeMos D1约定的格式,使用正则表达式提取加速度数据,并存入文件dataCenter中Pattern pattern = Pattern.compile("begin .* end");Matcher matcher = pattern.matcher(tmp);FileOutputStream out = null;BufferedWriter writer = null;try {out = openFileOutput("dataCenter", Context.MODE_APPEND);writer = new BufferedWriter(new OutputStreamWriter(out));while (matcher.find()) {String _tmp = "" + System.currentTimeMillis() + " " + matcher.group() + (dataStatus ? " 1\r\n" : " 0\r\n");Log.d("tag", _tmp);writer.write(_tmp);}} catch (IOException e) {e.printStackTrace();} finally {try {if (writer != null) {writer.close();}} catch (IOException e) {e.printStackTrace();}}
}

4 遇到的问题

  • 一开始使用Micropython写的代码,但是它没有软串口的库,IIC有关的例子又少,没看懂,后来换了C语言,用Arduino IDE编写代码,果然这种硬件有关的东西还是C语言好用啊。
  • 写APP的时候,代码明明没错,但是Socket却总是建立失败,而后排查问题发现,是没有获取权限的问题。
  • 有天开机的时候电脑挂了【ps:神舟电脑伤不起…】,而后重装电脑,将Android Studio重装,导入之前的项目,发现怎么都运行不了,谷歌了好久都没解决,后来灵机一动,新建了个项目,把代码复制粘贴进去,运行成功。
  • 在写APP的时候数据持久化遇到了个问题,存下来的数据找不到了,谷歌后才知道是在data/data/包名/file文件夹下面
  • 暂时想不起别的问题了。
  • 各位看官有问题请留言。

  1. 软串口通信——SoftwareSerial库的使用 ↩︎

  2. 博哥零基础教你玩转ESP8266 ↩︎

移动端点对点获取WeMos D1上搭载的JY-901九轴振动加速度传感器加速度数据相关推荐

  1. wemos D1 Mini (esp8266)实验九 --- blynk APP远程控制格力空调开机并显示温湿度DHT22

    物联网-wemos D1 Mini (esp8266)实验九 --- blynk APP远程控制格力空调开机并显示温湿度 概述 实验材料 硬件 硬件连接图 引脚连接对照表 Arduino端软件编写 测 ...

  2. Android 手机获取加速度传感器加速度数据并自定义采样频率

    原文链接:http://blog.csdn.net/llp1992/article/details/41786865 因为项目需要利用到了Android手机中的加速度传感器来获取三个加速度轴的加速度大 ...

  3. 物联网-wemos D1 Mini (esp8266)实验十 ---服务器版温度计

    物联网-wemos D1 Mini (esp8266)实验十 ---服务器版温度计 概述 实验材料 硬件连接图 引脚对照表 DHT库的引入 网页代码 Arduino端代码 效果展示 概述 Wemos ...

  4. Arduino系列-Wemos D1 WIFI UNO R3开发版的智能家居

    一.开发版介绍 Wemos D1有十一个数字输出引脚(包括RX和TX),除了D0引脚之外,其余的十个引脚均支持PWM.I2C.中断和单总线.有一个模拟引脚(最大3.3V输入).它的形状和Arduino ...

  5. Wemos D1串口通信

    串口通信控制Wemos D1上的蜂鸣器 一.Wemos D1与蜂鸣器的连接 1.1控制代码 1.2项目总结 二.开发板+HC-SRO4超声波模块控制蜂鸣器 2.1模块讲解和接线 2.2控制代码 2.3 ...

  6. 基于Wemos D1的感应开盖垃圾桶

    参考:基于Wemos D1的感应开盖垃圾桶(增加自己的代码实现部分) 作者:LEO-max 发布时间:2020-12-29 15:21:26 网址:https://blog.csdn.net/zouc ...

  7. esp8266单片机透传_基于WeMos D1(ESP8266)的校园卡门禁系统

    刷卡https://www.zhihu.com/video/1107591334902345728 电脑端控制https://www.zhihu.com/video/11075913630797332 ...

  8. 阿里云IOT入门教程(三)阿里云IOT Studio自建手机App控制Wemos D1 Mini( ESP8266 )板载灯亮灭

    阿里云IOT入门教程(一)阿里云IOT Studio自建手机App控制Wemos D1 Mini( ESP8266 )板载灯亮灭 概述 所需材料 Mqtt预备知识 hacklab端开发 * 硬件端上报 ...

  9. 基于WeMos D1+esp8266的智能垃圾桶和智能家居

    1.环境搭建 本次主要是用ArduinoIDE平台来进行开发,可以去官网进行下载:Arduino - Home 也可去网盘下载,在这里为大家准备了网盘连接: 链接:https://pan.baidu. ...

最新文章

  1. 手动将jar添加到maven仓库中
  2. 安卓学习笔记18:常用控件 - 按钮、图像视图和图像按钮
  3. [冷枫推荐]:数据库操作,内外联查询,分组查询,嵌套查询,交叉查询,多表查询,语句小结。...
  4. Linux终端的概念
  5. 创建单IP的×××网络
  6. python冰雪奇缘使用教程_99 行 Python 代码实现《冰雪奇缘》特效?网友:大神碉堡!...
  7. html在页面显示一个正方形,CSS实现一个自适应的正方形的方法示例
  8. 嵌入式 在开发板显示bmp图片、jpeg图片
  9. webstorm 移动到末尾并且换行快捷键
  10. log4j2远程代码执行漏洞学习总结
  11. 探店「无聊猿」BAYC 主题餐厅:食物都很棒,口味超预期,有点小贵
  12. autojs之大柒侧滑栏详解
  13. 解决CSS IOS字体自动调整放大了
  14. HTML/设置网页背景图片+背景透明度设置
  15. VCS makefile文件
  16. 关于xshell突然就连不上虚拟机的解决方法(之前可以正常连接)
  17. 日志打印工具Lcat
  18. Kahn算法-拓扑排序
  19. 自由的百科全书 Wikipedia 18 周岁了
  20. ubuntu20.04常用命令(自用)

热门文章

  1. 同样可用来建站,云服务器和硅云虚拟主机到底哪个更好?
  2. 太空船海洋号(spaceship)
  3. TRC丨艾美捷TRC羟基Telaprevir-d4说明书
  4. 【时间序列】N-BEATS:对可解释时序预测的神经基础扩展分析
  5. 电容笔哪个品牌好?平板电脑好用的电容笔测评
  6. c语言mfc写贪吃蛇,手把手教你用MFC编写贪吃蛇.doc
  7. 电脑的计算机控制面板都打不开怎么办,控制面板打不开怎么办解决方案
  8. 在html5中怎么设置背景音乐,HTML5简单实现添加背景音乐的几种方法
  9. 十进制小数或带小数的十进制转二进制--控制小数位数输出(C语言)
  10. 微服务架构-系统可靠性保障