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 23import numpy as np 24import matplotlib.pyplot as plot 25import subprocess 26import re 27import fcntl 28import os 29import errno 30import bisect 31from datetime import datetime, timedelta 32 33# Parameters. 34timespan = 15 # seconds total span shown 35scrolljump = 5 # seconds jump when scrolling 36timeticks = 1 # seconds between each time tick 37 38# Non-blocking stream wrapper. 39class 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 73class 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 441print "Window Orientation Listener plotting tool" 442print "-----------------------------------------\n" 443print "Please turn on the Window Orientation Listener logging. See README.txt." 444 445# Start adb. 446print "Starting adb logcat.\n" 447 448adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'WindowOrientationListener:V'], 449 stdout=subprocess.PIPE) 450adbout = NonBlockingStream(adb.stdout) 451 452# Prepare plotter. 453plotter = Plotter(adbout) 454plotter.update() 455 456# Main loop. 457plot.show() 458