[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)