CC2652P: 无法接收AF数据帧长超过82字节的端口广播。

Part Number: CC2652P
Other Parts Discussed in Thread: Z-STACK

AF_DataRequest发送超过82字节的数据包,目标端口0xFF,可以发送给Silicon Labs的zigbee芯片,但是无法发给自己同类型芯片,SDK 8.30。

  • 感谢您对TI产品的关注。
    我们正在核实您的问题,请等待我们的答复。

  • 不是这个问题,我使用AF_DataRequest发送一包数据到协调器,dstAddr->shortAddr = 0x0000, dstAddr->endPoint = 0xFF, len = 100. 协调器就不能接收该包数据,但是把len改成80或者把dstAddr->endPoint的值改成0x01,协调器就能接收这包数据。

  • 有可能是TI Z-Stack对广播端点的分片限制,您把dstAddr->endPoint改为特定的端点,而不是广播端点

  •  如果是TI Z-Stack对广播端点的分片限制,那这样就不合理。如果分片传输只是禁止短地址广播是合理的,但是如果是端点广播,目标地址是单播,不应该限制这种使用方式。我在Z-stack 3.0.2的aps_frag.c中发现,函数APSF_AllocRxObj只筛选aff->DstEndPoint为本机注册端点,未考虑aff->DstEndPoint等于0xFF的情况。我想提供一种思路,aff->DstEndPoint为0xFF时,把pRx->taskID赋值为APSF_taskID,分片接收完毕后把AF_INCOMING_MSG_CMD消息先发给APSF_ProcessEvent任务,在APSF_ProcessEvent任务中再增加一次处理,把AF_INCOMING_MSG_CMD按照注册端点数量进行复制,再发给各个端点对应的Task。

    所以你们能否提供一下SDK 8.30的aps_frag.c文件,就像之前对nl_mede_cb.c中对APS Retry缓存溢出不能触发DATA CONFIRM的修复方式那样。

  • 我们没有这个文件。

  • 您可以到英文论坛问一下,他们或许有更多资源。

  • 以下只是参考意见

    /**************************************************************************************************
      Filename:       aps_frag.c
      Revised:        $Date: 2013-09-27 11:56:40 -0700 (Fri, 27 Sep 2013) $
      Revision:       $Revision: 35472 $
    
      Description:    Implements APS Application Data Unit Fragmentation
    
    
      Copyright 2006-2013 Texas Instruments Incorporated. All rights reserved.
    
      IMPORTANT: Your use of this Software is limited to those specific rights
      granted under the terms of a software license agreement between the user
      who downloaded the software, his/her employer (which must be your employer)
      and Texas Instruments Incorporated (the "License").  You may not use this
      Software unless you agree to abide by the terms of the License. The License
      limits your use, and you acknowledge, that the Software may not be modified,
      copied or distributed unless embedded on a Texas Instruments microcontroller
      or used solely and exclusively in conjunction with a Texas Instruments radio
      frequency transceiver, which is integrated into your product.  Other than for
      the foregoing purpose, you may not use, reproduce, copy, prepare derivative
      works of, modify, distribute, perform, display or sell this Software and/or
      its documentation for any purpose.
    
      YOU FURTHER ACKNOWLEDGE AND AGREE THAT THE SOFTWARE AND DOCUMENTATION ARE
      PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED,
      INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY, TITLE,
      NON-INFRINGEMENT AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL
      TEXAS INSTRUMENTS OR ITS LICENSORS BE LIABLE OR OBLIGATED UNDER CONTRACT,
      NEGLIGENCE, STRICT LIABILITY, CONTRIBUTION, BREACH OF WARRANTY, OR OTHER
      LEGAL EQUITABLE THEORY ANY DIRECT OR INDIRECT DAMAGES OR EXPENSES
      INCLUDING BUT NOT LIMITED TO ANY INCIDENTAL, SPECIAL, INDIRECT, PUNITIVE
      OR CONSEQUENTIAL DAMAGES, LOST PROFITS OR LOST DATA, COST OF PROCUREMENT
      OF SUBSTITUTE GOODS, TECHNOLOGY, SERVICES, OR ANY CLAIMS BY THIRD PARTIES
      (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF), OR OTHER SIMILAR COSTS.
    
      Should you have any questions regarding your right to use this Software,
      contact Texas Instruments Incorporated at www.TI.com.
    **************************************************************************************************/
    
    /*********************************************************************
     * INCLUDES
     */
    #include "OSAL.h"
    #include "AF.h"
    #include "ZDApp.h"
    #include "aps_frag.h"
    #include "aps_util.h"
    #include "hal_assert.h"
    #include "nwk_util.h"
    #include "ZGlobals.h"
    
    /*********************************************************************
     * MACROS
     */
    
    // Time to wait for an ackknowledgement
    #define APSF_ACK_WAIT           (100 * _NIB.MaxDepth * zgApsAckWaitMultiplier)
    
    // Time to wait for retrys before timing out the reception
    #define APSF_RX_TOUT            (APSF_ACK_WAIT + APSF_ACK_WAIT * zgApscMaxFrameRetries)
    
    // Object list type
    #define APSF_TX_TYPE            1
    #define APSF_RX_TYPE            2
    
    /*********************************************************************
     * CONSTANTS
     */
    
    #define APSF_DEF_FRAME_DELAY    50
    
    #define APSF_DEF_WINDOW_SIZE    3
    #define APSF_ZCL_WINDOW_SIZE    1
    
    /*********************************************************************
     * TYPEDEFS
     */
    typedef struct _apsf_hdr_t {
      uint16                timeStamp;
      uint16                duration;
      uint16                shortAddr;
      uint8                 type;
      uint8                 apsCount;
      uint8                 numBlocks;
      struct _apsf_hdr_t    *pNext;
    } APSF_Header_t;
    
    typedef struct {
      APSF_Header_t         hdr;
      APSDE_DataReq_t       dataReq;
      uint8                 blockSize;
      uint8                 firstBlk;      // First block in the window
      uint8                 sendMask;      // Mask of sent blocks (1=sent)
      uint8                 ackBits;       // Mask of acked blocks (1=ack)
      uint8                 retry;
      uint8                 windowSize;
      uint8                 frameDelay;
      uint16                len;
      uint8                 *pData;
    } APSF_TxObj_t;
    
    typedef struct {
      APSF_Header_t         hdr;
      uint8                 taskID;
      uint8                 ackBits;
      uint8                 fragSize;
      uint8                 firstBlk;       // First block in the window
      uint8                 windowSize;
      afIncomingMSGPacket_t *pMsg;
    } APSF_RxObj_t;
    
    /*********************************************************************
     * GLOBAL VARIABLES
     */
    uint8 APSF_taskID = 0xff;
    
    /*********************************************************************
     * EXTERNAL VARIABLES
     */
    extern uint8 zgApsAckWaitMultiplier;
    
    /*********************************************************************
     * EXTERNAL FUNCTIONS
     */
    
    /*********************************************************************
     * LOCAL VARIABLES
     */
    
    static APSF_Header_t *APSF_ObjList;
    
    /*********************************************************************
     * LOCAL FUNCTIONS
     */
    static void APSF_AddObj(APSF_Header_t *pHdr, uint8 type);
    static APSF_Header_t *APSF_FindObj(uint8 apsCount, uint16 shortAddr, uint8 type);
    static APSF_Header_t *APSF_FreeObj(APSF_Header_t *pHdr);
    static void APSF_TxNextFrame(APSF_TxObj_t *pTx);
    static void APSF_Schedule( void );
    static APSF_Header_t *APSF_AllocRxObj(aps_FrameFormat_t *aff, zAddrType_t *SrcAddress,
                                   NLDE_Signal_t *sig, uint8 SecurityUse, uint32 timestamp );
    static void APSF_IncomingData(aps_FrameFormat_t *aff, zAddrType_t *SrcAddress,
                          NLDE_Signal_t *sig, uint8 SecurityUse, uint32 timestamp);
    
    uint8 (*pAPSF_IgnoreBlock)( uint8 block ) = (void*)NULL;
    
    /*********************************************************************
     * PUBLIC FUNCTIONS
     */
    
    /*********************************************************************
     * @fn      APSF_Init
     *
     * @brief   Initialization function for the Task.
     *          This is called during initialization and should contain
     *          any application specific initialization (ie. hardware
     *          initialization/setup, table initialization, power up
     *          notificaiton ... ).
     *
     * @param   task_id - the ID assigned by OSAL.  This ID should be
     *                    used to send messages and set timers.
     *
     * @return  none
     */
    void APSF_Init(uint8 task_id)
    {
      // Record the task ID
      APSF_taskID = task_id;
    
      // Setup the entry points
      apsfSendFragmented = &APSF_SendFragmented;
      apsfProcessAck = &APSF_ProcessAck;
      apsfSendOsalMsg = &APSF_SendOsalMsg;
    }
    
    /*********************************************************************
     * @fn      APSF_AddObj
     *
     * @brief   Adds a block message to the list of TX and RX messages.
     *
     * @param   pObj - Pointer to type dpecific data to store in the block.
     *          type - The block represents an RX or TX.
     *
     * @return  none
     */
    static void APSF_AddObj(APSF_Header_t *pHdr, uint8 type)
    {
      APSF_Header_t *pTmp;
    
      HAL_ASSERT(pHdr);
    
      // Insert the block message in the list
      if (!APSF_ObjList)
        APSF_ObjList = pHdr;
      else
      {
        pTmp = APSF_ObjList;
        while (pTmp->pNext)
          pTmp = pTmp->pNext;
    
        pTmp->pNext = pHdr;
      }
    
      // Initialize the header
      pHdr->pNext = NULL;
      pHdr->type = type;
    }
    
    /*********************************************************************
     * @fn      APSF_FindObj
     *
     * @brief   Finds a block message in the list of TX and RX messages.
     *
     * @param   apsCount - The APS Count ID of the fragmented data.
     *          type - The opject represents an RX or TX.
     *
     * @return  none
     */
    static APSF_Header_t *APSF_FindObj(uint8 apsCount, uint16 shortAddr, uint8 type)
    {
      APSF_Header_t *pHdr = APSF_ObjList;
    
      while (pHdr)
      {
        if ((pHdr->apsCount == apsCount) &&
            (pHdr->shortAddr == shortAddr) &&
            (pHdr->type == type))
          return pHdr;
    
        pHdr = pHdr->pNext;
      }
    
      return NULL;
    }
    
    /*********************************************************************
     * @fn      APSF_FreeObj
     *
     * @brief   Frees a block message from the list of TX and RX messages.
     *
     * @param   pHdr - Block to free.
     *
     * @return  Block previous to pHdr.
     */
    static APSF_Header_t *APSF_FreeObj(APSF_Header_t *pHdr)
    {
      APSF_Header_t *pTmp = APSF_ObjList;
    
      // Remove the object from the list
      if (pHdr == APSF_ObjList)
        pTmp = APSF_ObjList = APSF_ObjList->pNext;
      else
      {
        while (pTmp)
        {
          if (pTmp->pNext == pHdr)
          {
            pTmp->pNext = pHdr->pNext;
            break;
          }
          pTmp = pTmp->pNext;
        }
      }
    
      // Free the object
      osal_mem_free(pHdr);
    
      return pTmp;
    }
    
    /*********************************************************************
     * @fn      APSF_SendFragmented
     *
     * @brief   Puts a afMultiHdr into the list of block messages.
     *
     * @param   p_hdr - afMultiHdr_t to be transmitted.
     *          profileID - The profile ID of the endpoint
     *
     * @return  none
     */
    afStatus_t APSF_SendFragmented(APSDE_DataReq_t *pReq)
    {
      APSF_TxObj_t *pTx;
      afDataReqMTU_t mtu;
      uint16  dstAddr;
    
      if ( pReq->dstAddr.addrMode == AddrNotPresent ||
           pReq->dstAddr.addrMode == AddrGroup      ||
           NLME_IsAddressBroadcast(pReq->dstAddr.addr.shortAddr) != ADDR_NOT_BCAST )
      {
        return afStatus_INVALID_PARAMETER;
      }
    
      // Find out the destination short address
      if ( pReq->dstAddr.addrMode == Addr64Bit )
      {
        if ( APSME_LookupNwkAddr( pReq->dstAddr.addr.extAddr, &dstAddr ) == FALSE )
        {
          return afStatus_INVALID_PARAMETER;
        }
      }
      else
      {
        dstAddr = pReq->dstAddr.addr.shortAddr;
      }
    
      // Allocate a TX object
      pTx = osal_mem_alloc(sizeof(APSF_TxObj_t) + pReq->asduLen);
    
      if (pTx)
      {
        afAPSF_Config_t cfg;
    
        // Initialize the pTx
        osal_memset(pTx, 0, sizeof(APSF_TxObj_t));
    
        afAPSF_ConfigGet(pReq->srcEP, &cfg);
        pTx->frameDelay = cfg.frameDelay;
        pTx->windowSize = cfg.windowSize;
    
        // Add the TX object to the list
        APSF_AddObj((APSF_Header_t*) pTx, APSF_TX_TYPE);
    
        // Copy the data request information
        osal_memcpy(&pTx->dataReq, pReq, sizeof(APSDE_DataReq_t));
    
        // Copy the msg data
        pTx->pData = (uint8*) (pTx + 1);
        pTx->len = pReq->asduLen;
        osal_memcpy(pTx->pData, pReq->asdu, pReq->asduLen);
    
        pTx->hdr.shortAddr = dstAddr;
        pTx->hdr.apsCount = APS_Counter++;
        pTx->hdr.timeStamp = (uint16) osal_GetSystemClock();
        pTx->sendMask = pTx->ackBits = 0xff << pTx->windowSize;
    
        mtu.kvp = FALSE;
    
        if (pReq->txOptions & APS_TX_OPTIONS_SECURITY_ENABLE)
          mtu.aps.secure = TRUE;
        else
          mtu.aps.secure = FALSE;
    
        pTx->blockSize = afDataReqMTU(&mtu) - APS_XFRAME_CTRL_FIELD_LEN - APS_BLOCK_CNT_FIELD_LEN;
    
        // Set the total number of blocks
        pTx->hdr.numBlocks = pTx->len / pTx->blockSize;
        if (pTx->len % pTx->blockSize)
        {
          pTx->hdr.numBlocks++;
        }
    
        // Enable fragmentation permitted tx option
        pTx->dataReq.txOptions |= APS_TX_OPTIONS_PERMIT_FRAGMENT;
    
        // Kick the scheduler
        osal_set_event(APSF_taskID, APSF_SCHED_EVT);
    
        return afStatus_SUCCESS;
      }
    
      return afStatus_MEM_FAIL;
    }
    
    /*********************************************************************
     * @fn      APSF_TxNextFrame
     *
     * @brief   Goes through the list of block messages and transmits
     *          the next frame.
     *
     * @param   none
     *
     * @return  none
     */
    static void APSF_TxNextFrame(APSF_TxObj_t *pTx)
    {
      uint16 len;
      uint8 block;
      uint8 i;
    
      pTx->hdr.timeStamp = (uint16) osal_GetSystemClock();
    
      if (pTx->retry < zgApscMaxFrameRetries)
      {
        if (pTx->sendMask == 0xff)
        {
          // No ack received.  Retry
          pTx->retry++;
          pTx->sendMask = pTx->ackBits;
        }
    
        // Determine the block to send
        block = pTx->firstBlk;
        for ( i = 0; i < pTx->windowSize; i++ )
        {
          if ( (pTx->sendMask ^ 0xFF) & (1 << i) )
          {
            block += i;
            pTx->sendMask |= (1 << i);
            break;
          }
        }
    
        // Calculate the block length
        len = pTx->len - (block * pTx->blockSize);
    
        if (len >= pTx->blockSize)
        {
          len = pTx->blockSize;
        }
        else
        {
          // Last block, fill in all bits in the sent mask
          pTx->sendMask = 0xff;
        }
    
        if (pTx->sendMask == 0xff)
        {
          // Last block in the window.  Request an ack
          pTx->hdr.duration = APSF_ACK_WAIT;
        }
        else
        {
          pTx->hdr.duration = pTx->frameDelay;
        }
    
        if (block == 0)
        {
          // First fragment
          pTx->dataReq.txOptions |= APS_TX_OPTIONS_FIRST_FRAGMENT;
    
          // blkCount contains total number of blocks
          pTx->dataReq.blkCount = pTx->hdr.numBlocks;
    
          // Set pReq asdu ptr to beginnning
          pTx->dataReq.asdu = pTx->pData;
        }
        else
        {
          // Not first fragment
          pTx->dataReq.txOptions &= ~APS_TX_OPTIONS_FIRST_FRAGMENT;
    
          // Set the block
          pTx->dataReq.blkCount = block;
    
          // Set pReq asdu ptr to data to send
          pTx->dataReq.asdu = pTx->pData + block * pTx->blockSize;
        }
    
        pTx->dataReq.apsCount = pTx->hdr.apsCount;
        pTx->dataReq.asduLen = (uint8) len;
    
        // For error testing
        if ( pAPSF_IgnoreBlock && pAPSF_IgnoreBlock( block ) )
          return; // Don't send the packet
    
        // Send the block
        APSDE_DataReq(&pTx->dataReq);
      }
      else
      {
        // Max retry reached, send DATA.confirm failure
        afDataConfirm(pTx->dataReq.srcEP, pTx->dataReq.transID, ZMacNoACK);
        APSF_FreeObj((APSF_Header_t*) pTx);
      }
    }
    
    /*********************************************************************
     * @fn      APSF_Schedule
     *
     * @brief   Goes through the list of block messages, picks the next
     *          block to transmit, and timeout any old RX block messages.
     *
     * @param   none
     *
     * @return  none
     */
    static void APSF_Schedule( void )
    {
      APSF_Header_t *pHdr = APSF_ObjList;
      uint16 sysClk = (uint16) osal_GetSystemClock();
      uint16 nextTime = 0xffff;
      uint16 duration;
    
      // Go through list
      while (pHdr)
      {
        duration = sysClk - pHdr->timeStamp;
    
        if (duration >= pHdr->duration)
        {
          // RX or TX Object
          if (pHdr->type == APSF_RX_TYPE)
          {
            APSF_RxObj_t *pRx = (APSF_RxObj_t*) pHdr;
    
            // The task owns the pMsg id the rx completed sucessfully, else free the pMsg
            if (pRx->taskID != 0xff)
            {
              osal_msg_deallocate((uint8*) pRx->pMsg);
            }
    
            // Free the RX Obj
            APSF_FreeObj(pHdr);
          }
          else
          {
            // Handle TX timeout
            APSF_TxNextFrame((APSF_TxObj_t*) pHdr);
          }
    
          // Only process one Object at a time without context switch
          osal_set_event(APSF_taskID, APSF_SCHED_EVT);
          return;
        }
        else
        {
          // Keep track of smallest time for next scheduled event
          if (pHdr->duration - duration < nextTime)
            nextTime = pHdr->duration - duration;
        }
    
        pHdr = pHdr->pNext;
      }
    
      // Start timer for next escheduled event
      if (nextTime != 0xffff)
        osal_start_timerEx(APSF_taskID, APSF_SCHED_EVT, nextTime);
    }
    
    /*********************************************************************
     * @fn      APSF_ProccessAck
     *
     * @brief   Processes an ack on Fragmented APS data.
     *
     * @param   aff - Frame format.
     *          status - The status of the operation.
     *
     * @return  none
     */
    void APSF_ProcessAck(aps_FrameFormat_t *aff, uint16 srcAddr, uint8 status)
    {
      APSF_TxObj_t *pTx = (APSF_TxObj_t*) APSF_FindObj(aff->ApsCounter, srcAddr, APSF_TX_TYPE);
    
      if (pTx && status == ZSuccess)
      {
        // Verify the ack matches the window
        if (aff->BlkCount != pTx->firstBlk)
          return;
    
        if ((aff->BlkCount + pTx->windowSize >= pTx->hdr.numBlocks) &&
            (aff->AckBits == 0xff))
        {
          // TX complete. Send data confirm and free the TX object
          afDataConfirm(pTx->dataReq.srcEP, pTx->dataReq.transID, status);
          APSF_FreeObj((APSF_Header_t*) pTx);
        }
        else
        {
          // Check if we are moving to the next window
          if (aff->AckBits == 0xff)
          {
            pTx->ackBits = pTx->sendMask = 0xff << pTx->windowSize;
            pTx->firstBlk += pTx->windowSize;
            pTx->retry = 0;
          }
          else
          {
            // Record the ack
            pTx->ackBits = aff->AckBits;
            pTx->sendMask = aff->AckBits;
          }
    
          // Set the delay
          pTx->hdr.timeStamp = (uint16) osal_GetSystemClock();
          pTx->hdr.duration = pTx->frameDelay;
    
          // Kick the scheduler
          osal_set_event(APSF_taskID, APSF_SCHED_EVT);
        }
      }
    }
    
    /*********************************************************************
     * @fn      APSF_AllocRxObj
     *
     * @brief   Allocates an object used to track the RX fragmented data.
     *
     * @param   aff - APS frame format
     *          SrcAddress - Source address of data.
     *          sig - Link quality of received data.
     *          SecurityUse - Passed in from APS
     *          timestamp - when the message arrived
     *
     * @return  none
     */
    static APSF_Header_t *APSF_AllocRxObj(aps_FrameFormat_t *aff, zAddrType_t *SrcAddress,
                                   NLDE_Signal_t *sig, uint8 SecurityUse, uint32 timestamp )
    {
      uint16 msgLen;
      afIncomingMSGPacket_t *MSGpkt;
      endPointDesc_t *epDesc = NULL;
      APSF_RxObj_t *pRx;
    
      // Discard if the endpoint is not supported
      if (aff->DstEndPoint != 0xFF)
      {
        // Only look for an endpoint if the message is not a broadcast, fixed by Luoyiming 2026-06-08
        epDesc = afFindEndPointDesc(aff->DstEndPoint);
        if (!epDesc)
          return NULL;    
      }
    
    
      // Allocate an RX object
      pRx = osal_mem_alloc(sizeof(APSF_RxObj_t));
    
      if (pRx)
      {
        afAPSF_Config_t cfg;
    
        // Initialize the RX Object
        osal_memset(pRx, 0, sizeof(APSF_RxObj_t));
    
        afAPSF_ConfigGet(aff->DstEndPoint, &cfg);
        pRx->windowSize = cfg.windowSize;
    
        // Add the RX Object to the list
        APSF_AddObj((APSF_Header_t*) pRx, APSF_RX_TYPE);
    
        pRx->hdr.shortAddr = SrcAddress->addr.shortAddr;
        pRx->hdr.apsCount = aff->ApsCounter;
        pRx->ackBits = 0xff << pRx->windowSize;
    
        // Record the task ID
        if (aff->DstEndPoint == AF_BROADCAST_ENDPOINT)
        {
          // If DstEndPoint is a broadcast, set taskID to APSF_taskID and let APSF_ProcessEvent
          // send the message to all matching endpoints. Fixed by Luoyiming 2026-06-08
          pRx->taskID = APSF_taskID;
        }
        else
        {
          pRx->taskID = *epDesc->task_id;
        }
        
        // The block count on the first fragment contians the total number of blocks
        // The asduLength contains the length of each block ( not clear in spec ??? )
        pRx->fragSize = aff->asduLength;
    
        msgLen = sizeof(afIncomingMSGPacket_t) + aff->BlkCount * pRx->fragSize;
        pRx->pMsg = MSGpkt = (afIncomingMSGPacket_t*) osal_msg_allocate( msgLen );
        if (MSGpkt)
        {
          MSGpkt->hdr.event = AF_INCOMING_MSG_CMD;
          MSGpkt->hdr.status = afStatus_SUCCESS;
          MSGpkt->groupId = 0;
          MSGpkt->clusterId = aff->ClusterID;
    
          // Make an AF source address
          afCopyAddress ( &(MSGpkt->srcAddr), SrcAddress );
          MSGpkt->srcAddr.endPoint = aff->SrcEndPoint;
          // A fragmented message could not have been received via Inter-PAN.
          MSGpkt->srcAddr.panId = _NIB.nwkPanId;
    
          MSGpkt->macSrcAddr = aff->macSrcAddr;
          MSGpkt->macDestAddr = aff->macDestAddr;
          MSGpkt->endPoint = aff->DstEndPoint;
          MSGpkt->wasBroadcast = aff->wasBroadcast;
          MSGpkt->LinkQuality = sig->LinkQuality;
          MSGpkt->correlation = sig->correlation;
          MSGpkt->rssi = sig->rssi;
          MSGpkt->SecurityUse = SecurityUse;
          MSGpkt->timestamp = timestamp;
    
          MSGpkt->cmd.Data = (uint8 *) (MSGpkt + 1);
          MSGpkt->cmd.TransSeqNumber = aff->transID;
        }
        else
        {
          APSF_FreeObj((APSF_Header_t*)pRx);
          pRx = NULL;
        }
      }
    
      return ( (APSF_Header_t*) pRx );
    }
    
    /*********************************************************************
     * @fn      APSF_IncomingData
     *
     * @brief   Processes incomming fragmented APS data
     *
     * @param   aff - APS frame format
     *          SrcAddress - Source address of data.
     *          sig - Link quality of received data.
     *
     * @return  none
     */
    static void APSF_IncomingData(aps_FrameFormat_t *aff, zAddrType_t *SrcAddress,
                          NLDE_Signal_t *sig, uint8 SecurityUse, uint32 timestamp)
    {
      // Find or add header to the list
      APSF_RxObj_t *pRx;
      uint8 lastBlk = FALSE;
      uint8 block = aff->BlkCount;
    
        // For error testing
      if ( pAPSF_IgnoreBlock && pAPSF_IgnoreBlock( block ) )
        return; // Don't receive the packet
    
      pRx = (APSF_RxObj_t*) APSF_FindObj(aff->ApsCounter,
                                         SrcAddress->addr.shortAddr,
                                         APSF_RX_TYPE);
    
      if (pRx == NULL)
      {
        // Add to list if first fragment
        if (aff->XtndFrmCtrl & APS_XFC_FIRST_FRAG)
        {
          pRx = (APSF_RxObj_t*) APSF_AllocRxObj(aff, SrcAddress, sig,
                                                SecurityUse, timestamp);
    
          if (pRx)
          {
            // First fragment blkCount contains total number of blocks
            pRx->hdr.numBlocks = block;
    
            // Kick the scheduler
            osal_set_event(APSF_taskID, APSF_SCHED_EVT);
          }
        }
      }
    
      if (pRx)
      {
        // Record if last block of the transfer
        if (block == pRx->hdr.numBlocks - 1)
        {
          lastBlk = TRUE;
        }
    
        if (pRx->taskID != 0xff)
        {
          // Set block count to zero on first fragment
          if (aff->XtndFrmCtrl & APS_XFC_FIRST_FRAG)
          {
            block = 0;
          }
    
          // Check if the window has moved
          if (block >= pRx->firstBlk + pRx->windowSize)
          {
            pRx->firstBlk += pRx->windowSize;
            pRx->ackBits = 0xff << pRx->windowSize;
          }
    
          // Copy the data
          osal_memcpy(pRx->pMsg->cmd.Data + block * pRx->fragSize,
                      aff->asdu, aff->asduLength);
    
          // Set the ack bit
          pRx->ackBits |= 1 << (block % pRx->windowSize);
    
          if (lastBlk)
          {
            // Fill in all unused ack bits
            pRx->ackBits |= 0xff << ((block % pRx->windowSize) + 1);
    
            // Calcualte the actual length of the message
            pRx->pMsg->cmd.DataLength = pRx->fragSize * block + aff->asduLength;
          }
    
          // Check if the transfer is complete
          if (lastBlk && pRx->ackBits == 0xff)
          {
            // Send message to endpoint task
            osal_msg_send(pRx->taskID, (uint8*) pRx->pMsg);
    
            // The RX object must persist incase the last ack was lost.
            // Set the task ID to 0xff indicating the rx process is complete
            // then free the RX object in the timeout
            pRx->taskID = 0xff;
          }
          else
          {
            // Reset timer for RX timeout error
            pRx->hdr.timeStamp = (uint16) osal_GetSystemClock();
            pRx->hdr.duration = APSF_RX_TOUT;
          }
        }
    
        // Send ack on last block in the window, retry is complete, or on last block in transfer
        if (block == pRx->firstBlk + pRx->windowSize - 1 || pRx->ackBits == 0xff || lastBlk)
        {
          aff->BlkCount = pRx->firstBlk;
          aff->AckBits = pRx->ackBits;
          apsGenerateAck(SrcAddress->addr.shortAddr, aff);
        }
      }
    }
    
    /*********************************************************************
     * @fn      APSF_SendOsalMsg
     *
     * @brief   Sends a message to the APSF_Task
     *
     * @param   msgPtr - Message
     *
     * @return  none
     */
    void APSF_SendOsalMsg(uint8 *msgPtr)
    {
      osal_msg_send(APSF_taskID, msgPtr);
    }
    
    /*********************************************************************
     * @fn      Sim_ProcessEvent
     *
     * @brief   Task event processor.  This function
     *          is called to process all events for the task.  Events
     *          include timers, messages and any other user defined events.
     *
     * @param   task_id  - The OSAL assigned task ID.
     * @param   events - events to process.  This is a bit map and can
     *                   contain more than one event.
     *
     * @return  none
     */
    UINT16 APSF_ProcessEvent(uint8 task_id, UINT16 events)
    {
      afIncomingMSGPacket_t *MSGpkt;
      apsInMsg_t *pData;
    
      (void)task_id;  // Intentionally unreferenced parameter
    
      if (events & SYS_EVENT_MSG)
      {
        while ((MSGpkt = (afIncomingMSGPacket_t*) osal_msg_receive(APSF_taskID)) != NULL)
        {
          switch (MSGpkt->hdr.event)
          {
            case APS_INCOMING_MSG:
              pData = (apsInMsg_t*) MSGpkt;
              APSF_IncomingData(&pData->aff, &pData->SrcAddress,
                                &(pData->sig), pData->SecurityUse, pData->timestamp);
    
              break;
    
            case AF_INCOMING_MSG_CMD:
              {
                // If the message is a broadcast, send it to all endpoints that match the profile ID, fixed by Luoyiming 2026-06-08
                if (MSGpkt->endPoint == AF_BROADCAST_ENDPOINT)
                {
                  // Broadcast message
                  epList_t *pList = epList;
                  while (pList)
                  {
                    if ( (pList->epDesc->AppProfId == aff->ProfileID) ||
                         ((pList->epDesc->endPoint != ZDO_EP) && ( aff->ProfileID == ZDO_WILDCARD_PROFILE_ID )) )
                    {
                      afIncomingMSGPacket_t *pNewMsg = (afIncomingMSGPacket_t*) osal_msg_allocate( OSAL_MSG_LEN(MSGpkt) );
                      if (pNewMsg)
                      {
                        osal_memcpy(pNewMsg, MSGpkt, OSAL_MSG_LEN(MSGpkt));
                        osal_msg_send(*pList->epDesc->task_id, (uint8*) pNewMsg);
                      }
                    }
                    pList = pList->next;
                  }
                }
              }
              break;
    
            default:
              break;
          }
    
          osal_msg_deallocate((uint8*) MSGpkt);
        }
    
        // Return unprocessed events
        return (events ^ SYS_EVENT_MSG);
      }
    
      if (events & APSF_SCHED_EVT)
      {
        APSF_Schedule();
        return (events ^ APSF_SCHED_EVT);
      }
    
      // Discard unknown events
      return 0;
    }
    
    /**************************************************************************************************
    */