summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Seguin <guillaume@segu.in>2008-05-11 22:35:46 +0200
committerGuillaume Seguin <guillaume@segu.in>2008-05-11 22:35:46 +0200
commitf16f5de1fb7e13543214c4951a055e64cebf6fc4 (patch)
treeb55c47c48d40c9760488319c1a186d45ac0d8a81
parentce6dfd1e3fbac9852017ae81837c13c25ab53738 (diff)
downloadgnxtlogger-f16f5de1fb7e13543214c4951a055e64cebf6fc4.tar.gz
gnxtlogger-f16f5de1fb7e13543214c4951a055e64cebf6fc4.tar.bz2
* Initial import
-rw-r--r--gnxtlogger.glade274
-rwxr-xr-xgnxtlogger.py28
-rw-r--r--nxtlogger.pngbin0 -> 375 bytes
-rw-r--r--nxtlogger.xcfbin0 -> 2263 bytes
-rw-r--r--nxtlogger/__init__.py0
-rw-r--r--nxtlogger/chart.py78
-rw-r--r--nxtlogger/gui.py368
-rw-r--r--nxtlogger/logger.py102
8 files changed, 850 insertions, 0 deletions
diff --git a/gnxtlogger.glade b/gnxtlogger.glade
new file mode 100644
index 0000000..a38f52a
--- /dev/null
+++ b/gnxtlogger.glade
@@ -0,0 +1,274 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
+<!--Generated with glade3 3.4.2 on Sun May 11 21:48:19 2008 -->
+<glade-interface>
+ <widget class="GtkWindow" id="mainWindow">
+ <property name="title" translatable="yes">gNXTLogger</property>
+ <property name="window_position">GTK_WIN_POS_CENTER</property>
+ <property name="default_width">860</property>
+ <property name="default_height">525</property>
+ <property name="icon">nxtlogger.png</property>
+ <signal name="delete_event" handler="quit_cb"/>
+ <child>
+ <widget class="GtkVBox" id="mainBox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkToolbar" id="toolBar">
+ <property name="visible">True</property>
+ <property name="toolbar_style">GTK_TOOLBAR_ICONS</property>
+ <property name="icon_size">GTK_ICON_SIZE_SMALL_TOOLBAR</property>
+ <child>
+ <widget class="GtkToolButton" id="startButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-media-record</property>
+ <signal name="clicked" handler="start_cb"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="stopButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-stop</property>
+ <signal name="clicked" handler="stop_cb"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="saveButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-save</property>
+ <signal name="clicked" handler="save_cb"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="clearButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-clear</property>
+ <signal name="clicked" handler="clear_cb"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="aboutButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-about</property>
+ <signal name="clicked" handler="about_cb"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkToolButton" id="quitButton">
+ <property name="visible">True</property>
+ <property name="stock_id">gtk-quit</property>
+ <signal name="clicked" handler="quit_cb"/>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkHPaned" id="mainPaned">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="position">350</property>
+ <child>
+ <widget class="GtkScrolledWindow" id="dataWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <widget class="GtkTreeView" id="dataView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkNotebook" id="dataNotebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <widget class="GtkVBox" id="vbox1">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkTreeView" id="optionsView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkTreeView" id="chartColumnsView">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_clickable">True</property>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="optionsLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Options</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkVBox" id="chartBox">
+ <property name="visible">True</property>
+ <child>
+ <widget class="GtkHButtonBox" id="chartButtonBox">
+ <property name="visible">True</property>
+ <property name="spacing">30</property>
+ <property name="layout_style">GTK_BUTTONBOX_CENTER</property>
+ <child>
+ <widget class="GtkButton" id="drawChartButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Draw</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="draw_chart_cb"/>
+ <signal name="activate" handler="draw_chart_cb"/>
+ </widget>
+ </child>
+ <child>
+ <widget class="GtkButton" id="saveChartButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="label" translatable="yes">Save</property>
+ <property name="response_id">0</property>
+ <signal name="pressed" handler="save_chart_cb"/>
+ <signal name="activate" handler="save_chart_cb"/>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="padding">3</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkScrolledWindow" id="chartWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
+ <child>
+ <placeholder/>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkLabel" id="chartLabel">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Chart</property>
+ </widget>
+ <packing>
+ <property name="type">tab</property>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </widget>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <widget class="GtkStatusbar" id="statusBar">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+ <widget class="GtkAboutDialog" id="aboutDialog">
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">À propos de Glade</property>
+ <property name="resizable">False</property>
+ <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
+ <property name="icon">nxtlogger.png</property>
+ <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
+ <property name="has_separator">False</property>
+ <property name="program_name">gNXTLogger</property>
+ <property name="version">0.1</property>
+ <property name="copyright" translatable="yes">Copyright © 2008 Guillaume Seguin
+&lt;guillaume@segu.in&gt;</property>
+ <property name="comments" translatable="yes">Logger for LEJOS OSEK NXT bluetooth data feed</property>
+ <property name="logo">nxtlogger.png</property>
+ <signal name="delete_event" handler="about_close_cb"/>
+ <signal name="response" handler="about_close_cb"/>
+ <child internal-child="vbox">
+ <widget class="GtkVBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="spacing">2</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="action_area">
+ <widget class="GtkHButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="layout_style">GTK_BUTTONBOX_END</property>
+ </widget>
+ <packing>
+ <property name="expand">False</property>
+ <property name="pack_type">GTK_PACK_END</property>
+ </packing>
+ </child>
+ </widget>
+ </child>
+ </widget>
+</glade-interface>
diff --git a/gnxtlogger.py b/gnxtlogger.py
new file mode 100755
index 0000000..5b08c96
--- /dev/null
+++ b/gnxtlogger.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+# coding=utf-8
+
+'''
+Copyright (C) 2008 Guillaume Seguin <guillaume@segu.in>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Authors:
+ Guillaume Seguin <guillaume@segu.in>
+'''
+
+from nxtlogger.gui import GNXTLogger
+
+gui = GNXTLogger ()
+gui.run ()
diff --git a/nxtlogger.png b/nxtlogger.png
new file mode 100644
index 0000000..36ea6dc
--- /dev/null
+++ b/nxtlogger.png
Binary files differ
diff --git a/nxtlogger.xcf b/nxtlogger.xcf
new file mode 100644
index 0000000..97c06eb
--- /dev/null
+++ b/nxtlogger.xcf
Binary files differ
diff --git a/nxtlogger/__init__.py b/nxtlogger/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/nxtlogger/__init__.py
diff --git a/nxtlogger/chart.py b/nxtlogger/chart.py
new file mode 100644
index 0000000..b1576c1
--- /dev/null
+++ b/nxtlogger/chart.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+# coding=utf-8
+
+'''
+Copyright (C) 2008 Guillaume Seguin <guillaume@segu.in>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Authors:
+ Guillaume Seguin <guillaume@segu.in>
+'''
+
+import cairo
+
+import matplotlib
+matplotlib.use ('Cairo')
+from matplotlib.figure import Figure
+from matplotlib.backends.backend_cairo import FigureCanvasCairo, RendererCairo
+from matplotlib.numerix import asarray
+from matplotlib.ticker import Formatter
+
+class FigureCanvas (FigureCanvasCairo):
+
+ def print_surface (self, *args, **kwargs):
+ width, height = self.get_width_height()
+
+ renderer = RendererCairo (self.figure.dpi)
+ renderer.set_width_height (width, height)
+ surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, width, height)
+ renderer.set_ctx_from_surface (surface)
+
+ self.figure.draw (renderer)
+ return surface
+
+class TickFormatter (Formatter):
+
+ def __init__ (self, values):
+ self.values = values
+
+ def __call__ (self, tick, pos = 0):
+ try:
+ return round (float (self.values[int (tick)]) / 1000, 2)
+ except IndexError:
+ return ""
+
+DPI = 72
+COLORS = ["b", "r", "g"]
+
+def plot_data (y_data_sets, x_data, width, height):
+ size = (float (width) / DPI, float (height) / DPI)
+ fig = Figure (dpi = DPI, figsize = size)
+ subplot = fig.add_subplot (111, axisbg = "#EEEEEE")
+ formatter = TickFormatter (x_data)
+ subplot.xaxis.set_major_formatter (formatter)
+ subplot.grid (color = "#BBBBBB", linestyle = "-")
+
+ for i in range (len (y_data_sets)):
+ subplot.plot (asarray (y_data_sets[i][1]),
+ "-%s" % COLORS[i % len (COLORS)])
+
+ fig.autofmt_xdate ()
+
+ canvas = FigureCanvas (fig)
+ canvas.draw ()
+
+ return canvas.print_surface ()
diff --git a/nxtlogger/gui.py b/nxtlogger/gui.py
new file mode 100644
index 0000000..c2a9d27
--- /dev/null
+++ b/nxtlogger/gui.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python
+# coding=utf-8
+
+'''
+Copyright (C) 2008 Guillaume Seguin <guillaume@segu.in>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Authors:
+ Guillaume Seguin <guillaume@segu.in>
+'''
+
+import pygtk
+pygtk.require ('2.0')
+
+import gobject
+import gtk
+import gtk.glade
+import cairo
+gtk.gdk.threads_init ()
+
+import gettext
+import csv
+
+from nxtlogger.logger import BluetoothLogger
+from nxtlogger.chart import plot_data
+
+GLADE_FILE = "gnxtlogger.glade"
+GETTEXT_DOMAIN = "gnxtlogger"
+LOCALES_DIR = "po"
+gettext.install (GETTEXT_DOMAIN, LOCALES_DIR, unicode = True)
+
+CLOSE_RUNNING_MESSAGE = _("Data acquisition is still running, do you really want to quit anyway?")
+CLOSE_SAVE_DATA_MESSAGE = _("There is unsaved data remaining, would you like to save it before quitting?")
+OPTIONS = [
+ ("width", _("Width"), 500),
+ ("height", _("Height"), 400),
+]
+DATA_COLUMNS = [
+ (0, _("Time"), False),
+ (1, _("Data 1"), True),
+ (2, _("Data 2"), True),
+ (3, _("Battery"), False),
+ (4, _("Motor Rev A"), False),
+ (5, _("Motor Rev B"), False),
+ (6, _("Motor Rev C"), False),
+ (7, _("Sensor 1"), False),
+ (8, _("Sensor 2"), False),
+ (9, _("Sensor 3"), False),
+ (10, _("Sensor 4"), False),
+]
+
+def autoconnect (ob, glade):
+ '''Autoconnect signals of a Glade object to another object properties'''
+ handlers = {}
+ for i in dir (ob):
+ handlers[i] = getattr (ob, i)
+ glade.signal_autoconnect (handlers);
+
+def file_dialog (name, action, button, parent, filters = None):
+ '''Helper function for file chooser dialogs'''
+ dialog = gtk.FileChooserDialog (name, None, action,
+ (gtk.STOCK_CANCEL,
+ gtk.RESPONSE_CANCEL,
+ button,
+ gtk.RESPONSE_OK))
+ if filters and "png" in filters:
+ filter = gtk.FileFilter ()
+ filter.set_name (_("PNG files"))
+ filter.add_pattern ('*.png')
+ if filters and "csv" in filters:
+ filter = gtk.FileFilter ()
+ filter.set_name (_("CSV files"))
+ filter.add_pattern ('*.csv')
+ filter = gtk.FileFilter ()
+ filter.set_name (_("All files"))
+ filter.add_pattern ('*')
+ dialog.add_filter (filter)
+ dialog.set_default_response (gtk.RESPONSE_OK)
+ dialog.set_position (gtk.WIN_POS_CENTER)
+ dialog.set_icon (parent.get_icon ())
+ dialog.set_transient_for (parent)
+ dialog.set_modal (True)
+ response = dialog.run ()
+ file = None
+ if response == gtk.RESPONSE_OK:
+ file = dialog.get_filename ()
+ dialog.destroy ()
+ return file
+
+def save_dialog (parent, filters = None):
+ '''Pops a Save file chooser dialog'''
+ action = gtk.FILE_CHOOSER_ACTION_SAVE
+ button = gtk.STOCK_SAVE
+ return file_dialog (_("Save as..."), action, button, parent, filters)
+
+def close_confirmation_dialog (parent, message, cancel_button = False):
+ '''Pops a confirmation dialog when closing app'''
+ msg = '<span weight="bold" size="larger">%s</span>\n' % message
+ dialog = gtk.MessageDialog (parent, gtk.DIALOG_MODAL,
+ gtk.MESSAGE_QUESTION,
+ gtk.BUTTONS_YES_NO, None)
+ dialog.set_markup (msg)
+ if cancel_button:
+ dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ dialog.set_default_response (gtk.RESPONSE_CANCEL)
+ else:
+ dialog.set_default_response (gtk.RESPONSE_NO)
+ dialog.set_position (gtk.WIN_POS_CENTER)
+ dialog.set_transient_for (parent)
+ dialog.set_icon (parent.get_icon ())
+ response = dialog.run ()
+ dialog.destroy ()
+ return response
+
+class ChartWidget (gtk.DrawingArea):
+
+ __gsignals__ = {
+ "expose-event" : "override",
+ }
+
+ __surface = None
+
+ def __init__ (self, *args, **kwargs):
+ gtk.DrawingArea.__init__ (self, *args, **kwargs)
+
+ def update_chart (self, surface):
+ self.__surface = surface
+ self.queue_draw ()
+
+ def do_expose_event (self, event):
+ '''Handle expose events'''
+ if event.window == self.window and self.__surface:
+ cr = self.window.cairo_create ()
+ cr.set_operator (cairo.OPERATOR_OVER)
+ cr.set_source_surface (self.__surface)
+ cr.paint ()
+
+class GNXTLogger:
+ '''NXTLogger GTK GUI'''
+
+ glade = None
+
+ window = None
+ about_dialog = None
+ data_view = None
+ options_view = None
+ chart_columns_view = None
+ chart_widget = None
+ start_button = None
+ stop_button = None
+ save_button = None
+
+ logger = None
+ data = None
+ data_store = None
+ data_saved = True
+
+ options_store = None
+ chart_columns_store = None
+
+ def __init__ (self):
+ self.glade = gtk.glade.XML (fname = GLADE_FILE,
+ domain = GETTEXT_DOMAIN)
+ self.window = self.glade.get_widget ("mainWindow")
+ self.about_dialog = self.glade.get_widget ("aboutDialog")
+ self.data_view = self.glade.get_widget ("dataView")
+ self.options_view = self.glade.get_widget ("optionsView")
+ self.chart_columns_view = self.glade.get_widget ("chartColumnsView")
+ self.chart_window = self.glade.get_widget ("chartWindow")
+ self.start_button = self.glade.get_widget ("startButton")
+ self.stop_button = self.glade.get_widget ("stopButton")
+ self.save_button = self.glade.get_widget ("saveButton")
+ self.draw_chart_button = self.glade.get_widget ("drawChartButton")
+ self.save_chart_button = self.glade.get_widget ("saveChartButton")
+ autoconnect (self, self.glade)
+
+ self.chart_widget = ChartWidget ()
+ self.chart_window.add_with_viewport (self.chart_widget)
+
+ self.start_button.set_sensitive (True)
+ self.stop_button.set_sensitive (False)
+ self.save_button.set_sensitive (False)
+ self.draw_chart_button.set_sensitive (False)
+ self.save_chart_button.set_sensitive (False)
+
+ self.data = []
+ self.data_store = gtk.ListStore (gobject.TYPE_ULONG,
+ gobject.TYPE_INT,
+ gobject.TYPE_INT,
+ gobject.TYPE_UINT,
+ gobject.TYPE_LONG,
+ gobject.TYPE_LONG,
+ gobject.TYPE_LONG,
+ gobject.TYPE_INT,
+ gobject.TYPE_INT,
+ gobject.TYPE_INT,
+ gobject.TYPE_INT,
+ gobject.TYPE_LONG)
+ self.data_view.set_model (self.data_store)
+ self.logger = BluetoothLogger (self.data_read_cb)
+
+ self.options_store = gtk.ListStore (gobject.TYPE_STRING,
+ gobject.TYPE_STRING,
+ gobject.TYPE_INT)
+ self.options_view.set_model (self.options_store)
+ text_renderer = gtk.CellRendererText ()
+ column = gtk.TreeViewColumn (_("Option"), text_renderer, text = 1)
+ self.options_view.append_column (column)
+ text_renderer = gtk.CellRendererText ()
+ text_renderer.set_property ("editable", True)
+ text_renderer.connect ("edited", self.edit_option)
+ column = gtk.TreeViewColumn (_("Value"), text_renderer, text = 2)
+ self.options_view.append_column (column)
+
+ for option in OPTIONS:
+ self.options_store.append (option)
+
+ self.chart_columns_store = gtk.ListStore (gobject.TYPE_INT,
+ gobject.TYPE_STRING,
+ gobject.TYPE_BOOLEAN)
+ self.chart_columns_view.set_model (self.chart_columns_store)
+ text_renderer = gtk.CellRendererText ()
+ column = gtk.TreeViewColumn (_("Column"), text_renderer, text = 1)
+ self.chart_columns_view.append_column (column)
+ bool_renderer = gtk.CellRendererToggle ()
+ bool_renderer.set_property ("activatable", True)
+ bool_renderer.connect ("toggled", self.toggle_column_drawn)
+ column = gtk.TreeViewColumn (_("Draw"), bool_renderer, active = 2)
+ self.chart_columns_view.append_column (column)
+
+ text_renderer = gtk.CellRendererText ()
+ for row, name, drawn_default in DATA_COLUMNS:
+ column = gtk.TreeViewColumn (name, text_renderer, text = row)
+ self.data_view.append_column (column)
+
+ self.chart_columns_store.append ([row, name, drawn_default])
+
+ def edit_option (self, cell, path, data):
+ if "%s" % int (data) == data:
+ self.options_store[path][2] = int (data)
+
+ def toggle_column_drawn (self, cell, path):
+ self.chart_columns_store[path][2] = not cell.get_active ()
+
+ def run (self):
+ self.window.show_all ()
+ gtk.main ()
+
+ def start_cb (self, *args):
+ if not self.logger.running:
+ self.logger.start ()
+ self.start_button.set_sensitive (False)
+ self.stop_button.set_sensitive (True)
+
+ def stop_cb (self, *args):
+ if self.logger.running:
+ self.logger.stop ()
+ self.start_button.set_sensitive (True)
+ self.stop_button.set_sensitive (False)
+
+ def data_read_cb (self, raw_data):
+ if self.data_saved:
+ self.save_button.set_sensitive (True)
+ self.draw_chart_button.set_sensitive (True)
+ self.save_chart_button.set_sensitive (True)
+ self.data_saved = False
+ data = map (lambda s: s, raw_data)
+ self.data.append (data)
+ self.data_store.append (data)
+
+ def save (self):
+ path = save_dialog (self.window, "csv")
+ if not path:
+ return False
+ try:
+ writer = csv.write (open (path, "wb"))
+ writer.writerows (self.data)
+ self.data_saved = True
+ self.save_button.set_sensitive (False)
+ return True
+ except Exception, e:
+ print "Failed to save data to %s: %s" % (path, e)
+ return False
+
+ def save_cb (self, *args):
+ self.save ()
+
+ def clear_cb (self, *args):
+ self.data = []
+ self.data_store.clear ()
+ self.data_saved = True
+ self.chart_widget.update_chart (None)
+ self.save_button.set_sensitive (False)
+ self.draw_chart_button.set_sensitive (False)
+ self.save_chart_button.set_sensitive (False)
+
+ def about_cb (self, *args):
+ self.about_dialog.show ()
+
+ def about_close_cb (self, *args):
+ self.about_dialog.hide ()
+ return True
+
+ def quit_cb (self, *args):
+ if self.logger.running:
+ response = close_confirmation_dialog (self.window,
+ CLOSE_RUNNING_MESSAGE)
+ if response == gtk.RESPONSE_NO:
+ return True
+ self.logger.stop ()
+ if self.data and not self.data_saved:
+ response = close_confirmation_dialog (self.window,
+ CLOSE_SAVE_DATA_MESSAGE,
+ cancel_button = True)
+ if (response == gtk.RESPONSE_YES and not self.save ()) \
+ or response == gtk.RESPONSE_CANCEL:
+ return True
+ gtk.main_quit ()
+
+ def get_option (self, option):
+ try:
+ opts = filter (lambda row: row[0] == option,
+ self.options_store)
+ return opts[0][2]
+ except:
+ return 0
+
+ def get_data_column (self, column):
+ return map (lambda row: row[column], self.data)
+
+ def draw_chart (self):
+ columns = filter (lambda row: row[2], self.chart_columns_store)
+ columns = map (lambda row: (row[0], row[1]), columns)
+ y_data_sets = map (lambda c: (c[1], self.get_data_column (c[0])),
+ columns)
+ x_data = self.get_data_column (0)
+ width = self.get_option ("width")
+ height = self.get_option ("height")
+ return plot_data (y_data_sets, x_data, width, height)
+
+ def draw_chart_cb (self, *args):
+ surface = self.draw_chart ()
+ self.chart_widget.update_chart (surface)
+
+ def save_chart_cb (self, *args):
+ path = save_dialog (self.window, "png")
+ if not path:
+ return False
+ surface = self.draw_chart ()
+ try:
+ surface.write_to_png (path)
+ return True
+ except Exception, e:
+ print "Failed to save chart to %s: %s" % (path, e)
+ return False
diff --git a/nxtlogger/logger.py b/nxtlogger/logger.py
new file mode 100644
index 0000000..8d568e8
--- /dev/null
+++ b/nxtlogger/logger.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+# coding=utf-8
+
+'''
+Copyright (C) 2008 Guillaume Seguin <guillaume@segu.in>
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+
+Authors:
+ Guillaume Seguin <guillaume@segu.in>
+'''
+
+import threading
+import serial
+import struct
+
+SERIAL_TIMEOUT = 1
+
+class BluetoothLogger:
+
+ thread = None
+ read_cb = None
+ running = False
+
+ def __init__ (self, read_cb):
+ self.read_cb = read_cb
+
+ def start (self):
+ self.thread = BluetoothLoggerThread (self.read_cb)
+ self.thread.start ()
+ self.running = True
+
+ def stop (self):
+ if self.thread:
+ self.thread.stop ()
+ self.thread = None
+ self.running = False
+
+class BluetoothSerialConnection (serial.Serial):
+
+ '''
+ def __init__ (self, *args, **kwargs):
+ serial.Serial.__init__ (self, *args, **kwargs)
+ self.data = ""
+
+ def get (self):
+ self.data += self.read (34 - len (self.data))
+ if len (self.data) >= 34:
+ size = struct.unpack ("h", self.data[:2])[0]
+ if size != 32:
+ self.data = self.data[1:]
+ return None
+ if size == 32:
+ data = struct.unpack ("LccHlllhhhhl", self.data[:32])
+ self.data = self.data[32:]
+ return data
+ return None
+ '''
+
+ def get (self):
+ data = self.read (2)
+ if len (data) == 2:
+ size = struct.unpack ("h", data)[0]
+ if size == 32:
+ data = self.read (size)
+ return struct.unpack ("LbbHlllhhhhl", data)
+ return None
+
+class BluetoothLoggerThread (threading.Thread):
+
+ read_cb = None
+ connection = None
+
+ terminate = False
+
+ def __init__ (self, read_cb):
+ threading.Thread.__init__ (self)
+ self.read_cb = read_cb
+ self.connection = BluetoothSerialConnection ("/dev/rfcomm0",
+ timeout = SERIAL_TIMEOUT)
+
+ def run (self):
+ self.connection.flushInput ()
+ while not self.terminate:
+ data = self.connection.get ()
+ if data:
+ self.read_cb (data)
+
+ def stop (self):
+ self.terminate = True