From d0d91272068f267cf880f9d56787ca28da885673 Mon Sep 17 00:00:00 2001 From: Jae Son <12516308+jaehs6sam@users.noreply.github.com> Date: Thu, 1 Aug 2024 17:04:22 -0500 Subject: [PATCH] Occupancy Sensing cluster Python test scripts (#34572) * Occupancy Sensing cluster Python test scripts Consolidate previous TC-OCC-xx test script PRs (PR 34490, PR 34491, PR 34492, PR 34523) into this one upload. * Update TC_OCC_2_1.py * Update TC_OCC_2_2.py syntax error * Update TC_OCC_2_3.py * Update TC_OCC_3_1.py * Update TC_OCC_3_2.py * Update TC_OCC_2_1.py Accommodated recent test plan attribute conformance and Cecille comments * Update TC_OCC_2_2.py Reflected table 2.7.6.2 * Update TC_OCC_2_3.py Modified according to comments * Update TC_OCC_3_1.py Add CLI * Update TC_OCC_3_2.py Fixed some errors and added some random values for subscription testing * Update TC_OCC_2_1.py Updated more detail test step failure description. * Update TC_OCC_3_1.py Updating some of user input call according to comments. * Update TC_OCC_2_1.py python code bug fixing * Update TC_OCC_2_2.py python bug fixing * Update TC_OCC_2_3.py python bug fix * Update TC_OCC_3_2.py * Update TC_OCC_3_2.py step numbering fix * Update TC_OCC_3_2.py * Update TC_OCC_3_2.py * Update TC_OCC_3_1.py * Update TC_OCC_2_3.py * Update TC_OCC_2_2.py * Update TC_OCC_2_1.py * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update src/python_testing/TC_OCC_2_1.py Co-authored-by: C Freeman * Update TC_OCC_2_2.py Fixed bitmap conditional statement * Update TC_OCC_3_1.py * Update TC_OCC_2_1.py Add additional changes according to comments received. * Update TC_OCC_3_1.py * Update TC_OCC_3_2.py test step change * Update TC_OCC_2_1.py * Update TC_OCC_2_1.py * Update TC_OCC_2_1.py * Update TC_OCC_2_1.py * Update TC_OCC_2_1.py * Update TC_OCC_2_1.py update on PIR * Update TC_OCC_2_2.py * Update TC_OCC_2_1.py restyled * Update TC_OCC_2_2.py restyled * Update TC_OCC_2_3.py restyled * Update TC_OCC_3_1.py restyled * Update TC_OCC_3_2.py restyled * Update TC_OCC_2_1.py * Update TC_OCC_2_1.py * Update TC_OCC_2_1.py * Restyle * Update TC_OCC_2_2.py bitmap conditional statement revised * Update TC_OCC_2_2.py Bitmap matching statement update * Update TC_OCC_3_1.py * Update src/python_testing/TC_OCC_2_2.py typo Co-authored-by: Andrei Litvin * Update src/python_testing/TC_OCC_2_2.py Co-authored-by: Andrei Litvin * Update TC_OCC_2_2.py * Update TC_OCC_2_2.py * Update TC_OCC_2_2.py rewriting sensor type check * Update TC_OCC_2_2.py * Update TC_OCC_2_2.py * Update TC_OCC_2_2.py * Restyle * Proposal for TC_OCC bitmap compares * Fix typo * Fix map * Update TC_OCC_3_2.py added else on conditional statement. * Update TC_OCC_3_2.py put skip back * Restyle * Typo fixes and enable some tests in CI * Fix OCC_2_1 to run in CI * Fix all clusters for TC_OCC_2_2 and some logging * make all TC_OCC_2_* pass * Restyle * Enable tests in ci --------- Co-authored-by: C Freeman Co-authored-by: Andrei Litvin Co-authored-by: Andrei Litvin --- .github/workflows/tests.yaml | 3 + .../all-clusters-app.matter | 10 +- .../all-clusters-common/all-clusters-app.zap | 44 ++- .../src/occupancy-sensing-stub.cpp | 2 +- src/python_testing/TC_OCC_2_1.py | 278 ++++++++++++++++++ src/python_testing/TC_OCC_2_2.py | 132 +++++++++ src/python_testing/TC_OCC_2_3.py | 127 ++++++++ src/python_testing/TC_OCC_3_1.py | 126 ++++++++ src/python_testing/TC_OCC_3_2.py | 248 ++++++++++++++++ 9 files changed, 959 insertions(+), 11 deletions(-) create mode 100644 src/python_testing/TC_OCC_2_1.py create mode 100644 src/python_testing/TC_OCC_2_2.py create mode 100644 src/python_testing/TC_OCC_2_3.py create mode 100644 src/python_testing/TC_OCC_3_1.py create mode 100644 src/python_testing/TC_OCC_3_2.py diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5e38c6c341bea6..c19eccb9a30457 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -571,6 +571,9 @@ 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_TestEventTrigger.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/TestBatchInvoke.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/TestGroupTableReports.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_OCC_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_OCC_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_OCC_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_OPCREDS_3_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_OPCREDS_3_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_OPSTATE_2_1.py' diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter index f69ed20583379c..eb172fa54dcff9 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.matter @@ -9025,10 +9025,11 @@ endpoint 1 { server cluster OccupancySensing { ram attribute occupancy; ram attribute occupancySensorType; - ram attribute occupancySensorTypeBitmap; + ram attribute occupancySensorTypeBitmap default = 1; ram attribute holdTime default = 10; callback attribute holdTimeLimits; - ram attribute featureMap default = 0x01; + ram attribute PIROccupiedToUnoccupiedDelay default = 10; + ram attribute featureMap default = 0x02; ram attribute clusterRevision default = 5; } @@ -9502,10 +9503,11 @@ endpoint 2 { server cluster OccupancySensing { ram attribute occupancy; ram attribute occupancySensorType; - ram attribute occupancySensorTypeBitmap; + ram attribute occupancySensorTypeBitmap default = 1; ram attribute holdTime default = 20; callback attribute holdTimeLimits; - ram attribute featureMap default = 0x01; + ram attribute PIROccupiedToUnoccupiedDelay default = 10; + ram attribute featureMap default = 0x02; ram attribute clusterRevision default = 5; } } diff --git a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap index 7b3ccf977dbc2b..cde6889cd93f19 100644 --- a/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap +++ b/examples/all-clusters-app/all-clusters-common/all-clusters-app.zap @@ -16678,7 +16678,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -16726,7 +16726,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -19103,7 +19103,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "1", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -19141,6 +19141,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "PIROccupiedToUnoccupiedDelay", + "code": 16, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -19151,7 +19167,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x01", + "defaultValue": "0x02", "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -25244,7 +25260,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": "1", "reportable": 1, "minInterval": 0, "maxInterval": 65344, @@ -25282,6 +25298,22 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "PIROccupiedToUnoccupiedDelay", + "code": 16, + "mfgCode": null, + "side": "server", + "type": "int16u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "10", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "FeatureMap", "code": 65532, @@ -25292,7 +25324,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x01", + "defaultValue": "0x02", "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/examples/all-clusters-app/all-clusters-common/src/occupancy-sensing-stub.cpp b/examples/all-clusters-app/all-clusters-common/src/occupancy-sensing-stub.cpp index dcbb90ff659bcb..776e108911aaef 100644 --- a/examples/all-clusters-app/all-clusters-common/src/occupancy-sensing-stub.cpp +++ b/examples/all-clusters-app/all-clusters-common/src/occupancy-sensing-stub.cpp @@ -36,7 +36,7 @@ void emberAfOccupancySensingClusterInitCallback(EndpointId endpointId) VerifyOrDie(!gAttrAccess[endpointId]); gAttrAccess[endpointId] = std::make_unique( - BitMask(OccupancySensing::Feature::kOther)); + BitMask(OccupancySensing::Feature::kPassiveInfrared)); OccupancySensing::Structs::HoldTimeLimitsStruct::Type holdTimeLimits = { .holdTimeMin = 1, diff --git a/src/python_testing/TC_OCC_2_1.py b/src/python_testing/TC_OCC_2_1.py new file mode 100644 index 00000000000000..cf39a7eff5a111 --- /dev/null +++ b/src/python_testing/TC_OCC_2_1.py @@ -0,0 +1,278 @@ +# +# 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. +# +# There are CI issues to be followed up for the test cases below that implements manually controlling sensor device for +# the occupancy state ON/OFF change. +# [TC-OCC-3.1] test procedure step 4 +# [TC-OCC-3.2] test precedure step 3a, 3c + +# 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: --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 logging + +import chip.clusters as Clusters +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_OCC_2_1(MatterBaseTest): + async def read_occ_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.OccupancySensing + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + def desc_TC_OCC_2_1(self) -> str: + return "[TC-OCC-2.1] Attributes with DUT as Server" + + def steps_TC_OCC_2_1(self) -> list[TestStep]: + steps = [ + TestStep(1, "Commissioning, already done", is_commissioning=True), + TestStep(2, "Read Occupancy attribute."), + TestStep(3, "Read OccupancySensorType attribute."), + TestStep(4, "Read OccupancySensorTypeBitmap attribute."), + TestStep(5, "Read HoldTimeLimits attribute, if supported"), + TestStep(6, "Read HoldTime attribute, if supported"), + TestStep(7, "Read PIROccupiedToUnoccupiedDelay attribute, if supported"), + TestStep(8, "Read PIRUnoccupiedToOccupiedDelay attribute, if supported"), + TestStep(9, "Read PIRUnoccupiedToOccupiedThreshold attribute, if supported"), + TestStep(10, "Read UltrasonicOccupiedToUnoccupiedDelay attribute, if supported"), + TestStep(11, "Read UltrasonicUnoccupiedToOccupiedDelay attribute, if supported"), + TestStep(12, "Read UltrasonicUnoccupiedToOccupiedThreshold attribute, if supported"), + TestStep(13, "Read PhysicalContactOccupiedToUnoccupiedDelay attribute, if supported"), + TestStep(14, "Read PhysicalContactUnoccupiedToOccupiedDelay attribute, if supported"), + TestStep(15, "Read PhysicalContactUnoccupiedToOccupiedThreshold attribute, if supported") + ] + return steps + + def pics_TC_OCC_2_1(self) -> list[str]: + pics = [ + "OCC.S", + ] + return pics + + @async_test_body + async def test_TC_OCC_2_1(self): + + endpoint = self.user_params.get("endpoint", 1) + + self.step(1) + attributes = Clusters.OccupancySensing.Attributes + attribute_list = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.AttributeList) + + self.step(2) + asserts.assert_in(attributes.Occupancy.attribute_id, attribute_list, "Occupancy attribute is mandatory") + occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + asserts.assert_less_equal(occupancy_dut, 0b00000001, "Occupancy attribute is not in valid range") + + self.step(3) + asserts.assert_in(attributes.OccupancySensorType.attribute_id, attribute_list, + "OccupancySensorType attribute is a mandatory attribute.") + + occupancy_sensor_type_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.OccupancySensorType) + asserts.assert_less(occupancy_sensor_type_dut, Clusters.Objects.OccupancySensing.Enums.OccupancySensorTypeEnum.kUnknownEnumValue, + "OccupancySensorType is not in valid range") + asserts.assert_in(occupancy_sensor_type_dut, {Clusters.Objects.OccupancySensing.Enums.OccupancySensorTypeEnum.kPir, + Clusters.Objects.OccupancySensing.Enums.OccupancySensorTypeEnum.kUltrasonic, + Clusters.Objects.OccupancySensing.Enums.OccupancySensorTypeEnum.kPIRAndUltrasonic, + Clusters.Objects.OccupancySensing.Enums.OccupancySensorTypeEnum.kPhysicalContact}, "OccupancySensorType is not in valid range") + self.step(4) + asserts.assert_in(attributes.OccupancySensorTypeBitmap.attribute_id, attribute_list, + "OccupancySensorTypeBitmap attribute is a mandatory attribute.") + + occupancy_sensor_type_bitmap_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.OccupancySensorTypeBitmap) + asserts.assert_less_equal(occupancy_sensor_type_bitmap_dut, 0b00000111, + "OccupancySensorTypeBitmap attribute is not in valid range") + + self.step(5) + if attributes.HoldTimeLimits.attribute_id in attribute_list: + asserts.assert_in(attributes.HoldTime.attribute_id, attribute_list, "HoldTime attribute conformance failed.") + hold_time_limits_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.HoldTimeLimits) + asserts.assert_less_equal(hold_time_limits_dut.holdTimeMin, hold_time_limits_dut.holdTimeMax, + "HoldTimeMin is not in valid range") + asserts.assert_greater_equal(hold_time_limits_dut.holdTimeMin, 0, "HoldTimeMin is not in valid range") + asserts.assert_less_equal(hold_time_limits_dut.holdTimeMax, 0xFFFE, "HoldTimeMin is not in valid range") + asserts.assert_greater_equal(hold_time_limits_dut.holdTimeMax, + hold_time_limits_dut.holdTimeMin, "HoldTimeMin is not in valid range") + asserts.assert_less_equal(hold_time_limits_dut.holdTimeDefault, + hold_time_limits_dut.holdTimeMax, "HoldTimeMin is not in valid range") + asserts.assert_greater_equal(hold_time_limits_dut.holdTimeDefault, + hold_time_limits_dut.holdTimeMin, "HoldTimeMin is not in valid range") + else: + logging.info("HoldTimeLimits not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(6) + if attributes.HoldTime.attribute_id in attribute_list: + hold_time_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.HoldTime) + hold_time_limits_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.HoldTimeLimits) + + asserts.assert_less_equal(hold_time_dut, hold_time_limits_dut.holdTimeMax, "HoldTime attribute is out of range") + asserts.assert_greater_equal(hold_time_dut, hold_time_limits_dut.holdTimeMin, "HoldTime attribute is out of range") + else: + logging.info("HoldTime not supported. The rest of legacy attribute test can be skipped") + self.skip_all_remaining_steps(7) + return + + self.step(7) + if attributes.PIROccupiedToUnoccupiedDelay.attribute_id in attribute_list: + has_pir_bitmap = (occupancy_sensor_type_bitmap_dut & + Clusters.OccupancySensing.Bitmaps.OccupancySensorTypeBitmap.kPir) != 0 + has_ultrasonic_bitmap = (occupancy_sensor_type_bitmap_dut & + Clusters.OccupancySensing.Bitmaps.OccupancySensorTypeBitmap.kUltrasonic) != 0 + has_phy_bitmap = (occupancy_sensor_type_bitmap_dut & + Clusters.OccupancySensing.Bitmaps.OccupancySensorTypeBitmap.kPhysicalContact) != 0 + if has_pir_bitmap or (not has_ultrasonic_bitmap and not has_phy_bitmap): + pir_otou_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PIROccupiedToUnoccupiedDelay) + asserts.assert_less_equal(pir_otou_delay_dut, 0xFFFE, "PIROccupiedToUnoccupiedDelay is not in valid range") + asserts.assert_greater_equal(pir_otou_delay_dut, 0, "PIROccupiedToUnoccupiedDelay is not in valid range") + else: + logging.info("PIROccupiedToUnoccupiedDelay conformance failed") + asserts.fail( + f"PIROccupiedToUnoccupiedDelay conformance is incorrect: {has_pir_bitmap}, {has_ultrasonic_bitmap}, {has_phy_bitmap}") + else: + logging.info("PIROccupiedToUnoccupiedDelay not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(8) + if attributes.PIRUnoccupiedToOccupiedDelay.attribute_id in attribute_list: + has_delay = attributes.PIRUnoccupiedToOccupiedDelay.attribute_id in attribute_list + has_threshold = attributes.PIRUnoccupiedToOccupiedThreshold.attribute_id in attribute_list + asserts.assert_equal(has_delay, has_threshold, "PIRUnoccupiedToOccupiedDelay conformance failure") + pir_utoo_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PIRUnoccupiedToOccupiedDelay) + asserts.assert_less_equal(pir_utoo_delay_dut, 0xFFFE, "PIRUnoccupiedToOccupiedDelay is not in valid range") + asserts.assert_greater_equal(pir_utoo_delay_dut, 0, "PIRUnoccupiedToOccupiedDelay is not in valid range") + else: + logging.info("PIRUnoccupiedToOccupiedDelay not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(9) + if attributes.PIRUnoccupiedToOccupiedThreshold.attribute_id in attribute_list: + has_delay = attributes.PIRUnoccupiedToOccupiedDelay.attribute_id in attribute_list + has_threshold = attributes.PIRUnoccupiedToOccupiedThreshold.attribute_id in attribute_list + asserts.assert_equal(has_delay, has_threshold, "PIRUnoccupiedToOccupiedThreshold conformance failure") + pir_utoo_threshold_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PIRUnoccupiedToOccupiedThreshold) + asserts.assert_less_equal(pir_utoo_threshold_dut, 0xFE, "PIRUnoccupiedToOccupiedThreshold is not in valid range") + asserts.assert_greater_equal(pir_utoo_threshold_dut, 0, "PIRUnoccupiedToOccupiedThreshold is not in valid range") + else: + logging.info("PIRUnoccupiedToOccupiedThreshold not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(10) + if attributes.UltrasonicOccupiedToUnoccupiedDelay.attribute_id in attribute_list: + has_ultrasonic_bitmap = (occupancy_sensor_type_bitmap_dut & + Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kUltrasonic) != 0 + has_ultrasonic_delay = attributes.UltrasonicOccupiedToUnoccupiedDelay.attribute_id in attribute_list + asserts.assert_equal(has_ultrasonic_bitmap, has_ultrasonic_delay, "Bad conformance on Ultrasonic bitmap") + + ultrasonic_otou_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.UltrasonicOccupiedToUnoccupiedDelay) + asserts.assert_less_equal(ultrasonic_otou_delay_dut, 0xFFFE, + "UltrasonicOccupiedToUnoccupiedDelay is not in valid range") + asserts.assert_greater_equal(ultrasonic_otou_delay_dut, 0, "UltrasonicOccupiedToUnoccupiedDelay is not in valid range") + + else: + logging.info("UltrasonicOccupiedToUnoccupiedDelay not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(11) + if attributes.UltrasonicUnoccupiedToOccupiedDelay.attribute_id in attribute_list: + has_delay = attributes.UltrasonicUnoccupiedToOccupiedDelay.attribute_id in attribute_list + has_threshold = attributes.UltrasonicUnoccupiedToOccupiedThreshold.attribute_id in attribute_list + asserts.assert_equal(has_delay, has_threshold, "UltrasonicUnoccupiedToOccupiedDelay conformance failure") + + ultrasonic_utoo_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.UltrasonicUnoccupiedToOccupiedDelay) + asserts.assert_less_equal(ultrasonic_utoo_delay_dut, 0xFFFE, + "UltrasonicUnoccupiedToOccupiedDelay is not in valid range") + asserts.assert_greater_equal(ultrasonic_utoo_delay_dut, 0, "UltrasonicUnoccupiedToOccupiedDelay is not in valid range") + else: + logging.info("UltrasonicUnoccupiedToOccupiedDelay not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(12) + if attributes.UltrasonicUnoccupiedToOccupiedThreshold.attribute_id in attribute_list: + has_delay = attributes.UltrasonicUnoccupiedToOccupiedDelay.attribute_id in attribute_list + has_threshold = attributes.UltrasonicUnoccupiedToOccupiedThreshold.attribute_id in attribute_list + asserts.assert_equal(has_delay, has_threshold, "UltrasonicUnoccupiedToOccupiedThreshold conformance failure") + + ultrasonic_utoo_threshold_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.UltrasonicUnoccupiedToOccupiedThreshold) + asserts.assert_less_equal(ultrasonic_utoo_threshold_dut, 0xFE, + "UltrasonicUnoccupiedToOccupiedThreshold is not in valid range") + asserts.assert_greater_equal(ultrasonic_utoo_threshold_dut, 0, + "UltrasonicUnoccupiedToOccupiedThreshold is not in valid range") + + else: + logging.info("UltrasonicUnoccupiedToOccupiedThreshold not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(13) + if attributes.PhysicalContactOccupiedToUnoccupiedDelay.attribute_id in attribute_list: + has_phycon_bitmap = (occupancy_sensor_type_bitmap_dut & + Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kPhysicalContact) != 0 + has_phycon_delay = attributes.PhysicalContactOccupiedToUnoccupiedDelay.attribute_id in attribute_list + asserts.assert_equal(has_phycon_bitmap, has_phycon_delay, "Bad conformance on PhysicalContact bitmap") + phycontact_otou_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PhysicalContactOccupiedToUnoccupiedDelay) + asserts.assert_less_equal(phycontact_otou_delay_dut, 0xFFFE, + "PhysicalContactOccupiedToUnoccupiedDelay is not in valid range") + asserts.assert_greater_equal(phycontact_otou_delay_dut, 0, + "PhysicalContactOccupiedToUnoccupiedDelay is not in valid range") + + else: + logging.info("PhysicalContactOccupiedToUnoccupiedDelay not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(14) + if attributes.PhysicalContactUnoccupiedToOccupiedDelay.attribute_id in attribute_list: + has_delay = attributes.PhysicalContactUnoccupiedToOccupiedDelay.attribute_id in attribute_list + has_threshold = attributes.PhysicalContactUnoccupiedToOccupiedThreshold.attribute_id in attribute_list + asserts.assert_equal(has_delay, has_threshold, "PhysicalContactUnoccupiedToOccupiedDelay conformance failure") + + phycontact_utoo_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PhysicalContactUnoccupiedToOccupiedDelay) + asserts.assert_less_equal(phycontact_utoo_delay_dut, 0xFFFE, + "PhysicalContactUnoccupiedToOccupiedDelay is not in valid range") + asserts.assert_greater_equal(phycontact_utoo_delay_dut, 0, + "PhysicalContactUnoccupiedToOccupiedDelay is not in valid range") + + else: + logging.info("PhysicalContactUnoccupiedToOccupiedDelay not supported. Test step skipped") + self.mark_current_step_skipped() + + self.step(15) + if attributes.PhysicalContactUnoccupiedToOccupiedThreshold.attribute_id in attribute_list: + has_delay = attributes.PhysicalContactUnoccupiedToOccupiedDelay.attribute_id in attribute_list + has_threshold = attributes.PhysicalContactUnoccupiedToOccupiedThreshold.attribute_id in attribute_list + asserts.assert_equal(has_delay, has_threshold, "PhysicalContactUnoccupiedToOccupiedThreshold conformance failure") + + phycontact_utoo_threshold_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PhysicalContactUnoccupiedToOccupiedThreshold) + asserts.assert_less_equal(phycontact_utoo_threshold_dut, 0xFE, + "PhysicalContactUnoccupiedToOccupiedThreshold is not in valid range") + asserts.assert_greater_equal(phycontact_utoo_threshold_dut, 0, + "PhysicalContactUnoccupiedToOccupiedThreshold is not in valid range") + + else: + logging.info("PhysicalContactUnoccupiedToOccupiedThreshold not supported. Test step skipped") + self.mark_current_step_skipped() + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_OCC_2_2.py b/src/python_testing/TC_OCC_2_2.py new file mode 100644 index 00000000000000..e27bbf30ebcade --- /dev/null +++ b/src/python_testing/TC_OCC_2_2.py @@ -0,0 +1,132 @@ +# +# 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: --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 mobly import asserts + + +class TC_OCC_2_2(MatterBaseTest): + async def read_occ_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.OccupancySensing + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + def desc_TC_OCC_2_2(self) -> str: + return "[TC-OCC-2.2] OccupancySensorTypeBitmap and OccupancySensorType interdependency with server as DUT" + + def steps_TC_OCC_2_2(self) -> list[TestStep]: + steps = [ + TestStep(1, "Commissioning, already done", is_commissioning=True), + TestStep(2, "Read OccupancySensorType attribute selection based on FeatureMap Bitmap."), + TestStep(3, "Read OccupancySensorTypeBitmap attribute selection based on FeatureMap Bitmap.") + ] + return steps + + def pics_TC_OCC_2_2(self) -> list[str]: + pics = [ + "OCC.S", + ] + return pics + + @async_test_body + async def test_TC_OCC_2_2(self): + + endpoint = self.user_params.get("endpoint", 1) + + attributes = Clusters.OccupancySensing.Attributes + feature_map = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.FeatureMap) + + self.step(1) + attribute_list = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.AttributeList) + + self.step(2) + # OccupancySensorType will be determined by FeatureMap matching table at 2.7.6.2. + asserts.assert_in(attributes.OccupancySensorType.attribute_id, attribute_list, + "OccupancySensorType attribute is a mandatory attribute.") + occupancy_sensor_type_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.OccupancySensorType) + + # For validation purposes, 2.7.6.2 table describes what feature flags map to what type of sensors + TypeEnum = Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum + + Y = True + N = False + # Map is PIR, US, PHY => expected sensor type + # odd Y/N mapping to make the table align nicely + mappings = { + (N, N, N): TypeEnum.kPir, + (Y, N, N): TypeEnum.kPir, + (N, Y, N): TypeEnum.kUltrasonic, + (Y, Y, N): TypeEnum.kPIRAndUltrasonic, + (N, N, Y): TypeEnum.kPhysicalContact, + (Y, N, Y): TypeEnum.kPir, + (N, Y, Y): TypeEnum.kUltrasonic, + (Y, Y, Y): TypeEnum.kPIRAndUltrasonic, + } + + FeatureBit = Clusters.OccupancySensing.Bitmaps.Feature + expected = mappings.get( + ( + (feature_map & FeatureBit.kPassiveInfrared) != 0, + (feature_map & FeatureBit.kUltrasonic) != 0, + (feature_map & FeatureBit.kPhysicalContact) != 0 + )) + + asserts.assert_equal( + occupancy_sensor_type_dut, + expected, + f"Sensor Type should be f{expected}" + ) + + self.step(3) + # OccupancySensorTypeBitmap will be determined by FeatureMap matching table at 2.7.6.2. + asserts.assert_in(attributes.OccupancySensorTypeBitmap.attribute_id, attribute_list, + "OccupancySensorTypeBitmap attribute is a mandatory attribute.") + + occupancy_sensor_type_bitmap_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.OccupancySensorTypeBitmap) + + # Feature map must match the sensor type bitmap + must_match_bits = [ + (Clusters.OccupancySensing.Bitmaps.OccupancySensorTypeBitmap.kPir, + Clusters.OccupancySensing.Bitmaps.Feature.kPassiveInfrared, "PIR"), + (Clusters.OccupancySensing.Bitmaps.OccupancySensorTypeBitmap.kUltrasonic, + Clusters.OccupancySensing.Bitmaps.Feature.kUltrasonic, "Ultrasonic"), + (Clusters.OccupancySensing.Bitmaps.OccupancySensorTypeBitmap.kPhysicalContact, + Clusters.OccupancySensing.Bitmaps.Feature.kPhysicalContact, "Physical contact"), + ] + + for sensor_bit, feature_bit, name in must_match_bits: + asserts.assert_equal( + (occupancy_sensor_type_bitmap_dut & sensor_bit) != 0, + (feature_map & feature_bit) != 0, + f"Feature bit and sensor bitmap must be equal for {name} (BITMAP: 0x{occupancy_sensor_type_bitmap_dut:02X}, FEATUREMAP: 0x{feature_map:02X})" + ) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_OCC_2_3.py b/src/python_testing/TC_OCC_2_3.py new file mode 100644 index 00000000000000..0184b670c6b306 --- /dev/null +++ b/src/python_testing/TC_OCC_2_3.py @@ -0,0 +1,127 @@ +# +# 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: --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 logging + +import chip.clusters as Clusters +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_OCC_2_3(MatterBaseTest): + async def read_occ_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.OccupancySensing + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + def desc_TC_OCC_2_3(self) -> str: + return "[TC-OCC-2.3] HoldTime Backward Compatibility Test with server as DUT" + + def steps_TC_OCC_2_3(self) -> list[TestStep]: + steps = [ + TestStep(1, "Commission DUT to TH", is_commissioning=True), + TestStep(2, "DUT supports HoldTime attribute. If DUT doesn’t support it, then stop and exit this test case."), + TestStep(3, "Based on the feature flag value table, read OccupancySensorType attribute from DUT"), + TestStep(4, "If TH reads 0 - PIR, TH reads PIROccupiedToUnoccupiedDelay attribute and its value should be same as HoldTime"), + TestStep(5, "If TH reads 1 - Ultrasonic, TH reads UltrasonicOccupiedToUnoccupiedDelay attribute and its value should be same as HoldTime"), + TestStep(6, "If TH reads 2 - PHY, TH reads PhysicalContactOccupiedToUnoccupiedDelay attribute and its value should be same as HoldTime") + ] + return steps + + def pics_TC_OCC_2_3(self) -> list[str]: + pics = [ + "OCC.S", + ] + return pics + + @async_test_body + async def test_TC_OCC_2_3(self): + + endpoint = self.user_params.get("endpoint", 1) + + self.step(1) + attributes = Clusters.OccupancySensing.Attributes + attribute_list = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.AttributeList) + + self.step(2) + if attributes.HoldTime.attribute_id in attribute_list: + occupancy_hold_time_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.HoldTime) + else: + logging.info("No HoldTime attribute supports. Terminate this test case") + + self.step(3) + if attributes.OccupancySensorType.attribute_id in attribute_list: + occupancy_sensor_type_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.OccupancySensorType) + + asserts.assert_less_equal(occupancy_sensor_type_dut, 3, "OccupancySensorType attribute is out of range") + asserts.assert_greater_equal(occupancy_sensor_type_dut, 0, "OccupancySensorType attribute is out of range") + else: + logging.info("OccupancySensorType attribute doesn't exist. Test step skipped") + + if occupancy_sensor_type_dut == Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kPir: + self.step(4) + occupancy_pir_otou_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PIROccupiedToUnoccupiedDelay) + + asserts.assert_equal(occupancy_pir_otou_delay_dut, occupancy_hold_time_dut, + "HoldTime attribute value is not equal to PIROccupiedToUnoccupiedDelay") + self.skip_step(5) + self.skip_step(6) + + elif occupancy_sensor_type_dut == Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kUltrasonic: + self.step(4) + occupancy_pir_otou_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PIROccupiedToUnoccupiedDelay) + + asserts.assert_equal(occupancy_pir_otou_delay_dut, occupancy_hold_time_dut, + "HoldTime attribute value is not equal to PIROccupiedToUnoccupiedDelay") + self.step(5) + occupancy_us_otou_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.UltrasonicOccupiedToUnoccupiedDelay) + + asserts.assert_equal(occupancy_us_otou_delay_dut, occupancy_hold_time_dut, + "HoldTime attribute value is not equal to UltrasonicOccupiedToUnoccupiedDelay") + self.skip_step(6) + + elif occupancy_sensor_type_dut == Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kPIRAndUltrasonic: + occupancy_pirus_otou_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PIROccupiedToUnoccupiedDelay) + + asserts.assert_equal(occupancy_pirus_otou_delay_dut, occupancy_hold_time_dut, + "HoldTime attribute value is not equal to PIROccupiedToUnoccupiedDelay") + + elif occupancy_sensor_type_dut == Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kPhysicalContact: + self.skip_step(4) + self.skip_step(5) + self.step(6) + occupancy_phy_otou_delay_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PhysicalContactOccupiedToUnoccupiedDelay) + + asserts.assert_equal(occupancy_phy_otou_delay_dut, occupancy_hold_time_dut, + "HoldTime attribute value is not equal to PhysicalContactOccupiedToUnoccupiedDelay") + else: + logging.info("OccupancySensorType attribute value is out of range") + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_OCC_3_1.py b/src/python_testing/TC_OCC_3_1.py new file mode 100644 index 00000000000000..4380866333f339 --- /dev/null +++ b/src/python_testing/TC_OCC_3_1.py @@ -0,0 +1,126 @@ +# +# 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. +# +# === BEGIN CI TEST ARGUMENTS === +# test-runner-runs: run1 +# test-runner-run/run1/app: ${TYPE_OF_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: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === +# There are CI issues to be followed up for the test cases below that implements manually controlling sensor device for +# the occupancy state ON/OFF change. +# [TC-OCC-3.1] test procedure step 4 +# [TC-OCC-3.2] test precedure step 3c + +import logging +import time + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.interaction_model import Status +from matter_testing_support import MatterBaseTest, TestStep, async_test_body, default_matter_test_main +from mobly import asserts + + +class TC_OCC_3_1(MatterBaseTest): + async def read_occ_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.OccupancySensing + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + def desc_TC_OCC_3_1(self) -> str: + return "[TC-OCC-3.1] Primary functionality with server as DUT" + + def steps_TC_OCC_3_1(self) -> list[TestStep]: + steps = [ + TestStep(1, "Commission DUT to TH and obtain DUT attribute list.", is_commissioning=True), + TestStep(2, "Change DUT HoldTime attribute value to 10 seconds."), + TestStep(3, "Do not trigger DUT occupancy sensing for the period of HoldTime. TH reads Occupancy attribute from DUT."), + TestStep(4, "Trigger DUT occupancy sensing to change the occupancy state and start a timer."), + TestStep(5, "After 10 seconds, TH reads Occupancy attribute from DUT.") + ] + return steps + + def pics_TC_OCC_3_1(self) -> list[str]: + pics = [ + "OCC.S", + ] + return pics + + @async_test_body + async def test_TC_OCC_3_1(self): + + endpoint = self.user_params.get("endpoint", 1) + node_id = self.matter_test_config.dut_node_ids[0] + hold_time = 10 # 10 seconds for occupancy state hold time + + self.step(1) # commissioning and getting cluster attribute list + attributes = Clusters.OccupancySensing.Attributes + attribute_list = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.AttributeList) + + self.step(2) + if attributes.HoldTime.attribute_id in attribute_list: + # write 10 as a HoldTime attibute + write_res = await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.HoldTime(hold_time))]) + asserts.assert_equal(write_res[0].status, Status.Success, "Write HoldTime failed") + + else: + logging.info("No HoldTime attribute supports. Will test only occupancy attribute triggering functionality") + + self.step(3) + # check if Occupancy attribute is 0 + occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + + # if occupancy is on, then wait until the sensor occupancy state is 0. + if occupancy_dut == 1: + # Don't trigger occupancy sensor to render occupancy attribute to 0 + if attributes.HoldTime.attribute_id in attribute_list: + time.sleep(hold_time + 2) # add some extra 2 seconds to ensure hold time has passed. + else: # a user wait until a sensor specific time to change occupancy attribute to 0. This is the case where the sensor doesn't support HoldTime. + self.wait_for_user_input( + prompt_msg="Type any letter and press ENTER after the sensor occupancy is detection ready state (occupancy attribute = 0)") + + # check sensor occupancy state is 0 for the next test step + occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + asserts.assert_equal(occupancy_dut, 0, "Occupancy attribute is still 1.") + + self.step(4) + # Trigger occupancy sensor to change Occupancy attribute value to 1 => TESTER ACTION on DUT + self.wait_for_user_input(prompt_msg="Type any letter and press ENTER after a sensor occupancy is triggered.") + + # And then check if Occupancy attribute has changed. + occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + asserts.assert_equal(occupancy_dut, 1, "Occupancy state is not changed to 1") + + self.step(5) + # check if Occupancy attribute is back to 0 after HoldTime attribute period + # Tester should not be triggering the sensor for this test step. + if attributes.HoldTime.attribute_id in attribute_list: + + # Start a timer based on HoldTime + time.sleep(hold_time+2) # add some extra 2 seconds to ensure hold time has passed. + + occupancy_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + asserts.assert_equal(occupancy_dut, 0, "Occupancy state is not 0 after HoldTime period") + + else: + logging.info("HoldTime attribute not supported. Skip this return to 0 timing test procedure.") + self.skip_step(5) + + +if __name__ == "__main__": + default_matter_test_main() diff --git a/src/python_testing/TC_OCC_3_2.py b/src/python_testing/TC_OCC_3_2.py new file mode 100644 index 00000000000000..3624f1044c40e8 --- /dev/null +++ b/src/python_testing/TC_OCC_3_2.py @@ -0,0 +1,248 @@ +# +# Copyright (c) 2024 Project CHIP (Matter) 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: ${TYPE_OF_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: --storage-path admin_storage.json --commissioning-method on-network --discriminator 1234 --passcode 20202021 --trace-to json:${TRACE_TEST_JSON}.json --trace-to perfetto:${TRACE_TEST_PERFETTO}.perfetto +# === END CI TEST ARGUMENTS === +# TODO: There are CI issues to be followed up for the test cases below that implements manually controlling sensor device for +# the occupancy state ON/OFF change. +# [TC-OCC-3.1] test procedure step 4 +# [TC-OCC-3.2] test precedure step 3a, 3c + +import logging +import queue +import time +from typing import Any + +import chip.clusters as Clusters +from chip import ChipDeviceCtrl +from chip.clusters.Attribute import TypedAttributePath +from matter_testing_support import (AttributeValue, ClusterAttributeChangeAccumulator, MatterBaseTest, TestStep, async_test_body, + default_matter_test_main) +from mobly import asserts + + +class TC_OCC_3_2(MatterBaseTest): + async def read_occ_attribute_expect_success(self, endpoint, attribute): + cluster = Clusters.Objects.OccupancySensing + return await self.read_single_attribute_check_success(endpoint=endpoint, cluster=cluster, attribute=attribute) + + def _await_sequence_of_reports(self, report_queue: queue.Queue, endpoint_id: int, attribute: TypedAttributePath, sequence: list[Any], timeout_sec: float): + start_time = time.time() + elapsed = 0.0 + time_remaining = timeout_sec + + sequence_idx = 0 + actual_values = [] + + while time_remaining > 0: + expected_value = sequence[sequence_idx] + logging.info(f"Expecting value {expected_value} for attribute {attribute} on endpoint {endpoint_id}") + try: + item: AttributeValue = report_queue.get(block=True, timeout=time_remaining) + + # Track arrival of all values for the given attribute. + if item.endpoint_id == endpoint_id and item.attribute == attribute: + actual_values.append(item.value) + + if item.value == expected_value: + logging.info(f"Got expected attribute change {sequence_idx+1}/{len(sequence)} for attribute {attribute}") + sequence_idx += 1 + else: + asserts.assert_equal(item.value, expected_value, + msg="Did not get expected attribute value in correct sequence.") + + # We are done waiting when we have accumulated all results. + if sequence_idx == len(sequence): + logging.info("Got all attribute changes, done waiting.") + return + except queue.Empty: + # No error, we update timeouts and keep going + pass + + elapsed = time.time() - start_time + time_remaining = timeout_sec - elapsed + + asserts.fail(f"Did not get full sequence {sequence} in {timeout_sec:.1f} seconds. Got {actual_values} before time-out.") + + def desc_TC_OCC_3_2(self) -> str: + return "[TC-OCC-3.2] Subscription Report Verification with server as DUT" + + def steps_TC_OCC_3_2(self) -> list[TestStep]: + steps = [ + TestStep(1, "Commission DUT to TH if not already done", is_commissioning=True), + TestStep(2, "TH establishes a wildcard subscription to all attributes on Occupancy Sensing Cluster on the endpoint under test. Subscription min interval = 0 and max interval = 30 seconds."), + TestStep("3a", "Do not trigger DUT for occupancy state change."), + TestStep("3b", "TH reads DUT Occupancy attribute and saves the initial value as initial"), + TestStep("3c", "Trigger DUT to change the occupancy state."), + TestStep("3d", "TH awaits a ReportDataMessage containing an attribute report for DUT Occupancy attribute."), + TestStep("4a", "Check if DUT supports HoldTime attribute, If not supported, then stop and skip the rest of test cases."), + TestStep("4b", "TH reads DUT HoldTime attribute and saves the initial value as initial"), + TestStep("4c", "TH writes a different value to DUT HoldTime attribute."), + TestStep("4d", "TH awaits a ReportDataMessage containing an attribute report for DUT HoldTime attribute."), + TestStep("5a", "Check if DUT supports DUT feature flag PIR or OTHER, If not supported, then stop and skip to 6a."), + TestStep("5b", "TH reads DUT PIROccupiedToUnoccupiedDelay attribute and saves the initial value as initial"), + TestStep("5c", "TH writes a different value to DUT PIROccupiedToUnoccupiedDelay attribute."), + TestStep("5d", "TH awaits a ReportDataMessage containing an attribute report for DUT PIROccupiedToUnoccupiedDelay attribute."), + TestStep("6a", "Check if DUT supports DUT feature flag US, If not supported, then stop and skip to 7a."), + TestStep("6b", "TH reads DUT UltrasonicOccupiedToUnoccupiedDelay attribute and saves the initial value as initial"), + TestStep("6c", "TH writes a different value to DUT UltrasonicOccupiedToUnoccupiedDelay attribute."), + TestStep("6d", "TH awaits a ReportDataMessage containing an attribute report for DUT UltrasonicOccupiedToUnoccupiedDelay attribute."), + TestStep("7a", "Check if DUT supports DUT feature flag PHY, If not supported, terminate this test case."), + TestStep("7b", "TH reads DUT PhysicalContactOccupiedToUnoccupiedDelay attribute and saves the initial value as initial"), + TestStep("7c", "TH writes a different value to DUT PhysicalContactOccupiedToUnoccupiedDelay attribute."), + TestStep("7d", "TH awaits a ReportDataMessage containing an attribute report for DUT PhysicalContactOccupiedToUnoccupiedDelay attribute.") + ] + return steps + + def pics_TC_OCC_3_2(self) -> list[str]: + pics = [ + "OCC.S", + ] + return pics + + @async_test_body + async def test_TC_OCC_3_2(self): + + endpoint = self.user_params.get("endpoint", 1) + endpoint_id = self.matter_test_config.endpoint + node_id = self.matter_test_config.dut_node_ids[0] + post_prompt_settle_delay_seconds = 10.0 + cluster = Clusters.Objects.OccupancySensing + attributes = Clusters.OccupancySensing.Attributes + occupancy_sensor_type_bitmap_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.OccupancySensorTypeBitmap) + + self.step(1) + attribute_list = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.AttributeList) + + self.step(2) + # min interval = 0, and max interval = 30 seconds + attrib_listener = ClusterAttributeChangeAccumulator(Clusters.Objects.OccupancySensing) + await attrib_listener.start(ChipDeviceCtrl, node_id, endpoint=endpoint_id) + + # TODO - Will add Namepiped to assimilate the manual sensor untrigger here + self.step("3a") + self.wait_for_user_input(prompt_msg="Type any letter and press ENTER after DUT goes back to unoccupied state.") + + self.step("3b") + if attributes.Occupancy.attribute_id in attribute_list: + initial_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.Occupancy) + asserts.assert_equal(initial_dut, 0, "Occupancy attribute is still detected state") + + # TODO - Will add Namepiped to assimilate the manual sensor trigger here + self.step("3c") + self.wait_for_user_input( + prompt_msg="Type any letter and press ENTER after the sensor occupancy is triggered and its occupancy state changed.") + + self.step("3d") + self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.Occupancy, sequence=[ + 0, 1], timeout_sec=post_prompt_settle_delay_seconds) + + self.step("4a") + if attributes.HoldTime.attribute_id not in attribute_list: + logging.info("No HoldTime attribute supports. Terminate this test case") + self.skip_all_remaining_steps("4b") + + self.step("4b") + initial_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.HoldTime) + + self.step("4c") + # write a different a HoldTime attibute + diff_val = 12 + await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.HoldTime(diff_val))]) + + self.step("4d") + self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.HoldTime, sequence=[ + initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) + + self.step("5a") + if (occupancy_sensor_type_bitmap_dut & Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kPir) == 0: + logging.info("No PIR timing attribute supports. Skip this test cases, 5b, 5c, 5d") + self.skip_step("5b") + self.skip_test("5c") + self.skip_step("5d") + else: + self.step("5b") + if attributes.PIROccupiedToUnoccupiedDelay.attribute_id in attribute_list: + initial_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.PIROccupiedToUnoccupiedDelay) + + else: + logging.info("No PIROccupiedToUnoccupiedDelay attribute supports. Terminate this test case") + + self.step("5c") + # write the new attribute value + diff_val = 11 + await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.PIROccupiedToUnoccupiedDelay(diff_val))]) + + self.step("5d") + self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.PIROccupiedToUnoccupiedDelay, sequence=[ + initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) + + self.step("6a") + if (occupancy_sensor_type_bitmap_dut & Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kUltrasonic) == 0: + logging.info("No Ultrasonic timing attribute supports. Skip this test cases, 6b, 6c, 6d") + self.skip_step("6b") + self.skip_test("6c") + self.skip_step("6d") + else: + self.step("6b") + if attributes.UltrasonicOccupiedToUnoccupiedDelay.attribute_id in attribute_list: + initial_dut = await self.read_occ_attribute_expect_success(endpoint=endpoint, attribute=attributes.UltrasonicOccupiedToUnoccupiedDelay) + + else: + logging.info("No UltrasonicOccupiedToUnoccupiedDelay attribute supports. Skip this test case") + + self.step("6c") + # write the new attribute value + diff_val = 14 + await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.UltrasonicOccupiedToUnoccupiedDelay(diff_val))]) + + self.step("6d") + self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.UltrasonicOccupiedToUnoccupiedDelay, sequence=[ + initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) + + self.step("7a") + if (occupancy_sensor_type_bitmap_dut & Clusters.OccupancySensing.Enums.OccupancySensorTypeEnum.kPhysicalContact) == 0: + logging.info("No Physical contact timing attribute supports. Skip this test case") + self.skip_all_remaining_steps("7b") + + self.step("7b") + if attributes.PhysicalContactOccupiedToUnoccupiedDelay.attribute_id in attribute_list: + initial_dut = await self.t_success(endpoint=endpoint, attribute=attributes.PhysicalContactOccupiedToUnoccupiedDelay) + + else: + logging.info("No PhysicalContactOccupiedToUnoccupiedDelay attribute supports. Skip this test case") + + self.step("7c") + # write the new attribute value + diff_val = 9 + await ChipDeviceCtrl.WriteAttribute(node_id, [(endpoint, attributes.PhysicalContactOccupiedToUnoccupiedDelay(diff_val))]) + + self.step("7d") + self._await_sequence_of_reports(report_queue=attrib_listener.attribute_queue, endpoint_id=endpoint_id, attribute=cluster.Attributes.PhysicalContactOccupiedToUnoccupiedDelay, sequence=[ + initial_dut, diff_val], timeout_sec=post_prompt_settle_delay_seconds) + + +if __name__ == "__main__": + default_matter_test_main()