blob: 02486beea315a46fd54ec823928c050b354882c1 [file] [log] [blame]
/**
*
* 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);
}