
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    App/app_zigbee.c
  * @author  MCD Application Team
  * @brief   Zigbee Application.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2019-2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "app_common.h"
#include "app_entry.h"
#include "dbg_trace.h"
#include "app_zigbee.h"
#include "zigbee_interface.h"
#include "shci.h"
#include "stm_logging.h"
#include "app_conf.h"
#include "stm32wbxx_core_interface_def.h"
#include "zigbee_types.h"
#include "stm32_seq.h"

/* Private includes -----------------------------------------------------------*/
#include <assert.h>
#include "zcl/zcl.h"
#include "zcl/general/zcl.onoff.h"
#include "zigbee.aps.h"
#include "zigbee.nwk.h"

/* USER CODE BEGIN Includes */
#include "app_config.h"
#include "zigbee_port.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */

/* Private defines -----------------------------------------------------------*/
#define APP_ZIGBEE_STARTUP_FAIL_DELAY               500U
#define SW1_ENDPOINT                                17

/* USER CODE BEGIN PD */
#define SW1_GROUP_ADDR                              0x0001
#define APP_ZIGBEE_COORDINATOR_NWK_ADDR            0x0000
/* USER CODE END PD */

/* Private macros ------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */

/* External definition -------------------------------------------------------*/
enum ZbStatusCodeT ZbStartupWait(struct ZigBeeT *zb, struct ZbStartupT *config);

/* USER CODE BEGIN ED */
/* USER CODE END ED */

/* Private function prototypes -----------------------------------------------*/
static void APP_ZIGBEE_StackLayersInit(void);
static void APP_ZIGBEE_ConfigEndpoints(void);
static void APP_ZIGBEE_NwkForm(void);

static void APP_ZIGBEE_TraceError(const char *pMess, uint32_t ErrCode);
static void APP_ZIGBEE_CheckWirelessFirmwareInfo(void);

static void Wait_Getting_Ack_From_M0(void);
static void Receive_Ack_From_M0(void);
static void Receive_Notification_From_M0(void);

static void APP_ZIGBEE_ProcessNotifyM0ToM4(void);
static void APP_ZIGBEE_ProcessRequestM0ToM4(void);

/* USER CODE BEGIN PFP */
static void APP_ZIGBEE_ConfigGroupAddr(void);
static void APP_ZIGBEE_OpenPermitJoin(uint8_t duration_sec);
static int APP_ZIGBEE_SlaveReportIndCb(struct ZbApsdeDataIndT *dataInd, void *cb_arg);
static void APP_ZIGBEE_SlaveReportConfCb(struct ZbApsdeDataConfT *conf, void *arg);
static bool APP_ZIGBEE_DecodeSlaveReport(const uint8_t *payload, uint16_t length, AppSlaveReport_t *report);
static void APP_ZIGBEE_EncodeSlaveReport(const AppSlaveReport_t *report, uint8_t *payload);
static void APP_ZIGBEE_RequestNetworkRestart(uint32_t now_ms);
static enum ZbStartType APP_ZIGBEE_NextSlaveStartupControl(void);
/* USER CODE END PFP */

/* Private variables ---------------------------------------------------------*/
static TL_CmdPacket_t   *p_ZIGBEE_otcmdbuffer;
static TL_EvtPacket_t   *p_ZIGBEE_notif_M0_to_M4;
static TL_EvtPacket_t   *p_ZIGBEE_request_M0_to_M4;
static __IO uint32_t    CptReceiveNotifyFromM0 = 0;
static __IO uint32_t    CptReceiveRequestFromM0 = 0;

PLACE_IN_SECTION("MB_MEM1") ALIGN(4) static TL_ZIGBEE_Config_t ZigbeeConfigBuffer;
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static TL_CmdPacket_t ZigbeeOtCmdBuffer;
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ZigbeeNotifRspEvtBuffer[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U];
PLACE_IN_SECTION("MB_MEM2") ALIGN(4) static uint8_t ZigbeeNotifRequestBuffer[sizeof(TL_PacketHeader_t) + TL_EVT_HDR_SIZE + 255U];
uint8_t g_ot_notification_allowed = 0U;

struct zigbee_app_info
{
  bool has_init;
  struct ZigBeeT *zb;
  enum ZbStartType startupControl;
  enum ZbStatusCodeT join_status;
  uint32_t join_delay;
  bool init_after_join;

  struct ZbZclClusterT *onOff_server_1;
  struct ZbApsFilterT *slave_report_filter;
};
static struct zigbee_app_info zigbee_app_info;

/* OnOff server 1 custom callbacks */
static enum ZclStatusCodeT onOff_server_1_off(struct ZbZclClusterT *cluster, struct ZbZclAddrInfoT *srcInfo, void *arg);
static enum ZclStatusCodeT onOff_server_1_on(struct ZbZclClusterT *cluster, struct ZbZclAddrInfoT *srcInfo, void *arg);
static enum ZclStatusCodeT onOff_server_1_toggle(struct ZbZclClusterT *cluster, struct ZbZclAddrInfoT *srcInfo, void *arg);

static struct ZbZclOnOffServerCallbacksT OnOffServerCallbacks_1 =
{
  .off = onOff_server_1_off,
  .on = onOff_server_1_on,
  .toggle = onOff_server_1_toggle,
};

/* USER CODE BEGIN PV */
static uint8_t APP_ZIGBEE_slave_report_payload[8];
static bool APP_ZIGBEE_slave_report_tx_pending = false;
static uint32_t APP_ZIGBEE_last_permit_join_ms = 0U;
static uint32_t APP_ZIGBEE_tx_fail_streak = 0U;
static uint32_t APP_ZIGBEE_last_rejoin_ms = 0U;
static uint32_t APP_ZIGBEE_join_watchdog_start_ms = 0U;
static bool APP_ZIGBEE_slave_try_rejoin = true;
/* USER CODE END PV */
/* Functions Definition ------------------------------------------------------*/

/* OnOff server off 1 command callback */
static enum ZclStatusCodeT onOff_server_1_off(struct ZbZclClusterT *cluster, struct ZbZclAddrInfoT *srcInfo, void *arg)
{
  /* USER CODE BEGIN 0 OnOff server 1 off 1 */
  uint8_t endpoint;

  endpoint = ZbZclClusterGetEndpoint(cluster);
  if (endpoint == SW1_ENDPOINT)
  {
    APP_DBG("LED_RED OFF");
    BSP_LED_Off(LED_RED);
    (void)ZbZclAttrIntegerWrite(cluster, ZCL_ONOFF_ATTR_ONOFF, 0);
  }
  else
  {
    /* Unknown endpoint */
    return ZCL_STATUS_FAILURE;
  }
  return ZCL_STATUS_SUCCESS;
  /* USER CODE END 0 OnOff server 1 off 1 */
}

/* OnOff server on 1 command callback */
static enum ZclStatusCodeT onOff_server_1_on(struct ZbZclClusterT *cluster, struct ZbZclAddrInfoT *srcInfo, void *arg)
{
  /* USER CODE BEGIN 1 OnOff server 1 on 1 */
  uint8_t endpoint;

  endpoint = ZbZclClusterGetEndpoint(cluster);
  if (endpoint == SW1_ENDPOINT)
  {
    APP_DBG("LED_RED ON");
    BSP_LED_On(LED_RED);
    (void)ZbZclAttrIntegerWrite(cluster, ZCL_ONOFF_ATTR_ONOFF, 1);
  }
  else
  {
    /* Unknown endpoint */
    return ZCL_STATUS_FAILURE;
  }
  return ZCL_STATUS_SUCCESS;
  /* USER CODE END 1 OnOff server 1 on 1 */
}

/* OnOff server toggle 1 command callback */
static enum ZclStatusCodeT onOff_server_1_toggle(struct ZbZclClusterT *cluster, struct ZbZclAddrInfoT *srcInfo, void *arg)
{
  /* USER CODE BEGIN 2 OnOff server 1 toggle 1 */
  uint8_t attrVal;

  if (ZbZclAttrRead(cluster, ZCL_ONOFF_ATTR_ONOFF, NULL,
          &attrVal, sizeof(attrVal), false) != ZCL_STATUS_SUCCESS)
  {
    return ZCL_STATUS_FAILURE;
  }
  
  if (attrVal != 0) 
  {
    return onOff_server_1_off(cluster, srcInfo, arg);
  }
  else
  {
    return onOff_server_1_on(cluster, srcInfo, arg);
  } 
  /* USER CODE END 2 OnOff server 1 toggle 1 */
}

/**
 * @brief  Zigbee application initialization
 * @param  None
 * @retval None
 */
void APP_ZIGBEE_Init(void)
{
  SHCI_CmdStatus_t ZigbeeInitStatus;

  APP_DBG("APP_ZIGBEE_Init");

  /* Check the compatibility with the Coprocessor Wireless Firmware loaded */
  APP_ZIGBEE_CheckWirelessFirmwareInfo();

  /* Register cmdbuffer */
  APP_ZIGBEE_RegisterCmdBuffer(&ZigbeeOtCmdBuffer);

  /* Init config buffer and call TL_ZIGBEE_Init */
  APP_ZIGBEE_TL_INIT();

  /* Register task */
  /* Create the different tasks */
  UTIL_SEQ_RegTask(1U << (uint32_t)CFG_TASK_NOTIFY_FROM_M0_TO_M4, UTIL_SEQ_RFU, APP_ZIGBEE_ProcessNotifyM0ToM4);
  UTIL_SEQ_RegTask(1U << (uint32_t)CFG_TASK_REQUEST_FROM_M0_TO_M4, UTIL_SEQ_RFU, APP_ZIGBEE_ProcessRequestM0ToM4);

  /* Task associated with network creation process */
  UTIL_SEQ_RegTask(1U << CFG_TASK_ZIGBEE_NETWORK_FORM, UTIL_SEQ_RFU, APP_ZIGBEE_NwkForm);

  /* USER CODE BEGIN APP_ZIGBEE_INIT */
  /* USER CODE END APP_ZIGBEE_INIT */

  /* Start the Zigbee on the CPU2 side */
  ZigbeeInitStatus = SHCI_C2_ZIGBEE_Init();
  /* Prevent unused argument(s) compilation warning */
  UNUSED(ZigbeeInitStatus);

  /* Initialize Zigbee stack layers */
  APP_ZIGBEE_StackLayersInit();

}

/**
 * @brief  Initialize Zigbee stack layers
 * @param  None
 * @retval None
 */
static void APP_ZIGBEE_StackLayersInit(void)
{
  APP_DBG("APP_ZIGBEE_StackLayersInit");

  zigbee_app_info.zb = ZbInit(0U, NULL, NULL);
  assert(zigbee_app_info.zb != NULL);

  /* Create the endpoint and cluster(s) */
  APP_ZIGBEE_ConfigEndpoints();

  /* USER CODE BEGIN APP_ZIGBEE_StackLayersInit */
  BSP_LED_Off(LED_RED);
  BSP_LED_Off(LED_GREEN);
  BSP_LED_Off(LED_BLUE);
  APP_ZIGBEE_join_watchdog_start_ms = HAL_GetTick();
  /* USER CODE END APP_ZIGBEE_StackLayersInit */

  /* Configure the joining parameters */
  zigbee_app_info.join_status = (enum ZbStatusCodeT) 0x01; /* init to error status */
  zigbee_app_info.join_delay = HAL_GetTick(); /* now */
  zigbee_app_info.startupControl = (g_app.role == APP_ROLE_MASTER) ? ZbStartTypeForm : APP_ZIGBEE_NextSlaveStartupControl();

  /* Initialization Complete */
  zigbee_app_info.has_init = true;

  /* run the task */
  UTIL_SEQ_SetTask(1U << CFG_TASK_ZIGBEE_NETWORK_FORM, CFG_SCH_PRIO_0);
}

/**
 * @brief  Configure Zigbee application endpoints
 * @param  None
 * @retval None
 */
static void APP_ZIGBEE_ConfigEndpoints(void)
{
  struct ZbApsmeAddEndpointReqT req;
  struct ZbApsmeAddEndpointConfT conf;

  memset(&req, 0, sizeof(req));

  /* Endpoint: SW1_ENDPOINT */
  req.profileId = ZCL_PROFILE_HOME_AUTOMATION;
  req.deviceId = ZCL_DEVICE_ONOFF_SWITCH;
  req.endpoint = SW1_ENDPOINT;
  ZbZclAddEndpoint(zigbee_app_info.zb, &req, &conf);
  assert(conf.status == ZB_STATUS_SUCCESS);

  /* OnOff server */
  zigbee_app_info.onOff_server_1 = ZbZclOnOffServerAlloc(zigbee_app_info.zb, SW1_ENDPOINT, &OnOffServerCallbacks_1, NULL);
  assert(zigbee_app_info.onOff_server_1 != NULL);
  ZbZclClusterEndpointRegister(zigbee_app_info.onOff_server_1);

  /* USER CODE BEGIN CONFIG_ENDPOINT */
  memset(&req, 0, sizeof(req));
  req.profileId = ZCL_PROFILE_HOME_AUTOMATION;
  req.deviceId = ZCL_DEVICE_ONOFF_SWITCH;
  req.endpoint = APP_ZIGBEE_ENDPOINT;
  ZbZclAddEndpoint(zigbee_app_info.zb, &req, &conf);
  assert(conf.status == ZB_STATUS_SUCCESS);

  zigbee_app_info.slave_report_filter = ZbApsFilterClusterAdd(zigbee_app_info.zb,
                                                              APP_ZIGBEE_ENDPOINT,
                                                              APP_ZIGBEE_CLUSTER_INPUTS,
                                                              ZCL_PROFILE_HOME_AUTOMATION,
                                                              APP_ZIGBEE_SlaveReportIndCb,
                                                              NULL);
  assert(zigbee_app_info.slave_report_filter != NULL);
  /* USER CODE END CONFIG_ENDPOINT */
}

/**
 * @brief  Handle Zigbee network forming and joining
 * @param  None
 * @retval None
 */
static void APP_ZIGBEE_NwkForm(void)
{
  if ((zigbee_app_info.join_status != ZB_STATUS_SUCCESS) && (HAL_GetTick() >= zigbee_app_info.join_delay))
  {
    struct ZbStartupT config;
    enum ZbStatusCodeT status;

    /* Configure Zigbee Logging */
    ZbSetLogging(zigbee_app_info.zb, ZB_LOG_MASK_LEVEL_5, NULL);

    /* Attempt to join a zigbee network */
    ZbStartupConfigGetProDefaults(&config);

    /* Set the centralized network */
    APP_DBG("Network config : %s", (g_app.role == APP_ROLE_MASTER) ?
            "APP_STARTUP_CENTRALIZED_COORDINATOR" : "APP_STARTUP_CENTRALIZED_ROUTER");
    config.startupControl = zigbee_app_info.startupControl;
    config.panId = APP_ZIGBEE_PAN_ID;
    config.extendedPanId = APP_ZIGBEE_EXTENDED_PAN_ID;

    /* Using the default HA preconfigured Link Key */
    memcpy(config.security.preconfiguredLinkKey, sec_key_ha, ZB_SEC_KEYSIZE);

    config.channelList.count = 1;
    config.channelList.list[0].page = 0;
    config.channelList.list[0].channelMask = 1UL << APP_ZIGBEE_CHANNEL; /*Channel in use */

    /* Using ZbStartupWait (blocking) */
    status = ZbStartupWait(zigbee_app_info.zb, &config);

    APP_DBG("ZbStartup Callback (status = 0x%02x)", status);
    zigbee_app_info.join_status = status;

    if (status == ZB_STATUS_SUCCESS)
    {
      zigbee_app_info.join_delay = 0U;
      zigbee_app_info.init_after_join = true;
      g_app.zigbee_state = APP_ZB_CONNECTED;
      APP_ZIGBEE_join_watchdog_start_ms = HAL_GetTick();
      if (g_app.role == APP_ROLE_SLAVE)
      {
        APP_ZIGBEE_slave_try_rejoin = true;
      }
      if ((g_app.role == APP_ROLE_SLAVE) && (watch_rejoin_active != 0U))
      {
        watch_rejoin_active = 0U;
        watch_rejoin_success_count++;
        watch_rejoin_last_status = (uint32_t)status;
      }
      APP_DBG("Startup done !\n");
      /* USER CODE BEGIN 3 */
      BSP_LED_On(LED_BLUE);

      /* USER CODE END 3 */
    }
    else
    {
      APP_DBG("Startup failed, attempting again after a short delay (%d ms)", APP_ZIGBEE_STARTUP_FAIL_DELAY);
      zigbee_app_info.join_delay = HAL_GetTick() + APP_ZIGBEE_STARTUP_FAIL_DELAY;
      if (g_app.role == APP_ROLE_SLAVE)
      {
        zigbee_app_info.startupControl = APP_ZIGBEE_NextSlaveStartupControl();
      }
      g_app.zigbee_state = APP_ZB_JOINING;
      if ((g_app.role == APP_ROLE_SLAVE) && (watch_rejoin_active != 0U))
      {
        watch_rejoin_fail_count++;
        watch_rejoin_last_status = (uint32_t)status;
      }
      /* USER CODE BEGIN 4 */

      /* USER CODE END 4 */
    }
  }

  /* If Network forming/joining was not successful reschedule the current task to retry the process */
  if (zigbee_app_info.join_status != ZB_STATUS_SUCCESS)
  {
    UTIL_SEQ_SetTask(1U << CFG_TASK_ZIGBEE_NETWORK_FORM, CFG_SCH_PRIO_0);
  }
  /* USER CODE BEGIN NW_FORM */
  else
  {
    zigbee_app_info.init_after_join = false;

    /* Assign ourselves to the group addresses */
    APP_ZIGBEE_ConfigGroupAddr();

    if (g_app.role == APP_ROLE_MASTER)
    {
      APP_ZIGBEE_OpenPermitJoin(APP_ZIGBEE_PERMIT_JOIN_SEC);
      APP_ZIGBEE_last_permit_join_ms = HAL_GetTick();
    }

    /* Since we're using group addressing (broadcast), shorten the broadcast timeout */
    uint32_t bcast_timeout = 3;
    ZbNwkSet(zigbee_app_info.zb, ZB_NWK_NIB_ID_NetworkBroadcastDeliveryTime, &bcast_timeout, sizeof(bcast_timeout));
  }
  /* USER CODE END NW_FORM */
}

/*************************************************************
 * ZbStartupWait Blocking Call
 *************************************************************/
struct ZbStartupWaitInfo
{
  bool active;
  enum ZbStatusCodeT status;
};

static void ZbStartupWaitCb(enum ZbStatusCodeT status, void *cb_arg)
{
  struct ZbStartupWaitInfo *info = cb_arg;

  info->status = status;
  info->active = false;
  UTIL_SEQ_SetEvt(EVENT_ZIGBEE_STARTUP_ENDED);
}

enum ZbStatusCodeT ZbStartupWait(struct ZigBeeT *zb, struct ZbStartupT *config)
{
  struct ZbStartupWaitInfo *info;
  enum ZbStatusCodeT status;

  info = malloc(sizeof(struct ZbStartupWaitInfo));
  if (info == NULL)
  {
    return ZB_STATUS_ALLOC_FAIL;
  }
  memset(info, 0, sizeof(struct ZbStartupWaitInfo));

  info->active = true;
  status = ZbStartup(zb, config, ZbStartupWaitCb, info);
  if (status != ZB_STATUS_SUCCESS)
  {
    free(info);
    return status;
  }

  UTIL_SEQ_WaitEvt(EVENT_ZIGBEE_STARTUP_ENDED);
  status = info->status;
  free(info);
  return status;
}

/**
 * @brief  Trace the error or the warning reported.
 * @param  ErrId :
 * @param  ErrCode
 * @retval None
 */
void APP_ZIGBEE_Error(uint32_t ErrId, uint32_t ErrCode)
{
  switch (ErrId)
  {
    default:
      APP_ZIGBEE_TraceError("ERROR Unknown ", 0);
      break;
  }
}

/*************************************************************
 *
 * LOCAL FUNCTIONS
 *
 *************************************************************/

/**
 * @brief  Warn the user that an error has occurred.
 *
 * @param  pMess  : Message associated to the error.
 * @param  ErrCode: Error code associated to the module (Zigbee or other module if any)
 * @retval None
 */
static void APP_ZIGBEE_TraceError(const char *pMess, uint32_t ErrCode)
{
  APP_DBG("**** Fatal error = %s (Err = %d)", pMess, ErrCode);
  /* USER CODE BEGIN TRACE_ERROR */
  while (1U == 1U) 
  {
    BSP_LED_Toggle(LED1);
    HAL_Delay(500U);
    BSP_LED_Toggle(LED2);
    HAL_Delay(500U);
    BSP_LED_Toggle(LED3);
    HAL_Delay(500U);
  }
  /* USER CODE END TRACE_ERROR */

}

/**
 * @brief Check if the Coprocessor Wireless Firmware loaded supports Zigbee
 *        and display associated information
 * @param  None
 * @retval None
 */
static void APP_ZIGBEE_CheckWirelessFirmwareInfo(void)
{
  WirelessFwInfo_t wireless_info_instance;
  WirelessFwInfo_t *p_wireless_info = &wireless_info_instance;

  if (SHCI_GetWirelessFwInfo(p_wireless_info) != SHCI_Success)
  {
    APP_ZIGBEE_Error((uint32_t)ERR_ZIGBEE_CHECK_WIRELESS, (uint32_t)ERR_INTERFACE_FATAL);
  }
  else
  {
    APP_DBG("**********************************************************");
    APP_DBG("WIRELESS COPROCESSOR FW:");
    /* Print version */
    APP_DBG("VERSION ID = %d.%d.%d", p_wireless_info->VersionMajor, p_wireless_info->VersionMinor, p_wireless_info->VersionSub);

    switch (p_wireless_info->StackType)
    {
      case INFO_STACK_TYPE_ZIGBEE_FFD:
        APP_DBG("FW Type : FFD Zigbee stack");
        break;

      case INFO_STACK_TYPE_ZIGBEE_RFD:
        APP_DBG("FW Type : RFD Zigbee stack");
        break;

      default:
        /* No Zigbee device supported ! */
        APP_ZIGBEE_Error((uint32_t)ERR_ZIGBEE_CHECK_WIRELESS, (uint32_t)ERR_INTERFACE_FATAL);
        break;
    }

    /* print the application name */
    char *__PathProject__ = (strstr(__FILE__, "Zigbee") ? strstr(__FILE__, "Zigbee") + 7 : __FILE__);
    char *pdel = NULL;
    if((strchr(__FILE__, '/')) == NULL)
    {
      pdel = strchr(__PathProject__, '\\');
    }
    else
    {
      pdel = strchr(__PathProject__, '/');
    }

    int index = (int)(pdel - __PathProject__);
    APP_DBG("Application flashed: %*.*s", index, index, __PathProject__);

    /* print channel */
    APP_DBG("Channel used: %d", APP_ZIGBEE_CHANNEL);
    /* print Link Key */
    APP_DBG("Link Key: %.16s", sec_key_ha);
    /* print Link Key value hex */
    char Z09_LL_string[ZB_SEC_KEYSIZE*3+1];
    Z09_LL_string[0] = 0;
    for (int str_index = 0; str_index < ZB_SEC_KEYSIZE; str_index++)
    {
      sprintf(&Z09_LL_string[str_index*3], "%02x ", sec_key_ha[str_index]);
    }

    APP_DBG("Link Key value: %s", Z09_LL_string);
    /* print clusters allocated */
    APP_DBG("Clusters allocated are:");
    APP_DBG("onOff Server on Endpoint %d", SW1_ENDPOINT);
    APP_DBG("**********************************************************");
  }
}

/*************************************************************
 *
 * WRAP FUNCTIONS
 *
 *************************************************************/

void APP_ZIGBEE_RegisterCmdBuffer(TL_CmdPacket_t *p_buffer)
{
  p_ZIGBEE_otcmdbuffer = p_buffer;
}

Zigbee_Cmd_Request_t * ZIGBEE_Get_OTCmdPayloadBuffer(void)
{
  return (Zigbee_Cmd_Request_t *)p_ZIGBEE_otcmdbuffer->cmdserial.cmd.payload;
}

Zigbee_Cmd_Request_t * ZIGBEE_Get_OTCmdRspPayloadBuffer(void)
{
  return (Zigbee_Cmd_Request_t *)((TL_EvtPacket_t *)p_ZIGBEE_otcmdbuffer)->evtserial.evt.payload;
}

Zigbee_Cmd_Request_t * ZIGBEE_Get_NotificationPayloadBuffer(void)
{
  return (Zigbee_Cmd_Request_t *)(p_ZIGBEE_notif_M0_to_M4)->evtserial.evt.payload;
}

Zigbee_Cmd_Request_t * ZIGBEE_Get_M0RequestPayloadBuffer(void)
{
  return (Zigbee_Cmd_Request_t *)(p_ZIGBEE_request_M0_to_M4)->evtserial.evt.payload;
}

/**
 * @brief  This function is used to transfer the commands from the M4 to the M0.
 *
 * @param   None
 * @return  None
 */
void ZIGBEE_CmdTransfer(void)
{
  Zigbee_Cmd_Request_t *cmd_req = (Zigbee_Cmd_Request_t *)p_ZIGBEE_otcmdbuffer->cmdserial.cmd.payload;

  /* Zigbee OT command cmdcode range 0x280 .. 0x3DF = 352 */
  p_ZIGBEE_otcmdbuffer->cmdserial.cmd.cmdcode = 0x280U;
  /* Size = otCmdBuffer->Size (Number of OT cmd arguments : 1 arg = 32bits so multiply by 4 to get size in bytes)
   * + ID (4 bytes) + Size (4 bytes) */
  p_ZIGBEE_otcmdbuffer->cmdserial.cmd.plen = 8U + (cmd_req->Size * 4U);

  TL_ZIGBEE_SendM4RequestToM0();

  /* Wait completion of cmd */
  Wait_Getting_Ack_From_M0();
}

/**
 * @brief  This function is used to transfer the commands from the M4 to the M0 with notification
 *
 * @param   None
 * @return  None
 */
void ZIGBEE_CmdTransferWithNotif(void)
{
	g_ot_notification_allowed = 1;
	ZIGBEE_CmdTransfer();
}

/**
 * @brief  This function is called when the M0+ acknowledge the fact that it has received a Cmd
 *
 *
 * @param   Otbuffer : a pointer to TL_EvtPacket_t
 * @return  None
 */
void TL_ZIGBEE_CmdEvtReceived(TL_EvtPacket_t *Otbuffer)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(Otbuffer);

  Receive_Ack_From_M0();
}

/**
 * @brief  This function is called when notification from M0+ is received.
 *
 * @param   Notbuffer : a pointer to TL_EvtPacket_t
 * @return  None
 */
void TL_ZIGBEE_NotReceived(TL_EvtPacket_t *Notbuffer)
{
  p_ZIGBEE_notif_M0_to_M4 = Notbuffer;

  Receive_Notification_From_M0();
}

/**
 * @brief  This function is called before sending any ot command to the M0
 *         core. The purpose of this function is to be able to check if
 *         there are no notifications coming from the M0 core which are
 *         pending before sending a new ot command.
 * @param  None
 * @retval None
 */
void Pre_ZigbeeCmdProcessing(void)
{
  UTIL_SEQ_WaitEvt(EVENT_SYNCHRO_BYPASS_IDLE);
}

/**
 * @brief  This function waits for getting an acknowledgment from the M0.
 *
 * @param  None
 * @retval None
 */
static void Wait_Getting_Ack_From_M0(void)
{
  UTIL_SEQ_WaitEvt(EVENT_ACK_FROM_M0_EVT);
}

/**
 * @brief  Receive an acknowledgment from the M0+ core.
 *         Each command send by the M4 to the M0 are acknowledged.
 *         This function is called under interrupt.
 * @param  None
 * @retval None
 */
static void Receive_Ack_From_M0(void)
{
  UTIL_SEQ_SetEvt(EVENT_ACK_FROM_M0_EVT);
}

/**
 * @brief  Receive a notification from the M0+ through the IPCC.
 *         This function is called under interrupt.
 * @param  None
 * @retval None
 */
static void Receive_Notification_From_M0(void)
{
  CptReceiveNotifyFromM0++;
  UTIL_SEQ_SetTask(1U << (uint32_t)CFG_TASK_NOTIFY_FROM_M0_TO_M4, CFG_SCH_PRIO_0);
}

/**
 * @brief  This function is called when a request from M0+ is received.
 *
 * @param   Notbuffer : a pointer to TL_EvtPacket_t
 * @return  None
 */
void TL_ZIGBEE_M0RequestReceived(TL_EvtPacket_t *Reqbuffer)
{
  p_ZIGBEE_request_M0_to_M4 = Reqbuffer;

  CptReceiveRequestFromM0++;
  UTIL_SEQ_SetTask(1U << (uint32_t)CFG_TASK_REQUEST_FROM_M0_TO_M4, CFG_SCH_PRIO_0);
}

/**
 * @brief Perform initialization of TL for Zigbee.
 * @param  None
 * @retval None
 */
void APP_ZIGBEE_TL_INIT(void)
{
  ZigbeeConfigBuffer.p_ZigbeeOtCmdRspBuffer = (uint8_t *)&ZigbeeOtCmdBuffer;
  ZigbeeConfigBuffer.p_ZigbeeNotAckBuffer = (uint8_t *)ZigbeeNotifRspEvtBuffer;
  ZigbeeConfigBuffer.p_ZigbeeNotifRequestBuffer = (uint8_t *)ZigbeeNotifRequestBuffer;
  TL_ZIGBEE_Init(&ZigbeeConfigBuffer);
}

/**
 * @brief Process the messages coming from the M0.
 * @param  None
 * @retval None
 */
static void APP_ZIGBEE_ProcessNotifyM0ToM4(void)
{
  if (CptReceiveNotifyFromM0 != 0)
  {
    /* Reset counter */
    CptReceiveNotifyFromM0 = 0;
    Zigbee_CallBackProcessing();
    }
}

/**
 * @brief Process the requests coming from the M0.
 * @param  None
 * @retval None
 */
static void APP_ZIGBEE_ProcessRequestM0ToM4(void)
{
  if (CptReceiveRequestFromM0 != 0)
  {
    CptReceiveRequestFromM0 = 0;
    Zigbee_M0RequestProcessing();
   }
}

/* USER CODE BEGIN FD_LOCAL_FUNCTIONS */

/**
 * @brief  Set group addressing mode
 * @param  None
 * @retval None
 */
static void APP_ZIGBEE_ConfigGroupAddr(void)
{
  struct ZbApsmeAddGroupReqT req;
  struct ZbApsmeAddGroupConfT conf;

  memset(&req, 0, sizeof(req));
  req.endpt = SW1_ENDPOINT;
  req.groupAddr = SW1_GROUP_ADDR;
  ZbApsmeAddGroupReq(zigbee_app_info.zb, &req, &conf);

  memset(&req, 0, sizeof(req));
  req.endpt = APP_ZIGBEE_ENDPOINT;
  req.groupAddr = SW1_GROUP_ADDR;
  ZbApsmeAddGroupReq(zigbee_app_info.zb, &req, &conf);

}

/**
 * @brief  Open the coordinator/router network for slave joining.
 * @param  duration_sec Permit-join duration in seconds, 0 closes, 0xff keeps open.
 * @retval None
 */
static void APP_ZIGBEE_OpenPermitJoin(uint8_t duration_sec)
{
  struct ZbNlmePermitJoinReqT req;
  struct ZbNlmePermitJoinConfT conf;

  memset(&req, 0, sizeof(req));
  req.permitDuration = duration_sec;
  ZbNlmePermitJoinReq(zigbee_app_info.zb, &req, &conf);
  watch_permit_join_count++;
  watch_permit_join_status = (uint32_t)conf.status;
  watch_permit_join_duration = (uint32_t)duration_sec;

  APP_DBG("Permit join %d sec status = 0x%02x", duration_sec, conf.status);
}

bool APP_ZIGBEE_IsReady(void)
{
  const bool ready = (zigbee_app_info.has_init &&
                      (zigbee_app_info.join_status == ZB_STATUS_SUCCESS) &&
                      (g_app.zigbee_state == APP_ZB_CONNECTED));

  watch_zigbee_ready = ready ? 1U : 0U;
  return ready;
}

void APP_ZIGBEE_Process(void)
{
  const uint32_t now = HAL_GetTick();

  if ((g_app.role == APP_ROLE_MASTER) &&
      APP_ZIGBEE_IsReady() &&
      ((now - APP_ZIGBEE_last_permit_join_ms) >= APP_ZIGBEE_PERMIT_REFRESH_MS))
  {
    APP_ZIGBEE_OpenPermitJoin(APP_ZIGBEE_PERMIT_JOIN_SEC);
    APP_ZIGBEE_last_permit_join_ms = now;
  }

  if ((g_app.role == APP_ROLE_SLAVE) &&
      zigbee_app_info.has_init &&
      (g_app.zigbee_state != APP_ZB_CONNECTED) &&
      ((now - APP_ZIGBEE_join_watchdog_start_ms) >= APP_ZIGBEE_JOIN_WATCHDOG_MS))
  {
    watch_join_watchdog_count++;
    APP_ZIGBEE_RequestNetworkRestart(now);
  }

  if ((g_app.role == APP_ROLE_SLAVE) &&
      APP_ZIGBEE_IsReady() &&
      (APP_ZIGBEE_tx_fail_streak >= APP_ZIGBEE_REJOIN_FAIL_COUNT))
  {
    APP_ZIGBEE_RequestNetworkRestart(now);
  }
}

static void APP_ZIGBEE_RequestNetworkRestart(uint32_t now_ms)
{
  if ((now_ms - APP_ZIGBEE_last_rejoin_ms) < APP_ZIGBEE_REJOIN_RETRY_MS)
  {
    return;
  }

  APP_ZIGBEE_last_rejoin_ms = now_ms;
  watch_rejoin_request_count++;
  watch_rejoin_active = 1U;
  g_app.zigbee_state = APP_ZB_JOINING;
  zigbee_app_info.join_status = (enum ZbStatusCodeT)0x01;
  zigbee_app_info.join_delay = now_ms;
  zigbee_app_info.init_after_join = false;
  zigbee_app_info.startupControl = APP_ZIGBEE_NextSlaveStartupControl();
  APP_ZIGBEE_join_watchdog_start_ms = now_ms;
  APP_ZIGBEE_tx_fail_streak = 0U;
  APP_ZIGBEE_slave_report_tx_pending = false;
  watch_report_tx_fail_streak = 0U;

  ZbReset(zigbee_app_info.zb);
  UTIL_SEQ_SetTask(1U << CFG_TASK_ZIGBEE_NETWORK_FORM, CFG_SCH_PRIO_0);
}

static enum ZbStartType APP_ZIGBEE_NextSlaveStartupControl(void)
{
  const enum ZbStartType startup = APP_ZIGBEE_slave_try_rejoin ? ZbStartTypeRejoin : ZbStartTypeJoin;

  APP_ZIGBEE_slave_try_rejoin = !APP_ZIGBEE_slave_try_rejoin;
  return startup;
}

bool APP_ZIGBEE_SendSlaveReport(const AppSlaveReport_t *report)
{
  struct ZbApsdeDataReqT req;
  enum ZbStatusCodeT status;

  if ((report == NULL) || !APP_ZIGBEE_IsReady() || APP_ZIGBEE_slave_report_tx_pending)
  {
    watch_report_tx_busy_count++;
    return false;
  }

  watch_report_tx_attempt_count++;
  APP_ZIGBEE_EncodeSlaveReport(report, APP_ZIGBEE_slave_report_payload);

  memset(&req, 0, sizeof(req));
  req.dst.mode = ZB_APSDE_ADDRMODE_SHORT;
  req.dst.nwkAddr = APP_ZIGBEE_COORDINATOR_NWK_ADDR;
  req.dst.endpoint = APP_ZIGBEE_ENDPOINT;
  req.profileId = ZCL_PROFILE_HOME_AUTOMATION;
  req.clusterId = APP_ZIGBEE_CLUSTER_INPUTS;
  req.srcEndpt = APP_ZIGBEE_ENDPOINT;
  req.asdu = APP_ZIGBEE_slave_report_payload;
  req.asduLength = sizeof(APP_ZIGBEE_slave_report_payload);
  req.txOptions = ZB_APSDE_DATAREQ_TXOPTIONS_SECURITY | ZB_APSDE_DATAREQ_TXOPTIONS_ACK;
  req.discoverRoute = true;
  req.radius = 0U;

  status = ZbApsdeDataReqCallback(zigbee_app_info.zb, &req, APP_ZIGBEE_SlaveReportConfCb, NULL);
  if (status == ZB_STATUS_SUCCESS)
  {
    APP_ZIGBEE_slave_report_tx_pending = true;
    watch_report_tx_ok_count++;
  }
  else
  {
    watch_report_tx_busy_count++;
    watch_report_tx_confirm_status = (uint32_t)status;
  }

  return (status == ZB_STATUS_SUCCESS);
}

static int APP_ZIGBEE_SlaveReportIndCb(struct ZbApsdeDataIndT *dataInd, void *cb_arg)
{
  AppSlaveReport_t report;

  (void)cb_arg;

  watch_report_rx_ind_count++;

  if (dataInd == NULL)
  {
    watch_report_rx_decode_fail_count++;
    return ZB_STATUS_SUCCESS;
  }

  watch_report_rx_last_cluster = dataInd->clusterId;
  watch_report_rx_last_length = dataInd->asduLength;

  if ((dataInd->clusterId != APP_ZIGBEE_CLUSTER_INPUTS) ||
      !APP_ZIGBEE_DecodeSlaveReport(dataInd->asdu, dataInd->asduLength, &report))
  {
    watch_report_rx_decode_fail_count++;
    return ZB_STATUS_SUCCESS;
  }

  ZigbeePort_OnSlaveReportReceived(&report, HAL_GetTick());
  return ZB_STATUS_SUCCESS;
}

static void APP_ZIGBEE_SlaveReportConfCb(struct ZbApsdeDataConfT *conf, void *arg)
{
  (void)arg;

  APP_ZIGBEE_slave_report_tx_pending = false;
  watch_report_tx_confirm_count++;

  if ((conf != NULL) && (conf->status != ZB_STATUS_SUCCESS))
  {
    APP_ZIGBEE_tx_fail_streak++;
    watch_report_tx_fail_streak = APP_ZIGBEE_tx_fail_streak;
    watch_report_tx_confirm_status = (uint32_t)conf->status;
    APP_DBG("Slave report tx status = 0x%02x", conf->status);
  }
  else if (conf != NULL)
  {
    APP_ZIGBEE_tx_fail_streak = 0U;
    watch_report_tx_fail_streak = 0U;
    watch_report_tx_confirm_status = (uint32_t)conf->status;
  }
}

static bool APP_ZIGBEE_DecodeSlaveReport(const uint8_t *payload, uint16_t length, AppSlaveReport_t *report)
{
  if ((payload == NULL) || (report == NULL) || (length < 8U))
  {
    return false;
  }

  report->sequence = ((uint32_t)payload[0]) |
                     ((uint32_t)payload[1] << 8) |
                     ((uint32_t)payload[2] << 16) |
                     ((uint32_t)payload[3] << 24);
  report->button_mask = payload[4];
  report->analog_raw = ((uint16_t)payload[5]) | ((uint16_t)payload[6] << 8);
  report->analog_percent = payload[7];

  return true;
}

static void APP_ZIGBEE_EncodeSlaveReport(const AppSlaveReport_t *report, uint8_t *payload)
{
  payload[0] = (uint8_t)(report->sequence);
  payload[1] = (uint8_t)(report->sequence >> 8);
  payload[2] = (uint8_t)(report->sequence >> 16);
  payload[3] = (uint8_t)(report->sequence >> 24);
  payload[4] = report->button_mask;
  payload[5] = (uint8_t)(report->analog_raw);
  payload[6] = (uint8_t)(report->analog_raw >> 8);
  payload[7] = report->analog_percent;
}

/* USER CODE END FD_LOCAL_FUNCTIONS */
