[Local Agent] Sync go/nest-cl/288136 and go/nest-cl/288758
Change-Id: I533639aeb3b3475b58966716397c01efbc9e5555
diff --git a/local_agent/tests/unit_tests/test_command_handlers/test_dimmable_light.py b/local_agent/tests/unit_tests/test_command_handlers/test_dimmable_light.py
new file mode 100644
index 0000000..495ede0
--- /dev/null
+++ b/local_agent/tests/unit_tests/test_command_handlers/test_dimmable_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 DimmableLight endpoint command handler."""
+import unittest
+from unittest import mock
+
+from local_agent.translation_layer.command_handlers import dimmable_light
+
+
+class DimmableLightCommandHandlerTest(unittest.TestCase):
+ """Unit tests for DimmableLightCommandHandler."""
+
+ def setUp(self):
+ super().setUp()
+ self.mock_dut = mock.Mock()
+ self.handler = dimmable_light.DimmableLightCommandHandler(self.mock_dut)
+
+ def test_endpoint_initialization(self):
+ """Verifies if endpoint instance is initialized successfully."""
+ self.assertEqual(self.mock_dut.dimmable_light, self.handler.endpoint)
+
+
+
+if __name__ == '__main__':
+ unittest.main(failfast=True)
diff --git a/local_agent/tests/unit_tests/test_gdm_manager.py b/local_agent/tests/unit_tests/test_gdm_manager.py
index 784c30a..9255248 100644
--- a/local_agent/tests/unit_tests/test_gdm_manager.py
+++ b/local_agent/tests/unit_tests/test_gdm_manager.py
@@ -13,6 +13,7 @@
# limitations under the License.
"""Unit tests for gdm_manager."""
+from parameterized import parameterized
import unittest
from unittest import mock
@@ -35,7 +36,6 @@
'persistent': {'device_type': 'nrf52849', 'serial_number': '1234'},
},
}
-_FAKE_DEVICE_CAPABILITIES = ['flash_build', 'switchboard']
_FAKE_ERROR_MSG = 'error'
##############################################################################
@@ -47,37 +47,44 @@
super().setUp()
self.mgr = gdm_manager.GdmManager(mock.Mock())
- @mock.patch.object(gazoo_device.Manager, 'get_supported_device_capabilities')
+ @parameterized.expand([(True, ['pw_rpc_common']), (False, [])])
+ @mock.patch.object(gazoo_device.Manager, 'create_and_close_device')
@mock.patch.object(gazoo_device.Manager, 'is_device_connected')
@mock.patch.object(gazoo_device.Manager, 'get_devices')
@mock.patch.object(gazoo_device.Manager, 'detect')
- def test_01_detect_devices(
+ def test_detect_devices(
self,
+ has_capabilities_result,
+ expected_capability,
mock_detect,
mock_get_devices,
mock_is_connect,
- mock_get_capabilities):
+ mock_create_and_close_device):
"""Verifies detect_devices on success."""
mock_get_devices.return_value = _FAKE_DETECTION_RESULT
mock_is_connect.side_effect = [True, False]
- mock_get_capabilities.return_value = _FAKE_DEVICE_CAPABILITIES
+ fake_device = mock.Mock()
+ mock_create_and_close_device.return_value.iter.return_value = fake_device
+ mock_create_and_close_device.return_value.__enter__.return_value = fake_device
+ fake_device.matter_endpoints.get_supported_endpoints_and_clusters.return_value = {}
+ fake_device.has_capabilities.return_value = has_capabilities_result
+
device_statuses = self.mgr.detect_devices()
+
expected_statuses = [
{
'deviceId': 'efr32-3453',
'serialNumber': '000440173453',
'deviceType': 'efr32',
- 'capabilities': ['flash_build', 'switchboard'],
+ 'capabilities': expected_capability,
},
]
self.assertEqual(expected_statuses, device_statuses)
mock_detect.assert_called_once_with(
force_overwrite=True, log_directory='/tmp')
- self.assertEqual(2, mock_is_connect.call_count)
- self.assertEqual(1, mock_get_capabilities.call_count)
@mock.patch.object(gazoo_device.Manager, 'create_device')
- def test_02_create_devices_on_success(self, mock_create):
+ def test_create_devices_on_success(self, mock_create):
"""Verifies create_devices on success."""
identifiers = [_FAKE_DEVICE_ID, _FAKE_DEVICE_ID2]
self.mgr.create_devices(identifiers)
@@ -86,7 +93,7 @@
self.assertEqual(2, mock_create.call_count)
@mock.patch.object(gazoo_device.Manager, 'is_device_connected')
- def test_03_check_device_connected_on_success(
+ def test_check_device_connected_on_success(
self, mock_is_device_connected):
"""Verifies check_device_connected succeeds when device connected."""
mock_is_device_connected.return_value = True
@@ -94,7 +101,7 @@
self.assertEqual(1, mock_is_device_connected.call_count)
@mock.patch.object(gazoo_device.Manager, 'is_device_connected')
- def test_03_check_device_connected_raises_when_disconnected(
+ def test_check_device_connected_raises_when_disconnected(
self, mock_is_device_connected):
"""Verifies check_device_connected raises when device disconnected."""
mock_is_device_connected.return_value = False
@@ -103,7 +110,7 @@
self.assertEqual(1, mock_is_device_connected.call_count)
@mock.patch.object(gazoo_device.Manager, 'is_device_connected')
- def test_03_check_device_connected_raises_unexpected_error(
+ def test_check_device_connected_raises_unexpected_error(
self, mock_is_device_connected):
"""
Verifies check_device_connected raises when device operation has error.
@@ -116,7 +123,7 @@
@mock.patch.object(gazoo_device.Manager, 'get_open_device')
@mock.patch.object(gazoo_device.Manager, 'get_open_device_names')
- def test_04_get_device_instance_on_success(
+ def test_get_device_instance_on_success(
self, mock_get_open_device_names, mock_get_open_device):
"""Verifies get_device_instance on success."""
mock_get_open_device_names.return_value = [_FAKE_DEVICE_ID]
@@ -125,7 +132,7 @@
mock_get_open_device.assert_called_once_with(_FAKE_DEVICE_ID)
@mock.patch.object(gazoo_device.Manager, 'get_open_device_names')
- def test_04_get_device_instance_failure_because_device_not_open(
+ def test_get_device_instance_failure_because_device_not_open(
self, mock_get_open_device_names):
"""Verifies get_device_instance on failure."""
mock_get_open_device_names.return_value = []
@@ -135,7 +142,7 @@
@mock.patch.object(gazoo_device.Manager, 'get_open_device')
@mock.patch.object(gazoo_device.Manager, 'get_open_device_names')
- def test_05_get_device_type_on_success(
+ def test_get_device_type_on_success(
self, mock_get_names, mock_get_device):
"""Verifies _get_device_type on success."""
mock_get_names.return_value = {_FAKE_DEVICE_ID,}
@@ -146,7 +153,7 @@
self.assertEqual(1, mock_get_device.call_count)
@mock.patch.object(gazoo_device.Manager, 'get_open_device_names')
- def test_05_get_device_type_on_failure_not_open(self, mock_get_names):
+ def test_get_device_type_on_failure_not_open(self, mock_get_names):
"""Verifies _get_device_type on failure not open."""
mock_get_names.return_value = []
error_msg = f'{_FAKE_DEVICE_ID} is not open'
@@ -154,7 +161,7 @@
self.mgr.get_device_type(_FAKE_DEVICE_ID)
@mock.patch.object(gazoo_device.Manager, 'close_open_devices')
- def test_06_close_open_devices_on_success(self, mock_close_open_devices):
+ def test_close_open_devices_on_success(self, mock_close_open_devices):
"""Verifies close_open_devices on success."""
self.mgr.close_open_devices()
self.assertEqual(1, mock_close_open_devices.call_count)
diff --git a/local_agent/translation_layer/command_handlers/color_temperature_light.py b/local_agent/translation_layer/command_handlers/color_temperature_light.py
index 7c07c6e..a1e92c8 100644
--- a/local_agent/translation_layer/command_handlers/color_temperature_light.py
+++ b/local_agent/translation_layer/command_handlers/color_temperature_light.py
@@ -17,13 +17,9 @@
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'
diff --git a/local_agent/translation_layer/command_handlers/dimmable_light.py b/local_agent/translation_layer/command_handlers/dimmable_light.py
new file mode 100644
index 0000000..4e55a40
--- /dev/null
+++ b/local_agent/translation_layer/command_handlers/dimmable_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.
+
+"""Module for DimmableLight endpoint command handler."""
+from typing import Any, Dict
+
+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
+from local_agent.translation_layer.command_handlers.cluster_handlers import on_off
+
+ENDPOINT_CAPABILITY = 'dimmable_light'
+
+
+class DimmableLightCommandHandler(
+ base.BaseCommandHandler, level.LevelControlHandler, on_off.OnOffHandler):
+ """Command handler for DimmableLight 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.dimmable_light
diff --git a/local_agent/translation_layer/command_handlers/handler_registry.py b/local_agent/translation_layer/command_handlers/handler_registry.py
index b4ece37..f5dd7eb 100644
--- a/local_agent/translation_layer/command_handlers/handler_registry.py
+++ b/local_agent/translation_layer/command_handlers/handler_registry.py
@@ -21,16 +21,16 @@
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 dimmable_light
from local_agent.translation_layer.command_handlers import door_lock
+from local_agent.translation_layer.command_handlers import on_off_light
-# 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,
+ dimmable_light.ENDPOINT_CAPABILITY: dimmable_light.DimmableLightCommandHandler,
+ door_lock.ENDPOINT_CAPABILITY: door_lock.DoorLockCommandHandler,
on_off_light.ENDPOINT_CAPABILITY: on_off_light.OnOffLightCommandHandler,
- door_lock.ENDPOINT_CAPABILITY: door_lock.DoorLockCommandHandler
})
diff --git a/local_agent/translation_layer/command_handlers/on_off_light.py b/local_agent/translation_layer/command_handlers/on_off_light.py
index 02dbbac..859f325 100644
--- a/local_agent/translation_layer/command_handlers/on_off_light.py
+++ b/local_agent/translation_layer/command_handlers/on_off_light.py
@@ -17,14 +17,10 @@
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'
diff --git a/local_agent/translation_layer/gdm_manager.py b/local_agent/translation_layer/gdm_manager.py
index 74b3c96..cfa6dcb 100644
--- a/local_agent/translation_layer/gdm_manager.py
+++ b/local_agent/translation_layer/gdm_manager.py
@@ -21,6 +21,7 @@
from local_agent import errors as agent_errors
from local_agent import logger as logger_module
+from local_agent.translation_layer.command_handlers import common
logger = logger_module.get_logger()
@@ -40,6 +41,10 @@
self._first_detection = True
self._update_handlers_cls_map = update_handlers_cls_map_fn
+ # Caching device information to avoid race condition when doing device
+ # communication multiple times.
+ self._connected_devices = {} # device id -> device information dict
+
def detect_devices(self) -> List[Dict[str, Any]]:
"""Detects connected devices.
@@ -53,21 +58,43 @@
log_directory=_DEVICE_DETECTION_LOG_DIR)
connected_devices = self._mgr.get_devices('all')
for device_id, info in connected_devices.items():
+
+ # The device has been removed.
if not self._mgr.is_device_connected(device_id):
continue
- serial_number = info['persistent']['serial_number']
- device_type = info['persistent']['device_type']
- capabilities = (
- self._mgr.get_supported_device_capabilities(device_type))
- device_dict = {
- 'deviceId': device_id,
- 'serialNumber': serial_number,
- 'deviceType': device_type,
- 'capabilities': capabilities,
- }
- devices.append(device_dict)
- self._update_handlers_cls_map(device_type, capabilities)
+
+ # The first time this device is detected.
+ if device_id not in self._connected_devices:
+ serial_number = info['persistent']['serial_number']
+ device_type = info['persistent']['device_type']
+
+ # Retrieve the Matter endpoints and clusters in the first detection.
+ with self._mgr.create_and_close_device(device_id) as device:
+ matter_endpoints = device.matter_endpoints.get_supported_endpoints()
+ endpoint_clusters_mapping = device.matter_endpoints.get_supported_endpoints_and_clusters()
+ cluster_capabilities = list(set().union(*endpoint_clusters_mapping.values()))
+
+ # Additional non-Matter capabilities.
+ if device.has_capabilities([common.PWRPC_COMMON_CAPABILITY]):
+ matter_endpoints.append(common.PWRPC_COMMON_CAPABILITY)
+ cluster_capabilities.append(common.PWRPC_COMMON_CAPABILITY)
+
+ # Therefore, we only need to update the command handlers once.
+ self._update_handlers_cls_map(device_type, matter_endpoints)
+
+ # Store the device information in cache.
+ device_info = {
+ 'deviceId': device_id,
+ 'serialNumber': serial_number,
+ 'deviceType': device_type,
+ 'capabilities': cluster_capabilities,
+ }
+ self._connected_devices[device_id] = device_info
+
+ devices.append(self._connected_devices[device_id])
+
self._first_detection = False
+
return devices
def create_devices(