主机接口的信息处理流程

在我们翻译的文档中是用电脑端来模拟主机的,电脑代替网关发送主机接口命令的环节是在zll_controller.c中实现的,(在下载的文件中已经提供了其对应的可执行文件zllCmdLine.exe)下面我们看看这个文件的结构。

先看最前面的两个函数:

void usage( char* exeName )

printf("Usage: ./%s <port>\n", exeName);

printf("Eample: ./%s /dev/ttyACM0\n", 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: \n");

printf("    Network Addr    :0x%04x\n    End Point       :0x%02x\n    Addr Mode       :0x%02x\n    Value           :0x%02x\n\n",

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\n", 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\n\n");

}

else

{

printf("processRpcSysApp: Command Error\n\n");

}

}

else

{

printf("processRpcSysApp: Unsupported MT App Msg\n");

}

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("\nzclGetStateCb:\n    Network Addr :
0x%04x\n    End Point    :
0x%02x\n    State        :
0x%02x\n\n",

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之间的信息往返,整个数据链就打通了。

附件列表

ZLL主机接口的信息处理流程的更多相关文章

  1. 深入理解Java:注解

    注解作用:每当你创建描述符性质的类或者接口时,一旦其中包含重复性的工作,就可以考虑使用注解来简化与自动化该过程. Java提供了四种元注解,专门负责新注解的创建工作. 元注解 元注解的作用就是负责注解 ...

  2. 深入理解Java:注解(Annotation)自定义注解入门

    转载:http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html 元注解: 元注解的作用就是负责注解其他注解.Java5.0定义了4个标准 ...

  3. TI Zigbee Light Link 参考设计

    TI  Zigbee Light Link 参考设计 原文出处: http://processors.wiki.ti.com/index.php/Category:ZigBee_Light_Link ...

  4. 注解Annotation 详解(转)

    要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. 元注解: 元注解的作用就是负责注解其他注解.Java5. ...

  5. annotation(@Retention@Target)详解

    一.注解:深入理解JAVA注解 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. 1.元注解(meta-a ...

  6. Java注解(Annotation)自定义注解入门

    要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的注解之前,我们就必须要了解Java为我们提供的元注解和相关定义注解的语法. 元注解: 元注解的作用就是负责注解其他注解.Java5. ...

  7. Java之注解

    package com.demo.test; import java.lang.annotation.Documented; import java.lang.annotation.ElementTy ...

  8. Java:注解(Annotation)自定义注解入门

    转载地址:http://www.cnblogs.com/peida/archive/2013/04/24/3036689.html 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义自己的 ...

  9. JAVA提高五:注解Annotation

    今天我们学习JDK5.0中一个非常重要的特性,叫做注解.是现在非常流行的一种方式,可以说因为配置XML 比较麻烦或者比容易查找出错误,现在越来越多的框架开始支持注解方式,比如注明的Spring 框架, ...

随机推荐

  1. docker open files的设置

  2. Redis Sentinel机制与用法(一)

    Sentinel spring 集群配置: 概述 Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕 ...

  3. 【leetcode】Valid Number

    Valid Number Validate if a given string is numeric. Some examples:"0" => true" 0.1 ...

  4. Wince下sqlce数据库开发(二)

    上次写到使用数据绑定的方法测试本地sqlce数据库,这次使用访问SQL Server的方法访问sqlce,你会发现他们是如此的相似... 参考资料:http://www.cnblogs.com/rai ...

  5. VMware Workstation 下进行 桥连接

    大家都知道进行桥连接的时候,需要我们的宿主机与虚拟机同处于一个网络段, 使得mask与默认网关相同即可进行连接 ; 本地的IP .掩码 . 网关: 虚拟机的Ip 掩码,网关: // 当然这里的DNS ...

  6. Storm自带测试案例的运行

    之前Storm安装之后,也知道了Storm的一些相关概念,那么怎么样才可以运行一个例子对Storm流式计算有一个感性的认识呢,那么下面来运行一个Storm安装目录自带的测试案例,我们的Storm安装在 ...

  7. Java for LeetCode 223 Rectangle Area

    Find the total area covered by two rectilinear rectangles in a 2D plane. Each rectangle is defined b ...

  8. pthread_cond_wait的原子性

    使用的基本模板如下(参考APUE): signal代码序列如下, pthread_mutex_lock ... pthread_cond_signal pthread_mutex_unlock   w ...

  9. JavaScript高级程序设计学习笔记--变量、作用域和内存问题

    传递参数 function setName(obj){ obj.name="Nicholas"; obj=new object(); obj.name="Greg&quo ...

  10. 回发或回调参数无效。在配置中使用 <pages enableEventValidation="true"/> 或在页面中使用 <%@ Page EnableEventValidation="true" %> 启用了事件验证。

    问题补充: “/Source”应用程序中的服务器错误. 回发或回调参数无效.在配置中使用 <pages enableEventValidation="true"/> 或 ...