原创 【原创】ZigBee学习之39——Home Automation Profile2

2010-2-26 10:17 5672 9 9 分类: MCU/ 嵌入式

TI Home Automation Profile样例<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />


Sample Light Application


此例用开关簇集命令来开关LED4,或者用ZCL写入命令将IdentifyTime属性设为非0值来将设备置于认证模式(如闪烁LED4)。在文档中列出了这个示例程序所支持的属性。如:?  The On/Off cluster attributes:


o  zclSampleLight_OnOff现在在考虑一点,就是这些属性是怎么支持的,这些属性又是怎么确定的,可能在程序中应该会有所体现,等后面分析程序时再看一下。要理解这个首先需要有个概念,就是多个属性组成一个簇集(簇集是属性和命令的集合!),而簇集又是组成应用剖面的必备条件,在规范中对不同的应用剖面其客户端和服务器端必须或可选实现的簇集都有规定,在簇集库规范中对每个簇集中必须或可选实现的属性又有相应的规范。可参考文档:【075366r01ZB_AFG-ZigBee_Cluster_Library_Public_download_version.pdf】和【075367r01ZB_AFG-Home_Automation_Profile_for_Public_Download.pdf】做初步理解。


按键动作:SW2:通过ZDApp_SendEndDeviceBindReq发送绑定。


Sample Switch Application


此示例应用可以作为开关LED4的灯开关来使用,其中LED4所在的设备运行的是Sample Light Application



按键动作:SW1:给灯发送切换消息;SW2:通过ZDApp_SendEndDeviceBindReq发送绑定;SW4:通过ZDApp_AutoFindDestination来进行自动发现。


程序分析:


同样从ZMain.c开始,main()函数依然没有改变,同样是对各种设备初始化,并且初始化内存和系统,然后进入系统循环。在OSAL_SimpleLight.c模块中需要实现任务初始化队列和事件处理函数队列:


void osalInitTasks( void )


{


  uint8 taskID = 0;


 


  tasksEvents = (uint16 *)osal_mem_alloc( sizeof( uint16 ) * tasksCnt);


  osal_memset( tasksEvents, 0, (sizeof( uint16 ) * tasksCnt));


 


  macTaskInit( taskID++ );


  nwk_init( taskID++ );


  Hal_Init( taskID++ );


#if defined( MT_TASK )


  MT_TaskInit( taskID++ );


#endif


  APS_Init( taskID++ );


  ZDApp_Init( taskID++ );


#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )


//如果定义了频率捷变或者PANID冲突,才进行如下函数的初始化,频率捷变和PANID冲突解决是是ZigBee 2007和ZigBee Pro后才引入的,所以这里可以不需要。不管他,反正别预编译掉了。


  ZDNwkMgr_Init( taskID++ );


#endif


  zcl_Init( taskID++ );


//如果要使用ZCL,必须首先进行ZCL初始化【zcl.c】,然后才能进行ZCL应用初始化,ZCL的初始化主要是对一些数据结构赋初值,使其为空的状态,比如属性表,簇ID转换表等。


  zclSampleLight_Init( taskID );


//在这里将注册命令回调表,终端,属性表,节点的简单描述以及为按键注册任务,这个函数一般在zcl_<appname>.c中实现


}


TI ZStack任务系统中另一个重要的元素就是事件处理函数,当特定的任务接收到事件时就会调用相应的事件处理函数,所以还必须初始化一个事件处理函数表:


const pTaskEventHandlerFn tasksArr[] = {


  macEventLoop,


  nwk_event_loop,


  Hal_ProcessEvent,


#if defined( MT_TASK )


  MT_ProcessEvent,


#endif


  APS_event_loop,


  ZDApp_event_loop,


#if defined ( ZIGBEE_FREQ_AGILITY ) || defined ( ZIGBEE_PANID_CONFLICT )


  ZDNwkMgr_event_loop,


#endif


  zcl_event_loop,


//ZCL事件循环


  zclSampleLight_event_loop


//这个是ZCL应用事件处理循环是我们自己针对自己的应用构建的


};


//此函数中事件处理循环功能函数的顺序必须和系统初始化任务列表中的顺序相一致,也就是一个任务初始化函数就对应这个一个事件处理循环


至此OSAL_SimpleLight.c模块就完成了自己的任务:对系统进行初始化设定,下面我们深入到初始化函数和事件处理循环去看一下。在此例中zcl_event_loop仅接收一个事件AF_INCOMING_MSG_CMD,处理也仅仅是找到对应的终端和根据是否要发送应答来发送或者不发送应答。


zcl_samplelight.c】应用初始化函数:


void zclSampleLight_Init( byte task_id )


{


  zclSampleLight_TaskID = task_id;


//分配一个任务ID


  // Set destination address to indirect


  //zclSampleLight_DstAddr.addrMode = (afAddrMode_t)AddrNotPresent;


  //zclSampleLight_DstAddr.endPoint = 0;


  //zclSampleLight_DstAddr.addr.shortAddr = 0;


 


  // This app is part of the Home Automation Profile


//注册HA剖面的简单描述和簇ID转换表


zclHA_Init( &zclSampleLight_SimpleDesc );


zcl_ha.c


void zclHA_Init( SimpleDescriptionFormat_t *simpleDesc )


{


  endPointDesc_t *epDesc;


 


//如果没有簇ID转换表则需要构建一个转换表其中保存着真是簇ID和逻辑簇ID之间的对应


  // Set up the Real Cluster ID to Logical Cluster ID conversion


  if ( !zclHA_RegisteredClusterList )


  {


    zcl_registerClusterConvertTable( ZCL_HA_PROFILE_ID,


       (sizeof( ha_ClusterConvertTable ) / sizeof( zclConvertClusterRec_t )) ,


           (GENERIC zclConvertClusterRec_t *)ha_ClusterConvertTable );


    zclHA_RegisteredClusterList = TRUE;


  }


 


//注册应用的终端描述,需要为终端描述分配内存


  // Register the application's endpoint descriptor


  //  - This memory is allocated and never freed.


  epDesc = osal_mem_alloc( sizeof ( endPointDesc_t ) );


  if ( epDesc )


  {


    // Fill out the endpoint description.


    epDesc->endPoint = simpleDesc->EndPoint;


    epDesc->task_id = &zcl_TaskID;   // all messages get sent to ZCL first


    epDesc->simpleDesc = simpleDesc;


    epDesc->latencyReq = noLatencyReqs;


 


//所有的终端都要注册到af层上。按照这样的话,如果在一个节点上要实现多个终端的话,是不是就需要调用几次zclHA_Init()来注册所有终端呢?还是说可以在函数外面再通过多次调用afRegister()来注册所有的终端?不过可以肯定这里注册的是设备简单描述里面的终端!这个简单描述是网络用来设别设备用的,如果仅要说明节点上的一个终端可以只定义终端描述就可以了!那么这个简单描述到底和终端描述在一个设备上的区别在哪里呢?一个节点设备肯定是需要一个简单描述,而一个终端也必须要有一个终端描述,那如果一个节点设备只有一个终端的话是不是简单描述中的的终端号和终端描述中的终端号要一致的呢?【看一下传递进来的简单描述在文件zcl_samplelight_data.c中:


SimpleDescriptionFormat_t zclSampleLight_SimpleDesc =


{


  SAMPLELIGHT_ENDPOINT,                  //设置在节点号10上,即此宏就是10


  ZCL_HA_PROFILE_ID,                     //此设备上支持的剖面:0x0104为联盟规定的HA剖面ID;


  ZCL_HA_DEVICEID_DIMMABLE_LIGHT,        //可调亮度灯的设备ID0x0101,规范中规定


  SAMPLELIGHT_DEVICE_VERSION,            //应用设备上使用的版本,可自设,此例中设为了0


  SAMPLELIGHT_FLAGS,                     //应用标志,此例中设为了0,此位为简单描述中的保留位


ZCLSAMPLELIGHT_MAX_INCLUSTERS,         //输入簇集数目,此例设为了5,在samplelight应用中使用了5个输入簇集: 


ZCL_HA_CLUSTER_ID_GEN_BASIC,


  ZCL_HA_CLUSTER_ID_GEN_SCENES,


  ZCL_HA_CLUSTER_ID_GEN_GROUPS,


  ZCL_HA_CLUSTER_ID_GEN_ON_OFF,


  ZCL_HA_CLUSTER_ID_GEN_LEVEL_CONTROL


  (cId_t *)zclSampleLight_InClusterList, //输入簇集列表,为一个包含所有输入簇的数组


  ZCLSAMPLELIGHT_MAX_OUTCLUSTERS,        //输出簇集数目


  (cId_t *)zclSampleLight_OutClusterList //输出簇集列表,为一个包含所有输出簇的数组


};以上所有联盟或者是规范中规定的ID都能在文档【075367r01ZB_AFG-Home_Automation_Profile_for_Public_Download.pdf】中找到】


    // Register the endpoint description with the AF


    afRegister( epDesc );


  }


}


//注册命令回调函数(将要注册的命令回调函数和终端关联起来然后插入到链表中),第二个参数是一个一般簇命令函调函数的列表数据结构,我们只需要填充我们要用到的命令就可以了。其结构定义在【zcl_general.h】:


typedef struct


{


  zclGCB_BasicReset_t               pfnBasicReset;                // Basic Cluster Reset command


  zclGCB_Identify_t                 pfnIdentify;                  // Identify command


  zclGCB_IdentifyQueryRsp_t         pfnIdentifyQueryRsp;          // Identify Query Response command


  zclGCB_OnOff_t                    pfnOnOff;                     // On/Off cluster commands


  zclGCB_LevelControlMoveToLevel_t  pfnLevelControlMoveToLevel;   // Level Control Move to Level command


  zclGCB_LevelControlMove_t         pfnLevelControlMove;          // Level Control Move command


  zclGCB_LevelControlStep_t         pfnLevelControlStep;          // Level Control Step command


  zclGCB_LevelControlStop_t         pfnLevelControlStop;          // Level Control Stop command


  zclGCB_GroupRsp_t                 pfnGroupRsp;                  // Group Response commands


  zclGCB_SceneStoreReq_t            pfnSceneStoreReq;             // Scene Store Request command


  zclGCB_SceneRecallReq_t           pfnSceneRecallReq;            // Scene Recall Request command


  zclGCB_SceneRsp_t                 pfnSceneRsp;                  // Scene Response command


  zclGCB_Alarm_t                    pfnAlarm;                     // Alarm (Response) commands


  zclGCB_Location_t                 pfnLocation;                  // RSSI Location command


  zclGCB_LocationRsp_t              pfnLocationRsp;               // RSSI Location Response command


} zclGeneral_AppCallbacks_t;


在此例中主要实现开关的命令回调函数,其命令回调函数表定义在【zcl_samplelight.c】中:


static zclGeneral_AppCallbacks_t zclSampleLight_CmdCallbacks =


{


  zclSampleLight_BasicResetCB,              // Basic Cluster Reset command


  zclSampleLight_IdentifyCB,                // Identify command 


  zclSampleLight_IdentifyQueryRspCB,        // Identify Query Response command


  zclSampleLight_OnOffCB,                   // On/Off cluster command


  NULL,                                     // Level Control Move to Level command


  NULL,                                     // Level Control Move command


  NULL,                                     // Level Control Step command


  NULL,                                     // Group Response commands


  NULL,                                     // Scene Store Request command


  NULL,                                     // Scene Recall Request command


  NULL,                                     // Scene Response command


  NULL,                                     // Alarm (Response) command


  NULL,                                     // RSSI Location commands


  NULL,                                     // RSSI Location Response commands


};可见仅仅填充了想要实现的部分,其他均填充为NULL


  // Register the ZCL General Cluster Library callback functions


  zclGeneral_RegisterCmdCallbacks( SAMPLELIGHT_ENDPOINT, &zclSampleLight_CmdCallbacks );


 


//为终端注册属性,注意属性列表中属性ID必须升序排列,否则发现响应命令将不能正确获得属性信息。


//zclSampleLight_Attrs定义在【zcl_samplelight_data.c】中


  // Register the application's attribute list


  zcl_registerAttrList( SAMPLELIGHT_ENDPOINT, SAMPLELIGHT_MAX_ATTRIBUTES, zclSampleLight_Attrs );


 


//为任务注册按键事件就不用解释了吧,前面好几个地方都有提到过来


  // Register for all key events - This app will handle all key events


  RegisterForKeys( zclSampleLight_TaskID );


 


//注册一个测试终端,此终端ID20,和前面设备简单描述中的终端ID不同了!


  // Register for a test endpoint


  afRegister( &sampleLight_TestEp );


}


下面我们进入zclSampleLight_event_loop看看这个应用的事件处理循环是怎么做的。


uint16 zclSampleLight_event_loop( uint8 task_id, uint16 events )


{


  afIncomingMSGPacket_t *MSGpkt;


 


  if ( events & SYS_EVENT_MSG )


  {


    while ( (MSGpkt = (afIncomingMSGPacket_t *)osal_msg_receive( zclSampleLight_TaskID )) )


    {//得到系统消息,从应用任务中接收消息


      switch ( MSGpkt->hdr.event )


      {


        case KEY_CHANGE:


//处理按键事件,按下按键2时就发送绑定请求


          zclSampleLight_HandleKeys( ((keyChange_t *)MSGpkt)->state, ((keyChange_t *)MSGpkt)->keys );


          break;


 


        default:


          break;


      }


 


      // Release the memory


      osal_msg_deallocate( (uint8 *)MSGpkt );


    }


 


    // return unprocessed events


    return (events ^ SYS_EVENT_MSG);


  }


 


  if ( events & SAMPLELIGHT_IDENTIFY_TIMEOUT_EVT )


  {


    if ( zclSampleLight_IdentifyTime > 0 )


      zclSampleLight_IdentifyTime--;


    zclSampleLight_ProcessIdentifyTimeChange();


 


    return ( events ^ SAMPLELIGHT_IDENTIFY_TIMEOUT_EVT );


  }


 


  // Discard unknown events


  return 0;


}


//现在问题来了,如果对发发送了命令,那么命令是如果传递过来,如何能够产生效果呢?通篇查看【zcl_samplelight.c】发现只有在函数zclSampleLight_OnOffCB()中出现了对LED的操作代码,其行为也符合这个函数的定义:命令回调函数,当接收到一个命令时就会调用此函数,此函数位于命令回调表zclSampleLight_CmdCallbacks中,那么我们只要找到怎么样进入这个命令回调表然后定位到这个回调函数就可以了,前面的分析可得命令回调表是通过zclGeneral_RegisterCmdCallbacks()函数注册给终端的,在这个函数中只存在一个与外部发生联系的变量zclGenCBs,此变量为静态变量,定义在【zcl_general.c】中。再回到OSAL,当收到其他设备发过来的数据时会产生AF_INCOMING_MSG_CMD消息,zcl_event_loop调用zclProcessMessageMSG()来处理消息

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
9
关闭 站长推荐上一条 /3 下一条