This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

ZCL messages between local endpoints

Other Parts Discussed in Thread: Z-STACK

Hi,

My question is rather nonstandard one.

What I'm trying to achieve is to send messages using zcl_SendCommand() between two endpoints on the same node.

I binded two endpoints within the same profile (HA) that are registered on the same node (I want no OTA communication in this situation). I used BindingTable and bindAddEntry() to bind two local endpoints. 

Then I tried to call zcl_SendCommand() or to be more specific using macro zclGeneral_SendOnOff_CmdOff().

Unfortunately I only receive confirmation message with 0xC2 (ZNwkInvalidRequest) status on source endpoint but cannot get anything on the destination endpoint.

Is there any trick that allows me to communicate two endpoints located on the same node with zcl_SendCommand() ? Is it even possible? If so then where should I start digging in to get this type of nonstandard communication working?

Thank you in advance for any help!

Best regards,

Lukasz Spas

  • If the two end point is under the same node, why do you need to use zcl_SendCommand? zcl_SendCommand is for you to send command from one node to another.

  • Hi,

    i think if you really want to send data between endpoints, you should not use stack API (which as Yikai said are for sending data between remote nodes) but rather use OSAL APIs.

    Endpoints on a node are implemented as OSAL tasks. So you can use OSAL services to have the two endpoints communicate between each other, without the need for the endpoint to bind nor exchange ZCL message, using inter-task messaging.

    The following OSAL API can be useful for you:

    osal_msg_receive()

    osal_msg_send()

    osal_msg_allocate()

    osal_msg_deallocate()

    Please also remember to register the processing event handler function in taskArr[] function pointer and create the corresponding endpoint task init function within osalInitTasks API.

    For a full API reference, please consult Documents\API\OSAL API.pdf of your Z-stack install

    I also suggest you check the OSAL_xxxxx.c module, which can give you a reference guidance of how an endpoint is implemented as an OSAL task.

    Thanks,

    TheDarkSide 

     

  • The reason why I want to use ZCL APIs is that I thought it would be kindda obvious to have one way of communication (same functions) for both remote and local endpoints and that ZStack would figure out how to handle my requests. It feels more natural that if you have two ZCL endpoints that have similar representation (address, endpoint ID, bindings, etc.) then you should be able to communicate with them in similar way using same functions/APIs. 

    I also wanted ZStack to handle frames building/parsing and looking through binding tables for me. But while it doesn't  seem feasible to use the stack for local endpoints communication then basically I need to implement some of the ZCL methods just for local communication or extend existing ones by modifying ZStack source code. Seems a bit too complicated as for such simple thing.

  • I modified the source code of AF_DataRequest() method so now it is possible to communicate two local endpoints that are registered on the same node. Lines 23-27 & 185-254 are the ones I added (wanted to have as few changes as possible so it is easy to update the code when necessary. Hope somebody will find this useful if anyone will need similar functionality in the future.

    In this implementation calling zcl_SendCommand (or any other macro that wraps this method) will result in:
    - building a proper frame
    - forwarding it to ZCL task (zcl.c/.h) to process the frame
    - ZCL task will call registered cluster callback that will process this message.

    Could anyone from TI validate if this approach is correct? Any drawbacks or stuff that is missing in this mod?

    Thanks in advance!

    / Lukasz


    afStatus_t AF_DataRequest( afAddrType_t *dstAddr, endPointDesc_t *srcEP,
                               uint16 cID, uint16 len, uint8 *buf, uint8 *transID,
                               uint8 options, uint8 radius )
    {
      pDescCB pfnDescCB;
      ZStatus_t stat;
      APSDE_DataReq_t req;
      afDataReqMTU_t mtu;
    
      // Verify source end point
      if ( srcEP == NULL )
      {
        return afStatus_INVALID_PARAMETER;
      }
    
    #if !defined( REFLECTOR )
      if ( dstAddr->addrMode == afAddrNotPresent )
      {
        return afStatus_INVALID_PARAMETER;
      }
    #endif
    
      if ((dstAddr->addrMode == afAddr16Bit) &&
          (dstAddr->addr.shortAddr == NLME_GetShortAddr()))
      {
        goto local_endpoint;
      }
    
      // Check if route is available before sending data
      if ( options & AF_LIMIT_CONCENTRATOR  )
      {
        if ( dstAddr->addrMode != afAddr16Bit )
        {
          return ( afStatus_INVALID_PARAMETER );
        }
    
        // First, make sure the destination is not its self, then check for an existing route.
        if ( (dstAddr->addr.shortAddr != NLME_GetShortAddr())
            && (RTG_CheckRtStatus( dstAddr->addr.shortAddr, RT_ACTIVE, (MTO_ROUTE | NO_ROUTE_CACHE) ) != RTG_SUCCESS) )
        {
          // A valid route to a concentrator wasn't found
          return ( afStatus_NO_ROUTE );
        }
      }
    
      // Validate broadcasting
      if ( ( dstAddr->addrMode == afAddr16Bit     ) ||
           ( dstAddr->addrMode == afAddrBroadcast )    )
      {
        // Check for valid broadcast values
        if( ADDR_NOT_BCAST != NLME_IsAddressBroadcast( dstAddr->addr.shortAddr )  )
        {
          // Force mode to broadcast
          dstAddr->addrMode = afAddrBroadcast;
        }
        else
        {
          // Address is not a valid broadcast type
          if ( dstAddr->addrMode == afAddrBroadcast )
          {
            return afStatus_INVALID_PARAMETER;
          }
        }
      }
      else if ( dstAddr->addrMode != afAddr64Bit &&
                dstAddr->addrMode != afAddrGroup &&
                dstAddr->addrMode != afAddrNotPresent )
      {
        return afStatus_INVALID_PARAMETER;
      }
    
      // Set destination address
      req.dstAddr.addrMode = dstAddr->addrMode;
      if ( dstAddr->addrMode == afAddr64Bit )
        osal_cpyExtAddr( req.dstAddr.addr.extAddr, dstAddr->addr.extAddr );
      else
        req.dstAddr.addr.shortAddr = dstAddr->addr.shortAddr;
    
      // This option is to use Wildcard ProfileID in outgoing packets
      if ( options & AF_WILDCARD_PROFILEID )
      {
        req.profileID = ZDO_WILDCARD_PROFILE_ID;
      }
      else
      {
        req.profileID = ZDO_PROFILE_ID;
    
        if ( (pfnDescCB = afGetDescCB( srcEP )) )
        {
          uint16 *pID = (uint16 *)(pfnDescCB(
                                       AF_DESCRIPTOR_PROFILE_ID, srcEP->endPoint ));
          if ( pID )
          {
            req.profileID = *pID;
            osal_mem_free( pID );
          }
        }
        else if ( srcEP->simpleDesc )
        {
          req.profileID = srcEP->simpleDesc->AppProfId;
        }
      }
    
      req.txOptions = 0;
    
      if ( ( options & AF_ACK_REQUEST              ) &&
           ( req.dstAddr.addrMode != AddrBroadcast ) &&
           ( req.dstAddr.addrMode != AddrGroup     )    )
      {
        req.txOptions |=  APS_TX_OPTIONS_ACK;
      }
    
      if ( options & AF_SKIP_ROUTING )
      {
        req.txOptions |=  APS_TX_OPTIONS_SKIP_ROUTING;
      }
    
      if ( options & AF_EN_SECURITY )
      {
        req.txOptions |= APS_TX_OPTIONS_SECURITY_ENABLE;
        mtu.aps.secure = TRUE;
      }
      else
      {
        mtu.aps.secure = FALSE;
      }
    
      if ( options & AF_PREPROCESS )
      {
        req.txOptions |=  APS_TX_OPTIONS_PREPROCESS;
      }
    
      mtu.kvp = FALSE;
    
      if ( options & AF_SUPRESS_ROUTE_DISC_NETWORK )
      {
        req.discoverRoute = DISC_ROUTE_INITIATE;
      }
      else
      {
        req.discoverRoute = AF_DataRequestDiscoverRoute;
      }
    
      req.transID       = *transID;
      req.srcEP         = srcEP->endPoint;
      req.dstEP         = dstAddr->endPoint;
      req.clusterID     = cID;
      req.asduLen       = len;
      req.asdu          = buf;
      req.radiusCounter = radius;
    #if defined ( INTER_PAN )
      req.dstPanId      = dstAddr->panId;
    
      if ( StubAPS_InterPan( dstAddr->panId, dstAddr->endPoint ) )
      {
        if ( len > INTERP_DataReqMTU() )
        {
          stat = afStatus_INVALID_PARAMETER;
        }
        else
        {
          stat = INTERP_DataReq( &req );
        }
      }
      else
    #endif // INTER_PAN
      {
        if (len > afDataReqMTU( &mtu ) )
        {
          if (apsfSendFragmented)
          {
            stat = (*apsfSendFragmented)( &req );
          }
          else
          {
            stat = afStatus_INVALID_PARAMETER;
          }
        }
        else
        {
          stat = APSDE_DataReq( &req );
        }
      }
    
      goto finish_remote;
    
    local_endpoint:
    
      // Source address not present as it is an internal message
      zAddrType_t SrcAddress;
      osal_memset(&SrcAddress, 0, sizeof(zAddrType_t));
      SrcAddress.addrMode = Addr16Bit;
      SrcAddress.addr.shortAddr = NLME_GetShortAddr();
    
      // Undefined signal info - no RF has been used for this message
      NLDE_Signal_t sig;
      osal_memset(&sig, 0, sizeof(NLDE_Signal_t));
    
      // Define this data in the UART message header
      uint8 nwkSeqNum = 0;        // Currently not supported
      uint8 SecurityUse = FALSE;  // Currently not supported
    
      // APS frame
      aps_FrameFormat_t aff;
      osal_memset(&aff, 0, sizeof(aps_FrameFormat_t));
      aff.FrmCtrl = 0x00;
      aff.XtndFrmCtrl = 0x00; // Currently not supported
      aff.DstEndPoint = dstAddr->endPoint;
      aff.SrcEndPoint = srcEP->endPoint;
      aff.ClusterID = cID;
    
      if (options & AF_WILDCARD_PROFILEID)
      {
          aff.ProfileID = ZDO_WILDCARD_PROFILE_ID;
      }
      else
      {
          aff.ProfileID = ZDO_PROFILE_ID;
    
          if ((pfnDescCB = afGetDescCB(srcEP)))
          {
              uint16 *pID = (uint16 *)(pfnDescCB(
                  AF_DESCRIPTOR_PROFILE_ID, srcEP->endPoint));
              if (pID)
              {
                  aff.ProfileID = *pID;
                  osal_mem_free(pID);
              }
          }
          else if (srcEP->simpleDesc)
          {
              aff.ProfileID = srcEP->simpleDesc->AppProfId;
          }
      }
    
      aff.macDestAddr = NLME_GetShortAddr();
      aff.wasBroadcast = false;
      aff.asdu = buf;
      aff.asduLength = len;
      aff.ApsCounter = 0;
    
      // Inject data into the application framework
      afIncomingData(
          &aff,
          &SrcAddress,
          zgConfigPANID, /* Use global PAN ID to simulate messages within the current network */
          &sig,
          nwkSeqNum,
          SecurityUse,
          osal_GetSystemClock());
    
      stat = afStatus_SUCCESS;
    
    finish_remote:
    
      /*
       * If this is an EndPoint-to-EndPoint message on the same device, it will not
       * get added to the NWK databufs. So it will not go OTA and it will not get
       * a MACCB_DATA_CONFIRM_CMD callback. Thus it is necessary to generate the
       * AF_DATA_CONFIRM_CMD here. Note that APSDE_DataConfirm() only generates one
       * message with the first in line TransSeqNumber, even on a multi message.
       * Also note that a reflected msg will not have its confirmation generated
       * here.
       */
      if ( (req.dstAddr.addrMode == Addr16Bit) &&
           (req.dstAddr.addr.shortAddr == NLME_GetShortAddr()) )
      {
        afDataConfirm( srcEP->endPoint, *transID, stat );
      }
    
      if ( stat == afStatus_SUCCESS )
      {
        (*transID)++;
      }
    
      return (afStatus_t)stat;
    }
    

  • Lukasz,

    In your code, you are simulating incoming packet  which i believe is  not needed since we already have OSAL tasks implemented,  using which end points on same node can communicate without any issues.  Hacking the state machine might be an overkill if the incentive is just to have common send fn.

    Saurabh

  • Hi Saurabh,

    Probably you are right in some extent. But in our application taking care of calling the right callback on our own, while having that kind of logic already implemented in ZCL OSAL task, would be rather waste of time.

    I don't get it, why ZStack doesn't support communication between ZCL endpoints via common ZCL APIs... C'mon... this is the same stack level so why should I call OSAL on my own and reimplement common parts of logic to forward my frames via OSAL just because I want to do it locally.

    Imagine simple scenario where you want to use common ZigBee stack to communicate via different medium. I can imagine for instance a situation where two nodes are tide up together via SPI or serial and try to forward frames. One node works in ZigBee and another one in some other wireless network stack (BT for instance). If you can convert data received from different medium it could be rather straightforward to simply forward these data to ZCL directly using ZCL APIs when you want to trigger some actions via endpoint callback. Otherwise you have to hack (like I did) or write a lot of unnecessary code that basically repeats AF logic locally.

    Maybe it's that ZStack hasn't been made for such nonstandard scenarios but on the other hand sometimes developer needs more flexibility that could save some unnecessary effort - that's basically purpose of having APIs and framework, isn't it?

    / Lukasz

  • Hi Lukasz,

    I think the implementatin of ZStack is based on Zigbee spec and there is no consideration for communication between two end points which run on the same device. So, that is why TheDarkSide suggested you using OSAL API to do it.