ZWave对COMAND CLASS的处理流程
文章主题
在开发一个 ZWave Device 的过程中,对 COMAND CLASS(单词太长了,后面就简写为 CC 啦) 的处理是最基本、最重要的工作。这篇文章以最最简单的 CC:COMMNAD_CLASS_BASIC 为例子,来拆解、分析应用层对它的处理流程。
内容导航
接收指令
处理指令
发送数据
接收指令
1. 指令接口函数
我们就以一个最简单的场景为示例:网关 G 发送指令:COMMNAD_CLASS_BASIC 给设备 D。这个 CC 包含 3 个 COMMAND,分别是: BASIC_GET, BASIC_REPORT, BASIC_SET,官方提供的文档里对这 3 个命令有详细解释。(这里要说一句:官方的文档太 TMD 的多了,多的看不过来!)
先上图1:
是不是太乱了,有点接受不了啊?将就着看吧,我已经尽最大努力了,如果你会 PS,可以教教我。
也就是上图中标记0的地方,看起来是不是这些数据结构挺挺长、挺吓人的?!其实 C 语言开发中,我觉得核心就是数据结构和算法,其中数据结构又决定了算法。在分析 ZWave 代码的过程中,有很大一部分就是分析数据结构,当然了这是我的观点。
图中标记1是返回值的定义,它是一个枚举类型。
图中标记2是参数 rxOpt 的数据结构,它是一个结构体,这里结构体里面关于 NODE_ID 的解释可以看一下源代码,比较简单。
图中标记3是参数 pCmd 的数据结构,这里是重点:这是一个[共用体],简单的说,就是内存中的一块地址空间,你可以按照不同的组织方式去理解其中保存的内容。
2. 关于 C 语言的共用体/共同体/union
刚才的藐视似乎有点拗口,举个栗子,比如:有一块地址空间一共有 4 个字节的容量:
你可以定义一个指针 char *p, p 指向这个地址空间的首地址 A,然后你就可以读取或者写入一 char 类型的数据;然后移动指针 p 到第二个字节的位置 A+1,读取或者写入另一 char 类型的数据;再然后是第三个字节的地址 A+2;再然后是第四个字节的地址 A+3。
你也可以定义一个指针 int *p,指针 p 指向这个地址空间的首地址 A,此时,你可以直接读取/写入一个 4 字节的 int 型数据。
理解了上面的内容,那么对 ZWave 指令部分的处理可以说就理解一半了。回到参数 pCmd,当设备D 接收到不同的 CC 指令时,这个指针所指向的地址空间就保存了不同的数据(什么?这个地址空间是在哪里分配的?我也不知道。可以肯定的是协议层为我们准备好的,应用开发者不用操心这个事情)。至于如何解析这些指令,当然是参考官方提供的文档,其中详细说明了每一个 CC 所对应的指令中,每一个字节,每一个 bit 代表什么意思。
注意:在这个地址空间中,开头2个字节的意思一定是确定的,也就是图1中标记5列出的:cmdClass 和 cmd。
所以,在这个指令接收函数中,首先通过 pCmd->ZW_Common.cmdClass 来判断该指令是哪一个 COMMAND CLASS。也就是说,pCmd 理解成:这个指针指向了 ZW_COMMON_FRAME 这个结构体。所以,我们就知道了当前的指令是:COMMAND_CLASS_BASIC,从而进入了第一个 case 中调用处理函数:handleCommandClassBasic()
处理指令
handleCommandClassBasic( ) 函数位于 ApplicationHandlers 文件夹下面,这里应该说是官方为了降低开发者的难度,把所有 CC 的处理逻辑的共性提取出来。
继续上图:图2。

好像比图1更乱了?!真的是尽力了。
这个函数首先判断指令是 COMMAND_CLASS_BASIC 中的哪一个 COMMAND: BASIC_SET or BASIC_GET。
1. BASIC_SET
看到没?参数 pCmd 又指向了另一个数据结构,即图中标记1处的 ZW_BASIC_SET_V2_FRAME。如果这里没有明白,需要回到上面再重新理解一下。
首先检查了参数 value 的范围,BYTE 的类型是通过 typedef 定义的,本质上是 unsigned char。ZWave 对 Basic Value 允许的范围是 0x00~0x63, 以及 0xFF,反正是代表不同的意思了。
然后调用函数 handleBasicSetCommand。 这个函数是什么鬼?在哪里? 原来这个函数是需要开发者自己实现的,官方已经替你在 CommandClassBasic.h 中声明了这个函数是外部的,也就是需要开发者实现的。
可以看到,传递了2个参数:
pCmd->ZW_BasicSetFrame.value
rxOpt->destNode.endpoint
第一个参数是设置的 value,第二个参数是说这个value是用来设置哪一个 EndPoint 的,比如接收指令的设备是一个有4个插孔的排插,那么每一个插孔就是一个 EndPoint。当然,如果当设备就是一个灯泡,那么这个 EndPoint 就等于0。
2. BASIC_GET
从字面上理解:就是发出指令的网关G要从接收指令的设备D获取一些信息,比如灯泡的亮度,那么设备D就要把当前的亮度值发送给网关G。于是,这里的处理流程是:
2.1 申请地址空间
申请的这块地址空间赋值给 pTxBuf,待会需要发送的数据就往这个地址放,图中标记2的地方。先看一下文件 ZW-tx_mutex.c 中一个重要的结构体变量:
static MUTEX myMutex;
这个 MUTEX 结构体定义在图中标记3处,也就是说,在系统的数据区域,默认定义了一块内存地址,专门用于在发送数据时使用。正因为这块地址空间是共用的,所以每个时刻只能有一个人使用,所以使用互斥量来保护这块地址空间。
简言之:谁先抢到谁先用(上锁),用完之后要归还(解锁)。
2.2 设置发送者、接收者参数
RxToTxOptions(rxOpt, &pTxOptionsEx);这个函数的作用就是:设置接收数据的nodeId 好 endpoint等参数, 发送者(也就是当前设备自己)的 sourceEndpoint, 以及其他一些安全上属性,都是从 rxOpt中复制而来。此时,设备D变成了发送者,网关G就编程了接受者。
注意这个函数中的2个静态变量:txOptionsEx, destNode。
2.3 往第一步得到的内存地址空间填数据
至于需要填哪些数据,看文档!
COMMAND_CLASS_BASIC 的 BASIC_GET 需要返回3个数据:
currentValue
targetValue
duration
这3个数据都是由开发者定义的3个函数提供,至于这几个值代表什么意思,就需要开发者根据所开发的具体产品类型来决定了。拿灯泡做栗子,当前亮度是50,目标亮度是80,还需要5秒钟达到目标亮度值。
2.4 发送数据
所有的数据发送都是调用函数Transport_SendResponseEP,传递的几个参数格式都是固定的,如果继续跟进到这个函数里,又是一个天地,特别是涉及到 MultiChannel 部分,也是比较复杂,以后再单独拿出来分析。
别忘了,发送完成之后,调用了函数 FreeResponseBuffer,把申请的内存地址空间释放掉,这里并不是 free掉,而只是解锁一下,对系统说:谢谢,我已经使用结束了,现在别人可以申请使用了。
总结
到这里,COMMAND_CLASS_BASIC 的分析过程就结束了,其他的 COMMAND CLASS 执行流程是完全一样的,有区别的地方就在于不同 CC 携带了不同的数据结构,当然,最开头的2个字节永远是固定的:cmdClass 和 cmd。
请容忍我再啰嗦两句啊。
ZWave 的开发博大精深,文档更是数不胜数。我进入 ZWave 的开发时间不长,以上分析过程难免会存在一些理解上的错误,希望没让您误入歧途。另外,知识的学习都是螺旋式的,不能追求一下子把所有相关的东西都理解正确,只要能满足当前的开发需求就可以了,循序渐进的提高、进步,最后就一定能够得到真经。
欢迎转载,请尊重版权,保留全部内容并注明来源。
如果这篇文章侵犯了您的权益,请在此处或微信公众号留言,我会及时处理。
如果需要一块探讨,请联系下面微信公众号。

ZWave对COMAND CLASS的处理流程的更多相关文章
- ENode 2.0 - 深入分析ENode的内部实现流程和关键地方的幂等设计
前言 ENode是一个基于消息的架构,使用ENode开发的系统,每个环节都是处理消息,处理完后产生新的消息.本篇文章我想详细分析一下ENode框架内部是如何实现整个消息处理流程的.为了更好的理解我后面 ...
- shell中命令的执行流程
在shell中,一个命令有3中写法: 1 可以直接写(Normal Command) 2 可以放在双引号中("Command") 3 可以放在单引号中('Comand') 这3中写 ...
- 深入分析ENode的内部实现流程和关键地方的幂等设计
ENode 2.0 - 深入分析ENode的内部实现流程和关键地方的幂等设计 前言 ENode架构图 ENode框架内部实现流程分析 Command的幂等处理 Domain Event持久化时的并发冲 ...
- EDKII Build Process:EDKII项目源码的配置、编译流程[三]
<EDKII Build Process:EDKII项目源码的配置.编译流程[3]>博文目录: 3. EDKII Build Process(EDKII项目源码的配置.编译流程) -> ...
- ZWave 中的消息队列机制
文章主题 在我们的日常编程中,对消息队列的需求非常常见,使用一个简洁.高效的消息队列编程模型,对于代码逻辑的清晰性,对于事件处理的高效率来说,是非常重要的.这篇文章就来看看 ZWave 中是通过什 ...
- Linux下服务器端开发流程及相关工具介绍(C++)
去年刚毕业来公司后,做为新人,发现很多东西都没有文档,各种工具和地址都是口口相传的,而且很多时候都是不知道有哪些工具可以使用,所以当时就想把自己接触到的这些东西记录下来,为后来者提供参考,相当于一个路 ...
- 基于netty http协议栈的轻量级流程控制组件的实现
今儿个是冬至,所谓“冬大过年”,公司也应景五点钟就放大伙儿回家吃饺子喝羊肉汤了,而我本着极高的职业素养依然坚持留在公司(实则因为没饺子吃没羊肉汤喝,只能呆公司吃食堂……).趁着这一个多小时的时间,想跟 ...
- 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?
在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...
- nginx+iis+redis+Task.MainForm构建分布式架构 之 (redis存储分布式共享的session及共享session运作流程)
本次要分享的是利用windows+nginx+iis+redis+Task.MainForm组建分布式架构,上一篇分享文章制作是在windows上使用的nginx,一般正式发布的时候是在linux来配 ...
随机推荐
- Istio-架构
读书笔记整理 工作机制:分为控制面和数据面 控制面:Pilot, Mixer(接收来自Envoy上报的数据), Citadel(证书和密钥管理) 数据面:Envoy 工作流程: 自动注入 应用程序启动 ...
- js-实现多列布局(瀑布流)
本文是使用面向对象的思想实现多列布局(瀑布流).当然,使用面向过程也能实现,具体效果图和案例如下: 具体实现代码如下: <!DOCTYPE html> <html lang=&quo ...
- 对于近似有序序列(即除掉少数K个元素后是有序序列且K<<n),试分析直接插入排序,冒牌排序和简单选择排序的时间复杂度
学弟问的一道数据结构的题,关于一些排序算法的时间复杂度. 针对近似有序序列, ①当使用直接插入排序时,其基本操作为数组中元素的移动.最好情况下,待排序列有序,无需移动,此时时间复杂度为O(n), 当为 ...
- oracle 多表连接查询 join
转 简介: 多表连接查询通过表之间的关联字段,一次查询多表数据. 下面将依次介绍 多表连接中的如下方法: 1.from a,b 2.inner join 3.left outer join 4.rig ...
- Python的多线程锁跟队列
一.互斥锁: 1.线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁. 2.互斥锁为资源引入一个状态:锁定.非锁定 3.某个线程要更改共享数据是,先将其锁定.此时资源的状态为锁定, ...
- AD17无法复制原理图到Word的解决方法
标题: 解决AD17无法复制原理图到WORD 作者: 梦幻之心星 347369787@QQ.com 标签: [AD, Word, 原理图] 目录: 软件 日期: 2019-3-17 目录 前提说明: ...
- C#线程 入门
Threading in C# 第一部分: 入门 介绍和概念 C#支持通过多线程并行执行代码.线程是一个独立的执行路径,能够与其他线程同时运行.C#客户端程序(控制台,WPF或Windows窗体) ...
- 深度学习玩LOL-游戏助手-概述
目标 用深度学习技术实现常规英雄联盟游戏助手的主要功能,功能主要包括:英雄推荐,装备推荐,地图预警等. 基本思路 首先使用图像分类算法模型对游戏客户端内的英雄头像进行截取和识别. 使用线性回归模型对可 ...
- Dubbo源码笔记-服务注册
今天来简单做一下Dubbo服务注册部分源码学习手记. 一.Dubbo配置解析 目前Dubbo最多的用法就是跟Spring集成,既然跟Spring集成,那么,Dubbo对象的实例化都将交由Spring统 ...
- UML ——区分类图中的几种关系.md
目录 关联关系 (association): 聚合关系 (aggregation): 合成关系 (composition): 依赖关系 (dependency): 总结: 原文地址 http://ww ...