class SensorView(BaseView, UnitMixin, PropertyObject): __gtype_name__ = 'SensorView' gproperty('units-widget', object) gproperty('update-name', bool, False) def __init__(self, pid, index=0, units='Metric', active_widget=None, name_widget=None, value_widget=None, units_widget=None, helper=None): self.command = Sensor(pid, index) self.command.connect('data-changed', self._data_changed_cb) BaseView.__init__(self, active_widget, name_widget, value_widget, helper) PropertyObject.__init__(self, active_widget=active_widget, name_widget=name_widget, value_widget=value_widget, units_widget=units_widget, unit_standard=units, helper=helper) def __post_init__(self): self.connect('notify::unit-standard', self._notify_unit_standard_cb) BaseView.__post_init__(self) def _notify_unit_standard_cb(self, o, pspec): self._update_view() def _do_sensitize_widgets(self): if self.units_widget: self.units_widget.set_sensitive(self.supported and self.active) def _do_update_view(self): if self.unit_standard == 'Imperial': value = self.command.imperial_value units = self.command.imperial_units else: value = self.command.metric_value units = self.command.metric_units if not units: units = '' if not value: value = '' if self.name_widget and self.update_name: self.name_widget.set_text(self.command.name) if self.value_widget: self.value_widget.set_text(value) if self.units_widget: self.units_widget.set_text(units)
class SensorProgressView(BaseView, PropertyObject): __gtype_name__ = 'SensorProgressView' gproperty('progress-widget', object) gproperty('min-value', float) gproperty('max-value', float) gproperty('update-name', bool, False) def __init__(self, pid, index=0, min_value=0, max_value=100, active_widget=None, name_widget=None, value_widget=None, helper=None, progress_widget=None): self.command = Sensor(pid, index) self.command.connect('data-changed', self._data_changed_cb) BaseView.__init__(self, active_widget, name_widget, value_widget, helper) PropertyObject.__init__(self, active_widget=active_widget, name_widget=name_widget, value_widget=value_widget, helper=helper, progress_widget=progress_widget, min_value=min_value, max_value=max_value) def _do_sensitize_widgets(self): if self.progress_widget: self.progress_widget.set_sensitive(self.supported and self.active) def _do_update_view(self): value = self.command.metric_value if not value: value = '' fraction = 0 else: fraction = eval(value) / (self.max_value - self.min_value) if fraction > 1: fraction = 1 if fraction < 0: fraction = 0 if self.name_widget and self.update_name: self.name_widget.set_text(self.command.name) if self.value_widget: self.value_widget.set_text(value) if self.progress_widget: self.progress_widget.set_fraction(fraction)
class MILWidget(gtk.Entry, StateMixin, PropertyObject): __gtype_name__ = 'MILWidget' gproperty('on', bool, False) gproperty('on-color', str, '#F7D30D') gproperty('off-color', str, '#AAAAAA') def __init__(self, app): gtk.Entry.__init__(self, 3) self.command = Sensor('0101', 1) PropertyObject.__init__(self) self._pref_cbs = [] self.app = app self.set_text('MIL') self.set_property('editable', False) self.set_property('width-chars', 3) def __post_init__(self): self.on_color = self.app.prefs.get('mil.on-color') self.off_color = self.app.prefs.get('mil.off-color') cb_id = self.app.prefs.add_watch('mil.on-color', self._notify_prefs_cb) self._pref_cbs.append(('mil.on-color', cb_id)) cb_id = self.app.prefs.add_watch('mil.off-color', self._notify_prefs_cb) self._pref_cbs.append(('mil.off-color', cb_id)) self.connect('notify::on', self._notify_cb) self.connect('notify::on-color', self._notify_cb) self.connect('notify::off-color', self._notify_cb) self.notify('on') self.command.connect('data-changed', self._data_changed_cb) def _notify_cb(self, o, pspec): if self.on: self.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.on_color)) else: self.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(self.off_color)) def _notify_prefs_cb(self, pname, pvalue, args): if pname == 'mil.on-color': self.on_color = pvalue elif pname == 'mil.off-color': self.off_color = pvalue def _data_changed_cb(self, command, data): on = self.command.metric_value == 'On' self.on = on
class UnitMixin(object): gproperty('unit-standard', str, 'Metric') def prop_set_unit_standard(self, standard): if not standard in ('Metric', 'Imperial'): raise ValueError, 'unit-standard should be either Metric or Imperial' return standard
class Command(GObject, PropertyObject): __gtype_name__ = 'Command' gproperty('command', str, flags=gobject.PARAM_READABLE) gproperty('data', object) gsignal('data-changed', object) def __init__(self, command): GObject.__init__(self) PropertyObject.__init__(self, command=command) def __post_init__(self): self.connect('notify::data', self._notify_data_cb) def _notify_data_cb(self, o, pspec): self.emit('data-changed', self.data) def clear(self): self.data = None
class DTCInfo(GObject, PropertyObject): gproperty('code', str) gproperty('code-class', str) gproperty('description', str) gproperty('additional', str) gproperty('lookup', str) def __init__(self, glade): GObject.__init__(self) PropertyObject.__init__(self) self._code_label = glade.get_widget('code_label') self._class_label = glade.get_widget('class_label') self._description_label = glade.get_widget('description_label') self._additional_textview = glade.get_widget('additional_textview') self._additional_buffer = gtk.TextBuffer() self._additional_textview.set_buffer(self._additional_buffer) self._dtc_entry = glade.get_widget('dtc_entry') def __post_init__(self): self.connect('notify::code', self._notify_cb) self.connect('notify::code-class', self._notify_cb) self.connect('notify::description', self._notify_cb) self.connect('notify::additional', self._notify_cb) self.connect('notify::lookup', self._notify_cb) def _notify_cb(self, o, pspec): if pspec.name == 'code': self._code_label.set_text(self.code) elif pspec.name == 'code-class': self._class_label.set_text(self.code_class) elif pspec.name == 'description': self._description_label.set_text(self.description) elif pspec.name == 'additional': self._additional_buffer.set_text(self.additional) elif pspec.name == 'lookup': self._dtc_entry.set_text(self.lookup)
class SchedulerTimer(gtk.Label, PropertyObject): gproperty('active', bool, False) def __init__(self, scheduler): GObject.__init__(self) PropertyObject.__init__(self) self._rate = 0 self._samples = [] self.set_text(_('refresh rate: N/A')) scheduler.connect('command_executed', self._scheduler_command_executed_cb) scheduler.connect('notify::working', self._scheduler_notify_working_cb) def _scheduler_notify_working_cb(self, scheduler, working): if not working: self.set_text(_('refresh rate: N/A')) def _scheduler_command_executed_cb(self, scheduler): now = datetime.datetime.now() if len(self._samples) == 0: self._samples.append((now, datetime.timedelta(0, 0, 0))) else: if len(self._samples) > 20: self._samples.pop(0) previous = self._samples[len(self._samples) - 1] delta = now - previous[0] self._samples.append((now, delta)) total = 0.0 count = 0 for item in self._samples: count += 1 u_seconds = item[1].seconds + item[1].microseconds / 1000000.0 total += u_seconds rate = round(count / total, 1) self.set_text(_('refresh rate: %s Hz') % rate)
class Gauge(gtk.EventBox, StateMixin, UnitMixin, PropertyObject): __gtype_name__ = "Gauge" def prop_get_value(self): return self._value # FIXME: min-angle and max-angle should be float, # but set_property does not accept negative values for gdouble or gint. # Warning: value "-50" of type `gint' is invalid or out of range for property `min-angle' of type `gint' gproperty('min-angle', object) gproperty('max-angle', object) gproperty('min-value', float) gproperty('max-value', float) gproperty('idle-value', float) gproperty('value', float, flags=gobject.PARAM_READABLE) gproperty('needle-color', str, '#FDC62D') gproperty('needle-length', int) gproperty('needle-width', int) gproperty('metric-overlay', object) gproperty('imperial-overlay', object) def __init__(self, pid, metric, imperial=None, index=0): if not imperial: imperial = metric gtk.EventBox.__init__(self) PropertyObject.__init__(self, command=pid, index=index, metric_overlay=metric, imperial_overlay=imperial) self.sensor = Sensor(pid, index) width = self.metric_overlay.get_width() height = self.metric_overlay.get_height() self.set_size_request(width, height) self._needle_gc = None self._set_default_values() self._value = self.idle_value self._pixmap = None self._area = gtk.DrawingArea() self.add(self._area) self.connect('button-press-event', self._button_press_cb) self._area.connect("expose_event", self._expose_event) self._area.connect("configure_event", self._configure_event) def __post_init__(self): self.connect('notify::needle-length', self._notify_must_redraw) self.connect('notify::metric-overlay', self._notify_must_redraw) self.connect('notify::imperial-overlay', self._notify_must_redraw) self.connect('notify::needle-color', self._notify_needle_cb) self.connect('notify::needle-width', self._notify_needle_cb) self.connect('notify::supported', self._notify_supported_cb) self.connect('notify::active', self._notify_active_cb) self.sensor.connect('data-changed', self._sensor_data_changed_cb) def _notify_must_redraw(self, o, pspec): self._draw() def _notify_needle_cb(self, o, pspec): if pspec.name == 'needle-color': if self._needle_gc: self._needle_gc.set_rgb_fg_color( gtk.gdk.color_parse(self.needle_color)) if pspec.name == 'needle-width': self._needle_gc.line_width = width self._draw() def _notify_supported_cb(self, o, pspec): self.set_sensitive(self.supported) if not self.supported: self.active = False def _notify_active_cb(self, o, pspec): self._area.set_sensitive(self.active) self._draw() self.emit('active-changed', self.active) def _button_press_cb(self, widget, event): if event.type == gdk.BUTTON_PRESS: self.active = not self.active def _sensor_data_changed_cb(self, sensor, data): self._value = eval(self.sensor.metric_value) self._draw() def _set_default_values(self): raise NotImplementedError, 'Use one of the subclasses please' def _construct_needle(self): angle_range = self.max_angle - self.min_angle value_range = self.max_value - self.min_value value = self._value if value < self.min_value: value = self.min_value if value > self.max_value: value = self.max_value angle = (value - self.min_value) / value_range * angle_range + self.min_angle point_x = int(self._needle_origin_x + self.needle_length * math.cos((angle + 180) * math.pi / 180)) point_y = int(self._needle_origin_y + self.needle_length * math.sin((angle + 180) * math.pi / 180)) side1_x = int(self._needle_origin_x + self.needle_width * math.cos((angle + 270) * math.pi / 180)) side1_y = int(self._needle_origin_y + self.needle_width * math.sin((angle + 270) * math.pi / 180)) side2_x = int(self._needle_origin_x + self.needle_width * math.cos((angle + 90) * math.pi / 180)) side2_y = int(self._needle_origin_y + self.needle_width * math.sin((angle + 90) * math.pi / 180)) return [(self._needle_origin_x, self._needle_origin_y), (side1_x, side1_y), (point_x, point_y), (side2_x, side2_y)] def _draw(self): if self._pixmap is None: return x, y, width, height = self.get_allocation() bg_gc = self.get_style().bg_gc[gtk.STATE_NORMAL] self._pixmap.draw_rectangle(bg_gc, True, 0, 0, width, height) if self.unit_standard == 'Imperial': overlay = self.imperial_overlay else: overlay = self.metric_overlay self._pixmap.draw_pixbuf(gtk.gdk.GC(self.window), overlay, 0, 0, 0, 0) needle = self._construct_needle() if self.active: self._pixmap.draw_polygon(self._needle_gc, True, needle) fg_gc = self.get_style().fg_gc[gtk.STATE_NORMAL] fg_gc.set_foreground(gtk.gdk.color_parse('#000000')) self._pixmap.draw_arc(fg_gc, True, self._needle_origin_x - self._circle_radius, self._needle_origin_y - self._circle_radius, self._circle_radius * 2, self._circle_radius * 2, 0, 360 * 64) self._area.queue_draw() def _expose_event(self, widget, event): x, y, width, height = event.area widget.window.draw_drawable(widget.get_style().bg_gc[gtk.STATE_NORMAL], self._pixmap, x, y, x, y, width, height) return False def _configure_event(self, widget, event): x, y, width, height = widget.get_allocation() self._pixmap = gtk.gdk.Pixmap(widget.window, width, height) self._needle_gc = gtk.gdk.GC(widget.window) self._needle_gc.set_rgb_fg_color(gtk.gdk.color_parse( self.needle_color)) self._needle_gc.line_width = self.needle_width self.idle() def idle(self): """Set value to the defined idle value""" self._value = self.idle_value self._draw()
class Sensor(Command, PropertyObject): __gtype_name__ = 'Sensor' gproperty('index', int, 0) gproperty('indices', int) gproperty('name', str, flags=gobject.PARAM_READABLE) gproperty('metric_value', str, flags=gobject.PARAM_READABLE) gproperty('metric_units', str, flags=gobject.PARAM_READABLE) gproperty('imperial_value', str, flags=gobject.PARAM_READABLE) gproperty('imperial_units', str, flags=gobject.PARAM_READABLE) def prop_set_index(self, index): if self._indices < index + 1: raise ValueError, 'index too high' else: return index def prop_get_indices(self): return self._num_values def prop_get_metric_value(self): if self.data: return self._decoder(self.data)[0] else: return None def prop_get_imperial_value(self): if self.data: return self._decoder(self.data)[1] else: return None def prop_get_metric_units(self): return self._metric_units def prop_get_imperial_units(self): return self._imperial_units def prop_get_name(self): return self._name def __init__(self, command, index=0, units='Metric'): self._indices = len(SENSORS[command[2:]]) self._imperial_units = None self._metric_units = None self._decoder = None Command.__init__(self, command) PropertyObject.__init__(self, command=command, index=index) def __post_init__(self): Command.__post_init__(self) self.connect('notify::index', self._index_changed_cb) self._update_info() def _update_info(self): self._name = SENSORS[self.command[2:]][self.index][NAME] self._metric_units = SENSORS[self.command[2:]][self.index][METRIC] self._imperial_units = SENSORS[self.command[2:]][self.index][IMPERIAL] self._decoder = SENSORS[self.command[2:]][self.index][FUNC] def _index_changed_cb(self, o, pspec): self._clear_data() self._update_info()
class StateMixin(object): gproperty('active', bool, False) gproperty('supported', bool, False) gsignal('active-changed', bool)
class Scheduler(GObject, PropertyObject): """ This class receives OBDData objects, puts them in a queue and sends them to the OBDDevice """ __gtype_name__ = "Scheduler" ################# Properties and signals ############### gsignal('command-executed') gproperty('working', bool, False) gproperty('obd-device', object) def prop_set_obd_device(self, device): if device and not isinstance(device, OBDDevice): raise TypeError, 'obd should be an instance of OBDDevice' return device def prop_set_working(self, working): if working: if self.obd_device and self.obd_device.connected: return working else: working = False return working def __init__(self, obd_device): """ @param obd_device: the OBDDevice to send commands @param timeout: the time between two commands """ GObject.__init__(self) PropertyObject.__init__(self, obd_device=obd_device) self._queue = [] self._os_queue = [] def __post_init__(self): self.connect('notify::working', self._notify_working_cb) self.obd_device.connect('connected', self._obd_device_connected_cb) def _notify_working_cb(self, o, pspec): if self.working: self._execute_next_command() def _command_success_cb(self, cmd, result, args): if self.working: self.emit('command_executed') # We only care about the first result result = result[0] for item in cmd.list: item.data = result self._execute_next_command() def _command_error_cb(self, cmd, msg, args): debug('Scheduler._command_error_cb: command was: %s' % cmd) debug('Scheduler._command_error_cb: msg is %s' % msg) if self.working: self._execute_next_command() def _execute_next_command(self): if len(self._os_queue): queue_item = self._os_queue.pop(0) elif len(self._queue): queue_item = self._queue.pop(0) self._queue.append(queue_item) else: print 'nothing in queue' self.working = False return self.obd_device.read_command(queue_item, self._command_success_cb, self._command_error_cb) def _obd_device_connected_cb(self, obd_device, connected): if not connected: self.working = False ####################### Public Interface ################### def add(self, cmd, oneshot=False): """Add an item to the queue @param cmd: the item to add to the queue @param oneshot: wether the command should only be executed once. """ if not isinstance(cmd, Command): raise ValueError, 'command should be an instance of Command' if oneshot: queue = self._os_queue else: queue = self._queue if cmd.command in queue: queue_item = queue[queue.index(cmd.command)] else: queue_item = QueueItem(cmd.command) queue.append(queue_item) queue_item.list.append(cmd) def remove(self, cmd): """Remove an item from the queue @param cmd: Command instance """ if not isinstance(cmd, Command): raise ValueError, 'cmd should be an instance of Command' for queue in (self._queue, self._os_queue): for queue_item in queue: if queue_item == cmd.command: if cmd in queue_item.list: queue_item.list.remove(cmd) if queue_item.list == []: queue.remove(queue_item)
class BaseView(GObject, StateMixin, PropertyObject): __gtype_name__ = 'BaseView' gproperty('name-widget', object) gproperty('active-widget', object) gproperty('value-widget', object) gproperty('helper', object) def __init__(self, active_widget=None, name_widget=None, value_widget=None, helper=None): GObject.__init__(self) PropertyObject.__init__(self, active_widget=active_widget, name_widget=name_widget, value_widget=value_widget, helper=helper) self._toggleable = False if active_widget: if isinstance(active_widget, gtk.ToggleButton): self._toggleable = True self.active = active_widget.get_active() active_widget.connect('toggled', self._active_toggled_cb) elif isinstance(active_widget, gtk.Button): self._togglable = False active_widget.connect('clicked', self._active_clicked_cb) else: raise ValueError, 'active_widget should be gtk.Button or gtk.ToggleButton' def __post_init__(self): self.connect('notify::supported', self._notify_supported_cb) self.connect('notify::active', self._notify_active_cb) self._update_view() self._sensitize_widgets() def _notify_active_cb(self, o, pspec): if self._toggleable: self.active_widget.set_active(self.active) self._sensitize_widgets() if not self.active: self.command.clear() self.emit('active-changed', self.active) def _active_toggled_cb(self, togglebutton): self.active = togglebutton.get_active() def _active_clicked_cb(self, button): self.active = not self.active def _notify_supported_cb(self, o, pspec): if not self.supported: self.active = False self._sensitize_widgets() def _sensitize_widgets(self): if self.active_widget: self.active_widget.set_sensitive(self.supported) for widget in (self.name_widget, self.value_widget): if widget: widget.set_sensitive(self.supported and self.active) self._do_sensitize_widgets() def _data_changed_cb(self, command, data): self._update_view() def _update_view(self): if self.helper and callable(self.helper): self.helper(self) else: self._do_update_view()