blob: 90e9675dc58faaba9c421ec8869b77fc3e5d1752 [file] [log] [blame]
#
# Copyright (c) 2026 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.
#
import logging
from mobly import asserts
import matter.clusters as Clusters
from matter.clusters.Types import NullValue
from matter.interaction_model import InteractionModelError, Status
from matter.testing.decorators import async_test_body
from matter.testing.matter_testing import MatterBaseTest, TestStep
from matter.testing.runner import default_matter_test_main
logger = logging.getLogger(__name__)
class TC_LAUNDRYDRYER(MatterBaseTest):
"""Tests for chef laundry dryer device."""
_LAUNDRYDRYER_ENDPOINT = 1
def desc_TC_LAUNDRYDRYER(self) -> str:
return "[TC_LAUNDRYDRYER] chef laundry dryer functionality test."
def steps_TC_LAUNDRYDRYER(self):
return [TestStep(1, "Commissioning already done.", is_commissioning=True),
TestStep(
2, "[TC_LAUNDRY_DRYER_CONTROLS] Verification of Initial State."),
TestStep(
3, "[TC_OPERATIONAL_STATE] Stop operational cycle if running."),
TestStep(4, "[TC_LAUNDRY_DRYER_CONTROLS] Nullable Support."),
TestStep(
5, "[TC_LAUNDRY_DRYER_CONTROLS] Constraint Error: Value Not in Supported List."),
TestStep(
6, "[TC_LAUNDRY_DRYER_CONTROLS] Constraint Error: Out-of-Bounds Enum."),
TestStep(
7, "[TC_OPERATIONAL_STATE] Operational State Start Stop."),
TestStep(
8, "[TC_LAUNDRY_WASHER_MODE] Dynamic List Update (Mode Dependency)."),
TestStep(9, "[TC_ON_OFF] Dead Front (Off State) Behavior."),
TestStep(10, "[TC_IDENTIFY] Test Identify."),
TestStep(11, "[TC_TEMPERATURE_CONTROL] Test Temperature Control.")]
async def _read_supported_dryness_levels(self):
return await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.LaundryDryerControls,
attribute=Clusters.Objects.LaundryDryerControls.Attributes.SupportedDrynessLevels)
async def _read_selected_dryness_level(self):
return await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.LaundryDryerControls,
attribute=Clusters.Objects.LaundryDryerControls.Attributes.SelectedDrynessLevel)
async def _write_selected_dryness_level(self, level, expect_success=True):
return await self.write_single_attribute(
attribute_value=Clusters.Objects.LaundryDryerControls.Attributes.SelectedDrynessLevel(
value=level),
endpoint_id=self._LAUNDRYDRYER_ENDPOINT,
expect_success=expect_success)
async def _read_operational_state(self):
return await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.OperationalState,
attribute=Clusters.Objects.OperationalState.Attributes.OperationalState)
async def _read_on_off(self):
return await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.OnOff,
attribute=Clusters.Objects.OnOff.Attributes.OnOff)
async def _read_identify_time(self):
return await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.Identify,
attribute=Clusters.Objects.Identify.Attributes.IdentifyTime)
async def _read_laundry_washer_current_mode(self):
return await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.LaundryWasherMode,
attribute=Clusters.Objects.LaundryWasherMode.Attributes.CurrentMode)
async def _read_supported_temperature_levels(self):
return await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.TemperatureControl,
attribute=Clusters.Objects.TemperatureControl.Attributes.SupportedTemperatureLevels)
async def _read_selected_temperature_level(self):
return await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.TemperatureControl,
attribute=Clusters.Objects.TemperatureControl.Attributes.SelectedTemperatureLevel)
async def _send_set_temperature_level_command(self, level):
return await self.send_single_cmd(
cmd=Clusters.Objects.TemperatureControl.Commands.SetTemperature(
targetTemperatureLevel=level),
endpoint=self._LAUNDRYDRYER_ENDPOINT)
async def _send_on_off_command(self, on: bool):
command = Clusters.Objects.OnOff.Commands.On(
) if on else Clusters.Objects.OnOff.Commands.Off()
return await self.send_single_cmd(
cmd=command,
endpoint=self._LAUNDRYDRYER_ENDPOINT)
@async_test_body
async def test_TC_LAUNDRYDRYER(self):
"""Run all steps."""
self.step(1)
# Commissioning already done.
# Step 2: Verification of Initial State
self.step(2)
supported_levels = await self._read_supported_dryness_levels()
asserts.assert_greater(len(supported_levels), 0,
"SupportedDrynessLevels should not be empty")
selected_level = await self._read_selected_dryness_level()
if selected_level is not NullValue:
asserts.assert_in(selected_level, supported_levels,
"SelectedDrynessLevel should be in SupportedDrynessLevels")
# Step 3: Stop operational cycle if running.
self.step(3)
# Ensure we are in a state that allows changes (Stopped)
# OperationalStateEnum: kStopped = 0x00
op_state = await self._read_operational_state()
if op_state != Clusters.Objects.OperationalState.Enums.OperationalStateEnum.kStopped:
await self.send_single_cmd(
cmd=Clusters.Objects.OperationalState.Commands.Stop(),
endpoint=self._LAUNDRYDRYER_ENDPOINT)
target_level = supported_levels[0]
await self._write_selected_dryness_level(target_level)
asserts.assert_equal(await self._read_selected_dryness_level(), target_level, "SelectedDrynessLevel should match target")
# Step 4: Nullable Support
self.step(4)
await self._write_selected_dryness_level(NullValue)
asserts.assert_equal(await self._read_selected_dryness_level(), NullValue, "SelectedDrynessLevel should be null")
# Step 5: Constraint Error: Value Not in Supported List
self.step(5)
# Find a level that is not supported. DrynessLevelEnum has 0, 1, 2, 3.
all_levels = [Clusters.Objects.LaundryDryerControls.Enums.DrynessLevelEnum.kLow,
Clusters.Objects.LaundryDryerControls.Enums.DrynessLevelEnum.kNormal,
Clusters.Objects.LaundryDryerControls.Enums.DrynessLevelEnum.kExtra,
Clusters.Objects.LaundryDryerControls.Enums.DrynessLevelEnum.kMax]
unsupported_level = next(
(level for level in all_levels if level not in supported_levels), None)
if unsupported_level is not None:
status = await self._write_selected_dryness_level(unsupported_level, expect_success=False)
asserts.assert_equal(status, Status.ConstraintError,
"Expected ConstraintError for unsupported level")
# Step 6: Constraint Error: Out-of-Bounds Enum
self.step(6)
invalid_enum_value = 5 # Values 0-3 are valid, 4 is kUnknownEnumValue
status = await self._write_selected_dryness_level(invalid_enum_value, expect_success=False)
asserts.assert_equal(status, Status.ConstraintError,
"Expected ConstraintError for invalid enum value")
# Step 7: Operational State Start Stop
self.step(7)
# Change state to Running
await self.send_single_cmd(
cmd=Clusters.Objects.OperationalState.Commands.Start(),
endpoint=self._LAUNDRYDRYER_ENDPOINT)
op_state = await self._read_operational_state()
asserts.assert_equal(
op_state, Clusters.Objects.OperationalState.Enums.OperationalStateEnum.kRunning)
# Reset to Stopped for next tests
await self.send_single_cmd(
cmd=Clusters.Objects.OperationalState.Commands.Stop(),
endpoint=self._LAUNDRYDRYER_ENDPOINT)
# Step 8: Dynamic List Update (Mode Dependency)
self.step(8)
# Change LaundryWasherMode
supported_modes = await self.read_single_attribute_check_success(
endpoint=self._LAUNDRYDRYER_ENDPOINT,
cluster=Clusters.Objects.LaundryWasherMode,
attribute=Clusters.Objects.LaundryWasherMode.Attributes.SupportedModes)
if len(supported_modes) > 1:
# Try to find Delicates and Heavy labels
delicates_mode = next(
(m.mode for m in supported_modes if "Delicate" in m.label), None)
heavy_mode = next(
(m.mode for m in supported_modes if "Heavy" in m.label), None)
if delicates_mode is not None:
await self.send_single_cmd(
cmd=Clusters.Objects.LaundryWasherMode.Commands.ChangeToMode(
newMode=delicates_mode),
endpoint=self._LAUNDRYDRYER_ENDPOINT)
asserts.assert_equal(await self._read_laundry_washer_current_mode(), delicates_mode, "Mode should be Delicate")
# Check if it changes (optional but good)
updated_dryness = await self._read_supported_dryness_levels()
logger.info(
f"Delicate mode supported dryness levels: {updated_dryness}")
if heavy_mode is not None:
await self.send_single_cmd(
cmd=Clusters.Objects.LaundryWasherMode.Commands.ChangeToMode(
newMode=heavy_mode),
endpoint=self._LAUNDRYDRYER_ENDPOINT)
asserts.assert_equal(await self._read_laundry_washer_current_mode(), heavy_mode, "Mode should be Heavy")
updated_dryness = await self._read_supported_dryness_levels()
logger.info(
f"Heavy mode supported dryness levels: {updated_dryness}")
# If labels not found, just cycle through available modes
if delicates_mode is None or heavy_mode is None:
for mode_option in supported_modes:
await self.send_single_cmd(
cmd=Clusters.Objects.LaundryWasherMode.Commands.ChangeToMode(
newMode=mode_option.mode),
endpoint=self._LAUNDRYDRYER_ENDPOINT)
asserts.assert_equal(await self._read_laundry_washer_current_mode(), mode_option.mode, f"Mode should be {mode_option.label}")
updated_supported_dryness = await self._read_supported_dryness_levels()
logger.info(
f"Mode {mode_option.label} supports dryness levels: {updated_supported_dryness}")
# Step 9: Dead Front (Off State) Behavior
self.step(9)
# Ensure OnOff is True first
if not await self._read_on_off():
await self._send_on_off_command(True)
# Set OnOff to False using Off command
await self._send_on_off_command(False)
selected_level = await self._read_selected_dryness_level()
# Recommended to be null
if selected_level is not NullValue:
logger.warning(
"SelectedDrynessLevel is not null in Dead Front state (recommended best effort).")
# Step 10: Test Identify
self.step(10)
asserts.assert_equal(await self._read_identify_time(), 0)
await self.send_single_cmd(
cmd=Clusters.Objects.Identify.Commands.Identify(identifyTime=5),
endpoint=self._LAUNDRYDRYER_ENDPOINT
)
identify_time = await self._read_identify_time()
asserts.assert_greater(identify_time, 0)
asserts.assert_less_equal(identify_time, 5)
# Step 11: Test Temperature Control
self.step(11)
# Step 11.1: Discover Capabilities
supported_temp_levels = await self._read_supported_temperature_levels()
asserts.assert_greater(len(supported_temp_levels), 0,
"SupportedTemperatureLevels should not be empty")
logger.info(f"Supported temperature levels: {supported_temp_levels}")
# Step 11.2: Read Initial State
initial_temp_level = await self._read_selected_temperature_level()
logger.info(f"Initial temperature level: {initial_temp_level}")
# Step 11.3: Execute Valid Change
# Try to change to a different level if possible
target_temp_level = (initial_temp_level +
1) % len(supported_temp_levels)
await self._send_set_temperature_level_command(target_temp_level)
current_temp_level = await self._read_selected_temperature_level()
asserts.assert_equal(current_temp_level, target_temp_level,
"SelectedTemperatureLevel should match target")
# Step 11.4: Execute Invalid Change
invalid_temp_level = len(supported_temp_levels) + 1
try:
await self.send_single_cmd(
cmd=Clusters.Objects.TemperatureControl.Commands.SetTemperature(
targetTemperatureLevel=invalid_temp_level),
endpoint=self._LAUNDRYDRYER_ENDPOINT
)
asserts.fail(
"Expected ConstraintError for invalid temperature level but it succeeded")
except InteractionModelError as e:
asserts.assert_equal(e.status, Status.ConstraintError,
"Expected ConstraintError for invalid temperature level")
# Verify it remained unchanged
current_temp_level = await self._read_selected_temperature_level()
asserts.assert_equal(current_temp_level, target_temp_level,
"SelectedTemperatureLevel should remain unchanged after invalid write")
if __name__ == "__main__":
default_matter_test_main()