本帖最后由 夜哈哈2012 于 2020-10-31 21:54 编辑
经过两个月的使用,让我对5189 有了更深入的了解,从毕业就开始接触无线,对5189也不算陌生,从非正式的SDK开始,慢慢对ZigBee整个体系结构有了了解,进而去做一些比较有意思的事情。
基于5189的模拟ZigBee 设备的方法
目录
一、需要解决的问题
二、实现的步骤方法
三、测试结果
四、总结
一、需要解决的问题
在我们的开发测试中,常常有场景需要使用到以前开发的设备用来测试整个ZigBee网络的交互情况,但是由于现场空间限制或者其他原因,我们并不能拿出这么多真实的设备去现场测试,此时我们就可以使用dongle 或者开发板 模拟 以前设备的报文交互 来模拟测试整个网络与设备的交互可靠性。达到不要真实设备在场,就能测试的效果。这在一些工厂环境或者现场安装测试环境中有着重要的作用。
二、实现的步骤方法
整体实现方法:
通过设备串口接收相关从PC端发送来的串口指令,从而实现扫描入网,报文发送,离网等ZigBee设备的行为。从而实现一个ZigBee设备在ZigBee网络中的行为交互,完成模拟设备的整体功能。
整体架构
file:///C:UsersitAppDataLocalTempksohtml15124wps1.jpg
我们通过dongle去模拟一个实际的设备主要有以下操作:
1.设备加入一个ZigBee网关中,形成一个ZigBee网络
(1)首先我们要了解ZigBee设备入网的整体流程
参考文档《Base Device Behavior Specification》
一个ZigBee3.0设备如何加入ZigBee网络中,在ZigBee联盟中已经有明确的步骤规范了
详见:8.3 Network steering procedure for a node not on a network
联盟规定了详细的入网步骤
file:///C:UsersitAppDataLocalTempksohtml15124wps2.jpg
在5189 的SDK 中 ,steering 是符合联盟的规定的,只要我们发起steering ,剩下的步骤协议栈已经做好了,我们其实什么也不需要干,开启steering 即可。
在网关端(此处用dongle 模拟网关,烧录AN1247 网关固件),然后建立一个ZigBee网络,并允许设备入网:
具体操作步骤如下图
file:///C:UsersitAppDataLocalTempksohtml15124wps3.jpg
设备端(用开发板模拟一个子设备)
同样可以使用GUI 去实现入网等操作
file:///C:UsersitAppDataLocalTempksohtml15124wps4.jpg
通过上述步骤我们可以让开发板模拟一个子设备加入一个网关中。完成一个包含ZigBee协调器,和ZigBee router 的ZigBee网络的搭建。
(2)报文传输解析
在步骤一将设备加入网关后,我们需要开发板模拟的设备发送对应(真实设备发出)的报文,来模拟真实的网络交互测试。如果设备只用标准的报文发送的话,单纯使用NXP 提供的ZWGUI即可实现对应的测试,但是在实际的产品应用中,我们经常会用到私有的cluster 定义与自己的网关端交互。所以这里介绍一种比较灵活的ZigBee数据接收与自组包发送的机制。
上面我们说到AN1247 协调器的例程处理协调器外,可以设置成router ,我们正是利用这一例程进行改造。让他可以除了接收NXP定义的标准串口处理外,也能按照NXP串口处理的格式,接收我们自定义的命令,然后按我们自己组成ZigBee 报文的方式发送ZigBee 报文。
串口接收:关于NXP ZigBee串口帧如何组成和解析可以参考我上一个帖子的例程,这里不再多介绍,这里主要介绍在AN1247里面,那个部分按照nxp的机制接收数据与处理 。
AN里面对从PC端发送过来的串口命令都是在以下部分处理的
file:///C:UsersitAppDataLocalTempksohtml15124wps5.jpg
在函数
PUBLIC void APP_vProcessIncomingSerialCommands ( uint8 u8RxByte )
处理对应类型对应的事件
如下每一个case 代表NXP 定义的一种事件类型
case (E_SL_MSG_START_RECEIVE_MULTI_FRAME):
在下面serialLink.h 里面定义所有标准事件处理的类型,与文档对应
file:///C:UsersitAppDataLocalTempksohtml15124wps6.jpg
自定义数据处理步骤:
在seriallink.h ,NXP 官方定义的事件枚举下,定义自己的事件类型,注意不能和官方已定义的冲突
- typedef enum
- {
- 。。。。。。。。
- E_SL_MSG_AHI_DIO_SET_DIRECTION = 0x0801,
- E_SL_MSG_AHI_DIO_SET_OUTPUT = 0x0802,
- //add test
- E_SL_MSG_MY_MSG_TEST = 0x9FA0,
- } teSL_MsgType;
然后在APP_vProcessIncomingSerialCommands 增加自己的case 事件处理
au8LinkRxBuffer里面存在着串口数据的playload 部分 提取出来处理
- Eg:
- Case (E_SL_MSG_MY_MSG_TEST):
- {
- Data1 = au8LinkRxBuffer[0];
- Data2 = au8LinkRxBuffer[1];
- }
构建自己的发包函数:
这里构建了一个简单的report 报文
- uint8_t APP_u8MsgSend( void )
- {
- ZPS_teStatus eStatus;
- unsigned int u16Offset;
- PDUM_thAPduInstance hAPduInst;
- // Write command header
- uint8 u8Seq = u8GetTransactionSequenceNumber();
- // Node is not in network
- if( eGetNodeState() != E_RUNNING )
- {
- return E_ZCL_SUCCESS;
- }
- // Normal
- hAPduInst = PDUM_hAPduAllocateAPduInstance( apduZCL ); // apduZCL
- if ( hAPduInst == PDUM_INVALID_HANDLE )
- {
- return E_ZCL_ERR_ZBUFFER_FAIL;
- }
-
- u16Offset = u16ZCL_WriteCommandHeader(
- hAPduInst,
- 0, //eFrameType,
- TRUE, //mfr.specific
- ZCL_MANUFACTURER_CODE,
- TRUE, //From server to client
- TRUE, //Disable default response
- u8Seq,
- E_ZCL_REPORT_ATTRIBUTES );
- //Write attribute ID
- u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,
- u16Offset, "h", E_CLD_ATTR_ID_MSG); //u16AttributeID
- //Write attribute data type
- u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,
- u16Offset, "b",
- E_ZCL_OSTRING);
- //数据长度
- u16Offset+=PDUM_u16APduInstanceWriteNBO(hAPduInst,u16Offset,"b",u8ModualLen);
- u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,u16Offset, "b", 3);
- u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,u16Offset, "b", E_ZCL_INT8);
- u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,u16Offset, "b", psHeartBeat->i8Temp);
- //Send command
- tsZCL_Address ts_DestAddress;
- ts_DestAddress.eAddressMode = E_ZCL_AM_SHORT;
- ts_DestAddress.uAddress.u16DestinationAddress = DEST_SHORT_ADDRESS;
- uint8 u8SrcEndPoint = app_u8GetDeviceGeneralEndpoint(0);
- eStatus = eZCL_TransmitDataRequest(
- hAPduInst, //
- u16Offset, //PayloadSize
- u8SrcEndPoint, //Source EndPoint Id
- DEST_ENDPOINT, //Destination Endpoint Id
- GENERAL_CLUSTER_ID_PRIVATE, //Cluster ID basic cluster id
- &ts_DestAddress);//Address
- if (eStatus)
- {
- PDUM_eAPduFreeAPduInstance(hAPduInst);
- DBG_vPrintf(DEBUG_ERROR_ONLY, "n%s failed: 0x%02xn",__FUNCTION__, eStatus);
- }
- return eStatus;
- }
这样我们就可以随心所欲的在AN1247 这个例程里面添加自己的自定义的串口事件处理了。
(3)自动化切换
这里我们为了高效的去自动化切换模拟的设备就需要Python 脚本的配合了,通过模拟官方ZWGUI的串口发送(参照上一个帖子)将整个测试过程模拟下来,不断的更换设备的modle ID 执行不同的报文发送内容。达到自动化测试的过程。
file:///C:UsersitAppDataLocalTempksohtml15124wps7.jpg
- def nxp_data_unpack(bytes_stuffing):
- """
- 去除escape character 需要确保输入一定是bytes,输出bytes_reserves
- :param bytes_stuffing: bytes type
- :return:
- """
- escape_char_flag = False
- bytes_reserves = bytes()
- for item in bytes_stuffing:
- item_hex = item
- if item_hex == 0x02:
- escape_char_flag = True
- elif escape_char_flag:
- bytes_reserves += struct.pack('B', item_hex ^ 0x10)
- escape_char_flag = False
- else:
- bytes_reserves += struct.pack('B', item_hex)
- return bytes_reserves
- def nxp_data_pack(msg_type, msg_data):
- """
- NXP数据包装
- The protocol reserves byte values less than 0x10 for use as special characters (Start and
- End characters, for example). So to allow data which contains these reserved values to be
- sent, a procedure known as “byte stuffing” is used. This consists of identifying a byte to be
- sent that falls into the reserved character range, sending an Escape character (0x02) first,
- followed by the data byte XOR’d with 0x10.
- For example, if a non-special character with the value of 0x05 is to be sent:
- • Send the Escape byte (0x02)
- • XOR the byte to be sent with 0x10 (0x05 xor 0x10 = 0x15)
- • Send the modified byte
- The messages consist of the following: • Start character (special character)
- • Message type (byte stuffed)
- • Message length (byte stuffed)
- • Checksum (byte stuffed)
- • Message data (byte stuffed)
- • End character (special character)
- :param msg_type: list type, 如msg type 为0x0012 则[0x00, 0x12]
- :param msg_data: list type
- :return:
- """
- start_char = 0x01
- end_char = 0x03
- # ??msg_len
- msg_len = len(msg_data)
- check_sum = 0
- check_sum ^= msg_type[0]
- check_sum ^= msg_type[1]
- if msg_data:
- for each_byte in msg_data:
- check_sum ^= each_byte
- msg_len = [x for x in bytes(struct.pack('>H', msg_len))]
- check_sum ^= msg_len[0]
- check_sum ^= msg_len[1]
- command_in_hex = list()
- command_in_hex.append(start_char)
- command_in_hex.extend(msg_type)
- command_in_hex.extend(msg_len)
- command_in_hex.append(check_sum)
- command_in_hex.extend(msg_data)
- command_in_hex.append(end_char)
- # stuffing and send to port
- command = Bytes_Stuffing(bytes(command_in_hex))
- # Send
- return command
三、测试环境说明
利用开发板和dongle 搭建 ZigBee网络,并用sniffer 通过ubiqua 软件对空中报文进行抓包,对报文和交互流程进行观察,验证结果。
file:///C:UsersitAppDataLocalTempksohtml15124wps8.jpg
file:///C:UsersitAppDataLocalTempksohtml15124wps9.jpg
四、总结
上述的过程还存在很多不足的地方,例如目前只能通过sniffer 去观察交互的流程,Python脚本没有对返回的一些错误而进行反馈处理等。但是经过每一步的步骤学习与实验都会对5189整体的处理流程和ZigBee 网络的特性有进一步的了解和认识。