blob: fc02cf6b7b7c507eebb8227894e60bc38fd8f05b [file] [log] [blame]
/**
*
* Copyright (c) 2020 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
* Copyright (c) 2020 Silicon Labs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/***************************************************************************//**
* @file
* @brief Routines for the Poll Control Server plugin, which implement the
* server side of the Poll Control cluster. The Poll Control cluster
* provides a means to communicate with an end device with a sleep
* schedule.
*******************************************************************************
******************************************************************************/
#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];
static bool ignoreNonTrustCenter = false;
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,
const char * 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,
const char * 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) {
// If ignoreNonTrustCenter is true, then we only add
// the trust center as a client.
if (ignoreNonTrustCenter
&& (!isPollControlBindingTrustCenter(endpoint, bindingIndex))) {
emberAfPollControlClusterPrintln("Ignoring poll control client with bindingIndex %d in search of TC",
bindingIndex);
continue;
}
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;
}
}
void emberAfPluginPollControlServerSetIgnoreNonTrustCenter(bool ignoreNonTc)
{
ignoreNonTrustCenter = ignoreNonTc;
}
bool emberAfPluginPollControlServerGetIgnoreNonTrustCenter(void)
{
return ignoreNonTrustCenter;
}