主机接口的信息处理流程
在我们翻译的文档中是用电脑端来模拟主机的,电脑代替网关发送主机接口命令的环节是在zll_controller.c中实现的,(在下载的文件中已经提供了其对应的可执行文件zllCmdLine.exe)下面我们看看这个文件的结构。
先看最前面的两个函数:
void usage( char* exeName )
printf("Usage: ./%s <port> ", exeName);
printf("Eample: ./%s /dev/ttyACM0 ", exeName);
}
这个函数是用来显示程序名及一些附加信息的
void commandUsage( void )//用来提示本程序的功能信息
上面两个函数对我们的STM32网关无关紧要,在主函数中接下来使用zllSocOpen( (char*)argv[1] );打开了主机到CC2531的串口,这一功能需要移植到STM32网关中,其具体实现过程在此不探究。
接下来主函数调用了zllSocRegisterCallbacks( zllSocCbs );用来注册回调函数,这个功能的加入是主机接口中最难理解的,在后面我会详细分析,现在我们只需要知道一点,根据后面的分析,这里注册的回调函数是主机(也就是网关)接收到CC2531的数据并解析后调用的函数。
最后在主函数中用while(1)循环来处理用户从命令行输入的参数,
while(1)
{
processConsoleCommand();
}
在processConsoleCommand();中接收了用户输入的参数,如:
再调用
getConsoleCommandParams(cmdBuff, &nwkAddr, &addrMode, &endpoint, &value, &transitionTime);对参数进行了一些处理,这个函数我们也不需要清楚是如何实现的,因为其是对电脑模拟网关时参数的一种处理方式。而在STM32网关中我们已经实现了标准命令的解析。
接下来是重点,对参数处理过后,就需要解析用户要干什么,这是通过一系列条件语句实现的,我还是以设置灯的状态为例,如:
else if((strstr(cmdBuff, "setstate")) != 0)
{
zllSocSetState(value, nwkAddr, endpoint, addrMode);
printf("setstate command executed with params: ");
printf(" Network Addr :0x%04x End Point :0x%02x Addr Mode :0x%02x Value :0x%02x ",
nwkAddr, endpoint, addrMode, value);
} 从上面可以看看到,当程序解析到用户需要“setstate”,就调用了相应的主机接口函数,(红字标注)。这就与我上一篇文档分析的STM32网关解析命令后调用相应的主机接口函数的过程衔接起来了,这一过程在STM32中我们已经实现,也无需移植。
接下来我们看看zllSocSetState(value, nwkAddr, endpoint, addrMode);这个函数我们是需要移植的(在zllSocCmd.c中)
void zllSocSetState(uint8_t state, uint16_t dstAddr, uint8_t endpoint, uint8_t addrMode)
{
uint8_t cmd[] = {
0xFE,
11, /*RPC payload Len */
0x29, /*MT_RPC_CMD_AREQ + MT_RPC_SYS_APP */
0x00, /*MT_APP_MSG */
0x0B, /*Application Endpoint */
(dstAddr & 0x00ff),
(dstAddr & 0xff00) >> 8,
endpoint, /*Dst EP */
(ZCL_CLUSTER_ID_GEN_ON_OFF & 0x00ff),
(ZCL_CLUSTER_ID_GEN_ON_OFF & 0xff00) >> 8,
0x04, //Data Len
addrMode,
0x01, //0x01 ZCL frame control field. (send to the light cluster only)
transSeqNumber++,
(state ? 1:0),
0x00 //FCS - fill in later
};
calcFcs(cmd, sizeof(cmd));
COM_Write( hZllSoc,cmd, sizeof(cmd));
}
这个函数结构是很简单的,先构造命令,再构造帧,最后通过串口发给CC2531(我猜的)
对于上述这类“set ~~~”命令,是有去无回的,即CC2531接收到命令后就去执行,至于结果成功与否,CC2531都不会返回信息给主机。但是对于“get····”命令,如:”get state”,即获取灯的状态,CC2531接收到命令后,立即执行,当CC2531获取了灯的状态后还得将信息返回给主机,主机再转发给用户。那主机(网关)中是如何处理来自CC2531的消息的呢?这里我们就讲到了一个很重要的需要移植的一些函数,这些函数在zllSocCmd.c中实现的
当主机接收到CC2531的信息后,假设其存储在uint8_t* rpcBuff中,就调用
void zllSocProcessRpc (uint8_t* rpcBuff)
来处理这个消息,下面我们看看这个函数
void zllSocProcessRpc (uint8_t* rpcBuff)
{
static uint8_t retryAttempts = 0;
switch (rpcBuff[0] & MT_RPC_SUBSYSTEM_MASK)
{
case MT_RPC_SYS_DBG:
{
processRpcSysDbg(&rpcBuff[0]);
break;
}
case MT_RPC_SYS_APP:
{
processRpcSysApp(&rpcBuff[0]);
break;
}
default:
{
printf("zllSocProcessRpc: CMD0:%x, CMD1:%x, not handled ", rpcBuff[0], rpcBuff[1] );
break;
}
}
这个函数我们也不用深究,应该可以完整移植,我们简单看看其过程,switch语句中解析了消息的类别,即MT_RPC_SYS_DBG:和MT_RPC_SYS_APP,前一种是调试信息,如显示“命令成功接收”和“命令错误”等,这个不是重点,我们的重点在后一种情况,即接收到的是应用信息,下面我们看看其调用的函数processRpcSysApp(&rpcBuff[0]),也是要移植的
void processRpcSysApp(uint8_t *rpcBuff)
{
if( rpcBuff[1] == MT_APP_ZLL_TL_IND )
{
processRpcSysAppTlInd(&rpcBuff[2]);
}
else if( rpcBuff[1] == MT_APP_ZLL_NEW_DEV_IND )
{
processRpcSysAppNewDevInd(&rpcBuff[2]);
}
else if( rpcBuff[1] == MT_APP_RSP )
{
processRpcSysAppZclRsp(&rpcBuff[2]);
}
else if( rpcBuff[1] == 0 )
{
if( rpcBuff[2] == 0)
{
printf("processRpcSysApp: Command Received Successfully ");
}
else
{
printf("processRpcSysApp: Command Error ");
}
}
else
{
printf("processRpcSysApp: Unsupported MT App Msg ");
}
return;
}
从这个函数我们可以看到应用消息也有三种:
① TouchLink触摸指示 MT_APP_ZLL_TL_IND
② 新设备指示 MT_APP_ZLL_NEW_DEV_IND
③ 应用响应(如:状态响应,亮度响应,色调响应,饱和度响应) MT_APP_RSP
三种消息分别调用了三种处理函数:这三个函数都是需要移植的
processRpcSysAppTlInd(&rpcBuff[2]);
processRpcSysAppNewDevInd(&rpcBuff[2]);
processRpcSysAppZclRsp(&rpcBuff[2]);
这里我只以第三个消息处理函数为例,(在zllSocCmd.c中),因为是分析流程,具体代码在此不粘贴,可自行查看。我还是以获取灯的状态为例,即已经解析到CC2531发来的消息是响应灯的状态的,也就是说信息中含有说明灯的状态的字段,其解析过程我也分析了,可以完整移植,接下来主机应该将灯的状态返回给用户了,我们看看实现过程:
if( (clusterID == ZCL_CLUSTER_ID_GEN_ON_OFF) && (attrID ==
ATTRID_ON_OFF) )
{
if(zllSocCb.pfnZclGetStateCb)
{
uint8_t state = zclRspBuff[0];
zllSocCb.pfnZclGetStateCb(state, nwkAddr, endpoint);
}
}
这里我们就用到了我们前面注册的回调函数,我们回过头来看看这个注册过程是如何实现的,在前面我们知道主函数调用zllSocRegisterCallbacks( zllSocCbs );注册回调函数,我们先看看参数zllSocCbs,在zll_controller.c中定义为
static zllSocCallbacks_t zllSocCbs =
{
tlIndicationCb, // pfnTlIndicationCb - TouchLink Indication callback
NULL, // pfnNewDevIndicationCb - New Device Indication callback
zclGetStateCb, // pfnZclGetHueCb - ZCL response callback for get Hue
zclGetLevelCb, //pfnZclGetSatCb - ZCL response callback for get Sat
zclGetHueCb, //pfnZclGetLevelCb_t - ZCL response callback for get Level
zclGetSatCb //pfnZclGetStateCb - ZCL response callback for get State
};
其数据类型为zllSocCallbacks_t的结构体,在zllSocCmd.h中已定义
typedef struct
{
zllSocTlIndicationCb_t pfnTlIndicationCb; // TouchLink Indication callback
zllNewDevIndicationCb_t pfnNewDevIndicationCb; // New device Indication callback
zllSocZclGetStateCb_t pfnZclGetStateCb; // ZCL response callback for get State
zllSocZclGetLevelCb_t pfnZclGetLevelCb; // ZCL response callback for get Level
zllSocZclGetHueCb_t pfnZclGetHueCb; // ZCL response callback for get Hue
zllSocZclGetSatCb_t pfnZclGetSatCb; // ZCL response callback for get Sat
} zllSocCallbacks_t;
也就是说在zll_controller.c我们定义了一个这种结构的数据类型,并对其赋了初值,如现在zllSocCbs. pfnZclGetStateCb= zclGetStateCb
这个参数我们说清楚了,下面我们看看zllSocRegisterCallbacks( zllSocCbs );(在zllSocCmd.c中)的过程
void zllSocRegisterCallbacks( zllSocCallbacks_t zllSocCallbacks)
{
//copy the callback function pointers
memcpy(&zllSocCb, &zllSocCallbacks, sizeof(zllSocCallbacks_t));
return;
}
函数中zllSocCallbacks就是参数zllSocCbs,实际上就是将zllSocCbs复制给了zllSocCb,zllSocCb是在zllSocCmd.c中定义的与zllSocCbs类型相同的一个全局量
zllSocCallbacks_t zllSocCb;
现在我们可以回到processRpcSysAppZclRsp(&rpcBuff[2]);了,在得到“灯的状态”后,调用了
zllSocCb.pfnZclGetStateCb(state, nwkAddr, endpoint);
乍一看我们不知道它调用了什么函数,这时我们看在zllSocCmd.h中有一些指针函数定义
typedef uint8_t (*zllSocTlIndicationCb_t)(epInfo_t *epInfo);
typedef uint8_t (*zllNewDevIndicationCb_t)(epInfo_t *epInfo);
typedef uint8_t (*zllSocZclGetStateCb_t)(uint8_t state, uint16_t nwkAddr, uint8_t endpoint);
typedef uint8_t (*zllSocZclGetLevelCb_t)(uint8_t level, uint16_t nwkAddr, uint8_t endpoint);
typedef uint8_t (*zllSocZclGetHueCb_t)(uint8_t hue, uint16_t nwkAddr, uint8_t endpoint);
typedef uint8_t (*zllSocZclGetSatCb_t)(uint8_t sat, uint16_t nwkAddr, uint8_t endpoint);
pfnZclGetStateCb的数据类型就是zllSocZclGetStateCb_t
实际上前面的分析我们知道zllSocCb.pfnZclGetStateCb=zllSocCbs. pfnZclGetStateCb= zclGetStateCb;也就是说这里本质上调用的是
zclGetStateCb (state, nwkAddr, endpoint);这个函数的类型就是前面高亮显示的指针函数类型
下面我们看看zclGetStateCb (state, nwkAddr, endpoint)是干什么的,(在zll_controller.c中)
uint8_t zclGetStateCb(uint8_t state, uint16_t nwkAddr, uint8_t endpoint)
{
printf(" zclGetStateCb: Network Addr : 0x%04x End Point : 0x%02x State : 0x%02x ",
nwkAddr, endpoint, state);
return 0;
}
这个函数很简单,就是将得到的信息(包括灯的状态)打印给用户看,当然,这里是用电脑模拟主机(网关),只要在电脑上显示给用户看就行了,那如果是真正的网关需要调用的回调函数应该是将这个信息发送给客户端,即在interface_srpcserver.c中的
void RPCS_ZLL_CallBack_getStateRsp(uint8_t state, uint16_t srcAddr, uint8_t endpoint, uint32_t clientFd);
这就需要修改相应的回调函数了。
自此,客户端与网关之间的信息往返,网关与CC2531之间的信息往返,整个数据链就打通了。