发 帖  
原厂入驻New

基于NXP 5189的模拟ZigBee 设备的测试方法---结项贴

2020-10-31 21:21:48  167 ZigBee NXP 测试方法
分享
4
本帖最后由 夜哈哈2012 于 2020-10-31 21:54 编辑

首先感谢电子发烧友大联大提供的这次试用机会。
经过两个月的使用,让我对5189 有了更深入的了解,从毕业就开始接触无线,对5189也不算陌生,从非正式的SDK开始,慢慢对ZigBee整个体系结构有了了解,进而去做一些比较有意思的事情。




基于5189的模拟ZigBee 设备的方法
目录
一、需要解决的问题
二、实现的步骤方法
三、测试结果
四、总结
一、需要解决的问题
在我们的开发测试中,常常有场景需要使用到以前开发的设备用来测试整个ZigBee网络的交互情况,但是由于现场空间限制或者其他原因,我们并不能拿出这么多真实的设备去现场测试,此时我们就可以使用dongle 或者开发板 模拟 以前设备的报文交互 来模拟测试整个网络与设备的交互可靠性。达到不要真实设备在场,就能测试的效果。这在一些工厂环境或者现场安装测试环境中有着重要的作用。

二、实现的步骤方法
整体实现方法:
    通过设备串口接收相关从PC端发送来的串口指令,从而实现扫描入网,报文发送,离网等ZigBee设备的行为。从而实现一个ZigBee设备在ZigBee网络中的行为交互,完成模拟设备的整体功能。
整体架构
架构1 .png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps1.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
联盟规定了详细的入网步骤
入网.png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps2.jpg

5189 的SDK 中 ,steering 是符合联盟的规定的,只要我们发起steering ,剩下的步骤协议栈已经做好了,我们其实什么也不需要干,开启steering 即可。

在网关端(此处用dongle 模拟网关,烧录AN1247 网关固件),然后建立一个ZigBee网络,并允许设备入网:
具体操作步骤如下图
GUI1.png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps3.jpg

设备端(用开发板模拟一个子设备)
同样可以使用GUI 去实现入网等操作
GUI2.png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps4.jpg
通过上述步骤我们可以让开发板模拟一个子设备加入一个网关中。完成一个包含ZigBee协调器,和ZigBee router ZigBee网络的搭建。




(2)报文传输解析
在步骤一将设备加入网关后,我们需要开发板模拟的设备发送对应(真实设备发出)的报文,来模拟真实的网络交互测试。如果设备只用标准的报文发送的话,单纯使用NXP 提供的ZWGUI即可实现对应的测试,但是在实际的产品应用中,我们经常会用到私有的cluster 定义与自己的网关端交互。所以这里介绍一种比较灵活的ZigBee数据接收与自组包发送的机制。
上面我们说到AN1247 协调器的例程处理协调器外,可以设置成router ,我们正是利用这一例程进行改造。让他可以除了接收NXP定义的标准串口处理外,也能按照NXP串口处理的格式,接收我们自定义的命令,然后按我们自己组成ZigBee 报文的方式发送ZigBee 报文。
串口接收:关于NXP ZigBee串口帧如何组成和解析可以参考我上一个帖子的例程,这里不再多介绍,这里主要介绍在AN1247里面,那个部分按照nxp的机制接收数据与处理 。
AN里面对从PC端发送过来的串口命令都是在以下部分处理的
函数.png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps5.jpg
在函数
PUBLIC void APP_vProcessIncomingSerialCommands ( uint8    u8RxByte )
处理对应类型对应的事件
如下每一个case 代表NXP 定义的一种事件类型
case (E_SL_MSG_START_RECEIVE_MULTI_FRAME):
在下面serialLink.h 里面定义所有标准事件处理的类型,与文档对应
函数1.png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps6.jpg
自定义数据处理步骤:
seriallink.h ,NXP 官方定义的事件枚举下,定义自己的事件类型,注意不能和官方已定义的冲突
  1. typedef enum
  2. {
  3.        。。。。。。。。
  4. E_SL_MSG_AHI_DIO_SET_DIRECTION                              = 0x0801,
  5.     E_SL_MSG_AHI_DIO_SET_OUTPUT                                 = 0x0802,

  6. //add test
  7. E_SL_MSG_MY_MSG_TEST         =      0x9FA0,
  8. } teSL_MsgType;
复制代码


然后在APP_vProcessIncomingSerialCommands 增加自己的case 事件处理
au8LinkRxBuffer里面存在着串口数据的playload 部分 提取出来处理
  1. Eg:
  2. Case (E_SL_MSG_MY_MSG_TEST):
  3. {
  4.         Data1 = au8LinkRxBuffer[0];
  5.             Data2 = au8LinkRxBuffer[1];
  6. }
复制代码

构建自己的发包函数:
这里构建了一个简单的report 报文
  1. uint8_t    APP_u8MsgSend( void )   
  2. {
  3.         ZPS_teStatus  eStatus;
  4.         unsigned int u16Offset;
  5.         PDUM_thAPduInstance hAPduInst;
  6.         // Write command header
  7.         uint8 u8Seq = u8GetTransactionSequenceNumber();

  8.         // Node is not in network
  9.         if( eGetNodeState() != E_RUNNING )
  10.         {
  11.                 return E_ZCL_SUCCESS;
  12.         }

  13.         // Normal
  14.         hAPduInst = PDUM_hAPduAllocateAPduInstance( apduZCL );        // apduZCL
  15.         if ( hAPduInst == PDUM_INVALID_HANDLE )
  16.         {
  17.                 return E_ZCL_ERR_ZBUFFER_FAIL;
  18.         }
  19.       
  20.         u16Offset = u16ZCL_WriteCommandHeader(
  21.                         hAPduInst,
  22.                         0,                        //eFrameType,
  23.                         TRUE,                 //mfr.specific
  24.                         ZCL_MANUFACTURER_CODE,      
  25.                         TRUE,                //From server to client
  26.                         TRUE,                //Disable default response
  27.                         u8Seq,
  28.                         E_ZCL_REPORT_ATTRIBUTES );

  29.         //Write attribute ID
  30.         u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,
  31.                         u16Offset, "h", E_CLD_ATTR_ID_MSG);        //u16AttributeID

  32.         //Write attribute data type
  33.         u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,
  34.                                 u16Offset, "b",
  35.                                 E_ZCL_OSTRING);

  36. //数据长度
  37.         u16Offset+=PDUM_u16APduInstanceWriteNBO(hAPduInst,u16Offset,"b",u8ModualLen);


  38.         u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,u16Offset, "b", 3);
  39.         u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,u16Offset, "b", E_ZCL_INT8);
  40.         u16Offset += PDUM_u16APduInstanceWriteNBO(hAPduInst,u16Offset, "b", psHeartBeat->i8Temp);

  41.         //Send command
  42.         tsZCL_Address ts_DestAddress;
  43.         ts_DestAddress.eAddressMode = E_ZCL_AM_SHORT;
  44.         ts_DestAddress.uAddress.u16DestinationAddress = DEST_SHORT_ADDRESS;
  45.         uint8 u8SrcEndPoint = app_u8GetDeviceGeneralEndpoint(0);
  46.         eStatus = eZCL_TransmitDataRequest(
  47.                                                                 hAPduInst,        //
  48.                                                                 u16Offset,         //PayloadSize
  49.                                                                 u8SrcEndPoint,                //Source EndPoint Id
  50.                                                                 DEST_ENDPOINT,                //Destination Endpoint Id
  51.                                                                 GENERAL_CLUSTER_ID_PRIVATE, //Cluster ID  basic cluster id
  52.                                                                 &ts_DestAddress);//Address
  53.         if (eStatus)
  54.         {
  55.                 PDUM_eAPduFreeAPduInstance(hAPduInst);
  56.                 DBG_vprintf(debug_ERROR_ONLY, "\n%s faiLED: 0x%02x\n",__FUNCTION__, eStatus);
  57.         }
  58.         return eStatus;
  59. }
复制代码

这样我们就可以随心所欲的在AN1247 这个例程里面添加自己的自定义的串口事件处理了。


(3)自动化切换
     这里我们为了高效的去自动化切换模拟的设备就需要Python 脚本的配合了,通过模拟官方ZWGUI的串口发送(参照上一个帖子)将整个测试过程模拟下来,不断的更换设备的modle ID 执行不同的报文发送内容。达到自动化测试的过程。
自动化流程.png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps7.jpg

  1. def nxp_data_unpack(bytes_stuffing):
  2.     """
  3.     去除escape character 需要确保输入一定是bytes,输出bytes_reserves
  4.     :param bytes_stuffing: bytes type
  5.     :return:
  6.     """
  7.     escape_char_flag = False
  8.     bytes_reserves = bytes()
  9.     for item in bytes_stuffing:
  10.         item_hex = item
  11.         if item_hex == 0x02:
  12.             escape_char_flag = True
  13.         elif escape_char_flag:
  14.             bytes_reserves += struct.pack('B', item_hex ^ 0x10)
  15.             escape_char_flag = False
  16.         else:
  17.             bytes_reserves += struct.pack('B', item_hex)
  18.     return bytes_reserves
复制代码
  1. def nxp_data_pack(msg_type, msg_data):
  2.     """
  3.     NXP数据包装
  4.         The protocol reserves byte values less than 0x10 for use as special characters (Start and
  5.     End characters, for example). So to allow data which contains these reserved values to be
  6.     sent, a procedure known as “byte stuffing” is used. This consists of identifying a byte to be
  7.     sent that falls into the reserved character range, sending an Escape character (0x02) first,
  8.     followed by the data byte XOR’d with 0x10.
  9.     For example, if a non-special character with the value of 0x05 is to be sent:
  10.     • Send the Escape byte (0x02)
  11.     • XOR the byte to be sent with 0x10 (0x05 xor 0x10 = 0x15)
  12.     • Send the modified byte
  13.     The messages consist of the following: • Start character (special character)
  14.     • Message type (byte stuffed)
  15.     • Message length (byte stuffed)
  16.     • Checksum (byte stuffed)
  17.     • Message data (byte stuffed)
  18.     • End character (special character)
  19.     :param msg_type: list type, 如msg type 为0x0012 则[0x00, 0x12]
  20.     :param msg_data: list type
  21.     :return:
  22.     """
  23.     start_char = 0x01
  24.     end_char = 0x03
  25.     # ??msg_len
  26.     msg_len = len(msg_data)

  27.     check_sum = 0
  28.     check_sum ^= msg_type[0]
  29.     check_sum ^= msg_type[1]
  30.     if msg_data:
  31.         for each_byte in msg_data:
  32.             check_sum ^= each_byte
  33.     msg_len = [x for x in bytes(struct.pack('>H', msg_len))]

  34.     check_sum ^= msg_len[0]
  35.     check_sum ^= msg_len[1]

  36.     command_in_hex = list()
  37.     command_in_hex.append(start_char)
  38.     command_in_hex.extend(msg_type)
  39.     command_in_hex.extend(msg_len)
  40.     command_in_hex.append(check_sum)
  41.     command_in_hex.extend(msg_data)
  42.     command_in_hex.append(end_char)

  43.     # stuffing and send to port
  44.     command = Bytes_Stuffing(bytes(command_in_hex))
  45.     # Send
  46.     return command
复制代码








三、测试环境说明
利用开发板和dongle 搭建 ZigBee网络,并用sniffer 通过ubiqua 软件对空中报文进行抓包,对报文和交互流程进行观察,验证结果。
抓包1.png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps8.jpg
      抓包2.png file:///C:\Users\it\AppData\Local\Temp\ksohtml15124\wps9.jpg



四、总结
上述的过程还存在很多不足的地方,例如目前只能通过sniffer 去观察交互的流程,Python脚本没有对返回的一些错误而进行反馈处理等。但是经过每一步的步骤学习与实验都会对5189整体的处理流程和ZigBee 网络的特性有进一步的了解和认识。

相关经验

小菜鸟2020 2020-11-3 08:54:26
不错很实用,指导性很强
回复

举报

只有小组成员才能发言,加入小组>>

95个成员聚集在这个小组

加入小组

创建小组步骤

关闭

站长推荐 上一条 /9 下一条

快速回复 返回顶部 返回列表