#!/usr/bin/env python import datetime import dics import gamin import logging import re import tc08 import time import matplotlib matplotlib.use('GTKAgg') from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as \ FigureCanvas from matplotlib.backends.backend_gtk import NavigationToolbar2GTK as \ NavigationToolbar from matplotlib.axes import Subplot from matplotlib.figure import Figure import pygtk pygtk.require('2.0') import gobject import gtk class gui: """ First stab at a gui element. To begin with it's just a temperature monitor display. The GTK main loop will block, so to retain ability to use the python console we'll want to make this a seperate thread at some point. """ colours = ('b', 'r', 'c', 'm', 'y', 'k') styles = ('-', '--', '-.', ':') # This callback quits the program def delete_event(self, widget, event, data=None): gtk.main_quit() return False def __init__(self, instrument): """ Initialises the gui. Takes a dics.instrument object as an argument, which it scans through for tc08 instances from which it polls temperatures for display. """ if not isinstance(instrument, dics.instrument): raise TypeError('Expected dics.instrument, got %s!' % \ type(instrument)) self.logger = logging.getLogger('gui') # Need these in order to parse the date/times. self.pattern = re.compile(\ r'(?:(?<=:\d{2})\s+)|(?:(?<=\.\d{2})\s+(?!$))') self.timefmt = '%d%b%y %H:%M:%S' # Now, look through instrument collecting references to tc08's. self.tc08s = [value for value in instrument.__dict__.values() if \ isinstance(value, tc08.tc08)] # Create a window and set the title. self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) self.window.set_title('DICS - Temperature monitor') self.window.set_default_size(700,500) # Set a handler for delete_event signal that immediately exits GTK. self.window.connect("delete_event", self.delete_event) # Create an hbox, and add it to the window. self.hbox = gtk.HBox(homogeneous=False, spacing=10) self.window.add(self.hbox) # Create a table to hold the live data. num_rows = sum([len(temp_logger.getNames()) for temp_logger in \ self.tc08s]) self.table = gtk.Table(num_rows, 4, homogeneous=False) self.table.set_row_spacings(0) self.table.set_col_spacings(0) self.hbox.pack_start(self.table, expand=False, fill=False, \ padding=10) # Now run through the tc08 objects, adding name labels, # temperature labels and unit labels. self.labels_name = [] self.labels_temp = [] self.labels_unit = [] self.checks_plot = [] row = 0 for temp_logger in self.tc08s: for name in temp_logger.getNames(): label_name = gtk.Label(name) label_temp = gtk.Label('****.**') label_unit = gtk.Label(temp_logger.units) check_plot = gtk.CheckButton() self.table.attach(label_name, 0, 1, row, row + 1, \ xoptions=0, yoptions=0, \ xpadding=0, ypadding=0) self.table.attach(label_temp, 1, 2, row, row + 1, \ xoptions=0, yoptions=0, \ xpadding=0, ypadding=0) self.table.attach(label_unit, 2, 3, row, row + 1, \ xoptions=0, yoptions=0, \ xpadding=0, ypadding=0) self.table.attach(check_plot, 3, 4, row, row + 1, \ xoptions=0, yoptions=0, \ xpadding=0, ypadding=0) label_name.set_alignment(0,0.5) check_plot.set_active(True) label_name.show() label_temp.show() label_unit.show() check_plot.show() self.labels_name += [label_name] self.labels_temp += [label_temp] self.labels_unit += [label_unit] self.checks_plot += [check_plot] row += 1 # We also want a vbox for the plot and its toolbar to go into. self.vbox_plot = gtk.VBox(homogeneous=False, spacing=0) self.hbox.pack_start(self.vbox_plot, expand=True, fill=True, \ padding=0) # Get all teh data. self.logfiles = [] self.logs = [] for temp_logger in self.tc08s: filename = temp_logger.filename self.logfiles.append(filename) try: logfile = file(filename, 'r') except IOError: self.logger.warning("Couldn't open temperature logfile %s!" % \ filename) continue log = logfile.readlines() logfile.close() self.logs.append(self.parse_log(log)) # Create the initial plot based on the data in the log file self.figure = Figure(figsize=(5,4), dpi=100) self.axes = self.figure.add_subplot(111) # Create the plot in the self.axes object. self.make_plot() # Put the top level matplotlib bits in. self.canvas = FigureCanvas(self.figure) self.vbox_plot.pack_start(self.canvas, expand=True, fill=True, \ padding=0) self.toolbar = NavigationToolbar(self.canvas, self.window) self.vbox_plot.pack_start(self.toolbar, expand=False, fill=False, \ padding=0) self.canvas.show() self.toolbar.show() # Now show everything remaining. self.table.show() self.vbox_plot.show() self.hbox.show() self.window.show() def line_colour(self, number): """ Given an integer, returns a matplotlib line colour character. As the number is increased the line colour cycles through blue, green, red, cyan, magenta, yellow, black. """ index = int(number) % len(gui.colours) return gui.colours[index] def line_style(self, number): """ Given an integer, returns a matplotlib line style character. As the number is increased the line style cycles through solid line, dashed line, dot-dashed line and dotted line. """ index = int(number) % len(gui.styles) return gui.styles[index] def make_plot(self): # Plots the stuff. self.axes.clear() for index1 in range(0, len(self.tc08s)): log = self.logs[index1] temp_logger = self.tc08s[index1] names = temp_logger.getNames() style = self.line_style(index1) for index2 in range(1, len(log)): channel = log[index2] name = names[index2-1] colour = self.line_colour(index2-2) self.axes.plot_date(matplotlib.dates.date2num(log[0]), \ channel, colour + style, label=name) self.axes.legend(numpoints=2) self.axes.set_xlabel('Date/time') self.axes.set_ylabel('Temperature') def parse_log(self, temp_log): """ Parses a temperature log file, and returns a tuple containing a list of datetime.datetime objects and a one list of temperatures per channel. """ # Run the lines of the log through the regex splits = [self.pattern.split(line) for line in temp_log if \ line[0] != '#'] # Converting from a string to a datetime.datetime object is stupidly # awkward, have to use time.strptime to convert into a time.struct_time # but even that's not usable for constructing a datetime, first convert # to a POSIX timestamp using time.mktime. data = [\ [datetime.datetime.fromtimestamp(\ time.mktime(time.strptime(split[0], self.timefmt)))] + \ [float(temp) for temp in split[2:]] for split in splits] # Rearrange data = zip(*data) # Return the result return data def updateDisplay(self): """ Callback function that gets the current temperatures from the tc08 objects in self.tc08s and updates the temperature display. """ # Collect all the current temperature values. newTemps = [] for temp_logger in self.tc08s: newTemps += temp_logger.getTemps() # Now set the text of the corresponding labels. for label, temp in zip(self.labels_temp, newTemps): label.set_text('%7.2f' % temp) # return True so the timer continues. return True def main(self): # Set up the timeout to update the display. self.timer_id = gobject.timeout_add(5000, self.updateDisplay) gtk.main() # Need to do this so that the tc08's aren't multiply referenced, # and so will get their __del__ methods called when the program # is exited. del self.tc08s return 0