// *******************************************************************
// * poll-control-server.c
// *
// *
// * Copyright 2014 Silicon Laboratories, Inc.                              *80*
// *******************************************************************

#include "app/framework/include/af.h"

// This plugin does not synchronize attributes between endpoints and does not
// handle multi-network issues with regard to polling.  Because of this, it is
// limited to exactly one endpoint that implements the Poll Control cluster
// server.
#if EMBER_AF_POLL_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT != 1
  #error "The Poll Control Server plugin only supports one endpoint."
#endif

// The built-in cluster tick has hooks into the polling code and is therefore
// used to control both the temporary fast poll mode while waiting for
// CheckInResponse commands and the actual fast poll mode that follows one or
// more positive CheckInResponse commands.  The endpoint event is used to send
// the periodic CheckIn commands.
//
// When it is time to check in, a new fast poll period begins with the
// selection of clients.  The clients are determined by scanning the binding
// table for nodes bound to the local endpoint for the Poll Control server.
// A CheckIn command is sent to each client, up to the limit set in the plugin
// options.  After the CheckIn commands are sent, the plugin enters a temporary
// fast poll mode until either all clients send a CheckInResponse command or
// the check-in timeout expires.  If one or more clients requests fast polling,
// the plugin continues fast polling for the maximum requested duration.  If
// FastPollStop commands are received from any clients, the fast poll duration
// is adjusted so that it reflects the maximum duration requested by all active
// clients.  Once the requested duration for all clients is satisfied, fast
// polling ends.
//
// Note that if a required check in happens to coincide with an existing fast
// poll period, the current fast poll period is terminated, all existing
// clients are forgetten, and a new fast poll period begins with the selection
// of new clients and the sending of new CheckIn commands.

extern EmberEventControl emberAfPluginPollControlServerCheckInEndpointEventControls[];

typedef struct {
  uint8_t bindingIndex;
  uint16_t fastPollTimeoutQs;
} Client;
static Client clients[EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS];

enum {
  INITIAL = 0,
  WAITING = 1,
  POLLING = 2,
};
static uint8_t state = INITIAL;
static uint32_t fastPollStartTimeMs;

// Flags used to track trust center check in failures
static bool trustCenterCheckInRequestSent;
static bool trustCenterCheckInResponseReceived;
static uint8_t trustCenterCheckInFailureCount;

// The timeout option is in quarter seconds, but we use it in milliseconds.
#define CHECK_IN_TIMEOUT_DURATION_MS                             \
  (EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_CHECK_IN_RESPONSE_TIMEOUT \
   * MILLISECOND_TICKS_PER_QUARTERSECOND)
#define NULL_INDEX 0xFF

static EmberAfStatus readServerAttribute(uint8_t endpoint,
                                         EmberAfAttributeId attributeId,
                                         PGM_P name,
                                         uint8_t *data,
                                         uint8_t size)
{
  EmberAfStatus status = emberAfReadServerAttribute(endpoint,
                                                    ZCL_POLL_CONTROL_CLUSTER_ID,
                                                    attributeId,
                                                    data,
                                                    size);
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    emberAfPollControlClusterPrintln("ERR: %ping %p 0x%x", "read", name, status);
  }
  return status;
}

static EmberAfStatus writeServerAttribute(uint8_t endpoint,
                                          EmberAfAttributeId attributeId,
                                          PGM_P name,
                                          uint8_t *data,
                                          EmberAfAttributeType type)
{
  EmberAfStatus status = emberAfWriteServerAttribute(endpoint,
                                                     ZCL_POLL_CONTROL_CLUSTER_ID,
                                                     attributeId,
                                                     data,
                                                     type);
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    emberAfPollControlClusterPrintln("ERR: %ping %p 0x%x", "writ", name, status);
  }
  return status;
}

static EmberStatus scheduleServerTick(uint8_t endpoint, uint32_t delayMs)
{
  return emberAfScheduleServerTickExtended(endpoint,
                                           ZCL_POLL_CONTROL_CLUSTER_ID,
                                           delayMs,
                                           EMBER_AF_SHORT_POLL,
                                           EMBER_AF_OK_TO_SLEEP);
}

static EmberStatus deactivateServerTick(uint8_t endpoint)
{
  return emberAfDeactivateServerTick(endpoint, ZCL_POLL_CONTROL_CLUSTER_ID);
}

static void scheduleCheckIn(uint8_t endpoint)
{
  EmberAfStatus status;
  uint32_t checkInIntervalQs;
  status = readServerAttribute(endpoint,
                               ZCL_CHECK_IN_INTERVAL_ATTRIBUTE_ID,
                               "check in interval",
                               (uint8_t *)&checkInIntervalQs,
                               sizeof(checkInIntervalQs));
  if (status == EMBER_ZCL_STATUS_SUCCESS && checkInIntervalQs != 0) {
    emberAfEndpointEventControlSetDelayMS(emberAfPluginPollControlServerCheckInEndpointEventControls,
                                          endpoint,
                                          (checkInIntervalQs
                                           * MILLISECOND_TICKS_PER_QUARTERSECOND));
  } else {
    emberAfEndpointEventControlSetInactive(emberAfPluginPollControlServerCheckInEndpointEventControls,
                                           endpoint);
  }
}

static uint8_t findClientIndex(void)
{
  EmberBindingTableEntry incomingBinding;
  uint8_t incomingBindingIndex = emberAfGetBindingIndex();
  if (emberGetBinding(incomingBindingIndex, &incomingBinding)
      == EMBER_SUCCESS) {
    uint8_t clientIndex;
    for (clientIndex = 0;
         clientIndex < EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS;
         clientIndex++) {
      EmberBindingTableEntry clientBinding;
      if ((emberGetBinding(clients[clientIndex].bindingIndex, &clientBinding)
           == EMBER_SUCCESS)
          && incomingBinding.type == clientBinding.type
          && incomingBinding.local == clientBinding.local
          && incomingBinding.remote == clientBinding.remote
          && (MEMCOMPARE(incomingBinding.identifier,
                         clientBinding.identifier,
                         EUI64_SIZE)
              == 0)) {
        return clientIndex;
      }
    }
  }
  return NULL_INDEX;
}

static bool pendingCheckInResponses(void)
{
  uint8_t i;
  for (i = 0; i < EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS; i++) {
    if (clients[i].bindingIndex != NULL_INDEX
        && clients[i].fastPollTimeoutQs == 0) {
      return true;
    }
  }
  return false;
}

static bool isPollControlBindingTrustCenter(uint8_t endpoint, uint8_t bindingIndex)
{
  EmberBindingTableEntry binding;
  if (emberGetBinding(bindingIndex, &binding) == EMBER_SUCCESS
      && binding.type == EMBER_UNICAST_BINDING
      && binding.local == endpoint
      && binding.clusterId == ZCL_POLL_CONTROL_CLUSTER_ID) {
    EmberEUI64 trustCenterEui64;
    if (emberLookupEui64ByNodeId(EMBER_TRUST_CENTER_NODE_ID, trustCenterEui64) != EMBER_SUCCESS) {
      return false;
    }

    if (0 == MEMCOMPARE(binding.identifier, trustCenterEui64, EUI64_SIZE)) {
      return true;
    }
  }
  return false;
}

static bool outstandingFastPollRequests(uint8_t endpoint)
{
  uint32_t currentTimeMs = halCommonGetInt32uMillisecondTick();
  uint32_t elapsedFastPollTimeMs = elapsedTimeInt32u(fastPollStartTimeMs,
                                                     currentTimeMs);
  uint16_t fastPollTimeoutQs = 0;
  uint8_t i;
  for (i = 0; i < EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS; i++) {
    if (clients[i].bindingIndex != NULL_INDEX) {
      if (clients[i].fastPollTimeoutQs * MILLISECOND_TICKS_PER_QUARTERSECOND
          < elapsedFastPollTimeMs) {
        clients[i].bindingIndex = NULL_INDEX;
      } else if (fastPollTimeoutQs < clients[i].fastPollTimeoutQs) {
        fastPollTimeoutQs = clients[i].fastPollTimeoutQs;
      }
    }
  }

  if (fastPollTimeoutQs == 0) {
    return false;
  } else {
    uint32_t newFastPollEndTimeMs = (fastPollStartTimeMs
                                     + (fastPollTimeoutQs
                                        * MILLISECOND_TICKS_PER_QUARTERSECOND));
    uint32_t remainingFastPollTimeMs = elapsedTimeInt32u(currentTimeMs,
                                                         newFastPollEndTimeMs);
    scheduleServerTick(endpoint, remainingFastPollTimeMs);
    return true;
  }
}

static EmberAfStatus validateCheckInInterval(uint8_t endpoint,
                                             uint32_t newCheckInIntervalQs)
{
  EmberAfStatus status;
  uint32_t longPollIntervalQs;

  if (newCheckInIntervalQs == 0) {
    return EMBER_ZCL_STATUS_SUCCESS;
  }

  status = readServerAttribute(endpoint,
                               ZCL_LONG_POLL_INTERVAL_ATTRIBUTE_ID,
                               "long poll interval",
                               (uint8_t *)&longPollIntervalQs,
                               sizeof(longPollIntervalQs));
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    return status;
  } else if (newCheckInIntervalQs < longPollIntervalQs) {
    return EMBER_ZCL_STATUS_INVALID_VALUE;
  }

#ifdef ZCL_USING_POLL_CONTROL_CLUSTER_CHECK_IN_INTERVAL_MIN_ATTRIBUTE
  {
    uint32_t checkInIntervalMinQs;
    status = readServerAttribute(endpoint,
                                 ZCL_CHECK_IN_INTERVAL_MIN_ATTRIBUTE_ID,
                                 "check in interval min",
                                 (uint8_t *)&checkInIntervalMinQs,
                                 sizeof(checkInIntervalMinQs));
    if (status != EMBER_ZCL_STATUS_SUCCESS) {
      return status;
    } else if (newCheckInIntervalQs < checkInIntervalMinQs) {
      return EMBER_ZCL_STATUS_INVALID_VALUE;
    }
  }
#endif

  return EMBER_ZCL_STATUS_SUCCESS;
}

static EmberAfStatus validateLongPollInterval(uint8_t endpoint,
                                              uint32_t newLongPollIntervalQs)
{
  EmberAfStatus status;
  uint32_t checkInIntervalQs;
  uint16_t shortPollIntervalQs;

  status = readServerAttribute(endpoint,
                               ZCL_CHECK_IN_INTERVAL_ATTRIBUTE_ID,
                               "check in interval",
                               (uint8_t *)&checkInIntervalQs,
                               sizeof(checkInIntervalQs));
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    return status;
  } else if (checkInIntervalQs < newLongPollIntervalQs) {
    return EMBER_ZCL_STATUS_INVALID_VALUE;
  }

  status = readServerAttribute(endpoint,
                               ZCL_SHORT_POLL_INTERVAL_ATTRIBUTE_ID,
                               "short poll interval",
                               (uint8_t *)&shortPollIntervalQs,
                               sizeof(shortPollIntervalQs));
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    return status;
  } else if (newLongPollIntervalQs < shortPollIntervalQs) {
    return EMBER_ZCL_STATUS_INVALID_VALUE;
  }

#ifdef ZCL_USING_POLL_CONTROL_CLUSTER_LONG_POLL_INTERVAL_MIN_ATTRIBUTE
  {
    uint32_t longPollIntervalMinQs;
    status = readServerAttribute(endpoint,
                                 ZCL_LONG_POLL_INTERVAL_MIN_ATTRIBUTE_ID,
                                 "long poll interval min",
                                 (uint8_t *)&longPollIntervalMinQs,
                                 sizeof(longPollIntervalMinQs));
    if (status != EMBER_ZCL_STATUS_SUCCESS) {
      return status;
    } else if (newLongPollIntervalQs < longPollIntervalMinQs) {
      return EMBER_ZCL_STATUS_INVALID_VALUE;
    }
  }
#endif

  return EMBER_ZCL_STATUS_SUCCESS;
}

static EmberAfStatus validateShortPollInterval(uint8_t endpoint,
                                               uint16_t newShortPollIntervalQs)
{
  EmberAfStatus status;
  uint32_t longPollIntervalQs;
  status = readServerAttribute(endpoint,
                               ZCL_LONG_POLL_INTERVAL_ATTRIBUTE_ID,
                               "long poll interval",
                               (uint8_t *)&longPollIntervalQs,
                               sizeof(longPollIntervalQs));
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    return status;
  } else if (longPollIntervalQs < newShortPollIntervalQs) {
    return EMBER_ZCL_STATUS_INVALID_VALUE;
  }
  return EMBER_ZCL_STATUS_SUCCESS;
}

static EmberAfStatus validateFastPollInterval(uint8_t endpoint,
                                              uint16_t newFastPollIntervalQs)
{
#ifdef ZCL_USING_POLL_CONTROL_CLUSTER_FAST_POLL_TIMEOUT_MAX_ATTRIBUTE
  EmberAfStatus status;
  uint16_t fastPollTimeoutMaxQs;
  status = readServerAttribute(endpoint,
                               ZCL_FAST_POLL_TIMEOUT_MAX_ATTRIBUTE_ID,
                               "fast poll timeout max",
                               (uint8_t *)&fastPollTimeoutMaxQs,
                               sizeof(fastPollTimeoutMaxQs));
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    return status;
  } else if (fastPollTimeoutMaxQs < newFastPollIntervalQs) {
    return EMBER_ZCL_STATUS_INVALID_VALUE;
  }
#endif

  return EMBER_ZCL_STATUS_SUCCESS;
}

static EmberAfStatus validateCheckInIntervalMin(uint8_t endpoint,
                                                uint32_t newCheckInIntervalMinQs)
{
  EmberAfStatus status;
  uint32_t checkInIntervalQs;
  status = readServerAttribute(endpoint,
                               ZCL_CHECK_IN_INTERVAL_ATTRIBUTE_ID,
                               "check in interval",
                               (uint8_t *)&checkInIntervalQs,
                               sizeof(checkInIntervalQs));
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    return status;
  } else if (checkInIntervalQs < newCheckInIntervalMinQs) {
    return EMBER_ZCL_STATUS_INVALID_VALUE;
  }
  return EMBER_ZCL_STATUS_SUCCESS;
}

static EmberAfStatus validateLongPollIntervalMin(uint8_t endpoint,
                                                 uint32_t newLongPollIntervalMinQs)
{
  EmberAfStatus status;
  uint32_t longPollIntervalQs;
  status = readServerAttribute(endpoint,
                               ZCL_LONG_POLL_INTERVAL_ATTRIBUTE_ID,
                               "long poll interval",
                               (uint8_t *)&longPollIntervalQs,
                               sizeof(longPollIntervalQs));
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    return status;
  } else if (longPollIntervalQs < newLongPollIntervalMinQs) {
    return EMBER_ZCL_STATUS_INVALID_VALUE;
  }
  return EMBER_ZCL_STATUS_SUCCESS;
}

static EmberAfStatus validateFastPollTimeoutMax(uint8_t endpoint,
                                                uint16_t newFastPollTimeoutMaxQs)
{
  EmberAfStatus status;
  uint16_t fastPollTimeoutQs;
  status = readServerAttribute(endpoint,
                               ZCL_FAST_POLL_TIMEOUT_ATTRIBUTE_ID,
                               "fast poll timeout",
                               (uint8_t *)&fastPollTimeoutQs,
                               sizeof(fastPollTimeoutQs));
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    return status;
  } else if (newFastPollTimeoutMaxQs < fastPollTimeoutQs) {
    return EMBER_ZCL_STATUS_INVALID_VALUE;
  }
  return EMBER_ZCL_STATUS_SUCCESS;
}

void emberAfPollControlClusterServerInitCallback(uint8_t endpoint)
{
  uint32_t longPollIntervalQs;
  uint16_t shortPollIntervalQs;

  if (readServerAttribute(endpoint,
                          ZCL_LONG_POLL_INTERVAL_ATTRIBUTE_ID,
                          "long poll interval",
                          (uint8_t *)&longPollIntervalQs,
                          sizeof(longPollIntervalQs))
      == EMBER_ZCL_STATUS_SUCCESS) {
    emberAfSetLongPollIntervalQsCallback(longPollIntervalQs);
  }

  if (readServerAttribute(endpoint,
                          ZCL_SHORT_POLL_INTERVAL_ATTRIBUTE_ID,
                          "short poll interval",
                          (uint8_t *)&shortPollIntervalQs,
                          sizeof(shortPollIntervalQs))
      == EMBER_ZCL_STATUS_SUCCESS) {
    emberAfSetShortPollIntervalQsCallback(shortPollIntervalQs);
  }

  // TODO: Begin checking in after the network comes up instead of at startup.
  scheduleCheckIn(endpoint);
}

void emberAfPollControlClusterServerTickCallback(uint8_t endpoint)
{
  if (state == WAITING) {
    uint16_t fastPollTimeoutQs = 0;
    uint8_t i;

    if (trustCenterCheckInRequestSent && !trustCenterCheckInResponseReceived) {
      trustCenterCheckInFailureCount++;
      emberAfPollControlClusterPrintln("ERR: Poll control check in failure (%d of %d)",
                                       trustCenterCheckInFailureCount,
                                       EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_TRUST_CENTER_CHECK_IN_FAILURE_THRESHOLD);
      if (trustCenterCheckInFailureCount >= EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_TRUST_CENTER_CHECK_IN_FAILURE_THRESHOLD) {
        emberAfPollControlClusterPrintln("ERR: Trust Center check in failure threshold reached");
        emberAfPluginPollControlServerCheckInTimeoutCallback();
        trustCenterCheckInFailureCount = 0;
      }
    }

    for (i = 0; i < EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS; i++) {
      if (clients[i].bindingIndex != NULL_INDEX
          && fastPollTimeoutQs < clients[i].fastPollTimeoutQs) {
        fastPollTimeoutQs = clients[i].fastPollTimeoutQs;
      }
    }

    if (fastPollTimeoutQs != 0) {
      state = POLLING;
      fastPollStartTimeMs = halCommonGetInt32uMillisecondTick();
      scheduleServerTick(endpoint,
                         (fastPollTimeoutQs
                          * MILLISECOND_TICKS_PER_QUARTERSECOND));
      return;
    }
  }

  state = INITIAL;
  deactivateServerTick(endpoint);
}

void emberAfPluginPollControlServerCheckInEndpointEventHandler(uint8_t endpoint)
{
  uint8_t bindingIndex, clientIndex;

  trustCenterCheckInRequestSent = false;
  trustCenterCheckInResponseReceived = false;
  for (clientIndex = 0;
       clientIndex < EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS;
       clientIndex++) {
    clients[clientIndex].bindingIndex = EMBER_NULL_BINDING;
  }

  for (bindingIndex = 0, clientIndex = 0;
       (bindingIndex < EMBER_BINDING_TABLE_SIZE
        && clientIndex < EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS);
       bindingIndex++) {
    EmberBindingTableEntry binding;
    if (emberGetBinding(bindingIndex, &binding) == EMBER_SUCCESS
        && binding.type == EMBER_UNICAST_BINDING
        && binding.local == endpoint
        && binding.clusterId == ZCL_POLL_CONTROL_CLUSTER_ID) {
      emberAfFillCommandPollControlClusterCheckIn();
      if (emberAfSendCommandUnicast(EMBER_OUTGOING_VIA_BINDING, bindingIndex)
          == EMBER_SUCCESS) {
        clients[clientIndex].bindingIndex = bindingIndex;
        clients[clientIndex].fastPollTimeoutQs = 0;
        clientIndex++;
        if (isPollControlBindingTrustCenter(endpoint, bindingIndex)) {
          trustCenterCheckInRequestSent = true;
        }
      }
    }
  }

  if (clientIndex == 0) {
    state = INITIAL;
    deactivateServerTick(endpoint);
  } else {
    state = WAITING;
    scheduleServerTick(endpoint, CHECK_IN_TIMEOUT_DURATION_MS);
  }

  scheduleCheckIn(endpoint);
}

EmberAfStatus emberAfPollControlClusterServerPreAttributeChangedCallback(uint8_t endpoint,
                                                                         EmberAfAttributeId attributeId,
                                                                         EmberAfAttributeType attributeType,
                                                                         uint8_t size,
                                                                         uint8_t *value)
{
  switch (attributeId) {
    case ZCL_CHECK_IN_INTERVAL_ATTRIBUTE_ID:
    {
      uint32_t newCheckInIntervalQs;
      MEMMOVE(&newCheckInIntervalQs, value, size);
      return validateCheckInInterval(endpoint, newCheckInIntervalQs);
    }
    case ZCL_LONG_POLL_INTERVAL_ATTRIBUTE_ID:
    {
      uint32_t newLongPollIntervalQs;
      MEMMOVE(&newLongPollIntervalQs, value, size);
      return validateLongPollInterval(endpoint, newLongPollIntervalQs);
    }
    case ZCL_SHORT_POLL_INTERVAL_ATTRIBUTE_ID:
    {
      uint16_t newShortPollIntervalQs;
      MEMMOVE(&newShortPollIntervalQs, value, size);
      return validateShortPollInterval(endpoint, newShortPollIntervalQs);
    }
    case ZCL_FAST_POLL_TIMEOUT_ATTRIBUTE_ID:
    {
      uint16_t newFastPollIntervalQs;
      MEMMOVE(&newFastPollIntervalQs, value, size);
      return validateFastPollInterval(endpoint, newFastPollIntervalQs);
    }
    case ZCL_CHECK_IN_INTERVAL_MIN_ATTRIBUTE_ID:
    {
      uint32_t newCheckInIntervalMinQs;
      MEMMOVE(&newCheckInIntervalMinQs, value, size);
      return validateCheckInIntervalMin(endpoint, newCheckInIntervalMinQs);
    }
    case ZCL_LONG_POLL_INTERVAL_MIN_ATTRIBUTE_ID:
    {
      uint32_t newLongPollIntervalMinQs;
      MEMMOVE(&newLongPollIntervalMinQs, value, size);
      return validateLongPollIntervalMin(endpoint, newLongPollIntervalMinQs);
    }
    case ZCL_FAST_POLL_TIMEOUT_MAX_ATTRIBUTE_ID:
    {
      uint32_t newFastPollTimeoutMaxQs;
      MEMMOVE(&newFastPollTimeoutMaxQs, value, size);
      return validateFastPollTimeoutMax(endpoint, newFastPollTimeoutMaxQs);
    }
    default:
      return EMBER_ZCL_STATUS_SUCCESS;
  }
}

void emberAfPollControlClusterServerAttributeChangedCallback(uint8_t endpoint,
                                                             EmberAfAttributeId attributeId)
{
  switch (attributeId) {
    case ZCL_CHECK_IN_INTERVAL_ATTRIBUTE_ID:
      scheduleCheckIn(endpoint);
      break;
    case ZCL_LONG_POLL_INTERVAL_ATTRIBUTE_ID:
    {
      EmberAfStatus status;
      uint32_t longPollIntervalQs;
      status = readServerAttribute(endpoint,
                                   ZCL_LONG_POLL_INTERVAL_ATTRIBUTE_ID,
                                   "long poll interval",
                                   (uint8_t *)&longPollIntervalQs,
                                   sizeof(longPollIntervalQs));
      if (status == EMBER_ZCL_STATUS_SUCCESS) {
        emberAfSetLongPollIntervalQsCallback(longPollIntervalQs);
      }
      break;
    }
    case ZCL_SHORT_POLL_INTERVAL_ATTRIBUTE_ID:
    {
      EmberAfStatus status;
      uint16_t shortPollIntervalQs;
      status = readServerAttribute(endpoint,
                                   ZCL_SHORT_POLL_INTERVAL_ATTRIBUTE_ID,
                                   "short poll interval",
                                   (uint8_t *)&shortPollIntervalQs,
                                   sizeof(shortPollIntervalQs));
      if (status == EMBER_ZCL_STATUS_SUCCESS) {
        emberAfSetShortPollIntervalQsCallback(shortPollIntervalQs);
      }
      break;
    }
    case ZCL_FAST_POLL_TIMEOUT_ATTRIBUTE_ID:
    case ZCL_CHECK_IN_INTERVAL_MIN_ATTRIBUTE_ID:
    case ZCL_LONG_POLL_INTERVAL_MIN_ATTRIBUTE_ID:
    case ZCL_FAST_POLL_TIMEOUT_MAX_ATTRIBUTE_ID:
    default:
      break;
  }
}

bool emberAfPollControlClusterCheckInResponseCallback(uint8_t startFastPolling,
                                                      uint16_t fastPollTimeoutQs)
{
  EmberAfStatus status = EMBER_ZCL_STATUS_ACTION_DENIED;
  uint8_t clientIndex = findClientIndex();

  emberAfPollControlClusterPrintln("RX: CheckInResponse 0x%x, 0x%2x",
                                   startFastPolling,
                                   fastPollTimeoutQs);

  // clientIndex will always be less than EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS
  if (clientIndex != NULL_INDEX
      && clientIndex < EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS) {
    if (state == WAITING) {
      uint8_t endpoint = emberAfCurrentEndpoint();

      if (isPollControlBindingTrustCenter(endpoint, clients[clientIndex].bindingIndex)) {
        trustCenterCheckInResponseReceived = true;
        if (trustCenterCheckInFailureCount > 0) {
          emberAfPollControlClusterPrintln("Poll Control: trust center "
                                           "responding to checkins after %d"
                                           "failure%s",
                                           trustCenterCheckInFailureCount,
                                           trustCenterCheckInFailureCount == 1
                                           ? "" : "s");
        }
        trustCenterCheckInFailureCount = 0;
      }

      if (startFastPolling) {
        if (fastPollTimeoutQs == 0) {
          status = readServerAttribute(endpoint,
                                       ZCL_FAST_POLL_TIMEOUT_ATTRIBUTE_ID,
                                       "fast poll timeout",
                                       (uint8_t *)&fastPollTimeoutQs,
                                       sizeof(fastPollTimeoutQs));
        } else {
          status = validateFastPollInterval(endpoint, fastPollTimeoutQs);
        }
        if (status == EMBER_ZCL_STATUS_SUCCESS) {
          clients[clientIndex].fastPollTimeoutQs = fastPollTimeoutQs;
        } else {
          clients[clientIndex].bindingIndex = NULL_INDEX;
        }
      } else {
        status = EMBER_ZCL_STATUS_SUCCESS;
        clients[clientIndex].bindingIndex = NULL_INDEX;
      }

      // Calling the tick directly when in the waiting state will cause the
      // temporarily fast poll mode to stop and will begin the actual fast poll
      // mode if applicable.
      if (!pendingCheckInResponses()) {
        emberAfPollControlClusterServerTickCallback(endpoint);
      }
    } else {
      status = EMBER_ZCL_STATUS_TIMEOUT;
    }
  }

  emberAfSendImmediateDefaultResponse(status);
  return true;
}

bool emberAfPollControlClusterFastPollStopCallback(void)
{
  EmberAfStatus status = EMBER_ZCL_STATUS_ACTION_DENIED;
  uint8_t clientIndex = findClientIndex();

  emberAfPollControlClusterPrintln("RX: FastPollStop");

  // clientIndex will always be less than EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS
  if (clientIndex != NULL_INDEX
      && clientIndex < EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_MAX_CLIENTS) {
    if (state == POLLING) {
      uint8_t endpoint = emberAfCurrentEndpoint();
      status = EMBER_ZCL_STATUS_SUCCESS;
      clients[clientIndex].bindingIndex = NULL_INDEX;

      // Calling the tick directly in the polling state will cause the fast
      // poll mode to stop.
      if (!outstandingFastPollRequests(endpoint)) {
        emberAfPollControlClusterServerTickCallback(endpoint);
      }
    } else {
      status = EMBER_ZCL_STATUS_TIMEOUT;
    }
  }

  emberAfSendImmediateDefaultResponse(status);
  return true;
}

bool emberAfPollControlClusterSetLongPollIntervalCallback(uint32_t newLongPollIntervalQs)
{
#ifdef EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_ACCEPT_SET_LONG_POLL_INTERVAL_COMMAND
  EmberAfStatus status;
  uint8_t endpoint = emberAfCurrentEndpoint();

  emberAfPollControlClusterPrintln("RX: SetLongPollInterval 0x%4x",
                                   newLongPollIntervalQs);

  // Trying to write the attribute will trigger the PreAttributeChanged
  // callback, which will handle validation.  If the write is successful, the
  // AttributeChanged callback will fire, which will handle setting the new
  // long poll interval.
  status = writeServerAttribute(endpoint,
                                ZCL_LONG_POLL_INTERVAL_ATTRIBUTE_ID,
                                "long poll interval",
                                (uint8_t *)&newLongPollIntervalQs,
                                ZCL_INT32U_ATTRIBUTE_TYPE);

  emberAfSendImmediateDefaultResponse(status);
  return true;
#else
  return false;
#endif
}

bool emberAfPollControlClusterSetShortPollIntervalCallback(uint16_t newShortPollIntervalQs)
{
#ifdef EMBER_AF_PLUGIN_POLL_CONTROL_SERVER_ACCEPT_SET_SHORT_POLL_INTERVAL_COMMAND
  EmberAfStatus status;
  uint8_t endpoint = emberAfCurrentEndpoint();

  emberAfPollControlClusterPrintln("RX: SetShortPollInterval 0x%2x",
                                   newShortPollIntervalQs);

  // Trying to write the attribute will trigger the PreAttributeChanged
  // callback, which will handle validation.  If the write is successful, the
  // AttributeChanged callback will fire, which will handle setting the new
  // short poll interval.
  status = writeServerAttribute(endpoint,
                                ZCL_SHORT_POLL_INTERVAL_ATTRIBUTE_ID,
                                "short poll interval",
                                (uint8_t *)&newShortPollIntervalQs,
                                ZCL_INT16U_ATTRIBUTE_TYPE);

  emberAfSendImmediateDefaultResponse(status);
  return true;
#else
  return false;
#endif
}

void emAfPluginPollControlServerResetAttributesCallback(uint8_t endpointId)
{
  // EMAPPFWKV2-1437: when we reset our attributes, we need to re-sync with
  // the consumers of our attribute values. For example, the consumers of
  // emberAfSetLongPollIntervalQsCallback and
  // emberAfSetShortPollIntervalQsCallback will want to know that the poll
  // intervals might have changed. Therefore, we simply call the init function
  // to read the attribute values and notify interested parties.
  emberAfPollControlClusterServerInitCallback(endpointId);
}

void emberAfPluginPollControlServerStackStatusCallback(EmberStatus status)
{
  // Reset failure count if just joining network
  if (status == EMBER_NETWORK_UP) {
    trustCenterCheckInFailureCount = 0;
  }
}
