blob: 58eb813316a99ed2b9a485ffbcb162d321a827ec [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.
*/
#include "ias-zone-client.h"
#include <app/CommandHandler.h>
#include <app/util/af.h>
using namespace chip;
//-----------------------------------------------------------------------------
// Globals
IasZoneDevice emberAfIasZoneClientKnownServers[EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES];
typedef enum
{
IAS_ZONE_CLIENT_STATE_NONE,
IAS_ZONE_CLIENT_STATE_DISCOVER_ENDPOINT,
IAS_ZONE_CLIENT_STATE_SET_CIE_ADDRESS,
IAS_ZONE_CLIENT_STATE_READ_CIE_ADDRESS,
IAS_ZONE_CLIENT_STATE_READ_ATTRIBUTES,
} IasZoneClientState;
static IasZoneClientState iasZoneClientState = IAS_ZONE_CLIENT_STATE_NONE;
static uint8_t currentIndex = NO_INDEX;
static uint8_t myEndpoint = 0;
EmberEventControl emberAfPluginIasZoneClientStateMachineEventControl;
//-----------------------------------------------------------------------------
// Forward Declarations
void readIasZoneServerAttributes(EmberNodeId nodeId);
static void iasClientSaveCommand(void);
static void iasClientLoadCommand(void);
//-----------------------------------------------------------------------------
// Functions
void emberAfIasZoneClusterClientInitCallback(EndpointId endpoint)
{
emAfClearServers();
myEndpoint = endpoint;
iasClientLoadCommand();
}
void emAfClearServers(void)
{
MEMSET(emberAfIasZoneClientKnownServers, 0xFF, sizeof(IasZoneDevice) * EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES);
}
static void clearState(void)
{
currentIndex = 0;
iasZoneClientState = IAS_ZONE_CLIENT_STATE_NONE;
}
static void setServerZoneStatus(uint8_t serverIndex, uint16_t zoneStatus)
{
emberAfIasZoneClientKnownServers[serverIndex].zoneStatus = zoneStatus;
iasClientSaveCommand();
}
static void setServerIeee(uint8_t serverIndex, uint8_t * ieeeAddress)
{
MEMCOPY(emberAfIasZoneClientKnownServers[serverIndex].ieeeAddress, ieeeAddress, EUI64_SIZE);
iasClientSaveCommand();
}
static void clearServerIeee(uint8_t serverIndex)
{
MEMSET(emberAfIasZoneClientKnownServers[serverIndex].ieeeAddress, 0xFF, sizeof(IasZoneDevice));
iasClientSaveCommand();
}
static void setServerNodeId(uint8_t serverIndex, EmberNodeId nodeId)
{
emberAfIasZoneClientKnownServers[serverIndex].nodeId = nodeId;
}
static void clearServerNodeId(uint8_t serverIndex)
{
emberAfIasZoneClientKnownServers[serverIndex].nodeId = EMBER_NULL_NODE_ID;
}
static void setServerZoneState(uint8_t serverIndex, uint8_t zoneState)
{
emberAfIasZoneClientKnownServers[serverIndex].zoneState = zoneState;
iasClientSaveCommand();
}
static void setServerEndpoint(uint8_t serverIndex, EndpointId endpoint)
{
emberAfIasZoneClientKnownServers[serverIndex].endpoint = endpoint;
iasClientSaveCommand();
}
static void setServerZoneType(uint8_t serverIndex, uint16_t zoneType)
{
emberAfIasZoneClientKnownServers[serverIndex].zoneType = zoneType;
iasClientSaveCommand();
}
static void setServerZoneId(uint8_t serverIndex, uint16_t zoneId)
{
emberAfIasZoneClientKnownServers[serverIndex].zoneId = zoneId;
iasClientSaveCommand();
}
static void setCurrentIndex(uint8_t serverIndex)
{
currentIndex = serverIndex;
iasClientSaveCommand();
}
static void setIasZoneClientState(uint8_t clientState)
{
iasZoneClientState = clientState;
iasClientSaveCommand();
}
static void iasClientSaveCommand(void)
{
#if defined(EZSP_HOST) && !defined(EMBER_TEST) && defined(UNIX_HOST)
FILE * fp;
uint16_t i, j;
// save zone server list
fp = fopen("iaszone.txt", "w");
for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
{
if (emberAfIasZoneClientKnownServers[i].zoneId != 0xFF)
{
fprintf(fp, "%x %x %x %x %x ", emberAfIasZoneClientKnownServers[i].zoneId,
emberAfIasZoneClientKnownServers[i].zoneStatus, emberAfIasZoneClientKnownServers[i].zoneState,
emberAfIasZoneClientKnownServers[i].endpoint, emberAfIasZoneClientKnownServers[i].zoneType);
for (j = 0; j < 8; j++)
{
fprintf(fp, "%x ", emberAfIasZoneClientKnownServers[i].ieeeAddress[j]);
}
}
}
// Write something to mark the end of the file.
fprintf(fp, "ff");
int res = fclose(fp);
assert(res == 0);
#endif //#if defined(EZSP_HOST) && !defined(EMBER_TEST) && defined(UNIX_HOST)
}
static void iasClientLoadCommand(void)
{
#if defined(EZSP_HOST) && !defined(EMBER_TEST) && defined(UNIX_HOST)
FILE * fp;
uint16_t i, j;
unsigned int data1, data2, data3, data4, data5;
fp = fopen("iaszone.txt", "r");
if (!fp)
{
return;
}
for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
{
if (feof(fp))
{
break;
}
fscanf(fp, "%x ", &data1);
if (data1 == 0xff)
{
break;
}
fscanf(fp, "%x %x %x %x ", &data2, &data3, &data4, &data5);
emberAfIasZoneClientKnownServers[i].zoneId = (uint8_t) data1;
emberAfIasZoneClientKnownServers[i].zoneStatus = (uint16_t) data2;
emberAfIasZoneClientKnownServers[i].zoneState = (uint8_t) data3;
emberAfIasZoneClientKnownServers[i].endpoint = (uint8_t) data4;
emberAfIasZoneClientKnownServers[i].zoneType = (uint16_t) data5;
for (j = 0; j < 8; j++)
{
fscanf(fp, "%x ", &data1);
emberAfIasZoneClientKnownServers[i].ieeeAddress[j] = (uint8_t) data1;
}
}
int res = fclose(fp);
assert(res == 0);
#endif // #if defined(EZSP_HOST) && !defined(EMBER_TEST) && defined(UNIX_HOST)
}
static uint8_t findIasZoneServerByIeee(uint8_t * ieeeAddress)
{
uint8_t i;
for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
{
if (0 == memcmp(ieeeAddress, emberAfIasZoneClientKnownServers[i].ieeeAddress, EUI64_SIZE))
{
return i;
}
}
return NO_INDEX;
}
static uint8_t findIasZoneServerByNodeId(EmberNodeId nodeId)
{
uint8_t i;
for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
{
if (nodeId == emberAfIasZoneClientKnownServers[i].nodeId)
{
return i;
}
}
// If we didn't find the node ID in the table, see if the stack knows about
// it.
EmberEUI64 eui64;
if (emberLookupEui64ByNodeId(nodeId, eui64) == EMBER_SUCCESS)
{
i = findIasZoneServerByIeee(eui64);
if (i != NO_INDEX)
{
setServerNodeId(i, nodeId);
}
}
return i;
}
bool emberAfIasZoneClusterZoneStatusChangeNotificationCallback(app::CommandHandler * commandObj, uint16_t zoneStatus,
uint8_t extendedStatus, uint8_t zoneId, uint16_t delay)
{
uint8_t serverIndex = findIasZoneServerByNodeId(emberAfCurrentCommand()->source);
uint8_t status = EMBER_ZCL_STATUS_NOT_FOUND;
if (serverIndex != NO_INDEX)
{
status = EMBER_ZCL_STATUS_SUCCESS;
setServerZoneStatus(serverIndex, zoneStatus);
emberAfIasZoneClusterPrintln("Zone %d status change, 0x%2X from 0x%2X", zoneId, zoneStatus,
emberAfCurrentCommand()->source);
// The Test case calls for readding attributes after status change.
// that is silly for the production device.
// readIasZoneServerAttributes(emberAfCurrentCommand()->source);
}
emberAfSendDefaultResponse(emberAfCurrentCommand(), status);
return true;
}
bool emberAfIasZoneClusterZoneEnrollRequestCallback(app::CommandHandler * commandObj, uint16_t zoneType, uint16_t manufacturerCode)
{
EmberAfIasEnrollResponseCode responseCode = EMBER_ZCL_IAS_ENROLL_RESPONSE_CODE_NO_ENROLL_PERMIT;
uint8_t zoneId = UNKNOWN_ZONE_ID;
uint8_t serverIndex = findIasZoneServerByNodeId(emberAfCurrentCommand()->source);
EmberStatus status;
if (serverIndex != NO_INDEX)
{
responseCode = EMBER_ZCL_IAS_ENROLL_RESPONSE_CODE_SUCCESS;
zoneId = serverIndex;
setServerZoneId(serverIndex, zoneId);
}
emberAfFillExternalBuffer((ZCL_CLUSTER_SPECIFIC_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER), ZCL_IAS_ZONE_CLUSTER_ID,
ZCL_ZONE_ENROLL_RESPONSE_COMMAND_ID, "uu", responseCode, zoneId);
// Need to send this command with our source EUI because the server will
// check our EUI64 against his CIE Address to see if we're his CIE.
emberAfGetCommandApsFrame()->options |= EMBER_APS_OPTION_SOURCE_EUI64;
status = emberAfSendResponse();
emberAfCorePrintln("Sent enroll response with responseCode: 0x%X, zoneId: 0x%X, status: 0x%X", responseCode, zoneId, status);
return true;
}
void emberAfPluginIasZoneClientStateMachineEventHandler(void)
{
emberAfIasZoneClusterPrintln("IAS Zone Client Timeout waiting for message response.");
emberEventControlSetInactive(emberAfPluginIasZoneClientStateMachineEventControl);
clearState();
}
static uint8_t addServer(EmberNodeId nodeId, uint8_t * ieeeAddress)
{
uint8_t i = findIasZoneServerByIeee(ieeeAddress);
if (i != NO_INDEX)
{
return i;
}
for (i = 0; i < EMBER_AF_PLUGIN_IAS_ZONE_CLIENT_MAX_DEVICES; i++)
{
const uint8_t unsetEui64[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
if (0 == memcmp(emberAfIasZoneClientKnownServers[i].ieeeAddress, unsetEui64, EUI64_SIZE))
{
setServerIeee(i, ieeeAddress);
setServerNodeId(i, nodeId);
setServerEndpoint(i, UNKNOWN_ENDPOINT);
return i;
}
}
return NO_INDEX;
}
static void removeServer(uint8_t * ieeeAddress)
{
uint8_t index = findIasZoneServerByIeee(ieeeAddress);
clearServerIeee(index);
clearServerNodeId(index);
}
static EmberStatus sendCommand(EmberNodeId destAddress)
{
emberAfSetCommandEndpoints(myEndpoint, emberAfIasZoneClientKnownServers[currentIndex].endpoint);
EmberStatus status = emberAfSendCommandUnicast(MessageSendDestination::Direct(destAddress));
emberAfIasZoneClusterPrintln("Sent IAS Zone Client Command to 0x%2X (%d -> %d) status: 0x%X", destAddress, myEndpoint,
emberAfIasZoneClientKnownServers[currentIndex].endpoint, status);
if (status != EMBER_SUCCESS)
{
clearState();
}
return status;
}
static void setCieAddress(EmberNodeId destAddress)
{
uint8_t writeAttributes[] = {
EMBER_LOW_BYTE(ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID),
EMBER_HIGH_BYTE(ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID),
ZCL_IEEE_ADDRESS_ATTRIBUTE_TYPE,
0,
0,
0,
0,
0,
0,
0,
0, // ieee (filled in later)
};
emberAfGetEui64(&writeAttributes[3]);
emberAfFillExternalBuffer((ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER), ZCL_IAS_ZONE_CLUSTER_ID,
ZCL_WRITE_ATTRIBUTES_COMMAND_ID, "b", writeAttributes, sizeof(writeAttributes));
emberAfIasZoneClusterPrintln("Writing CIE Address to IAS Zone Server");
if (EMBER_SUCCESS == sendCommand(destAddress))
{
setIasZoneClientState(IAS_ZONE_CLIENT_STATE_SET_CIE_ADDRESS);
}
}
static void iasZoneClientServiceDiscoveryCallback(const EmberAfServiceDiscoveryResult * result)
{
if (result->status == EMBER_AF_UNICAST_SERVICE_DISCOVERY_COMPLETE_WITH_RESPONSE &&
result->zdoRequestClusterId == MATCH_DESCRIPTORS_REQUEST)
{
const EmberAfEndpointList * endpointList = (const EmberAfEndpointList *) result->responseData;
if (endpointList->count > 0)
{
setServerEndpoint(currentIndex, endpointList->list[0]);
emberAfIasZoneClusterPrintln("Device 0x%2X supports IAS Zone Server", result->matchAddress);
setCieAddress(result->matchAddress);
return;
}
}
clearState();
}
static void checkForIasZoneServer(EmberNodeId emberNodeId, uint8_t * ieeeAddress)
{
uint8_t endpointIndex = emberAfIndexFromEndpoint(myEndpoint);
uint8_t serverIndex = addServer(emberNodeId, ieeeAddress);
if (serverIndex == NO_INDEX)
{
emberAfIasZoneClusterPrintln("Error: Could not add IAS Zone server.");
return;
}
setCurrentIndex(serverIndex);
if (emberAfIasZoneClientKnownServers[serverIndex].endpoint != UNKNOWN_ENDPOINT)
{
// If a remote endpoint that you have already seen announces itself,
// write your IEEE in them just in case they left and are rejoining. --agkeesle
// Bug: EMAPPFWKV2-1078
setCieAddress(emberNodeId);
emberAfIasZoneClusterPrintln("Node 0x%2X already known to IAS client", emberNodeId);
return;
}
EmberStatus status = emberAfFindDevicesByCluster(emberNodeId, ZCL_IAS_ZONE_CLUSTER_ID,
true, // server cluster?
iasZoneClientServiceDiscoveryCallback);
if (status != EMBER_SUCCESS)
{
emberAfIasZoneClusterPrintln("Error: Failed to initiate service discovery for IAS Zone Server 0x%2X", emberNodeId);
clearState();
}
}
void emberAfPluginIasZoneClientZdoMessageReceivedCallback(EmberNodeId emberNodeId, EmberApsFrame * apsFrame, uint8_t * message,
uint16_t length)
{
emberAfIasZoneClusterPrintln("Incoming ZDO, Cluster: 0x%2X", apsFrame->clusterId);
if (apsFrame->clusterId == END_DEVICE_ANNOUNCE)
{
checkForIasZoneServer(emberNodeId, &(message[3]));
}
}
void readIasZoneServerAttributes(EmberNodeId nodeId)
{
uint8_t iasZoneAttributeIds[] = {
EMBER_LOW_BYTE(ZCL_ZONE_STATE_ATTRIBUTE_ID), EMBER_HIGH_BYTE(ZCL_ZONE_STATE_ATTRIBUTE_ID),
EMBER_LOW_BYTE(ZCL_ZONE_TYPE_ATTRIBUTE_ID), EMBER_HIGH_BYTE(ZCL_ZONE_TYPE_ATTRIBUTE_ID),
EMBER_LOW_BYTE(ZCL_ZONE_STATUS_ATTRIBUTE_ID), EMBER_HIGH_BYTE(ZCL_ZONE_STATUS_ATTRIBUTE_ID),
};
emberAfFillExternalBuffer((ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER), ZCL_IAS_ZONE_CLUSTER_ID,
ZCL_READ_ATTRIBUTES_COMMAND_ID, "b", iasZoneAttributeIds, sizeof(iasZoneAttributeIds));
if (EMBER_SUCCESS == sendCommand(nodeId))
{
setIasZoneClientState(IAS_ZONE_CLIENT_STATE_READ_ATTRIBUTES);
}
}
void readIasZoneServerCieAddress(EmberNodeId nodeId)
{
uint8_t iasZoneAttributeIds[] = {
EMBER_LOW_BYTE(ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID),
EMBER_HIGH_BYTE(ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID),
};
emberAfFillExternalBuffer((ZCL_GLOBAL_COMMAND | ZCL_FRAME_CONTROL_CLIENT_TO_SERVER), ZCL_IAS_ZONE_CLUSTER_ID,
ZCL_READ_ATTRIBUTES_COMMAND_ID, "b", iasZoneAttributeIds, sizeof(iasZoneAttributeIds));
if (EMBER_SUCCESS == sendCommand(nodeId))
{
setIasZoneClientState(IAS_ZONE_CLIENT_STATE_READ_CIE_ADDRESS);
}
}
void emberAfPluginIasZoneClientWriteAttributesResponseCallback(ClusterId clusterId, uint8_t * buffer, uint16_t bufLen)
{
if (clusterId == ZCL_IAS_ZONE_CLUSTER_ID && iasZoneClientState == IAS_ZONE_CLIENT_STATE_SET_CIE_ADDRESS &&
buffer[0] == EMBER_ZCL_STATUS_SUCCESS)
{
readIasZoneServerCieAddress(emberAfCurrentCommand()->source);
return;
}
return;
}
void emberAfPluginIasZoneClientReadAttributesResponseCallback(ClusterId clusterId, uint8_t * buffer, uint16_t bufLen)
{
uint8_t zoneStatus, zoneType, zoneState;
if (clusterId == ZCL_IAS_ZONE_CLUSTER_ID &&
(iasZoneClientState == IAS_ZONE_CLIENT_STATE_READ_ATTRIBUTES ||
iasZoneClientState == IAS_ZONE_CLIENT_STATE_READ_CIE_ADDRESS))
{
uint16_t i = 0;
while ((i + 3) <= bufLen)
{ // 3 to insure we can read at least the attribute ID
// and the status
AttributeId attributeId = buffer[i] + (buffer[i + 1] << 8);
uint8_t status = buffer[i + 2];
i += 3;
// emberAfIasZoneClusterPrintln("Parsing Attribute 0x%2X, Status: 0x%X", attributeId, status);
if (status == EMBER_ZCL_STATUS_SUCCESS)
{
if ((i + 1) > bufLen)
{
// Too short, dump the message.
return;
}
i++; // skip the type of the attribute. We already know what it should be.
switch (attributeId)
{
case ZCL_ZONE_STATUS_ATTRIBUTE_ID:
if ((i + 2) > bufLen)
{
// Too short, dump the message.
return;
}
zoneStatus = (buffer[i] + (buffer[i + 1] << 8));
setServerZoneStatus(currentIndex, zoneStatus);
i += 2;
break;
case ZCL_ZONE_TYPE_ATTRIBUTE_ID:
if ((i + 2) > bufLen)
{
// Too short, dump the message.
return;
}
zoneType = (buffer[i] + (buffer[i + 1] << 8));
setServerZoneType(currentIndex, zoneType);
i += 2;
break;
case ZCL_ZONE_STATE_ATTRIBUTE_ID:
if ((i + 1) > bufLen)
{
// Too short, dump the message
return;
}
zoneState = buffer[i];
setServerZoneState(currentIndex, zoneState);
i++;
break;
case ZCL_IAS_CIE_ADDRESS_ATTRIBUTE_ID: {
uint8_t myIeee[EUI64_SIZE];
emberAfGetEui64(myIeee);
if ((i + 8) > bufLen)
{
// Too short, dump the message
}
else if (0 != memcmp(&(buffer[i]), myIeee, EUI64_SIZE))
{
emberAfIasZoneClusterPrintln("CIE Address not set to mine, removing IAS zone server.");
removeServer(&(buffer[i]));
clearState();
}
else
{
readIasZoneServerAttributes(emberAfCurrentCommand()->source);
}
return;
}
}
}
}
emberAfIasZoneClusterPrintln("Retrieved IAS Zone Server attributes from 0x%2X", emberAfCurrentCommand()->source);
clearState();
}
}
void emberAfPluginIasZoneClientZdoCallback(EmberNodeId emberNodeId, EmberApsFrame * apsFrame, uint8_t * message, uint16_t length) {}
void emberAfPluginIasZoneClientWriteAttributesResponseCallback(ClusterId clusterId, uint8_t * buffer, uint16_t bufLen) {}
void emberAfPluginIasZoneClientReadAttributesResponseCallback(ClusterId clusterId, uint8_t * buffer, uint16_t bufLen) {}