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 /****************************************************************************
38 * 1. Look for ZDO device announce notification.
39 * 2. Perform ZDO match descriptor on device.
40 * 3. If supports IAS Zone Server, Add that server
41 *to our known list. Write CIE Address.
42 * 4. Read CIE address, verify it is ours. This is
43 *done mostly because the test case requires it.
44 * 5. Read the IAS Zone Server attributes.
46 * 6. When we get an enroll request, give them our
48 * 7. When we get a notification, read their
51 * Improvements that could be made:
52 * Add support for multiple endpoints on server.
53 *Most often this is a legacy security system
54 *retrofitted with a single ZigBee radio. Therefore
55 * each sensor is on a different endpoint. Right
56 *now our client only handles a single endpoint per
59 * Integration with Poll Control. When the device
60 *boots we should configure its polling to make it
61 *possible to read/write its attributes.
63 * Update the emberAfIasZoneClientKnownServers list
64 *when we know a server un-enrolls. Right now, we
65 *don't have any way to tell when we don't need to
66 *keep track of a server anymore, i.e., when it
67 *un-enrolls. Therefore, we could potentially keep
68 *adding servers to our known list, and run out of
69 *room to add more. Fortunately, we have two things
71 * 1. Servers will most likely stay around in a
72 *network. It is unlikely that an IAS Zone Client in
73 *production will have to handle 254 different
75 * 2. If a server un-enrolls and then enrolls
76 *again, it will get the same Zone ID and have a spot
77 *in the list, since we store servers by long address.
78 *******************************************************************************
79 ******************************************************************************/
81 #include "ias-zone-client.h"
84 //-----------------------------------------------------------------------------
87 IasZoneDevice emberAfIasZoneClientKnownServers[EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES];
91 IAS_ZONE_CLIENT_STATE_NONE,
92 IAS_ZONE_CLIENT_STATE_DISCOVER_ENDPOINT,
93 IAS_ZONE_CLIENT_STATE_SET_CIE_ADDRESS,
94 IAS_ZONE_CLIENT_STATE_READ_CIE_ADDRESS,
95 IAS_ZONE_CLIENT_STATE_READ_ATTRIBUTES,
98 static IasZoneClientState iasZoneClientState = IAS_ZONE_CLIENT_STATE_NONE;
99 static uint8_t currentIndex = NO_INDEX;
100 static uint8_t myEndpoint = 0;
102 EmberEventControl emberAfPluginIasZoneClientStateMachineEventControl;
104 //-----------------------------------------------------------------------------
105 // Forward Declarations
107 void readIasZoneServerAttributes(EmberNodeId nodeId);
108 static void iasClientSaveCommand(void);
109 static void iasClientLoadCommand(void);
111 //-----------------------------------------------------------------------------
114 void emberAfIasZoneClusterClientInitCallback(EndpointId endpoint)
117 myEndpoint = endpoint;
118 iasClientLoadCommand();
121 void emAfClearServers(void)
123 MEMSET(emberAfIasZoneClientKnownServers, 0xFF, sizeof(IasZoneDevice) * EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES);
126 static void clearState(void)
129 iasZoneClientState = IAS_ZONE_CLIENT_STATE_NONE;
132 static void setServerZoneStatus(uint8_t serverIndex, uint16_t zoneStatus)
134 emberAfIasZoneClientKnownServers[serverIndex].zoneStatus = zoneStatus;
135 iasClientSaveCommand();
138 static void setServerIeee(uint8_t serverIndex, uint8_t * ieeeAddress)
140 MEMCOPY(emberAfIasZoneClientKnownServers[serverIndex].ieeeAddress, ieeeAddress, EUI64_SIZE);
141 iasClientSaveCommand();
144 static void clearServerIeee(uint8_t serverIndex)
146 MEMSET(emberAfIasZoneClientKnownServers[serverIndex].ieeeAddress, 0xFF, sizeof(IasZoneDevice));
147 iasClientSaveCommand();
150 static void setServerNodeId(uint8_t serverIndex, EmberNodeId nodeId)
152 emberAfIasZoneClientKnownServers[serverIndex].nodeId = nodeId;
155 static void clearServerNodeId(uint8_t serverIndex)
157 emberAfIasZoneClientKnownServers[serverIndex].nodeId = EMBER_NULL_NODE_ID;
160 static void setServerZoneState(uint8_t serverIndex, uint8_t zoneState)
162 emberAfIasZoneClientKnownServers[serverIndex].zoneState = zoneState;
163 iasClientSaveCommand();
166 static void setServerEndpoint(uint8_t serverIndex, EndpointId endpoint)
168 emberAfIasZoneClientKnownServers[serverIndex].endpoint = endpoint;
169 iasClientSaveCommand();
172 static void setServerZoneType(uint8_t serverIndex, uint16_t zoneType)
174 emberAfIasZoneClientKnownServers[serverIndex].zoneType = zoneType;
175 iasClientSaveCommand();
178 static void setServerZoneId(uint8_t serverIndex, uint16_t zoneId)
180 emberAfIasZoneClientKnownServers[serverIndex].zoneId = zoneId;
181 iasClientSaveCommand();
184 static void setCurrentIndex(uint8_t serverIndex)
186 currentIndex = serverIndex;
187 iasClientSaveCommand();
190 static void setIasZoneClientState(uint8_t clientState)
192 iasZoneClientState = clientState;
193 iasClientSaveCommand();
196 static void iasClientSaveCommand(void)
198 #if defined(EZSP_HOST) && !defined(EMBER_TEST) && defined(UNIX_HOST)
202 // save zone server list
203 fp = fopen("iaszone.txt", "w");
205 for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
207 if (emberAfIasZoneClientKnownServers[i].zoneId != 0xFF)
209 fprintf(fp, "%x %x %x %x %x ", emberAfIasZoneClientKnownServers[i].zoneId,
210 emberAfIasZoneClientKnownServers[i].zoneStatus, emberAfIasZoneClientKnownServers[i].zoneState,
211 emberAfIasZoneClientKnownServers[i].endpoint, emberAfIasZoneClientKnownServers[i].zoneType);
212 for (j = 0; j < 8; j++)
214 fprintf(fp, "%x ", emberAfIasZoneClientKnownServers[i].ieeeAddress[j]);
218 // Write something to mark the end of the file.
220 int res = fclose(fp);
222 #endif //#if defined(EZSP_HOST) && !defined(EMBER_TEST) && defined(UNIX_HOST)
225 static void iasClientLoadCommand(void)
227 #if defined(EZSP_HOST) && !defined(EMBER_TEST) && defined(UNIX_HOST)
231 unsigned int data1, data2, data3, data4, data5;
233 fp = fopen("iaszone.txt", "r");
240 for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
246 fscanf(fp, "%x ", &data1);
251 fscanf(fp, "%x %x %x %x ", &data2, &data3, &data4, &data5);
253 emberAfIasZoneClientKnownServers[i].zoneId = (uint8_t) data1;
254 emberAfIasZoneClientKnownServers[i].zoneStatus = (uint16_t) data2;
255 emberAfIasZoneClientKnownServers[i].zoneState = (uint8_t) data3;
256 emberAfIasZoneClientKnownServers[i].endpoint = (uint8_t) data4;
257 emberAfIasZoneClientKnownServers[i].zoneType = (uint16_t) data5;
259 for (j = 0; j < 8; j++)
261 fscanf(fp, "%x ", &data1);
262 emberAfIasZoneClientKnownServers[i].ieeeAddress[j] = (uint8_t) data1;
265 int res = fclose(fp);
267 #endif // #if defined(EZSP_HOST) && !defined(EMBER_TEST) && defined(UNIX_HOST)
270 static uint8_t findIasZoneServerByIeee(uint8_t * ieeeAddress)
273 for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
275 if (0 == memcmp(ieeeAddress, emberAfIasZoneClientKnownServers[i].ieeeAddress, EUI64_SIZE))
283 static uint8_t findIasZoneServerByNodeId(EmberNodeId nodeId)
286 for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
288 if (nodeId == emberAfIasZoneClientKnownServers[i].nodeId)
294 // If we didn't find the node ID in the table, see if the stack knows about
297 if (emberLookupEui64ByNodeId(nodeId, eui64) == EMBER_SUCCESS)
299 i = findIasZoneServerByIeee(eui64);
302 setServerNodeId(i, nodeId);
309 bool emberAfIasZoneClusterZoneStatusChangeNotificationCallback(uint16_t zoneStatus, uint8_t extendedStatus, uint8_t zoneId,
312 uint8_t serverIndex = findIasZoneServerByNodeId(emberAfCurrentCommand()->source);
313 uint8_t status = EMBER_ZCL_STATUS_NOT_FOUND;
314 if (serverIndex != NO_INDEX)
316 status = EMBER_ZCL_STATUS_SUCCESS;
317 setServerZoneStatus(serverIndex, zoneStatus);
319 emberAfIasZoneClusterPrintln("Zone %d status change, 0x%2X from 0x%2X", zoneId, zoneStatus,
320 emberAfCurrentCommand()->source);
322 // The Test case calls for readding attributes after status change.
323 // that is silly for the production device.
324 // readIasZoneServerAttributes(emberAfCurrentCommand()->source);
326 emberAfSendDefaultResponse(emberAfCurrentCommand(), status);
330 bool emberAfIasZoneClusterZoneEnrollRequestCallback(uint16_t zoneType, uint16_t manufacturerCode)
332 EmberAfIasEnrollResponseCode responseCode = EMBER_ZCL_IAS_ENROLL_RESPONSE_CODE_NO_ENROLL_PERMIT;
333 uint8_t zoneId = UNKNOWN_ZONE_ID;
334 uint8_t serverIndex = findIasZoneServerByNodeId(emberAfCurrentCommand()->source);
337 if (serverIndex != NO_INDEX)
339 responseCode = EMBER_ZCL_IAS_ENROLL_RESPONSE_CODE_SUCCESS;
340 zoneId = serverIndex;
341 setServerZoneId(serverIndex, zoneId);
343 emberAfFillExternalBuffer((ZCL_CLUSTER_SPECIFIC_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER), ZCL_IAS_ZONE_CLUSTER_ID,
344 ZCL_ZONE_ENROLL_RESPONSE_COMMAND_ID, "uu", responseCode, zoneId);
345 // Need to send this command with our source EUI because the server will
346 // check our EUI64 against his CIE Address to see if we're his CIE.
347 emberAfGetCommandApsFrame()->options |= EMBER_APS_OPTION_SOURCE_EUI64;
348 status = emberAfSendResponse();
349 emberAfCorePrintln("Sent enroll response with responseCode: 0x%X, zoneId: 0x%X, status: 0x%X", responseCode, zoneId, status);
353 void emberAfPluginIasZoneClientStateMachineEventHandler(void)
355 emberAfIasZoneClusterPrintln("IAS Zone Client Timeout waiting for message response.");
356 emberEventControlSetInactive(emberAfPluginIasZoneClientStateMachineEventControl);
360 static uint8_t addServer(EmberNodeId nodeId, uint8_t * ieeeAddress)
362 uint8_t i = findIasZoneServerByIeee(ieeeAddress);
368 for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
370 const uint8_t unsetEui64[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
371 if (0 == memcmp(emberAfIasZoneClientKnownServers[i].ieeeAddress, unsetEui64, EUI64_SIZE))
373 setServerIeee(i, ieeeAddress);
374 setServerNodeId(i, nodeId);
375 setServerEndpoint(i, UNKNOWN_ENDPOINT);
382 static void removeServer(uint8_t * ieeeAddress)
384 uint8_t index = findIasZoneServerByIeee(ieeeAddress);
385 clearServerIeee(index);
386 clearServerNodeId(index);
389 static EmberStatus sendCommand(EmberNodeId destAddress)
391 emberAfSetCommandEndpoints(myEndpoint, emberAfIasZoneClientKnownServers[currentIndex].endpoint);
392 EmberStatus status = emberAfSendCommandUnicast(EMBER_OUTGOING_DIRECT, destAddress);
393 emberAfIasZoneClusterPrintln("Sent IAS Zone Client Command to 0x%2X (%d -> %d) status: 0x%X", destAddress, myEndpoint,
394 emberAfIasZoneClientKnownServers[currentIndex].endpoint, status);
395 if (status != EMBER_SUCCESS)
402 static void setCieAddress(EmberNodeId destAddress)
404 uint8_t writeAttributes[] = {
405 EMBER_LOW_BYTE(ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID),
406 EMBER_HIGH_BYTE(ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID),
407 ZCL_IEEE_ADDRESS_ATTRIBUTE_TYPE,
415 0, // ieee (filled in later)
417 emberAfGetEui64(&writeAttributes[3]);
418 emberAfFillExternalBuffer((ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER), ZCL_IAS_ZONE_CLUSTER_ID,
419 ZCL_WRITE_ATTRIBUTES_COMMAND_ID, "b", writeAttributes, sizeof(writeAttributes));
420 emberAfIasZoneClusterPrintln("Writing CIE Address to IAS Zone Server");
421 if (EMBER_SUCCESS == sendCommand(destAddress))
423 setIasZoneClientState(IAS_ZONE_CLIENT_STATE_SET_CIE_ADDRESS);
427 static void iasZoneClientServiceDiscoveryCallback(const EmberAfServiceDiscoveryResult * result)
429 if (result->status == EMBER_AF_UNICAST_SERVICE_DISCOVERY_COMPLETE_WITH_RESPONSE &&
430 result->zdoRequestClusterId == MATCH_DESCRIPTORS_REQUEST)
432 const EmberAfEndpointList * endpointList = (const EmberAfEndpointList *) result->responseData;
433 if (endpointList->count > 0)
435 setServerEndpoint(currentIndex, endpointList->list[0]);
436 emberAfIasZoneClusterPrintln("Device 0x%2X supports IAS Zone Server", result->matchAddress);
437 setCieAddress(result->matchAddress);
444 static void checkForIasZoneServer(EmberNodeId emberNodeId, uint8_t * ieeeAddress)
446 uint8_t endpointIndex = emberAfIndexFromEndpoint(myEndpoint);
447 uint8_t serverIndex = addServer(emberNodeId, ieeeAddress);
449 if (serverIndex == NO_INDEX)
451 emberAfIasZoneClusterPrintln("Error: Could not add IAS Zone server.");
455 setCurrentIndex(serverIndex);
457 if (emberAfIasZoneClientKnownServers[serverIndex].endpoint != UNKNOWN_ENDPOINT)
459 // If a remote endpoint that you have already seen announces itself,
460 // write your IEEE in them just in case they left and are rejoining. --agkeesle
461 // Bug: EMAPPFWKV2-1078
462 setCieAddress(emberNodeId);
463 emberAfIasZoneClusterPrintln("Node 0x%2X already known to IAS client", emberNodeId);
467 EmberStatus status = emberAfFindDevicesByCluster(emberNodeId, ZCL_IAS_ZONE_CLUSTER_ID,
468 true, // server cluster?
469 iasZoneClientServiceDiscoveryCallback);
471 if (status != EMBER_SUCCESS)
473 emberAfIasZoneClusterPrintln("Error: Failed to initiate service discovery for IAS Zone Server 0x%2X", emberNodeId);
478 void emberAfPluginIasZoneClientZdoMessageReceivedCallback(EmberNodeId emberNodeId, EmberApsFrame * apsFrame, uint8_t * message,
481 emberAfIasZoneClusterPrintln("Incoming ZDO, Cluster: 0x%2X", apsFrame->clusterId);
482 if (apsFrame->clusterId == END_DEVICE_ANNOUNCE)
484 checkForIasZoneServer(emberNodeId, &(message[3]));
488 void readIasZoneServerAttributes(EmberNodeId nodeId)
490 uint8_t iasZoneAttributeIds[] = {
491 EMBER_LOW_BYTE(ZCL_ZONE_STATE_ATTRIBUTE_ID), EMBER_HIGH_BYTE(ZCL_ZONE_STATE_ATTRIBUTE_ID),
493 EMBER_LOW_BYTE(ZCL_ZONE_TYPE_ATTRIBUTE_ID), EMBER_HIGH_BYTE(ZCL_ZONE_TYPE_ATTRIBUTE_ID),
495 EMBER_LOW_BYTE(ZCL_ZONE_STATUS_ATTRIBUTE_ID), EMBER_HIGH_BYTE(ZCL_ZONE_STATUS_ATTRIBUTE_ID),
497 emberAfFillExternalBuffer((ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER), ZCL_IAS_ZONE_CLUSTER_ID,
498 ZCL_READ_ATTRIBUTES_COMMAND_ID, "b", iasZoneAttributeIds, sizeof(iasZoneAttributeIds));
499 if (EMBER_SUCCESS == sendCommand(nodeId))
501 setIasZoneClientState(IAS_ZONE_CLIENT_STATE_READ_ATTRIBUTES);
505 void readIasZoneServerCieAddress(EmberNodeId nodeId)
507 uint8_t iasZoneAttributeIds[] = {
508 EMBER_LOW_BYTE(ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID),
509 EMBER_HIGH_BYTE(ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID),
511 emberAfFillExternalBuffer((ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER), ZCL_IAS_ZONE_CLUSTER_ID,
512 ZCL_READ_ATTRIBUTES_COMMAND_ID, "b", iasZoneAttributeIds, sizeof(iasZoneAttributeIds));
513 if (EMBER_SUCCESS == sendCommand(nodeId))
515 setIasZoneClientState(IAS_ZONE_CLIENT_STATE_READ_CIE_ADDRESS);
519 void emberAfPluginIasZoneClientWriteAttributesResponseCallback(ClusterId clusterId, uint8_t * buffer, uint16_t bufLen)
521 if (clusterId == ZCL_IAS_ZONE_CLUSTER_ID && iasZoneClientState == IAS_ZONE_CLIENT_STATE_SET_CIE_ADDRESS &&
522 buffer[0] == EMBER_ZCL_STATUS_SUCCESS)
524 readIasZoneServerCieAddress(emberAfCurrentCommand()->source);
530 void emberAfPluginIasZoneClientReadAttributesResponseCallback(ClusterId clusterId, uint8_t * buffer, uint16_t bufLen)
532 uint8_t zoneStatus, zoneType, zoneState;
533 if (clusterId == ZCL_IAS_ZONE_CLUSTER_ID &&
534 (iasZoneClientState == IAS_ZONE_CLIENT_STATE_READ_ATTRIBUTES ||
535 iasZoneClientState == IAS_ZONE_CLIENT_STATE_READ_CIE_ADDRESS))
538 while ((i + 3) <= bufLen)
539 { // 3 to insure we can read at least the attribute ID
541 AttributeId attributeId = buffer[i] + (buffer[i + 1] << 8);
542 uint8_t status = buffer[i + 2];
544 // emberAfIasZoneClusterPrintln("Parsing Attribute 0x%2X, Status: 0x%X", attributeId, status);
545 if (status == EMBER_ZCL_STATUS_SUCCESS)
547 if ((i + 1) > bufLen)
549 // Too short, dump the message.
552 i++; // skip the type of the attribute. We already know what it should be.
555 case ZCL_ZONE_STATUS_ATTRIBUTE_ID:
556 if ((i + 2) > bufLen)
558 // Too short, dump the message.
561 zoneStatus = (buffer[i] + (buffer[i + 1] << 8));
562 setServerZoneStatus(currentIndex, zoneStatus);
565 case ZCL_ZONE_TYPE_ATTRIBUTE_ID:
566 if ((i + 2) > bufLen)
568 // Too short, dump the message.
571 zoneType = (buffer[i] + (buffer[i + 1] << 8));
572 setServerZoneType(currentIndex, zoneType);
575 case ZCL_ZONE_STATE_ATTRIBUTE_ID:
576 if ((i + 1) > bufLen)
578 // Too short, dump the message
581 zoneState = buffer[i];
582 setServerZoneState(currentIndex, zoneState);
585 case ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID: {
586 uint8_t myIeee[EUI64_SIZE];
587 emberAfGetEui64(myIeee);
588 if ((i + 8) > bufLen)
590 // Too short, dump the message
592 else if (0 != memcmp(&(buffer[i]), myIeee, EUI64_SIZE))
594 emberAfIasZoneClusterPrintln("CIE Address not set to mine, removing IAS zone server.");
595 removeServer(&(buffer[i]));
600 readIasZoneServerAttributes(emberAfCurrentCommand()->source);
607 emberAfIasZoneClusterPrintln("Retrieved IAS Zone Server attributes from 0x%2X", emberAfCurrentCommand()->source);
612 void emberAfPluginIasZoneClientZdoCallback(EmberNodeId emberNodeId, EmberApsFrame * apsFrame, uint8_t * message, uint16_t length) {}
614 void emberAfPluginIasZoneClientWriteAttributesResponseCallback(ClusterId clusterId, uint8_t * buffer, uint16_t bufLen) {}
616 void emberAfPluginIasZoneClientReadAttributesResponseCallback(ClusterId clusterId, uint8_t * buffer, uint16_t bufLen) {}