Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[OPSTATE] Add Q test script for CountdownTime #34632

Merged
merged 25 commits into from
Aug 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -578,6 +578,7 @@ jobs:
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'
Original file line number Diff line number Diff line change
@@ -138,6 +138,7 @@ class OperationalStateDelegate : public GenericOperationalStateDelegateImpl
};

Instance * GetOperationalStateInstance();
OperationalStateDelegate * GetOperationalStateDelegate();

void Shutdown();

Original file line number Diff line number Diff line change
@@ -59,6 +59,7 @@ void GenericOperationalStateDelegateImpl::HandlePauseStateCallback(GenericOperat
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 @@ void GenericOperationalStateDelegateImpl::HandleResumeStateCallback(GenericOpera
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 @@ void GenericOperationalStateDelegateImpl::HandleStartStateCallback(GenericOperat
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 GenericOperationalStateDelegateImpl::HandleStopStateCallback(GenericOperati
{
(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 @@ static void onOperationalStateTimerTick(System::Layer * systemLayer, void * data
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 @@ OperationalState::Instance * OperationalState::GetOperationalStateInstance()
return gOperationalStateInstance;
}

OperationalStateDelegate * OperationalState::GetOperationalStateDelegate()
{
return gOperationalStateDelegate;
}

void OperationalState::Shutdown()
{
if (gOperationalStateInstance != nullptr)
50 changes: 47 additions & 3 deletions examples/all-clusters-app/linux/AllClustersCommandDelegate.cpp
Original file line number Diff line number Diff line change
@@ -667,21 +667,65 @@ void AllClustersAppCommandHandler::OnModeChangeHandler(std::string device, std::

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));
10 changes: 10 additions & 0 deletions examples/all-clusters-app/linux/AllClustersCommandDelegate.h
Original file line number Diff line number Diff line change
@@ -105,6 +105,16 @@ class AllClustersAppCommandHandler
* 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
62 changes: 62 additions & 0 deletions src/python_testing/TC_OPSTATE_2_6.py
Original file line number Diff line number Diff line change
@@ -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()
112 changes: 111 additions & 1 deletion src/python_testing/TC_OpstateCommon.py
Original file line number Diff line number Diff line change
@@ -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 @@ async def TEST_TC_OPSTATE_BASE_2_5(self, endpoint=1):

# 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 @@ async def TEST_TC_OPSTATE_BASE_2_5(self, endpoint=1):
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)