3 * Copyright (c) 2020 Project CHIP Authors
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
20 * Copyright (c) 2020 Silicon Labs
22 * Licensed under the Apache License, Version 2.0 (the "License");
23 * you may not use this file except in compliance with the License.
24 * You may obtain a copy of the License at
26 * http://www.apache.org/licenses/LICENSE-2.0
28 * Unless required by applicable law or agreed to in writing, software
29 * distributed under the License is distributed on an "AS IS" BASIS,
30 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 * See the License for the specific language governing permissions and
32 * limitations under the License.
34 /****************************************************************************
36 * @brief This is the source for the plugin used to
37 *add an IAS Zone cluster server to a project. This
38 *source handles zone enrollment and storing of
39 * attributes from a CIE device, and provides an API
40 *for different plugins to post updated zone status
42 *******************************************************************************
43 ******************************************************************************/
45 // *****************************************************************************
46 // * ias-zone-server.c
50 // * Copyright 2015 Silicon Laboratories, Inc. *80*
51 // *****************************************************************************
53 #include "ias-zone-server.h"
54 #include <app/util/af-event.h>
55 #include <app/util/af.h>
56 #include <app/util/binding-table.h>
57 #include <system/SystemLayer.h>
59 #include "gen/att-storage.h"
60 #include "gen/attribute-id.h"
61 #include "gen/attribute-type.h"
62 #include "gen/callback.h"
63 #include "gen/cluster-id.h"
64 #include "gen/command-id.h"
68 #define UNDEFINED_ZONE_ID 0xFF
69 #define DELAY_TIMER_MS (1 * MILLISECOND_TICKS_PER_SECOND)
70 #define IAS_ZONE_SERVER_PAYLOAD_COMMAND_IDX 0x02
71 #define ZCL_FRAME_CONTROL_IDX 0x00
73 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
74 #if defined(EMBER_AF_PLUGIN_WWAH_APP_EVENT_RETRY_MANAGER)
75 #define NUM_QUEUE_ENTRIES EMBER_AF_PLUGIN_WWAH_APP_EVENT_RETRY_MANAGER_QUEUE_SIZE
77 #define NUM_QUEUE_ENTRIES EMBER_AF_PLUGIN_IAS_ZONE_SERVER_QUEUE_DEPTH
80 #define NUM_QUEUE_ENTRIES 0
83 #define DEFAULT_ENROLLMENT_METHOD EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_REQUEST
85 // TODO: Need to figure out what needs to happen wrt HAL tokens here, but for
86 // now define ESZP_HOST to disable it. See
87 // https://github.com/project-chip/connectedhomeip/issues/3275
95 } IasZoneStatusQueueEntry;
99 uint8_t entriesInQueue;
102 IasZoneStatusQueueEntry buffer[NUM_QUEUE_ENTRIES];
103 } IasZoneStatusQueue;
105 //-----------------------------------------------------------------------------
108 EmberEventControl emberAfPluginIasZoneServerManageQueueEventControl;
109 static EmberAfIasZoneEnrollmentMode enrollmentMethod;
111 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
112 IasZoneStatusQueue messageQueue;
114 // Status queue retry parameters
117 IasZoneStatusQueueRetryConfig config;
118 uint32_t currentBackoffTimeSec;
119 uint8_t currentRetryCount;
120 } IasZoneStatusQueueRetryParameters;
122 // Set up status queue retry parameters.
123 IasZoneStatusQueueRetryParameters queueRetryParams = {
124 .config = { .firstBackoffTimeSec = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_FIRST_BACKOFF_TIME_SEC,
125 .backoffSeqCommonRatio = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_BACKOFF_SEQUENCE_COMMON_RATIO,
126 .maxBackoffTimeSec = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MAX_BACKOFF_TIME_SEC,
127 #ifdef EMBER_AF_PLUGIN_IAS_ZONE_SERVER_UNLIMITED_RETRIES
128 .unlimitedRetries = true,
130 .unlimitedRetries = false,
132 .maxRetryAttempts = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MAX_RETRY_ATTEMPTS },
133 .currentBackoffTimeSec = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_FIRST_BACKOFF_TIME_SEC,
134 .currentRetryCount = 0,
137 static void resetCurrentQueueRetryParams(void)
139 queueRetryParams.currentRetryCount = 0;
140 queueRetryParams.currentBackoffTimeSec = queueRetryParams.config.firstBackoffTimeSec;
143 #endif // EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE
145 //-----------------------------------------------------------------------------
146 // Forward declarations
148 static void setZoneId(EndpointId endpoint, uint8_t zoneId);
149 static bool areZoneServerAttributesTokenized(EndpointId endpoint);
150 static bool isValidEnrollmentMode(EmberAfIasZoneEnrollmentMode method);
151 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
152 static uint16_t computeElapsedTimeQs(IasZoneStatusQueueEntry * entry);
153 static void bufferInit(IasZoneStatusQueue * ring);
154 static int16_t copyToBuffer(IasZoneStatusQueue * ring, const IasZoneStatusQueueEntry * entry);
155 static int16_t popFromBuffer(IasZoneStatusQueue * ring, IasZoneStatusQueueEntry * entry);
158 // TODO: https://github.com/project-chip/connectedhomeip/issues/3276 needs to be
159 // fixed to implement this for real.
160 EmberNetworkStatus emberAfNetworkState(void)
162 return EMBER_JOINED_NETWORK;
165 //-----------------------------------------------------------------------------
168 static EmberStatus sendToClient(EndpointId endpoint)
172 // If the device is not a network, there is no one to send to, so do nothing
173 if (emberAfNetworkState() != EMBER_JOINED_NETWORK)
175 return EMBER_NETWORK_DOWN;
178 // Remote endpoint need not be set, since it will be provided by the call to
179 // emberAfSendCommandUnicastToBindings()
180 emberAfSetCommandEndpoints(endpoint, 0);
182 // A binding table entry is created on Zone Enrollment for each endpoint, so
183 // a simple call to SendCommandUnicastToBinding will handle determining the
184 // destination endpoint, address, etc for us.
185 status = emberAfSendCommandUnicastToBindings();
187 if (EMBER_SUCCESS != status)
195 static void enrollWithClient(EndpointId endpoint)
198 emberAfFillExternalBuffer((ZCL_CLUSTER_SPECIFIC_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT), ZCL_IAS_ZONE_CLUSTER_ID,
199 ZCL_ZONE_ENROLL_REQUEST_COMMAND_ID, "vv", EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ZONE_TYPE,
200 EMBER_AF_MANUFACTURER_CODE);
201 status = sendToClient(endpoint);
202 if (status == EMBER_SUCCESS)
204 emberAfIasZoneClusterPrintln("Sent enroll request to IAS Zone client.");
208 emberAfIasZoneClusterPrintln("Error sending enroll request: 0x%x\n", status);
212 EmberAfStatus emberAfIasZoneClusterServerPreAttributeChangedCallback(EndpointId endpoint, AttributeId attributeId,
213 EmberAfAttributeType attributeType, uint8_t size,
218 EmberBindingTableEntry bindingEntry;
219 EmberBindingTableEntry currentBind;
221 uint8_t ieeeAddress[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
223 // If this is not a CIE Address write, the CIE address has already been
224 // written, or the IAS Zone server is already enrolled, do nothing.
225 if (attributeId != ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID || emberAfCurrentCommand() == NULL)
227 return EMBER_ZCL_STATUS_SUCCESS;
230 memcpy(&destNodeId, value, sizeof(NodeId));
232 // Create the binding table entry
234 // This code assumes that the endpoint and device that is setting the CIE
235 // address is the CIE device itself, and as such the remote endpoint to bind
236 // to is the endpoint that generated the attribute change. This
237 // assumption is made based on analysis of the behavior of CIE devices
238 // currently existing in the field.
239 bindingEntry.type = EMBER_UNICAST_BINDING;
240 bindingEntry.local = endpoint;
241 bindingEntry.clusterId = ZCL_IAS_ZONE_CLUSTER_ID;
242 bindingEntry.remote = emberAfCurrentCommand()->apsFrame->sourceEndpoint;
243 bindingEntry.nodeId = destNodeId;
245 // Cycle through the binding table until we find a valid entry that is not
246 // being used, then use the created entry to make the bind.
247 for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++)
249 if (emberGetBinding(i, ¤tBind) != EMBER_SUCCESS)
251 // break out of the loop to ensure that an error message still prints
254 if (currentBind.type != EMBER_UNUSED_BINDING)
256 // If the binding table entry created based on the response already exists
258 if ((currentBind.local == bindingEntry.local) && (currentBind.clusterId == bindingEntry.clusterId) &&
259 (currentBind.remote == bindingEntry.remote) && (currentBind.type == bindingEntry.type))
263 // If this spot in the binding table already exists, move on to the next
268 emberSetBinding(i, &bindingEntry);
274 emberAfReadServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID, (uint8_t *) ieeeAddress, 8);
275 for (i = 0; i < 8; i++)
277 if (ieeeAddress[i] != 0)
282 emberAfAppPrint("\nzero address: %d\n", zeroAddress);
284 if ((zeroAddress == true) && (enrollmentMethod == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_REQUEST))
286 // Only send the enrollment request if the mode is AUTO-ENROLL-REQUEST.
287 // We need to delay to get around a bug where we can't send a command
288 // at this point because then the Write Attributes response will not
289 // be sent. But we also delay to give the client time to configure us.
290 emberAfIasZoneClusterPrintln("Sending enrollment after %d ms", DELAY_TIMER_MS);
291 emberAfScheduleServerTickExtended(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, DELAY_TIMER_MS, EMBER_AF_SHORT_POLL,
292 EMBER_AF_STAY_AWAKE);
295 return EMBER_ZCL_STATUS_SUCCESS;
298 EmberAfStatus emberAfPluginIasZoneClusterSetEnrollmentMethod(EndpointId endpoint, EmberAfIasZoneEnrollmentMode method)
300 EmberAfStatus status;
302 if (emberAfIasZoneClusterAmIEnrolled(endpoint))
304 emberAfIasZoneClusterPrintln("Error: Already enrolled");
305 status = EMBER_ZCL_STATUS_NOT_AUTHORIZED;
307 else if (!isValidEnrollmentMode(method))
309 emberAfIasZoneClusterPrintln("Invalid IAS Zone Server Enrollment Mode: %d", method);
310 status = EMBER_ZCL_STATUS_INVALID_VALUE;
314 enrollmentMethod = method;
316 halCommonSetToken(TOKEN_PLUGIN_IAS_ZONE_SERVER_ENROLLMENT_METHOD, &enrollmentMethod);
318 emberAfIasZoneClusterPrintln("IAS Zone Server Enrollment Mode: %d", method);
319 status = EMBER_ZCL_STATUS_SUCCESS;
324 static bool isValidEnrollmentMode(EmberAfIasZoneEnrollmentMode method)
326 return ((method == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_TRIP_TO_PAIR) ||
327 (method == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_AUTO_ENROLLMENT_RESPONSE) ||
328 (method == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_REQUEST));
331 bool emberAfIasZoneClusterAmIEnrolled(EndpointId endpoint)
333 EmberAfIasZoneState zoneState = EMBER_ZCL_IAS_ZONE_STATE_NOT_ENROLLED; // Clear this out completely.
334 EmberAfStatus status;
336 emberAfReadServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_STATE_ATTRIBUTE_ID, (unsigned char *) &zoneState,
339 return (status == EMBER_ZCL_STATUS_SUCCESS && zoneState == EMBER_ZCL_IAS_ZONE_STATE_ENROLLED);
342 static void updateEnrollState(EndpointId endpoint, bool enrolled)
344 EmberAfIasZoneState zoneState = (enrolled ? EMBER_ZCL_IAS_ZONE_STATE_ENROLLED : EMBER_ZCL_IAS_ZONE_STATE_NOT_ENROLLED);
346 emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_STATE_ATTRIBUTE_ID, (uint8_t *) &zoneState,
347 ZCL_INT8U_ATTRIBUTE_TYPE);
348 emberAfIasZoneClusterPrintln("IAS Zone Server State: %pEnrolled", (enrolled ? "" : "NOT "));
351 bool emberAfIasZoneClusterZoneEnrollResponseCallback(uint8_t enrollResponseCode, uint8_t zoneId)
355 EmberAfStatus status;
357 endpoint = emberAfCurrentEndpoint();
358 status = emberAfReadServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_ID_ATTRIBUTE_ID, &epZoneId, sizeof(uint8_t));
359 if (status == EMBER_ZCL_STATUS_SUCCESS)
361 if (enrollResponseCode == EMBER_ZCL_IAS_ENROLL_RESPONSE_CODE_SUCCESS)
363 updateEnrollState(endpoint, true);
364 setZoneId(endpoint, zoneId);
368 updateEnrollState(endpoint, false);
369 setZoneId(endpoint, UNDEFINED_ZONE_ID);
375 emberAfAppPrintln("ERROR: IAS Zone Server unable to read zone ID attribute");
379 static EmberStatus sendZoneUpdate(uint16_t zoneStatus, uint16_t timeSinceStatusOccurredQs, EndpointId endpoint)
383 if (!emberAfIasZoneClusterAmIEnrolled(endpoint))
385 return EMBER_INVALID_CALL;
387 emberAfFillExternalBuffer((ZCL_CLUSTER_SPECIFIC_COMMAND | ZCL_FRAME_CONTROL_SERVER_TO_CLIENT), ZCL_IAS_ZONE_CLUSTER_ID,
388 ZCL_ZONE_STATUS_CHANGE_NOTIFICATION_COMMAND_ID, "vuuv", zoneStatus,
389 0 /*extended status, must be zero per spec*/, emberAfPluginIasZoneServerGetZoneId(endpoint),
390 timeSinceStatusOccurredQs /* called "delay" in the spec */);
391 status = sendToClient(endpoint);
396 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
397 static void addNewEntryToQueue(const IasZoneStatusQueueEntry * newEntry)
399 emberAfIasZoneClusterPrintln("Adding new entry to queue");
400 copyToBuffer(&messageQueue, newEntry);
404 EmberStatus emberAfPluginIasZoneServerUpdateZoneStatus(EndpointId endpoint, uint16_t newStatus, uint16_t timeSinceStatusOccurredQs)
406 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
407 IasZoneStatusQueueEntry newBufferEntry;
408 newBufferEntry.endpoint = endpoint;
409 newBufferEntry.status = newStatus;
410 newBufferEntry.eventTimeMs = System::Layer::GetClock_MonotonicMS();
412 EmberStatus sendStatus;
414 emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_STATUS_ATTRIBUTE_ID, (uint8_t *) &newStatus,
415 ZCL_INT16U_ATTRIBUTE_TYPE);
417 if (enrollmentMethod == EMBER_ZCL_IAS_ZONE_ENROLLMENT_MODE_TRIP_TO_PAIR)
419 // If unenrolled, send Zone Enroll Request command.
420 if (!emberAfIasZoneClusterAmIEnrolled(endpoint))
422 emberAfScheduleServerTick(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, DELAY_TIMER_MS);
423 // Don't send the zone status update since not enrolled.
424 return EMBER_SUCCESS;
428 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
429 // If there are items in the queue waiting to send, this event should not
430 // be transmitted, as that could cause the client to receive the events out
431 // of order. Instead, just add the device to the queue
432 if (messageQueue.entriesInQueue == 0)
434 sendStatus = sendZoneUpdate(newStatus, timeSinceStatusOccurredQs, endpoint);
438 // Add a new element to the status queue and depending on the network state
439 // either try to resend the first element in the queue immediately or try to
440 // restart the parent research pattern.
441 addNewEntryToQueue(&newBufferEntry);
443 EmberNetworkStatus networkState = emberAfNetworkState();
445 if (networkState == EMBER_JOINED_NETWORK_NO_PARENT)
447 emberAfStartMoveCallback();
449 else if (networkState == EMBER_JOINED_NETWORK)
451 resetCurrentQueueRetryParams();
452 emberEventControlSetActive(&emberAfPluginIasZoneServerManageQueueEventControl);
455 return EMBER_SUCCESS;
459 sendStatus = sendZoneUpdate(newStatus, timeSinceStatusOccurredQs, endpoint);
462 if (sendStatus == EMBER_SUCCESS)
464 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
465 // Add a new entry to the zoneUpdate buffer
466 addNewEntryToQueue(&newBufferEntry);
471 // If we're not on a network and never were, we don't need to do anything.
472 // If we used to be on a network and can't talk to our parent, we should
473 // try to rejoin the network and add the message to the queue
474 if (emberAfNetworkState() == EMBER_JOINED_NETWORK_NO_PARENT)
476 emberAfStartMoveCallback();
477 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
478 // Add a new entry to the zoneUpdate buffer
479 addNewEntryToQueue(&newBufferEntry);
482 emberAfIasZoneClusterPrintln("Failed to send IAS Zone update. Err 0x%x", sendStatus);
487 void emberAfPluginIasZoneServerManageQueueEventHandler(void)
489 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
490 IasZoneStatusQueueEntry * bufferStart;
492 uint16_t elapsedTimeQs;
493 uint16_t airTimeRemainingMs;
495 // If the queue was emptied without our interaction, do nothing
496 if (messageQueue.entriesInQueue == 0)
498 emberEventControlSetInactive(&emberAfPluginIasZoneServerManageQueueEventControl);
502 // Otherwise, pull out the first item and attempt to retransmit it. The
503 // message complete callback will handle removing items from the queue
505 // To prevent an activity storm from flooding with retry requests, only
506 // re-send a message if it's been at least
507 // EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MIN_OTA_TIME_MS since it was sent.
508 bufferStart = &(messageQueue.buffer[messageQueue.startIdx]);
509 elapsedTimeQs = computeElapsedTimeQs(bufferStart);
511 if (elapsedTimeQs < (EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MIN_OTA_TIME_MS / (MILLISECOND_TICKS_PER_SECOND / 4)))
513 airTimeRemainingMs = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MIN_OTA_TIME_MS - (elapsedTimeQs * MILLISECOND_TICKS_PER_SECOND / 4);
514 emberAfIasZoneClusterPrintln("Not enough time passed for a retry, sleeping %d more mS", airTimeRemainingMs);
515 emberEventControlSetDelayMS(emberAfPluginIasZoneServerManageQueueEventControl, airTimeRemainingMs);
519 status = bufferStart->status;
520 emberAfIasZoneClusterPrintln("Attempting to resend a queued zone status update (status: 0x%02X, "
521 "event time (s): %d) with time of %d. Retry count: %d",
522 bufferStart->status, bufferStart->eventTimeMs / MILLISECOND_TICKS_PER_SECOND, elapsedTimeQs,
523 queueRetryParams.currentRetryCount);
524 sendZoneUpdate(status, elapsedTimeQs, bufferStart->endpoint);
525 emberEventControlSetInactive(&emberAfPluginIasZoneServerManageQueueEventControl);
528 emberEventControlSetInactive(&emberAfPluginIasZoneServerManageQueueEventControl);
532 void emberAfIasZoneClusterServerInitCallback(EndpointId endpoint)
534 EmberAfIasZoneType zoneType;
535 if (!areZoneServerAttributesTokenized(endpoint))
537 emberAfAppPrint("WARNING: ATTRIBUTES ARE NOT BEING STORED IN FLASH! ");
538 emberAfAppPrintln("DEVICE WILL NOT FUNCTION PROPERLY AFTER REBOOTING!!");
542 halCommonGetToken(&enrollmentMethod, TOKEN_PLUGIN_IAS_ZONE_SERVER_ENROLLMENT_METHOD);
544 enrollmentMethod = DEFAULT_ENROLLMENT_METHOD;
546 if (!isValidEnrollmentMode(enrollmentMethod))
548 // Default Enrollment Method to AUTO-ENROLL-REQUEST.
549 enrollmentMethod = DEFAULT_ENROLLMENT_METHOD;
552 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
553 bufferInit(&messageQueue);
556 zoneType = (EmberAfIasZoneType) EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ZONE_TYPE;
557 emberAfWriteAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_TYPE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER, (uint8_t *) &zoneType,
558 ZCL_INT16U_ATTRIBUTE_TYPE);
560 emberAfPluginIasZoneServerUpdateZoneStatus(endpoint,
561 0, // status: All alarms cleared
562 0); // time since status occurred
565 void emberAfIasZoneClusterServerTickCallback(EndpointId endpoint)
567 enrollWithClient(endpoint);
570 uint8_t emberAfPluginIasZoneServerGetZoneId(EndpointId endpoint)
572 uint8_t zoneId = UNDEFINED_ZONE_ID;
573 emberAfReadServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_ID_ATTRIBUTE_ID, &zoneId,
574 emberAfGetDataSize(ZCL_INT8U_ATTRIBUTE_TYPE));
578 //------------------------------------------------------------------------------
580 // This function will verify that all attributes necessary for the IAS zone
581 // server to properly retain functionality through a power failure are
584 //------------------------------------------------------------------------------
585 static bool areZoneServerAttributesTokenized(EndpointId endpoint)
587 EmberAfAttributeMetadata * metadata;
589 metadata = emberAfLocateAttributeMetadata(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID,
590 CLUSTER_MASK_SERVER, EMBER_AF_NULL_MANUFACTURER_CODE);
591 if (!emberAfAttributeIsTokenized(metadata))
596 metadata = emberAfLocateAttributeMetadata(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_STATE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
597 EMBER_AF_NULL_MANUFACTURER_CODE);
598 if (!emberAfAttributeIsTokenized(metadata))
603 metadata = emberAfLocateAttributeMetadata(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_TYPE_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
604 EMBER_AF_NULL_MANUFACTURER_CODE);
605 if (!emberAfAttributeIsTokenized(metadata))
610 metadata = emberAfLocateAttributeMetadata(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_ID_ATTRIBUTE_ID, CLUSTER_MASK_SERVER,
611 EMBER_AF_NULL_MANUFACTURER_CODE);
612 if (!emberAfAttributeIsTokenized(metadata))
620 static void setZoneId(EndpointId endpoint, uint8_t zoneId)
622 emberAfIasZoneClusterPrintln("IAS Zone Server Zone ID: 0x%X", zoneId);
623 emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_ID_ATTRIBUTE_ID, &zoneId, ZCL_INT8U_ATTRIBUTE_TYPE);
626 static void unenrollSecurityDevice(EndpointId endpoint)
628 uint8_t ieeeAddress[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
629 uint16_t zoneType = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ZONE_TYPE;
631 emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID, (uint8_t *) ieeeAddress,
632 ZCL_IEEE_ADDRESS_ATTRIBUTE_TYPE);
634 emberAfWriteServerAttribute(endpoint, ZCL_IAS_ZONE_CLUSTER_ID, ZCL_ZONE_TYPE_ATTRIBUTE_ID, (uint8_t *) &zoneType,
635 ZCL_INT16U_ATTRIBUTE_TYPE);
637 setZoneId(endpoint, UNDEFINED_ZONE_ID);
638 // Restore the enrollment method back to its default value.
639 emberAfPluginIasZoneClusterSetEnrollmentMethod(endpoint, DEFAULT_ENROLLMENT_METHOD);
640 updateEnrollState(endpoint, false); // enrolled?
643 // If you leave the network, unenroll yourself.
644 void emberAfPluginIasZoneServerStackStatusCallback(EmberStatus status)
647 uint8_t networkIndex;
650 // If the device has left the network, unenroll all endpoints on the device
651 // that are servers of the IAS Zone Cluster
652 if (status == EMBER_NETWORK_DOWN && emberAfNetworkState() == EMBER_NO_NETWORK)
654 for (i = 0; i < emberAfEndpointCount(); i++)
656 endpoint = emberAfEndpointFromIndex(i);
657 networkIndex = emberAfNetworkIndexFromEndpointIndex(i);
658 if (networkIndex == 0 /* emberGetCurrentNetwork() */ && emberAfContainsServer(endpoint, ZCL_IAS_ZONE_CLUSTER_ID))
660 unenrollSecurityDevice(endpoint);
664 else if (status == EMBER_NETWORK_UP)
666 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
667 // If we're reconnecting, send any items still in the queue
668 emberAfIasZoneClusterPrintln("Rejoined network, retransmiting any queued event");
669 emberEventControlSetActive(&emberAfPluginIasZoneServerManageQueueEventControl);
674 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
675 EmberStatus emberAfIasZoneServerConfigStatusQueueRetryParams(IasZoneStatusQueueRetryConfig * retryConfig)
677 if (!(retryConfig->firstBackoffTimeSec) || (!retryConfig->backoffSeqCommonRatio) ||
678 (retryConfig->maxBackoffTimeSec < retryConfig->firstBackoffTimeSec) ||
679 (retryConfig->maxBackoffTimeSec > IAS_ZONE_STATUS_QUEUE_RETRY_ABS_MAX_BACKOFF_TIME_SEC) || (!retryConfig->maxRetryAttempts))
681 return EMBER_BAD_ARGUMENT;
684 queueRetryParams.config.firstBackoffTimeSec = retryConfig->firstBackoffTimeSec;
685 queueRetryParams.config.backoffSeqCommonRatio = retryConfig->backoffSeqCommonRatio;
686 queueRetryParams.config.maxBackoffTimeSec = retryConfig->maxBackoffTimeSec;
687 queueRetryParams.config.unlimitedRetries = retryConfig->unlimitedRetries;
688 queueRetryParams.config.maxRetryAttempts = retryConfig->maxRetryAttempts;
690 queueRetryParams.currentBackoffTimeSec = retryConfig->firstBackoffTimeSec;
691 queueRetryParams.currentRetryCount = 0;
693 return EMBER_SUCCESS;
696 void emberAfIasZoneServerSetStatusQueueRetryParamsToDefault(void)
698 queueRetryParams.config.firstBackoffTimeSec = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_FIRST_BACKOFF_TIME_SEC;
699 queueRetryParams.config.backoffSeqCommonRatio = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_BACKOFF_SEQUENCE_COMMON_RATIO;
700 queueRetryParams.config.maxBackoffTimeSec = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MAX_BACKOFF_TIME_SEC;
701 #ifdef EMBER_AF_PLUGIN_IAS_ZONE_SERVER_UNLIMITED_RETRIES
702 queueRetryParams.config.unlimitedRetries = true;
704 queueRetryParams.config.unlimitedRetries = false;
706 queueRetryParams.config.maxRetryAttempts = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_MAX_RETRY_ATTEMPTS;
708 queueRetryParams.currentBackoffTimeSec = EMBER_AF_PLUGIN_IAS_ZONE_SERVER_FIRST_BACKOFF_TIME_SEC;
709 queueRetryParams.currentRetryCount = 0;
712 void emberAfIasZoneServerDiscardPendingEventsInStatusQueue(void)
714 emberEventControlSetInactive(&emberAfPluginIasZoneServerManageQueueEventControl);
715 bufferInit(&messageQueue);
716 resetCurrentQueueRetryParams();
719 #if defined(EMBER_AF_PLUGIN_WWAH_APP_EVENT_RETRY_MANAGER)
720 EmberStatus emberAfWwahAppEventRetryManagerConfigBackoffParamsCallback(uint8_t firstBackoffTimeSeconds,
721 uint8_t backoffSeqCommonRatio,
722 uint32_t maxBackoffTimeSeconds,
723 uint8_t maxRedeliveryAttempts)
725 IasZoneStatusQueueRetryConfig retryConfig = { firstBackoffTimeSeconds, backoffSeqCommonRatio, maxBackoffTimeSeconds,
726 (maxRedeliveryAttempts == 0xFF), maxRedeliveryAttempts };
728 // Setting up retry parameters
729 return emberAfIasZoneServerConfigStatusQueueRetryParams(&retryConfig);
732 void emberAfWwahAppEventRetryManagerSetBackoffParamsToDefault(void)
734 emberAfIasZoneServerSetStatusQueueRetryParamsToDefault();
736 #endif // defined(EMBER_AF_PLUGIN_WWAH_APP_EVENT_RETRY_MANAGER)
738 void emberAfPluginIasZoneServerPrintQueue(void)
740 emberAfIasZoneClusterPrintln("%d/%d entries", messageQueue.entriesInQueue, NUM_QUEUE_ENTRIES);
741 for (int i = 0; i < messageQueue.entriesInQueue; i++)
743 emberAfIasZoneClusterPrintln("Entry %d: Endpoint: %d Status: %d EventTimeMs: %d", i, messageQueue.buffer[i].endpoint,
744 messageQueue.buffer[i].status, messageQueue.buffer[i].eventTimeMs);
748 void emberAfPluginIasZoneServerPrintQueueConfig(void)
750 emberAfCorePrintln("First backoff time (sec): %d", queueRetryParams.config.firstBackoffTimeSec);
751 emberAfCorePrintln("Backoff sequence common ratio: %d", queueRetryParams.config.backoffSeqCommonRatio);
752 emberAfCorePrintln("Max backoff time (sec): %d", queueRetryParams.config.maxBackoffTimeSec);
753 emberAfCorePrintln("Max redelivery attempts: %d", queueRetryParams.config.maxRetryAttempts);
756 #endif // defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
758 // This callback will be generated any time the node receives an ACK or a NAK
759 // for a message transmitted for the IAS Zone Cluster Server. Note that this
760 // will not be called in the case that the message was not delivered to the
761 // destination when the destination is the only router the node is joined to.
762 // In that case, the command will never have been sent, as the device will have
763 // had no router by which to send the command.
764 void emberAfIasZoneClusterServerMessageSentCallback(EmberOutgoingMessageType type, uint64_t indexOrDestination,
765 EmberApsFrame * apsFrame, uint16_t msgLen, uint8_t * message,
768 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
769 uint8_t frameControl;
772 IasZoneStatusQueueEntry dummyEntry;
774 // Verify that this response is for a ZoneStatusChangeNotification command
775 // by checking the message length, the command and direction bits of the
776 // Frame Control byte, and the command ID
777 if (msgLen < IAS_ZONE_SERVER_PAYLOAD_COMMAND_IDX)
782 frameControl = message[ZCL_FRAME_CONTROL_IDX];
783 if (!(frameControl & ZCL_CLUSTER_SPECIFIC_COMMAND) || !(frameControl & ZCL_FRAME_CONTROL_SERVER_TO_CLIENT))
788 commandId = message[IAS_ZONE_SERVER_PAYLOAD_COMMAND_IDX];
789 if (commandId != ZCL_ZONE_STATUS_CHANGE_NOTIFICATION_COMMAND_ID)
794 // If a change status change notification command is not received by the
795 // client, delay the option specified amount of time and try to resend it.
796 // The event handler will perform the retransmit per the preset queue retry
797 // parameteres, and the original send request will handle populating the buffer.
798 // Do not try to retransmit again if the maximum number of retries attempts
799 // is reached, this is however discarded if configured for unlimited retries.
800 if ((status == EMBER_DELIVERY_FAILED) &&
801 (queueRetryParams.config.unlimitedRetries ||
802 (queueRetryParams.currentRetryCount < queueRetryParams.config.maxRetryAttempts)))
804 queueRetryParams.currentRetryCount++;
806 emberAfIasZoneClusterPrintln("Status command update failed to send... Retrying in %d seconds...",
807 queueRetryParams.currentBackoffTimeSec);
809 // Delay according to the current retransmit backoff time.
810 emberEventControlSetDelayMS(emberAfPluginIasZoneServerManageQueueEventControl,
811 queueRetryParams.currentBackoffTimeSec * MILLISECOND_TICKS_PER_SECOND);
813 // The backoff time needs to be increased if the maximum backoff time is not reached yet.
814 if ((queueRetryParams.currentBackoffTimeSec * queueRetryParams.config.backoffSeqCommonRatio) <=
815 queueRetryParams.config.maxBackoffTimeSec)
817 queueRetryParams.currentBackoffTimeSec *= queueRetryParams.config.backoffSeqCommonRatio;
822 // If a command message was sent or max redelivery attempts were reached,
823 // remove it from the queue and move on to the next queued message until the queue is empty.
824 if (status == EMBER_SUCCESS)
826 emberAfIasZoneClusterPrintln("\nZone update successful, remove entry from queue");
830 emberAfIasZoneClusterPrintln("\nZone update unsuccessful, max retry attempts reached, remove entry from queue");
832 popFromBuffer(&messageQueue, &dummyEntry);
834 // Reset queue retry parameters.
835 resetCurrentQueueRetryParams();
837 if (messageQueue.entriesInQueue)
839 emberEventControlSetActive(&emberAfPluginIasZoneServerManageQueueEventControl);
845 #if defined(EMBER_AF_PLUGIN_IAS_ZONE_SERVER_ENABLE_QUEUE)
846 static void bufferInit(IasZoneStatusQueue * ring)
848 ring->entriesInQueue = 0;
850 ring->lastIdx = NUM_QUEUE_ENTRIES - 1;
853 // Add the entry to the buffer by copying, returning the index at which it was
854 // added. If the buffer is full, return -1, but still copy the entry over the
855 // last item of the buffer, to ensure that the last item in the buffer is
856 // always representative of the last known device state.
857 static int16_t copyToBuffer(IasZoneStatusQueue * ring, const IasZoneStatusQueueEntry * entry)
859 if (ring->entriesInQueue == NUM_QUEUE_ENTRIES)
861 ring->buffer[ring->lastIdx] = *entry;
865 // Increment the last pointer. If it rolls over the size, circle it back to
868 if (ring->lastIdx >= NUM_QUEUE_ENTRIES)
873 ring->buffer[ring->lastIdx].endpoint = entry->endpoint;
874 ring->buffer[ring->lastIdx].status = entry->status;
875 ring->buffer[ring->lastIdx].eventTimeMs = entry->eventTimeMs;
877 ring->entriesInQueue++;
878 return ring->lastIdx;
881 // Return the idx of the popped entry, or -1 if the buffer was empty.
882 static int16_t popFromBuffer(IasZoneStatusQueue * ring, IasZoneStatusQueueEntry * entry)
886 if (ring->entriesInQueue == 0)
891 // Copy out the first entry, then increment the start pointer. If it rolls
892 // over, circle it back to zero.
893 *entry = ring->buffer[ring->startIdx];
894 retVal = ring->startIdx;
897 if (ring->startIdx >= NUM_QUEUE_ENTRIES)
902 ring->entriesInQueue--;
907 uint16_t computeElapsedTimeQs(IasZoneStatusQueueEntry * entry)
909 uint32_t currentTimeMs = System::Layer::GetClock_MonotonicMS();
910 int64_t deltaTimeMs = currentTimeMs - entry->eventTimeMs;
914 deltaTimeMs = -deltaTimeMs + (0xFFFFFFFF - currentTimeMs);
917 return deltaTimeMs / MILLISECOND_TICKS_PER_QUARTERSECOND;