[OPSTATE] Add Q test script for CountdownTime (#34632)
* Add Q test
* Added test to test set
* Remove unused var
* Restyled by autopep8
* Restyled by isort
* Fix name
* Use pics over other method
* Removed unused stuff
* Added pipe commands
* Fix reset
* Get example to report appropriate changes.
* WiP
* Added some comments
* Changes to make things work
* Removed dev msgs
* Missed some
* Removed dev msgs
* Straggler
* Restyled by clang-format
* Restyled by autopep8
* Restyled by isort
* Commented unused var
* Update examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
* Fix bug
---------
Co-authored-by: Restyled.io <commits@restyled.io>
diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml
index 8284b5a..5e38c6c 100644
--- a/.github/workflows/tests.yaml
+++ b/.github/workflows/tests.yaml
@@ -578,6 +578,7 @@
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_3.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_4.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_5.py'
+ scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OPSTATE_2_6.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_1.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_2.py'
scripts/run_in_python_env.sh out/venv './scripts/tests/run_python_test.py --load-from-env /tmp/test_env.yaml --script src/python_testing/TC_OVENOPSTATE_2_3.py'
diff --git a/examples/all-clusters-app/all-clusters-common/include/operational-state-delegate-impl.h b/examples/all-clusters-app/all-clusters-common/include/operational-state-delegate-impl.h
index 60b6b09..badadd6 100644
--- a/examples/all-clusters-app/all-clusters-common/include/operational-state-delegate-impl.h
+++ b/examples/all-clusters-app/all-clusters-common/include/operational-state-delegate-impl.h
@@ -138,6 +138,7 @@
};
Instance * GetOperationalStateInstance();
+OperationalStateDelegate * GetOperationalStateDelegate();
void Shutdown();
diff --git a/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp
index d258b82..d4a91cf 100644
--- a/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp
+++ b/examples/all-clusters-app/all-clusters-common/src/operational-state-delegate-impl.cpp
@@ -59,6 +59,7 @@
auto error = GetInstance()->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kPaused));
if (error == CHIP_NO_ERROR)
{
+ GetInstance()->UpdateCountdownTimeFromDelegate();
err.Set(to_underlying(ErrorStateEnum::kNoError));
}
else
@@ -73,6 +74,7 @@
auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning));
if (error == CHIP_NO_ERROR)
{
+ GetInstance()->UpdateCountdownTimeFromDelegate();
err.Set(to_underlying(ErrorStateEnum::kNoError));
}
else
@@ -96,6 +98,7 @@
auto error = GetInstance()->SetOperationalState(to_underlying(OperationalStateEnum::kRunning));
if (error == CHIP_NO_ERROR)
{
+ GetInstance()->UpdateCountdownTimeFromDelegate();
(void) DeviceLayer::SystemLayer().StartTimer(System::Clock::Seconds16(1), onOperationalStateTimerTick, this);
err.Set(to_underlying(ErrorStateEnum::kNoError));
}
@@ -113,6 +116,8 @@
{
(void) DeviceLayer::SystemLayer().CancelTimer(onOperationalStateTimerTick, this);
+ GetInstance()->UpdateCountdownTimeFromDelegate();
+
OperationalState::GenericOperationalError current_err(to_underlying(OperationalState::ErrorStateEnum::kNoError));
GetInstance()->GetCurrentOperationalError(current_err);
@@ -152,6 +157,11 @@
delegate->mPausedTime++;
}
}
+ else if (!countdown_time.IsNull() && countdown_time.Value() <= 0)
+ {
+ OperationalState::GenericOperationalError noError(to_underlying(OperationalState::ErrorStateEnum::kNoError));
+ delegate->HandleStopStateCallback(noError);
+ }
if (state == OperationalState::OperationalStateEnum::kRunning || state == OperationalState::OperationalStateEnum::kPaused)
{
@@ -173,6 +183,11 @@
return gOperationalStateInstance;
}
+OperationalStateDelegate * OperationalState::GetOperationalStateDelegate()
+{
+ return gOperationalStateDelegate;
+}
+
void OperationalState::Shutdown()
{
if (gOperationalStateInstance != nullptr)
diff --git a/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp b/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
index d620731..6c1467f 100644
--- a/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
+++ b/examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
@@ -667,21 +667,65 @@
void AllClustersAppCommandHandler::OnOperationalStateChange(std::string device, std::string operation, Json::Value param)
{
- OperationalState::Instance * operationalStateInstance = nullptr;
if (device == "Generic")
{
- operationalStateInstance = OperationalState::GetOperationalStateInstance();
+ OnGenericOperationalStateChange(device, operation, param);
}
else if (device == "Oven")
{
- operationalStateInstance = OvenCavityOperationalState::GetOperationalStateInstance();
+ OnOvenOperationalStateChange(device, operation, param);
}
else
{
ChipLogDetail(NotSpecified, "Invalid device type : %s", device.c_str());
return;
}
+}
+void AllClustersAppCommandHandler::OnGenericOperationalStateChange(std::string device, std::string operation, Json::Value param)
+{
+ OperationalState::Instance * operationalStateInstance = OperationalState::GetOperationalStateInstance();
+ OperationalState::OperationalStateDelegate * operationalStateDelegate = OperationalState::GetOperationalStateDelegate();
+ OperationalState::GenericOperationalError noError(to_underlying(OperationalState::ErrorStateEnum::kNoError));
+ OperationalState::OperationalStateEnum state =
+ static_cast<OperationalState::OperationalStateEnum>(operationalStateInstance->GetCurrentOperationalState());
+ if (operation == "Start")
+ {
+ operationalStateDelegate->HandleStartStateCallback(noError);
+ }
+ else if (operation == "Resume")
+ {
+ operationalStateDelegate->HandleResumeStateCallback(noError);
+ }
+ else if (operation == "Pause")
+ {
+ operationalStateDelegate->HandlePauseStateCallback(noError);
+ }
+ else if (operation == "Stop" && state == OperationalState::OperationalStateEnum::kRunning)
+ {
+ operationalStateDelegate->HandleStopStateCallback(noError);
+ }
+ else if (operation == "OnFault")
+ {
+ uint8_t event_id = to_underlying(OperationalState::ErrorStateEnum::kUnableToCompleteOperation);
+ if (!param.isNull())
+ {
+ event_id = to_underlying(static_cast<OperationalState::ErrorStateEnum>(param.asUInt()));
+ }
+
+ OperationalState::GenericOperationalError err(event_id);
+ operationalStateInstance->OnOperationalErrorDetected(err);
+ }
+ else
+ {
+ ChipLogDetail(NotSpecified, "Invalid operation : %s", operation.c_str());
+ return;
+ }
+}
+
+void AllClustersAppCommandHandler::OnOvenOperationalStateChange(std::string device, std::string operation, Json::Value param)
+{
+ OperationalState::Instance * operationalStateInstance = OvenCavityOperationalState::GetOperationalStateInstance();
if (operation == "Start" || operation == "Resume")
{
operationalStateInstance->SetOperationalState(to_underlying(OperationalState::OperationalStateEnum::kRunning));
diff --git a/examples/all-clusters-app/linux/AllClustersCommandDelegate.h b/examples/all-clusters-app/linux/AllClustersCommandDelegate.h
index f097c53..f1b873f 100644
--- a/examples/all-clusters-app/linux/AllClustersCommandDelegate.h
+++ b/examples/all-clusters-app/linux/AllClustersCommandDelegate.h
@@ -105,6 +105,16 @@
* Should be called when it is necessary to change the operational state as a manual operation.
*/
void OnOperationalStateChange(std::string device, std::string operation, Json::Value param);
+
+ /**
+ * Should be called when it is necessary to change the operational state as a manual operation.
+ */
+ void OnGenericOperationalStateChange(std::string device, std::string operation, Json::Value param);
+
+ /**
+ * Should be called when it is necessary to change the operational state as a manual operation.
+ */
+ void OnOvenOperationalStateChange(std::string device, std::string operation, Json::Value param);
};
class AllClustersCommandDelegate : public NamedPipeCommandDelegate
diff --git a/src/python_testing/TC_OPSTATE_2_6.py b/src/python_testing/TC_OPSTATE_2_6.py
new file mode 100644
index 0000000..8560452
--- /dev/null
+++ b/src/python_testing/TC_OPSTATE_2_6.py
@@ -0,0 +1,62 @@
+#
+# Copyright (c) 2024 Project CHIP Authors
+# All rights reserved.
+#
+# 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.
+#
+
+# See https://github.com/project-chip/connectedhomeip/blob/master/docs/testing/python.md#defining-the-ci-test-arguments
+# for details about the block below.
+#
+# === BEGIN CI TEST ARGUMENTS ===
+# test-runner-runs: run1
+# test-runner-run/run1/app: ${ALL_CLUSTERS_APP}
+# test-runner-run/run1/factoryreset: True
+# test-runner-run/run1/quiet: True
+# test-runner-run/run1/app-args: --discriminator 1234 --KVS kvs1 --trace-to json:${TRACE_APP}.json
+# test-runner-run/run1/script-args: --endpoint 1 --int-arg PIXIT.WAITTIME.REBOOT:5 --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --PICS src/app/tests/suites/certification/ci-pics-values --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto
+# === END CI TEST ARGUMENTS ===
+
+
+import chip.clusters as Clusters
+from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main
+from TC_OpstateCommon import TC_OPSTATE_BASE, TestInfo
+
+
+class TC_OPSTATE_2_6(MatterBaseTest, TC_OPSTATE_BASE):
+ def __init__(self, *args):
+ super().__init__(*args)
+
+ test_info = TestInfo(
+ pics_code="OPSTATE",
+ cluster=Clusters.OperationalState
+ )
+
+ super().setup_base(test_info=test_info)
+
+ def steps_TC_OPSTATE_2_6(self) -> list[TestStep]:
+ return self.steps_TC_OPSTATE_BASE_2_6()
+
+ def pics_TC_OPSTATE_2_6(self) -> list[str]:
+ return ["OPSTATE.S", "OPSTATE.S.A0002"]
+
+ @async_test_body
+ async def test_TC_OPSTATE_2_6(self):
+ # endpoint = self.matter_test_config.endpoint
+
+ # await self.TEST_TC_OPSTATE_BASE_2_6(endpoint=endpoint)
+ await self.TEST_TC_OPSTATE_BASE_2_6(endpoint=1)
+
+
+if __name__ == "__main__":
+ default_matter_test_main()
diff --git a/src/python_testing/TC_OpstateCommon.py b/src/python_testing/TC_OpstateCommon.py
index 09dc73e..ece50ac 100644
--- a/src/python_testing/TC_OpstateCommon.py
+++ b/src/python_testing/TC_OpstateCommon.py
@@ -28,7 +28,7 @@
from chip.clusters.Attribute import EventReadResult, SubscriptionTransaction
from chip.clusters.Types import NullValue
from chip.interaction_model import InteractionModelError, Status
-from matter_testing_support import EventChangeCallback, TestStep
+from matter_testing_support import ClusterAttributeChangeAccumulator, EventChangeCallback, TestStep
from mobly import asserts
@@ -1082,6 +1082,7 @@
# STEP 7: TH waits for initial-countdown-time
self.step(7)
+ logging.info(f'Sleeping for {initial_countdown_time:.1f} seconds.')
time.sleep(initial_countdown_time)
# STEP 8: TH sends Stop command to the DUT
@@ -1221,3 +1222,112 @@
self.skip_step(20)
self.skip_step(21)
self.skip_step(22)
+
+ ############################
+ # TEST CASE 2.6 - Optional Reports with DUT as Server
+ ############################
+ def steps_TC_OPSTATE_BASE_2_6(self) -> list[TestStep]:
+ steps = [TestStep(1, "Commissioning, already done", is_commissioning=True),
+ TestStep(2, "Subscribe to CountdownTime attribute"),
+ TestStep(3, "Manually put the DUT into a state where it will use the CountdownTime attribute, "
+ "the initial value of the CountdownTime is greater than 30, "
+ "and it will begin counting down the CountdownTime attribute."),
+ TestStep(4, "Over a period of 30 seconds, TH counts all report transactions with an attribute "
+ "report for the CountdownTime attribute in numberOfReportsReceived"),
+ TestStep(5, "Until the current operation finishes, TH counts all report transactions with "
+ "an attribute report for the CountdownTime attribute in numberOfReportsReceived and saves up to 5 such reports."),
+ TestStep(6, "Manually put the DUT into a state where it will use the CountdownTime attribute, "
+ "the initial value of the CountdownTime is greater than 30, and it will begin counting down the CountdownTime attribute."),
+ TestStep(7, "TH reads from the DUT the OperationalState attribute"),
+ TestStep(8, "Manually put the device in the Paused(0x02) operational state")
+ ]
+ return steps
+
+ async def TEST_TC_OPSTATE_BASE_2_6(self, endpoint=1):
+ cluster = self.test_info.cluster
+ attributes = cluster.Attributes
+
+ self.init_test()
+
+ # commission
+ self.step(1)
+
+ # Note that this does a subscribe-all instead of subscribing only to the CountdownTime attribute.
+ # To-Do: Update the TP to subscribe-all.
+ self.step(2)
+ sub_handler = ClusterAttributeChangeAccumulator(cluster)
+ await sub_handler.start(self.default_controller, self.dut_node_id, endpoint)
+
+ self.step(3)
+ if self.pics_guard(self.check_pics(f"{self.test_info.pics_code}.S.M.ST_RUNNING")):
+ self.send_manual_or_pipe_command(name="OperationalStateChange",
+ device=self.device,
+ operation="Start")
+ time.sleep(1)
+ await self.read_and_expect_value(endpoint=endpoint,
+ attribute=attributes.OperationalState,
+ expected_value=cluster.Enums.OperationalStateEnum.kRunning)
+ count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+ asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
+ else:
+ self.skip_step(3)
+
+ sub_handler.reset()
+ self.step(4)
+ logging.info('Test will now collect data for 30 seconds')
+ time.sleep(30)
+
+ count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+ sub_handler.reset()
+ asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
+ asserts.assert_greater_equal(count, 0, "Did not receive any reports for CountdownTime")
+
+ attr_value = await self.read_expect_success(
+ endpoint=endpoint,
+ attribute=attributes.OperationalState)
+ if attr_value == cluster.Enums.OperationalStateEnum.kRunning:
+ self.step(5)
+ wait_count = 0
+ while (attr_value != cluster.Enums.OperationalStateEnum.kStopped) and (wait_count < 20):
+ time.sleep(1)
+ wait_count = wait_count + 1
+ attr_value = await self.read_expect_success(
+ endpoint=endpoint,
+ attribute=attributes.OperationalState)
+ count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+ asserts.assert_less_equal(count, 5, "Received more than 5 reports for CountdownTime")
+ asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
+ else:
+ self.skip_step(5)
+
+ sub_handler.reset()
+ self.step(6)
+ if self.pics_guard(self.check_pics(f"{self.test_info.pics_code}.S.M.ST_RUNNING")):
+ self.send_manual_or_pipe_command(name="OperationalStateChange",
+ device=self.device,
+ operation="Start")
+ time.sleep(1)
+ await self.read_and_expect_value(endpoint=endpoint,
+ attribute=attributes.OperationalState,
+ expected_value=cluster.Enums.OperationalStateEnum.kRunning)
+ count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+ asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
+ else:
+ self.skip_step(6)
+
+ self.step(7)
+ await self.read_and_expect_value(endpoint=endpoint,
+ attribute=attributes.OperationalState,
+ expected_value=cluster.Enums.OperationalStateEnum.kRunning)
+
+ sub_handler.reset()
+ self.step(8)
+ if self.pics_guard(self.check_pics(f"{self.test_info.pics_code}.S.M.ST_PAUSED")):
+ self.send_manual_or_pipe_command(name="OperationalStateChange",
+ device=self.device,
+ operation="Pause")
+ time.sleep(1)
+ count = sub_handler.attribute_report_counts[attributes.CountdownTime]
+ asserts.assert_greater(count, 0, "Did not receive any reports for CountdownTime")
+ else:
+ self.skip_step(8)