def recv_file_from_device(self, src, dest="./"): """Copies srcs on device to dest on host. Args: src (str): file path on device to copy to host. dest (str): destination path on host computer. Raises: DeviceError: if destination directory doesn't exist or copy failed. Note: If no dest is provided, the file will be copied to the current working directory on the host computer. """ destination_dir = os.path.dirname(dest) if destination_dir != "." and not os.path.exists(destination_dir): raise errors.DeviceError( "Device {} receive from device failed. " "Destination directory {} doesn't appear to exist.".format( self._device_name, destination_dir)) logger.info("{} receiving from device. Source: {} Destination: {}", self._device_name, src, dest) self._check_adb_mode(self._communication_address) output = adb_utils.pull_from_device( self._communication_address, src, destination_path=dest) for line in output.splitlines(): self._add_log_note_fn(line + "\n") if not os.path.exists(dest): raise errors.DeviceError("Final file {} does not exist".format(dest))
def send_file_to_device(self, src, dest): """Copies src from host to dest on the device. Args: src (str): local file path on host computer. dest (str): file path on device where the file should be copied to. Raises: DeviceError: if source file doesn't exist or copy failed. """ if not os.path.exists(src): raise errors.DeviceError("Device {} send to device failed. " "Source file {} doesn't appear to exist.".format( self._device_name, src)) logger.info("{} sending file(s) to device. Source: {} Destination: {}", self._device_name, src, dest) self._check_adb_mode(self._communication_address) try: output = adb_utils.push_to_device(self._communication_address, src, dest) for line in output.splitlines(): self._add_log_note_fn(line + "\n") except RuntimeError as err: raise errors.DeviceError("Unable to copy {} to {} on device. " "Error: {!r}".format(src, dest, err))
def _lock_unlock(self, locked: bool, verify: bool = True) -> None: """Locks or unlocks the device. Args: locked: Locks the device if true, unlocks the device if false. verify: If true, verifies the lock configurations before returning. Raises: DeviceError: Ack value is false or the device does not transition to the appropriate state. """ ack, _ = self._switchboard_call( method=pigweed_rpc_transport.PigweedRPCTransport.rpc, method_args=("Locking", "Set"), method_kwargs={ "locked": locked, "pw_rpc_timeout_s": self._rpc_timeout_s }) action = "Locking" if locked else "Unlocking" error_mesg = f"{action} device {self._device_name} failed: " if not ack: raise errors.DeviceError(error_mesg + "device did not acknowledge the RPC.") if verify: if locked != self.state: # pylint: disable=comparison-with-callable raise errors.DeviceError( error_mesg + f"device's locked state remains {self.state}.")
def send_file_to_device(self, src, dest): """Copies src from host to dest on the device. Args: src (str): local file path on host computer. dest (str): file path on device where the file should be copied to. Raises: DeviceError: if source file doesn't exist or copy failed. """ if not os.path.exists(src): raise errors.DeviceError("Device {} send to device failed. " "Source file {} doesn't appear to exist.".format( self._device_name, src)) ip_address = self._get_valid_ip_address() logger.info( "{} sending file to device. Source: {}, destination: {}.".format( self._device_name, src, dest)) try: output = host_utils.scp_to_device( ip_address, src, dest, user=self._user, key_info=self._key_info) for line in output.splitlines(): self._add_log_note_fn(line + "\n") except RuntimeError as err: raise errors.DeviceError("Device {} send to device failed. " "Unable to copy {} to {} on device. " "Error: {!r}".format(self._device_name, src, dest, err))
def eject(self, timeout=100.0): """Unmounts and ejects the mass storage device within the timeout specified. Args: timeout (float): time to wait to confirm the mass storage has been unmounted and ejected Raises: DeviceError: if timeout is exceeded before storage is confirmed as being unmounted or ejected. """ start_time = time.time() self.add_log_note("{} eject requested.".format(self._serial_number)) if self.is_device_ejected(timeout=.1): self.add_log_note("{} already ejected.".format(self._serial_number)) return self.unmount(timeout=timeout) _validate_suid_bit(self.commands["EJECT"]) try: cmd_list = self.commands["EJECT"].format(self.disk).split() subprocess.check_output(cmd_list) except subprocess.CalledProcessError as err: raise errors.DeviceError("Device eject failed. " "{} produced this error: {!r}".format( cmd_list, err.output)) if self.is_device_ejected(timeout=timeout): self.add_log_note("{} ejected in {}s".format( self._serial_number, int(time.time() - start_time))) return raise errors.DeviceError("Device eject failed. " "Unable to eject USB mass storage {} in {}s" "Check if device is currently in use. ".format( self.mount_point, timeout))
def _on_off(self, on: bool, no_wait: bool = False) -> None: """Turn on/off the light of the device. Args: on: Turns on the light if true, turn off the light otherwise. no_wait: If True, returns before verifying the light state. Raises: DeviceError: When the device does not transition to the appropriate state or if it remains the same state. """ lighting_ack, _ = self._switchboard_call( method=pigweed_rpc_transport.PigweedRPCTransport.rpc, method_args=("Lighting", "Set"), method_kwargs={"on": on}) action = "on" if on else "off" if not lighting_ack: raise errors.DeviceError( "Device {} turning light {} failed.".format( self._device_name, action)) if not no_wait: if on != self.state: # pylint: disable=comparison-with-callable raise errors.DeviceError( "Device {} light didn't turn {}.".format( self._device_name, action))
def __init__(self, manager, device_config, log_file_name=None, log_directory=None): self._log_object_lifecycle_event("__init__") self.manager_weakref = weakref.ref(manager) # Create a dictionary to store "properties". For now keep the # classification of "persistent" and "optional". self.props = { "persistent_identifiers": device_config["persistent"], "optional": device_config["options"] } logger.debug("{} device_config: {}", self.name, self.props["persistent_identifiers"]) logger.debug("{} device_options: {}", self.name, self.props["optional"]) self._make_device_ready_setting = device_config.get( "make_device_ready", "on") if not isinstance(device_config.get("make_device_ready", "on"), str): raise errors.DeviceError( "Device creation failed. 'make_device_ready' " "should be a string. but instead it is a {}".format( str(type(device_config["make_device_ready"])))) self._make_device_ready_setting = self._make_device_ready_setting.lower( ) if self._make_device_ready_setting not in ["on", "off", "check_only"]: raise errors.DeviceError( "Device creation failed. 'make_device_ready' " "should be 'on', 'off' or 'check_only' not {}".format( device_config["make_device_ready"])) self._commands = {} self._regexes = {} self._timeouts = TIMEOUTS.copy() self.device_type = self.DEVICE_TYPE if not self._DEFAULT_FILTERS: logger.warning( f"Device type {self.device_type!r} has no default event " "filters defined.") self.filter_paths = self._DEFAULT_FILTERS.copy() self.filter_paths += device_config.get("filters") or [] # Initialize log files self.log_directory = log_directory if log_file_name: self._log_file_name = os.path.join(log_directory, log_file_name) else: log_name_prefix = device_config["log_name_prefix"] self._log_file_name = get_log_filename(log_directory, self.name, name_prefix=log_name_prefix) self._update_event_filename_and_symlinks()
def __init__(self, manager, device_config, log_file_name=None, log_directory=None): self._log_object_lifecycle_event("__init__") self._manager_weakref = weakref.ref(manager) # Create a dictionary to store "properties". For now keep the # classification of "persistent" and "optional". self.props = { "persistent_identifiers": device_config["persistent"], "optional": device_config["options"] } logger.debug("{} device_config: {}", self.name, self.props["persistent_identifiers"]) logger.debug("{} device_options: {}", self.name, self.props["optional"]) self._make_device_ready_setting = device_config.get("make_device_ready", "on") if not isinstance(device_config.get("make_device_ready", "on"), str): raise errors.DeviceError( "Device creation failed. 'make_device_ready' " "should be a string. but instead it is a {}".format( str(type(device_config["make_device_ready"])))) self._make_device_ready_setting = self._make_device_ready_setting.lower() if self._make_device_ready_setting not in ["on", "off", "check_only"]: raise errors.DeviceError( "Device creation failed. 'make_device_ready' " "should be 'on', 'off' or 'check_only' not {}".format( device_config["make_device_ready"])) self._commands = {} self._regexes = {} self._timeouts = TIMEOUTS.copy() self.device_type = self.DEVICE_TYPE self.filter_paths = self._DEFAULT_FILTERS + tuple( device_config.get("filters") or ()) # Initialize log files self.log_directory = log_directory if log_file_name: self._log_file_name = os.path.join(log_directory, log_file_name) else: log_name_prefix = device_config["log_name_prefix"] self._log_file_name = get_log_filename( log_directory, self.name, name_prefix=log_name_prefix) self._update_event_filename_and_symlinks() # b/201669630: Ensure the device instance is closed (again) if it's # continued to be used after an explicit <device>.close() call. atexit.register(common_utils.MethodWeakRef(self.close))
def test_611_shell_with_regex_success_with_retries(self): """Verify shell_with_regex() works when it has to retry.""" shell_responses = [ errors.DeviceError("Error 1"), errors.DeviceError("Error 2"), ("gobbly", 0) ] with mock.patch.object(self.uut, "shell", side_effect=shell_responses) as mock_shell: result = self.uut.shell_with_regex("some command", r"(.*)", tries=3) self.assertEqual(result, "gobbly") self.assertEqual(mock_shell.call_count, 3)
def move_file_from(self, relative_source_file_path, destination, delete_file=True): """Moves file from storage mount point to destination directory specified. Args: relative_source_file_path (str): relative path to file on device to copy over. destination (str): destination directory or file name path delete_file (bool): delete file from device. Raises: DeviceError: if storage is not mounted, the mount path doesn't exist, or if the source file doesn't exist Note: The storage device must be mounted before calling this method. """ if not self.is_mounted(): raise errors.DeviceError( "Move_file_from failed. " "Device with Serial {} is not mounted.".format( self._serial_number)) source_file_path = os.path.join(self.mount_point, relative_source_file_path) if not os.path.exists(source_file_path): raise errors.DeviceError( "Device copy file failed. " "Source file {} doesn't exist. ".format(source_file_path)) msg = "Copying {} to {}".format(source_file_path, destination) logger.debug(msg) self.add_log_note(msg) shutil.copy(source_file_path, destination) if delete_file: logger.debug("Removing %s", source_file_path) os.remove(source_file_path) if not os.path.exists(destination): raise errors.DeviceError( "Device copy file failed. " "Destination {} does not exist. " "Check destination path permissions.".format(destination)) msg = "Copying {} to {} completed.".format(relative_source_file_path, destination) logger.debug(msg) self.add_log_note(msg)
def mount(self, timeout=180): """Mounts the mass storage device within the timeout specified. Args: timeout (float): time to wait to confirm storage has been mounted and/or after manually attempting to mount the storage Raises: DeviceError: if timeout is exceeded before storage is confirmed as being mounted. Note: Since there is a udev rule that is responsible for performing the actual mounting of the device this method will wait up to the timeout specified before attempting to manually mount the device and then wait again for the timeout specified to confirm that the device has been successfully mounted """ self.add_log_note("{} mount requested.".format(self._serial_number)) if self.is_mounted(): return if not self.is_found(): self.add_log_note("Error: {} not found on machine.".format( self._serial_number)) raise errors.DeviceError( "Device mount failed. " "USB mass storage matching Serial#={} was not found. " "\nCheck cables and device or try rebooting.".format( self._serial_number)) msg = "Mounting {}".format(self.disk) logger.debug(msg) self.add_log_note(msg) _validate_suid_bit(self.commands["MOUNT"]) cmd_list = self.commands["MOUNT"].format(self.disk).split() try: subprocess.check_output(cmd_list) except subprocess.CalledProcessError as err: if isinstance(err.output, bytes): err.output = err.output.decode("utf-8") if "Not authorized" in err.output: err = "Try running 'sudo chmod u+s {}' to fix permission error.".format( " ".join(cmd_list[0])) raise errors.DeviceError( "Device mount failed. Err: {!r}".format(err)) if not self.is_mounted(timeout=timeout): raise errors.DeviceError( "Device mount failed. Not mounted after {}s".format(timeout))
def copy_file_to(self, source_file_path, relative_destination_dir=None): """Copies source file to destination directory specified. Args: source_file_path (str): path to source file to copy to mass storage device relative_destination_dir (str): optional path relative to the mount path of the mass storage device Raises: DeviceError: if storage is not mounted, the mount path doesn't exist, or if the source file doesn't exist after copying the source file to the mount path + relative destination directory destination Note: The storage device must be mounted before calling this method. If the relative destination directory specified doesn't exist it will be created before the source file is copied """ if not self.is_mounted(): raise errors.DeviceError( "Device copy file failed. " "Device with Serial {} is not mounted.".format( self._serial_number)) mount_point = self.mount_point if relative_destination_dir is not None: dst_file_path = os.path.join(mount_point, relative_destination_dir) else: dst_file_path = mount_point if not os.path.exists(dst_file_path): logger.debug("Creating destination path %s", dst_file_path) os.makedirs(dst_file_path) file_name = os.path.basename(source_file_path) dst_file_path = os.path.join(dst_file_path, file_name) msg = "Copying {} to {}".format(source_file_path, dst_file_path) logger.debug(msg) self.add_log_note(msg) shutil.copyfile(source_file_path, dst_file_path) if not os.path.exists(dst_file_path): raise errors.DeviceError( "Device copy file failed. " "File does not exist at destination {}" "Check destination path permissions.".format(dst_file_path)) msg = "Copying {} to {} completed".format(source_file_path, dst_file_path) logger.debug(msg) self.add_log_note(msg)
def test_convenience_parallel_function( self, function, method_args, method_kwargs, raises): """Tests one of the provided convenience parallel functions.""" mock_manager = mock.MagicMock(spec=manager.Manager) mock_device = mock.MagicMock(spec=gazoo_device_base.GazooDeviceBase) mock_device.flash_build = mock.MagicMock(flash_build_base.FlashBuildBase) mock_device.name = "device-1234" mock_manager.create_device.return_value = mock_device if function is parallel_utils.factory_reset: device_method = mock_device.factory_reset elif function is parallel_utils.reboot: device_method = mock_device.reboot else: device_method = mock_device.flash_build.upgrade if raises: device_method.side_effect = errors.DeviceError("Failed") with self.assertRaisesRegex(errors.DeviceError, "Failed"): function(mock_manager, mock_device.name, *method_args, **method_kwargs) else: device_method.return_value = None self.assertIsNone( function( mock_manager, mock_device.name, *method_args, **method_kwargs)) mock_manager.create_device.assert_called_once_with(mock_device.name) device_method.assert_called_once_with(*method_args, **method_kwargs) mock_device.close.assert_called_once()
def flash_device(self, list_of_files: List[str], expected_version: Optional[str] = None, expected_build_type: Optional[str] = None, verify_flash: bool = True, method: Optional[str] = None) -> None: """Flashes the firmware image (.hex file) on the device. Args: list_of_files: Image files on local host, currently supports flashing only one hex file at a time. expected_version: Not used. expected_build_type: Not used. verify_flash: Not used. method: Not used. """ del expected_version, expected_build_type, verify_flash, method # Unused. if len(list_of_files) != 1: raise ValueError("Only one hex file can be flashed via JLink.") image_path = list_of_files[0] if not image_path.endswith(".hex"): raise ValueError("Only hex type file can be flashed.") if not os.path.exists(image_path): raise errors.DeviceError( f"Firmware image {image_path} does not exist.") self._open_and_halt() image = intelhex.IntelHex(os.path.abspath(image_path)) for segment_start, segment_end in image.segments(): segment_size = segment_end - segment_start segment = image.tobinarray(start=segment_start, size=segment_size) self._jlink.flash_write8(segment_start, segment) self._reset_and_close()
def test_002_unable_to_ready_device(self): """Verifies capability raises a error if manager is not set.""" err_msg = (f"{self.name} cambrionix not responding") self.mock_manager.create_device.side_effect = errors.DeviceError( "cambrionix not responding") with self.assertRaisesRegex(errors.CapabilityNotReadyError, err_msg): self.uut.health_check()
def get_mode(self, port: int) -> str: """Gets the mode for the specified port. Args: port: Use this port to get the mode. Returns: Port mode settings "OFF", "ON". Raises: DeviceError: Raised if passed invalid port. """ self._validate_port(port) get_mode_command = _GET_MODE_SNMP_COMMAND.format( snmp_command=_GET, community=self._community, ip=self._ip_address, port=port) # A command that query's port 1's status looks like: # "snmpget -v 2c -c private 123.45.67.89:161 1.3.6.1.2.1.2.2.1.7.1" # And its expected response should look like: # "iso.3.6.1.2.1.2.2.1.7.1 = INTEGER: 1" response = subprocess.check_output(get_mode_command.split(), text=True, timeout=_SNMP_TIMEOUT_S) match = re.search(_MODE_RESPONSE_REG_EX, response) if match and match.group(1) in _GET_MODE_MAPPING.keys(): return _GET_MODE_MAPPING[match.group(1)] raise errors.DeviceError( f"{self._device_name} failed to get port status" f"with command: {get_mode_command}\n" f"Unexpected output: {response}")
def _open(self): start_time = time.time() error = "" while time.time() - start_time < self.timeouts["OPEN"]: try: if self._serial_port is None: self._serial_port = serial.Serial( port=self.communication_address, baudrate=115200, timeout=0.1, exclusive=True) # Prevent inheritance of file descriptors to exec'd child processes [NEP-1852] file_descriptor = self._serial_port.fd flags = fcntl.fcntl(file_descriptor, fcntl.F_GETFD) fcntl.fcntl(file_descriptor, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) return if not self._serial_port.is_open: self._serial_port.open() return except Exception as err: error = err raise errors.DeviceError( "Device {} open failed. " "Unable to open control serial port in {} seconds" "Error: {}".format(self.name, self.timeouts["OPEN"], error))
def get_capability_classes( self, capability_name: str) -> List[Type[capability_base.CapabilityBase]]: """Returns possible capability classes (flavors) for the capability. Args: capability_name (str): name of the capability, such as "switchboard" or "file_transfer". Returns: list[type]: capability classes that can be used for the capability. Raises: DeviceError: if the capability is not supported by the device. Note: For a given device class, most capabilities are implemented by just 1 flavor. In a few cases, there are several flavors. Which flavor is used depends on the firmware version and is determined at runtime. """ if not self.has_capabilities([capability_name]): raise errors.DeviceError( "{} get_capability_classes failed. " "Capability {} is not supported by {}." .format(self.name, capability_name, self.device_type)) capability_property = getattr(type(self), capability_name) return list(capability_property.capability_classes)
def recv_file_from_device(self, src, dest="./"): """Copy a file from the device to the local host. Args: src (str): Absolute path of the source file on the device. dest (str): Absolute path of the destination on the host Raises: DeviceError: if the file transfer fails for any reason. RuntimeError: if the final file does not exist Note: dest can be directory or a file name. """ dest_dir = os.path.dirname(dest) if dest_dir != "." and not os.path.exists(dest_dir): raise errors.DeviceError( "Device {} receive from device failed. " "Destination directory {} doesn't appear to exist.".format( self._device_name, dest_dir)) logger.info("{} receiving file from device. Source: {} Destination: {}", self._device_name, src, dest) host_utils.docker_cp_from_device( docker_container=self._docker_container, local_file_path=dest, container_file_path=src) if not os.path.exists(dest): raise RuntimeError("Final file {} does not exist".format(dest))
def set_mode(self, mode, port): """Sets the given port to the mode specified. Args: mode (str): mode to set. Options: 'off', 'on' port (int): The port to set. Raises: DeviceError: invalid port, or mode. """ self._validate_mode(mode) logger.info("{} setting power mode to {} for port {}".format( self._device_name, mode, port)) if mode == ON: self._turn_on_port_func(port) else: self._turn_off_port_func(port) try: common_utils.retry(func=self._verify_mode_change, func_args=[port, mode], timeout=TIMEOUTS["STATE_CHANGE"], is_successful=common_utils.is_true, interval=1, reraise=False) except errors.CommunicationTimeoutError: raise errors.DeviceError( "{} failed to verify that ethernet connection is {}".format( self._device_name, mode))
def issue_devices_match(self, match: str, attribute_name: str, *method_args: Any, timeout: float = parallel_utils.TIMEOUT_PROCESS, **method_kwargs: Any) -> List[Any]: """Executes a device method or property in parallel for matching devices. For example: 'gdm issue-devices-match raspberrypi* reboot --no_wait=True'. Args: match: Wildcard-supported string to match against device names, i.e. "raspberrypi*" will call provided method on all connected Raspberry Pis. attribute_name: Name of device method or property to execute in parallel. Can be nested. For example: "shell", "wifi.ssid". *method_args: Positional arguments to the device method. timeout: Maximum amount of seconds to allow parallel methods to complete. **method_kwargs: Keyword arguments to the device method. Returns: Results from parallel calls. Raises: DeviceError: if provided wildcard does not match any connected devices. """ devices = fnmatch.filter(self.get_connected_devices(), match) if not devices: raise errors.DeviceError(f"No devices match {match!r}.") return self._issue_devices(devices, attribute_name, timeout, method_args, method_kwargs)
def issue_devices_all(self, attribute_name: str, *method_args: Any, timeout: float = parallel_utils.TIMEOUT_PROCESS, **method_kwargs: Any) -> List[Any]: """Executes a device method or property in parallel for connected devices. For example: 'gdm issue-devices-all reboot --no_wait=True'. Args: attribute_name: Name of device method or property to execute in parallel. Can be nested. For example: "shell", "wifi.ssid". *method_args: Positional arguments to the device method. timeout: Maximum amount of seconds to allow parallel methods to complete. **method_kwargs: Keyword arguments to the device method. Returns: Results from parallel calls. Raises: DeviceError: if no devices are connected. """ devices = self.get_connected_devices() if not devices: raise errors.DeviceError("No devices are connected.") return self._issue_devices(devices, attribute_name, timeout, method_args, method_kwargs)
def send_file_to_device(self, src, dest): """Copies src from host to dest on the device. Args: src (str): local file path on host computer. dest (str): file path on device where the file should be copied to. Raises: DeviceError: if source file doesn't exist or copy failed. """ if not os.path.exists(src): raise errors.DeviceError( "Device {} send to device failed. " "Source file {} doesn't appear to exist.".format( self._device_name, src)) if self._is_valid_device_directory(dest): dest = os.path.join(dest, os.path.basename(src)) logger.info("{} sending file to device. Source: {} Destination: {}", self._device_name, src, dest) for try_num in range(self._send_tries): try: self._echo_file_to_transport_fn(src, dest) return except errors.DeviceError as err: logger.info( "{} failed to send to device {!r} - Retrying.".format( self._device_name, src)) if try_num == self._send_tries - 1: raise err
def make_testbed_ready(self, setting="on"): """Check testbed readiness and attempt recovery if allowed. Args: setting (str): 'on'|'off'|'check_only'. Raises: DeviceError: if testbed health checks fail. """ if setting not in ["on", "off", "check_only"]: raise errors.DeviceError( "{} failed make_testbed_ready. Setting '{}' is invalid.". format(self.name, setting)) elif setting == "off": return # check testbed health try: self.check_testbed_ready() except errors.DeviceError as err: if setting == "check_only": logger.info("{} skipping recovery".format(self.name)) raise logger.info("{} failed check_testbed_ready with {}".format( self.name, repr(err))) # attempt to recover the testbed self.recover(err) logger.info( "{} re-checking readiness after recovery attempt".format( self.name)) self.check_testbed_ready() logger.info("{} successfully recovered to ready state".format( self.name))
def send_and_expect(self, command, pattern_list, timeout=30.0, searchwindowsize=config.SEARCHWINDOWSIZE, expect_type=line_identifier.LINE_TYPE_RESPONSE, mode=switchboard.MODE_TYPE_ANY, port=0, slow=False, add_newline=True, newline="\n", command_tries=1, raise_for_timeout=False): """Mock implementation of Switchboard.send_and_expect.""" # Delete unused arguments. del timeout, searchwindowsize, expect_type, port, slow, command_tries cmd = switchboard._ensure_has_newline( # pylint: disable=protected-access command, add_newline=add_newline, newline=newline) self.debug_print("Send and expect on cmd {}:".format(repr(cmd))) output = self._command_and_expect_parser(cmd, pattern_list, mode) if output and output.timedout and raise_for_timeout: raise errors.DeviceError( "send_and_expect timed out\n" "command: {}\nRemaining patterns: {}\nOutput: {}".format( command, "\n".join(output.remaining), output.before)) return output
def _open(self): start_time = time.time() error = "" while time.time() - start_time < self.timeouts["OPEN"]: try: if self._serial_port is None: # Windows is not supported due to use of fcntl. # Cast to Posix serial so pytype understands this. self._serial_port = typing.cast( serial.serialposix.Serial, serial.Serial(port=self.communication_address, baudrate=115200, timeout=0.1, exclusive=True)) # NEP-1852: Prevent inheritance of file descriptors to exec'd child # processes. file_descriptor = self._serial_port.fd flags = fcntl.fcntl(file_descriptor, fcntl.F_GETFD) fcntl.fcntl(file_descriptor, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) return if not self._serial_port.is_open: self._serial_port.open() return except Exception as err: error = err raise errors.DeviceError( "Device {} open failed. " "Unable to open control serial port in {} seconds" "Error: {}".format(self.name, self.timeouts["OPEN"], error))
def send_file_to_device(self, src, dest): """Copy a file from the local host to the device. Args: src (str): Absolute path of the source file on the host. dest (str): Absolute path of the destination on the device Raises: DeviceError: if the file transfer fails for any reason, or if the specified mode is invalid. Note: dest may be either a directory or a file name. """ if not os.path.exists(src): raise errors.DeviceError("Device {} send to device failed. " "Source file {} doesn't appear to exist.".format( self._device_name, src)) logger.info("{} sending file to device. Source: {} Destination: {}", self._device_name, src, dest) host_utils.docker_cp_to_device( docker_container=self._docker_container, local_file_path=src, container_file_path=dest)
def __get_response(serial_port): """Internal helper returning the response from the hub as a list of lines. Args: serial_port (str): Cambrionix serial port. Returns: str: response. Raises: DeviceError: Device not responding. Note: The command prompt is always this string: ">> ". """ read_data = "" while not read_data.endswith("\n>> "): ready = select.select([serial_port], [], [], 25)[0] if ready: read_data += serial_port.read(serial_port.inWaiting()).decode( "utf-8", "replace") else: raise errors.DeviceError( "Device cambrionix get response failed. " "Read timeout on serial port: {}".format(serial_port)) return read_data.splitlines()
def _ensure_device_goes_offline(self, timeout=None): """Ensure device is no longer pingable over ssh. Args: timeout (float): Time in seconds to wait for device to respond. Raises: DeviceError: Device failed to go offline before the timeout """ timeout = timeout or self.timeouts["SHUTDOWN"] start_time = time.time() max_disconnect_time = start_time + timeout count = 0 while time.time() < max_disconnect_time: if not host_utils.is_pingable(self.ip_address): count += 1 # Ensure device is really offline not just a blip else: count = 0 if count == 2: logger.info("{} offline in {}s.".format(self.name, int(time.time() - start_time))) # close ssh transport as the ssh connection is disconnected. self.switchboard.close_all_transports() time.sleep(5) # to ensure offline return time.sleep(.5) raise errors.DeviceError("Failed to go offline within {}s.".format(timeout))
def _on_off( self, on: bool, level: Optional[int] = None, color: Optional[lighting_service_pb2.LightingColor] = None, verify: bool = True) -> None: """Turn on/off the light of the device. Args: on: Turns on the light if true, turn off the light otherwise. level: Brightness level to be set, unused if on = False. color: Lighting color to be set, unused if on = False. verify: If true, verifies the light configurations before returning. Raises: DeviceError: When the device does not transition to the appropriate lighting configuration. """ set_onoff_kwargs = {"on": on, "pw_rpc_timeout_s": self._rpc_timeout_s} if on: color_proto_state = pwrpc_utils.PigweedProtoState( color, _LIGHTING_COLOR_PROTO_CLASS) set_onoff_kwargs.update({"level": level, "color": color_proto_state}) ack, _ = self._switchboard_call( method=pigweed_rpc_transport.PigweedRPCTransport.rpc, method_args=("Lighting", "Set"), method_kwargs=set_onoff_kwargs) action = "on" if on else "off" if not ack: raise errors.DeviceError( f"Device {self._device_name} turning light {action} failed.") if verify: if on != self.state: # pylint: disable=comparison-with-callable raise errors.DeviceError( f"Device {self._device_name} light didn't turn {action}.") if on: if level != self.brightness: # pylint: disable=comparison-with-callable raise errors.DeviceError( f"Device {self._device_name} brightness level didn't change " f"to {level}.") if color != self.color: # pylint: disable=comparison-with-callable raise errors.DeviceError( f"Device {self._device_name} lighting color didn't change " f"to {color}.")