Skip to content

Commit

Permalink
Valve: Disco ball - argument parsing for open
Browse files Browse the repository at this point in the history
  • Loading branch information
cecille committed Sep 9, 2024
1 parent 82624dc commit cbe7da5
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,77 @@ CHIP_ERROR ClusterLogic::ClearValveFault(const ValveFaultBitmap valveFault)
return CHIP_ERROR_NOT_IMPLEMENTED;
}

CHIP_ERROR ClusterLogic::GetRealTargetLevel(const std::optional<uint8_t> & targetLevel,
DataModel::Nullable<uint8_t> & realTargetLevel)
{
if (!mConformance.HasFeature(Feature::kLevel))
{
if (targetLevel.has_value())
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
realTargetLevel = DataModel::NullNullable;
return CHIP_NO_ERROR;
}
// LVL is supported
if (!targetLevel.has_value())
{
if (mConformance.supportsDefaultOpenLevel)
{
realTargetLevel = mState.defaultOpenLevel;
return CHIP_NO_ERROR;
}
realTargetLevel.SetNonNull(100u);
return CHIP_NO_ERROR;
}
// targetLevel has a value
if (mConformance.supportsLevelStep)
{
if ((targetLevel.value() != 100u) && ((targetLevel.value() % mState.levelStep) != 0))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
realTargetLevel.SetNonNull(targetLevel.value());
return CHIP_NO_ERROR;
}

CHIP_ERROR ClusterLogic::HandleOpenCommand(std::optional<DataModel::Nullable<uint32_t>> openDuration,
std::optional<uint8_t> targetLevel)
{
// openDuration
// - if this is omitted, fall back to defaultOpenDuration
// - if this is NULL, remaining duration is NULL
// - if this is a value, use that value
// - if remaining duration is not null and TS is supported, set the autoCloseTime as appropriate
// targetLevel
// - if LVL is not supported
// - if this is omitted, that's correct
// - if this is supplied return error
// - if LVL is supported
// - if this value is not supplied, use defaultOpenLevel if supported, otherwise 100
// - if this value is supplied, check against levelStep, error if not OK, otherwise set targetLevel
VerifyOrReturnError(mInitialized, CHIP_ERROR_INCORRECT_STATE);

DataModel::Nullable<uint32_t> realOpenDuration;
if (openDuration.has_value())
{
realOpenDuration = openDuration.value();
}
else
{
realOpenDuration = mState.defaultOpenDuration;
}

DataModel::Nullable<uint8_t> realTargetLevel;
ReturnErrorOnFailure(GetRealTargetLevel(targetLevel, realTargetLevel));

mClusterDriver.HandleOpenValve(realTargetLevel);
mState.openDuration = realOpenDuration;
mState.targetLevel = realTargetLevel;
return CHIP_NO_ERROR;
}

} // namespace ValveConfigurationAndControl
} // namespace Clusters
} // namespace app
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,16 @@ class ClusterLogic
// Current state
// Current level

// Return CHIP_ERROR_INCORRECT_STATE if the class has not been initialized.
// Return CHIP_ERROR_INVALID_ARGUMENT if the input values are out is out of range or the targetLevel is supplied when LVL is not
// supported.
// Calls delegate HandleOpen function after validating the parameters
CHIP_ERROR HandleOpenCommand(std::optional<DataModel::Nullable<uint32_t>> openDuration, std::optional<uint8_t> targetLevel);

private:
// Returns the target level to send to the delegate based on the targetLevel command field, the device conformance and the
// defaults. Returns error if the supplied target level is invalid or not supported by the device conformance.
CHIP_ERROR GetRealTargetLevel(const std::optional<uint8_t> & targetLevel, DataModel::Nullable<uint8_t> & realTargetLevel);
bool mInitialized = false;

Delegate & mClusterDriver;
Expand Down
154 changes: 153 additions & 1 deletion src/app/tests/TestValveConfigurationAndControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ class TestDelegate : public Delegate
{
public:
TestDelegate() {}
DataModel::Nullable<chip::Percent> HandleOpenValve(DataModel::Nullable<chip::Percent> level) override
DataModel::Nullable<Percent> HandleOpenValve(DataModel::Nullable<Percent> level) override
{
lastRequestedLevel = level;
return DataModel::NullNullable;
}
CHIP_ERROR HandleCloseValve() override { return CHIP_NO_ERROR; }
void HandleRemainingDurationTick(uint32_t duration) override {}
DataModel::Nullable<Percent> lastRequestedLevel;
};

TEST_F(TestValveConfigurationAndControlClusterLogic, TestConformanceValid)
Expand Down Expand Up @@ -504,6 +506,156 @@ TEST_F(TestValveConfigurationAndControlClusterLogic, TestSetDefaultOpenLevel)
EXPECT_EQ(logic_no_level.SetDefaultOpenLevel(testVal), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE);
}

TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenDuration)
{
TestDelegate delegate;
TestPersistentStorageDelegate storageDelegate;
EndpointId endpoint = 0;
MatterContext context = MatterContext(endpoint, storageDelegate);
ClusterLogic logic = ClusterLogic(delegate, context);

ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel) | to_underlying(Feature::kTimeSync),
.supportsDefaultOpenLevel = true,
.supportsValveFault = true,
.supportsLevelStep = true };
EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR);

DataModel::Nullable<ElapsedS> valElapsedSNullable;

EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR);
EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable);

EXPECT_EQ(logic.GetDefaultOpenDuration(valElapsedSNullable), CHIP_NO_ERROR);
EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable);

// Fall back to default
EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR);
EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR);
EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable);

DataModel::Nullable<ElapsedS> defaultOpenDuration;
defaultOpenDuration.SetNonNull(12u);
EXPECT_EQ(logic.SetDefaultOpenDuration(defaultOpenDuration), CHIP_NO_ERROR);
EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR);
EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR);
EXPECT_EQ(valElapsedSNullable, defaultOpenDuration);

// Set from command parameters
DataModel::Nullable<ElapsedS> openDuration;
openDuration.SetNull();
EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR);
EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR);
EXPECT_EQ(valElapsedSNullable, DataModel::NullNullable);

openDuration.SetNonNull(12u);
EXPECT_EQ(logic.HandleOpenCommand(std::make_optional(openDuration), std::nullopt), CHIP_NO_ERROR);
EXPECT_EQ(logic.GetOpenDuration(valElapsedSNullable), CHIP_NO_ERROR);
EXPECT_EQ(valElapsedSNullable.ValueOr(0), 12u);
}

TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenTargetLevelFeatureUnsupported)
{
TestDelegate delegate;
TestPersistentStorageDelegate storageDelegate;
EndpointId endpoint = 0;
MatterContext context = MatterContext(endpoint, storageDelegate);
ClusterLogic logic = ClusterLogic(delegate, context);

ClusterConformance conformance = {
.featureMap = 0, .supportsDefaultOpenLevel = false, .supportsValveFault = false, .supportsLevelStep = false
};
EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR);

// Set the last value to something non-null first so we can ensure the delegate was called correctly.
delegate.lastRequestedLevel.SetNonNull(0);
EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR);
// This configuration doesn't support level, so the delegate should be called with a NullNullable
EXPECT_EQ(delegate.lastRequestedLevel, DataModel::NullNullable);

// Should get an error when this is called with target level set since the feature is unsupported.
EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(50u)), CHIP_ERROR_INVALID_ARGUMENT);
}

TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenTargetLevelNotSuppliedNoDefaultSupported)
{
TestDelegate delegate;
TestPersistentStorageDelegate storageDelegate;
EndpointId endpoint = 0;
MatterContext context = MatterContext(endpoint, storageDelegate);
ClusterLogic logic = ClusterLogic(delegate, context);

ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel),
.supportsDefaultOpenLevel = false,
.supportsValveFault = false,
.supportsLevelStep = false };
EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR);
// Set the last value to something non-null first so we can ensure the delegate was called correctly.
delegate.lastRequestedLevel.SetNonNull(0);
EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR);
EXPECT_EQ(delegate.lastRequestedLevel.ValueOr(0), 100u);
}

TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenTargetLevelNotSuppliedDefaultSupported)
{
TestDelegate delegate;
TestPersistentStorageDelegate storageDelegate;
EndpointId endpoint = 0;
MatterContext context = MatterContext(endpoint, storageDelegate);
ClusterLogic logic = ClusterLogic(delegate, context);

ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel),
.supportsDefaultOpenLevel = true,
.supportsValveFault = false,
.supportsLevelStep = false };
EXPECT_EQ(logic.Init(conformance), CHIP_NO_ERROR);
// Set the last value to something non-null first so we can ensure the delegate was called correctly.
delegate.lastRequestedLevel.SetNonNull(0);
EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR);
EXPECT_EQ(delegate.lastRequestedLevel.ValueOr(0), 100u);

EXPECT_EQ(logic.SetDefaultOpenLevel(50u), CHIP_NO_ERROR);
EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::nullopt), CHIP_NO_ERROR);
EXPECT_EQ(delegate.lastRequestedLevel.ValueOr(0), 50u);
}

TEST_F(TestValveConfigurationAndControlClusterLogic, TestHandleOpenTargetLevelSupplied)
{
TestDelegate delegate;
TestPersistentStorageDelegate storageDelegate;
EndpointId endpoint = 0;
MatterContext context = MatterContext(endpoint, storageDelegate);
ClusterLogic logic = ClusterLogic(delegate, context);

ClusterConformance conformance = { .featureMap = to_underlying(Feature::kLevel),
.supportsDefaultOpenLevel = true,
.supportsValveFault = false,
.supportsLevelStep = true };
ClusterState state = ClusterState();
state.levelStep = 33;
EXPECT_EQ(logic.Init(conformance, state), CHIP_NO_ERROR);
// Set the last value to something non-null first so we can ensure the delegate was called correctly.
delegate.lastRequestedLevel.SetNonNull(0);

// 33, 66, 99 and 100 should all work, nothing else should
EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(33u)), CHIP_NO_ERROR);
EXPECT_EQ(delegate.lastRequestedLevel.ValueOr(0), 33u);

EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(66u)), CHIP_NO_ERROR);
EXPECT_EQ(delegate.lastRequestedLevel.ValueOr(0), 66u);

EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(99u)), CHIP_NO_ERROR);
EXPECT_EQ(delegate.lastRequestedLevel.ValueOr(0), 99u);

EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(100u)), CHIP_NO_ERROR);
EXPECT_EQ(delegate.lastRequestedLevel.ValueOr(0), 100u);

EXPECT_EQ(logic.HandleOpenCommand(std::nullopt, std::make_optional(32u)), CHIP_ERROR_INVALID_ARGUMENT);
// Ensure this wasn't called again.
EXPECT_EQ(delegate.lastRequestedLevel.ValueOr(0), 100u);
}

// TODO: Add tests (and support) for checking default open level against the level step

} // namespace ValveConfigurationAndControl
} // namespace Clusters
} // namespace app
Expand Down

0 comments on commit cbe7da5

Please sign in to comment.