blob: 58369e8eb11837c5dd4186a32fb44e5caa2533a9 [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 "scenes.h"
#include "app/util/common.h"
#include <app-common/zap-generated/attribute-id.h>
#include <app-common/zap-generated/attribute-type.h>
#include <app-common/zap-generated/cluster-objects.h>
#include <app-common/zap-generated/command-id.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/CommandHandler.h>
#include <app/ConcreteCommandPath.h>
#include <app/util/af.h>
#ifdef EMBER_AF_PLUGIN_GROUPS_SERVER
#include <app/clusters/groups-server/groups-server.h>
#endif
#ifdef EMBER_AF_PLUGIN_ZLL_SCENES_SERVER
#include "../zll-scenes-server/zll-scenes-server.h"
#endif
using namespace chip;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::Scenes;
uint8_t emberAfPluginScenesServerEntriesInUse = 0;
#if !defined(EMBER_AF_PLUGIN_SCENES_USE_TOKENS) || defined(EZSP_HOST)
EmberAfSceneTableEntry emberAfPluginScenesServerSceneTable[MATTER_SCENES_TABLE_SIZE];
#endif
static bool readServerAttribute(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId, const char * name,
uint8_t * data, uint8_t size)
{
bool success = false;
if (emberAfContainsServer(endpoint, clusterId))
{
EmberAfStatus status = emberAfReadServerAttribute(endpoint, clusterId, attributeId, data, size);
if (status == EMBER_ZCL_STATUS_SUCCESS)
{
success = true;
}
else
{
emberAfScenesClusterPrintln("ERR: %ping %p 0x%x", "read", name, status);
}
}
return success;
}
static EmberAfStatus writeServerAttribute(EndpointId endpoint, ClusterId clusterId, AttributeId attributeId, const char * name,
uint8_t * data, EmberAfAttributeType type)
{
EmberAfStatus status = emberAfWriteServerAttribute(endpoint, clusterId, attributeId, data, type);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
emberAfScenesClusterPrintln("ERR: %ping %p 0x%x", "writ", name, status);
}
return status;
}
bool isEndpointInGroup(chip::FabricIndex fabricIndex, EndpointId endpoint, GroupId groupId)
{
#ifdef EMBER_AF_PLUGIN_GROUPS_SERVER
return (groupId == ZCL_SCENES_GLOBAL_SCENE_GROUP_ID ||
emberAfGroupsClusterEndpointInGroupCallback(fabricIndex, endpoint, groupId));
#else
return (groupId == ZCL_SCENES_GLOBAL_SCENE_GROUP_ID);
#endif // EMBER_AF_PLUGIN_GROUPS_SERVER
}
void emberAfScenesClusterServerInitCallback(EndpointId endpoint)
{
#if defined(MATTER_CLUSTER_SCENE_NAME_SUPPORT) && MATTER_CLUSTER_SCENE_NAME_SUPPORT
{
// The high bit of Name Support indicates whether scene names are supported.
uint8_t nameSupport = EMBER_BIT(7);
writeServerAttribute(endpoint, Scenes::Id, ZCL_SCENE_NAME_SUPPORT_ATTRIBUTE_ID, "name support", (uint8_t *) &nameSupport,
ZCL_BITMAP8_ATTRIBUTE_TYPE);
}
#endif
#if !defined(EMBER_AF_PLUGIN_SCENES_USE_TOKENS) || defined(EZSP_HOST)
{
uint8_t i;
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
EmberAfSceneTableEntry entry;
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
entry.endpoint = EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID;
emberAfPluginScenesServerSaveSceneEntry(entry, i);
}
emberAfPluginScenesServerSetNumSceneEntriesInUse(0);
}
#endif
emberAfScenesSetSceneCountAttribute(endpoint, emberAfPluginScenesServerNumSceneEntriesInUse());
}
EmberAfStatus emberAfScenesSetSceneCountAttribute(EndpointId endpoint, uint8_t newCount)
{
return writeServerAttribute(endpoint, Scenes::Id, ZCL_SCENE_COUNT_ATTRIBUTE_ID, "scene count", (uint8_t *) &newCount,
ZCL_INT8U_ATTRIBUTE_TYPE);
}
EmberAfStatus emberAfScenesMakeValid(EndpointId endpoint, uint8_t sceneId, GroupId groupId)
{
EmberAfStatus status;
bool valid = true;
// scene ID
status = writeServerAttribute(endpoint, Scenes::Id, ZCL_CURRENT_SCENE_ATTRIBUTE_ID, "current scene", (uint8_t *) &sceneId,
ZCL_INT8U_ATTRIBUTE_TYPE);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
return status;
}
// group ID
status = writeServerAttribute(endpoint, Scenes::Id, ZCL_CURRENT_GROUP_ATTRIBUTE_ID, "current group", (uint8_t *) &groupId,
ZCL_INT16U_ATTRIBUTE_TYPE);
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
return status;
}
status = writeServerAttribute(endpoint, Scenes::Id, ZCL_SCENE_VALID_ATTRIBUTE_ID, "scene valid", (uint8_t *) &valid,
ZCL_BOOLEAN_ATTRIBUTE_TYPE);
return status;
}
EmberAfStatus emberAfScenesClusterMakeInvalidCallback(EndpointId endpoint)
{
bool valid = false;
return writeServerAttribute(endpoint, Scenes::Id, ZCL_SCENE_VALID_ATTRIBUTE_ID, "scene valid", (uint8_t *) &valid,
ZCL_BOOLEAN_ATTRIBUTE_TYPE);
}
void emAfPluginScenesServerPrintInfo()
{
uint8_t i;
EmberAfSceneTableEntry entry;
emberAfCorePrintln("using 0x%x out of 0x%x table slots", emberAfPluginScenesServerNumSceneEntriesInUse(),
MATTER_SCENES_TABLE_SIZE);
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
emberAfCorePrint("%x: ", i);
if (entry.endpoint != EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID)
{
emberAfCorePrint("ep %x grp %2x scene %x tt %d", entry.endpoint, entry.groupId, entry.sceneId, entry.transitionTime);
emberAfCorePrint(".%d", entry.transitionTime100ms);
#if defined(MATTER_CLUSTER_SCENE_NAME_SUPPORT) && MATTER_CLUSTER_SCENE_NAME_SUPPORT
emberAfCorePrint(" name(%x)\"", emberAfStringLength(entry.name));
emberAfCorePrintString(entry.name);
emberAfCorePrint("\"");
#endif
#ifdef ZCL_USING_ON_OFF_CLUSTER_SERVER
emberAfCorePrint(" on/off %x", entry.onOffValue);
#endif
#ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER
emberAfCorePrint(" lvl %x", entry.currentLevelValue);
#endif
#ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER
emberAfCorePrint(" therm %2x %2x %x", entry.occupiedCoolingSetpointValue, entry.occupiedHeatingSetpointValue,
entry.systemModeValue);
#endif
#ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
emberAfCorePrint(" color %2x %2x", entry.currentXValue, entry.currentYValue);
emberAfCorePrint(" %2x %x %x %x %2x %2x", entry.enhancedCurrentHueValue, entry.currentSaturationValue,
entry.colorLoopActiveValue, entry.colorLoopDirectionValue, entry.colorLoopTimeValue,
entry.colorTemperatureMiredsValue);
emberAfCoreFlush();
#endif // ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
#ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER
emberAfCorePrint(" door %x", entry.lockStateValue);
#endif
#ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER
emberAfCorePrint(" Window percentage Lift %3u, Tilt %3u", entry.currentPositionLiftPercentageValue,
entry.currentPositionTiltPercentageValue);
emberAfCorePrint(" Window percent100ths Lift %5u, Tilt %5u", entry.targetPositionLiftPercent100thsValue,
entry.targetPositionTiltPercent100thsValue);
#endif
}
emberAfCorePrintln("%s", "");
}
}
bool emberAfScenesClusterAddSceneCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::AddScene::DecodableType & commandData)
{
auto & groupId = commandData.groupId;
auto & sceneId = commandData.sceneId;
auto & transitionTime = commandData.transitionTime;
auto & sceneName = commandData.sceneName;
auto & extensionFieldSets = commandData.extensionFieldSets;
return emberAfPluginScenesServerParseAddScene(commandObj, emberAfCurrentCommand(), groupId, sceneId, transitionTime, sceneName,
extensionFieldSets);
}
bool emberAfScenesClusterViewSceneCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::ViewScene::DecodableType & commandData)
{
auto & groupId = commandData.groupId;
auto & sceneId = commandData.sceneId;
return emberAfPluginScenesServerParseViewScene(commandObj, emberAfCurrentCommand(), groupId, sceneId);
}
bool emberAfScenesClusterRemoveSceneCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::RemoveScene::DecodableType & commandData)
{
auto fabricIndex = commandObj->GetAccessingFabricIndex();
auto & groupId = commandData.groupId;
auto & sceneId = commandData.sceneId;
EmberAfStatus status = EMBER_ZCL_STATUS_NOT_FOUND;
CHIP_ERROR err = CHIP_NO_ERROR;
emberAfScenesClusterPrintln("RX: RemoveScene 0x%2x, 0x%x", groupId, sceneId);
if (!isEndpointInGroup(fabricIndex, emberAfCurrentEndpoint(), groupId))
{
status = EMBER_ZCL_STATUS_INVALID_FIELD;
}
else
{
uint8_t i;
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
EmberAfSceneTableEntry entry;
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
if (entry.endpoint == emberAfCurrentEndpoint() && entry.groupId == groupId && entry.sceneId == sceneId)
{
entry.endpoint = EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID;
emberAfPluginScenesServerSaveSceneEntry(entry, i);
emberAfPluginScenesServerDecrNumSceneEntriesInUse();
emberAfScenesSetSceneCountAttribute(emberAfCurrentEndpoint(), emberAfPluginScenesServerNumSceneEntriesInUse());
status = EMBER_ZCL_STATUS_SUCCESS;
break;
}
}
}
// Remove Scene commands are only responded to when they are addressed to a
// single device.
if (emberAfCurrentCommand()->type == EMBER_INCOMING_UNICAST || emberAfCurrentCommand()->type == EMBER_INCOMING_UNICAST_REPLY)
{
{
app::ConcreteCommandPath path = { emberAfCurrentEndpoint(), Scenes::Id, ZCL_REMOVE_SCENE_RESPONSE_COMMAND_ID };
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(path));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), groupId));
SuccessOrExit(err = writer->Put(TLV::ContextTag(2), sceneId));
SuccessOrExit(err = commandObj->FinishCommand());
}
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
bool emberAfScenesClusterRemoveAllScenesCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::RemoveAllScenes::DecodableType & commandData)
{
auto fabricIndex = commandObj->GetAccessingFabricIndex();
auto & groupId = commandData.groupId;
EmberAfStatus status = EMBER_ZCL_STATUS_INVALID_FIELD;
CHIP_ERROR err = CHIP_NO_ERROR;
emberAfScenesClusterPrintln("RX: RemoveAllScenes 0x%2x", groupId);
if (isEndpointInGroup(fabricIndex, emberAfCurrentEndpoint(), groupId))
{
uint8_t i;
status = EMBER_ZCL_STATUS_SUCCESS;
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
EmberAfSceneTableEntry entry;
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
if (entry.endpoint == emberAfCurrentEndpoint() && entry.groupId == groupId)
{
entry.endpoint = EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID;
emberAfPluginScenesServerSaveSceneEntry(entry, i);
emberAfPluginScenesServerDecrNumSceneEntriesInUse();
}
}
emberAfScenesSetSceneCountAttribute(emberAfCurrentEndpoint(), emberAfPluginScenesServerNumSceneEntriesInUse());
}
// Remove All Scenes commands are only responded to when they are addressed
// to a single device.
if (emberAfCurrentCommand()->type == EMBER_INCOMING_UNICAST || emberAfCurrentCommand()->type == EMBER_INCOMING_UNICAST_REPLY)
{
{
app::ConcreteCommandPath path = { emberAfCurrentEndpoint(), Scenes::Id, ZCL_REMOVE_ALL_SCENES_RESPONSE_COMMAND_ID };
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(path));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), groupId));
SuccessOrExit(err = commandObj->FinishCommand());
}
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
bool emberAfScenesClusterStoreSceneCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::StoreScene::DecodableType & commandData)
{
auto fabricIndex = commandObj->GetAccessingFabricIndex();
auto & groupId = commandData.groupId;
auto & sceneId = commandData.sceneId;
EmberAfStatus status;
CHIP_ERROR err = CHIP_NO_ERROR;
emberAfScenesClusterPrintln("RX: StoreScene 0x%2x, 0x%x", groupId, sceneId);
status = emberAfScenesClusterStoreCurrentSceneCallback(fabricIndex, emberAfCurrentEndpoint(), groupId, sceneId);
// Store Scene commands are only responded to when they are addressed to a
// single device.
if (emberAfCurrentCommand()->type == EMBER_INCOMING_UNICAST || emberAfCurrentCommand()->type == EMBER_INCOMING_UNICAST_REPLY)
{
{
app::ConcreteCommandPath path = { emberAfCurrentEndpoint(), Scenes::Id, ZCL_STORE_SCENE_RESPONSE_COMMAND_ID };
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(path));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), groupId));
SuccessOrExit(err = writer->Put(TLV::ContextTag(2), sceneId));
SuccessOrExit(err = commandObj->FinishCommand());
}
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
bool emberAfScenesClusterRecallSceneCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::RecallScene::DecodableType & commandData)
{
auto fabricIndex = commandObj->GetAccessingFabricIndex();
auto & groupId = commandData.groupId;
auto & sceneId = commandData.sceneId;
// NOTE: TransitionTime field in the RecallScene command is currently
// ignored. Per Zigbee Alliance ZCL 7 (07-5123-07):
//
// "The transition time determines how long the tranition takes from the
// old cluster state to the new cluster state. It is recommended that, where
// possible (e.g., it is not possible for attributes with Boolean type),
// a gradual transition SHOULD take place from the old to the new state
// over this time. However, the exact transition is manufacturer dependent."
//
// The manufacturer-dependent implementation here is to immediately set
// all attributes to their scene-specified values, without regard to the
// value of TransitionTime.
EmberAfStatus status;
EmberStatus sendStatus = EMBER_SUCCESS;
emberAfScenesClusterPrintln("RX: RecallScene 0x%2x, 0x%x", groupId, sceneId);
status = emberAfScenesClusterRecallSavedSceneCallback(fabricIndex, emberAfCurrentEndpoint(), groupId, sceneId);
#ifdef EMBER_AF_PLUGIN_ZLL_SCENES_SERVER
if (status == EMBER_ZCL_STATUS_SUCCESS)
{
emberAfPluginZllScenesServerRecallSceneZllExtensions(emberAfCurrentEndpoint());
}
#endif
sendStatus = emberAfSendImmediateDefaultResponse(status);
if (EMBER_SUCCESS != sendStatus)
{
emberAfScenesClusterPrintln("Scenes: failed to send %s: 0x%x", "default_response", sendStatus);
}
return true;
}
bool emberAfScenesClusterGetSceneMembershipCallback(app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath,
const Commands::GetSceneMembership::DecodableType & commandData)
{
auto fabricIndex = commandObj->GetAccessingFabricIndex();
auto & groupId = commandData.groupId;
CHIP_ERROR err = CHIP_NO_ERROR;
EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS;
uint8_t sceneCount = 0;
uint8_t sceneList[MATTER_SCENES_TABLE_SIZE];
emberAfScenesClusterPrintln("RX: GetSceneMembership 0x%2x", groupId);
if (!isEndpointInGroup(fabricIndex, emberAfCurrentEndpoint(), groupId))
{
status = EMBER_ZCL_STATUS_INVALID_FIELD;
}
if (status == EMBER_ZCL_STATUS_SUCCESS)
{
uint8_t i;
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
EmberAfSceneTableEntry entry;
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
if (entry.endpoint == emberAfCurrentEndpoint() && entry.groupId == groupId)
{
sceneList[sceneCount] = entry.sceneId;
sceneCount++;
}
}
emberAfPutInt8uInResp(sceneCount);
for (i = 0; i < sceneCount; i++)
{
emberAfPutInt8uInResp(sceneList[i]);
}
}
{
app::ConcreteCommandPath path = { emberAfCurrentEndpoint(), Scenes::Id, ZCL_GET_SCENE_MEMBERSHIP_RESPONSE_COMMAND_ID };
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(path));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(
err = writer->Put(TLV::ContextTag(1),
static_cast<uint8_t>(MATTER_SCENES_TABLE_SIZE - emberAfPluginScenesServerNumSceneEntriesInUse())));
SuccessOrExit(err = writer->Put(TLV::ContextTag(2), groupId));
SuccessOrExit(err = writer->Put(TLV::ContextTag(3), sceneCount));
SuccessOrExit(err = writer->Put(TLV::ContextTag(4), ByteSpan(sceneList, sceneCount)));
SuccessOrExit(err = commandObj->FinishCommand());
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
EmberAfStatus emberAfScenesClusterStoreCurrentSceneCallback(chip::FabricIndex fabricIndex, EndpointId endpoint, GroupId groupId,
uint8_t sceneId)
{
EmberAfSceneTableEntry entry;
uint8_t i, index = EMBER_AF_SCENE_TABLE_NULL_INDEX;
if (!isEndpointInGroup(fabricIndex, endpoint, groupId))
{
return EMBER_ZCL_STATUS_INVALID_FIELD;
}
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
if (entry.endpoint == endpoint && entry.groupId == groupId && entry.sceneId == sceneId)
{
index = i;
break;
}
if (index == EMBER_AF_SCENE_TABLE_NULL_INDEX && entry.endpoint == EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID)
{
index = i;
}
}
// If the target index is still zero, the table is full.
if (index == EMBER_AF_SCENE_TABLE_NULL_INDEX)
{
return EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
}
emberAfPluginScenesServerRetrieveSceneEntry(entry, index);
// When creating a new entry or refreshing an existing one, the extension
// fields are updated with the current state of other clusters on the device.
#ifdef ZCL_USING_ON_OFF_CLUSTER_SERVER
entry.hasOnOffValue = readServerAttribute(endpoint, OnOff::Id, ZCL_ON_OFF_ATTRIBUTE_ID, "on/off", (uint8_t *) &entry.onOffValue,
sizeof(entry.onOffValue));
#endif
#ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER
entry.hasCurrentLevelValue = readServerAttribute(endpoint, LevelControl::Id, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, "current level",
(uint8_t *) &entry.currentLevelValue, sizeof(entry.currentLevelValue));
#endif
#ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER
entry.hasOccupiedCoolingSetpointValue =
readServerAttribute(endpoint, Thermostat::Id, ZCL_OCCUPIED_COOLING_SETPOINT_ATTRIBUTE_ID, "occupied cooling setpoint",
(uint8_t *) &entry.occupiedCoolingSetpointValue, sizeof(entry.occupiedCoolingSetpointValue));
entry.hasOccupiedHeatingSetpointValue =
readServerAttribute(endpoint, Thermostat::Id, ZCL_OCCUPIED_HEATING_SETPOINT_ATTRIBUTE_ID, "occupied heating setpoint",
(uint8_t *) &entry.occupiedHeatingSetpointValue, sizeof(entry.occupiedHeatingSetpointValue));
entry.hasSystemModeValue = readServerAttribute(endpoint, Thermostat::Id, ZCL_SYSTEM_MODE_ATTRIBUTE_ID, "system mode",
(uint8_t *) &entry.systemModeValue, sizeof(entry.systemModeValue));
#endif
#ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
entry.hasCurrentXValue = readServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_CURRENT_X_ATTRIBUTE_ID, "current x",
(uint8_t *) &entry.currentXValue, sizeof(entry.currentXValue));
entry.hasCurrentYValue = readServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_CURRENT_Y_ATTRIBUTE_ID, "current y",
(uint8_t *) &entry.currentYValue, sizeof(entry.currentYValue));
entry.hasEnhancedCurrentHueValue =
readServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_ENHANCED_CURRENT_HUE_ATTRIBUTE_ID, "enhanced current hue",
(uint8_t *) &entry.enhancedCurrentHueValue, sizeof(entry.enhancedCurrentHueValue));
entry.hasCurrentSaturationValue =
readServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_CURRENT_SATURATION_ATTRIBUTE_ID, "current saturation",
(uint8_t *) &entry.currentSaturationValue, sizeof(entry.currentSaturationValue));
entry.hasColorLoopActiveValue =
readServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_COLOR_LOOP_ACTIVE_ATTRIBUTE_ID, "color loop active",
(uint8_t *) &entry.colorLoopActiveValue, sizeof(entry.colorLoopActiveValue));
entry.hasColorLoopDirectionValue =
readServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_COLOR_LOOP_DIRECTION_ATTRIBUTE_ID, "color loop direction",
(uint8_t *) &entry.colorLoopDirectionValue, sizeof(entry.colorLoopDirectionValue));
entry.hasColorLoopTimeValue =
readServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_COLOR_LOOP_TIME_ATTRIBUTE_ID, "color loop time",
(uint8_t *) &entry.colorLoopTimeValue, sizeof(entry.colorLoopTimeValue));
entry.hasColorTemperatureMiredsValue =
readServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_COLOR_TEMPERATURE_ATTRIBUTE_ID, "color temp mireds",
(uint8_t *) &entry.colorTemperatureMiredsValue, sizeof(entry.colorTemperatureMiredsValue));
#endif // ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
#ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER
entry.hasLockStateValue = readServerAttribute(endpoint, DoorLock::Id, ZCL_LOCK_STATE_ATTRIBUTE_ID, "lock state",
(uint8_t *) &entry.lockStateValue, sizeof(entry.lockStateValue));
#endif
#ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER
entry.hasCurrentPositionLiftPercentageValue = readServerAttribute(
endpoint, WindowCovering::Id, ZCL_WC_CURRENT_POSITION_LIFT_PERCENTAGE_ATTRIBUTE_ID, "currentPositionLiftPercentage",
(uint8_t *) &entry.currentPositionLiftPercentageValue, sizeof(entry.currentPositionLiftPercentageValue));
entry.hasCurrentPositionTiltPercentageValue = readServerAttribute(
endpoint, WindowCovering::Id, ZCL_WC_CURRENT_POSITION_TILT_PERCENTAGE_ATTRIBUTE_ID, "currentPositionTiltPercentage",
(uint8_t *) &entry.currentPositionTiltPercentageValue, sizeof(entry.currentPositionTiltPercentageValue));
entry.hasTargetPositionLiftPercent100thsValue = readServerAttribute(
endpoint, WindowCovering::Id, ZCL_WC_TARGET_POSITION_LIFT_PERCENT100_THS_ATTRIBUTE_ID, "targetPositionLiftPercent100ths",
(uint8_t *) &entry.targetPositionLiftPercent100thsValue, sizeof(entry.targetPositionLiftPercent100thsValue));
entry.hasTargetPositionTiltPercent100thsValue = readServerAttribute(
endpoint, WindowCovering::Id, ZCL_WC_TARGET_POSITION_TILT_PERCENT100_THS_ATTRIBUTE_ID, "targetPositionTiltPercent100ths",
(uint8_t *) &entry.targetPositionTiltPercent100thsValue, sizeof(entry.targetPositionTiltPercent100thsValue));
#endif
// When creating a new entry, the name is set to the null string (i.e., the
// length is set to zero) and the transition time is set to zero. The scene
// count must be increased and written to the attribute table when adding a
// new scene. Otherwise, these fields and the count are left alone.
if (i != index)
{
entry.endpoint = endpoint;
entry.groupId = groupId;
entry.sceneId = sceneId;
#if defined(MATTER_CLUSTER_SCENE_NAME_SUPPORT) && MATTER_CLUSTER_SCENE_NAME_SUPPORT
entry.name[0] = 0;
#endif
entry.transitionTime = 0;
entry.transitionTime100ms = 0;
emberAfPluginScenesServerIncrNumSceneEntriesInUse();
emberAfScenesSetSceneCountAttribute(endpoint, emberAfPluginScenesServerNumSceneEntriesInUse());
}
// Save the scene entry and mark is as valid by storing its scene and group
// ids in the attribute table and setting valid to true.
emberAfPluginScenesServerSaveSceneEntry(entry, index);
emberAfScenesMakeValid(endpoint, sceneId, groupId);
return EMBER_ZCL_STATUS_SUCCESS;
}
EmberAfStatus emberAfScenesClusterRecallSavedSceneCallback(chip::FabricIndex fabricIndex, EndpointId endpoint, GroupId groupId,
uint8_t sceneId)
{
if (!isEndpointInGroup(fabricIndex, endpoint, groupId))
{
return EMBER_ZCL_STATUS_INVALID_FIELD;
}
uint8_t i;
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
EmberAfSceneTableEntry entry;
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
if (entry.endpoint == endpoint && entry.groupId == groupId && entry.sceneId == sceneId)
{
#ifdef ZCL_USING_ON_OFF_CLUSTER_SERVER
if (entry.hasOnOffValue)
{
writeServerAttribute(endpoint, OnOff::Id, ZCL_ON_OFF_ATTRIBUTE_ID, "on/off", (uint8_t *) &entry.onOffValue,
ZCL_BOOLEAN_ATTRIBUTE_TYPE);
}
#endif
#ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER
if (entry.hasCurrentLevelValue)
{
writeServerAttribute(endpoint, LevelControl::Id, ZCL_CURRENT_LEVEL_ATTRIBUTE_ID, "current level",
(uint8_t *) &entry.currentLevelValue, ZCL_INT8U_ATTRIBUTE_TYPE);
}
#endif
#ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER
if (entry.hasOccupiedCoolingSetpointValue)
{
writeServerAttribute(endpoint, Thermostat::Id, ZCL_OCCUPIED_COOLING_SETPOINT_ATTRIBUTE_ID,
"occupied cooling setpoint", (uint8_t *) &entry.occupiedCoolingSetpointValue,
ZCL_INT16S_ATTRIBUTE_TYPE);
}
if (entry.hasOccupiedHeatingSetpointValue)
{
writeServerAttribute(endpoint, Thermostat::Id, ZCL_OCCUPIED_HEATING_SETPOINT_ATTRIBUTE_ID,
"occupied heating setpoint", (uint8_t *) &entry.occupiedHeatingSetpointValue,
ZCL_INT16S_ATTRIBUTE_TYPE);
}
if (entry.hasSystemModeValue)
{
writeServerAttribute(endpoint, Thermostat::Id, ZCL_SYSTEM_MODE_ATTRIBUTE_ID, "system mode",
(uint8_t *) &entry.systemModeValue, ZCL_INT8U_ATTRIBUTE_TYPE);
}
#endif
#ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
if (entry.hasCurrentXValue)
{
writeServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_CURRENT_X_ATTRIBUTE_ID, "current x",
(uint8_t *) &entry.currentXValue, ZCL_INT16U_ATTRIBUTE_TYPE);
}
if (entry.hasCurrentYValue)
{
writeServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_CURRENT_Y_ATTRIBUTE_ID, "current y",
(uint8_t *) &entry.currentYValue, ZCL_INT16U_ATTRIBUTE_TYPE);
}
if (entry.hasEnhancedCurrentHueValue)
{
writeServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_ENHANCED_CURRENT_HUE_ATTRIBUTE_ID,
"enhanced current hue", (uint8_t *) &entry.enhancedCurrentHueValue, ZCL_INT16U_ATTRIBUTE_TYPE);
}
if (entry.hasCurrentSaturationValue)
{
writeServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_CURRENT_SATURATION_ATTRIBUTE_ID,
"current saturation", (uint8_t *) &entry.currentSaturationValue, ZCL_INT8U_ATTRIBUTE_TYPE);
}
if (entry.hasColorLoopActiveValue)
{
writeServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_COLOR_LOOP_ACTIVE_ATTRIBUTE_ID,
"color loop active", (uint8_t *) &entry.colorLoopActiveValue, ZCL_INT8U_ATTRIBUTE_TYPE);
}
if (entry.hasColorLoopDirectionValue)
{
writeServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_COLOR_LOOP_DIRECTION_ATTRIBUTE_ID,
"color loop direction", (uint8_t *) &entry.colorLoopDirectionValue, ZCL_INT8U_ATTRIBUTE_TYPE);
}
if (entry.hasColorLoopTimeValue)
{
writeServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_COLOR_LOOP_TIME_ATTRIBUTE_ID, "color loop time",
(uint8_t *) &entry.colorLoopTimeValue, ZCL_INT16U_ATTRIBUTE_TYPE);
}
if (entry.hasColorTemperatureMiredsValue)
{
writeServerAttribute(endpoint, ColorControl::Id, ZCL_COLOR_CONTROL_COLOR_TEMPERATURE_ATTRIBUTE_ID,
"color temp mireds", (uint8_t *) &entry.colorTemperatureMiredsValue,
ZCL_INT16U_ATTRIBUTE_TYPE);
}
#endif // ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
#ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER
if (entry.hasLockStateValue)
{
writeServerAttribute(endpoint, DoorLock::Id, ZCL_LOCK_STATE_ATTRIBUTE_ID, "lock state",
(uint8_t *) &entry.lockStateValue, ZCL_INT8U_ATTRIBUTE_TYPE);
}
#endif
#ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER
if (entry.hasCurrentPositionLiftPercentageValue)
{
writeServerAttribute(endpoint, WindowCovering::Id, ZCL_WC_CURRENT_POSITION_LIFT_PERCENTAGE_ATTRIBUTE_ID,
"CurrentPositionLiftPercentage", (uint8_t *) &entry.currentPositionLiftPercentageValue,
ZCL_INT8U_ATTRIBUTE_TYPE);
}
if (entry.hasCurrentPositionTiltPercentageValue)
{
writeServerAttribute(endpoint, WindowCovering::Id, ZCL_WC_CURRENT_POSITION_TILT_PERCENTAGE_ATTRIBUTE_ID,
"CurrentPositionTiltPercentage", (uint8_t *) &entry.currentPositionTiltPercentageValue,
ZCL_INT8U_ATTRIBUTE_TYPE);
}
if (entry.hasTargetPositionLiftPercent100thsValue)
{
writeServerAttribute(endpoint, WindowCovering::Id, ZCL_WC_TARGET_POSITION_LIFT_PERCENT100_THS_ATTRIBUTE_ID,
"TargetPositionLiftPercent100ths", (uint8_t *) &entry.targetPositionLiftPercent100thsValue,
ZCL_INT16U_ATTRIBUTE_TYPE);
}
if (entry.hasTargetPositionTiltPercent100thsValue)
{
writeServerAttribute(endpoint, WindowCovering::Id, ZCL_WC_TARGET_POSITION_TILT_PERCENT100_THS_ATTRIBUTE_ID,
"TargetPositionTiltPercent100ths", (uint8_t *) &entry.targetPositionTiltPercent100thsValue,
ZCL_INT16U_ATTRIBUTE_TYPE);
}
#endif
emberAfScenesMakeValid(endpoint, sceneId, groupId);
return EMBER_ZCL_STATUS_SUCCESS;
}
}
return EMBER_ZCL_STATUS_NOT_FOUND;
}
bool emberAfPluginScenesServerParseAddScene(
app::CommandHandler * commandObj, const EmberAfClusterCommand * cmd, GroupId groupId, uint8_t sceneId, uint16_t transitionTime,
const CharSpan & sceneName, const app::DataModel::DecodableList<Structs::ExtensionFieldSet::DecodableType> & extensionFieldSets)
{
CHIP_ERROR err = CHIP_NO_ERROR;
EmberAfSceneTableEntry entry;
EmberAfStatus status;
bool enhanced = (cmd->commandId == ZCL_ENHANCED_ADD_SCENE_COMMAND_ID);
auto fabricIndex = commandObj->GetAccessingFabricIndex();
EndpointId endpoint = cmd->apsFrame->destinationEndpoint;
uint8_t i, index = EMBER_AF_SCENE_TABLE_NULL_INDEX;
emberAfScenesClusterPrintln("RX: %pAddScene 0x%2x, 0x%x, 0x%2x, \"%.*s\"", (enhanced ? "Enhanced" : ""), groupId, sceneId,
transitionTime, static_cast<int>(sceneName.size()), sceneName.data());
auto fieldSetIter = extensionFieldSets.begin();
// Add Scene commands can only reference groups to which we belong.
if (!isEndpointInGroup(fabricIndex, endpoint, groupId))
{
status = EMBER_ZCL_STATUS_INVALID_FIELD;
goto kickout;
}
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
if (entry.endpoint == endpoint && entry.groupId == groupId && entry.sceneId == sceneId)
{
index = i;
break;
}
if (index == EMBER_AF_SCENE_TABLE_NULL_INDEX && entry.endpoint == EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID)
{
index = i;
}
}
// If the target index is still zero, the table is full.
if (index == EMBER_AF_SCENE_TABLE_NULL_INDEX)
{
status = EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
goto kickout;
}
emberAfPluginScenesServerRetrieveSceneEntry(entry, index);
// The transition time is specified in seconds in the regular version of the
// command and tenths of a second in the enhanced version.
if (enhanced)
{
entry.transitionTime = transitionTime / 10;
entry.transitionTime100ms = (uint8_t)(transitionTime - entry.transitionTime * 10);
}
else
{
entry.transitionTime = transitionTime;
entry.transitionTime100ms = 0;
}
#if defined(MATTER_CLUSTER_SCENE_NAME_SUPPORT) && MATTER_CLUSTER_SCENE_NAME_SUPPORT
emberAfCopyString(entry.name, Uint8::from_const_char(sceneName.data()), ZCL_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH);
#endif
// When adding a new scene, wipe out all of the extensions before parsing the
// extension field sets data.
if (i != index)
{
#ifdef ZCL_USING_ON_OFF_CLUSTER_SERVER
entry.hasOnOffValue = false;
#endif
#ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER
entry.hasCurrentLevelValue = false;
#endif
#ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER
entry.hasOccupiedCoolingSetpointValue = false;
entry.hasOccupiedHeatingSetpointValue = false;
entry.hasSystemModeValue = false;
#endif
#ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
entry.hasCurrentXValue = false;
entry.hasCurrentYValue = false;
entry.hasEnhancedCurrentHueValue = false;
entry.hasCurrentSaturationValue = false;
entry.hasColorLoopActiveValue = false;
entry.hasColorLoopDirectionValue = false;
entry.hasColorLoopTimeValue = false;
entry.hasColorTemperatureMiredsValue = false;
#endif // ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
#ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER
entry.hasLockStateValue = false;
#endif
#ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER
entry.hasCurrentPositionLiftPercentageValue = false;
entry.hasCurrentPositionTiltPercentageValue = false;
entry.hasTargetPositionLiftPercent100thsValue = false;
entry.hasTargetPositionTiltPercent100thsValue = false;
#endif
}
while (fieldSetIter.Next())
{
auto & fieldSet = fieldSetIter.GetValue();
ClusterId clusterId = fieldSet.clusterId;
// TODO: We need to encode scene field sets in TLV.
// https://github.com/project-chip/connectedhomeip/issues/10334
switch (clusterId)
{
#if 0
#ifdef ZCL_USING_ON_OFF_CLUSTER_SERVER
case OnOff::Id:
// We only know of one extension for the On/Off cluster and it is just one
// byte, which means we can skip some logic for this cluster. If other
// extensions are added in this cluster, more logic will be needed here.
entry.hasOnOffValue = true;
entry.onOffValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
break;
#endif
#ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER
case LevelControl::Id:
// We only know of one extension for the Level Control cluster and it is
// just one byte, which means we can skip some logic for this cluster. If
// other extensions are added in this cluster, more logic will be needed
// here.
entry.hasCurrentLevelValue = true;
entry.currentLevelValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
break;
#endif
#ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER
case Thermostat::Id:
if (length < 2)
{
break;
}
entry.hasOccupiedCoolingSetpointValue = true;
entry.occupiedCoolingSetpointValue =
(int16_t) emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex = static_cast<uint16_t>(extensionFieldSetsIndex + 2);
length = static_cast<uint8_t>(length - 2);
if (length < 2)
{
break;
}
entry.hasOccupiedHeatingSetpointValue = true;
entry.occupiedHeatingSetpointValue =
(int16_t) emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex = static_cast<uint16_t>(extensionFieldSetsIndex + 2);
length = static_cast<uint8_t>(length - 2);
if (length < 1)
{
break;
}
entry.hasSystemModeValue = true;
entry.systemModeValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
// If additional Thermostat extensions are added, adjust the index and
// length variables here.
break;
#endif
#ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
case ColorControl::Id:
if (length < 2)
{
break;
}
entry.hasCurrentXValue = true;
entry.currentXValue = emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex = static_cast<uint16_t>(extensionFieldSetsIndex + 2);
length = static_cast<uint8_t>(length - 2);
if (length < 2)
{
break;
}
entry.hasCurrentYValue = true;
entry.currentYValue = emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
if (enhanced)
{
extensionFieldSetsIndex = static_cast<uint16_t>(extensionFieldSetsIndex + 2);
length = static_cast<uint8_t>(length - 2);
;
if (length < 2)
{
break;
}
entry.hasEnhancedCurrentHueValue = true;
entry.enhancedCurrentHueValue =
emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex = static_cast<uint16_t>(extensionFieldSetsIndex + 2);
length = static_cast<uint8_t>(length - 2);
if (length < 1)
{
break;
}
entry.hasCurrentSaturationValue = true;
entry.currentSaturationValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex++;
length--;
if (length < 1)
{
break;
}
entry.hasColorLoopActiveValue = true;
entry.colorLoopActiveValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex++;
length--;
if (length < 1)
{
break;
}
entry.hasColorLoopDirectionValue = true;
entry.colorLoopDirectionValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex++;
length--;
if (length < 2)
{
break;
}
entry.hasColorLoopTimeValue = true;
entry.colorLoopTimeValue = emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex = static_cast<uint16_t>(extensionFieldSetsIndex + 2);
length = static_cast<uint8_t>(length - 2);
if (length < 2)
{
break;
}
entry.hasColorTemperatureMiredsValue = true;
entry.colorTemperatureMiredsValue =
emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
}
// If additional Color Control extensions are added, adjust the index and
// length variables here.
break;
#endif // ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
#ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER
case DoorLock::Id:
// We only know of one extension for the Door Lock cluster and it is just
// one byte, which means we can skip some logic for this cluster. If
// other extensions are added in this cluster, more logic will be needed
// here.
entry.hasLockStateValue = true;
entry.lockStateValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
break;
#endif
#ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER
case WindowCovering::Id:
// If we're here, we know we have at least one byte, so we can skip the
// length check for the first field.
entry.hasCurrentPositionLiftPercentageValue = true;
entry.currentPositionLiftPercentageValue =
emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex++;
length--;
if (length < 1)
{
break;
}
entry.hasCurrentPositionTiltPercentageValue = true;
entry.currentPositionTiltPercentageValue =
emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex++;
length--;
if (length < 2)
{
break;
}
entry.hasTargetPositionLiftPercent100thsValue = true;
entry.targetPositionLiftPercent100thsValue =
emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
extensionFieldSetsIndex = static_cast<uint16_t>(extensionFieldSetsIndex + 2);
length = static_cast<uint8_t>(length - 2);
if (length < 2)
{
break;
}
entry.hasTargetPositionTiltPercent100thsValue = true;
entry.targetPositionTiltPercent100thsValue =
emberAfGetInt16u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen);
// If additional Window Covering extensions are added, adjust the index
// and length variables here.
break;
#endif
#endif // if 0 disabling all the code.
default:
break;
}
}
if (fieldSetIter.GetStatus() != CHIP_NO_ERROR)
{
status = EMBER_ZCL_STATUS_MALFORMED_COMMAND;
goto kickout;
}
// If we got this far, we either added a new entry or updated an existing one.
// If we added, store the basic data and increment the scene count. In either
// case, save the entry.
if (i != index)
{
entry.endpoint = endpoint;
entry.groupId = groupId;
entry.sceneId = sceneId;
emberAfPluginScenesServerIncrNumSceneEntriesInUse();
emberAfScenesSetSceneCountAttribute(endpoint, emberAfPluginScenesServerNumSceneEntriesInUse());
}
emberAfPluginScenesServerSaveSceneEntry(entry, index);
status = EMBER_ZCL_STATUS_SUCCESS;
kickout:
// Add Scene commands are only responded to when they are addressed to a
// single device.
if (emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST && emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST_REPLY)
{
return true;
}
{
app::ConcreteCommandPath path = { emberAfCurrentEndpoint(), Scenes::Id, ZCL_ADD_SCENE_RESPONSE_COMMAND_ID };
if (enhanced)
{
path = { emberAfCurrentEndpoint(), Scenes::Id, ZCL_ENHANCED_ADD_SCENE_RESPONSE_COMMAND_ID };
}
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(path));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), groupId));
SuccessOrExit(err = writer->Put(TLV::ContextTag(2), sceneId));
SuccessOrExit(err = commandObj->FinishCommand());
}
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
bool emberAfPluginScenesServerParseViewScene(app::CommandHandler * commandObj, const EmberAfClusterCommand * cmd, GroupId groupId,
uint8_t sceneId)
{
CHIP_ERROR err = CHIP_NO_ERROR;
EmberAfSceneTableEntry entry = {};
EmberAfStatus status = EMBER_ZCL_STATUS_NOT_FOUND;
bool enhanced = (cmd->commandId == ZCL_ENHANCED_VIEW_SCENE_COMMAND_ID);
FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex();
EndpointId endpoint = cmd->apsFrame->destinationEndpoint;
emberAfScenesClusterPrintln("RX: %pViewScene 0x%2x, 0x%x", (enhanced ? "Enhanced" : ""), groupId, sceneId);
// View Scene commands can only reference groups which we belong to.
if (!isEndpointInGroup(fabricIndex, endpoint, groupId))
{
status = EMBER_ZCL_STATUS_INVALID_FIELD;
}
else
{
uint8_t i;
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
if (entry.endpoint == endpoint && entry.groupId == groupId && entry.sceneId == sceneId)
{
status = EMBER_ZCL_STATUS_SUCCESS;
break;
}
}
}
// The status, group id, and scene id are always included in the response, but
// the transition time, name, and extension fields are only included if the
// scene was found.
app::ConcreteCommandPath path = { emberAfCurrentEndpoint(), Scenes::Id, ZCL_VIEW_SCENE_RESPONSE_COMMAND_ID };
if (enhanced)
{
path = { emberAfCurrentEndpoint(), Scenes::Id, ZCL_ENHANCED_VIEW_SCENE_RESPONSE_COMMAND_ID };
}
TLV::TLVWriter * writer = nullptr;
SuccessOrExit(err = commandObj->PrepareCommand(path));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(err = writer->Put(TLV::ContextTag(0), status));
SuccessOrExit(err = writer->Put(TLV::ContextTag(1), groupId));
SuccessOrExit(err = writer->Put(TLV::ContextTag(2), sceneId));
SuccessOrExit(err = writer->Put(TLV::ContextTag(3),
static_cast<uint16_t>(enhanced ? entry.transitionTime * 10 + entry.transitionTime100ms
: entry.transitionTime)));
#if defined(MATTER_CLUSTER_SCENE_NAME_SUPPORT) && MATTER_CLUSTER_SCENE_NAME_SUPPORT
SuccessOrExit(err = writer->Put(TLV::ContextTag(4), entry.name));
#else
SuccessOrExit(err = writer->PutString(TLV::ContextTag(4), ""));
#endif
// #6620: Need the build the array for response.
SuccessOrExit(err = writer->Put(TLV::ContextTag(5), ByteSpan(nullptr, 0)));
SuccessOrExit(err = commandObj->FinishCommand());
/*
if (status == EMBER_ZCL_STATUS_SUCCESS)
{
// The transition time is returned in seconds in the regular version of the
// command and tenths of a second in the enhanced version.
emberAfPutInt16uInResp(
static_cast<uint16_t>(enhanced ? entry.transitionTime * 10 + entry.transitionTime100ms : entry.transitionTime));
#if defined(MATTER_CLUSTER_SCENE_NAME_SUPPORT) && MATTER_CLUSTER_SCENE_NAME_SUPPORT
emberAfPutStringInResp(entry.name);
#else
emberAfPutInt8uInResp(0); // name length
#endif
#ifdef ZCL_USING_ON_OFF_CLUSTER_SERVER
if (entry.hasOnOffValue)
{
emberAfPutInt16uInResp(OnOff::Id);
emberAfPutInt8uInResp(1); // length
emberAfPutInt8uInResp(entry.onOffValue);
}
#endif
#ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER
if (entry.hasCurrentLevelValue)
{
emberAfPutInt16uInResp(LevelControl::Id);
emberAfPutInt8uInResp(1); // length
emberAfPutInt8uInResp(entry.currentLevelValue);
}
#endif
#ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER
if (entry.hasOccupiedCoolingSetpointValue)
{
uint8_t * length;
emberAfPutInt16uInResp(Thermostat::Id);
length = &appResponseData[appResponseLength];
emberAfPutInt8uInResp(0); // temporary length
emberAfPutInt16sInResp(entry.occupiedCoolingSetpointValue);
*length += 2;
if (entry.hasOccupiedHeatingSetpointValue)
{
emberAfPutInt16sInResp(entry.occupiedHeatingSetpointValue);
*length += 2;
if (entry.hasSystemModeValue)
{
emberAfPutInt8uInResp(entry.systemModeValue);
(*length)++;
}
}
}
#endif
#ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
if (entry.hasCurrentXValue)
{
uint8_t * length;
emberAfPutInt16uInResp(ColorControl::Id);
length = &appResponseData[appResponseLength];
emberAfPutInt8uInResp(0); // temporary length
emberAfPutInt16uInResp(entry.currentXValue);
*length = static_cast<uint8_t>(*length + 2);
if (entry.hasCurrentYValue)
{
emberAfPutInt16uInResp(entry.currentYValue);
*length = static_cast<uint8_t>(*length + 2);
if (enhanced)
{
if (entry.hasEnhancedCurrentHueValue)
{
emberAfPutInt16uInResp(entry.enhancedCurrentHueValue);
*length = static_cast<uint8_t>(*length + 2);
if (entry.hasCurrentSaturationValue)
{
emberAfPutInt8uInResp(entry.currentSaturationValue);
(*length)++;
if (entry.hasColorLoopActiveValue)
{
emberAfPutInt8uInResp(entry.colorLoopActiveValue);
(*length)++;
if (entry.hasColorLoopDirectionValue)
{
emberAfPutInt8uInResp(entry.colorLoopDirectionValue);
(*length)++;
if (entry.hasColorLoopTimeValue)
{
emberAfPutInt16uInResp(entry.colorLoopTimeValue);
*length = static_cast<uint8_t>(*length + 2);
if (entry.hasColorTemperatureMiredsValue)
{
emberAfPutInt16uInResp(entry.colorTemperatureMiredsValue);
*length = static_cast<uint8_t>(*length + 2);
}
}
}
}
}
}
}
}
}
#endif // ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER
#ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER
if (entry.hasLockStateValue)
{
emberAfPutInt16uInResp(DoorLock::Id);
emberAfPutInt8uInResp(1); // length
emberAfPutInt8uInResp(entry.lockStateValue);
}
#endif
#ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER
if (entry.hasCurrentPositionLiftPercentageValue)
{
uint8_t * length;
emberAfPutInt16uInResp(WindowCovering::Id);
length = &appResponseData[appResponseLength];
emberAfPutInt8uInResp(0); // temporary length
emberAfPutInt8uInResp(entry.currentPositionLiftPercentageValue);
(*length)++;
if (entry.hasCurrentPositionTiltPercentageValue)
{
emberAfPutInt8uInResp(entry.currentPositionTiltPercentageValue);
(*length)++;
if (entry.hasTargetPositionLiftPercent100thsValue)
{
emberAfPutInt16uInResp(entry.targetPositionLiftPercent100thsValue);
*length = static_cast<uint8_t>(*length + 2);
if (entry.hasTargetPositionTiltPercent100thsValue)
{
emberAfPutInt16uInResp(entry.targetPositionTiltPercent100thsValue);
*length = static_cast<uint8_t>(*length + 2);
}
}
}
}
#endif
}
// View Scene commands are only responded to when they are addressed to a
// single device.
if (emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST && emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST_REPLY)
{
return true;
}
sendStatus = emberAfSendResponse();
if (EMBER_SUCCESS != sendStatus)
{
emberAfScenesClusterPrintln("Scenes: failed to send %s response: 0x%x", "view_scene", sendStatus);
}
*/
exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Zcl, "Failed to encode response command.");
}
return true;
}
void emberAfScenesClusterRemoveScenesInGroupCallback(EndpointId endpoint, GroupId groupId)
{
uint8_t i;
for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++)
{
EmberAfSceneTableEntry entry;
emberAfPluginScenesServerRetrieveSceneEntry(entry, i);
if (entry.endpoint == endpoint && entry.groupId == groupId)
{
entry.groupId = ZCL_SCENES_GLOBAL_SCENE_GROUP_ID;
entry.endpoint = EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID;
emberAfPluginScenesServerSaveSceneEntry(entry, i);
emberAfPluginScenesServerDecrNumSceneEntriesInUse();
emberAfScenesSetSceneCountAttribute(emberAfCurrentEndpoint(), emberAfPluginScenesServerNumSceneEntriesInUse());
}
}
}
void MatterScenesPluginServerInitCallback() {}