1 #!/usr/bin/env python2.6 2 # 3 # Copyright (C) 2011 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 # 17 18 # 19 # Plots debug log output from WindowOrientationListener. 20 # See README.txt for details. 21 # 22 23 import numpy as np 24 import matplotlib.pyplot as plot 25 import subprocess 26 import re 27 import fcntl 28 import os 29 import errno 30 import bisect 31 from datetime import datetime, timedelta 32 33 # Parameters. 34 timespan = 15 # seconds total span shown 35 scrolljump = 5 # seconds jump when scrolling 36 timeticks = 1 # seconds between each time tick 37 38 # Non-blocking stream wrapper. 39 class NonBlockingStream: 40 def __init__(self, stream): 41 fcntl.fcntl(stream, fcntl.F_SETFL, os.O_NONBLOCK) 42 self.stream = stream 43 self.buffer = '' 44 self.pos = 0 45 46 def readline(self): 47 while True: 48 index = self.buffer.find('\n', self.pos) 49 if index != -1: 50 result = self.buffer[self.pos:index] 51 self.pos = index + 1 52 return result 53 54 self.buffer = self.buffer[self.pos:] 55 self.pos = 0 56 try: 57 chunk = os.read(self.stream.fileno(), 4096) 58 except OSError, e: 59 if e.errno == errno.EAGAIN: 60 return None 61 raise e 62 if len(chunk) == 0: 63 if len(self.buffer) == 0: 64 raise(EOFError) 65 else: 66 result = self.buffer 67 self.buffer = '' 68 self.pos = 0 69 return result 70 self.buffer += chunk 71 72 # Plotter 73 class Plotter: 74 def __init__(self, adbout): 75 self.adbout = adbout 76 77 self.fig = plot.figure(1) 78 self.fig.suptitle('Window Orientation Listener', fontsize=12) 79 self.fig.set_dpi(96) 80 self.fig.set_size_inches(16, 12, forward=True) 81 82 self.raw_acceleration_x = self._make_timeseries() 83 self.raw_acceleration_y = self._make_timeseries() 84 self.raw_acceleration_z = self._make_timeseries() 85 self.raw_acceleration_magnitude = self._make_timeseries() 86 self.raw_acceleration_axes = self._add_timeseries_axes( 87 1, 'Raw Acceleration', 'm/s^2', [-20, 20], 88 yticks=range(-15, 16, 5)) 89 self.raw_acceleration_line_x = self._add_timeseries_line( 90 self.raw_acceleration_axes, 'x', 'red') 91 self.raw_acceleration_line_y = self._add_timeseries_line( 92 self.raw_acceleration_axes, 'y', 'green') 93 self.raw_acceleration_line_z = self._add_timeseries_line( 94 self.raw_acceleration_axes, 'z', 'blue') 95 self.raw_acceleration_line_magnitude = self._add_timeseries_line( 96 self.raw_acceleration_axes, 'magnitude', 'orange', linewidth=2) 97 self._add_timeseries_legend(self.raw_acceleration_axes) 98 99 shared_axis = self.raw_acceleration_axes 100 101 self.filtered_acceleration_x = self._make_timeseries() 102 self.filtered_acceleration_y = self._make_timeseries() 103 self.filtered_acceleration_z = self._make_timeseries() 104 self.filtered_acceleration_magnitude = self._make_timeseries() 105 self.filtered_acceleration_axes = self._add_timeseries_axes( 106 2, 'Filtered Acceleration', 'm/s^2', [-20, 20], 107 sharex=shared_axis, 108 yticks=range(-15, 16, 5)) 109 self.filtered_acceleration_line_x = self._add_timeseries_line( 110 self.filtered_acceleration_axes, 'x', 'red') 111 self.filtered_acceleration_line_y = self._add_timeseries_line( 112 self.filtered_acceleration_axes, 'y', 'green') 113 self.filtered_acceleration_line_z = self._add_timeseries_line( 114 self.filtered_acceleration_axes, 'z', 'blue') 115 self.filtered_acceleration_line_magnitude = self._add_timeseries_line( 116 self.filtered_acceleration_axes, 'magnitude', 'orange', linewidth=2) 117 self._add_timeseries_legend(self.filtered_acceleration_axes) 118 119 self.tilt_angle = self._make_timeseries() 120 self.tilt_angle_axes = self._add_timeseries_axes( 121 3, 'Tilt Angle', 'degrees', [-105, 105], 122 sharex=shared_axis, 123 yticks=range(-90, 91, 30)) 124 self.tilt_angle_line = self._add_timeseries_line( 125 self.tilt_angle_axes, 'tilt', 'black') 126 self._add_timeseries_legend(self.tilt_angle_axes) 127 128 self.orientation_angle = self._make_timeseries() 129 self.orientation_angle_axes = self._add_timeseries_axes( 130 4, 'Orientation Angle', 'degrees', [-25, 375], 131 sharex=shared_axis, 132 yticks=range(0, 361, 45)) 133 self.orientation_angle_line = self._add_timeseries_line( 134 self.orientation_angle_axes, 'orientation', 'black') 135 self._add_timeseries_legend(self.orientation_angle_axes) 136 137 self.current_rotation = self._make_timeseries() 138 self.proposed_rotation = self._make_timeseries() 139 self.predicted_rotation = self._make_timeseries() 140 self.orientation_axes = self._add_timeseries_axes( 141 5, 'Current / Proposed Orientation', 'rotation', [-1, 4], 142 sharex=shared_axis, 143 yticks=range(0, 4)) 144 self.current_rotation_line = self._add_timeseries_line( 145 self.orientation_axes, 'current', 'black', linewidth=2) 146 self.predicted_rotation_line = self._add_timeseries_line( 147 self.orientation_axes, 'predicted', 'purple', linewidth=3) 148 self.proposed_rotation_line = self._add_timeseries_line( 149 self.orientation_axes, 'proposed', 'green', linewidth=3) 150 self._add_timeseries_legend(self.orientation_axes) 151 152 self.time_until_settled = self._make_timeseries() 153 self.time_until_flat_delay_expired = self._make_timeseries() 154 self.time_until_swing_delay_expired = self._make_timeseries() 155 self.time_until_acceleration_delay_expired = self._make_timeseries() 156 self.stability_axes = self._add_timeseries_axes( 157 6, 'Proposal Stability', 'ms', [-10, 600], 158 sharex=shared_axis, 159 yticks=range(0, 600, 100)) 160 self.time_until_settled_line = self._add_timeseries_line( 161 self.stability_axes, 'time until settled', 'black', linewidth=2) 162 self.time_until_flat_delay_expired_line = self._add_timeseries_line( 163 self.stability_axes, 'time until flat delay expired', 'green') 164 self.time_until_swing_delay_expired_line = self._add_timeseries_line( 165 self.stability_axes, 'time until swing delay expired', 'blue') 166 self.time_until_acceleration_delay_expired_line = self._add_timeseries_line( 167 self.stability_axes, 'time until acceleration delay expired', 'red') 168 self._add_timeseries_legend(self.stability_axes) 169 170 self.sample_latency = self._make_timeseries() 171 self.sample_latency_axes = self._add_timeseries_axes( 172 7, 'Accelerometer Sampling Latency', 'ms', [-10, 500], 173 sharex=shared_axis, 174 yticks=range(0, 500, 100)) 175 self.sample_latency_line = self._add_timeseries_line( 176 self.sample_latency_axes, 'latency', 'black') 177 self._add_timeseries_legend(self.sample_latency_axes) 178 179 self.fig.canvas.mpl_connect('button_press_event', self._on_click) 180 self.paused = False 181 182 self.timer = self.fig.canvas.new_timer(interval=100) 183 self.timer.add_callback(lambda: self.update()) 184 self.timer.start() 185 186 self.timebase = None 187 self._reset_parse_state() 188 189 # Handle a click event to pause or restart the timer. 190 def _on_click(self, ev): 191 if not self.paused: 192 self.paused = True 193 self.timer.stop() 194 else: 195 self.paused = False 196 self.timer.start() 197 198 # Initialize a time series. 199 def _make_timeseries(self): 200 return [[], []] 201 202 # Add a subplot to the figure for a time series. 203 def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None): 204 num_graphs = 7 205 height = 0.9 / num_graphs 206 top = 0.95 - height * index 207 axes = self.fig.add_axes([0.1, top, 0.8, height], 208 xscale='linear', 209 xlim=[0, timespan], 210 ylabel=ylabel, 211 yscale='linear', 212 ylim=ylim, 213 sharex=sharex) 214 axes.text(0.02, 0.02, title, transform=axes.transAxes, fontsize=10, fontweight='bold') 215 axes.set_xlabel('time (s)', fontsize=10, fontweight='bold') 216 axes.set_ylabel(ylabel, fontsize=10, fontweight='bold') 217 axes.set_xticks(range(0, timespan + 1, timeticks)) 218 axes.set_yticks(yticks) 219 axes.grid(True) 220 221 for label in axes.get_xticklabels(): 222 label.set_fontsize(9) 223 for label in axes.get_yticklabels(): 224 label.set_fontsize(9) 225 226 return axes 227 228 # Add a line to the axes for a time series. 229 def _add_timeseries_line(self, axes, label, color, linewidth=1): 230 return axes.plot([], label=label, color=color, linewidth=linewidth)[0] 231 232 # Add a legend to a time series. 233 def _add_timeseries_legend(self, axes): 234 axes.legend( 235 loc='upper left', 236 bbox_to_anchor=(1.01, 1), 237 borderpad=0.1, 238 borderaxespad=0.1, 239 prop={'size': 10}) 240 241 # Resets the parse state. 242 def _reset_parse_state(self): 243 self.parse_raw_acceleration_x = None 244 self.parse_raw_acceleration_y = None 245 self.parse_raw_acceleration_z = None 246 self.parse_raw_acceleration_magnitude = None 247 self.parse_filtered_acceleration_x = None 248 self.parse_filtered_acceleration_y = None 249 self.parse_filtered_acceleration_z = None 250 self.parse_filtered_acceleration_magnitude = None 251 self.parse_tilt_angle = None 252 self.parse_orientation_angle = None 253 self.parse_current_rotation = None 254 self.parse_proposed_rotation = None 255 self.parse_predicted_rotation = None 256 self.parse_time_until_settled = None 257 self.parse_time_until_flat_delay_expired = None 258 self.parse_time_until_swing_delay_expired = None 259 self.parse_time_until_acceleration_delay_expired = None 260 self.parse_sample_latency = None 261 262 # Update samples. 263 def update(self): 264 timeindex = 0 265 while True: 266 try: 267 line = self.adbout.readline() 268 except EOFError: 269 plot.close() 270 return 271 if line is None: 272 break 273 print line 274 275 try: 276 timestamp = self._parse_timestamp(line) 277 except ValueError, e: 278 continue 279 if self.timebase is None: 280 self.timebase = timestamp 281 delta = timestamp - self.timebase 282 timeindex = delta.seconds + delta.microseconds * 0.000001 283 284 if line.find('Raw acceleration vector:') != -1: 285 self.parse_raw_acceleration_x = self._get_following_number(line, 'x=') 286 self.parse_raw_acceleration_y = self._get_following_number(line, 'y=') 287 self.parse_raw_acceleration_z = self._get_following_number(line, 'z=') 288 self.parse_raw_acceleration_magnitude = self._get_following_number(line, 'magnitude=') 289 290 if line.find('Filtered acceleration vector:') != -1: 291 self.parse_filtered_acceleration_x = self._get_following_number(line, 'x=') 292 self.parse_filtered_acceleration_y = self._get_following_number(line, 'y=') 293 self.parse_filtered_acceleration_z = self._get_following_number(line, 'z=') 294 self.parse_filtered_acceleration_magnitude = self._get_following_number(line, 'magnitude=') 295 296 if line.find('tiltAngle=') != -1: 297 self.parse_tilt_angle = self._get_following_number(line, 'tiltAngle=') 298 299 if line.find('orientationAngle=') != -1: 300 self.parse_orientation_angle = self._get_following_number(line, 'orientationAngle=') 301 302 if line.find('Result:') != -1: 303 self.parse_current_rotation = self._get_following_number(line, 'currentRotation=') 304 self.parse_proposed_rotation = self._get_following_number(line, 'proposedRotation=') 305 self.parse_predicted_rotation = self._get_following_number(line, 'predictedRotation=') 306 self.parse_sample_latency = self._get_following_number(line, 'timeDeltaMS=') 307 self.parse_time_until_settled = self._get_following_number(line, 'timeUntilSettledMS=') 308 self.parse_time_until_flat_delay_expired = self._get_following_number(line, 'timeUntilFlatDelayExpiredMS=') 309 self.parse_time_until_swing_delay_expired = self._get_following_number(line, 'timeUntilSwingDelayExpiredMS=') 310 self.parse_time_until_acceleration_delay_expired = self._get_following_number(line, 'timeUntilAccelerationDelayExpiredMS=') 311 312 self._append(self.raw_acceleration_x, timeindex, self.parse_raw_acceleration_x) 313 self._append(self.raw_acceleration_y, timeindex, self.parse_raw_acceleration_y) 314 self._append(self.raw_acceleration_z, timeindex, self.parse_raw_acceleration_z) 315 self._append(self.raw_acceleration_magnitude, timeindex, self.parse_raw_acceleration_magnitude) 316 self._append(self.filtered_acceleration_x, timeindex, self.parse_filtered_acceleration_x) 317 self._append(self.filtered_acceleration_y, timeindex, self.parse_filtered_acceleration_y) 318 self._append(self.filtered_acceleration_z, timeindex, self.parse_filtered_acceleration_z) 319 self._append(self.filtered_acceleration_magnitude, timeindex, self.parse_filtered_acceleration_magnitude) 320 self._append(self.tilt_angle, timeindex, self.parse_tilt_angle) 321 self._append(self.orientation_angle, timeindex, self.parse_orientation_angle) 322 self._append(self.current_rotation, timeindex, self.parse_current_rotation) 323 if self.parse_proposed_rotation >= 0: 324 self._append(self.proposed_rotation, timeindex, self.parse_proposed_rotation) 325 else: 326 self._append(self.proposed_rotation, timeindex, None) 327 if self.parse_predicted_rotation >= 0: 328 self._append(self.predicted_rotation, timeindex, self.parse_predicted_rotation) 329 else: 330 self._append(self.predicted_rotation, timeindex, None) 331 self._append(self.time_until_settled, timeindex, self.parse_time_until_settled) 332 self._append(self.time_until_flat_delay_expired, timeindex, self.parse_time_until_flat_delay_expired) 333 self._append(self.time_until_swing_delay_expired, timeindex, self.parse_time_until_swing_delay_expired) 334 self._append(self.time_until_acceleration_delay_expired, timeindex, self.parse_time_until_acceleration_delay_expired) 335 self._append(self.sample_latency, timeindex, self.parse_sample_latency) 336 self._reset_parse_state() 337 338 # Scroll the plots. 339 if timeindex > timespan: 340 bottom = int(timeindex) - timespan + scrolljump 341 self.timebase += timedelta(seconds=bottom) 342 self._scroll(self.raw_acceleration_x, bottom) 343 self._scroll(self.raw_acceleration_y, bottom) 344 self._scroll(self.raw_acceleration_z, bottom) 345 self._scroll(self.raw_acceleration_magnitude, bottom) 346 self._scroll(self.filtered_acceleration_x, bottom) 347 self._scroll(self.filtered_acceleration_y, bottom) 348 self._scroll(self.filtered_acceleration_z, bottom) 349 self._scroll(self.filtered_acceleration_magnitude, bottom) 350 self._scroll(self.tilt_angle, bottom) 351 self._scroll(self.orientation_angle, bottom) 352 self._scroll(self.current_rotation, bottom) 353 self._scroll(self.proposed_rotation, bottom) 354 self._scroll(self.predicted_rotation, bottom) 355 self._scroll(self.time_until_settled, bottom) 356 self._scroll(self.time_until_flat_delay_expired, bottom) 357 self._scroll(self.time_until_swing_delay_expired, bottom) 358 self._scroll(self.time_until_acceleration_delay_expired, bottom) 359 self._scroll(self.sample_latency, bottom) 360 361 # Redraw the plots. 362 self.raw_acceleration_line_x.set_data(self.raw_acceleration_x) 363 self.raw_acceleration_line_y.set_data(self.raw_acceleration_y) 364 self.raw_acceleration_line_z.set_data(self.raw_acceleration_z) 365 self.raw_acceleration_line_magnitude.set_data(self.raw_acceleration_magnitude) 366 self.filtered_acceleration_line_x.set_data(self.filtered_acceleration_x) 367 self.filtered_acceleration_line_y.set_data(self.filtered_acceleration_y) 368 self.filtered_acceleration_line_z.set_data(self.filtered_acceleration_z) 369 self.filtered_acceleration_line_magnitude.set_data(self.filtered_acceleration_magnitude) 370 self.tilt_angle_line.set_data(self.tilt_angle) 371 self.orientation_angle_line.set_data(self.orientation_angle) 372 self.current_rotation_line.set_data(self.current_rotation) 373 self.proposed_rotation_line.set_data(self.proposed_rotation) 374 self.predicted_rotation_line.set_data(self.predicted_rotation) 375 self.time_until_settled_line.set_data(self.time_until_settled) 376 self.time_until_flat_delay_expired_line.set_data(self.time_until_flat_delay_expired) 377 self.time_until_swing_delay_expired_line.set_data(self.time_until_swing_delay_expired) 378 self.time_until_acceleration_delay_expired_line.set_data(self.time_until_acceleration_delay_expired) 379 self.sample_latency_line.set_data(self.sample_latency) 380 381 self.fig.canvas.draw_idle() 382 383 # Scroll a time series. 384 def _scroll(self, timeseries, bottom): 385 bottom_index = bisect.bisect_left(timeseries[0], bottom) 386 del timeseries[0][:bottom_index] 387 del timeseries[1][:bottom_index] 388 for i, timeindex in enumerate(timeseries[0]): 389 timeseries[0][i] = timeindex - bottom 390 391 # Extract a word following the specified prefix. 392 def _get_following_word(self, line, prefix): 393 prefix_index = line.find(prefix) 394 if prefix_index == -1: 395 return None 396 start_index = prefix_index + len(prefix) 397 delim_index = line.find(',', start_index) 398 if delim_index == -1: 399 return line[start_index:] 400 else: 401 return line[start_index:delim_index] 402 403 # Extract a number following the specified prefix. 404 def _get_following_number(self, line, prefix): 405 word = self._get_following_word(line, prefix) 406 if word is None: 407 return None 408 return float(word) 409 410 # Extract an array of numbers following the specified prefix. 411 def _get_following_array_of_numbers(self, line, prefix): 412 prefix_index = line.find(prefix + '[') 413 if prefix_index == -1: 414 return None 415 start_index = prefix_index + len(prefix) + 1 416 delim_index = line.find(']', start_index) 417 if delim_index == -1: 418 return None 419 420 result = [] 421 while start_index < delim_index: 422 comma_index = line.find(', ', start_index, delim_index) 423 if comma_index == -1: 424 result.append(float(line[start_index:delim_index])) 425 break; 426 result.append(float(line[start_index:comma_index])) 427 start_index = comma_index + 2 428 return result 429 430 # Add a value to a time series. 431 def _append(self, timeseries, timeindex, number): 432 timeseries[0].append(timeindex) 433 timeseries[1].append(number) 434 435 # Parse the logcat timestamp. 436 # Timestamp has the form '01-21 20:42:42.930' 437 def _parse_timestamp(self, line): 438 return datetime.strptime(line[0:18], '%m-%d %H:%M:%S.%f') 439 440 # Notice 441 print "Window Orientation Listener plotting tool" 442 print "-----------------------------------------\n" 443 print "Please turn on the Window Orientation Listener logging in Development Settings." 444 445 # Start adb. 446 print "Starting adb logcat.\n" 447 448 adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'WindowOrientationListener:V'], 449 stdout=subprocess.PIPE) 450 adbout = NonBlockingStream(adb.stdout) 451 452 # Prepare plotter. 453 plotter = Plotter(adbout) 454 plotter.update() 455 456 # Main loop. 457 plot.show() 458