/** * * 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 #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 remainingDuration; }; RemainingDurationTable gRemainingDuration[kValveConfigurationAndControlDelegateTableSize]; Delegate * gDelegateTable[kValveConfigurationAndControlDelegateTableSize] = { nullptr }; bool GetRemainingDuration(EndpointId endpoint, DataModel::Nullable & 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 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::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 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 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(data); VerifyOrReturn(item != nullptr, ChipLogError(Zcl, "Error retrieving RemainingDuration item")); DataModel::Nullable rDuration = item->remainingDuration; VerifyOrReturn(!rDuration.IsNull()); EndpointId ep = item->endpoint; if (rDuration.Value() > 0) { SetRemainingDuration(ep, DataModel::MakeNullable(--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 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 level, DataModel::Nullable openDuration) { Delegate * delegate = GetDelegate(ep); Optional status = Optional::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 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 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 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 level; DataModel::Nullable duration; BitMask fault(0); Optional status = Optional::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 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 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() { AttributeAccessInterfaceRegistry::Instance().Register(&gAttrAccess); }