komelgman
komelgman

Reputation: 6999

Non standard ZCL package support for CC253x firmware

My problem is:

Livolo switches have their own Zigbee gate. I want to connect them from zigbee2mqtt with CC2531 USB dongle. In general it works, but when I turn on/off switch button (on physical device), the switch sends an incorrect ZCL package.

I'm an absolutely newbie in microcontrollers programming and in Zigbee architecture. So I hope someone can help me and answer these questions:

I use Z-STACK-HOME 1.2.2a firmware and compile it as described there: https://github.com/Koenkk/Z-Stack-firmware/blob/master/coordinator/Z-Stack_Home_1.2/COMPILE.md

// Malformed ZCL package
// header    
0x7c, // [0111 1100]
      // 01 - frame type = "Command is specific or local to a cluster"
      // 1 - manufacturer spec = manufacturer code present
      // 1 - direction = "from server to client"
      // 1 - disable default response 
      // 100 - reserved

0xd2, 0x15, // manufacturer  
0xd8, // sequence  
0x00, // read attrs command

// endpoint adress 
0x12, 0x0f, 0x05, 0x18, 0x00, 0x4b, 0x12, 0x00, 

0x22, 0x00, // ????? need more data from another switches

0x03 // 0x00|0x01|0x02|0x03 - switch state

upd: I think, what I can intercept message in AF.c file in afIncomingData function and fix in afBuildMSGIncoming.

So now I hope, someone can help me with the right message format. Which can be processed standard ZCL parcer.

void afIncomingData( aps_FrameFormat_t *aff, zAddrType_t *SrcAddress, uint16 SrcPanId,
                     NLDE_Signal_t *sig, uint8 nwkSeqNum, uint8 SecurityUse,
                     uint32 timestamp, uint8 radius )
{
  endPointDesc_t *epDesc = NULL;
  epList_t *pList = epList;
#if !defined ( APS_NO_GROUPS )
  uint8 grpEp = APS_GROUPS_EP_NOT_FOUND;
#endif

  if ( ((aff->FrmCtrl & APS_DELIVERYMODE_MASK) == APS_FC_DM_GROUP) )
  {
#if !defined ( APS_NO_GROUPS )
    // Find the first endpoint for this group
    grpEp = aps_FindGroupForEndpoint( aff->GroupID, APS_GROUPS_FIND_FIRST );
    if ( grpEp == APS_GROUPS_EP_NOT_FOUND ) {
      // No endpoint found, default to endpoint 1.
      // In the original source code there is a return here. 
      // This prevent the messags from being forwarded.
      // For our use-case we want to capture all messages. 
      // Even if the coordinator is not in the group.
      epDesc = afFindEndPointDesc( 1 );
    }
    else {
      epDesc = afFindEndPointDesc( grpEp );
    }

    if ( epDesc == NULL )
      return;   // Endpoint descriptor not found

    pList = afFindEndPointDescList( epDesc->endPoint );
#else
    return; // Not supported
#endif
  }
  else if ( aff->DstEndPoint == AF_BROADCAST_ENDPOINT )
  {
    // Set the list
    if ( pList != NULL )
    {
      epDesc = pList->epDesc;
    }
  }
  else if ( aff->DstEndPoint == 10 || aff->DstEndPoint == 11 ) {
    if ( (epDesc = afFindEndPointDesc( 1 )) )
    {
      pList = afFindEndPointDescList( epDesc->endPoint );
    }
  }
  else if ( (epDesc = afFindEndPointDesc( aff->DstEndPoint )) )
  {
    pList = afFindEndPointDescList( epDesc->endPoint );
  }

  while ( epDesc )
  {
    uint16 epProfileID = 0xFFFE;  // Invalid Profile ID

    if ( pList->pfnDescCB )
    {
      uint16 *pID = (uint16 *)(pList->pfnDescCB(
                                 AF_DESCRIPTOR_PROFILE_ID, epDesc->endPoint ));
      if ( pID )
      {
        epProfileID = *pID;
        osal_mem_free( pID );
      }
    }
    else if ( epDesc->simpleDesc )
    {
      epProfileID = epDesc->simpleDesc->AppProfId;
    }

    // First part of verification is to make sure that:
    // the local Endpoint ProfileID matches the received ProfileID OR
    // the message is specifically send to ZDO (this excludes the broadcast endpoint) OR
    // if the Wildcard ProfileID is received the message should not be sent to ZDO endpoint
    if ( (aff->ProfileID == epProfileID) ||
         ((epDesc->endPoint == ZDO_EP) && (aff->ProfileID == ZDO_PROFILE_ID)) ||
         ((epDesc->endPoint != ZDO_EP) && ( aff->ProfileID == ZDO_WILDCARD_PROFILE_ID )) )
    {
      // Save original endpoint
      uint8 endpoint = aff->DstEndPoint;

      // overwrite with descriptor's endpoint
      aff->DstEndPoint = epDesc->endPoint;

      afBuildMSGIncoming( aff, epDesc, SrcAddress, SrcPanId, sig,
                         nwkSeqNum, SecurityUse, timestamp, radius );

      // Restore with original endpoint
      aff->DstEndPoint = endpoint;
    }

    if ( ((aff->FrmCtrl & APS_DELIVERYMODE_MASK) == APS_FC_DM_GROUP) )
    {
#if !defined ( APS_NO_GROUPS )
      // Find the next endpoint for this group
      grpEp = aps_FindGroupForEndpoint( aff->GroupID, grpEp );
      if ( grpEp == APS_GROUPS_EP_NOT_FOUND )
        return;   // No endpoint found

      epDesc = afFindEndPointDesc( grpEp );
      if ( epDesc == NULL )
        return;   // Endpoint descriptor not found

      pList = afFindEndPointDescList( epDesc->endPoint );
#else
      return;
#endif
    }
    else if ( aff->DstEndPoint == AF_BROADCAST_ENDPOINT )
    {
      pList = pList->nextDesc;
      if ( pList )
        epDesc = pList->epDesc;
      else
        epDesc = NULL;
    }
    else
      epDesc = NULL;
  }
}

static void afBuildMSGIncoming( aps_FrameFormat_t *aff, endPointDesc_t *epDesc,
                 zAddrType_t *SrcAddress, uint16 SrcPanId, NLDE_Signal_t *sig,
                 uint8 nwkSeqNum, uint8 SecurityUse, uint32 timestamp, uint8 radius )
{
  afIncomingMSGPacket_t *MSGpkt;
  const uint8 len = sizeof( afIncomingMSGPacket_t ) + aff->asduLength;
  uint8 *asdu = aff->asdu;
  MSGpkt = (afIncomingMSGPacket_t *)osal_msg_allocate( len );

  if ( MSGpkt == NULL )
  {
    return;
  }

  MSGpkt->hdr.event = AF_INCOMING_MSG_CMD;
  MSGpkt->groupId = aff->GroupID;
  MSGpkt->clusterId = aff->ClusterID;
  afCopyAddress( &MSGpkt->srcAddr, SrcAddress );
  MSGpkt->srcAddr.endPoint = aff->SrcEndPoint;
  MSGpkt->endPoint = epDesc->endPoint;
  MSGpkt->wasBroadcast = aff->wasBroadcast;
  MSGpkt->LinkQuality = sig->LinkQuality;
  MSGpkt->correlation = sig->correlation;
  MSGpkt->rssi = sig->rssi;
  MSGpkt->SecurityUse = SecurityUse;
  MSGpkt->timestamp = timestamp;
  MSGpkt->nwkSeqNum = nwkSeqNum;
  MSGpkt->macSrcAddr = aff->macSrcAddr;
  MSGpkt->macDestAddr = aff->macDestAddr;
  MSGpkt->srcAddr.panId = SrcPanId;
  MSGpkt->cmd.TransSeqNumber = 0;
  MSGpkt->cmd.DataLength = aff->asduLength;
  MSGpkt->radius = radius;

  if ( MSGpkt->cmd.DataLength )
  {
    MSGpkt->cmd.Data = (uint8 *)(MSGpkt + 1);
    osal_memcpy( MSGpkt->cmd.Data, asdu, MSGpkt->cmd.DataLength );
  }
  else
  {
    MSGpkt->cmd.Data = NULL;
  }

#if defined ( MT_AF_CB_FUNC )
  // If ZDO or SAPI have registered for this endpoint, dont intercept it here
  if (AFCB_CHECK(CB_ID_AF_DATA_IND, *(epDesc->task_id)))
  {
    MT_AfIncomingMsg( (void *)MSGpkt );
    // Release the memory.
    osal_msg_deallocate( (void *)MSGpkt );
  }
  else
#endif
  {
    // Send message through task message.
    osal_msg_send( *(epDesc->task_id), (uint8 *)MSGpkt );
  }
}

Upvotes: 3

Views: 390

Answers (2)

komelgman
komelgman

Reputation: 6999

In AF.c file in functions afIncomingData and afBuildMSGIncoming added bloks marked by

#if defined ( LIVOLO_SUPPORT ) 
#endif

In afIncomingData I add:

#if defined ( LIVOLO_SUPPORT )  
  else if ( aff->DstEndPoint == 0x08 ) 
  {
    if ( (epDesc = afFindEndPointDesc( 1 )) )
    {
      pList = afFindEndPointDescList( epDesc->endPoint );
    }
  }  
#endif  

It's prewent filtering message sended to unknown destEndpoint

And in afBuildMSGIncoming function:

#if defined ( LIVOLO_SUPPORT )
  uint8 fixedPackage[] = { 
        0x18, 0xd8, 0x01, // header
        0x00, 0x00, // attrId
        0x00, // success
        0x10, // boolean
        0x00
      };

  if (aff->SrcEndPoint == 0x06 && aff->DstEndPoint == 0x01 
      && aff->ClusterID == 0x0001 && aff->ProfileID == 0x0104) {

    const uint8 mlfrmdHdr[] = { 0x7c, 0xd2, 0x15, 0xd8, 0x00 }; 
    if (osal_memcmp(asdu, mlfrmdHdr, 5) == TRUE) {      
      fixedPackage[7] = asdu[aff->asduLength - 1];
      MSGpkt->cmd.DataLength = 8; // sizeof(fixedPackage)
      MSGpkt->clusterId = 0x06; // genOnOff
      asdu = fixedPackage;
    }
  }
#endif

It change unsupported package to readAttrResp package.

Upvotes: 3

tomlogic
tomlogic

Reputation: 11694

I don't think you're decoding the frame control byte correctly. Looking at some code I've written, I interpret it as follows:

0x7c, // [0111 1100]
      // 011 - reserved
      // 1 - disable default response 
      // 1 - direction = "from server to client"
      // 1 - manufacturer spec = manufacturer code present
      // 00 - frame type = "Command acts across entire profile"

This is based on an old ZCL spec (around 2008?) and perhaps the reserved bits have taken on some meaning in a later version of the spec.

I believe the manufacturer specific bit indicates that this is not a standard Zigbee command (Read Attributes). If it was Read Attributes, I think it should have an even number of bytes following it (16-bit attribute IDs).

What were the source and destination endpoints, profile ID and cluster ID for that frame?

Update: It looks like you could modify afIncomingData() to look at the fields of aff to identify this exact message type (frame control, endpoints, cluster, profile), and then hand it off to your own function for processing (and responding if necessary).

But I'd also take a look at documentation for MT_AF_CB_FUNC and MT_AfIncomingMsg() to see if that's the "official" way to identify frames that you want to handle in your own code.

Upvotes: 3

Related Questions