blob: 198f16431abc73ec818c0f971b9a11cb818c81c3 [file] [log] [blame]
/*
* Copyright (c) 2026 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 <app/clusters/level-control/tests/TestLevelControlCommon.h>
#include <app/server-cluster/testing/ClusterTester.h>
#include <app/server-cluster/testing/TestServerClusterContext.h>
#include <app/server-cluster/testing/ValidateGlobalAttributes.h>
#include <clusters/LevelControl/Attributes.h>
#include <clusters/LevelControl/Commands.h>
#include <clusters/LevelControl/Metadata.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::LevelControl;
using chip::Testing::IsAttributesListEqualTo;
struct TestLevelControlOnOff : public LevelControlTestBase
{
};
TEST_F(TestLevelControlOnOff, TestExecuteIfOff)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster) };
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
EXPECT_TRUE(cluster
.MoveToLevel(10, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
Commands::MoveToLevel::Type data;
data.level = 20;
data.transitionTime.SetNonNull(0);
data.optionsMask.ClearAll();
data.optionsOverride.ClearAll();
EXPECT_TRUE(tester.Invoke(Commands::MoveToLevel::Id, data).IsSuccess());
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 10u); // Still 10
}
TEST_F(TestLevelControlOnOff, TestExecuteIfOff_OverrideOff)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster) };
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_TRUE(cluster
.MoveToLevel(10, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
// Set Options to HAVE ExecuteIfOff
BitMask<OptionsBitmap> options;
options.Set(OptionsBitmap::kExecuteIfOff);
EXPECT_TRUE(tester.WriteAttribute(Attributes::Options::Id, options).IsSuccess());
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
// Command: MoveToLevel 20.
// Mask = ExecuteIfOff (we want to override this bit)
// Override = 0 (we want to set it to 0, i.e., Don't Execute)
Commands::MoveToLevel::Type data;
data.level = 20;
data.transitionTime.SetNonNull(0);
data.optionsMask.Set(OptionsBitmap::kExecuteIfOff);
data.optionsOverride.ClearAll(); // Force bit to 0
// Expectation: Command succeeds (status success) but DOES NOT CHANGE LEVEL because it was suppressed by override.
EXPECT_TRUE(tester.Invoke(Commands::MoveToLevel::Id, data).IsSuccess());
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 10u); // Should remain 10
}
TEST_F(TestLevelControlOnOff, TestWriteOnLevel)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster) };
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
DataModel::Nullable<uint8_t> onLevel;
onLevel.SetNonNull(50);
EXPECT_TRUE(tester.WriteAttribute(Attributes::OnLevel::Id, onLevel).IsSuccess());
}
TEST_F(TestLevelControlOnOff, TestMoveToLevelWithOnOffCommand)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster) };
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_TRUE(cluster
.MoveToLevel(0, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
// 1. Move Up to 10 with OnOff. Should Turn On immediately.
Commands::MoveToLevelWithOnOff::Type data;
data.level = 10;
data.transitionTime.SetNonNull(100);
data.optionsMask.ClearAll();
data.optionsOverride.ClearAll();
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
EXPECT_TRUE(tester.Invoke(Commands::MoveToLevelWithOnOff::Id, data).IsSuccess());
EXPECT_TRUE(onOffCluster.GetOnOff());
// Advance to end
while (mockTimer.IsTimerActive(nullptr))
{
AdvanceClock(System::Clock::Milliseconds64(1000));
}
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 10u);
// 2. Move Down to 0 with OnOff. Should Turn Off AT END.
data.level = 0;
EXPECT_TRUE(tester.Invoke(Commands::MoveToLevelWithOnOff::Id, data).IsSuccess());
// Should NOT have turned off yet (it's decreasing)
EXPECT_TRUE(onOffCluster.GetOnOff());
// Advance to end
while (mockTimer.IsTimerActive(nullptr))
{
AdvanceClock(System::Clock::Milliseconds64(1000));
}
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 0u);
// NOW it should have called SetOnOff(false)
EXPECT_FALSE(onOffCluster.GetOnOff());
}
TEST_F(TestLevelControlOnOff, TestMoveWithOnOff)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster) };
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// Initialize level to 0
EXPECT_TRUE(cluster
.MoveToLevel(0, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
// Initial: Off
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
// Move Up with OnOff -> Should turn On
Commands::MoveWithOnOff::Type data;
data.moveMode = MoveModeEnum::kUp;
data.rate.SetNonNull(10);
data.optionsMask.ClearAll();
data.optionsOverride.ClearAll();
EXPECT_TRUE(tester.Invoke(Commands::MoveWithOnOff::Id, data).IsSuccess());
EXPECT_TRUE(onOffCluster.GetOnOff());
EXPECT_TRUE(mockTimer.IsTimerActive(nullptr));
}
TEST_F(TestLevelControlOnOff, TestStepWithOnOff)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster) };
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// Initialize level to 0
EXPECT_TRUE(cluster
.MoveToLevel(0, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
// Initial: Off
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
// Step Up with OnOff -> Should turn On
Commands::StepWithOnOff::Type data;
data.stepMode = StepModeEnum::kUp;
data.stepSize = 10;
data.transitionTime.SetNonNull(0);
data.optionsMask.ClearAll();
data.optionsOverride.ClearAll();
EXPECT_TRUE(tester.Invoke(Commands::StepWithOnOff::Id, data).IsSuccess());
EXPECT_TRUE(onOffCluster.GetOnOff());
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 10u);
}
TEST_F(TestLevelControlOnOff, TestOnOffAttributes)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate)
.WithOnOff(onOffCluster)
.WithOnOffTransitionTime(0)
.WithOnTransitionTime(0)
.WithOffTransitionTime(0) };
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{ Attributes::CurrentLevel::kMetadataEntry, Attributes::Options::kMetadataEntry,
Attributes::OnLevel::kMetadataEntry, Attributes::OnTransitionTime::kMetadataEntry,
Attributes::OffTransitionTime::kMetadataEntry,
Attributes::OnOffTransitionTime::kMetadataEntry }));
}
TEST_F(TestLevelControlOnOff, TestOnOffChanged)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
// Configure OnOffTransitionTime
LevelControlCluster cluster{
LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster).WithOnOffTransitionTime(100)
}; // 10s
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// Initial State: On
EXPECT_EQ(onOffCluster.SetOnOff(true), CHIP_NO_ERROR);
EXPECT_TRUE(cluster
.MoveToLevel(200, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
// 1. Turn OFF
// Should store 200, move to MinLevel (0) over 10s.
// Then, because OnLevel is null, it should RESTORE the stored level (200) at the end.
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
EXPECT_TRUE(mockTimer.IsTimerActive(nullptr));
while (mockTimer.IsTimerActive(nullptr))
{
AdvanceClock(System::Clock::Milliseconds64(1000));
}
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
// Spec: If OnLevel is not defined, set the CurrentLevel to the stored level.
// Stored level was 200.
EXPECT_EQ(readLevel.Value(), 200u);
// 2. Turn ON
// Should restore 200 (from stored), over 10s.
EXPECT_EQ(onOffCluster.SetOnOff(true), CHIP_NO_ERROR);
EXPECT_TRUE(mockTimer.IsTimerActive(nullptr));
while (mockTimer.IsTimerActive(nullptr))
{
AdvanceClock(System::Clock::Milliseconds64(1000));
}
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 200u);
}
TEST_F(TestLevelControlOnOff, TestOnOffChangedDefaultLevel)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
// Test that if OnLevel is null and StoredLevel is unknown (null or 0?), it defaults to MaxLevel.
LevelControlCluster cluster{
LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster).WithOnOffTransitionTime(0)
}; // Immediate
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// Initial state: Level 0 (Min)
EXPECT_TRUE(cluster
.MoveToLevel(0, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
// Turn ON. No OnLevel set. No StoredLevel (as we haven't turned off from a high level).
// Should default to MaxLevel (254).
EXPECT_EQ(onOffCluster.SetOnOff(true), CHIP_NO_ERROR);
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 254u);
}
TEST_F(TestLevelControlOnOff, TestRestorationBehaviorWhenOnLevelNull)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
// Reproduce TC-LVL-3.1 scenario:
// Device has Lighting (MinLevel=1) and OnOff.
// OnLevel is null (default).
// When turning Off, it should transition to MinLevel but then RESTORE the stored level.
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate)
.WithLighting(DataModel::NullNullable) // MinLevel=1
.WithOnOff(onOffCluster) }; // Dependency active
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// Initial state: Level 100, On
EXPECT_TRUE(cluster
.MoveToLevel(100, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
// Turn Off
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
// Expectation:
// 1. Stored Level = 100.
// 2. Transition to MinLevel (1).
// 3. Since OnLevel is null, restore Stored Level (100).
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 100u);
}
TEST_F(TestLevelControlOnOff, TestMoveToLevelWithOnOffReentrancy)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster) };
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// Initial state: Level 0, Off
EXPECT_TRUE(cluster
.MoveToLevel(0, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
// Command: Move to 254 (Max) with OnOff.
// This triggers SetOnOff(true) -> OnOffChanged(true).
// OnOffChanged sets level to Min (0) and moves to OnLevel (default 0 as current is 0).
// This nested move sets mTargetLevel = 0.
// Without the fix, the outer MoveToLevel uses this 0 as target and stops.
Commands::MoveToLevelWithOnOff::Type data;
data.level = 254;
data.transitionTime.SetNonNull(100); // 10s transition
data.optionsMask.ClearAll();
data.optionsOverride.ClearAll();
// Verify command succeeds
EXPECT_TRUE(tester.Invoke(Commands::MoveToLevelWithOnOff::Id, data).IsSuccess());
// Verify timer IS active (transition to 254 started)
EXPECT_TRUE(mockTimer.IsTimerActive(nullptr));
// Verify we reach 254 eventually
int limit = 300;
while (mockTimer.IsTimerActive(nullptr) && limit-- > 0)
{
AdvanceClock(System::Clock::Milliseconds64(100));
}
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 254u);
}
TEST_F(TestLevelControlOnOff, TestStoredLevelCorruption)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{ LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster) };
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// 1. Set Level 200. On.
EXPECT_EQ(onOffCluster.SetOnOff(true), CHIP_NO_ERROR);
EXPECT_TRUE(cluster
.MoveToLevel(200, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
// Prime mLevelBeforeTurnedOff by turning Off and On once
// Off -> Stores 200.
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
while (mockTimer.IsTimerActive(nullptr))
AdvanceClock(System::Clock::Milliseconds64(1000));
EXPECT_FALSE(onOffCluster.GetOnOff());
// On -> Restores 200.
EXPECT_EQ(onOffCluster.SetOnOff(true), CHIP_NO_ERROR);
while (mockTimer.IsTimerActive(nullptr))
AdvanceClock(System::Clock::Milliseconds64(1000));
EXPECT_TRUE(onOffCluster.GetOnOff());
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 200u);
// 2. MoveToLevelWithOnOff(MinLevel, time=10s).
// This should NOT overwrite mLevelBeforeTurnedOff (200) with MinLevel (0/1).
uint8_t minLevel = cluster.GetMinLevel();
Commands::MoveToLevelWithOnOff::Type data;
data.level = minLevel;
data.transitionTime.SetNonNull(100);
data.optionsMask.ClearAll();
data.optionsOverride.ClearAll();
EXPECT_TRUE(tester.Invoke(Commands::MoveToLevelWithOnOff::Id, data).IsSuccess());
// 3. Advance time to finish.
int limit = 200;
while (mockTimer.IsTimerActive(nullptr) && limit-- > 0)
{
AdvanceClock(System::Clock::Milliseconds64(1000));
}
EXPECT_FALSE(onOffCluster.GetOnOff());
// 4. Turn On (Simulate OnOff Cluster On command)
// Should restore 200 (from mLevelBeforeTurnedOff) NOT MinLevel (0/1).
EXPECT_EQ(onOffCluster.SetOnOff(true), CHIP_NO_ERROR);
while (mockTimer.IsTimerActive(nullptr))
{
AdvanceClock(System::Clock::Milliseconds64(1000));
}
// 5. Check Level.
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 200u);
}
TEST_F(TestLevelControlOnOff, TestImmediateMoveToMinLevelWithOnOff)
{
chip::app::Clusters::OnOffCluster::Context onOffContext{ mockTimer };
chip::app::Clusters::OnOffCluster onOffCluster{ kTestEndpointId, onOffContext };
LevelControlCluster cluster{
LevelControlCluster::Config(kTestEndpointId, mockTimer, mockDelegate).WithOnOff(onOffCluster).WithMinLevel(1)
};
onOffCluster.AddDelegate(&cluster);
chip::Testing::ClusterTester tester(cluster);
EXPECT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
EXPECT_EQ(onOffCluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
// Initial state: Level 100, Off
EXPECT_TRUE(cluster
.MoveToLevel(100, DataModel::MakeNullable(static_cast<uint16_t>(0)),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff),
BitMask<LevelControl::OptionsBitmap>(LevelControl::OptionsBitmap::kExecuteIfOff))
.IsSuccess());
EXPECT_EQ(onOffCluster.SetOnOff(false), CHIP_NO_ERROR);
// Command: Move to 1 (MinLevel) with OnOff, immediate (0s)
Commands::MoveToLevelWithOnOff::Type data;
data.level = 1;
data.transitionTime.SetNonNull(0);
data.optionsMask.ClearAll();
data.optionsOverride.ClearAll();
EXPECT_TRUE(tester.Invoke(Commands::MoveToLevelWithOnOff::Id, data).IsSuccess());
// Check Level
DataModel::Nullable<uint8_t> readLevel;
EXPECT_TRUE(tester.ReadAttribute(Attributes::CurrentLevel::Id, readLevel).IsSuccess());
EXPECT_EQ(readLevel.Value(), 1u);
// Check OnOff state.
// mOn should be FALSE at the end because we moved to MinLevel.
EXPECT_FALSE(onOffCluster.GetOnOff());
}