[Local Agent] Synced go/nest-cl/280677, go/nest-cl/280072 and fixed b/220206029
Change-Id: Icfec6483a14098f3931bb7d3386250c8729dd1a5
diff --git a/README.md b/README.md
index e91eda6..9f1a936 100644
--- a/README.md
+++ b/README.md
@@ -69,9 +69,10 @@
> For development, we might want to use `pip install -e .`.
> (See documentation of the "-e" option [here](https://pip.pypa.io/en/stable/cli/pip_install/#cmdoption-e))
-5. Copy the config file [example_config.ini](https://testsuite-smarthome-matter.googlesource.com/local-agent/+/refs/heads/master/example_config.ini) to your host at ```~/.config/google/local_agent_config.ini```.
-
-6. Fill the names of your Matter controller extension packages (The extension you built in the above ```Matter extension packages``` section) in ```~/.config/google/local_agent_config.ini```. See [example_config.ini](https://testsuite-smarthome-matter.googlesource.com/local-agent/+/refs/heads/master/example_config.ini#20) for references.
+5. (Optional) Only needed if you want to register your own Matter extension packages:
+ - Create an empty config file named ```local_agent_config.ini``` under ```~/.config/google/```.
+ - Copy the content in [example_config.ini](https://testsuite-smarthome-matter.googlesource.com/local-agent/+/refs/heads/master/example_config.ini) to the ```local_agent_config.ini```.
+ - Fill the names of your Matter controller extension packages (The extension you built in the above ```Matter extension packages``` section) in ```local_agent_config.ini```. See [example_config.ini](https://testsuite-smarthome-matter.googlesource.com/local-agent/+/refs/heads/master/example_config.ini#20) for references.
## Usage
@@ -178,4 +179,28 @@
20211207 14:41:20.456 I local_agent.py:249 Stopped reporting info because stop event is set.
```
+
+## Troubleshooting
+
+### I can't install the Local Agent on my host.
+
+Make sure you're using Ubuntu LTS on your host and python version is ```>= 3.7```.
+
+### My Matter device is not detected by Local Agent.
+
+Local Agent uses Pigweed RPC debug interface for communicating with Matter devices, make sure
+your Matter device implements the Pigweed RPC endpoint and your Matter device class is already
+registered in GDM (step 5 in the ```Setup``` section). You can also try detecting the devices
+via GDM directly by following the [GDM instructions](https://github.com/google/gazoo-device#detecting-devices).
+
+### What if my Matter device is accidentally shutdown during the test execution?
+
+If the device gets shutdown or rebooted during test execution, GDM may crash and you'll need to close
+the Local Agent and restart it again after the test execution. Local agent does not need to be re-linked..
+
+### What if my host machine is accidentally shutdown during the test execution?
+
+Local agent will be closed forcely and you'll need to restart it. Local agent does not need to be re-linked.
+
+
Please contact your **Google Developer Support** or **smarthome-testsuite-contrib@google.com** if you encounter any unexpected problems.
\ No newline at end of file
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_color.py b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_color.py
new file mode 100644
index 0000000..7b44894
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_cluster_handlers/test_color.py
@@ -0,0 +1,82 @@
+# 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 Color Control 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 color
+
+_FAKE_ERROR_MSG = 'fake-error-msg'
+_FAKE_HUE = 10
+_FAKE_SATURATION = 11
+
+
+class DummyHandler(base.BaseCommandHandler, color.ColorControlHandler):
+ """Dummy handler for testing Color control cluster handler."""
+
+ def __init__(self, dut: Any) -> None:
+ super().__init__(dut)
+ self.endpoint = self.dut.color_temperature_light
+
+
+class ColorControlHandlerTest(unittest.TestCase):
+ """Unit tests for Color Control cluster handler."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_dut = mock.Mock()
+ self.handler = DummyHandler(self.mock_dut)
+
+ def test_get_color_on_success(self):
+ """Verifies the _get_color method on success."""
+ self.handler.endpoint.color.current_hue = _FAKE_HUE
+ self.handler.endpoint.color.current_saturation = _FAKE_SATURATION
+ expected_result = {'hue': _FAKE_HUE, 'saturation': _FAKE_SATURATION}
+
+ self.assertEqual(expected_result, self.handler._get_color({}))
+
+ def test_get_color_on_failure(self):
+ """Verifies the _get_color method on failure."""
+ mock_current_hue= mock.PropertyMock(
+ side_effect=errors.DeviceError(_FAKE_ERROR_MSG))
+ type(self.handler.endpoint.color).current_hue = mock_current_hue
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._get_color({})
+
+ def test_set_color_on_success(self):
+ """Verifies the _set_color method on success."""
+ self.handler._set_color(
+ {'hue': _FAKE_HUE, 'saturation': _FAKE_SATURATION})
+
+ self.handler.endpoint.color.move_to_hue.assert_called_once_with(
+ hue=_FAKE_HUE)
+ self.handler.endpoint.color.move_to_saturation.assert_called_once_with(
+ saturation=_FAKE_SATURATION)
+
+ def test_set_color_on_failure(self):
+ """Verifies the _set_color method on failure."""
+ self.handler.endpoint.color.move_to_hue.side_effect = (
+ errors.DeviceError(_FAKE_ERROR_MSG))
+ with self.assertRaisesRegex(errors.DeviceError, _FAKE_ERROR_MSG):
+ self.handler._set_color(
+ {'hue': _FAKE_HUE, 'saturation': _FAKE_SATURATION})
+
+
+if __name__ == '__main__':
+ unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_color_temperature_light.py b/local_agent/tests/unit_tests/test_command_handlers/test_color_temperature_light.py
new file mode 100644
index 0000000..bd060f8
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_color_temperature_light.py
@@ -0,0 +1,39 @@
+# 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 Color Temperature Light endpoint command handler."""
+import unittest
+from unittest import mock
+
+from local_agent.translation_layer.command_handlers import color_temperature_light
+
+
+class ColorTemperatureLightCommandHandlerTest(unittest.TestCase):
+ """Unit tests for ColorTemperatureLightCommandHandler."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_dut = mock.Mock()
+ self.handler = color_temperature_light.ColorTemperatureLightCommandHandler(
+ self.mock_dut)
+
+ def test_endpoint_initialization(self):
+ """Verifies if endpoint instance is initialized successfully."""
+ self.assertEqual(
+ self.mock_dut.color_temperature_light, self.handler.endpoint)
+
+
+
+if __name__ == '__main__':
+ unittest.main(failfast=True)
diff --git a/local_agent/translation_layer/command_handlers/cluster_handlers/color.py b/local_agent/translation_layer/command_handlers/cluster_handlers/color.py
new file mode 100644
index 0000000..c329489
--- /dev/null
+++ b/local_agent/translation_layer/command_handlers/cluster_handlers/color.py
@@ -0,0 +1,72 @@
+# 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 Color 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 ColorControlHandler:
+ """Mixin for Matter Color Control cluster handler.
+
+ The mixin assumes self.dut and self.endpoint are set.
+ """
+
+ _GET_COLOR = 'getColor'
+ _SET_COLOR = 'setColor'
+
+ SUPPORTED_METHODS = {_GET_COLOR, _SET_COLOR}
+
+ def _get_color(self, params: Dict[str, Any]) -> Dict[str, int]:
+ """Queries the color attributes of the device.
+
+ Color attributes include CurrentHue and CurrentSaturation.
+
+ Returns:
+ A dict containing hue and saturation value.
+
+ Raises:
+ DeviceError: getting color attribute fails.
+ """
+ del params # not used
+ try:
+ color_configurations = {}
+ color_configurations['hue'] = self.endpoint.color.current_hue
+ color_configurations['saturation'] = (
+ self.endpoint.color.current_saturation)
+ return color_configurations
+ except errors.DeviceError as exc:
+ logger.exception(f'Getting color of {self.dut.name} failed.')
+ raise exc
+
+ def _set_color(self, params: Dict[str, Any]) -> None:
+ """Sets color of the device.
+
+ Raises:
+ DeviceError: turning light on 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:
+ self.endpoint.color.move_to_hue(hue=params['hue'])
+ self.endpoint.color.move_to_saturation(
+ saturation=params['saturation'])
+ except errors.DeviceError as exc:
+ logger.exception(f'Setting color of {self.dut.name} on failed.')
+ raise exc
diff --git a/local_agent/translation_layer/command_handlers/color_temperature_light.py b/local_agent/translation_layer/command_handlers/color_temperature_light.py
new file mode 100644
index 0000000..7c07c6e
--- /dev/null
+++ b/local_agent/translation_layer/command_handlers/color_temperature_light.py
@@ -0,0 +1,39 @@
+# 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 Color Temperature Light 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 color
+
+
+logger = logger_module.get_logger()
+
+ENDPOINT_CAPABILITY = 'color_temperature_light'
+
+
+# TODO(b/209362086): Add other clusters to this handler.
+class ColorTemperatureLightCommandHandler(
+ base.BaseCommandHandler, color.ColorControlHandler):
+ """Command handler for OnOffLight endpoint."""
+
+ SUPPORTED_METHODS = (color.ColorControlHandler.SUPPORTED_METHODS)
+
+ def __init__(self, dut: Any) -> None:
+ super().__init__(dut)
+ self.endpoint = self.dut.color_temperature_light
diff --git a/local_agent/translation_layer/command_handlers/handler_registry.py b/local_agent/translation_layer/command_handlers/handler_registry.py
index e68da93..fd9b4f8 100644
--- a/local_agent/translation_layer/command_handlers/handler_registry.py
+++ b/local_agent/translation_layer/command_handlers/handler_registry.py
@@ -19,6 +19,7 @@
"""
import immutabledict
+from local_agent.translation_layer.command_handlers import color_temperature_light
from local_agent.translation_layer.command_handlers import common
from local_agent.translation_layer.command_handlers import on_off_light
from local_agent.translation_layer.command_handlers import door_lock
@@ -27,8 +28,9 @@
# GDM capability -> Matter endpoint command handlers
# TODO(b/216406955): Make command handler mapping dynamic generated
GDM_CAPABILITIES_TO_COMMAND_HANDLERS = immutabledict.immutabledict({
+ color_temperature_light.ENDPOINT_CAPABILITY:
+ color_temperature_light.ColorTemperatureLightCommandHandler,
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.
})