Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Copyright 2019 - The Android Open Source Project |
| 4 | # |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | |
Omar El Ayach | 27522c6 | 2019-11-06 08:51:15 -0800 | [diff] [blame] | 17 | import contextlib |
| 18 | import io |
Alfie Chen | 2dd8698 | 2020-10-05 11:49:44 +0800 | [diff] [blame] | 19 | import serial |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 20 | import time |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 21 | from acts import logger |
| 22 | from acts import utils |
| 23 | |
Omar El Ayach | d442512 | 2020-01-30 16:28:48 -0800 | [diff] [blame] | 24 | SHORT_SLEEP = 1 |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 25 | CHAMBER_SLEEP = 30 |
| 26 | |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 27 | |
| 28 | def create(configs): |
| 29 | """Factory method for OTA chambers. |
| 30 | |
| 31 | Args: |
| 32 | configs: list of dicts with chamber settings. settings must contain the |
| 33 | following: type (string denoting type of chamber) |
| 34 | """ |
| 35 | objs = [] |
| 36 | for config in configs: |
| 37 | try: |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 38 | chamber_class = globals()[config['model']] |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 39 | except KeyError: |
| 40 | raise KeyError('Invalid chamber configuration.') |
| 41 | objs.append(chamber_class(config)) |
| 42 | return objs |
| 43 | |
| 44 | |
| 45 | def detroy(objs): |
| 46 | return |
| 47 | |
| 48 | |
| 49 | class OtaChamber(object): |
| 50 | """Base class implementation for OTA chamber. |
| 51 | |
| 52 | Base class provides functions whose implementation is shared by all |
| 53 | chambers. |
| 54 | """ |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 55 | def reset_chamber(self): |
| 56 | """Resets the chamber to its zero/home state.""" |
| 57 | raise NotImplementedError |
| 58 | |
| 59 | def set_orientation(self, orientation): |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 60 | """Set orientation for turn table in OTA chamber. |
| 61 | |
| 62 | Args: |
| 63 | angle: desired turn table orientation in degrees |
| 64 | """ |
| 65 | raise NotImplementedError |
| 66 | |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 67 | def set_stirrer_pos(self, stirrer_id, position): |
| 68 | """Starts turntables and stirrers in OTA chamber.""" |
| 69 | raise NotImplementedError |
| 70 | |
| 71 | def start_continuous_stirrers(self): |
| 72 | """Starts turntables and stirrers in OTA chamber.""" |
| 73 | raise NotImplementedError |
| 74 | |
| 75 | def stop_continuous_stirrers(self): |
| 76 | """Stops turntables and stirrers in OTA chamber.""" |
| 77 | raise NotImplementedError |
| 78 | |
Omar El Ayach | bbc6a0a | 2019-07-22 10:26:11 -0700 | [diff] [blame] | 79 | def step_stirrers(self, steps): |
| 80 | """Move stepped stirrers in OTA chamber to next step.""" |
| 81 | raise NotImplementedError |
| 82 | |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 83 | |
Omar El Ayach | 68395c4 | 2019-03-01 11:25:47 -0800 | [diff] [blame] | 84 | class MockChamber(OtaChamber): |
| 85 | """Class that implements mock chamber for test development and debug.""" |
Omar El Ayach | 68395c4 | 2019-03-01 11:25:47 -0800 | [diff] [blame] | 86 | def __init__(self, config): |
| 87 | self.config = config.copy() |
| 88 | self.device_id = self.config['device_id'] |
| 89 | self.log = logger.create_tagged_trace_logger('OtaChamber|{}'.format( |
| 90 | self.device_id)) |
Omar El Ayach | 3903e34 | 2019-09-17 19:17:03 -0700 | [diff] [blame] | 91 | self.current_mode = None |
Omar El Ayach | 68395c4 | 2019-03-01 11:25:47 -0800 | [diff] [blame] | 92 | |
| 93 | def set_orientation(self, orientation): |
| 94 | self.log.info('Setting orientation to {} degrees.'.format(orientation)) |
| 95 | |
Omar El Ayach | bbc6a0a | 2019-07-22 10:26:11 -0700 | [diff] [blame] | 96 | def reset_chamber(self): |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 97 | self.log.info('Resetting chamber to home state') |
| 98 | |
| 99 | def set_stirrer_pos(self, stirrer_id, position): |
| 100 | """Starts turntables and stirrers in OTA chamber.""" |
| 101 | self.log.info('Setting stirrer {} to {}.'.format(stirrer_id, position)) |
| 102 | |
| 103 | def start_continuous_stirrers(self): |
| 104 | """Starts turntables and stirrers in OTA chamber.""" |
| 105 | self.log.info('Starting continuous stirrer motion') |
| 106 | |
| 107 | def stop_continuous_stirrers(self): |
| 108 | """Stops turntables and stirrers in OTA chamber.""" |
| 109 | self.log.info('Stopping continuous stirrer motion') |
| 110 | |
Omar El Ayach | bbc6a0a | 2019-07-22 10:26:11 -0700 | [diff] [blame] | 111 | def configure_stepped_stirrers(self, steps): |
| 112 | """Programs parameters for stepped stirrers in OTA chamber.""" |
| 113 | self.log.info('Configuring stepped stirrers') |
| 114 | |
| 115 | def step_stirrers(self, steps): |
| 116 | """Move stepped stirrers in OTA chamber to next step.""" |
| 117 | self.log.info('Moving stirrers to the next step') |
| 118 | |
Omar El Ayach | 68395c4 | 2019-03-01 11:25:47 -0800 | [diff] [blame] | 119 | |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 120 | class OctoboxChamber(OtaChamber): |
| 121 | """Class that implements Octobox chamber.""" |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 122 | def __init__(self, config): |
| 123 | self.config = config.copy() |
| 124 | self.device_id = self.config['device_id'] |
| 125 | self.log = logger.create_tagged_trace_logger('OtaChamber|{}'.format( |
| 126 | self.device_id)) |
| 127 | self.TURNTABLE_FILE_PATH = '/usr/local/bin/fnPerformaxCmd' |
Omar El Ayach | 7e89543 | 2019-05-20 10:24:00 -0700 | [diff] [blame] | 128 | utils.exe_cmd('sudo {} -d {} -i 0'.format(self.TURNTABLE_FILE_PATH, |
| 129 | self.device_id)) |
Omar El Ayach | 7d66126 | 2019-09-18 08:29:36 -0700 | [diff] [blame] | 130 | self.current_mode = None |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 131 | |
| 132 | def set_orientation(self, orientation): |
Omar El Ayach | 2f9c4c4 | 2019-01-22 18:24:04 -0800 | [diff] [blame] | 133 | self.log.info('Setting orientation to {} degrees.'.format(orientation)) |
Omar El Ayach | 27522c6 | 2019-11-06 08:51:15 -0800 | [diff] [blame] | 134 | utils.exe_cmd('sudo {} -d {} -p {}'.format(self.TURNTABLE_FILE_PATH, |
| 135 | self.device_id, |
| 136 | orientation)) |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 137 | |
| 138 | def reset_chamber(self): |
| 139 | self.log.info('Resetting chamber to home state') |
| 140 | self.set_orientation(0) |
| 141 | |
| 142 | |
| 143 | class ChamberAutoConnect(object): |
| 144 | def __init__(self, chamber, chamber_config): |
| 145 | self._chamber = chamber |
| 146 | self._config = chamber_config |
| 147 | |
| 148 | def __getattr__(self, item): |
| 149 | def chamber_call(*args, **kwargs): |
| 150 | self._chamber.connect(self._config['ip_address'], |
| 151 | self._config['username'], |
| 152 | self._config['password']) |
| 153 | return getattr(self._chamber, item)(*args, **kwargs) |
| 154 | |
| 155 | return chamber_call |
| 156 | |
| 157 | |
| 158 | class BluetestChamber(OtaChamber): |
| 159 | """Class that implements Octobox chamber.""" |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 160 | def __init__(self, config): |
Omar El Ayach | 033b33a | 2019-08-01 11:32:08 -0700 | [diff] [blame] | 161 | import flow |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 162 | self.config = config.copy() |
| 163 | self.log = logger.create_tagged_trace_logger('OtaChamber|{}'.format( |
| 164 | self.config['ip_address'])) |
| 165 | self.chamber = ChamberAutoConnect(flow.Flow(), self.config) |
| 166 | self.stirrer_ids = [0, 1, 2] |
| 167 | self.current_mode = None |
| 168 | |
Omar El Ayach | 27522c6 | 2019-11-06 08:51:15 -0800 | [diff] [blame] | 169 | # Capture print output decorator |
| 170 | @staticmethod |
| 171 | def _capture_output(func, *args, **kwargs): |
| 172 | """Creates a decorator to capture stdout from bluetest module""" |
| 173 | f = io.StringIO() |
| 174 | with contextlib.redirect_stdout(f): |
| 175 | func(*args, **kwargs) |
| 176 | output = f.getvalue() |
| 177 | return output |
| 178 | |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 179 | def _connect(self): |
| 180 | self.chamber.connect(self.config['ip_address'], |
| 181 | self.config['username'], self.config['password']) |
| 182 | |
| 183 | def _init_manual_mode(self): |
| 184 | self.current_mode = 'manual' |
| 185 | for stirrer_id in self.stirrer_ids: |
Omar El Ayach | 27522c6 | 2019-11-06 08:51:15 -0800 | [diff] [blame] | 186 | out = self._capture_output( |
| 187 | self.chamber.chamber_stirring_manual_init, stirrer_id) |
| 188 | if "failed" in out: |
| 189 | self.log.warning("Initialization error: {}".format(out)) |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 190 | time.sleep(CHAMBER_SLEEP) |
| 191 | |
| 192 | def _init_continuous_mode(self): |
| 193 | self.current_mode = 'continuous' |
Omar El Ayach | 27522c6 | 2019-11-06 08:51:15 -0800 | [diff] [blame] | 194 | self.chamber.chamber_stirring_continuous_init() |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 195 | |
Omar El Ayach | bbc6a0a | 2019-07-22 10:26:11 -0700 | [diff] [blame] | 196 | def _init_stepped_mode(self, steps): |
| 197 | self.current_mode = 'stepped' |
| 198 | self.current_stepped_pos = 0 |
| 199 | self.chamber.chamber_stirring_stepped_init(steps, False) |
| 200 | |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 201 | def set_stirrer_pos(self, stirrer_id, position): |
| 202 | if self.current_mode != 'manual': |
| 203 | self._init_manual_mode() |
| 204 | self.log.info('Setting stirrer {} to {}.'.format(stirrer_id, position)) |
Omar El Ayach | 27522c6 | 2019-11-06 08:51:15 -0800 | [diff] [blame] | 205 | out = self._capture_output( |
| 206 | self.chamber.chamber_stirring_manual_set_pos, stirrer_id, position) |
| 207 | if "failed" in out: |
| 208 | self.log.warning("Bluetest error: {}".format(out)) |
| 209 | self.log.warning("Set position failed. Retrying.") |
| 210 | self.current_mode = None |
| 211 | self.set_stirrer_pos(stirrer_id, position) |
| 212 | else: |
| 213 | self._capture_output(self.chamber.chamber_stirring_manual_wait, |
| 214 | CHAMBER_SLEEP) |
| 215 | self.log.warning('Stirrer {} at {}.'.format(stirrer_id, position)) |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 216 | |
| 217 | def set_orientation(self, orientation): |
| 218 | self.set_stirrer_pos(2, orientation * 100 / 360) |
| 219 | |
| 220 | def start_continuous_stirrers(self): |
| 221 | if self.current_mode != 'continuous': |
| 222 | self._init_continuous_mode() |
Omar El Ayach | 27522c6 | 2019-11-06 08:51:15 -0800 | [diff] [blame] | 223 | self.chamber.chamber_stirring_continuous_start() |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 224 | |
| 225 | def stop_continuous_stirrers(self): |
Omar El Ayach | 27522c6 | 2019-11-06 08:51:15 -0800 | [diff] [blame] | 226 | self.chamber.chamber_stirring_continuous_stop() |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 227 | |
Omar El Ayach | bbc6a0a | 2019-07-22 10:26:11 -0700 | [diff] [blame] | 228 | def step_stirrers(self, steps): |
| 229 | if self.current_mode != 'stepped': |
| 230 | self._init_stepped_mode(steps) |
| 231 | if self.current_stepped_pos == 0: |
| 232 | self.current_stepped_pos += 1 |
| 233 | return |
| 234 | self.current_stepped_pos += 1 |
| 235 | self.chamber.chamber_stirring_stepped_next_pos() |
| 236 | |
Omar El Ayach | 7f7f10e | 2019-07-10 16:53:39 -0700 | [diff] [blame] | 237 | def reset_chamber(self): |
| 238 | if self.current_mode == 'continuous': |
| 239 | self._init_continuous_mode() |
Omar El Ayach | d442512 | 2020-01-30 16:28:48 -0800 | [diff] [blame] | 240 | time.sleep(SHORT_SLEEP) |
| 241 | self._init_continuous_mode() |
Omar El Ayach | bbc6a0a | 2019-07-22 10:26:11 -0700 | [diff] [blame] | 242 | else: |
Alfie Chen | 2dd8698 | 2020-10-05 11:49:44 +0800 | [diff] [blame] | 243 | self._init_manual_mode() |
| 244 | |
| 245 | |
| 246 | class EInstrumentChamber(OtaChamber): |
| 247 | """Class that implements Einstrument Chamber.""" |
| 248 | def __init__(self, config): |
| 249 | self.config = config.copy() |
| 250 | self.device_id = self.config['device_id'] |
| 251 | self.log = logger.create_tagged_trace_logger('EInstrumentChamber|{}'.format( |
| 252 | self.device_id)) |
| 253 | self.current_mode = None |
| 254 | self.ser = self._get_serial(config['port']) |
| 255 | |
| 256 | def _get_serial(self, port, baud=9600): |
| 257 | """Read com port. |
| 258 | |
| 259 | Args: |
| 260 | port: turn table com port |
| 261 | baud: baud rate |
| 262 | """ |
| 263 | ser = serial.Serial(port, baud) |
| 264 | return ser |
| 265 | |
| 266 | def set_orientation(self, orientation): |
| 267 | if int(orientation) > 360: |
| 268 | orientation = int(orientation) % 360 |
| 269 | elif int(orientation) < 0: |
| 270 | orientation = 0 |
| 271 | self.log.info('Setting orientation to {} degrees.'.format(orientation)) |
| 272 | orientation = str('DG') + str(orientation) + str(';') |
| 273 | self.ser.write(orientation.encode()) |
| 274 | return orientation |
| 275 | |
| 276 | def reset_chamber(self): |
| 277 | self.log.info('Resetting turn table to zero degree') |
| 278 | self.set_orientation(0) |