本项目的交流QQ群:701889554

物联网实战–入门篇http://blog.csdn.net/ypp240124016/category_12609773.html

物联网实战–驱动篇http://blog.csdn.net/ypp240124016/category_12631333.html

物联网实战–平台篇http://blog.csdn.net/ypp240124016/category_12653350.html

嵌入式文件 http://download.csdn.net/download/ypp240124016/89409505

APP文件 http://download.csdn.net/download/ypp240124016/89409506

一、设备演示

        熟悉的画面,做了点改进。

净化器演示

二、模型要素

        整个平台相当于搭了个窝,目的是为了下蛋,而产品就是我们要下的蛋;对于开发人员来讲,如何定义产品是个很关键的问题,在电信、移动等这些云平台上,他们一般是用一个profile解析插件作为设备模型的,本质上就是json文件,里面描述了产品设备的属性和功能,这种方式的缺点是性能损耗较大,对于功能比较复杂的产品定义和调试都是件很麻烦的事情,需要北向平台人员做配合,沟通成本很高。

        而我们这里对设备模型的定义直接用源代码定义,清晰简洁,产品开发者不需要过多了解平台的底层协议和后台机制,定义好产品本身的数据和类型即可;对于展示界面,美工设计好后对照着样式用前端代码很容易就能实现了。

        对于设备来讲,首先最重要的要素就是设备SN了,它是设备的身份标识,在这里我们的定义是一个4字节的数据,高2字节代表型号,低2字节代码地址码,这个SN是设备生产的时候保存在设备内部的。例如,在演示视频里,我们把之前的净化器进行了完善,并把A101这个型号定义为净化器产品,所以在本系统里,净化器产品的序列号范围是A1010001~A101FFFF,有6万多个,一般场景够用了。

        接下来是要定义产品的属性,比如温湿度、PM2.5和开关状态等这些都算是属性,属性的主要约束内容是范围和精度,比如温度,范围是-40.0~120.0 ℃,精度是0.1℃,这个一般是传感器的物理特性决定的。

        有些设备不仅有属性值,还能进行控制,我们称之为功能定义,比如净化器的开关和风扇速度设置,都属于产品功能。

        有了这些属性和功能之后,还要想办法对它们进行有序地读取和操作,一般我们会定义不同的命令类型去执行不同的操作,比如下图是净化器产品的命令定义,至于每个命令后跟着哪些数据,具体根据产品自己定义即可。,

物联网实战–平台篇之(十三)物模型设备端插图

        总的来讲,我们对设备模型的开发主要就是围绕着SN、属性、功能和命令类型这四个内容展开的;当然,还有一个是通讯密码,因为我们采用的是一型多密原则,所以每个型号的设备都定义了多组密码随机使用。那么,下面我们就以之前的净化器项目为例,做具体的讲解。

       

三、通讯协议

 这里的通讯协议是指建立在MQTT之上的应用层通用协议,类似于modbus协议,我这边已经实现了具体内容,开发者只需要学会配置和调用即可,如果感兴趣可以对照着协议(链接文章第三节内容)和驱动代码看看。物联网实战–平台篇之(二)基础搭建_qt数据库驱动 封装其它数据库-CSDN博客

drv_server.h


#ifndef __DRV_SERVER_H
#define __DRV_SERVER_H

#include "drv_common.h"  


#ifndef				SERVER_PACK_SIZE
#define				SERVER_PACK_SIZE	        256		//数据包最大值
#endif

#ifndef				SERVER_PROTOCOL_VER
#define				SERVER_PROTOCOL_VER	      1   //协议版本
#endif

#ifndef				SERVER_PASSWD_CNTS
#define				SERVER_PASSWD_CNTS	      5   //密码组数
#endif

#ifndef				SERVER_EEPROM_ADDR
#define				SERVER_EEPROM_ADDR         (0x0050)//存储地址
#endif

#ifndef			CONFIG_EEPROM_READ			
#define			CONFIG_EEPROM_READ		EEPROM_Read  //配置读取函数
#endif

#ifndef			CONFIG_EEPROM_WRITE			
#define			CONFIG_EEPROM_WRITE		EEPROM_Write  //配置保存函数
#endif

#define         APP_ID_MIN                       (u32)(123000)

typedef enum
{
  ENCRYPT_MODE_DISABLE=0,
  ENCRYPT_MODE_TEA,
  ENCRYPT_MODE_AES,
}encryptMode;

typedef enum
{

	SERVER_CMD_UP_DATA=100,
	SERVER_CMD_DOWN_DATA=200,
  
  REG_CMD_REPORT_APP_ID=210,
  REG_CMD_SET_APP_ID,
  
  UPDATE_CMD_IAP=220,//远程升级总命令
  UPDATE_CMD_INTO_BOOT,//使设备进入升级状态
  UPDATE_CMD_KEEP,//保持连接  
  
  SERVER_CMD_LORA=230,//LORA总命令
}ServerCmdType;

typedef struct
{
	u8 head[2];
	u8 version;
	u8 encrypt_index;//密码索引
	u8 crc_h;
	u8 crc_l;
	u8 data_len_h;
	u8 data_len_l;
	u8 app_id[4];
	u8 gw_sn[4];
}ServerHeadStruct;

typedef struct
{
  u32 app_id;//应用ID
  u32 gw_sn;//设备SN
  u16 reserved;
  u16 crcValue;
}ServerSaveStruct;

typedef struct
{	

  u8 passwd_table[SERVER_PASSWD_CNTS][16];//密码表,要跟用户端的模型对应

	int (*fun_send)(u8 *buff, u16 len);
	u16 (*fun_server_cmd_parse)(u8 cmd_type, u8 *in_buff, u16 in_len);	
	u16 (*fun_slave_cmd_parse)(u32 node_sn, u8 *in_buff, u16 in_len);	
   
}ServerWorkStruct;


void drv_server_read(void);
void drv_server_write(void);
void drv_server_init(void);


u16 drv_server_send_msg(u8 cmd_type, u8 *in_buff, u16 in_len);
void drv_server_send_slave_msg(u32 node_sn, u8 *in_buff, u16 in_len);
int drv_server_send_level(u8 *buff, u16 len);
void drv_server_send_register(int (*fun_send)(u8 *buff, u16 len));

u16 drv_server_recv_parse(u8 *buff, u16 len, int (*fun_send)(u8 *buff, u16 len));
u16 drv_server_cmd_parse(u8 cmd_type, u8 *in_buff, u16 in_len);
void drv_server_cmd_parse_register(u16 (*fun_server_cmd_parse)(u8 cmd_type, u8 *in_buff, u16 in_len));

u16 drv_slave_cmd_parse(u32 node_sn, u8 *in_buff, u16 in_len);
void drv_slave_cmd_parse_register(u16 (*fun_slave_cmd_parse)(u32 node_sn, u8 *in_buff, u16 in_len));

void drv_server_set_app_id(u32 app_id);
u32 drv_server_get_app_id(void);
void drv_server_set_gw_sn(u32 gw_sn);
u32 drv_server_get_gw_sn(void);
void drv_server_add_passwd(u8 index, u8 *passwd);

#endif

drv_server.c


#include "drv_server.h"
#include "drv_encrypt.h"
ServerWorkStruct g_sServerWork={0};
ServerSaveStruct g_sServerSave={0};
/*		
================================================================================
描述 :
输入 : 
输出 : 
================================================================================
*/
void drv_server_read(void)
{
CONFIG_EEPROM_READ(SERVER_EEPROM_ADDR, (u8 *)&g_sServerSave, sizeof(g_sServerSave));
//  printf_hex("read=", (u8 *)&g_sServerSave, sizeof(g_sServerSave));
if(g_sServerSave.crcValue!=drv_crc16((u8*)&g_sServerSave, sizeof(g_sServerSave)-2))
{
g_sServerSave.app_id=APP_ID_MIN;
g_sServerSave.gw_sn=M2M_DEV_TYPE<SERVER_PACK_SIZE)
{
printf("in len too long!
");
return 0;
}
if(gw_sn==0)
{
gw_sn=g_sServerSave.gw_sn;
}
if(app_id==0)
{
app_id=g_sServerSave.app_id;
}
pack_num++;
memset(data_buff, 0, SERVER_PACK_SIZE);
data_len=0;
union_len=in_len+4;//数据单元长度
data_buff[data_len++]=union_len>>8;//从此处开始加密
data_buff[data_len++]=union_len;		
data_buff[data_len++]=pack_num;
data_buff[data_len++]=cmd_type;
memcpy(&data_buff[data_len], in_buff, in_len);
data_len+=in_len;
crcValue=drv_crc16(data_buff, union_len);//数据单元校验
data_buff[data_len++]=crcValue>>8;
data_buff[data_len++]=crcValue;		
remain_len=data_len%8;
if(remain_len>0)
data_len+=(8-remain_len);//8字节对齐,便于TEA加密 
pHead->encrypt_index=drv_get_sec_counter()%SERVER_PASSWD_CNTS;//根据时间随机获取密码
memcpy(to_server_pwd, g_sServerWork.passwd_table[pHead->encrypt_index], 16);//根据索引复制密码
switch(encrypt_mode)
{
case ENCRYPT_MODE_DISABLE:
{
memcpy(pData, data_buff, data_len);
out_len=data_len;
break;
}
case ENCRYPT_MODE_TEA:
{
out_len=tea_encrypt_buff(data_buff, data_len, (u32*)to_server_pwd);
if(out_len==data_len)
{
memcpy(pData, data_buff, data_len);
}			
else
{
printf("server tea error!
");
return 0;
}
break;
}
#ifdef    USE_AES    //是否启用AES算法
case ENCRYPT_MODE_AES:
{
out_len=aes_encrypt_buff(data_buff, data_len, pData, SERVER_PACK_SIZE-16, to_server_pwd);//aes加密
if(out_lenhead[0]=0xAA;
pHead->head[1]=0x55;
pHead->version=SERVER_PROTOCOL_VER;
pHead->crc_h=crcValue>>8;
pHead->crc_l=crcValue;
pHead->data_len_h=data_len>>8;
pHead->data_len_l=data_len;
pHead->app_id[0]=app_id>>24;
pHead->app_id[1]=app_id>>16;
pHead->app_id[2]=app_id>>8;
pHead->app_id[3]=app_id;
pHead->gw_sn[0]=gw_sn>>24;
pHead->gw_sn[1]=gw_sn>>16;
pHead->gw_sn[2]=gw_sn>>8;
pHead->gw_sn[3]=gw_sn;	
make_len=data_len+8;
drv_server_send_level(make_buff, make_len);//发送
return make_len;
}
/*		
================================================================================
描述 : 转发从机消息
输入 : 
输出 : 
================================================================================
*/
void drv_server_send_slave_msg(u32 node_sn, u8 *in_buff, u16 in_len)
{
u8 make_buff[100]={0};
u16 make_len=0;
if(in_len+20>sizeof(make_buff))
{
return;
}
make_buff[make_len++]=node_sn>>24;
make_buff[make_len++]=node_sn>>16;
make_buff[make_len++]=node_sn>>8;
make_buff[make_len++]=node_sn;
u8 *pUnion=&make_buff[make_len];
make_len+=2;//单元长度  
memcpy(&make_buff[make_len], in_buff, in_len);
make_len+=in_len;
u16 union_len=make_len-4;//单元长度  
pUnion[0]=union_len>>8;
pUnion[1]=union_len;
u16 crcValue=drv_crc16(pUnion, union_len);
make_buff[make_len++]=crcValue>>8;  
make_buff[make_len++]=crcValue;  
//  printf_hex("slave msg=", make_buff, make_len);
drv_server_send_msg(SERVER_CMD_UP_DATA, make_buff, make_len);  //转发  
}
/*		
================================================================================
描述 : 服务端数据接收解析
输入 : 
输出 : 
================================================================================
*/
u16 drv_server_recv_parse(u8 *buff, u16 len, int (*fun_send)(u8 *buff, u16 len))
{
static u8 head[2]={0xAA, 0x55}, out_buff[800]={0}, recv_pack_num=109;
u8 to_server_pwd[16]={0}, encrypt_mode=0;//加密模式
u8 *pBuff=buff, *pData=NULL, pack_num, cmd_type;
u16 data_len=0, out_len=0, union_len=0, crcValue;
static u32 local_app_id=0, local_gw_sn=0;
u32 recv_gw_sn, recv_app_id;
if(local_gw_sn==0)
{
local_gw_sn=g_sServerSave.gw_sn;
}
if(local_app_id==0)
{
local_app_id=g_sServerSave.app_id;
}
//	printf_hex("drv_server_recv_parse: ", buff, len);
if( (pBuff=memstr(buff, len, head, 2))!=NULL )
{
ServerHeadStruct *pHead = (ServerHeadStruct *)pBuff;
data_len=pHead->data_len_h<data_len_l;
crcValue=pHead->crc_h<crc_l;
recv_app_id=pHead->app_id[0]<app_id[1]<app_id[2]<app_id[3];
recv_gw_sn=pHead->gw_sn[0]<gw_sn[1]<gw_sn[2]<gw_sn[3];
if(recv_app_id==0)
{
return 0;
}
if(recv_gw_sn!=local_gw_sn)
{
printf("drv_server_parse_recv error: recv_gw_sn(%u)!=local_gw_sn(%u)!", recv_gw_sn, local_gw_sn);
return 0;
}
if(data_lensizeof(out_buff))
{
printf("drv_server_parse_recv error: data_lensizeof(out_buff)
");
return 0;
}
if(pHead->encrypt_index>=SERVER_PASSWD_CNTS)
{
return 0;
}
memcpy(to_server_pwd, g_sServerWork.passwd_table[pHead->encrypt_index], 16);//根据索引复制密码
encrypt_mode=ENCRYPT_MODE_TEA;
pData=pBuff+8;
if(crcValue==drv_crc16(pData, data_len))
{
pData+=8;//app_id和gw_sn不加密
data_len-=8;
//解密
switch(encrypt_mode)
{
case ENCRYPT_MODE_DISABLE:
{
memcpy(out_buff, pData, data_len);
out_len=data_len;
break;
}
case ENCRYPT_MODE_TEA:
{
out_len=tea_decrypt_buff(pData, data_len, (u32*)to_server_pwd);
if(out_len==data_len)
{
memcpy(out_buff , pData, data_len);
}			
else
{
printf("server tea error!
");
return 0;
}
break;
}
#ifdef    USE_AES    //是否启用AES算法
case ENCRYPT_MODE_AES:
{
out_len=aes_decrypt_buff(pData, data_len, out_buff, sizeof(out_buff)-16, to_server_pwd);//aes
if(out_len<16)
{
printf("server aes error!
");
return 0;
}
break;
}
#endif 
default:
return 0;			
}
}
else
{
printf("drv_server_parse_recv crc error 000!
");
return 0;
}
pData=out_buff;
union_len=pData[0]<<8|pData[1];
pData+=2;
pack_num=pData[0];
pData+=1;
cmd_type=pData[0];
pData+=1;
//		printf("union_len=%d, pack_num=%d, cmd_type=%d
", union_len, pack_num, cmd_type);
if(recv_pack_num==pack_num)//过滤相同的包序号
{
printf("recv_pack_num==pack_num
");
return 0;
}
recv_pack_num=pack_num;//更新包序号
if(union_lensizeof(out_buff))
{
printf("drv_server_parse_recv error: union_lensizeof(out_buff)
");
return 0;		
}
crcValue=out_buff[union_len]<<8|out_buff[union_len+1];
if(crcValue==drv_crc16(out_buff, union_len))//解密后校验
{
union_len-=4;
//			printf("cmd_type=%d
", cmd_type);
switch(cmd_type)
{ 
case SERVER_CMD_DOWN_DATA://数据转发
{
u32 recv_node_sn=pData[0]<<24|pData[1]<<16|pData[2]<<8|pData[3];//目标节点序列号
drv_slave_cmd_parse(recv_node_sn, pData, union_len);//从机解析
break;
}
case REG_CMD_SET_APP_ID://设置APP ID
{
u32 new_app_id=pData[0]<<24|pData[1]<<16|pData[2]<=APP_ID_MIN)
{
drv_server_set_app_id(new_app_id);
delay_os(100);
drv_system_reset();//复位系统
}
break;
}        
default://其余命令交给应用层处理
drv_server_cmd_parse(cmd_type, pData, union_len);
}		
return 1;
}
else
{
printf("drv_server_parse_recv crc error 111!
");
return 0;
}		
}
return 0;
}
/*		
================================================================================
描述 :
输入 : 
输出 : 
================================================================================
*/
int drv_server_send_level(u8 *buff, u16 len)
{
if(g_sServerWork.fun_send != NULL)
{
return g_sServerWork.fun_send(buff, len);
}  
return 0;
}
/*		
================================================================================
描述 :
输入 : 
输出 : 
================================================================================
*/
void drv_server_send_register(int (*fun_send)(u8 *buff, u16 len))
{
g_sServerWork.fun_send=fun_send;
}
/*		
================================================================================
描述 : 服务端解析 
输入 : 
输出 : 
================================================================================
*/
u16 drv_server_cmd_parse(u8 cmd_type, u8 *in_buff, u16 in_len)
{
u16 make_len=0; 
if(g_sServerWork.fun_server_cmd_parse != NULL)
{
make_len=g_sServerWork.fun_server_cmd_parse(cmd_type, in_buff, in_len);
}
return make_len;
}
/*		
================================================================================
描述 : 服务端解析函数注册
输入 : 
输出 : 
================================================================================
*/
void drv_server_cmd_parse_register(u16 (*fun_server_cmd_parse)(u8 cmd_type, u8 *in_buff, u16 in_len))
{
g_sServerWork.fun_server_cmd_parse=fun_server_cmd_parse;
}
/*		
================================================================================
描述 : 从机端解析 
输入 : 
输出 : 
================================================================================
*/
u16 drv_slave_cmd_parse(u32 node_sn, u8 *in_buff, u16 in_len)
{
u16 make_len=0; 
if(g_sServerWork.fun_slave_cmd_parse != NULL)
{
make_len=g_sServerWork.fun_slave_cmd_parse(node_sn, in_buff, in_len);
}
return make_len;
}
/*		
================================================================================
描述 : 从机端解析函数注册
输入 : 
输出 : 
================================================================================
*/
void drv_slave_cmd_parse_register(u16 (*fun_slave_cmd_parse)(u32 node_sn, u8 *in_buff, u16 in_len))
{
g_sServerWork.fun_slave_cmd_parse=fun_slave_cmd_parse;
}
/*		
================================================================================
描述 : 
输入 : 
输出 : 
================================================================================
*/
void drv_server_set_app_id(u32 app_id)
{
g_sServerSave.app_id=app_id;
drv_server_write();
printf("set app_id=%u
", app_id);
}
/*		
================================================================================
描述 : 
输入 : 
输出 : 
================================================================================
*/
u32 drv_server_get_app_id(void)
{
return g_sServerSave.app_id;
}
/*		
================================================================================
描述 : 
输入 : 
输出 : 
================================================================================
*/
void drv_server_set_gw_sn(u32 gw_sn)
{
g_sServerSave.gw_sn=gw_sn;
drv_server_write(); 
printf("set gw_sn=%08X
", gw_sn);
}
/*		
================================================================================
描述 : 
输入 : 
输出 : 
================================================================================
*/
u32 drv_server_get_gw_sn(void)
{
return g_sServerSave.gw_sn;
}
/*		
================================================================================
描述 : 
输入 : 
输出 : 
================================================================================
*/
void drv_server_add_passwd(u8 index, u8 *passwd)
{
if(index<SERVER_PASSWD_CNTS)
{
memcpy(g_sServerWork.passwd_table[index], passwd, 16);
}
}
 四、密码表配置

          在这个与平台服务器对接的通讯文件里,不仅实现了发送加密和接收解密的功能,还保存了app_id和设备SN,即gw_sn (gw是网关GateWay的缩写,意味着是跟服务器直接网络对接的角色)。还有一个是密码表,这个表实际使用时最好动态混淆填充,这样才能提升密码获取难度,这里我是直接定义的,二进制文件打开是可以直接检索到的,并不安全,暂时图个方便。密码表是QT那边随机生成复制过来的。

物联网实战–平台篇之(十三)物模型设备端插图(1)

五、收发函数注册

         drv_server.c文件里的收发函数都是在应用层注册的,因为我们用了mqtt,所以这个过程在app_mqtt.c文件内完成的,具体如下所示,整个mqtt的配置跟原来差不多,就是订阅话题做了些改动。

物联网实战–平台篇之(十三)物模型设备端插图(2)

        收发函数:

物联网实战–平台篇之(十三)物模型设备端插图(3)

六、设备定义

        从设备端开始,首先定义产品型号值是A101,这个值是自己分配定义的,你要定义成A102也行,核心就是不同产品类型值不能重复就行了,在规划上也要有长远的打算,不要乱定义、浪费数字资源。

物联网实战–平台篇之(十三)物模型设备端插图(4)

        净化器的属性包含了温度、湿度、PM2.5、风速等级和开关状态,如下图所示,至于数据为什么要乘以10在加1000这种操作,我在之前的文章里有详细解释了,可以回头看看。物联网实战–入门篇之(八)嵌入式-空气净化器-CSDN博客

物联网实战–平台篇之(十三)物模型设备端插图(5)

物联网实战–平台篇之(十三)物模型设备端插图(6)

        上图是解析下发的指令,即功能定义,简单讲就是根据不同命令类型执行不同的操作了。     

        对于净化器本身的功能,跟原来入门篇是一样的,这里只是把发送和解析函数做了改动,接入现有的通讯协议系统。

        对于产品开发者来讲,通讯层的驱动文件都是定义好的,无需改动,只要专心完成app_ap01.c里的功能就行了,这样就可以把开发者从繁杂的底层通讯中解放出来,专注于产品本身的功能实现,尽可能优化,提升用户体验,这才是最为关键的。这也是我开发这个平台的根本原因所在。    

       

本站无任何商业行为
个人在线分享 » 物联网实战–平台篇之(十三)物模型设备端
E-->