zigbee物联网开发平台(工业物联网)

1.概述鉴于ZigBee技术适合用于数据采集系统的的特点,提出了基于ZigBee的数据采集系统的设计方案,着重探讨ZigBee节点的硬件设计及其组网设计.并详细讨论了基于CC2530芯片的数据采集节点的硬件设计方案,组网设计中的协调器建立网络、节点加入网络的设计方法,以及数据采集系统的软件设计方法.最后通过采集ZigBee网络传感器数据的实验,证明该方案能取得良好的通信效果.

大家好,又见面了,我是你们的朋友全栈君。

1.概述
鉴于ZigBee技术适合用于数据采集系统的的特点, 提出了基于ZigBee的数据采集系统的设计方案, 着重探讨ZigBee节点的硬件设计及其组网设计. 并详细讨论了基于CC2530芯片的数据采集节点的硬件设计方案, 组网设计中的协调器建立网络、节点加入网络的设计方法, 以及数据采集系统的软件设计方法. 最后通过采集ZigBee网络传感器数据的实验, 证明该方案能取得良好的通信效果.
1.1 系统描述
利用ZigBee传感器网络、网关、服务器实现简单的数据采集系统。项目中把使用了三种传感器,分别是:温湿度传感器,烟雾传感器,光敏传感器。终端节点传感器采集到数据之后发送给协调器之后,由协调器通过串口将数据发至给电脑客户端,最后电脑客户端将串口发送上来的传感器数据使用套接字封装成http格式后通过post方式发送到服务端,并且存储到数据库中。客户端通过访问服务器,获取数据展示出来。
1.2 系统结构介绍
这里写图片描述


图1:系统结构图
如图1所示,本系统主要有传感器节点、协调器、网关、服务器、客户端四个部分组成。传感器部分采集温湿度,可燃气,光照数据。通过使用Zstack协议栈来进行zigbee传感器节点组网,使用c语言来控制zigbee终端节点io口采集到传感器数据,协调器再将数据通过串口上传到网关。网关部分从串口接收来自协调器发来的传感器数据,并以及解析从串口收到的数据,并且把数据封装成http格式通过post方式可以实现远程发送数据到服务器。客户端使用python从服务器获取数据,设计图形界面用于数据展示。
1.3 软硬件开发平台
软件:
VMware Workstation(linux OS)
TCP&UDP测试工具(测试)
Configure Virtual Serial Port Driver(测试)
IAR Embedded Workbench(CC2530 传感器)
sublime(C 网关)
IntelliJ IDEA 2017.1.1(JAVA 服务器)
JetBrains CLion 2016.3.4(Python 显示)
xampp(数据库)
串口调试助手(测试)
硬件:温湿度传感器、光照传感器、可燃气传感器、CC2530开发板*4
1.4 实现功能
通过传感器采集环境数据,并将采集到的数据进行处理后在客户端做展现,客户端可以查询某一日下的数据变化情况。
2 系统设计方案
数据格式:
• FF00 01 0600 1A 27
• FF00: 包标志 2Byte
• 01: 传感器类型 1Byte
• 0600: 短地址 2Byte
• 1A27: 传感器数据 1A:温度26 27:湿度39
• 传感器类型数据大小
• 温湿度 PACKET_DHT11_DATA_SIZE 2byte
• 光敏 PACKET_PHOTORESUSTANCE_DATA_SIZE 1byte
• 可燃气体 PACKET_COMBUSTIBLEGAS_DATA_SIZE 1byte
3 系统具体实现 (项目整体实现细节、各个模块的流程,关键代码)
传感器部分:
整体流程:
这里写图片描述
传感器节点接入协调器网络,封装传感器数据

 if ( event & MY_REPORT_EVT )      // MY_REPORT_EVT事件触发处理
  {
    if ( appState == APP_REPORT ) 
    {
      pData[PACKET_FLAG_OFFSET] = PACKET_L_FLAG;
      pData[PACKET_FLAG_OFFSET + 1] = PACKET_H_FLAG;
      pData[PACKET_SENSOR_SHORT_ADDR_OFFSET] = (NLME_GetShortAddr() & 0xff);
      pData[PACKET_SENSOR_SHORT_ADDR_OFFSET + 1] = (NLME_GetShortAddr() >> 8);
#if PACKET_SENSOR_TYPE == PACKET_DHT11
    dht11_update(); 
    pData[PACKET_SENSOR_TYPE_OFFSET] = PACKET_DHT11;
    pData[PACKET_SENSOR_DATA_OFFSET] = dht11_temp();
    pData[PACKET_SENSOR_DATA_OFFSET + 1] = dht11_humidity();
#elif PACKET_SENSOR_TYPE == PACKET_PHOTORESUSTANCE
    pData[PACKET_SENSOR_TYPE_OFFSET] = PACKET_PHOTORESUSTANCE;
    pData[PACKET_SENSOR_DATA_OFFSET] = HalAdcRead (HAL_ADC_CHN_AIN1,HAL_ADC_RESOLUTION_8);
#else
    pData[PACKET_SENSOR_TYPE_OFFSET] = PACKET_COMBUSTIBLEGAS;
    pData[PACKET_SENSOR_DATA_OFFSET] = HalAdcRead (HAL_ADC_CHN_AIN1,HAL_ADC_RESOLUTION_8);

上传数据包结构
【包标志】【传感器类型】【短地址】【数据】
包标志 2Byte FF00
传感器类型
温湿度 2byte 传感器类型编号 01
光敏 1byte 传感器类型编号 02
可燃气体 1byte 传感器类型编号 03
将数据包发送给协调器

#if PACKET_SENSOR_TYPE == PACKET_DHT11
      zb_SendDataRequest( 0xFFFE, SENSOR_REPORT_CMD_ID, PACKET_BASE_SIZE + PACKET_DHT11_DATA_SIZE, pData, 0, AF_TX_OPTIONS_NONE, 0 );
#elif PACKET_SENSOR_TYPE == PACKET_PHOTORESUSTANCE
      zb_SendDataRequest( 0xFFFE, SENSOR_REPORT_CMD_ID, PACKET_BASE_SIZE + PACKET_PHOTORESUSTANCE_DATA_SIZE, pData, 0, AF_TX_OPTIONS_NONE, 0 );
#else
      zb_SendDataRequest( 0xFFFE, SENSOR_REPORT_CMD_ID, PACKET_BASE_SIZE + PACKET_COMBUSTIBLEGAS_DATA_SIZE, pData, 0, AF_TX_OPTIONS_NONE, 0 );

协调器接收到数据包通过串口上传

void zb_ReceiveDataIndication( uint16 source, uint16 command, uint16 len, uint8 *pData  )
{ 
  HalUARTWrite(HAL_UART_PORT_0, pData, len);
}

协调器接收到网关发送的命令做出命令判断后将其发送给传感器节点


  while (Hal_UART_RxBufLen(port))		//计算并返回接收缓冲区的长度
  {
    len = HalUARTRead (port, pBuf, RX_BUF_LEN);
    HalUARTWrite (port, pBuf, len);
    if(len >= PACKET_BASE_SIZE 
       && pBuf[PACKET_FLAG_OFFSET] == PACKET_L_FLAG 
         && pBuf[PACKET_FLAG_OFFSET + 1] == PACKET_H_FLAG)
    {
      zb_SendDataRequest( *(uint16 *)(pBuf + PACKET_SENSOR_SHORT_ADDR_OFFSET), OPERATION_CMD_ID, len, pBuf, 0, AF_TX_OPTIONS_NONE, 0 );
    }
  }


命令数据包结构
【包标志】【操作指令】【对应传感器短地址】
包标志 2Byte FF00
操作指令 1Byte
01 LED开启
02 LED关闭
03 LED状态反转
04 LED闪烁
对应传感器短地址 2Byte
传感器节点接收到协调器控制数据包并解析做出相应的反应

void zb_ReceiveDataIndication( uint16 source, uint16 command, uint16 len, uint8 *pData  )
{
  if(len >= PACKET_BASE_SIZE 
     && pData[PACKET_FLAG_OFFSET] == PACKET_L_FLAG 
     && pData[PACKET_FLAG_OFFSET + 1] == PACKET_H_FLAG
     && *(uint16 *)(pData + PACKET_SENSOR_SHORT_ADDR_OFFSET) == NLME_GetShortAddr())
  {
    switch(pData[PACKET_OPERATION_TYPE_OFFSET])
    {
      case PACKET_ON:
        HalLedSet( HAL_LED_1, HAL_LED_MODE_ON );
        break;
      case PACKET_OFF:
        HalLedSet( HAL_LED_1, HAL_LED_MODE_OFF );
        break;
      case PACKET_TOGGLE:
        HalLedSet( HAL_LED_1, HAL_LED_MODE_TOGGLE );
        break;
      case PACKET_BLINK:
        HalLedBlink(HAL_LED_1, 0, 50, 500);
        break;
      default:
        break;
      }
  }
}


网关部分:
程序流程:
这里写图片描述
关键代码:

//serial part串口部分
    	cssl_t *ser;     
    	cssl_start();
    	ser=cssl_open(dev_path,callback,0,38400,8,0,1);   
    	if (!ser){
        		printf("%s\n",cssl_geterrormsg());
    	}
    	printf("Serial connection successful, DEV = %s\n", dev_path);

//create socket
建立socket 建立一个sock连接

sockfd = socket( AF_INET,SOCK_STREAM,0 );

	if ( sockfd < 0 )
	{
		fprintf( stderr, "socket:%s\n", strerror(errno));
		exit(1);		
	}
	else{
		printf("success to connect the service\n");
	}

	struct sockaddr_in sockaddr;
	//设置连接信息结构
	memset( &sockaddr,0,sizeof(sockaddr) );
	sockaddr.sin_family = AF_INET;
	sockaddr.sin_port = htons ( atoi (port) );
	inet_pton(AF_INET,ip,&sockaddr.sin_addr.s_addr);
	socklen_t len = sizeof(sockaddr);
	//连接到远端服务器 
	if( connect(sockfd,(struct sockaddr*)&sockaddr,len) <0 ){
		fprintf(stderr, "connect: %s\n", strerror(errno) );
		exit(1);
	}

回调函数,打印串口收到的数据的信息,在这个方法中对串口收到的数据进行解析,并且调用通过socket发送数据给服务器

static void callback(int id, uint8_t *buf,int length)    
{
    int i;
    char dtype[2],data[50];
    printf("the date from serial:%s\n", buf);
    printf("\nread serial success:  length = %d\n", length); 
    sprintf(dtype,"%d",buf[2]);
    switch(dtype[0]){
	case '1':
		sprintf(data,"wendu=%d&shidu=%d",buf[5],buf[6]);
		puts(data);
		sendDatawenshidu(data,strlen(data));
		break;
	case '2':
		sprintf(data,"gz=%d",buf[5]);
		puts(data);
		sendDataguangzhao(data,strlen(data));
		break;
	case '3':
		sprintf(data,"gs=%d",buf[5]);
		puts(data);
		sendDataGas(data,strlen(data));
		break;
	default:
		puts("the date is error"); 
		break;
	}
}

把数据封装成http格式,并且通过post方式发送传感器数据

void sendDatawenshidu(uint8_t *buf,int length)
{
    printf("buf:%s\n",buf);
    //接口
    char page[] = "addWendu";  
    char content[4096];  
    char content_page[50];  
    char content_host[50];  
    char content_len[50]; 
    //1请求行
sprintf(content_page,"POST /%s HTTP/1.1\r\n",page);  

//把数据封装成http格式,并且通过post方式发送光照值数据
void sendDataguangzhao(uint8_t *buf,int length)
{
    printf("buf:%s\n",buf);
    char page[] = "Guangzhao";  
    char content[4096];  
    char content_page[50];  
    char content_len[50];  
    char content_host[50]; 
    sprintf(content_page,"POST /%s HTTP/1.1\r\n",page);  
    sprintf(content_host,"HOST: %s:%d\r\n",ip,port);  
    char content_type[] = "Content-Type: application/x-www-form-urlencoded\r\n";  
    sprintf(content_len,"Content-Length: %d\r\n\r\n",strlen(buf));  
    sprintf(content,"%s%s%s%s%s",content_page,content_host,content_type,content_len,buf);  
    send(sockfd,content,strlen(content),0);  
}
//把数据封装成http格式,并且通过post方式发送可燃气体值数据
void sendDataGas(uint8_t *buf,int length)
{
    printf("buf:%s\n",buf);
    char page[] = "Gas";  
    char content[4096];  
    char content_page[50];  
    char content_host[50];
    char content_len[50]; 
    sprintf(content_page,"POST /%s HTTP/1.1\r\n",page);       
    sprintf(content_host,"HOST: %s:%d\r\n",ip,port);  
    char content_type[] = "Content-Type: application/x-www-form-urlencoded\r\n";   
    sprintf(content_len,"Content-Length: %d\r\n\r\n",strlen(buf));  
    sprintf(content,"%s%s%s%s%s",content_page,content_host,content_type,content_len,buf); 
    send(sockfd,content,strlen(content),0);  
}

一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成,下图给出了请求报文的一般格式。
    /*2.请求头部
    /Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。
    */
    sprintf(content_host,"HOST: %s:%d\r\n",ip,port);  

 /*4.请求数据
    /请求数据不在GET方法中使用,而是在POST方法中使用。
    /POST方法适用于需要客户填写表单的场合。与请求数据相
    /关的最常使用的请求头是Content-Type和Content-Length。
    */
    char content_type[] = "Content-Type: application/x-www-form-urlencoded\r\n";  
    sprintf(content_len,"Content-Length: %d\r\n\r\n",strlen(buf));  
    sprintf(content,"%s%s%s%s%s",content_page,content_host,content_type,content_len,buf);  
    send(sockfd,content,strlen(content),0);  

}

服务端:
设计方案
服务端用maven整合springboot框架搭建项目,数据库使用mysql,代码主要分为四个层,POJO层、Dao层、Service层、Controller层。

POJO层:该层包含三个实体类,温湿度封装类、光照值封装类、可燃气体封装类;
Dao层:负责与数据库交互,提供数据源;
Service层:该层主要负责管理事务;
Controller层;负责提供json数据接口。
主要代码:

@RequestMapping(value = "/findAllShidu")
public Map<String,Object> findAllShidu(){
    Map<String,Object> wenduMap = new HashMap<String,Object>();
    List<Wendu> list = wenduService.findAllShidu();
    wenduMap.put("data",list);
    return wenduMap;
}
@RequestMapping(value = "/addWendu")
public Map<String,Object> addWendu(Double wendu,Double shidu) {
    Map<String, Object> wenduMap = new HashMap<String, Object>();
    System.out.print(wendu);
    try {
      wenduService.saveWendu(wendu,shidu);
    } catch (Exception e) {
        wenduMap.put("status", "fail");
        return wenduMap;
    }
    wenduMap.put("status", "success");
    return wenduMap;
}

Web显示端
主要代码:

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8">
        <link rel="icon" href="https://static.jianshukeji.com/highcharts/images/favicon.ico">
        <meta name="viewport"  content="width=device-width,initial-scale=1">
        <script src="https://img.hcharts.cn/jquery/jquery-1.8.3.min.js"></script>
        <script src="https://img.hcharts.cn/highcharts/highcharts.js"></script>
        <script src="https://img.hcharts.cn/highcharts/modules/exporting.js"></script>
        <script src="https://img.hcharts.cn/highcharts-plugins/highcharts-zh_CN.js"></script>
    </head>
    <body>
        <div id="container" style="min-width:400px;height:400px"></div>
        <script>
		var wendu=0;
			$(function(){
	getallRepairOrder();
	
	function getallRepairOrder(){
		$.ajax({
			url:"http://localhost:8080/findGas",
			type:"post",
			async:true,
			success:function(data){
				//oOrderContainer.text("");
				//回调
				 wendu=data.data[0][0];			
				//alert(data.data[0][0]);
				}
		});
		setTimeout(getallRepairOrder, 1000);
	
	}
	});
			Highcharts.setOptions({
                global: {
                    useUTC: false
                }
            });
            function activeLastPointToolip(chart) {
                var points = chart.series[0].points;
                chart.tooltip.refresh(points[points.length -1]);
            }
            $('#container').highcharts({
                chart: {
                    type: 'spline',
                    animation: Highcharts.svg, // don't animate in old IE
                    marginRight: 10,
                    events: {
                        load: function () {
                            // set up the updating of the chart each second
                            var series = this.series[0],
                                chart = this;
                            setInterval(function () {
                                var x = (new Date()).getTime(), // current time
                                    y = wendu;
                                series.addPoint([x, y], true, true);
                                activeLastPointToolip(chart)
                            }, 1000);
                        }
                    }
                },
                title: {
                    text: '可燃气体实时数据'
                },
                xAxis: {
                    type: 'datetime',
                    tickPixelInterval: 150
                },
                yAxis: {
                    title: {
                        text: '可燃气体值 %'
                    },
                    plotLines: [{
                        value: 0,
                        width: 1,
                        color: '#808080'
                    }]
                },
                tooltip: {
                    formatter: function () {
                        return '<b>' + this.series.name + '</b><br/>' +
                            Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) + '<br/>' +
                            Highcharts.numberFormat(this.y, 2);
                    }
                },
                legend: {
                    enabled: false
                },
                exporting: {
                    enabled: false
                },
                series: [{
                    name: '初始数据',
                    data: (function () {
                        // generate an array of random data
                        var data = [],
                            time = (new Date()).getTime(),
                            i;
                        for (i = -19; i <= 0; i += 1) {
                            data.push({
                                x: time + i * 1000,
                                y: 0
                            });
                        }
                        return data;
                    }())
                }]
            }, function(c) {
                activeLastPointToolip(c)
            });
	
        </script>
    </body>

</html>

客户端可视化展示部分:
主要流程:
这里写图片描述

数据访问:

def get_data(url,date):
    reponse = urllib.request.urlopen(url)
    html = reponse.read().decode('utf-8')
    PullData= json.loads(html)
    DataArry = PullData['data']
    xar,yar=[],[]
    for each in DataArry:

        if len(each)>1:
            y,x=each
            x=datetime.strptime(x,'%Y-%m-%d %H:%M:%S')
            if x>date and x<(date+timedelta(days=1)):
                xar.append(x)
                yar.append(float(y)
    return xar,yar
参数:
url:访问数据的接口。
对接口进行封装:
wendu_url = 'http://localhost:8080/findAllWendu'
shidu_url = 'http://localhost:8080/findAllShidu'
gas_url = 'http://localhost:8080/findGas'
sun_url='http://localhost:8080/findGuangzhao'
data = [('温度', wendu_url), ('湿度', shidu_url), ('可燃气', gas_url),('光照',sun_url)]
date:对其他日期进行过滤,可以查询特定日期下的数据变化情况。
对数据绘图:
drawPic.f.clf()
drawPic.a=drawPic.f.add_subplot(111)
x,y=get_data(url,sampleCount)
drawPic.a.set_xlim(min(x), max(x))
drawPic.a.plot(x,y,color='r',label='wendu',)
xlabels = drawPic.a.get_xticklabels()
for label in drawPic.a.xaxis.get_ticklabels():
    label.set_fontsize(5)
for label in drawPic.a.yaxis.get_ticklabels():
    label.set_fontsize(5)
for xl in xlabels:
    xl.set_rotation(45)
drawPic.a.set_title('Demo: DATA Display')
drawPic.a.set_xlabel(sampleCount.date())
drawPic.canvas.show()

图形界面:

drawPic.canvas = FigureCanvasTkAgg(drawPic.f, master=root)

drawPic.canvas.show()

drawPic.canvas.get_tk_widget().grid(row=0, columnspan=3)
var = StringVar()
var.set(1)
count = 0
for each in data:
    Radiobutton(root, text=each[0], variable=var, value=each[1]).grid(row=1, column=count, sticky=W)
    count += 1
Label(root,text='请输入查询的日期(Y-m-d):').grid(row=2,column=0)
#单选框,选择可以传入某一接口
inputEntry=Entry(root)
inputEntry.grid(row=2,column=1)
inputEntry.insert(0,'2017-6-12')
Button(root,text='画图',command=drawPic).grid(row=2,column=2,columnspan=3)
#command=drwaPic 按下Button执行drwaPic函数绘图

4 系统调试及运行测试截图
这里写图片描述
上图是网关运行截图
这里写图片描述
上图是服务器运行截图
这里写图片描述
上图是web显示截图
这里写图片描述
上图是python可视化窗口端截图
5 结束语
提出了一种基于ZigBee的数据采集系统的设计方案. 在实验中搭建了温度采集的ZigBee网络, 通信良好, 并实现了该系统的功能. ZigBee网络的覆盖范围一般为几十米, 但是可以通过网关与其他网络(例如WiFi、以太网)进行连接, 从而达到扩大网络覆盖范围或远程监控的目的, 这将使得ZigBee优良特性得到更好的体现, 使得它有更广阔的应用前景.
项目源码地址:https://github.com/Wlain/Environment-Data-Acquisition-System-of-Internet-of-Things-Based-on-ZigBee.git

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/127842.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • jmespath(2)投影Projections「建议收藏」

    jmespath(2)投影Projections「建议收藏」投影投影是JMESPath的关键特性之一。它允许您将表达式应用于元素集合。有五种投影:列表投影切片投影对象投影展平投影过滤投影处理投影需要注意的点投影分为两个步骤。左侧(LHS)创建一

  • 关于VUE双向绑定失效的问题「建议收藏」

    关于VUE双向绑定失效的问题「建议收藏」双向绑定失效的原因有很多。lz就说最近遇到的。是的,单价下的那个输入框我用了双向绑定(比如叫price,比如100)。然后ipnut键入中文时,(即使我做了输入验证)。回车时虽然框中不会保留中文,但事实上VUE的双向绑定已经失效了。不管你后面输入什么,绑定的price保存的值只会是中文前的那个值(100)。这样就导致表面好像没事,但是当你提交时就数据不对了。还有

  • quick-cocos2d-x android返回键监听并实现原生退出对话框

    quick-cocos2d-x android返回键监听并实现原生退出对话框

  • springmvc的工作流程

    springmvc的工作流程1、springmvc工作原理图2、springmvc工作流程1、用户向服务端发送一次请求,这个请求会先到前端控制器DispatcherServlet(也叫中央控制器)。2、DispatcherServlet接收到请求后会调用HandlerMapping处理器映射器。由此得知,该请求该由哪个Controller来处理(并未调用Controller,只是得知)3、DispatcherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器应该要去执行哪个Controlle

  • c语言和java哪个有前途_Java和C语言哪个好就业?

    c语言和java哪个有前途_Java和C语言哪个好就业?编程语言多达600余种,有畅行多年的,有逐渐被人遗忘的,还有发展迅速的后起之秀。Java和C语言作为使用者较多的两大语言,受到很多编程初学者的青睐,很多人在Java和C语言之间不知道该选哪个,想从事软件开发工作的话,究竟学哪个好呢?从两者的起源来看,Java是C语言的后辈,它是在C语言的基础上发展得来,抛弃了难懂的指针概念,和C完全不同的是Java是一门面向对象的语言,因为Java的跨平台和可移植…

  • AVX2浮点向量运算[通俗易懂]

    AVX2浮点向量运算[通俗易懂]在C/C++程序中,使用AVX2指令有很多种方法。嵌入汇编是一般的方法,但是对于不熟悉汇编语言的人来说,有点勉为其难。gcc编译支持AVX2指令的编程。程序中需要使用头文件和,这样通过调用其中定义的一些函数,达到使用AVX2指令的目的,即用C/C++调用SIMD指令(单指令多数据)。这里给出的样例程序是有关浮点向量运算的例子。其中函数_mm_add_ps()实现的是浮点向量(4个数

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号