| /** |
| * |
| * 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/attributes/Accessors.h> |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app-common/zap-generated/ids/Clusters.h> |
| #include <app-common/zap-generated/ids/Commands.h> |
| #include <app/CommandHandler.h> |
| #include <app/ConcreteCommandPath.h> |
| #include <app/util/af.h> |
| #include <app/util/attribute-storage-null-handling.h> |
| #include <app/util/config.h> |
| #include <app/util/error-mapping.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; |
| using namespace chip::app::Clusters::Scenes::Commands; |
| |
| uint8_t emberAfPluginScenesServerEntriesInUse = 0; |
| #if !defined(EMBER_AF_PLUGIN_SCENES_USE_TOKENS) || defined(EZSP_HOST) |
| EmberAfSceneTableEntry emberAfPluginScenesServerSceneTable[MATTER_SCENES_TABLE_SIZE]; |
| #endif |
| |
| static bool logReadError(EmberAfStatus status, const char * attributeName) |
| { |
| // Don't log errors for the "this cluster is not even supported" cases. |
| if (status != EMBER_ZCL_STATUS_SUCCESS && status != EMBER_ZCL_STATUS_UNSUPPORTED_ENDPOINT && |
| status != EMBER_ZCL_STATUS_UNSUPPORTED_CLUSTER) |
| { |
| emberAfScenesClusterPrintln("ERR: %sing %s 0x%x", "read", attributeName, status); |
| } |
| return status == EMBER_ZCL_STATUS_SUCCESS; |
| } |
| |
| static EmberAfStatus logWriteError(EmberAfStatus status, const char * attributeName) |
| { |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| emberAfScenesClusterPrintln("ERR: %sing %s 0x%x", "writ", attributeName, 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); |
| logWriteError(Attributes::NameSupport::Set(endpoint, nameSupport), "NameSupport"); |
| } |
| #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 logWriteError(Attributes::SceneCount::Set(endpoint, newCount), "SceneCount"); |
| } |
| |
| EmberAfStatus emberAfScenesMakeValid(EndpointId endpoint, uint8_t sceneId, GroupId groupId) |
| { |
| EmberAfStatus status; |
| bool valid = true; |
| |
| // scene ID |
| status = logWriteError(Attributes::CurrentScene::Set(endpoint, sceneId), "CurrentScene"); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| return status; |
| } |
| |
| // group ID |
| status = logWriteError(Attributes::CurrentGroup::Set(endpoint, groupId), "CurrentGroup"); |
| if (status != EMBER_ZCL_STATUS_SUCCESS) |
| { |
| return status; |
| } |
| |
| status = logWriteError(Attributes::SceneValid::Set(endpoint, valid), "SceneValid"); |
| return status; |
| } |
| |
| EmberAfStatus emberAfScenesClusterMakeInvalidCallback(EndpointId endpoint) |
| { |
| bool valid = false; |
| return logWriteError(Attributes::SceneValid::Set(endpoint, valid), "SceneValid"); |
| } |
| |
| 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 |
| if (entry.currentLevelValue.IsNull()) |
| { |
| emberAfCorePrint(" lvl null"); |
| } |
| else |
| { |
| emberAfCorePrint(" lvl %x", entry.currentLevelValue.Value()); |
| } |
| #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 |
| if (entry.lockStateValue.IsNull()) |
| { |
| emberAfCorePrint(" door null"); |
| } |
| else |
| { |
| emberAfCorePrint(" door %x", to_underlying(entry.lockStateValue.Value())); |
| } |
| #endif |
| #ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER |
| if (!entry.currentPositionLiftPercentageValue.IsNull() && !entry.currentPositionTiltPercentageValue.IsNull()) |
| { |
| emberAfCorePrint(" Window current percentage Lift %3u, Tilt %3u", entry.currentPositionLiftPercentageValue.Value(), |
| entry.currentPositionTiltPercentageValue.Value()); |
| } |
| else if (!entry.currentPositionLiftPercentageValue.IsNull()) |
| { |
| emberAfCorePrint(" Window current percentage Lift %3u", entry.currentPositionLiftPercentageValue.Value()); |
| } |
| else if (!entry.currentPositionTiltPercentageValue.IsNull()) |
| { |
| emberAfCorePrint(" Window current percentage Tilt %3u", entry.currentPositionTiltPercentageValue.Value()); |
| } |
| |
| if (!entry.targetPositionLiftPercent100thsValue.IsNull() && !entry.targetPositionTiltPercent100thsValue.IsNull()) |
| { |
| emberAfCorePrint(" Window target percent100ths Lift %5u, Tilt %5u", |
| entry.targetPositionLiftPercent100thsValue.Value(), |
| entry.targetPositionTiltPercent100thsValue.Value()); |
| } |
| else if (!entry.targetPositionLiftPercent100thsValue.IsNull()) |
| { |
| emberAfCorePrint(" Window target percent100ths Lift %5u", entry.targetPositionLiftPercent100thsValue.Value()); |
| } |
| else if (!entry.targetPositionTiltPercent100thsValue.IsNull()) |
| { |
| emberAfCorePrint(" Window target percent100ths Tilt %5u", entry.targetPositionTiltPercent100thsValue.Value()); |
| } |
| #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, commandPath, 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, commandPath, 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, commandPath.mEndpointId, groupId)) |
| { |
| status = EMBER_ZCL_STATUS_INVALID_COMMAND; |
| } |
| else |
| { |
| uint8_t i; |
| for (i = 0; i < MATTER_SCENES_TABLE_SIZE; i++) |
| { |
| EmberAfSceneTableEntry entry; |
| emberAfPluginScenesServerRetrieveSceneEntry(entry, i); |
| if (entry.endpoint == commandPath.mEndpointId && entry.groupId == groupId && entry.sceneId == sceneId) |
| { |
| entry.endpoint = EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID; |
| emberAfPluginScenesServerSaveSceneEntry(entry, i); |
| emberAfPluginScenesServerDecrNumSceneEntriesInUse(); |
| emberAfScenesSetSceneCountAttribute(commandPath.mEndpointId, emberAfPluginScenesServerNumSceneEntriesInUse()); |
| status = EMBER_ZCL_STATUS_SUCCESS; |
| break; |
| } |
| } |
| } |
| |
| app::ConcreteCommandPath path = { commandPath.mEndpointId, Scenes::Id, RemoveSceneResponse::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_COMMAND; |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| emberAfScenesClusterPrintln("RX: RemoveAllScenes 0x%2x", groupId); |
| |
| if (isEndpointInGroup(fabricIndex, commandPath.mEndpointId, 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 == commandPath.mEndpointId && entry.groupId == groupId) |
| { |
| entry.endpoint = EMBER_AF_SCENE_TABLE_UNUSED_ENDPOINT_ID; |
| emberAfPluginScenesServerSaveSceneEntry(entry, i); |
| emberAfPluginScenesServerDecrNumSceneEntriesInUse(); |
| } |
| } |
| emberAfScenesSetSceneCountAttribute(commandPath.mEndpointId, emberAfPluginScenesServerNumSceneEntriesInUse()); |
| } |
| |
| app::ConcreteCommandPath path = { commandPath.mEndpointId, Scenes::Id, RemoveAllScenesResponse::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, commandPath.mEndpointId, groupId, sceneId); |
| |
| app::ConcreteCommandPath path = { commandPath.mEndpointId, Scenes::Id, StoreSceneResponse::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; |
| emberAfScenesClusterPrintln("RX: RecallScene 0x%2x, 0x%x", groupId, sceneId); |
| status = emberAfScenesClusterRecallSavedSceneCallback(fabricIndex, commandPath.mEndpointId, groupId, sceneId); |
| #ifdef EMBER_AF_PLUGIN_ZLL_SCENES_SERVER |
| if (status == EMBER_ZCL_STATUS_SUCCESS) |
| { |
| emberAfPluginZllScenesServerRecallSceneZllExtensions(commandPath.mEndpointId); |
| } |
| #endif |
| CHIP_ERROR sendErr = commandObj->AddStatus(commandPath, app::ToInteractionModelStatus(status)); |
| if (CHIP_NO_ERROR != sendErr) |
| { |
| emberAfScenesClusterPrintln("Scenes: failed to send %s: %" CHIP_ERROR_FORMAT, "status_response", sendErr.Format()); |
| } |
| 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, commandPath.mEndpointId, groupId)) |
| { |
| status = EMBER_ZCL_STATUS_INVALID_COMMAND; |
| } |
| |
| 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 == commandPath.mEndpointId && entry.groupId == groupId) |
| { |
| sceneList[sceneCount] = entry.sceneId; |
| sceneCount++; |
| } |
| } |
| emberAfPutInt8uInResp(sceneCount); |
| for (i = 0; i < sceneCount; i++) |
| { |
| emberAfPutInt8uInResp(sceneList[i]); |
| } |
| } |
| |
| { |
| app::ConcreteCommandPath path = { commandPath.mEndpointId, Scenes::Id, GetSceneMembershipResponse::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_COMMAND; |
| } |
| |
| 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_RESOURCE_EXHAUSTED; |
| } |
| |
| 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 = logReadError(OnOff::Attributes::OnOff::Get(endpoint, &entry.onOffValue), "OnOff"); |
| #endif |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER |
| entry.hasCurrentLevelValue = |
| logReadError(LevelControl::Attributes::CurrentLevel::Get(endpoint, entry.currentLevelValue), "CurrentLevel"); |
| #endif |
| #ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER |
| { |
| using namespace Thermostat::Attributes; |
| entry.hasOccupiedCoolingSetpointValue = |
| logReadError(OccupiedCoolingSetpoint::Get(endpoint, &entry.occupiedCoolingSetpointValue), "OccupiedCoolingSetpoint"); |
| entry.hasOccupiedHeatingSetpointValue = |
| logReadError(OccupiedHeatingSetpoint::Get(endpoint, &entry.occupiedHeatingSetpointValue), "OccupiedHeatingSetpoint"); |
| entry.hasSystemModeValue = logReadError(SystemMode::Get(endpoint, &entry.systemModeValue), "SystemMode"); |
| } |
| #endif |
| #ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER |
| { |
| using namespace ColorControl::Attributes; |
| entry.hasCurrentXValue = logReadError(CurrentX::Get(endpoint, &entry.currentXValue), "CurrentX"); |
| entry.hasCurrentYValue = logReadError(CurrentY::Get(endpoint, &entry.currentYValue), "CurrentY"); |
| entry.hasEnhancedCurrentHueValue = |
| logReadError(EnhancedCurrentHue::Get(endpoint, &entry.enhancedCurrentHueValue), "EnhancedCurrentHue"); |
| entry.hasCurrentSaturationValue = |
| logReadError(CurrentSaturation::Get(endpoint, &entry.currentSaturationValue), "CurrentSaturation"); |
| entry.hasColorLoopActiveValue = |
| logReadError(ColorLoopActive::Get(endpoint, &entry.colorLoopActiveValue), "ColorLoopActive"); |
| entry.hasColorLoopDirectionValue = |
| logReadError(ColorLoopDirection::Get(endpoint, &entry.colorLoopDirectionValue), "ColorLoopDirection"); |
| entry.hasColorLoopTimeValue = logReadError(ColorLoopTime::Get(endpoint, &entry.colorLoopTimeValue), "ColorLoopTime"); |
| entry.hasColorTemperatureMiredsValue = |
| logReadError(ColorTemperatureMireds::Get(endpoint, &entry.colorTemperatureMiredsValue), "ColorTemperatureMireds"); |
| } |
| #endif // ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER |
| #ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER |
| entry.hasLockStateValue = logReadError(DoorLock::Attributes::LockState::Get(endpoint, entry.lockStateValue), "LockState"); |
| #endif |
| #ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER |
| { |
| using namespace WindowCovering::Attributes; |
| entry.hasCurrentPositionLiftPercentageValue = |
| logReadError(CurrentPositionLiftPercentage::Get(endpoint, entry.currentPositionLiftPercentageValue), |
| "CurrentPositionLiftPercentage"); |
| entry.hasCurrentPositionTiltPercentageValue = |
| logReadError(CurrentPositionTiltPercentage::Get(endpoint, entry.currentPositionTiltPercentageValue), |
| "CurrentPositionTiltPercentage"); |
| entry.hasTargetPositionLiftPercent100thsValue = |
| logReadError(TargetPositionLiftPercent100ths::Get(endpoint, entry.targetPositionLiftPercent100thsValue), |
| "TragetPositionLiftPercent100ths"); |
| entry.hasTargetPositionTiltPercent100thsValue = |
| logReadError(TargetPositionTiltPercent100ths::Get(endpoint, entry.targetPositionTiltPercent100thsValue), |
| "TragetPositionTiltPercent100ths"); |
| } |
| #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_COMMAND; |
| } |
| |
| 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) |
| { |
| logWriteError(OnOff::Attributes::OnOff::Set(endpoint, entry.onOffValue), "OnOff"); |
| } |
| #endif |
| #ifdef ZCL_USING_LEVEL_CONTROL_CLUSTER_SERVER |
| if (entry.hasCurrentLevelValue) |
| { |
| logWriteError(LevelControl::Attributes::CurrentLevel::Set(endpoint, entry.currentLevelValue), "CurrentLevel"); |
| } |
| #endif |
| #ifdef ZCL_USING_THERMOSTAT_CLUSTER_SERVER |
| { |
| using namespace Thermostat::Attributes; |
| if (entry.hasOccupiedCoolingSetpointValue) |
| { |
| logWriteError(OccupiedCoolingSetpoint::Set(endpoint, entry.occupiedCoolingSetpointValue), |
| "OccupiedCoolingSetpoint"); |
| } |
| if (entry.hasOccupiedHeatingSetpointValue) |
| { |
| logWriteError(OccupiedHeatingSetpoint::Set(endpoint, entry.occupiedHeatingSetpointValue), |
| "OccupiedHeatingSetpoint"); |
| } |
| if (entry.hasSystemModeValue) |
| { |
| logWriteError(SystemMode::Set(endpoint, entry.systemModeValue), "SystemMode"); |
| } |
| } |
| #endif |
| #ifdef ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER |
| { |
| using namespace ColorControl::Attributes; |
| if (entry.hasCurrentXValue) |
| { |
| logWriteError(CurrentX::Set(endpoint, entry.currentXValue), "CurrentX"); |
| } |
| if (entry.hasCurrentYValue) |
| { |
| logWriteError(CurrentY::Set(endpoint, entry.currentXValue), "CurrentY"); |
| } |
| |
| if (entry.hasEnhancedCurrentHueValue) |
| { |
| logWriteError(EnhancedCurrentHue::Set(endpoint, entry.enhancedCurrentHueValue), "EnhancedCurrentHue"); |
| } |
| if (entry.hasCurrentSaturationValue) |
| { |
| logWriteError(CurrentSaturation::Set(endpoint, entry.currentSaturationValue), "CurrentSaturation"); |
| } |
| if (entry.hasColorLoopActiveValue) |
| { |
| logWriteError(ColorLoopActive::Set(endpoint, entry.colorLoopActiveValue), "ColorLoopActive"); |
| } |
| if (entry.hasColorLoopDirectionValue) |
| { |
| logWriteError(ColorLoopDirection::Set(endpoint, entry.colorLoopDirectionValue), "ColorLoopDirection"); |
| } |
| if (entry.hasColorLoopTimeValue) |
| { |
| logWriteError(ColorLoopTime::Set(endpoint, entry.colorLoopTimeValue), "ColorLoopTime"); |
| } |
| if (entry.hasColorTemperatureMiredsValue) |
| { |
| logWriteError(ColorTemperatureMireds::Set(endpoint, entry.colorTemperatureMiredsValue), |
| "ColorTemperatureMireds"); |
| } |
| } |
| #endif // ZCL_USING_COLOR_CONTROL_CLUSTER_SERVER |
| #ifdef ZCL_USING_DOOR_LOCK_CLUSTER_SERVER |
| if (entry.hasLockStateValue) |
| { |
| logWriteError(DoorLock::Attributes::LockState::Set(endpoint, entry.lockStateValue), "LockState"); |
| } |
| #endif |
| #ifdef ZCL_USING_WINDOW_COVERING_CLUSTER_SERVER |
| { |
| using namespace WindowCovering::Attributes; |
| if (entry.hasCurrentPositionLiftPercentageValue) |
| { |
| logWriteError(CurrentPositionLiftPercentage::Set(endpoint, entry.currentPositionLiftPercentageValue), |
| "CurrentPositionLiftPercentage"); |
| } |
| if (entry.hasCurrentPositionTiltPercentageValue) |
| { |
| logWriteError(CurrentPositionTiltPercentage::Set(endpoint, entry.currentPositionTiltPercentageValue), |
| "CurrentPositionTiltPercentage"); |
| } |
| if (entry.hasTargetPositionLiftPercent100thsValue) |
| { |
| logWriteError(TargetPositionLiftPercent100ths::Set(endpoint, entry.targetPositionLiftPercent100thsValue), |
| "TargetPositionLiftPercent100ths"); |
| } |
| if (entry.hasTargetPositionTiltPercent100thsValue) |
| { |
| logWriteError(TargetPositionTiltPercent100ths::Set(endpoint, entry.targetPositionTiltPercent100thsValue), |
| "TargetPositionTiltPercent100ths"); |
| } |
| } |
| #endif |
| emberAfScenesMakeValid(endpoint, sceneId, groupId); |
| return EMBER_ZCL_STATUS_SUCCESS; |
| } |
| } |
| |
| return EMBER_ZCL_STATUS_NOT_FOUND; |
| } |
| |
| template <typename T> |
| struct NullableUnderlyingType |
| { |
| }; |
| |
| template <typename T> |
| struct NullableUnderlyingType<app::DataModel::Nullable<T>> |
| { |
| using Type = T; |
| }; |
| |
| bool emberAfPluginScenesServerParseAddScene( |
| app::CommandHandler * commandObj, const app::ConcreteCommandPath & commandPath, 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 = (commandPath.mCommandId == EnhancedAddScene::Id); |
| auto fabricIndex = commandObj->GetAccessingFabricIndex(); |
| EndpointId endpoint = commandPath.mEndpointId; |
| 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_COMMAND; |
| 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_RESOURCE_EXHAUSTED; |
| 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; |
| using Traits = NumericAttributeTraits<NullableUnderlyingType<decltype(entry.currentLevelValue)>::Type>; |
| // TODO: This is not right; we should be getting TLV values here or something! |
| Traits::StorageType storedValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen); |
| if (Traits::IsNullValue(storedValue)) { |
| entry.currentLevelValue.SetNull(); |
| } else { |
| entry.currentLevelValue.SetValue(storedValue); |
| } |
| 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; |
| using Traits = NumericAttributeTraits<NullableUnderlyingType<decltype(entry.lockStateValue)>::Type>; |
| // TODO: This is not right; we should be getting TLV values here or something! |
| Traits::StorageType storedValue = emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen); |
| if (Traits::IsNullValue(storedValue)) { |
| entry.lockStateValue.SetNull(); |
| } else { |
| entry.lockStateValue.SetValue(storedValue); |
| } |
| 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; |
| using Traits = NumericAttributeTraits<NullableUnderlyingType<decltype(entry.currentPositionLiftPercentageValue)>::Type>; |
| // TODO: This is not right; we should be getting TLV values here or something! |
| Traits::StorageType storedValue = |
| emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen); |
| if (Traits::IsNullValue(storedValue)){ |
| entry.currentPositionLiftPercentageValue.SetNull(); |
| } else { |
| entry.currentPositionLiftPercentageValue.SetValue(storedValue); |
| } |
| } |
| |
| extensionFieldSetsIndex++; |
| length--; |
| if (length < 1) |
| { |
| break; |
| } |
| |
| { |
| entry.hasCurrentPositionTiltPercentageValue = true; |
| using Traits = NumericAttributeTraits<NullableUnderlyingType<decltype(entry.currentPositionTiltPercentageValue)>::Type>; |
| // TODO: This is not right; we should be getting TLV values here or something! |
| Traits::StorageType storedValue = |
| emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen); |
| if (Traits::IsNullValue(storedValue)){ |
| entry.currentPositionTiltPercentageValue.SetNull(); |
| } else { |
| entry.currentPositionTiltPercentageValue.SetValue(storedValue); |
| } |
| } |
| |
| extensionFieldSetsIndex++; |
| length--; |
| if (length < 2) |
| { |
| break; |
| } |
| |
| { |
| entry.hasTargetPositionLiftPercent100thsValue = true; |
| using Traits = NumericAttributeTraits<NullableUnderlyingType<decltype(entry.targetPositionLiftPercent100thsValue)>::Type>; |
| // TODO: This is not right; we should be getting TLV values here or something! |
| Traits::StorageType storedValue = |
| emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen); |
| if (Traits::IsNullValue(storedValue)){ |
| entry.targetPositionLiftPercent100thsValue.SetNull(); |
| } else { |
| entry.targetPositionLiftPercent100thsValue.SetValue(storedValue); |
| } |
| } |
| |
| extensionFieldSetsIndex = static_cast<uint16_t>(extensionFieldSetsIndex + 2); |
| length = static_cast<uint8_t>(length - 2); |
| if (length < 2) |
| { |
| break; |
| } |
| |
| { |
| entry.hasTargetPositionTiltPercent100thsValue = true; |
| using Traits = NumericAttributeTraits<NullableUnderlyingType<decltype(entry.targetPositionTiltPercent100thsValue)>::Type>; |
| // TODO: This is not right; we should be getting TLV values here or something! |
| Traits::StorageType storedValue = |
| emberAfGetInt8u(extensionFieldSets, extensionFieldSetsIndex, extensionFieldSetsLen); |
| if (Traits::IsNullValue(storedValue)){ |
| entry.targetPositionTiltPercent100thsValue.SetNull(); |
| } else { |
| entry.targetPositionTiltPercent100thsValue.SetValue(storedValue); |
| } |
| } |
| // 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: |
| |
| app::ConcreteCommandPath path = { commandPath.mEndpointId, Scenes::Id, AddSceneResponse::Id }; |
| if (enhanced) |
| { |
| path = { commandPath.mEndpointId, Scenes::Id, EnhancedAddSceneResponse::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 app::ConcreteCommandPath & commandPath, |
| GroupId groupId, uint8_t sceneId) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| EmberAfSceneTableEntry entry = {}; |
| EmberAfStatus status = EMBER_ZCL_STATUS_NOT_FOUND; |
| bool enhanced = (commandPath.mCommandId == EnhancedViewScene::Id); |
| FabricIndex fabricIndex = commandObj->GetAccessingFabricIndex(); |
| EndpointId endpoint = commandPath.mEndpointId; |
| |
| 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_COMMAND; |
| } |
| 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 = { commandPath.mEndpointId, Scenes::Id, ViewSceneResponse::Id }; |
| if (enhanced) |
| { |
| path = { commandPath.mEndpointId, Scenes::Id, EnhancedViewSceneResponse::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 |
| } |
| |
| 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(endpoint, emberAfPluginScenesServerNumSceneEntriesInUse()); |
| } |
| } |
| } |
| |
| void MatterScenesPluginServerInitCallback() {} |