[Local Agent] Sync go/nest-cl/275332 and go/nest-cl/278096: Refactor
command handlers to align with GDM Matter capabilities.
Change-Id: I4df4705ef59328483f021ea8651948bb326ff538
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/__init__.py b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/__init__.py
new file mode 100644
index 0000000..6d6d126
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2022 Google LLC
+#
+# 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.
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_door_lock.py b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_door_lock.py
new file mode 100644
index 0000000..8d25d43
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_door_lock.py
@@ -0,0 +1,90 @@
+# Copyright 2022 Google LLC
+#
+# 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.
+
+"""Unit tests for DoorLock cluster handler."""
+from parameterized import parameterized
+from typing import Any
+import unittest
+from unittest import mock
+
+from gazoo_device import errors
+from gazoo_device.capabilities.matter_clusters import door_lock_pw_rpc
+
+from local_agent.translation_layer.command_handlers import base
+from local_agent.translation_layer.command_handlers.cluster_handlers import door_lock
+
+
+_FAKE_ERROR_MSG = 'fake-error-msg'
+LOCKED = door_lock_pw_rpc.LockState.LOCKED.value
+UNLOCKED = door_lock_pw_rpc.LockState.UNLOCKED.value
+
+
+class DummyHandler(base.BaseCommandHandler, door_lock.DoorLockHandler):
+ """Dummy handler for testing DoorLock cluster handler."""
+
+ def __init__(self, dut: Any) -> None:
+ super().__init__(dut)
+ self.endpoint = self.dut.door_lock
+
+
+class DoorLockHandlerTest(unittest.TestCase):
+ """Unit tests for DoorLock cluster handler."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_dut = mock.Mock()
+ self.handler = DummyHandler(self.mock_dut)
+
+ @parameterized.expand([(LOCKED, True), (UNLOCKED, False)])
+ def test_get_is_locked_on_success(self, locked_state, expected_ret):
+ """Verifies get_is_locked on success."""
+ self.handler.endpoint.door_lock.lock_state = locked_state
+
+ self.assertEqual(expected_ret, self.handler._get_is_locked({}))
+
+ def test_get_is_locked_on_failure(self):
+ """Verifies get_is_locked on failure."""
+ mock_state = mock.PropertyMock(
+ side_effect=errors.DeviceError(_FAKE_ERROR_MSG))
+ type(self.handler.endpoint.door_lock).lock_state = mock_state
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._get_is_locked({})
+
+ def test_set_lock_on_success(self):
+ """Verifies set_lock on success."""
+ self.handler._set_lock({})
+ self.handler.endpoint.door_lock.lock_door.assert_called_once()
+
+ def test_set_lock_on_failure(self):
+ """Verifies set_lock on failure."""
+ self.handler.endpoint.door_lock.lock_door.side_effect = (
+ errors.DeviceError(_FAKE_ERROR_MSG))
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._set_lock({})
+
+ def test_set_unlock_on_success(self):
+ """Verifies set_unlock on success."""
+ self.handler._set_unlock({})
+ self.handler.endpoint.door_lock.unlock_door.assert_called_once()
+
+ def test_set_unlock_on_failure(self):
+ """Verifies set_unlock on failure."""
+ self.handler.endpoint.door_lock.unlock_door.side_effect = (
+ errors.DeviceError(_FAKE_ERROR_MSG))
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._set_unlock({})
+
+
+if __name__ == '__main__':
+ unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_level.py b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_level.py
new file mode 100644
index 0000000..0880701
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_level.py
@@ -0,0 +1,77 @@
+# Copyright 2022 Google LLC
+#
+# 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.
+
+"""Unit tests for Level cluster handler."""
+from typing import Any
+import unittest
+from unittest import mock
+
+from gazoo_device import errors
+
+from local_agent.translation_layer.command_handlers import base
+from local_agent.translation_layer.command_handlers.cluster_handlers import level
+
+
+_FAKE_ERROR_MSG = 'fake-error-msg'
+_FAKE_LEVEL = 108
+
+
+
+class DummyHandler(base.BaseCommandHandler, level.LevelControlHandler):
+ """Dummy handler for testing Level Control cluster handler."""
+
+ def __init__(self, dut: Any) -> None:
+ super().__init__(dut)
+ self.endpoint = self.dut.on_off_light
+
+
+class LevelControlHandlerTest(unittest.TestCase):
+ """Unit tests for Level Control cluster handler."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_dut = mock.Mock()
+ self.handler = DummyHandler(self.mock_dut)
+
+ def test_get_level_on_success(self):
+ """Verifies the _get_level method on success."""
+ self.handler.endpoint.level.current_level = _FAKE_LEVEL
+ self.assertEqual(_FAKE_LEVEL, self.handler._get_level({}))
+
+ def test_get_level_on_failure(self):
+ """Verifies the _get_level method on failure."""
+ mock_current_level= mock.PropertyMock(
+ side_effect=errors.DeviceError(_FAKE_ERROR_MSG))
+ type(self.handler.endpoint.level).current_level = mock_current_level
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._get_level({})
+
+ @mock.patch.object(base.BaseCommandHandler, 'validate_key_in_params')
+ def test_set_level_on_success(self, mock_validate_key_in_params):
+ """Verifies the _set_level method on success."""
+ self.handler._set_level({'level': _FAKE_LEVEL})
+ self.handler.endpoint.level.move_to_level.assert_called_once_with(
+ level=_FAKE_LEVEL)
+
+ @mock.patch.object(base.BaseCommandHandler, 'validate_key_in_params')
+ def test_set_level_on_failure(self, mock_validate_key_in_params):
+ """Verifies the _set_level method on failure."""
+ self.handler.endpoint.level.move_to_level.side_effect = (
+ errors.DeviceError(_FAKE_ERROR_MSG))
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._set_level({'level': _FAKE_LEVEL})
+
+
+if __name__ == '__main__':
+ unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_on_off.py b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_on_off.py
new file mode 100644
index 0000000..61d098d
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_on_off.py
@@ -0,0 +1,85 @@
+# Copyright 2022 Google LLC
+#
+# 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.
+
+"""Unit tests for OnOff cluster handler."""
+from typing import Any
+import unittest
+from unittest import mock
+
+from gazoo_device import errors
+
+from local_agent.translation_layer.command_handlers import base
+from local_agent.translation_layer.command_handlers.cluster_handlers import on_off
+
+
+_FAKE_ERROR_MSG = 'fake-error-msg'
+
+
+
+class DummyHandler(base.BaseCommandHandler, on_off.OnOffHandler):
+ """Dummy handler for testing OnOff cluster handler."""
+
+ def __init__(self, dut: Any) -> None:
+ super().__init__(dut)
+ self.endpoint = self.dut.on_off_light
+
+
+class OnOffHandlerTest(unittest.TestCase):
+ """Unit tests for OnOff cluster handler."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_dut = mock.Mock()
+ self.handler = DummyHandler(self.mock_dut)
+
+ def test_get_on_off_on_success(self):
+ """Verifies the _get_on_off method on success."""
+ self.handler.endpoint.on_off.onoff = True
+ self.assertEqual(on_off.LIGHT_ON, self.handler._get_on_off({}))
+
+ def test_get_on_off_on_failure(self):
+ """Verifies the _get_on_off method on failure."""
+ mock_onoff= mock.PropertyMock(
+ side_effect=errors.DeviceError(_FAKE_ERROR_MSG))
+ type(self.handler.endpoint.on_off).onoff = mock_onoff
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._get_on_off({})
+
+ def test_set_on_on_success(self):
+ """Verifies the _set_on method on success."""
+ self.handler._set_on({})
+ self.handler.endpoint.on_off.on.assert_called_once()
+
+ def test_set_on_on_failure(self):
+ """Verifies the _set_on method on failure."""
+ self.handler.endpoint.on_off.on.side_effect = (
+ errors.DeviceError(_FAKE_ERROR_MSG))
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._set_on({})
+
+ def test_set_off_on_success(self):
+ """Verifies the _set_off method on success."""
+ self.handler._set_off({})
+ self.handler.endpoint.on_off.off.assert_called_once()
+
+ def test_set_off_on_failure(self):
+ """Verifies the _set_off method on failure."""
+ self.handler.endpoint.on_off.off.side_effect = (
+ errors.DeviceError(_FAKE_ERROR_MSG))
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._set_off({})
+
+
+if __name__ == '__main__':
+ unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_door_lock.py b/local_agent/tests/unit_tests/test_command_handlers/test_door_lock.py
new file mode 100644
index 0000000..900397c
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_door_lock.py
@@ -0,0 +1,36 @@
+# Copyright 2022 Google LLC
+#
+# 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.
+
+"""Unit tests for DoorLock endpoint command handlers."""
+import unittest
+from unittest import mock
+
+from local_agent.translation_layer.command_handlers import door_lock
+
+
+class DoorLockCommandHandlerTest(unittest.TestCase):
+ """Unit tests for DoorLockCommandHandler."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_dut = mock.Mock()
+ self.handler = door_lock.DoorLockCommandHandler(self.mock_dut)
+
+ def test_endpoint_initialization(self):
+ """Verifies if endpoint instance is initialized successfully."""
+ self.assertEqual(self.mock_dut.door_lock, self.handler.endpoint)
+
+
+if __name__ == '__main__':
+ unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_light.py b/local_agent/tests/unit_tests/test_command_handlers/test_light.py
deleted file mode 100644
index ee02fe8..0000000
--- a/local_agent/tests/unit_tests/test_command_handlers/test_light.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# Copyright 2021 Google LLC
-#
-# 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.
-
-"""Unit tests for light command handlers."""
-from parameterized import parameterized
-import unittest
-from unittest import mock
-
-from gazoo_device import errors
-
-from local_agent.translation_layer.command_handlers import light
-
-
-_FAKE_BRIGHTNESS = 100
-_FAKE_HUE = 10
-_FAKE_SATURATION = 10
-_FAKE_ERROR_MSG = 'fake-error-msg'
-
-
-class LightCommandHandlerTest(unittest.TestCase):
- """Unit tests for LightCommandHandler."""
-
- def setUp(self):
- super().setUp()
- self.dut = mock.Mock()
- self.handler = light.LightCommandHandler(self.dut)
-
- @parameterized.expand([(True, 'on'), (False, 'off')])
- def test_01_get_on_off_on_success(self, light_state, expected_state):
- """Verifies get_on_off on success."""
- self.dut.pw_rpc_light.state = light_state
-
- state = self.handler._get_on_off({})
-
- self.assertEqual(expected_state, state)
-
- def test_01_get_on_off_on_failure(self):
- """Verifies get_on_off on failure."""
- mock_state = mock.PropertyMock(
- side_effect=errors.DeviceError(_FAKE_ERROR_MSG))
- type(self.dut.pw_rpc_light).state = mock_state
- with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
- self.handler._get_on_off({})
-
- def test_02_set_on_on_success(self):
- """Verifies set_on on success."""
- self.handler._set_on({})
- self.dut.pw_rpc_light.on.assert_called_once()
-
- def test_02_set_on_on_failure(self):
- """Verifies set_on on failure."""
- self.dut.pw_rpc_light.on.side_effect = errors.DeviceError('')
- with self.assertRaises(errors.DeviceError):
- self.handler._set_on({})
-
- def test_03_set_off_on_success(self):
- """Verifies set_off on success."""
- self.handler._set_off({})
- self.dut.pw_rpc_light.off.assert_called_once()
-
- def test_03_set_off_on_failure(self):
- """Verifies set_off on failure."""
- self.dut.pw_rpc_light.off.side_effect = errors.DeviceError('')
- with self.assertRaises(errors.DeviceError):
- self.handler._set_off({})
-
- def test_04_get_brightness_on_success(self):
- """Verifies get_brightness on success."""
- self.dut.pw_rpc_light.brightness = _FAKE_BRIGHTNESS
-
- brightness = self.handler._get_brightness({})
-
- self.assertEqual(_FAKE_BRIGHTNESS, brightness)
-
- def test_04_get_brightness_on_failure(self):
- """Verifies get_brightness on failure."""
- mock_state = mock.PropertyMock(
- side_effect=errors.DeviceError(_FAKE_ERROR_MSG))
- type(self.dut.pw_rpc_light).brightness = mock_state
- with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
- self.handler._get_brightness({})
-
- @mock.patch.object(light.LightCommandHandler, 'validate_key_in_params')
- def test_05_set_brightness_on_success(self, mock_validate_key_in_params):
- """Verifies set_brightness on success."""
- self.handler._set_brightness({'level': _FAKE_BRIGHTNESS})
-
- mock_validate_key_in_params.assert_called_once()
- self.dut.pw_rpc_light.on.assert_called_once_with(level=_FAKE_BRIGHTNESS)
-
- @mock.patch.object(light.LightCommandHandler, 'validate_key_in_params')
- def test_05_set_brightness_on_failure(self, mock_validate_key_in_params):
- """Verifies set_brightness on failure."""
- self.dut.pw_rpc_light.on.side_effect = errors.DeviceError('')
- with self.assertRaises(errors.DeviceError):
- self.handler._set_brightness({'level': _FAKE_BRIGHTNESS})
-
- def test_06_get_color_on_success(self):
- """Verifies get_color on success."""
- self.dut.pw_rpc_light.color.hue = _FAKE_HUE
- self.dut.pw_rpc_light.color.saturation = _FAKE_SATURATION
- expected_response = {'hue': _FAKE_HUE, 'saturation': _FAKE_SATURATION}
-
- color = self.handler._get_color({})
-
- self.assertEqual(expected_response, color)
-
- def test_06_get_color_on_failure(self):
- """Verifies get_color on failure."""
- mock_state = mock.PropertyMock(
- side_effect=errors.DeviceError(_FAKE_ERROR_MSG))
- type(self.dut.pw_rpc_light.color).hue = mock_state
- with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
- self.handler._get_color({})
-
- @mock.patch.object(light.LightCommandHandler, 'validate_key_in_params')
- def test_07_set_color_on_success(self, mock_validate_key_in_params):
- """Verifies set_color on success."""
- params = {'hue': _FAKE_HUE, 'saturation': _FAKE_SATURATION}
-
- self.handler._set_color(params)
-
- self.assertEqual(2, mock_validate_key_in_params.call_count)
- self.dut.pw_rpc_light.on.assert_called_once_with(
- hue=_FAKE_HUE, saturation=_FAKE_SATURATION)
-
- @mock.patch.object(light.LightCommandHandler, 'validate_key_in_params')
- def test_07_set_color_on_failure(self, mock_validate_key_in_params):
- """Verifies set_color on failure."""
- params = {'hue': _FAKE_HUE, 'saturation': _FAKE_SATURATION}
- self.dut.pw_rpc_light.on.side_effect = errors.DeviceError('')
- with self.assertRaises(errors.DeviceError):
- self.handler._set_color(params)
-
-
-if __name__ == '__main__':
- unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_lock.py b/local_agent/tests/unit_tests/test_command_handlers/test_lock.py
deleted file mode 100644
index 393e7a3..0000000
--- a/local_agent/tests/unit_tests/test_command_handlers/test_lock.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# Copyright 2021 Google LLC
-#
-# 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.
-
-"""Unit tests for lock command handlers."""
-from parameterized import parameterized
-import unittest
-from unittest import mock
-
-from gazoo_device import errors
-
-from local_agent.translation_layer.command_handlers import lock
-
-
-_FAKE_ERROR_MSG = 'fake-error-msg'
-
-
-class LockCommandHandlerTest(unittest.TestCase):
- """Unit tests for LockCommandHandler."""
-
- def setUp(self):
- super().setUp()
- self.dut = mock.Mock()
- self.handler = lock.LockCommandHandler(self.dut)
-
- @parameterized.expand([(True,), (False,)])
- def test_01_get_is_locked_on_success(self, locked_state):
- """Verifies get_is_locked on success."""
- self.dut.pw_rpc_lock.state = locked_state
-
- is_locked = self.handler._get_is_locked({})
-
- self.assertEqual(locked_state, is_locked)
-
- def test_01_get_is_locked_on_failure(self):
- """Verifies get_is_locked on failure."""
- mock_state = mock.PropertyMock(
- side_effect=errors.DeviceError(_FAKE_ERROR_MSG))
- type(self.dut.pw_rpc_lock).state = mock_state
- with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
- self.handler._get_is_locked({})
-
- def test_02_set_lock_on_success(self):
- """Verifies set_lock on success."""
- self.handler._set_lock({})
- self.dut.pw_rpc_lock.lock.assert_called_once()
-
- def test_02_set_lock_on_failure(self):
- """Verifies set_lock on failure."""
- self.dut.pw_rpc_lock.lock.side_effect = errors.DeviceError('')
- with self.assertRaises(errors.DeviceError):
- self.handler._set_lock({})
-
- def test_03_set_unlock_on_success(self):
- """Verifies set_unlock on success."""
- self.handler._set_unlock({})
- self.dut.pw_rpc_lock.unlock.assert_called_once()
-
- def test_03_set_unlock_on_failure(self):
- """Verifies set_unlock on failure."""
- self.dut.pw_rpc_lock.unlock.side_effect = errors.DeviceError('')
- with self.assertRaises(errors.DeviceError):
- self.handler._set_unlock({})
-
-
-if __name__ == '__main__':
- unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_on_off_light.py b/local_agent/tests/unit_tests/test_command_handlers/test_on_off_light.py
new file mode 100644
index 0000000..c6947fc
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_on_off_light.py
@@ -0,0 +1,37 @@
+# Copyright 2022 Google LLC
+#
+# 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.
+
+"""Unit tests for OnOffLight endpoint command handler."""
+import unittest
+from unittest import mock
+
+from local_agent.translation_layer.command_handlers import on_off_light
+
+
+class OnOffLightCommandHandlerTest(unittest.TestCase):
+ """Unit tests for OnOffLightCommandHandler."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_dut = mock.Mock()
+ self.handler = on_off_light.OnOffLightCommandHandler(self.mock_dut)
+
+ def test_endpoint_initialization(self):
+ """Verifies if endpoint instance is initialized successfully."""
+ self.assertEqual(self.mock_dut.on_off_light, self.handler.endpoint)
+
+
+
+if __name__ == '__main__':
+ unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_translation_layer.py b/local_agent/tests/unit_tests/test_translation_layer.py
index 3113780..09e6b53 100644
--- a/local_agent/tests/unit_tests/test_translation_layer.py
+++ b/local_agent/tests/unit_tests/test_translation_layer.py
@@ -188,8 +188,7 @@
self, mock_validate):
"""Verifies _update_handlers_cls_map creation on success."""
fake_handler = mock.Mock(SUPPORTED_METHODS={_SET_ON,})
- fake_capabilities_map = {
- _FAKE_CAPABILITY: [fake_handler]}
+ fake_capabilities_map = {_FAKE_CAPABILITY: fake_handler}
translation_layer.GDM_CAPABILITIES_TO_COMMAND_HANDLERS = (
fake_capabilities_map)
self.translator.update_handlers_cls_map(
diff --git a/local_agent/translation_layer/command_handlers/cluster_handlers/__init__.py b/local_agent/translation_layer/command_handlers/cluster_handlers/__init__.py
new file mode 100644
index 0000000..d46dbae
--- /dev/null
+++ b/local_agent/translation_layer/command_handlers/cluster_handlers/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2021 Google LLC
+#
+# 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.
diff --git a/local_agent/translation_layer/command_handlers/lock.py b/local_agent/translation_layer/command_handlers/cluster_handlers/door_lock.py
similarity index 75%
rename from local_agent/translation_layer/command_handlers/lock.py
rename to local_agent/translation_layer/command_handlers/cluster_handlers/door_lock.py
index af1c707..f1d5033 100644
--- a/local_agent/translation_layer/command_handlers/lock.py
+++ b/local_agent/translation_layer/command_handlers/cluster_handlers/door_lock.py
@@ -1,4 +1,4 @@
-# Copyright 2021 Google LLC
+# Copyright 2022 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -12,25 +12,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-"""Module for lock command handler."""
+"""Module for DoorLock cluster command handling."""
from typing import Any, Dict
from gazoo_device import errors
+from gazoo_device.capabilities.matter_clusters import door_lock_pw_rpc
from local_agent import logger as logger_module
-from local_agent.translation_layer.command_handlers import base
logger = logger_module.get_logger()
-PWRPC_LOCK_CAPABILITY = 'pw_rpc_lock'
+LOCKED = door_lock_pw_rpc.LockState.LOCKED.value
-class LockCommandHandler(base.BaseCommandHandler):
- """Lock device command handler
+class DoorLockHandler:
+ """Mixin for Matter DoorLock cluster handler.
- Smart Home LockUnlock Trait Schema:
- https://developers.google.com/assistant/smarthome/traits/lockunlock
+ The mixin assumes self.dut and self.endpoint are set.
"""
_GET_IS_LOCKED = 'getIsLocked'
@@ -39,17 +38,17 @@
SUPPORTED_METHODS = {_GET_IS_LOCKED, _SET_LOCK, _SET_UNLOCK}
def _get_is_locked(self, params: Dict[str, Any]) -> bool:
- """Returns if the device is locked or not.
+ """Queries the LockState attribute of the device.
Returns:
True if the device is locked, false otherwise.
Raises:
- DeviceError: getting locked state fails.
+ DeviceError: getting LockState attribute fails.
"""
del params # not used
try:
- return self.dut.pw_rpc_lock.state
+ return self.endpoint.door_lock.lock_state == LOCKED
except errors.DeviceError as exc:
logger.exception(f'Getting locked state of {self.dut.name} failed.')
raise exc
@@ -62,7 +61,7 @@
"""
del params # not used
try:
- self.dut.pw_rpc_lock.lock()
+ self.endpoint.door_lock.lock_door()
except errors.DeviceError as exc:
logger.exception(f'Locking {self.dut.name} failed.')
raise exc
@@ -75,7 +74,7 @@
"""
del params # not used
try:
- self.dut.pw_rpc_lock.unlock()
+ self.endpoint.door_lock.unlock_door()
except errors.DeviceError as exc:
logger.exception(f'Unlocking {self.dut.name} failed.')
raise exc
diff --git a/local_agent/translation_layer/command_handlers/cluster_handlers/level.py b/local_agent/translation_layer/command_handlers/cluster_handlers/level.py
new file mode 100644
index 0000000..fdcc28c
--- /dev/null
+++ b/local_agent/translation_layer/command_handlers/cluster_handlers/level.py
@@ -0,0 +1,66 @@
+# Copyright 2021 Google LLC
+#
+# 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.
+
+"""Module for Level Control cluster command handling."""
+from typing import Any, Dict
+
+from gazoo_device import errors
+
+from local_agent import logger as logger_module
+
+
+logger = logger_module.get_logger()
+
+
+class LevelControlHandler:
+ """Mixin for Matter Level Control cluster handler.
+
+ The mixin assumes self.dut and self.endpoint are set.
+ """
+
+ _GET_LEVEL = 'getLevel'
+ _SET_LEVEL = 'setLevel'
+
+ SUPPORTED_METHODS = {_GET_LEVEL, _SET_LEVEL}
+
+ def _get_level(self, params: Dict[str, Any]) -> str:
+ """Queries the CurrentLevel attribute on the device.
+
+ Returns:
+ The current level.
+
+ Raises:
+ DeviceError: getting CurrentLevel attribute fails.
+ """
+ del params # not used
+ try:
+ return self.endpoint.level.current_level
+ except errors.DeviceError as exc:
+ logger.exception(
+ f'Getting CurrentLevel attribute of {self.dut.name} failed.')
+ raise exc
+
+ def _set_level(self, params: Dict[str, Any]) -> None:
+ """Set the CurrentLevel attribute on the device.
+
+ Raises:
+ DeviceError: setting CurrentLevel attribute fails.
+ """
+ self.validate_key_in_params(
+ params=params, param_key='level', expected_type=int)
+ try:
+ self.endpoint.level.move_to_level(level=params['level'])
+ except errors.DeviceError as exc:
+ logger.exception(f'Turning {self.dut.name} on failed.')
+ raise exc
diff --git a/local_agent/translation_layer/command_handlers/cluster_handlers/on_off.py b/local_agent/translation_layer/command_handlers/cluster_handlers/on_off.py
new file mode 100644
index 0000000..f2cbc35
--- /dev/null
+++ b/local_agent/translation_layer/command_handlers/cluster_handlers/on_off.py
@@ -0,0 +1,82 @@
+# Copyright 2021 Google LLC
+#
+# 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.
+
+"""Module for OnOff cluster command handling."""
+from typing import Any, Dict
+
+from gazoo_device import errors
+
+from local_agent import logger as logger_module
+
+
+logger = logger_module.get_logger()
+
+# OnOff Response States:
+LIGHT_ON = 'on'
+LIGHT_OFF = 'off'
+
+
+class OnOffHandler:
+ """Mixin for Matter OnOff cluster handler.
+
+ The mixin assumes self.dut and self.endpoint are set.
+ """
+
+ _GET_ONOFF = 'getOnOff'
+ _SET_ON = 'setOn'
+ _SET_OFF = 'setOff'
+
+ SUPPORTED_METHODS = {_GET_ONOFF, _SET_ON, _SET_OFF}
+
+ def _get_on_off(self, params: Dict[str, Any]) -> str:
+ """Queries the OnOff attribute of the device.
+
+ Returns:
+ 'on' if the device is in on state, 'off' if it's in off state.
+
+ Raises:
+ DeviceError: getting light state fails.
+ """
+ del params # not used
+ try:
+ return LIGHT_ON if self.endpoint.on_off.onoff else LIGHT_OFF
+ except errors.DeviceError as exc:
+ logger.exception(f'Getting OnOff attribute of {self.dut.name} failed.')
+ raise exc
+
+ def _set_on(self, params: Dict[str, Any]) -> None:
+ """Turns on the device.
+
+ Raises:
+ DeviceError: turning light on fails.
+ """
+ del params # not used
+ try:
+ self.endpoint.on_off.on()
+ except errors.DeviceError as exc:
+ logger.exception(f'Turning {self.dut.name} on failed.')
+ raise exc
+
+ def _set_off(self, params: Dict[str, Any]) -> None:
+ """Turns off of the device.
+
+ Raises:
+ DeviceError: turning light off fails.
+ """
+ del params # not used
+ try:
+ self.endpoint.on_off.off()
+ except errors.DeviceError as exc:
+ logger.exception(f'Turning {self.dut.name} off failed.')
+ raise exc
diff --git a/local_agent/translation_layer/command_handlers/door_lock.py b/local_agent/translation_layer/command_handlers/door_lock.py
new file mode 100644
index 0000000..7372d97
--- /dev/null
+++ b/local_agent/translation_layer/command_handlers/door_lock.py
@@ -0,0 +1,33 @@
+# Copyright 2022 Google LLC
+#
+# 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.
+
+"""Module for DoorLock endpoint command handler."""
+from typing import Any
+
+from local_agent.translation_layer.command_handlers import base
+from local_agent.translation_layer.command_handlers.cluster_handlers import door_lock
+
+
+ENDPOINT_CAPABILITY = 'door_lock'
+
+
+class DoorLockCommandHandler(
+ base.BaseCommandHandler, door_lock.DoorLockHandler):
+ """Command handler for DoorLock endpoint."""
+
+ SUPPORTED_METHODS = door_lock.DoorLockHandler.SUPPORTED_METHODS
+
+ def __init__(self, dut: Any) -> None:
+ super().__init__(dut)
+ self.endpoint = self.dut.door_lock
diff --git a/local_agent/translation_layer/command_handlers/handler_registry.py b/local_agent/translation_layer/command_handlers/handler_registry.py
index ef0cad0..e68da93 100644
--- a/local_agent/translation_layer/command_handlers/handler_registry.py
+++ b/local_agent/translation_layer/command_handlers/handler_registry.py
@@ -20,13 +20,15 @@
import immutabledict
from local_agent.translation_layer.command_handlers import common
-from local_agent.translation_layer.command_handlers import light
-from local_agent.translation_layer.command_handlers import lock
+from local_agent.translation_layer.command_handlers import on_off_light
+from local_agent.translation_layer.command_handlers import door_lock
-# GDM capability -> set of acceptable command handlers
+# GDM capability -> Matter endpoint command handlers
+# TODO(b/216406955): Make command handler mapping dynamic generated
GDM_CAPABILITIES_TO_COMMAND_HANDLERS = immutabledict.immutabledict({
- common.PWRPC_COMMON_CAPABILITY: {common.CommonCommandHandler,},
- light.PWRPC_LIGHT_CAPABILITY: {light.LightCommandHandler,},
- lock.PWRPC_LOCK_CAPABILITY: {lock.LockCommandHandler,}
+ common.PWRPC_COMMON_CAPABILITY: common.CommonCommandHandler,
+ on_off_light.ENDPOINT_CAPABILITY: on_off_light.OnOffLightCommandHandler,
+ door_lock.ENDPOINT_CAPABILITY: door_lock.DoorLockCommandHandler
+ # TODO(b/216407280) Add color temperature command handler.
})
diff --git a/local_agent/translation_layer/command_handlers/light.py b/local_agent/translation_layer/command_handlers/light.py
deleted file mode 100644
index bc4a1a6..0000000
--- a/local_agent/translation_layer/command_handlers/light.py
+++ /dev/null
@@ -1,169 +0,0 @@
-# Copyright 2021 Google LLC
-#
-# 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.
-
-"""Module for light command handler."""
-from typing import Any, Dict
-
-from gazoo_device import errors
-
-from local_agent import logger as logger_module
-from local_agent.translation_layer.command_handlers import base
-
-
-logger = logger_module.get_logger()
-
-PWRPC_LIGHT_CAPABILITY = 'pw_rpc_light'
-
-# OnOff Response States:
-LIGHT_ON = 'on'
-LIGHT_OFF = 'off'
-
-
-class LightCommandHandler(base.BaseCommandHandler):
- """Lighting device command handler
-
- Smart Home OnOff Trait Schema:
- https://developers.google.com/assistant/smarthome/traits/onoff
- """
-
- _GET_STATE = 'getOnOff'
- _SET_STATE_ON = 'setOn'
- _SET_STATE_OFF = 'setOff'
- _GET_BRIGHTNESS = 'getBrightness'
- _SET_BRIGHTNESS = 'setBrightness'
- _GET_COLOR = 'getColor'
- _SET_COLOR = 'setColor'
-
- SUPPORTED_METHODS = {
- _GET_STATE,
- _SET_STATE_ON,
- _SET_STATE_OFF,
- _GET_BRIGHTNESS,
- _SET_BRIGHTNESS,
- _GET_COLOR,
- _SET_COLOR}
-
- def _get_on_off(self, params: Dict[str, Any]) -> str:
- """Queries the light state of the device.
-
- Returns:
- The light state.
-
- Raises:
- DeviceError: getting light state fails.
- """
- del params # not used
- try:
- return LIGHT_ON if self.dut.pw_rpc_light.state else LIGHT_OFF
- except errors.DeviceError as exc:
- logger.exception(f'Getting light state of {self.dut.name} failed.')
- raise exc
-
- def _set_on(self, params: Dict[str, Any]) -> None:
- """Turns on the light of the device.
-
- Raises:
- DeviceError: turning light on fails.
- """
- del params # not used
- try:
- self.dut.pw_rpc_light.on()
- except errors.DeviceError as exc:
- logger.exception(f'Turning {self.dut.name} on failed.')
- raise exc
-
- def _set_off(self, params: Dict[str, Any]) -> None:
- """Turns off the light of the device.
-
- Raises:
- DeviceError: turning light off fails.
- """
- del params # not used
- try:
- self.dut.pw_rpc_light.off()
- except errors.DeviceError as exc:
- logger.exception(f'Turning {self.dut.name} off failed.')
- raise exc
-
- def _get_brightness(self, params: Dict[str, Any]) -> int:
- """Queries the current brightness level of the device.
-
- Returns:
- The current brightness level.
-
- Raises:
- DeviceError: getting light brightness fails.
- """
- del params # not used
- try:
- return self.dut.pw_rpc_light.brightness
- except errors.DeviceError as exc:
- logger.exception(
- f'Getting light brightness of {self.dut.name} failed.')
- raise exc
-
- def _set_brightness(self, params: Dict[str, Any]) -> None:
- """Sets the current brightness level of the device.
-
- Raises:
- DeviceError: setting brightness level fails.
- """
- self.validate_key_in_params(
- params=params, param_key='level', expected_type=int)
-
- try:
- self.dut.pw_rpc_light.on(level=params['level'])
- except errors.DeviceError as exc:
- logger.exception(
- f'Setting light brightness of {self.dut.name} failed.')
- raise exc
-
- def _get_color(self, params: Dict[str, Any]) -> Dict[str, int]:
- """Gets the current lighting color of the device.
-
- Returns:
- The current hue and saturation values in dict.
-
- Raises:
- DeviceError: getting color fails.
- """
- del params
- try:
- hue = self.dut.pw_rpc_light.color.hue
- saturation = self.dut.pw_rpc_light.color.saturation
- except errors.DeviceError as exc:
- logger.exception(
- f'Getting light color of {self.dut.name} failed.')
- raise exc
- return {'hue': hue, 'saturation': saturation}
-
- def _set_color(self, params: Dict[str, Any]) -> None:
- """Sets the lighting color to specific hue and saturation.
-
- Raises:
- DeviceError: setting color fails.
- """
- self.validate_key_in_params(
- params=params, param_key='hue', expected_type=int)
- self.validate_key_in_params(
- params=params, param_key='saturation', expected_type=int)
-
- try:
- hue = params['hue']
- saturation = params['saturation']
- self.dut.pw_rpc_light.on(hue=hue, saturation=saturation)
- except errors.DeviceError as exc:
- logger.exception(
- f'Setting light color of {self.dut.name} failed.')
- raise exc
diff --git a/local_agent/translation_layer/command_handlers/on_off_light.py b/local_agent/translation_layer/command_handlers/on_off_light.py
new file mode 100644
index 0000000..d33c76c
--- /dev/null
+++ b/local_agent/translation_layer/command_handlers/on_off_light.py
@@ -0,0 +1,41 @@
+# Copyright 2021 Google LLC
+#
+# 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.
+
+"""Module for OnOffLight endpoint command handler."""
+from typing import Any, Dict
+
+from gazoo_device import errors
+
+from local_agent import logger as logger_module
+from local_agent.translation_layer.command_handlers import base
+from local_agent.translation_layer.command_handlers.cluster_handlers import level
+from local_agent.translation_layer.command_handlers.cluster_handlers import on_off
+
+
+logger = logger_module.get_logger()
+
+ENDPOINT_CAPABILITY = 'on_off_light'
+
+
+class OnOffLightCommandHandler(
+ base.BaseCommandHandler, level.LevelControlHandler, on_off.OnOffHandler):
+ """Command handler for OnOffLight endpoint."""
+
+ SUPPORTED_METHODS = (
+ level.LevelControlHandler.SUPPORTED_METHODS |
+ on_off.OnOffHandler.SUPPORTED_METHODS)
+
+ def __init__(self, dut: Any) -> None:
+ super().__init__(dut)
+ self.endpoint = self.dut.on_off_light
diff --git a/local_agent/translation_layer/gdm_manager.py b/local_agent/translation_layer/gdm_manager.py
index c047ecc..18fdbfa 100644
--- a/local_agent/translation_layer/gdm_manager.py
+++ b/local_agent/translation_layer/gdm_manager.py
@@ -21,7 +21,6 @@
from local_agent import errors as agent_errors
from local_agent import logger as logger_module
-from local_agent.translation_layer.command_handlers import light
logger = logger_module.get_logger()
diff --git a/local_agent/translation_layer/translation_layer.py b/local_agent/translation_layer/translation_layer.py
index 04576fe..0bfc78c 100644
--- a/local_agent/translation_layer/translation_layer.py
+++ b/local_agent/translation_layer/translation_layer.py
@@ -163,8 +163,8 @@
matched_handlers = set()
for capability in capabilities:
- for handler in GDM_CAPABILITIES_TO_COMMAND_HANDLERS.get(
- capability, []):
+ handler = GDM_CAPABILITIES_TO_COMMAND_HANDLERS.get(capability)
+ if handler is not None:
matched_handlers.add(handler)
matched_handlers = list(matched_handlers)