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

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  1

IPAddress ip(192,168,4,22);
IPAddress gateway(192,168,4,9);
IPAddress subnet(255,255,255,0);
SoftwareSerial mySerial(0,16);//软串口定义,引脚0表示RX,引脚16表示TX

WiFiServer 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);//修改模式为AP
  WiFi.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") {
        @Override
        public 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") {
        @Override
        public 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 ↩︎

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值