1#!/usr/bin/env python3 2# coding=utf-8 3 4# 5# Copyright (c) 2022 Huawei Device Co., Ltd. 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18import re 19import threading 20 21from xdevice import ManagerType 22from xdevice import Plugin 23from xdevice import get_plugin 24from xdevice import IDeviceManager 25from xdevice import IFilter 26from xdevice import platform_logger 27from xdevice import ParamError 28from xdevice import ConfigConst 29from xdevice import HdcCommandRejectedException 30from xdevice import ShellCommandUnresponsiveException 31from xdevice import DeviceConnectorType 32from xdevice import DeviceEvent 33from xdevice import TestDeviceState 34from xdevice import DeviceState 35from xdevice import handle_allocation_event 36from xdevice import DeviceAllocationState 37from xdevice import DeviceStateMonitor 38from xdevice import convert_serial 39from xdevice import check_mode_in_sys 40from xdevice import DeviceNode 41from xdevice import DeviceSelector 42from xdevice import Variables 43 44from ohos.environment.dmlib import DeviceConnector 45from ohos.environment.dmlib import HDC_NAME 46from ohos.environment.dmlib import HDC_STD_NAME 47from ohos.config.config_manager import OHOSUserConfigManager 48 49__all__ = ["ManagerDevice"] 50 51LOG = platform_logger("ManagerDevice") 52 53 54@Plugin(type=Plugin.MANAGER, id=ManagerType.device) 55class ManagerDevice(IDeviceManager, IFilter): 56 """ 57 Class representing device manager 58 managing the set of available devices for testing 59 """ 60 61 instance = None 62 63 def __new__(cls): 64 """ 65 Singleton instance 66 """ 67 if cls.instance is None: 68 cls.instance = object.__new__(cls) 69 return cls.instance 70 71 def __init__(self): 72 self.devices_list = [] 73 self.global_device_filter = [] 74 self.lock_con = threading.Condition() 75 self.list_con = threading.Condition() 76 self.device_connectors = [] 77 self.usb_type = "usb-hdc" 78 self.managed_device_listener = None 79 self.support_labels = ["phone", "watch", "car", "tv", "tablet", "ivi", "2in1"] 80 self.support_types = ["device"] 81 self.wait_times = 0 82 self.__device_alias = {} 83 84 def init_environment(self, environment: str = "", user_config_file: str = "") -> bool: 85 return self._start_device_monitor(environment, user_config_file) 86 87 def env_stop(self): 88 self._stop_device_monitor() 89 90 def _start_device_monitor(self, environment: str = "", user_config_file: str = "") -> bool: 91 self.managed_device_listener = ManagedDeviceListener(self) 92 ohos_manager = OHOSUserConfigManager( 93 config_file=user_config_file, env=environment) 94 devices = ohos_manager.get_devices("environment/device") 95 if devices: 96 self.__set_device_alias(devices) 97 try: 98 for device in devices: 99 device_connector = DeviceConnector( 100 device.get("ip"), device.get("port"), device.get("usb_type")) 101 self.device_connectors.append(device_connector) 102 self.global_device_filter.extend( 103 ohos_manager.get_sn_list(device.get("sn"))) 104 device_connector.add_device_change_listener( 105 self.managed_device_listener) 106 device_connector.start() 107 except (ParamError, FileNotFoundError) as error: 108 self.env_stop() 109 for device in devices: 110 LOG.debug("Start %s error: %s" % ( 111 device.get("usb_type"), error)) 112 device_connector = DeviceConnector( 113 device.get("ip"), device.get("port"), 114 DeviceConnectorType.hdc) 115 self.device_connectors.append(device_connector) 116 device_connector.add_device_change_listener(self.managed_device_listener) 117 device_connector.start() 118 return True 119 else: 120 LOG.error("OHOS Manager device is not supported, please check config user_config.xml", error_no="00108") 121 return False 122 123 def _stop_device_monitor(self): 124 for device_connector in self.device_connectors: 125 device_connector.monitor_lock.acquire(1) 126 device_connector.remove_device_change_listener(self.managed_device_listener) 127 device_connector.terminate() 128 device_connector.monitor_lock.release() 129 130 def apply_device(self, device_option, timeout=3): 131 cnt = 0 132 max_reply_apply_time = 3 133 while cnt <= max_reply_apply_time: 134 LOG.debug("Apply device: apply lock con lock") 135 self.lock_con.acquire() 136 try: 137 device = self.allocate_device_option(device_option) 138 if device or cnt == max_reply_apply_time: 139 return device 140 LOG.debug("Wait for available device founded") 141 self.lock_con.wait(cnt * 2 + 1) 142 cnt += 1 143 finally: 144 LOG.debug("Apply device: release lock con lock") 145 self.lock_con.release() 146 147 def allocate_device_option(self, device_option): 148 """ 149 Request a device for testing that meets certain criteria. 150 """ 151 152 LOG.debug("Allocate device option: apply list con lock") 153 if not self.list_con.acquire(timeout=5): 154 LOG.debug("Allocate device option: list con wait timeout") 155 return None 156 try: 157 allocated_device = None 158 LOG.debug("Require device label is: %s" % device_option.label) 159 for device in self.devices_list: 160 if device_option.matches(device): 161 self.handle_device_event(device, 162 DeviceEvent.ALLOCATE_REQUEST) 163 LOG.debug("Allocate device sn: %s, type: %s" % ( 164 device.__get_serial__(), device.__class__)) 165 return device 166 return allocated_device 167 168 finally: 169 LOG.debug("Allocate device option: release list con lock") 170 self.list_con.release() 171 172 def release_device(self, device): 173 LOG.debug("Release device: apply list con lock") 174 self.list_con.acquire() 175 try: 176 if device.test_device_state == TestDeviceState.ONLINE: 177 self.handle_device_event(device, DeviceEvent.FREE_AVAILABLE) 178 else: 179 self.handle_device_event(device, DeviceEvent.FREE_UNAVAILABLE) 180 LOG.debug("Free device sn: %s, type: %s" % ( 181 device.__get_serial__(), device.__class__.__name__)) 182 finally: 183 LOG.debug("Release_device: release list con lock") 184 self.list_con.release() 185 186 def lock_device(self, device): 187 LOG.debug("Apply device: apply list con lock") 188 self.list_con.acquire() 189 try: 190 if device.test_device_state == TestDeviceState.ONLINE: 191 self.handle_device_event(device, DeviceEvent.ALLOCATE_REQUEST) 192 193 LOG.debug("Lock device sn: %s, type: %s" % ( 194 device.__get_serial__(), device.__class__.__name__)) 195 196 finally: 197 LOG.debug("Lock_device: release list con lock") 198 self.list_con.release() 199 200 def reset_device(self, device): 201 if device and hasattr(device, "reset"): 202 device.reset() 203 204 def find_device(self, idevice): 205 LOG.debug("Find device: apply list con lock") 206 self.list_con.acquire() 207 try: 208 for device in self.devices_list: 209 if device == idevice: 210 return device 211 return None 212 finally: 213 LOG.debug("Find device: release list con lock") 214 self.list_con.release() 215 216 def append_device_by_sort(self, device_instance): 217 LOG.debug("Append device: apply list con lock") 218 self.list_con.acquire() 219 try: 220 if (not self.global_device_filter or 221 not self.devices_list or 222 device_instance.device_sn not in self.global_device_filter): 223 self.devices_list.append(device_instance) 224 else: 225 device_dict = dict(zip( 226 self.global_device_filter, 227 list(range(1, len(self.global_device_filter) + 1)))) 228 for index in range(len(self.devices_list)): 229 if self.devices_list[index].device_sn not in \ 230 self.global_device_filter: 231 self.devices_list.insert(index, device_instance) 232 break 233 if device_dict[device_instance.device_sn] < \ 234 device_dict[self.devices_list[index].device_sn]: 235 self.devices_list.insert(index, device_instance) 236 break 237 else: 238 self.devices_list.append(device_instance) 239 finally: 240 LOG.debug("Append device: release list con lock") 241 self.list_con.release() 242 243 def find_or_create(self, idevice): 244 try: 245 device = self.find_device(idevice) 246 if device is None: 247 device = get_plugin( 248 plugin_type=Plugin.DEVICE, 249 plugin_id=idevice.device_os_type)[0] 250 device_sn = idevice.device_sn 251 device_instance = device.__class__() 252 device_instance.__set_serial__(device_sn) 253 device_instance.device_id = self.__device_alias.get(device_sn, "") 254 device_instance.host = idevice.host 255 device_instance.port = idevice.port 256 device_instance.usb_type = self.usb_type 257 LOG.debug("Create device(%s) host is %s, " 258 "port is %s, device sn is %s, usb type is %s" % 259 (device_instance, device_instance.host, 260 device_instance.port, device_instance.device_sn, 261 device_instance.usb_type)) 262 device_instance.device_state = idevice.device_state 263 device_instance.test_device_state = \ 264 TestDeviceState.get_test_device_state( 265 device_instance.device_state) 266 device_instance.device_state_monitor = \ 267 DeviceStateMonitor(device_instance) 268 if idevice.device_state == DeviceState.ONLINE or \ 269 idevice.device_state == DeviceState.CONNECTED: 270 device_instance.get_device_type() 271 self.append_device_by_sort(device_instance) 272 device = device_instance 273 else: 274 LOG.debug("Find device(%s), host is %s, " 275 "port is %s, device sn is %s, usb type is %s" % 276 (device, device.host, device.port, device.device_sn, 277 device.usb_type)) 278 return device 279 except (HdcCommandRejectedException, ShellCommandUnresponsiveException) as hcr_error: 280 LOG.debug("%s occurs error. Reason:%s" % 281 (idevice.device_sn, hcr_error)) 282 return None 283 284 def remove(self, idevice): 285 LOG.debug("Remove: apply list con lock") 286 self.list_con.acquire() 287 try: 288 self.devices_list.remove(idevice) 289 finally: 290 LOG.debug("Remove: release list con lock") 291 self.list_con.release() 292 293 def handle_device_event(self, device, event): 294 state_changed = None 295 old_state = device.device_allocation_state 296 new_state = handle_allocation_event(old_state, event) 297 298 if new_state == DeviceAllocationState.checking_availability: 299 if self.global_device_filter and \ 300 device.device_sn not in self.global_device_filter: 301 event = DeviceEvent.AVAILABLE_CHECK_IGNORED 302 else: 303 event = DeviceEvent.AVAILABLE_CHECK_PASSED 304 new_state = handle_allocation_event(new_state, event) 305 306 if old_state != new_state: 307 state_changed = True 308 device.device_allocation_state = new_state 309 310 if state_changed is True and \ 311 new_state == DeviceAllocationState.available: 312 # notify_device_state_change 313 LOG.debug("Handle device event apply lock con") 314 self.lock_con.acquire() 315 LOG.debug("Find available device") 316 self.lock_con.notify_all() 317 LOG.debug("Handle device event release lock con") 318 self.lock_con.release() 319 320 if device.device_allocation_state == \ 321 DeviceAllocationState.unknown: 322 self.remove(device) 323 return 324 325 def launch_emulator(self): 326 pass 327 328 def kill_emulator(self): 329 pass 330 331 def list_devices(self): 332 for device_connector in self.device_connectors: 333 device_connector.monitor_lock.acquire(1) 334 try: 335 if check_mode_in_sys(ConfigConst.app_test): 336 return self.get_device_info() 337 else: 338 print("OHOS devices:") 339 print("{0:<20}{1:<16}{2:<16}{3:<16}{4:<16}{5:<16}{6:<16}".format( 340 "Serial", "OsType", "State", "Allocation", "Product", "host", 341 "port")) 342 for device in self.devices_list: 343 print( 344 "{0:<20}{1:<16}{2:<16}{3:<16}{4:<16}{5:<16}{6:<16}".format( 345 convert_serial(device.device_sn), 346 device.device_os_type, 347 device.test_device_state.value, 348 device.device_allocation_state, 349 device.label if device.label else 'None', 350 device.host, device.port)) 351 return "" 352 finally: 353 for device_connector in self.device_connectors: 354 device_connector.monitor_lock.release() 355 356 def get_device_info(self): 357 devices_info = [] 358 for device in self.devices_list: 359 result = device.get_device_params() 360 devices_info.append(result) 361 return devices_info 362 363 def __set_device_alias(self, devices): 364 for device in devices: 365 sn, alias = device.get("sn"), device.get("alias") 366 if sn and alias: 367 self.__device_alias.update({sn: alias}) 368 369 def __filter_selector__(self, selector): 370 if isinstance(selector, DeviceSelector): 371 return True 372 return False 373 374 def __filter_xml_node__(self, node): 375 if isinstance(node, DeviceNode): 376 if HDC_NAME in node.get_connectors() or \ 377 HDC_STD_NAME in node.get_connectors(): 378 return True 379 return False 380 381 382class ManagedDeviceListener(object): 383 """ 384 A class to listen for and act on device presence updates from ddmlib 385 """ 386 387 def __init__(self, manager): 388 self.manager = manager 389 390 def device_changed(self, idevice): 391 test_device = self.manager.find_or_create(idevice) 392 if test_device is None: 393 return 394 new_state = TestDeviceState.get_test_device_state(idevice.device_state) 395 test_device.test_device_state = new_state 396 if hasattr(test_device, "_is_root"): 397 test_device._is_root = None 398 if new_state == TestDeviceState.ONLINE: 399 self.manager.handle_device_event(test_device, 400 DeviceEvent.STATE_CHANGE_ONLINE) 401 LOG.debug("Set device %s %s to true" % ( 402 convert_serial(test_device.device_sn), ConfigConst.recover_state)) 403 test_device.set_recover_state(True) 404 elif new_state == TestDeviceState.NOT_AVAILABLE: 405 self.manager.handle_device_event(test_device, 406 DeviceEvent.STATE_CHANGE_OFFLINE) 407 if hasattr(test_device, "call_proxy_listener"): 408 test_device.call_proxy_listener() 409 test_device.device_state_monitor.set_state( 410 test_device.test_device_state) 411 LOG.debug("Device changed to %s: %s %s %s %s" % ( 412 new_state, convert_serial(idevice.device_sn), 413 idevice.device_os_type, idevice.host, idevice.port)) 414 self.report_device(idevice, new_state) 415 416 def device_connected(self, idevice): 417 test_device = self.manager.find_or_create(idevice) 418 if test_device is None: 419 return 420 new_state = TestDeviceState.get_test_device_state(idevice.device_state) 421 test_device.test_device_state = new_state 422 if test_device.test_device_state == TestDeviceState.ONLINE: 423 self.manager.handle_device_event(test_device, 424 DeviceEvent.CONNECTED_ONLINE) 425 elif new_state == TestDeviceState.NOT_AVAILABLE: 426 self.manager.handle_device_event(test_device, 427 DeviceEvent.CONNECTED_OFFLINE) 428 test_device.device_state_monitor.set_state( 429 test_device.test_device_state) 430 LOG.debug("Device connected: {} {} {} {}, state: {}".format( 431 convert_serial(idevice.device_sn), idevice.device_os_type, 432 idevice.host, idevice.port, test_device.test_device_state)) 433 LOG.debug("Set device %s %s to true" % ( 434 convert_serial(idevice.device_sn), ConfigConst.recover_state)) 435 test_device.set_recover_state(True) 436 self.report_device(idevice, new_state) 437 438 def device_disconnected(self, idevice): 439 test_device = self.manager.find_device(idevice) 440 if test_device is not None: 441 test_device.test_device_state = TestDeviceState.NOT_AVAILABLE 442 self.manager.handle_device_event(test_device, 443 DeviceEvent.DISCONNECTED) 444 test_device.device_state_monitor.set_state( 445 TestDeviceState.NOT_AVAILABLE) 446 LOG.debug("Device disconnected: %s %s %s %s" % ( 447 convert_serial(idevice.device_sn), idevice.device_os_type, 448 idevice.host, idevice.port)) 449 self.report_device(idevice, TestDeviceState.NOT_AVAILABLE) 450 451 @staticmethod 452 def report_device(idevice, device_state): 453 """向controller上报设备的信息和状态""" 454 sn = idevice.device_sn 455 if not Variables.config.enable_cluster() \ 456 or re.match(r'COM\d+', sn, flags=re.IGNORECASE): 457 return 458 from xdevice._core.cluster.utils import report_worker_device 459 device_os = idevice.device_os_type 460 device_type, report_state = "", DeviceState.OFFLINE 461 if device_state == TestDeviceState.ONLINE: 462 device_type, report_state = idevice.get_device_type(), DeviceState.ONLINE 463 report_worker_device(sn, "", "", device_os, device_type, report_state) 464