主机接口的信息处理流程

在我们翻译的文档中是用电脑端来模拟主机的,电脑代替网关发送主机接口命令的环节是在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. git stash简介

    原文:http://gitbook.liuhui998.com/4_5.html 一.基本操作 当你正在做一项复杂的工作时, 发现了一个和当前工作不相关但是又很讨厌的bug. 你这时想先修复bug再做 ...

  2. HTTP 格式

    HTTP请求报文和HTTP响应报文 HTTP报文是面向文本的,报文中的每一个字段都是一些ASCII码串,各个字段的长度是不确定的.HTTP有两类报文:请求报文和响应报文. HTTP请求报文 一个HTT ...

  3. Node.js——Async

    一:流程控制 为了适应异步编程,减少回调的嵌套,我尝试了很多库.最终觉得还是async最靠谱. 地址:https://github.com/caolan/async Async的内容分为三部分: 流程 ...

  4. Html5 postMessage

    解释: 跨文档消息传输Cross Document Messaging. 编写代码前注意判断浏览器是否支持Html5 实例: b页面向a页面发送消息. <!DOCTYPE> <htm ...

  5. linux线程的实现

    首先从OS设计原理上阐明三种线程:内核线程.轻量级进程.用户线程 内核线程 内核线程就是内核的分身,一个分身可以处理一件特定事情.这在处理异步事件如异步IO时特别有用.内核线程的使用是廉价的,唯一使用 ...

  6. 【编程题目】输入两个整数 n 和 m,从数列 1,2,3.......n 中 随意取几个数, 使其和等于 m ... ★

    第 21 题(数组)2010 年中兴面试题编程求解:输入两个整数 n 和 m,从数列 1,2,3.......n 中 随意取几个数,使其和等于 m ,要求将其中所有的可能组合列出来. 我的思路: 从小 ...

  7. svn 默认忽略静态库 .a文件解决办法

    我也是在向SVN服务器上传文件时,遇到了这个问题,文件上传后,再下载后发现所有的.a文件全部丢失,后来才知道是上传文件的时候.a文件根本就没传上去,查找原因才知道上传的时候.a文件被过滤掉了,后来找到 ...

  8. java操作数据库出错

    "无效的列索引"其实是个低级的错误 出错原因:1.sql串的?号数目和提供的变量数目不一致:例如:jdbcTemplate.update(sql, new Object[] {ne ...

  9. CityEngine基于规则贴图的实现技巧

    转自:http://blog.sina.com.cn/s/blog_841eeb5201010p3e.html CityEngine在贴图的实现过程有两种方式:第一种是通过i(geometryPath ...

  10. 打开Genesis设置单位为mm

    打开Genesis界面: 点击Configuration: 可看到只要设置get_def_units的值即可: 打开C:\genesis\sys\config配置文件,在最后一行加入:get_def_ ...