目前智能家居领域,主流的硬件设备系统都使用无线自组织网络,如ZigBee,Z-Wave,最新出来的蓝牙-mesh网络,也极有可能进入智能家居通信领域(WIFI单件智能产品,其劣势就不用说了,只能玩玩而已)。
不管使用哪种网络,都有一个基站(或路由器)作为网络的管理者和数据中转器。大部分厂家把他嵌入到了所谓的智能网关设备中。该设备是智能家居设备商的核心技术所在,负责把外部网络传来的数据指令转化为智能家居内部网络中设备所需的指令,反之亦然。
然而,不同厂商生产的智能网关,使用自己的通信协议,互不兼容,且这个设备价格不菲,至少在4位数。则给消费者造成了智能家居系统使用体验差和重复投资的困境,有被厂家绑架的愤怒感。据美国的一份智能家居调研报告称,约80%的消费者,在购买某一品牌的智能家居设备后,不会考虑再购买其产品。多么悲剧的事情,怪不得智能家居行业这么多年来一直没有大的发展。巨大的市场蛋糕,就是画饼充饥,家居设备生产企业的意淫罢了。
总要有解决办法吧。制定智能家居的行业标准,是绕不开的最大障碍。各大有实力的企业,纷纷开放自己的“标准”,期望联合其他企业来垄断市场。这些标准,企业为了自己的利益,设置了种种限制条件,有的是从硬件设备限制,有的是从软件平台限制。要不绑架硬件设备商,或者绑架消费者的选择权。这种封闭式的生态系统,必定是要失败的,不管他能做到多大的规模,总有轰然倒塌的一天,因为智能家居是一个开发的行业,涉及成千上万的设备生产商和无数的消费者。正如互联网的TCP/IP协议,使得互联网走进千家万户,应阁智能家居的通用协议,在智能家居行业充当类似的作用,其意义之大,无需赘述。这里介绍应阁智能家居的通用协议在家居无线网络设备中的具体应用。
设计的目标:只需一个通用的基站设备,所有传感器节点终端设备都能顺利自动接入网络,并向其报告设备的功能和操作方法,基站再转发该信息给监控平台。这样,可以实现用一个移动APP,就可以监控任意复杂的终端设备(如智能空调、冰箱、门锁、净化器、温湿度传感器等),而不管这些设备来自那个厂家。激动吗?这已经实现了,你需要做的,就是修改已有设备的程序而已,使其满足通用监控协议的要求,硬件无需做任何的改动。还算公平吧?
以ZigBee无线网络为例,先介绍基站程序的设计开发(声明:您可免费试用该专利技术用于研发,但用于生产设备需要支付专利费,对侵权行为我们保留法律追究权力)。
一、通用的协议头文件定义
/**************************************************************************************************
Filename: smprotocol.h
Revised: $Date: 2016-05-04
Revision: $Revision: 1.0.0.0
Description: ZigBee智能家居通用监控协议
Copyright 2016-2020 吴志辉
该模块定义ZigBee智能家居通用监控协议的设备类型,监控指令和数据结构,
**************************************************************************************************/
#include <string.h>
#include <stdio.h>
#include <ioCC2530.h>
#include "hal_types.h"
//通用ZigBee智能家居协议
//设备类型定义:可参考以前的博文中的介绍
#define DI 0 // DI设备
#define DO 1 // DO设备
#define AI 2 // AI设备
#define AO 3 // AO设备
#define SI 4 // SI设备
#define SO 5 // SO设备
//流媒体子设备SI,SO的子分类subtype定义
#define Unknow 0 // SO设备
#define TEXT 1 // 文本信息
#define IMAGE 2 // 图像设备
#define AUDIO 3 // 音频设备
#define FILE 4 // 文件信息
#define STRINGJSON 5 // STRINGJSON信息
//DO开关的子分类subtype定义
#define OPEN 0 // 常开开关
#define CLOSE 1 // 常闭开关
//交互命令CMD协议定义,应用数据帧FF FX ADDR SIZE DATA
//数据内容标识头=0xFFF0+CMD; 第3,4个字节为设备ID号
//第5个字节SIZE为后面数据DATA的字节数,不包括SIZE字节本身
//CMD=0--15,共16种指令,目前只定义了4种
#define SENDDATAHead 0xFFF0// 交互数据内容识头
#define SENDDEVTABLE 0 // 发送设备信息表,E-->C-->P
#define SENDDEVDESCP 1 // 发送子设备描述记录,E-->C-->P
#define SENDDEVSTATE 2 // 发送子设备状态数据,E<-->C<-->P
#define SENDDEVCTRLL 3 // 控制子设备工作指令,P-->C-->E
typedef struct
{
uint8 subdevices[6]; //一个实际ZigBee硬件设备的6类子设备数量表
//每个字节的高三位为子设备类型,低4位为实际子设备编号0--15,第5位保留
uint8 description[24]; //设备简要描述:最多12个汉字,比如“应阁通用ZigBee基站”,
uint16 PanID; //16网络ID
} devicetable_t; //设备功能描述结构体,32字节
typedef struct
{
uint8 devicenumber; //设备类型和序号:高3位:子设备类型,低五位:子设备数量,
//为0x1F时,表述所有子设备,一般低4位为实际子设备编号0--15
uint8 unit[6]; //度量单位文字描述,比如 mg/kg
uint8 description[16]; //功能简要描述:最多8个汉字,比如“第一路开关”,
uint8 operation[16]; //操作简要描述:最多8个汉字,比如“输入控制温度”
uint8 subtype; //子设备分类描述,主要针对SI,SO子设备
} devicedescription_t; //设备功能描述结构体,40字节
extern devicetable_t* deviceinfo; //一个实际ZigBee硬件设备的6类子设备数量表
extern devicedescription_t* DODevices; //DO设备描述
extern devicedescription_t* DIDevices;
extern devicedescription_t* AODevices;
extern devicedescription_t* AIDevices;
extern devicedescription_t* SODevices;
extern devicedescription_t* SIDevices;
extern uint8 getdevicetype(uint8 adevice); //获取设备的类型:高3位
extern uint8 getdevicenumbers(uint8 adevice); //获取设备的数量:低5位
extern void setdevicetypeandnumbers(uint8 *adevice, uint8 type, uint8 numbers);
该文件定义了设备的详细描述结构。在对应的C文件中,定义了相应的变量和几个操作函数:
/**************************************************************************************************
Filename: smprotocol.c
Revised: $Date: 2016-05-04
Revision: $Revision: 1.0.0.0
Description: ZigBee智能家居通用监控协议
Copyright 2016-2020 吴志辉
该模块用于设置硬件设备的功能。厂商应该根据硬件设计,调整有关参数
**************************************************************************************************/
#include <string.h>
#include "OSAL.h"
#include "hal_types.h"
#include "smprotocol.h"
#include "GenericApp.h"
devicedescription_t* DODevices; //DO设备描述
devicedescription_t* DIDevices;
devicedescription_t* AODevices;
devicedescription_t* AIDevices;
devicedescription_t* SODevices;
devicedescription_t* SIDevices;
devicetable_t* deviceinfo; //一个实际ZigBee硬件设备的6类子设备数量表
uint8 getdevicetype(uint8 adevice) //获取设备的类型:高3位
{
return adevice>>5;
}
uint8 getdevicenumbers(uint8 adevice) //获取设备的数量:低5位
{
return adevice & 0X1F;
}
void setdevicetypeandnumbers(uint8 *adevice, uint8 type, uint8 numbers) //设置子设备的类型和数量
{
*adevice= (type<<5) + numbers;
}
就这么简单。具体怎么用?
1、在应用初始化的时候,初始化设备自身的信息。
在协调器相关程序中,编写代码:
void InitUART(void);
void InitUART(void) //如果用串口转WIFI芯片,需要修改基站与监控平台的通信方式
{ //初始化串口
halUARTCfg_t uartConfig;//定义个串口结构体
uartConfig.configured =TRUE;//串口配置为真
uartConfig.baudRate =HAL_UART_BR_115200;//波特率为9600
uartConfig.flowControl =FALSE;//流控制为假
uartConfig.callBackFunc = rxCB;//定义串口回调函数
HalUARTOpen(HAL_UART_PORT_0,&uartConfig);// 打开串口0
}
void GenericApp_Init( uint8 task_id ) //应用程序初始化函数
{
GenericApp_TaskID = task_id;
GenericApp_NwkState = DEV_INIT;
GenericApp_TransID = 0;
InitUART();
............
Initdevicestable(); //应用程序初始化后,要初始化“应用”硬件
}
void Initdevicestable(void) //1、硬件厂家必须描述子设备数量表:在应用程序初始化后调用该函数
{
uint16 PanID=_NIB.nwkPanId;
//pValue=(uint8 *)NLME_GetExtAddr(); //NLME_GetShortAddr
//osal_nv_read(ZCD_NV_EXTADDR , Z_EXTADDR_LEN, size, pValue);
InitLed();
deviceinfo = (devicetable_t *)osal_mem_alloc(sizeof(devicetable_t));//动态分配结构体
memset(deviceinfo, 0, sizeof(devicetable_t)); //首先全部清零
//对六类子设备填写数据
setdevicetypeandnumbers(&deviceinfo->subdevices[DO],DO,3); //这里有3个LED灯开关可用
sprintf(deviceinfo->description,"应阁通用ZigBee协调器");
deviceinfo->PanID=PanID;
InitDeviceDescription(); //初始化具体子设备的描述
}
void InitDeviceDescription(void) //2、硬件厂家必须初始化子设备的描述
{
uint8 i;
uint8 devnumbers=getdevicenumbers(deviceinfo->subdevices[DO]); //DO子设备数量
i=sizeof(devicedescription_t);
DODevices = (devicedescription_t *) osal_mem_alloc( devnumbers * i);//动态分配结构体
for (i=0;i<devnumbers;i++)
{
memset((void *)(DODevices+i), 0, sizeof(devicedescription_t)); //首先全部清零
DODevices[i].devicenumber=(DO<<5)+i; //类型+序号
DODevices[i].subtype=OPEN;
strcpy(DODevices[i].unit,""); //计量单位:无
sprintf(DODevices[i].description,"基站第%d个LED灯",i+1);
sprintf(DODevices[i].operation,"点击DO切换开关");
}
// 如果有其他类型的子设备,需要一一填写结构体
// .............
}
#define SIZE 80
//uint8 TxData[SIZE+32];
//下面是串口通信回调函数,/每当协调器从串口收到数据时,就会自动调用这个函数
static void rxCB(uint8 port,uint8 event)
{
uint8 uartbuf[SIZE];
uint8 cnt=0;
uint16 addr=0;
memset(uartbuf, 0, SIZE);
cnt=HalUARTRead(0,uartbuf,SIZE);//从串口读取两个字节的数据到uartbuf中
if (cnt==0) return;
//HalLedSet(HAL_LED_4, HAL_LED_MODE_BLINK);
//交互命令CMD协议定义,应用数据帧FF FX ADDR SIZE DATA
//数据内容标识头=0xFFF0+CMD; 第3,4个字节为设备ID号
//第5个字节SIZE为后面数据DATA的字节数
//提取地址,发给指定终节点
if(cnt<6) return; //协议规定了至少6个字节
if(uartbuf[0]!=0xFF || (uartbuf[1]>>4)!=0x0F) return;
addr=uartbuf[2]+(uartbuf[3]<<8);
GenericApp_DstAddr.addr.shortAddr=addr;
if (addr!=0x0000) //监控平台发给终端节点的数据指令:直接转发(也可过滤处理一下再转发,加强安全检查)
{
AF_DataRequest( &GenericApp_DstAddr, &GenericApp_epDesc,
GENERICAPP_CLUSTERID,
cnt, //发送字节数
uartbuf,//发送的数据内容,
&GenericApp_TransID,
AF_DISCV_ROUTE, AF_DEFAULT_RADIUS );
}
else //监控平台发给协调器自己的信息
{
ProcessUartData(uartbuf,cnt);
}
}
static void ProcessUartData( uint8* uartbuffer,uint8 len )//接受串口数据处理函数
{
uint8 buffer[128];
uint8 cmd;
uint8 type;
uint8 number;
uint16 addr;
memset(buffer, 0, 128);
osal_memcpy(buffer,uartbuffer,len);
//需要根据具体硬件设计来修改该函数,有待完善....//
//交互命令CMD协议定义,应用数据帧FF FX ADDR SIZE DATA
//数据内容标识头=0xFFF0+CMD; 第3,4个字节为设备ID号
//第5个字节SIZE为后面数据DATA的字节数
//提取地址,验证地址码?可能是广播
if(len<6) return; //协议规定了至少6个字节
if(buffer[0]!=0xFF || (buffer[1]>>4)!=0x0F) return;
addr=buffer[2]+buffer[3]<<8; //验证地址码?
if (addr!=0x0000) return;
cmd=buffer[1] & 0x0F;
if (cmd==SENDDEVCTRLL) // 3:控制子设备工作指令,P-->C-->E
{ //高3位:子设备类型,低五位:子设备编号0--15
type=buffer[5]>>5;
number=buffer[5]&0x1F;
if (type==DO) //DO子设备
{
if(number==0)
{
LED1=buffer[6];
buffer[1]=0xF0+SENDDEVSTATE;// 负责返回实际状态SENDDEVSTATE 2:发送子设备状态数据,E<-->C<-->P
buffer[6]=LED1; //查看接线图
buffer[4]=2; //两个字节的数据
HalUARTWrite(0,buffer,7);//回复数据给PC
}
else if (number==1)
{
LED2=buffer[6];
buffer[1]=0xF0+SENDDEVSTATE;// 负责返回实际状态SENDDEVSTATE 2:发送子设备状态数据,E<-->C<-->P
buffer[6]=LED2; //查看接线图
buffer[4]=2; //两个字节的数据
HalUARTWrite(0,buffer,7);//回复数据给PC
}
else if (number==2)
{
LED3=buffer[6];
buffer[1]=0xF0+SENDDEVSTATE;// 负责返回实际状态SENDDEVSTATE 2:发送子设备状态数据,E<-->C<-->P
buffer[6]=LED3; //查看接线图
buffer[4]=2; //两个字节的数据
HalUARTWrite(0,buffer,7);//回复数据给PC
}
}
}
else if (cmd==SENDDEVSTATE)// SENDDEVSTATE 2:发送子设备状态数据,E<-->C<-->P
{ //高3位:子设备类型,低五位:子设备编号0--15
type=buffer[5]>>5;
number=buffer[5]&0x1F;
if (type==DO) //获取DO子设备状态
{
if(number==0)
{
buffer[6]=LED1; //查看接线图
buffer[4]=2; //两个字节的数据
HalUARTWrite(0,buffer,7);//回复数据给PC
}
else if (number==1)
{
buffer[6]=LED2; //查看接线图
buffer[4]=2; //两个字节的数据
HalUARTWrite(0,buffer,7);//回复数据给PC
}
else if (number==2)
{
buffer[6]=LED3; //查看接线图
buffer[4]=2; //两个字节的数据
HalUARTWrite(0,buffer,7);//回复数据给PC
}
else if (number==0x1F) //所有DO子设备状态
{
buffer[6]=LED1; //查看接线图
buffer[7]=LED2; //查看接线图
buffer[8]=LED3; //查看接线图
buffer[4]=4; //4个字节的数据
HalUARTWrite(0,buffer,9);//回复数据给PC
}
}
else if (type==AI) //获取AI子设备状态
{
if(number==0)
{
//GenericApp_Send_LightSensor_Message;
}
else if (number==0x1F) //所有AI子设备状态
{
//GenericApp_Send_LightSensor_Message;
}
}
}
}
2、在无线网络建立后,基站把自身的信息报告给监控平台
uint16 GenericApp_ProcessEvent( uint8 task_id, uint16 events )
{
afIncomingMSGPacket_t *MSGpkt;
afDataConfirm_t *afDataConfirm;
byte sentEP;
ZStatus_t sentStatus;
byte sentTransID; // This should match the value sent
(void)task_id; // Intentionally unreferenced parameter
if ( events & SYS_EVENT_MSG )
{
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
while ( MSGpkt )
{
switch ( MSGpkt->hdr.event )
{
case ZDO_CB_MSG:
GenericApp_ProcessZDOMsgs( (zdoIncomingMsg_t *)MSGpkt );
break;
case KEY_CHANGE:
GenericApp_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );
break;
case AF_DATA_CONFIRM_CMD:
afDataConfirm = (afDataConfirm_t *)MSGpkt;
sentEP = afDataConfirm->endpoint;
sentStatus = afDataConfirm->hdr.status;
sentTransID = afDataConfirm->transID;
(void)sentEP;
(void)sentTransID;
// Action taken when confirmation is received.
if ( sentStatus != ZSuccess )
{
// The data wasn‘t delivered -- Do something
}
break;
case AF_INCOMING_MSG_CMD:
GenericApp_MessageMSGCB( MSGpkt ); //处理应用层消息!
break;
case ZDO_STATE_CHANGE:
GenericApp_NwkState = (devStates_t)(MSGpkt->hdr.status);
if ( (GenericApp_NwkState == DEV_ZB_COORD)
|| (GenericApp_NwkState == DEV_ROUTER)
|| (GenericApp_NwkState == DEV_END_DEVICE) )
{
if ( (GenericApp_NwkState == DEV_ZB_COORD) ) //协调器入网
{
NotifyCoordinatorDevices(); // !!入网后报告设备信息 !!
HalLedSet(HAL_LED_1, HAL_LED_MODE_ON); //三个LED灯点亮
HalLedSet(HAL_LED_2, HAL_LED_MODE_ON);
HalLedSet(HAL_LED_3, HAL_LED_MODE_ON);
}
osal_start_timerEx( GenericApp_TaskID,
GENERICAPP_SEND_MSG_EVT,
GENERICAPP_SEND_MSG_TIMEOUT );
}
break;
default:
break;
}
// Release the memory
osal_msg_deallocate( (uint8 *)MSGpkt );
// Next
MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( GenericApp_TaskID );
}
// return unprocessed events
return (events ^ SYS_EVENT_MSG);
}
// Send a message out - This event is generated by a timer
// (setup in GenericApp_Init()).
if ( events & GENERICAPP_SEND_MSG_EVT )
{
//GenericApp_Send_LightSensor_Message();
//Setup to send message again
osal_start_timerEx( GenericApp_TaskID,
GENERICAPP_SEND_MSG_EVT,
GENERICAPP_SEND_MSG_TIMEOUT );
// return unprocessed events
return (events ^ GENERICAPP_SEND_MSG_EVT);
}
............
return 0;
}
static void NotifyCoordinatorDevices(void) //报告设备信息给协调器
{
uint8 i,size;
uint8 cnt;
uint16 addr;
uint16 sended;
devicedescription_t* devdes;
//交互命令CMD协议定义,应用数据帧FF FX ADDR SIZE DATA
//数据内容标识头=0xFFF0+CMD; 第3,4个字节为设备ID号
//第5个字节SIZE为后面数据DATA的字节数
//#define SENDDATAHead 0xFFF0// 交互数据内容识头
//#define SENDDEVTABLE 0 // 发送设备信息表,E-->C
//#define SENDDEVDESCP 1 // 发送子设备描述记录,E-->C
//#define SENDDEVSTATE 2 // 发送子设备状态数据,E-->C
//#define SENDDEVCTRLL 3 // 控制子设备工作指令,C-->E
//1、发送设备数据表
uint8 table[80];
addr=NLME_GetShortAddr();
table[0]=SENDDATAHead>>8;
table[1]=(SENDDATAHead & 0x00F0)+SENDDEVTABLE;
table[2]=addr&0xFF;
table[3]=addr>>8;
table[4]=sizeof(devicetable_t);
memcpy((void*)(table+5), (void*)deviceinfo, table[4]);
sended=HalUARTWrite(0,(uint8 *)table,5+table[4]);//回复数据给PC
//2、发送各个子设备的描述
/*****************DO**************************/
DelayMS(1000); //稍微延时
cnt=getdevicenumbers(deviceinfo->subdevices[DO]); //DO子设备数量
size=sizeof(devicedescription_t); //子设备信息描述结构体大小
for (i=0;i<cnt;i++)
{
devdes=DODevices+i;
table[1]=(SENDDATAHead & 0x00F0)+SENDDEVDESCP; //发送子设备信息描述
table[4]=size;
memcpy((void*)(table+5), (void*)devdes, size);
//sended=
HalUARTWrite(0,(uint8 *)table,5+size);//回复数据给PC
DelayMS(1000); //稍微延时
HalUARTPoll();
}
/*****************AI**************************/
cnt=getdevicenumbers(deviceinfo->subdevices[AI]); //AI子设备数量
size=sizeof(devicedescription_t); //子设备信息描述结构体大小
for (i=0;i<cnt;i++)
{
devdes=AIDevices+i;
table[1]=(SENDDATAHead & 0x00F0)+SENDDEVDESCP; //发送子设备信息描述
table[4]=size;
memcpy((void*)(table+5), (void*)devdes, size);
sended=HalUARTWrite(0,(uint8 *)table,5+size);//回复数据给PC
DelayMS(50); //稍微延时
}
}
3、收到终节点数据,直接转发给监控平台
static void GenericApp_MessageMSGCB( afIncomingMSGPacket_t *pkt )
{
switch ( pkt->clusterId )
{
case GENERICAPP_CLUSTERID:
HalUARTWrite(0,pkt->cmd.Data,pkt->cmd.DataLength);//直接回复数据给PC
break;
}
}
好了! 程序写入CC2530,这个基站就是一个同的设备了。您家里有一个中东东足够了。成本不超过100元。
下一篇介绍终端设备的程序设计。