Home | History | Annotate | Download | only in orientationplot
      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.  See README.txt."
    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