Skip to content

Commit

Permalink
Merge 6edf3e4 into 79f87e4
Browse files Browse the repository at this point in the history
  • Loading branch information
mossblaser committed Jan 22, 2016
2 parents 79f87e4 + 6edf3e4 commit e95fc11
Show file tree
Hide file tree
Showing 7 changed files with 921 additions and 43 deletions.
69 changes: 66 additions & 3 deletions docs/source/spinner-wiring-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ machine and correcting wiring errors.

$ spinner-wiring-guide -h
usage: spinner-wiring-guide [-h] [--version] [--no-tts] [--no-auto-advance]
[--fix] (--num-boards N | --triads W H)
[--fix] [--log LOGFILE]
(--num-boards N | --triads W H)
[--transformation {shear,slice}]
[--uncrinkle-direction {columns,rows}]
[--folds X Y] [--board-dimensions W H D]
Expand Down Expand Up @@ -41,6 +42,7 @@ machine and correcting wiring errors.
--no-auto-advance disable auto-advancing through wiring steps
--fix detect errors in existing wiring and just show
corrective steps
--log LOGFILE record the times at which each cable is installed
machine topology dimensions:
--num-boards N, -n N build the 'squarest' system with this many boards
Expand Down Expand Up @@ -103,14 +105,14 @@ machine and correcting wiring errors.
frame in meters (default: (0.06, 0.017, 0.0))
--inter-frame-spacing S
physical spacing between frames in a cabinet in meters
(default: 0.089)
(default: 0.133)
cabinet physical dimensions:
--frames-per-cabinet FRAMES_PER_CABINET
number of frames per cabinet (default: 5)
--cabinet-dimensions W H D
cabinet physical dimensions in meters (default: (0.6,
1.822, 0.25))
2.0, 0.25))
--cabinet-frame-offset X Y Z
physical offset of the left-top-front corner of the
top frame from the left-top-front corner of a cabinet
Expand Down Expand Up @@ -189,6 +191,7 @@ Go to first wire Home
Go to last wire End
Toggle Text-to-Speech t
Toggle Auto-Advance a
Pause logging p
============================ ==========================

Future versions of this tool hope to include the ability to organise multiple
Expand Down Expand Up @@ -233,3 +236,63 @@ Adding the ``--fix`` option will check all installed wires in the machine and
guide you through any corrections which must be made::

$ spinner-wiring-guide -n 24 -l 0.15 0.30 0.50 1.00 --bmp 0 0 BMP_HOSTNAME --fix

Logging
-------

The ``--log FILENAME`` argument causes the wiring guide to log (into a CSV
file) how long it took to install each ceable. This may be useful for research
comparing ease of installation and maintainance of a SpiNNaker system. Note
that this system does not currently log cables being removed.

The CSV file contains the set of columns defined below. Various types of events
are recorded in the log and not every event has a sensible value for every
column. Columns without a sensible value are set to NA.

:event_type:
The type of event being logged (see list below).

:realtime:
The real time and date the event occurred.

:time:
Time that the event occurred, in seconds since the start of logging and
excluding any time spent paused.

:sc, sf, sb, sd, dc, df, db, dd:
Source and destination cabinet, frame, board and direction of a cable being
installed.

:duration:
Overall time, in seconds, to connect a cable correctly (or time spent paused for pause
events).

:attempt_duration:
Time since last attempt to connect the cable, in seconds.

:num_attempts:
Number of attempts made to install the current cable.

The following event types are defined:

:logging_started:
This event is produced when a new wiring session begins. All relative times
are measured in seconds from this point.

:logging_stopped:
Produced when logging ceases.

:connection_started:
Produced when a new cable to install is displayed on the screen.

:connection_error:
Produced each time a cable is connected incorrectly according the the wiring
probe.

:connection_complete:
Produced when the wiring probe detects that the cable has been installed
correctly.

:pause:
Produced *after* logging has been paused for some period of time. Relative
timings reported by other events will not include any time spent paused.
161 changes: 136 additions & 25 deletions spinner/diagrams/interactive_wiring_guide.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@

import subprocess

from six import iteritems, itervalues

import cairocffi as cairo

from math import pi

try: # pragma: no cover
# Python 3
from tkinter import Tk, Label
Expand Down Expand Up @@ -59,15 +63,16 @@ def __init__( self
, cabinet
, wire_lengths
, wires
, starting_wire = 0
, starting_wire=0
, focus=[]
, bmp_controller = None
, bmp_led = 7
, bmp_controller=None
, bmp_led=7
, wiring_probe=None
, auto_advance=True
, use_tts = True
, show_installed_wires = True
, show_future_wires = False
, use_tts=True
, show_installed_wires=True
, show_future_wires=False
, timing_logger=None
):
"""
cabinet defines the size of cabinets in the system.
Expand Down Expand Up @@ -104,6 +109,9 @@ def __init__( self
show_future_wires selects whether to-be-installed wires should be shown
(feintly) at all times.
timing_logger is a TimingLogger into which the cabling connections made
will be logged. If None, no timings are logged.
"""

self.cabinet = cabinet
Expand All @@ -128,17 +136,25 @@ def __init__( self
self.show_installed_wires = show_installed_wires
self.show_future_wires = show_future_wires

self.timing_logger = timing_logger

# Human readable names for each socket
self.socket_names = {d: d.name.replace("_", " ") for d in Direction}

# A reference to any running TTS job
self.tts_process = None

# A dict {fn: ([key, ...], description), ...} giving the keybindings and
# homan-readable help string for all bound keyboard shortcuts.
self.bindings = {}

# Set up the Tk UI
self.tk = Tk()
self._init_ui()

# Get started
if self.timing_logger is not None:
self.timing_logger.logging_started()
self.go_to_wire(starting_wire)


Expand Down Expand Up @@ -167,22 +183,31 @@ def _init_ui(self):
self.tk.protocol("WM_DELETE_WINDOW", self._on_close) # Window closed

# Setup key bindings
self.tk.bind("<Button-1>", self._on_next)
for key in "space Down Right Return Tab".split():
self.tk.bind("<Key-{}>".format(key), self._on_next)

self.tk.bind("<Button-3>", self._on_prev)
for key in "Up Left BackSpace".split():
self.tk.bind("<Key-{}>".format(key), self._on_prev)

self.tk.bind("<Key-Next>".format(key), self._on_skip_next) # PgDown
self.tk.bind("<Key-Prior>".format(key), self._on_skip_prev) # PgUp

self.tk.bind("<Key-Home>".format(key), self._on_first)
self.tk.bind("<Key-End>".format(key), self._on_last)

self.tk.bind("<Key-t>".format(key), self._on_tts_toggle)
self.tk.bind("<Key-a>".format(key), self._on_auto_advance_toggle)
self.bindings = {
self._on_next:
(["<Button-1>", "<Key-space>", "<Key-Down>", "<Key-Right>",
"<Key-Return>", "<Key-Tab>"],
"Next wire"),
self._on_prev:
(["<Button-3>", "<Key-Up>", "<Key-Left>", "<Key-BackSpace>"],
"Previous wire"),
self._on_skip_next: (["<Key-Next>"], "Skip forward"),
self._on_skip_prev: (["<Key-Prior>"], "Skip backward"),
self._on_first: (["<Key-Home>"], "First wire"),
self._on_last: (["<Key-End>"], "Last wire"),
self._on_tts_toggle: (["<Key-t>"], "Toggle speech"),
}

if self.wiring_probe is not None: # pragma: no branch
self.bindings[self._on_auto_advance_toggle] = (
["<Key-a>"], "Toggle auto-advance")

if self.timing_logger is not None: # pragma: no branch
self.bindings[self._on_pause] = (["<Key-p>"], "Pause")

for fn, (keys, help) in iteritems(self.bindings):
for key in keys:
self.tk.bind(key, fn)


def go_to_wire(self, wire):
Expand All @@ -199,6 +224,16 @@ def go_to_wire(self, wire):
self.set_leds(last_wire, False)
self.set_leds(self.cur_wire, True)

# Log the start of *insertion* of a new wire
src, dst, length = self.wires[wire]
if self.timing_logger is not None:
self.timing_logger.unpause()
if length is not None:
sc, sf, sb, sd = self.wires[wire][0]
dc, df, db, dd = self.wires[wire][1]
self.timing_logger.connection_started(sc, sf, sb, sd,
dc, df, db, dd)

# Announce via TTS the distance relative to the last position
if self.use_tts:
self.tts_delta(last_wire, self.cur_wire)
Expand Down Expand Up @@ -357,7 +392,37 @@ def _draw_text(self, ctx, text, size, rgba = (0.0,0.0,0.0, 1.0)):
ctx.show_text(text)

ctx.restore()


def _draw_help_text(self, ctx, width, height, rgba = (0.5,0.5,0.5, 1.0)):
"""
Draw the help text along the bottom of the screen, return the height of the
text in pixels.
"""
# Generate help string
help_text = " | ".join("{} {}".format(keys[0], help)
for keys, help in
sorted(itervalues(self.bindings),
key=(lambda kh: kh[0])))

ctx.save()

# Determine font size which will fill the width of the screen
ctx.select_font_face("Sans")
ctx.set_source_rgba(*rgba)
ctx.set_font_size(1.0)
x,y, w,h, _w,_h = ctx.text_extents(help_text)
scale_factor = (width * 0.95) / w

# Draw the text along the bottom of the screen
ctx.set_font_size(scale_factor)
x,y, w,h, _w,_h = ctx.text_extents(help_text)
ctx.move_to(x + ((width - w) / 2), height + y)
ctx.show_text(help_text)

ctx.restore()

return h


def _render_gui(self, ctx, width, height):
Expand All @@ -369,6 +434,9 @@ def _render_gui(self, ctx, width, height):
ctx.rectangle(0,0, width, height)
ctx.fill()

# Draw help text along bottom of screen.
height -= self._draw_help_text(ctx, width, height)

md = self._get_machine_diagram()

# Draw the main overview image
Expand Down Expand Up @@ -460,6 +528,33 @@ def _render_gui(self, ctx, width, height):
, height*self.TEXT_ROW_HEIGHT
)
ctx.restore()

# Draw a full-screen "paused" indicator, if paused
if self.timing_logger is not None and self.timing_logger.paused:
ctx.save()

ctx.translate(width / 2.0, height / 2.0)
scale = min(width, height) * 0.4
ctx.scale(scale, scale)

ctx.move_to(0, 0)
ctx.new_sub_path()
ctx.arc(0, 0, 1.0, 0.0, 2.0 * pi)
ctx.set_source_rgba(0.0,0.0,0.0,0.8);
ctx.fill_preserve()
ctx.set_source_rgba(0.0,0.0,0.0,1.0);
ctx.set_line_width(0.1)
ctx.stroke()

ctx.move_to(-0.25, -0.5);
ctx.rel_line_to(0.0, 1.0);
ctx.move_to(0.25, -0.5);
ctx.rel_line_to(0.0, 1.0);
ctx.set_source_rgba(0.7,0.7,0.7,1.0);
ctx.set_line_width(0.2)
ctx.stroke()

ctx.restore()


def _redraw(self):
Expand Down Expand Up @@ -504,10 +599,16 @@ def _poll_wiring_probe(self):
if actual_src == src and actual_dst == dst:
# Connected correctly! Advance to the next wire!
advance = True
if self.timing_logger is not None:
self.timing_logger.unpause()
self.timing_logger.connection_complete()
elif actual_dst is not None or actual_src is not None:
# The wire was connected, but was connected incorrectly!
if not self.connected_incorrectly:
self._tts_speak("Wire inserted incorrectly.")
if self.timing_logger is not None:
self.timing_logger.unpause()
self.timing_logger.connection_error()
self.connected_incorrectly = True
else:
# No wire is connected
Expand Down Expand Up @@ -579,6 +680,16 @@ def _on_auto_advance_toggle(self, event):
self._tts_speak("Auto advance not supported.")


def _on_pause(self, event):
"""Toggle whether timings are being recorded."""
if self.timing_logger is not None:
if self.timing_logger.paused:
self.timing_logger.unpause()
else:
self.timing_logger.pause()
self._redraw()


def _on_resize(self, event):
"""Window has been resized, trigger a redraw."""
new_size = (event.width, event.height)
Expand All @@ -590,6 +701,9 @@ def _on_resize(self, event):

def _on_close(self, event=None):
"""The window has been closed."""
if self.timing_logger is not None:
self.timing_logger.logging_stopped()

# Turn off LEDs before leaving
self.set_leds(self.cur_wire, False)
self.tk.destroy()
Expand All @@ -599,6 +713,3 @@ def mainloop(self): # pragma: no cover
"""Start the interactive wiring guide GUI. Returns when the window is
closed."""
return self.tk.mainloop()



0 comments on commit e95fc11

Please sign in to comment.