#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# (C) 2012, Pedro I. López <dreilopz@gmail.com>
#
# This source code is released under the new BSD license, a copy of the
# license is in the distribution directory.
import wx
import sys
import os
import serial
import numpy as np
import matplotlib.font_manager as font_manager
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigureCanvas
# wxWidgets object ID for timer.
TIMER_ID = wx.NewId()
# Select port.
PLATFORM = sys.platform
if 'linux' in PLATFORM:
PORT = '/dev/ttyS0'
elif 'win32' in PLATFORM:
PORT = 'COM10'
# Number of data points.
POINTS = 200
START = '\x00'
ACK = '\x03'
OK_STATUS = '\x06'
ERROR = '\x0a'
END = '\x04'
READ = '\x01'
WRITE = '\x0c'
REPORT_STATUS = '\x07'
DISTANCE_SAMPLE = '\x02'
ADC0_SAMPLE = '\x05'
OPMODE = '\x0b'
NORMAL_MODE = '\x08'
SERIAL_MODE = '\x09'
TIME_DELTA = 100
DEBUG = True
[docs]class UC(object):
FACTOR = 5.0 / 1023.0
'''UC interface object.'''
CONVERSION = 5.0 / (2**10)
[docs] def __init__( self, port=None ):
self.port = port
@property
[docs] def adc0D( self ):
buff = self.execute_command(READ, ADC0_SAMPLE)
buff = [ord(i) for i in buff]
return buff[0] + buff[1] * 255
@property
def adc0A(self):
return self.__class__.FACTOR * self.adc0D
@property
[docs] def distance(self):
return 12343.85 * (self.adc0D ** (-1.15))
@property
def distanceD( self ):
buff = self.execute_command(READ, DISTANCE_SAMPLE)
buff = [ord(i) for i in buff]
distance = buff[0] + buff[1] * 255
return distance
[docs] def execute_command( self, *command ):
command = list(command)
self.port.write(START)
assert self.port.read(1) == ACK
self.port.write(''.join(command))
n = ord(self.port.read(1))
buff = []
for i in range(n):
buff.append(self.port.read(1))
self.port.write(ACK)
self.port.write(END)
assert self.port.read(1) == ACK
return buff
@property
[docs] def status( self ):
return self.execute_command(REPORT_STATUS)[0]
@property
[docs] def mode( self ):
mode = self.execute_command(READ, OPMODE)[0]
return mode
[docs] def set_mode( self, mode ):
if mode == SERIAL_MODE:
self.execute_command(WRITE, OPMODE, SERIAL_MODE)
assert self.mode == SERIAL_MODE
elif mode == NORMAL_MODE:
self.execute_command(WRITE, OPMODE, NORMAL_MODE)
assert self.mode == NORMAL_MODE
def analog_to_digital(anv):
anv = float(anv)
assert (anv >= 0.0) and (anv <= 5.0)
return int((1023.0/5.0) * anv)
[docs]class PlotPanel(wx.Panel):
def __init__(self, *args, **kwds):
# begin wxGlade: PlotPanel.__init__
uc = kwds.pop('uc')
kwds["style"] = wx.DOUBLE_BORDER|wx.TAB_TRAVERSAL
wx.Panel.__init__(self, *args, **kwds)
self.__set_properties()
self.__do_layout()
# end wxGlade
# ``PlotPanel`` size is (600, 400).
self.figure = Figure(figsize=(6, 4), dpi=100)
self.ax = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, wx.ID_ANY, self.figure)
#self.ax.set_ylim([0.0, 1023.0])
self.ax.set_ylim([0.0, 80.0])
self.ax.set_xlim([0.0, POINTS])
self.ax.set_autoscale_on(False) # Disable autoscale.
self.ax.set_xticks([])
self.ax.grid(True, animated=True, linewidth=1, antialiased=True,
fillstyle='full')
self.ax.set_title(u'Distance vs time')
self.ax.set_ylabel('distance (cm)')
self.ax.set_xlabel('time')
# Initial empty plot.
self.distance = [None] * POINTS
self.distance_plot, = self.ax.plot(range(POINTS), self.distance,
label='Distance')
self.canvas.draw()
# Save the clean background - everything but the line is drawn and
# saved in the pixel buffer background.
self.bg = self.canvas.copy_from_bbox(self.ax.bbox)
# Represents UC interfaced through the serial port.
self.uc = uc
assert self.uc.port.isOpen()
assert self.uc.status == OK_STATUS
self.uc.set_mode(SERIAL_MODE)
# Take a snapshot of voltage, needed for the update algorithm.
self.before = self.uc.distance
wx.EVT_TIMER(self, TIMER_ID, self.onTimer)
# Initialize the timer.
self.t = wx.Timer(self, TIMER_ID)
self.samples = 0
def __set_properties(self):
# begin wxGlade: PlotPanel.__set_properties
self.SetMinSize((600, 400))
self.SetToolTipString("Distance plot.")
# end wxGlade
def __do_layout(self):
# begin wxGlade: PlotPanel.__do_layout
pass
# end wxGlade
[docs] def onTimer(self, evt):
# Get distance.
distance = self.uc.distance
if distance <= 80.0:
distance_str = str(distance)
else:
distance = 80.0
distance_str = u"OUT OF RANGE"
mainframe.distance_label.SetLabel(distance_str)
self.samples += 1
# Restore the clean background, saved at the beginning.
self.canvas.restore_region(self.bg)
# Update data array.
self.distance = self.distance[1:] + [distance]
# Update plot.
self.distance_plot.set_ydata(self.distance)
# Just draw the "animated" objects.
self.ax.draw_artist(self.distance_plot)
# Blit the background with the animated lines.
self.canvas.blit(self.ax.bbox)
with open(LOG_FILE_PATH, 'a+') as f:
f.write("{0},{1}\n".format(str(self.samples * TIME_DELTA),
str(distance)))
# end of class PlotPanel
[docs]class MainFrame(wx.Frame):
def __init__(self, *args, **kwds):
# begin wxGlade: MainFrame.__init__
uc = kwds.pop('uc')
#kwds["style"] = wx.CAPTION|wx.CLOSE_BOX|wx.MINIMIZE_BOX|wx.MAXIMIZE_BOX|wx.SYSTEM_MENU|wx.RESIZE_BORDER|wx.FULL_REPAINT_ON_RESIZE|wx.TAB_TRAVERSAL|wx.CLIP_CHILDREN
kwds['style'] = (wx.DEFAULT_FRAME_STYLE ^ (wx.RESIZE_BORDER |
wx.MINIMIZE_BOX |
wx.MAXIMIZE_BOX))
kwds['size'] = (650, 600)
kwds['pos'] = (0, 0)
wx.Frame.__init__(self, *args, **kwds)
self.mainPanel = wx.Panel(parent=self)
self.mainframe_statusbar = self.CreateStatusBar(1, 0)
self.plot_panel = PlotPanel(self.mainPanel, id=-1, uc=uc)
self.plot_enable = wx.ToggleButton(self.mainPanel, -1, "TI")
self.label_1 = wx.StaticText(self.mainPanel, -1, "Writing to file:", style=wx.ALIGN_RIGHT)
self.filepath_label = wx.StaticText(self.mainPanel, -1, "")
self.label_2 = wx.StaticText(self.mainPanel, -1, "Distance (cm):",
style=wx.ALIGN_RIGHT)
self.distance_label = wx.StaticText(self.mainPanel, -1, "TI")
self.__set_properties()
self.__do_layout()
self.Bind(wx.EVT_TOGGLEBUTTON, self.toggle_on_off, self.plot_enable)
# end wxGlade
def __set_properties(self):
# begin wxGlade: MainFrame.__set_properties
self.SetTitle("cerca")
self.SetFocus()
self.mainframe_statusbar.SetStatusWidths([-1])
# statusbar fields
mainframe_statusbar_fields = ["[INIT text]"]
for i in range(len(mainframe_statusbar_fields)):
self.mainframe_statusbar.SetStatusText(mainframe_statusbar_fields[i], i)
self.label_1.SetMinSize((180, 20))
self.filepath_label.SetFont(wx.Font(8, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
self.label_2.SetMinSize((180, 20))
self.distance_label.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.BOLD, 0, ""))
# end wxGlade
def __do_layout(self):
# begin wxGlade: MainFrame.__do_layout
main_sizer = wx.BoxSizer(wx.VERTICAL)
sizer_5 = wx.BoxSizer(wx.HORIZONTAL)
sizer_4 = wx.BoxSizer(wx.HORIZONTAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
main_sizer.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
sizer_3.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
sizer_3.Add(self.plot_panel, 1, wx.ALL|wx.EXPAND, 0)
sizer_3.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
main_sizer.Add(sizer_3, 0, wx.EXPAND, 0)
main_sizer.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
main_sizer.Add(self.plot_enable, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
main_sizer.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
sizer_4.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
sizer_4.Add(self.label_1, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
sizer_4.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
sizer_4.Add(self.filepath_label, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, 0)
main_sizer.Add(sizer_4, 0, wx.ALL, 0)
main_sizer.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
sizer_5.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
sizer_5.Add(self.label_2, 0, wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
sizer_5.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
sizer_5.Add(self.distance_label, 0, wx.ALL|wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
main_sizer.Add(sizer_5, 0, wx.ALL, 0)
main_sizer.Add((20, 20), 0, wx.ADJUST_MINSIZE, 0)
self.mainPanel.SetSizer(main_sizer)
main_sizer.Fit(self.mainPanel)
self.mainPanel.Layout()
# end wxGlade
[docs] def toggle_on_off(self, event): # wxGlade: MainFrame.<event_handler>
# Change label (from 'ON' to 'OFF', etc).
if self.plot_enable.GetValue():
self.plot_enable.SetLabel('ON')
self.mainframe_statusbar.SetStatusText(
u'Serial mode, writing to file {0}'.format(LOG_FILE_PATH),
0)
if not self.plot_panel.uc.port.isOpen():
self.plot_panel.uc.port.open()
assert self.plot_panel.uc.status == OK_STATUS
self.plot_panel.uc.set_mode(SERIAL_MODE)
# Timer.
self.plot_panel.t.Start(TIME_DELTA)
else:
self.plot_enable.SetLabel('OFF')
self.mainframe_statusbar.SetStatusText(
u'Normal mode, serial connection closed', 0)
# Timer.
self.plot_panel.t.Stop()
self.plot_panel.uc.set_mode(NORMAL_MODE)
# end of class MainFrame
[docs]class Cerca(wx.App):
[docs] def OnInit(self):
global LOG_FILE_PATH, mainframe
wx.InitAllImageHandlers()
available_ports = scan()
print 'Available ports: {0}'.format(available_ports)
print 'JUMP'
msg = u'''\
The application will use a serial port with *8N1* configuration:
- 9600 bauds
- 8 data bits
- No parity
- 1 stop bit
Found serial ports. Choose the serial port you want to use and press the 'OK'
button, or press 'Cancel' to exit.
'''
caption = u'Serial port selection'
dlg = wx.SingleChoiceDialog(None, msg, caption,
[i[1] for i in available_ports])
response = dlg.ShowModal()
if response == wx.ID_CANCEL:
print 'Exiting application before configuring serial port.'
dlg.Destroy()
return False
elif response == wx.ID_OK:
selected_serialport = available_ports[dlg.GetSelection()][0]
dlg.Destroy()
try:
self.sp = serial.Serial(selected_serialport, timeout=1)
except serial.SerialException as e:
print 'Error while opening serial port: {0}'.format(e.args)
return False
try:
self.uc = UC(port=self.sp)
except UCException as e:
print 'Error while abstracting microcontroller: {0}'.format(e.args)
return False
while True:
LOG_FILE_DIR = wx.DirSelector(message=wx.DirSelectorPromptStr,
style=0, pos=wx.DefaultPosition, parent=None)
if (os.access(LOG_FILE_DIR, os.R_OK) and
os.access(LOG_FILE_DIR, os.W_OK) and
os.access(LOG_FILE_DIR, os.X_OK) and
os.access(LOG_FILE_DIR, os.F_OK)):
break
LOG_FILE_NAME = wx.GetTextFromUser(
message=u"Log filename, with extension.",
caption="Input Text", parent=None)
assert isinstance(LOG_FILE_NAME, str) or isinstance(LOG_FILE_NAME, unicode)
LOG_FILE_PATH = os.path.join(LOG_FILE_DIR, LOG_FILE_NAME)
with open(LOG_FILE_PATH, 'w') as f:
f.write("time,distance\n")
mainframe = MainFrame(None, -1, "", uc=self.uc)
self.SetTopWindow(mainframe)
mainframe.Show()
mainframe.filepath_label.SetLabel(LOG_FILE_PATH)
mainframe.mainframe_statusbar.SetStatusText(
u'Normal mode, serial communication not started.', 0)
mainframe.plot_enable.SetLabel('OFF')
mainframe.distance_label.SetLabel('0.00')
return True
[docs] def OnExit(self):
print 'Closing serial port {0}'.format(self.sp.port)
self.sp.close()
print 'Application END.'
[docs]def scan():
available = []
for i in range(256):
try:
s = serial.Serial(i)
available.append( (i, s.portstr))
s.close()
except serial.SerialException:
pass
return available
if __name__ == "__main__":
LOG_FILENAME = 'log'
if os.path.exists(LOG_FILENAME):
os.remove(LOG_FILENAME)
cerca = Cerca(redirect=True, filename=LOG_FILENAME)
cerca.MainLoop()