引言

本文记录了笔者基于Z-Stack 3.0.1协议栈,通过学习Zigbee通信协议,实现一个简单的智能插座控制过程。通过这个过程,笔者对Zigbee网络的形成、设备间的通信以及低功耗设计有了更深入的理解。

工程代码链接:
链接:https://pan.baidu.com/s/1J58LBN3H_sZg5ZD6UY6t7A?pwd=zwha 
提取码:zwha

这里推荐一个学习zigbee 3.0的网址:

其中笔者也是在这个地方学习的zigbee相关知识

Zigbee 3.0 开发指南

一、前期准备

1.1 下载与安装

首先,从官方渠道下载Z-Stack 3.0.1协议栈,并进行安装。Z-Stack是TI公司提供的Zigbee协议栈,它包含了构建Zigbee网络所需的所有基础组件和示例代码。

1.2 学习基础

在学习实现智能插座之前,笔者已经对Zigbee的基础知识有了一定的了解,包括网络拓扑结构、设备类型(协调器、路由器、终端)以及ZCL(Zigbee Cluster Library)等。特别是ZCL,它是Zigbee设备间通信的标准语言,通过发送和接收ZCL命令,可以实现设备间的控制和数据交换。

二、实现过程

2.1 打开示例工程

在Z-Stack 3.0.1中,提供了多个示例工程,本文选择GenericApp作为起点。这个示例工程展示了如何使用Zigbee协议进行基本的设备通信。

2.2 初始化与网络形成

协调器是Zigbee网络的中心节点,负责创建和管理网络。在zclGenericApp_Init函数中,通过宏定义ZDO_COORDINATOR来区分协调器和其他设备(路由器或终端)。协调器的初始化过程包括注册ZDO消息、启动网络形成过程以及允许设备加入网络。

1.在APP组  ->  zcl_genericapp.c  ->   void zclGenericApp_Init( byte task_id )的末尾添加

#ifdef ZDO_COORDINATOR

  ZDO_RegisterForZDOMsg ( zclGenericApp_TaskID, Device_annce );
  
  bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_FORMATION |
                          BDB_COMMISSIONING_MODE_FINDING_BINDING );

  NLME_PermitJoiningRequest(255);
#else
  bdb_StartCommissioning( BDB_COMMISSIONING_MODE_NWK_STEERING |
                          BDB_COMMISSIONING_MODE_FINDING_BINDING );
  
  zclGenericapp_DeviceAnnce();
#endif

2.3 网络加入状态处理

在路由器或终端中,需要处理网络加入的状态。如果加入成功,则加快LED闪烁频率;如果加入失败,则减慢LED闪烁频率,并尝试重新加入网络。

需要定义,初始化500ms:

static uint16 zclGenericapp_ONOFF_TEST_PERIOD = 500;

在这里初始化了一个任务:在void zclGenericApp_Init( byte task_id )

osal_start_timerEx( zclGenericApp_TaskID, GENERICAPP_EVT_1, zclGenericapp_ONOFF_TEST_PERIOD );

uint16 zclGenericApp_event_loop( uint8 task_id, uint16 events )

  if ( events & GENERICAPP_EVT_1 )
  {
    // toggle LED 2 state, start another timer for 500ms
    HalLedSet ( HAL_LED_2, HAL_LED_MODE_TOGGLE );
    osal_start_timerEx( zclGenericApp_TaskID, GENERICAPP_EVT_1, zclGenericapp_ONOFF_TEST_PERIOD );
    
    return ( events ^ GENERICAPP_EVT_1 );
  }

2.4 设备发现与通信

2.4.1 设备广播

路由器或终端在加入网络后,会通过ZDP_DeviceAnnce函数广播自己的设备信息,包括网络地址、物理地址等。协调器接收到这些信息后,会保存设备的网络地址,以便后续通信。

    static void zclGenericapp_DeviceAnnce( void )
    {    
        ZDP_DeviceAnnce(
            NLME_GetShortAddr(),//获取本设备的网络地址(短地址)
            NLME_GetExtAddr(),//获取本设备的物理地址(通常就是MAC地址)
            ZDO_Config_Node_Descriptor.CapabilityFlags,//暂不展开简介,可忽略
            0//暂不展开讲解,可忽略
         );    
    }
2.4.2 协调器处理设备广播

协调器在接收到设备广播后,会自动调用zclGenericapp_processZDOMgs函数处理这些信息,并保存设备的网络地址。

这里的zclGenericapp_processZDOMgs函数需要在zclGenericApp_event_loop函数中调用:

    #ifdef ZDO_COORDINATOR
       case ZDO_CB_MSG:
          zclGenericapp_processZDOMgs( (zdoIncomingMsg_t *)MSGpkt );
          break; 
    #endif 
    static void zclGenericapp_processZDOMgs(zdoIncomingMsg_t *pMsg)
    {
      switch ( pMsg->clusterID )
      {
        case Device_annce:
        {
          zclGenericapp_OnOffTestAddr = pMsg->srcAddr.addr.shortAddr;
          zclGenericapp_ONOFF_TEST_PERIOD = 200;
        }
            break;

        default:
            break;
      }
    }
2.4.3 绑定命令回调

路由器或终端在初始化时,会绑定一系列ZCL命令的回调函数。当接收到相应的命令时,会调用对应的回调函数进行处理。例如,接收到开关命令时,会调用zclGenericapp_OnOffCB函数控制LED的开关。

在zclGenericApp_Init函数初始化:

#ifdef ZDO_COORDINATOR
  NULL,
#else
  zclGenericapp_OnOffCB,    // On/Off cluster commands
#endif

这里需要绑定函数,接收到开关命令时,会调用zclGenericapp_OnOffCB函数控制插座的开关。

 static void zclGenericapp_OnOffCB( uint8 cmd )
    {
        if(cmd == COMMAND_ON) // 命令为ON时  
        {               
            HalLedSet(HAL_LED_1, HAL_LED_MODE_ON);  // 开启所有LED  
        }    
        else if(cmd == COMMAND_OFF) // 命令为OFF时  
        {    
           HalLedSet(HAL_LED_1, HAL_LED_MODE_OFF);  // 关闭所有LED  
        }    
    }

2.5 发送与控制

协调器通过zclGenericapp_OnOffTest函数发送开关命令给路由器或终端。这个函数会根据当前状态发送开或关的命令,并切换状态。

在这里是通过按键来出发这个函数的

 static void zclGenericapp_OnOffTest(void)
    {
        afAddrType_t destAddr;
        static uint8 txID = 0;
        static bool  on   = true;
        
        destAddr.endPoint = GENERICAPP_ENDPOINT;
        destAddr.addrMode = Addr16Bit;
        destAddr.addr.shortAddr = zclGenericapp_OnOffTestAddr;
        
        if(on)
        {
          zclGeneral_SendOnOff_CmdOn(GENERICAPP_ENDPOINT, &destAddr, TRUE, txID++);
        }
        else
        {
          zclGeneral_SendOnOff_CmdOff(GENERICAPP_ENDPOINT, &destAddr, TRUE, txID++);
        }
        
        on = !on;
    }

2.6 按键控制

在协调器的代码中,通过按键来控制发送开关命令。当按下指定按键时,会调用zclGenericapp_OnOffTest函数发送命令。

static void zclGenericApp_HandleKeys( byte shift, byte keys )
{

  if ( keys & HAL_KEY_SW_6 )
  {
    #ifdef ZDO_COORDINATOR
        zclGenericapp_OnOffTest();
    #else
    #endif
        
    HalLedSet(HAL_LED_1, HAL_LED_MODE_TOGGLE);
  }
}

2.7 开启宏设置

在路由器或者终端设备编写代码时,为了确保代码正确运行,使用“开关命令收发”时需要开启对应的宏开关

ZCL_ON_OFF

三、问题与思考

3.1 网络重连问题

目前,如果协调器与路由器或终端断开连接,需要复位设备才能重新连接。这在实际应用中是不方便的。未来可以考虑实现更健壮的网络重连机制,如定时尝试重连、保存网络配置等。

3.2 命令确认与反馈

目前,协调器发送命令后,没有收到路由器或终端的确认反馈。这可能导致命令丢失或执行失败而不知情。未来可以实现命令确认机制,确保命令的可靠传输和执行。

3.3 低功耗设计

Zigbee设备的一个重要特点是低功耗。目前,本实现还没有进行低功耗设计。未来可以通过优化代码、使用低功耗硬件、设置休眠模式等方式来降低设备功耗。

3.4 工程裁剪与优化

Z-Stack 3.0.1协议栈包含了大量的功能和示例代码,对于特定的应用来说,可能有很多不需要的部分。未来可以根据实际需求对工程进行裁剪和优化,去除不需要的功能和代码,减少资源占用和功耗。

四、总结与展望

通过本次学习实践,笔者成功实现了基于Z-Stack 3.0.1的Zigbee智能插座控制。这个过程不仅加深了对Zigbee协议的理解,还提高了编程实践和问题解决的能力。未来,笔者将继续深入探索Zigbee技术的更多应用场景和优化方法,为物联网技术的发展贡献自己的力量。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部