| /** |
| * |
| * Copyright (c) 2023 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 "valve-configuration-and-control-server.h" |
| |
| #include <app/util/config.h> |
| #ifdef ZCL_USING_TIME_SYNCHRONIZATION_CLUSTER_SERVER |
| // Need the `nogncheck` because it's inter-cluster dependency and this |
| // breaks GN deps checks since that doesn't know how to deal with #ifdef'd includes :(. |
| #include "app/clusters/time-synchronization-server/time-synchronization-server.h" // nogncheck |
| |
| #endif // ZCL_USING_TIME_SYNCHRONIZATION_CLUSTER_SERVER |
| |
| #include <app-common/zap-generated/attributes/Accessors.h> |
| #include <app-common/zap-generated/cluster-objects.h> |
| #include <app-common/zap-generated/ids/Attributes.h> |
| #include <app-common/zap-generated/ids/Clusters.h> |
| #include <app/AttributeAccessInterface.h> |
| #include <app/AttributeAccessInterfaceRegistry.h> |
| #include <app/CommandHandler.h> |
| #include <app/ConcreteCommandPath.h> |
| #include <app/EventLogging.h> |
| #include <app/data-model/Encode.h> |
| #include <app/reporting/reporting.h> |
| #include <app/util/attribute-storage.h> |
| #include <lib/core/CHIPError.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <platform/CHIPDeviceConfig.h> |
| #include <platform/CHIPDeviceLayer.h> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Clusters; |
| using namespace chip::app::Clusters::ValveConfigurationAndControl::Attributes; |
| using chip::app::Clusters::ValveConfigurationAndControl::Delegate; |
| using chip::Protocols::InteractionModel::Status; |
| |
| static constexpr size_t kValveConfigurationAndControlDelegateTableSize = |
| MATTER_DM_VALVE_CONFIGURATION_AND_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT + CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT; |
| |
| static_assert(kValveConfigurationAndControlDelegateTableSize <= kEmberInvalidEndpointIndex, |
| "ValveConfigurationAndControl Delegate table size error"); |
| |
| namespace { |
| |
| struct RemainingDurationTable |
| { |
| EndpointId endpoint; |
| DataModel::Nullable<uint32_t> remainingDuration; |
| }; |
| |
| RemainingDurationTable gRemainingDuration[kValveConfigurationAndControlDelegateTableSize]; |
| Delegate * gDelegateTable[kValveConfigurationAndControlDelegateTableSize] = { nullptr }; |
| |
| bool GetRemainingDuration(EndpointId endpoint, DataModel::Nullable<uint32_t> & duration) |
| { |
| uint16_t epIdx = emberAfGetClusterServerEndpointIndex(endpoint, ValveConfigurationAndControl::Id, |
| MATTER_DM_VALVE_CONFIGURATION_AND_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT); |
| VerifyOrReturnValue(epIdx < kValveConfigurationAndControlDelegateTableSize, false); |
| duration = gRemainingDuration[epIdx].remainingDuration; |
| return true; |
| } |
| |
| void SetRemainingDuration(EndpointId endpoint, DataModel::Nullable<uint32_t> duration) |
| { |
| uint16_t epIdx = emberAfGetClusterServerEndpointIndex(endpoint, ValveConfigurationAndControl::Id, |
| MATTER_DM_VALVE_CONFIGURATION_AND_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT); |
| if (epIdx < kValveConfigurationAndControlDelegateTableSize) |
| { |
| gRemainingDuration[epIdx].endpoint = endpoint; |
| gRemainingDuration[epIdx].remainingDuration = duration; |
| } |
| } |
| |
| void SetRemainingDurationNull(EndpointId endpoint) |
| { |
| uint16_t epIdx = emberAfGetClusterServerEndpointIndex(endpoint, ValveConfigurationAndControl::Id, |
| MATTER_DM_VALVE_CONFIGURATION_AND_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT); |
| if (epIdx < kValveConfigurationAndControlDelegateTableSize) |
| { |
| if (!gRemainingDuration[epIdx].remainingDuration.IsNull()) |
| { |
| MatterReportingAttributeChangeCallback(endpoint, ValveConfigurationAndControl::Id, RemainingDuration::Id); |
| } |
| gRemainingDuration[epIdx].remainingDuration.SetNull(); |
| } |
| } |
| |
| RemainingDurationTable * GetRemainingDurationItem(EndpointId endpoint) |
| { |
| uint16_t epIdx = emberAfGetClusterServerEndpointIndex(endpoint, ValveConfigurationAndControl::Id, |
| MATTER_DM_VALVE_CONFIGURATION_AND_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT); |
| if (epIdx < kValveConfigurationAndControlDelegateTableSize) |
| { |
| return &gRemainingDuration[epIdx]; |
| } |
| return nullptr; |
| } |
| |
| Delegate * GetDelegate(EndpointId endpoint) |
| { |
| uint16_t epIdx = emberAfGetClusterServerEndpointIndex(endpoint, ValveConfigurationAndControl::Id, |
| MATTER_DM_VALVE_CONFIGURATION_AND_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT); |
| return (epIdx >= kValveConfigurationAndControlDelegateTableSize ? nullptr : gDelegateTable[epIdx]); |
| } |
| |
| bool isDelegateNull(Delegate * delegate) |
| { |
| if (delegate == nullptr) |
| { |
| return true; |
| } |
| return false; |
| } |
| |
| class ValveConfigAndControlAttrAccess : public AttributeAccessInterface |
| { |
| public: |
| ValveConfigAndControlAttrAccess() : AttributeAccessInterface(Optional<EndpointId>::Missing(), ValveConfigurationAndControl::Id) |
| {} |
| |
| CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override; |
| |
| private: |
| CHIP_ERROR ReadRemainingDuration(EndpointId endpoint, AttributeValueEncoder & aEncoder); |
| }; |
| |
| ValveConfigAndControlAttrAccess gAttrAccess; |
| |
| CHIP_ERROR ValveConfigAndControlAttrAccess::ReadRemainingDuration(EndpointId endpoint, AttributeValueEncoder & aEncoder) |
| { |
| DataModel::Nullable<uint32_t> rDuration; |
| VerifyOrReturnError(GetRemainingDuration(endpoint, rDuration), CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)); |
| |
| return aEncoder.Encode(rDuration); |
| } |
| |
| CHIP_ERROR ValveConfigAndControlAttrAccess::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| if (aPath.mClusterId != ValveConfigurationAndControl::Id) |
| { |
| return CHIP_ERROR_INVALID_PATH_LIST; |
| } |
| |
| switch (aPath.mAttributeId) |
| { |
| case RemainingDuration::Id: { |
| return ReadRemainingDuration(aPath.mEndpointId, aEncoder); |
| } |
| default: { |
| break; |
| } |
| } |
| |
| return err; |
| } |
| } // namespace |
| |
| static void startRemainingDurationTick(EndpointId ep); |
| |
| static bool emitValveStateChangedEvent(EndpointId ep, ValveConfigurationAndControl::ValveStateEnum state) |
| { |
| ValveConfigurationAndControl::Events::ValveStateChanged::Type event; |
| EventNumber eventNumber; |
| event.valveState = state; |
| |
| CHIP_ERROR error = LogEvent(event, ep, eventNumber); |
| |
| if (CHIP_NO_ERROR != error) |
| { |
| ChipLogError(Zcl, "Unable to emit ValveStateChanged event [ep=%d]", ep); |
| return false; |
| } |
| |
| ChipLogProgress(Zcl, "Emit ValveStateChanged event [ep=%d] %d", ep, to_underlying(state)); |
| return true; |
| } |
| |
| static CHIP_ERROR emitValveFaultEvent(EndpointId ep, BitMask<ValveConfigurationAndControl::ValveFaultBitmap> fault) |
| { |
| ValveConfigurationAndControl::Events::ValveFault::Type event; |
| EventNumber eventNumber; |
| event.valveFault = fault; |
| |
| CHIP_ERROR error = LogEvent(event, ep, eventNumber); |
| |
| if (CHIP_NO_ERROR != error) |
| { |
| ChipLogError(Zcl, "Unable to emit ValveFault event [ep=%d]", ep); |
| return error; |
| } |
| |
| ChipLogProgress(Zcl, "Emit ValveFault event [ep=%d]", ep); |
| return CHIP_NO_ERROR; |
| } |
| |
| static void onValveConfigurationAndControlTick(System::Layer * systemLayer, void * data) |
| { |
| RemainingDurationTable * item = reinterpret_cast<RemainingDurationTable *>(data); |
| VerifyOrReturn(item != nullptr, ChipLogError(Zcl, "Error retrieving RemainingDuration item")); |
| |
| DataModel::Nullable<uint32_t> rDuration = item->remainingDuration; |
| VerifyOrReturn(!rDuration.IsNull()); |
| |
| EndpointId ep = item->endpoint; |
| |
| if (rDuration.Value() > 0) |
| { |
| SetRemainingDuration(ep, DataModel::MakeNullable<uint32_t>(--rDuration.Value())); |
| startRemainingDurationTick(ep); |
| } |
| else |
| { |
| SetRemainingDurationNull(ep); |
| } |
| } |
| |
| void startRemainingDurationTick(EndpointId ep) |
| { |
| RemainingDurationTable * item = GetRemainingDurationItem(ep); |
| VerifyOrReturn(item != nullptr, ChipLogError(Zcl, "Error retrieving RemainingDuration item")); |
| |
| DataModel::Nullable<uint32_t> rDuration = item->remainingDuration; |
| VerifyOrReturn(!rDuration.IsNull()); |
| Delegate * delegate = GetDelegate(item->endpoint); |
| VerifyOrReturn(!isDelegateNull(delegate)); |
| |
| delegate->HandleRemainingDurationTick(rDuration.Value()); |
| if (rDuration.Value() > 0) |
| { |
| (void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onValveConfigurationAndControlTick, item); |
| } |
| else |
| { |
| ValveConfigurationAndControl::CloseValve(ep); |
| (void) DeviceLayer::SystemLayer().CancelTimer(onValveConfigurationAndControlTick, item); |
| } |
| } |
| |
| namespace chip { |
| namespace app { |
| namespace Clusters { |
| namespace ValveConfigurationAndControl { |
| |
| void SetDefaultDelegate(EndpointId endpoint, Delegate * delegate) |
| { |
| uint16_t ep = emberAfGetClusterServerEndpointIndex(endpoint, ValveConfigurationAndControl::Id, |
| MATTER_DM_VALVE_CONFIGURATION_AND_CONTROL_CLUSTER_SERVER_ENDPOINT_COUNT); |
| // if endpoint is found |
| if (ep < kValveConfigurationAndControlDelegateTableSize) |
| { |
| gDelegateTable[ep] = delegate; |
| } |
| } |
| |
| Delegate * GetDefaultDelegate(EndpointId endpoint) |
| { |
| return GetDelegate(endpoint); |
| } |
| |
| CHIP_ERROR CloseValve(EndpointId ep) |
| { |
| Delegate * delegate = GetDelegate(ep); |
| CHIP_ERROR attribute_error = CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); |
| |
| VerifyOrReturnError(Status::Success == TargetState::Set(ep, ValveConfigurationAndControl::ValveStateEnum::kClosed), |
| attribute_error); |
| VerifyOrReturnError(Status::Success == CurrentState::Set(ep, ValveConfigurationAndControl::ValveStateEnum::kTransitioning), |
| attribute_error); |
| VerifyOrReturnError(Status::Success == OpenDuration::SetNull(ep), attribute_error); |
| if (HasFeature(ep, ValveConfigurationAndControl::Feature::kLevel)) |
| { |
| VerifyOrReturnError(Status::Success == TargetLevel::Set(ep, 0), attribute_error); |
| } |
| if (HasFeature(ep, ValveConfigurationAndControl::Feature::kTimeSync)) |
| { |
| VerifyOrReturnError(Status::Success == AutoCloseTime::SetNull(ep), attribute_error); |
| } |
| SetRemainingDurationNull(ep); |
| RemainingDurationTable * item = GetRemainingDurationItem(ep); |
| (void) DeviceLayer::SystemLayer().CancelTimer(onValveConfigurationAndControlTick, item); |
| |
| emitValveStateChangedEvent(ep, ValveConfigurationAndControl::ValveStateEnum::kTransitioning); |
| if (!isDelegateNull(delegate)) |
| { |
| delegate->HandleCloseValve(); |
| } |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR SetValveLevel(EndpointId ep, DataModel::Nullable<Percent> level, DataModel::Nullable<uint32_t> openDuration) |
| { |
| Delegate * delegate = GetDelegate(ep); |
| Optional<Status> status = Optional<Status>::Missing(); |
| CHIP_ERROR attribute_error = CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); |
| |
| if (HasFeature(ep, ValveConfigurationAndControl::Feature::kTimeSync)) |
| { |
| #ifdef ZCL_USING_TIME_SYNCHRONIZATION_CLUSTER_SERVER |
| if (!openDuration.IsNull() && |
| TimeSynchronization::TimeSynchronizationServer::Instance().GetGranularity() != |
| TimeSynchronization::GranularityEnum::kNoTimeGranularity) |
| { |
| System::Clock::Microseconds64 utcTime; |
| uint64_t chipEpochTime; |
| ReturnErrorOnFailure(System::SystemClock().GetClock_RealTime(utcTime)); |
| VerifyOrReturnError(UnixEpochToChipEpochMicros(utcTime.count(), chipEpochTime), CHIP_ERROR_INVALID_TIME); |
| |
| uint64_t time = openDuration.Value() * chip::kMicrosecondsPerSecond; |
| DataModel::Nullable<uint64_t> autoCloseTime; |
| autoCloseTime.SetNonNull(chipEpochTime + time); |
| VerifyOrReturnError(Status::Success == AutoCloseTime::Set(ep, autoCloseTime), attribute_error); |
| } |
| else |
| { |
| VerifyOrReturnError(Status::Success == AutoCloseTime::SetNull(ep), attribute_error); |
| } |
| #else |
| return CHIP_ERROR_NOT_IMPLEMENTED; |
| #endif // ZCL_USING_TIME_SYNCHRONIZATION_CLUSTER_SERVER |
| } |
| |
| // level can only be null if LVL feature is not supported |
| if (HasFeature(ep, ValveConfigurationAndControl::Feature::kLevel) && !level.IsNull()) |
| { |
| VerifyOrReturnError(Status::Success == TargetLevel::Set(ep, level), attribute_error); |
| } |
| |
| VerifyOrReturnError(Status::Success == OpenDuration::Set(ep, openDuration), attribute_error); |
| |
| SetRemainingDuration(ep, openDuration); |
| // Trigger report for remainingduration |
| MatterReportingAttributeChangeCallback(ep, ValveConfigurationAndControl::Id, RemainingDuration::Id); |
| // set targetstate to open |
| VerifyOrReturnError(Status::Success == TargetState::Set(ep, ValveConfigurationAndControl::ValveStateEnum::kOpen), |
| attribute_error); |
| VerifyOrReturnError(Status::Success == CurrentState::Set(ep, ValveConfigurationAndControl::ValveStateEnum::kTransitioning), |
| attribute_error); |
| |
| // start movement towards target |
| emitValveStateChangedEvent(ep, ValveConfigurationAndControl::ValveStateEnum::kTransitioning); |
| if (!isDelegateNull(delegate)) |
| { |
| DataModel::Nullable<Percent> cLevel = delegate->HandleOpenValve(level); |
| if (HasFeature(ep, ValveConfigurationAndControl::Feature::kLevel)) |
| { |
| VerifyOrReturnError(Status::Success == CurrentLevel::Set(ep, cLevel), attribute_error); |
| } |
| } |
| // start countdown |
| startRemainingDurationTick(ep); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR UpdateCurrentLevel(EndpointId ep, Percent currentLevel) |
| { |
| if (HasFeature(ep, ValveConfigurationAndControl::Feature::kLevel)) |
| { |
| VerifyOrReturnError(Status::Success == CurrentLevel::Set(ep, currentLevel), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| return CHIP_NO_ERROR; |
| } |
| return CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute); |
| } |
| |
| CHIP_ERROR UpdateCurrentState(EndpointId ep, ValveConfigurationAndControl::ValveStateEnum currentState) |
| { |
| VerifyOrReturnError(Status::Success == CurrentState::Set(ep, currentState), CHIP_IM_GLOBAL_STATUS(ConstraintError)); |
| emitValveStateChangedEvent(ep, currentState); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR EmitValveFault(EndpointId ep, BitMask<ValveConfigurationAndControl::ValveFaultBitmap> fault) |
| { |
| ReturnErrorOnFailure(emitValveFaultEvent(ep, fault)); |
| return CHIP_NO_ERROR; |
| } |
| |
| void UpdateAutoCloseTime(uint64_t time) |
| { |
| for (auto & t : gRemainingDuration) |
| { |
| const auto & d = t.remainingDuration; |
| if (!d.IsNull() && d.Value() != 0) |
| { |
| uint64_t closingTime = d.Value() * chip::kMicrosecondsPerSecond + time; |
| if (Status::Success != AutoCloseTime::Set(t.endpoint, closingTime)) |
| { |
| ChipLogError(Zcl, "Unable to update AutoCloseTime"); |
| } |
| } |
| } |
| } |
| } // namespace ValveConfigurationAndControl |
| } // namespace Clusters |
| } // namespace app |
| } // namespace chip |
| |
| bool emberAfValveConfigurationAndControlClusterOpenCallback( |
| CommandHandler * commandObj, const ConcreteCommandPath & commandPath, |
| const ValveConfigurationAndControl::Commands::Open::DecodableType & commandData) |
| { |
| const auto & openDuration = commandData.openDuration; |
| const auto & targetLevel = commandData.targetLevel; |
| const auto & ep = commandPath.mEndpointId; |
| DataModel::Nullable<Percent> level; |
| DataModel::Nullable<uint32_t> duration; |
| BitMask<ValveConfigurationAndControl::ValveFaultBitmap> fault(0); |
| Optional<Status> status = Optional<Status>::Missing(); |
| |
| // if fault is registered return FailureDueToFault |
| if (Status::Success == ValveFault::Get(ep, &fault) && fault.HasAny()) |
| { |
| commandObj->AddClusterSpecificFailure(commandPath, |
| to_underlying(ValveConfigurationAndControl::StatusCodeEnum::kFailureDueToFault)); |
| return true; |
| } |
| |
| // verify min 1 requirement |
| VerifyOrExit(targetLevel.HasValue() ? targetLevel.Value() > 0 : true, status.Emplace(Status::ConstraintError)); |
| if (openDuration.HasValue()) |
| { |
| bool validOpenDuration = openDuration.Value().IsNull() ? true : openDuration.Value().Value() > 0; |
| // verify min 1 requirement |
| VerifyOrExit(validOpenDuration, status.Emplace(Status::ConstraintError)); |
| |
| duration = openDuration.Value(); |
| } |
| else |
| { |
| VerifyOrExit(Status::Success == DefaultOpenDuration::Get(ep, duration), status.Emplace(Status::Failure)); |
| } |
| |
| if (HasFeature(ep, ValveConfigurationAndControl::Feature::kLevel)) |
| { |
| Percent defOpenLevel; |
| if (targetLevel.HasValue()) |
| { |
| level.SetNonNull(targetLevel.Value()); |
| } |
| else if (Status::Success == DefaultOpenLevel::Get(ep, &defOpenLevel)) |
| { |
| level.SetNonNull(defOpenLevel); |
| } |
| else |
| { |
| level.SetNonNull(Percent(100)); |
| } |
| } |
| |
| VerifyOrExit(CHIP_NO_ERROR == ValveConfigurationAndControl::SetValveLevel(ep, level, duration), |
| status.Emplace(Status::Failure)); |
| |
| exit: |
| if (status.HasValue()) |
| { |
| BitMask<ValveConfigurationAndControl::ValveFaultBitmap> gFault( |
| ValveConfigurationAndControl::ValveFaultBitmap::kGeneralFault); |
| emitValveFaultEvent(ep, gFault); |
| commandObj->AddStatus(commandPath, status.Value()); |
| } |
| else |
| { |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| return true; |
| } |
| |
| bool emberAfValveConfigurationAndControlClusterCloseCallback( |
| CommandHandler * commandObj, const ConcreteCommandPath & commandPath, |
| const ValveConfigurationAndControl::Commands::Close::DecodableType & commandData) |
| { |
| const auto & ep = commandPath.mEndpointId; |
| BitMask<ValveConfigurationAndControl::ValveFaultBitmap> fault(0); |
| |
| // if fault is registered return FailureDueToFault |
| if (Status::Success == ValveFault::Get(ep, &fault) && fault.HasAny()) |
| { |
| commandObj->AddClusterSpecificFailure(commandPath, |
| to_underlying(ValveConfigurationAndControl::StatusCodeEnum::kFailureDueToFault)); |
| return true; |
| } |
| |
| if (CHIP_NO_ERROR == ValveConfigurationAndControl::CloseValve(ep)) |
| { |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| else |
| { |
| commandObj->AddStatus(commandPath, Status::Failure); |
| } |
| |
| return true; |
| } |
| |
| void MatterValveConfigurationAndControlPluginServerInitCallback() |
| { |
| registerAttributeAccessOverride(&gAttrAccess); |
| } |