1#!/usr/local/bin/python3.8
2############################################################################
3#
4# wxgui.py
5#
6# Copyright 2004 Donour Sizemore (donour@uchicago.edu)
7# Copyright 2009 Secons Ltd. (www.obdtester.com)
8#
9# This file is part of pyOBD.
10#
11# pyOBD is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
15#
16# pyOBD is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19# GNU General Public License for more details.
20#
21# You should have received a copy of the GNU General Public License
22# along with pyOBD; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24############################################################################
25
26#import wxversion
27#wxversion.select("2.6")
28import wx
29
30import obd_io #OBD2 funcs
31import os #os.environ
32
33import threading
34import sys
35import serial
36import platform
37import time
38import ConfigParser #safe application configuration
39import webbrowser #open browser from python
40
41from obd2_codes import pcodes
42from obd2_codes import ptest
43
44from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
45
46ID_ABOUT  = 101
47ID_EXIT   = 110
48ID_CONFIG = 500
49ID_CLEAR  = 501
50ID_GETC   = 502
51ID_RESET  = 503
52ID_LOOK   = 504
53ALL_ON    = 505
54ALL_OFF   = 506
55
56ID_DISCONNECT = 507
57ID_HELP_ABOUT = 508
58ID_HELP_VISIT = 509
59ID_HELP_ORDER = 510
60
61# Define notification event for sensor result window
62EVT_RESULT_ID = 1000
63def EVT_RESULT(win, func,id):
64    """Define Result Event."""
65    win.Connect(-1, -1, id, func)
66
67#event pro akutalizaci Trace tabu
68class ResultEvent(wx.PyEvent):
69   """Simple event to carry arbitrary result data."""
70   def __init__(self, data):
71       """Init Result Event."""
72       wx.PyEvent.__init__(self)
73       self.SetEventType(EVT_RESULT_ID)
74       self.data = data
75
76#event pro aktualizaci DTC tabu
77EVT_DTC_ID = 1001
78class DTCEvent(wx.PyEvent):
79   """Simple event to carry arbitrary result data."""
80   def __init__(self, data):
81       """Init Result Event."""
82       wx.PyEvent.__init__(self)
83       self.SetEventType(EVT_DTC_ID)
84       self.data = data
85
86#event pro aktualizaci status tabu
87EVT_STATUS_ID = 1002
88class StatusEvent(wx.PyEvent):
89   """Simple event to carry arbitrary result data."""
90   def __init__(self, data):
91       """Init Result Event."""
92       wx.PyEvent.__init__(self)
93       self.SetEventType(EVT_STATUS_ID)
94       self.data = data
95
96#event pro aktualizaci tests tabu
97EVT_TESTS_ID = 1003
98class TestEvent(wx.PyEvent):
99   """Simple event to carry arbitrary result data."""
100   def __init__(self, data):
101       """Init Result Event."""
102       wx.PyEvent.__init__(self)
103       self.SetEventType(EVT_TESTS_ID)
104       self.data = data
105
106#defines notification event for debug tracewindow
107from debugEvent import *
108
109class MyApp(wx.App):
110    # A listctrl which auto-resizes the column boxes to fill
111    class MyListCtrl(wx.ListCtrl, ListCtrlAutoWidthMixin):
112        def __init__(self, parent, id, pos = wx.DefaultPosition,
113                     size = wx.DefaultSize, style = 0):
114            wx.ListCtrl.__init__(self,parent,id,pos,size,style)
115            ListCtrlAutoWidthMixin.__init__(self)
116
117    class sensorProducer(threading.Thread):
118        def __init__(self, _notify_window,portName,SERTIMEOUT,RECONNATTEMPTS,_nb):
119            from Queue import Queue
120            self.portName = portName
121            self.RECONNATTEMPTS=RECONNATTEMPTS
122            self.SERTIMEOUT=SERTIMEOUT
123            self.port = None
124            self._notify_window=_notify_window
125            self._nb=_nb
126            threading.Thread.__init__ ( self )
127
128        def initCommunication(self):
129            self.port     = obd_io.OBDPort(self.portName,self._notify_window,self.SERTIMEOUT,self.RECONNATTEMPTS)
130
131            if self.port.State==0: #Cant open serial port
132                return None
133
134            self.active   = []
135            self.supp     = self.port.sensor(0)[1] #read supported PIDS
136
137            self.active.append(1); #PID 0 is always supported
138
139            wx.PostEvent(self._notify_window, ResultEvent([0,0,"X"]))
140            wx.PostEvent(self._notify_window, DebugEvent([1,"Communication initialized..."]))
141
142            for i in range(1, len(self.supp)):
143                if self.supp[i-1] == "1": #put X in coloum if PID is supported
144                    self.active.append(1)
145                    wx.PostEvent(self._notify_window, ResultEvent([i,0,"X"]))
146                else:
147                    self.active.append(0)
148                    wx.PostEvent(self._notify_window, ResultEvent([i,0,""]))
149            return "OK"
150
151        def run(self):
152            wx.PostEvent(self._notify_window, StatusEvent([0,1,"Connecting...."]))
153            self.initCommunication()
154            if self.port.State==0: #cant connect, exit thread
155              self.stop()
156              wx.PostEvent(self._notify_window, StatusEvent([666])) #signal apl, that communication was disconnected
157              wx.PostEvent(self._notify_window, StatusEvent([0,1,"Error cant connect..."]))
158              return None
159
160            wx.PostEvent(self._notify_window, StatusEvent([0,1,"Connected"]))
161            wx.PostEvent(self._notify_window, StatusEvent([2,1,self.port.ELMver]))
162            prevstate=-1
163            curstate=-1
164            while self._notify_window.ThreadControl!=666:
165                prevstate=curstate
166                curstate=self._nb.GetSelection()
167                if curstate==0: #show status tab
168                  pass
169                elif curstate==1: #show tests tab
170                  res=self.port.get_tests_MIL()
171                  for i in range(0,len(res)):
172                    wx.PostEvent(self._notify_window, TestEvent([i,1,res[i]]))
173
174                elif curstate==2: #show sensor tab
175                  for i in range(3, len(self.active)):
176                      if self.active[i]:
177                          s = self.port.sensor(i)
178                          wx.PostEvent(self._notify_window, ResultEvent([i,2,"%s (%s)" % (s[1], s[2])]))
179                      if self._notify_window.ThreadControl==666:
180                          break
181                elif curstate==3: #show DTC tab
182                  if self._notify_window.ThreadControl == 1: #clear DTC
183                      self.port.clear_dtc()
184
185                      if self._notify_window.ThreadControl==666: #before reset ThreadControl we must check if main thread did not want us to finish
186                          break
187
188                      self._notify_window.ThreadControl=0
189                      prevstate=-1 # to reread DTC
190                  if self._notify_window.ThreadControl == 2: #reread DTC
191                      prevstate=-1
192
193                      if self._notify_window.ThreadControl==666:
194                          break
195
196                      self._notify_window.ThreadControl=0
197                  if prevstate!=3:
198                    wx.PostEvent(self._notify_window, DTCEvent(0)) #clear list
199                    DTCCodes=self.port.get_dtc()
200                    if len(DTCCodes)==0:
201                      wx.PostEvent(self._notify_window, DTCEvent(["","","No DTC codes (codes cleared)"]))
202                    for i in range (0,len(DTCCodes)):
203                      wx.PostEvent(self._notify_window, DTCEvent([DTCCodes[i][1],DTCCodes[i][0],pcodes[DTCCodes[i][1]]]))
204                else:
205                 pass
206            self.stop()
207
208        def off(self, id):
209            if id >= 0 and id < len(self.active):
210                self.active[id] = 0
211            else:
212                debug("Invalid sensor id")
213        def on(self, id):
214            if id >= 0 and id < len(self.active):
215                self.active[id] = 1
216            else:
217                debug("Invalid sensor id")
218
219        def all_off(self):
220            for i in range(0, len(self.active)):
221                self.off(i)
222        def all_on(self):
223            for i in range(0, len(self.active)):
224                self.off(i)
225
226        def stop(self):
227            if self.port != None: #if stop is called before any connection port is not defined (and not connected )
228              self.port.close()
229            wx.PostEvent(self._notify_window, StatusEvent([0,1,"Disconnected"]))
230            wx.PostEvent(self._notify_window, StatusEvent([2,1,"----"]))
231
232  #class producer end
233
234    def sensor_control_on(self): #after connection enable few buttons
235        self.settingmenu.Enable(ID_CONFIG,False)
236        self.settingmenu.Enable(ID_RESET,False)
237        self.settingmenu.Enable(ID_DISCONNECT,True)
238        self.dtcmenu.Enable(ID_GETC,True)
239        self.dtcmenu.Enable(ID_CLEAR,True)
240        self.GetDTCButton.Enable(True)
241        self.ClearDTCButton.Enable(True)
242
243        def sensor_toggle(e):
244            sel = e.m_itemIndex
245            state = self.senprod.active[sel]
246            print sel, state
247            if   state == 0:
248                self.senprod.on(sel)
249                self.sensors.SetStringItem(sel,1,"1")
250            elif state == 1:
251                self.senprod.off(sel)
252                self.sensors.SetStringItem(sel,1,"0")
253            else:
254                debug("Incorrect sensor state")
255
256        self.sensors.Bind(wx.EVT_LIST_ITEM_ACTIVATED,sensor_toggle,id=self.sensor_id)
257
258    def sensor_control_off(self): #after disconnect disable fer buttons
259        self.dtcmenu.Enable(ID_GETC,False)
260        self.dtcmenu.Enable(ID_CLEAR,False)
261        self.settingmenu.Enable(ID_DISCONNECT,False)
262        self.settingmenu.Enable(ID_CONFIG,True)
263        self.settingmenu.Enable(ID_RESET,True)
264        self.GetDTCButton.Enable(False)
265        self.ClearDTCButton.Enable(False)
266        #http://pyserial.sourceforge.net/                                                    empty function
267        #EVT_LIST_ITEM_ACTIVATED(self.sensors,self.sensor_id, lambda : None)
268
269    def build_sensor_page(self):
270        HOFFSET_LIST=0
271        tID = wx.NewId()
272        self.sensor_id = tID
273        panel = wx.Panel(self.nb, -1)
274
275        self.sensors = self.MyListCtrl(panel, tID, pos=wx.Point(0,HOFFSET_LIST),
276                                  style=
277                                  wx.LC_REPORT     |
278                                  wx.SUNKEN_BORDER |
279                                  wx.LC_HRULES     |
280                                  wx.LC_SINGLE_SEL)
281
282
283        self.sensors.InsertColumn(0, "Supported",width=70)
284        self.sensors.InsertColumn(1, "Sensor",format=wx.LIST_FORMAT_RIGHT, width=250)
285        self.sensors.InsertColumn(2, "Value")
286        for i in range(0, len(obd_io.obd_sensors.SENSORS)):
287            s = obd_io.obd_sensors.SENSORS[i].name
288            self.sensors.InsertStringItem(i, "")
289            self.sensors.SetStringItem(i, 1, s)
290
291
292        ####################################################################
293        # This little bit of magic keeps the list the same size as the frame
294        def OnPSize(e, win = panel):
295            panel.SetSize(e.GetSize())
296            self.sensors.SetSize(e.GetSize())
297            w,h = self.frame.GetClientSizeTuple()
298            self.sensors.SetDimensions(0,HOFFSET_LIST, w-10 , h - 35 )
299
300        panel.Bind(wx.EVT_SIZE,OnPSize)
301        ####################################################################
302
303        self.nb.AddPage(panel, "Sensors")
304
305    def build_DTC_page(self):
306        HOFFSET_LIST=30 #offset from the top of panel (space for buttons)
307        tID = wx.NewId()
308        self.DTCpanel = wx.Panel(self.nb, -1)
309        self.GetDTCButton  = wx.Button(self.DTCpanel,-1 ,"Get DTC" , wx.Point(15,0))
310        self.ClearDTCButton = wx.Button(self.DTCpanel,-1,"Clear DTC", wx.Point(100,0))
311
312        #bind functions to button click action
313        self.DTCpanel.Bind(wx.EVT_BUTTON,self.GetDTC,self.GetDTCButton)
314        self.DTCpanel.Bind(wx.EVT_BUTTON,self.QueryClear,self.ClearDTCButton)
315
316        self.dtc = self.MyListCtrl(self.DTCpanel,tID, pos=wx.Point(0,HOFFSET_LIST),
317                          style=wx.LC_REPORT|wx.SUNKEN_BORDER|wx.LC_HRULES|wx.LC_SINGLE_SEL)
318
319        self.dtc.InsertColumn(0, "Code", width=100)
320        self.dtc.InsertColumn(1, "Status",width=100)
321        self.dtc.InsertColumn(2, "Trouble code")
322        ####################################################################
323        # This little bit of magic keeps the list the same size as the frame
324        def OnPSize(e, win = self.DTCpanel):
325            self.DTCpanel.SetSize(e.GetSize())
326            self.dtc.SetSize(e.GetSize())
327            w,h = self.frame.GetClientSizeTuple()
328            # I have no idea where 70 comes from
329            self.dtc.SetDimensions(0,HOFFSET_LIST, w-16 , h - 70 )
330
331        self.DTCpanel.Bind(wx.EVT_SIZE,OnPSize)
332        ####################################################################
333
334        self.nb.AddPage(self.DTCpanel, "DTC")
335
336    def TraceDebug(self,level,msg):
337        if self.DEBUGLEVEL<=level:
338            self.trace.Append([str(level),msg])
339
340    def OnInit(self):
341        self.ThreadControl = 0 #say thread what to do
342        self.COMPORT = 0
343        self.senprod = None
344        self.DEBUGLEVEL = 0 #debug everthing
345
346        tID = wx.NewId()
347
348        #read settings from file
349        self.config = ConfigParser.RawConfigParser()
350
351        #print platform.system()
352        #print platform.mac_ver()[]
353
354        if "OS" in os.environ.keys(): #runnig under windows
355          self.configfilepath="pyobd.ini"
356        else:
357          self.configfilepath=os.environ['HOME']+'/.pyobdrc'
358        if self.config.read(self.configfilepath)==[]:
359          self.COMPORT="/dev/ttyU0"
360          self.RECONNATTEMPTS=5
361          self.SERTIMEOUT=2
362        else:
363          self.COMPORT=self.config.get("pyOBD","COMPORT")
364          self.RECONNATTEMPTS=self.config.getint("pyOBD","RECONNATTEMPTS")
365          self.SERTIMEOUT=self.config.getint("pyOBD","SERTIMEOUT")
366
367        frame = wx.Frame(None, -1, "pyOBD-II")
368        self.frame=frame
369
370        EVT_RESULT(self,self.OnResult,EVT_RESULT_ID)
371        EVT_RESULT(self,self.OnDebug, EVT_DEBUG_ID)
372        EVT_RESULT(self,self.OnDtc,EVT_DTC_ID)
373        EVT_RESULT(self,self.OnStatus,EVT_STATUS_ID)
374        EVT_RESULT(self,self.OnTests,EVT_TESTS_ID)
375
376        # Main notebook frames
377        self.nb = wx.Notebook(frame, -1, style = wx.NB_TOP)
378
379        self.status = self.MyListCtrl(self.nb, tID,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
380        self.status.InsertColumn(0, "Description",width=200)
381        self.status.InsertColumn(1, "Value")
382        self.status.Append(["Link State","Disconnnected"]);
383        self.status.Append(["Protocol","---"]);
384        self.status.Append(["Cable version","---"]);
385        self.status.Append(["COM port",self.COMPORT]);
386
387        self.nb.AddPage(self.status, "Status")
388
389        self.OBDTests = self.MyListCtrl(self.nb, tID,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
390        self.OBDTests.InsertColumn(0, "Description",width=200)
391        self.OBDTests.InsertColumn(1, "Value")
392        self.nb.AddPage(self.OBDTests, "Tests")
393
394        for i in range(0,len(ptest)): #fill MODE 1 PID 1 test description
395          self.OBDTests.Append([ptest[i],"---"]);
396
397        self.build_sensor_page()
398
399        self.build_DTC_page()
400
401        self.trace = self.MyListCtrl(self.nb, tID,style=wx.LC_REPORT|wx.SUNKEN_BORDER)
402        self.trace.InsertColumn(0, "Level",width=40)
403        self.trace.InsertColumn(1, "Message")
404        self.nb.AddPage(self.trace, "Trace")
405        self.TraceDebug(1,"Application started")
406
407        # Setting up the menu.
408        self.filemenu= wx.Menu()
409        self.filemenu.Append(ID_EXIT,"E&xit"," Terminate the program")
410
411        self.settingmenu = wx.Menu()
412        self.settingmenu.Append(ID_CONFIG,"Configure"," Configure pyOBD")
413        self.settingmenu.Append(ID_RESET,"Connect"," Reopen and connect to device")
414        self.settingmenu.Append(ID_DISCONNECT,"Disconnect","Close connection to device")
415
416        self.dtcmenu= wx.Menu()
417        # tady toto nastavi automaticky tab DTC a provede akci
418        self.dtcmenu.Append(ID_GETC  ,"Get DTCs",   " Get DTC Codes")
419        self.dtcmenu.Append(ID_CLEAR ,"Clear DTC",  " Clear DTC Codes")
420        self.dtcmenu.Append(ID_LOOK  ,"Code Lookup"," Lookup DTC Codes")
421
422        self.helpmenu = wx.Menu()
423
424        self.helpmenu.Append(ID_HELP_ABOUT  ,"About this program",   " Get DTC Codes")
425        self.helpmenu.Append(ID_HELP_VISIT  ,"Visit program homepage"," Lookup DTC Codes")
426        self.helpmenu.Append(ID_HELP_ORDER ,"Order OBD-II interface",  " Clear DTC Codes")
427
428
429        # Creating the menubar.
430        self.menuBar = wx.MenuBar()
431        self.menuBar.Append(self.filemenu,"&File") # Adding the "filemenu" to the MenuBar
432        self.menuBar.Append(self.settingmenu,"&OBD-II")
433        self.menuBar.Append(self.dtcmenu,"&Trouble codes")
434        self.menuBar.Append(self.helpmenu,"&Help")
435
436        frame.SetMenuBar(self.menuBar)  # Adding the MenuBar to the Frame content.
437
438        frame.Bind(wx.EVT_MENU,self.OnExit,id=ID_EXIT)# attach the menu-event ID_EXIT to the
439        frame.Bind(wx.EVT_MENU,self.QueryClear,id=ID_CLEAR)
440        frame.Bind(wx.EVT_MENU,self.Configure,id=ID_CONFIG)
441        frame.Bind(wx.EVT_MENU,self.OpenPort,id=ID_RESET)
442        frame.Bind(wx.EVT_MENU,self.OnDisconnect,id=ID_DISCONNECT)
443        frame.Bind(wx.EVT_MENU,self.GetDTC,id=ID_GETC)
444        frame.Bind(wx.EVT_MENU,self.CodeLookup,id=ID_LOOK)
445        frame.Bind(wx.EVT_MENU,self.OnHelpAbout,id=ID_HELP_ABOUT)
446        frame.Bind(wx.EVT_MENU,self.OnHelpVisit,id=ID_HELP_VISIT)
447        frame.Bind(wx.EVT_MENU,self.OnHelpOrder,id=ID_HELP_ORDER)
448
449        self.SetTopWindow(frame)
450
451        frame.Show(True)
452        frame.SetSize((520,400))
453        self.sensor_control_off()
454
455        return True
456
457    def OnHelpVisit(self,event):
458        webbrowser.open("http://www.obdtester.com/pyobd")
459
460    def OnHelpOrder(self,event):
461        webbrowser.open("http://www.obdtester.com/order")
462
463    def OnHelpAbout(self,event): #todo about box
464        Text = """  PyOBD is an automotive OBD2 diagnosting application using ELM237 cable.
465
466(C) 2008-2009 SeCons Ltd.
467(C) 2004 Charles Donour Sizemore
468
469http://www.obdtester.com/
470http://www.secons.com/
471
472  PyOBD is free software; you can redistribute it and/or modify
473it under the terms of the GNU General Public License as published by the Free Software Foundation;
474either version 2 of the License, or (at your option) any later version.
475
476  PyOBD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
477without even the implied warranty of MEHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
478See the GNU General Public License for more details. You should have received a copy of
479the GNU General Public License along with PyOBD; if not, write to
480the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
481"""
482
483        #HelpAboutDlg = wx.Dialog(self.frame, id, title="About")
484
485
486        #box  = wx.BoxSizer(wx.HORIZONTAL)
487        #box.Add(wx.StaticText(reconnectPanel,-1,Text,pos=(0,0),size=(200,200)))
488        #box.Add(wx.Button(HelpAboutDlg,wx.ID_OK),0)
489        #box.Add(wx.Button(HelpAboutDlg,wx.ID_CANCEL),1)
490
491        #HelpAboutDlg.SetSizer(box)
492        #HelpAboutDlg.SetAutoLayout(True)
493        #sizer.Fit(HelpAboutDlg)
494        #HelpAboutDlg.ShowModal()
495
496        self.HelpAboutDlg = wx.MessageDialog(self.frame, Text, 'About',wx.OK | wx.ICON_INFORMATION)
497        self.HelpAboutDlg.ShowModal()
498        self.HelpAboutDlg.Destroy()
499
500    def OnResult(self,event):
501        self.sensors.SetStringItem(event.data[0], event.data[1], event.data[2])
502
503    def OnStatus(self,event):
504        if event.data[0] == 666: #signal, that connection falied
505            self.sensor_control_off()
506        else:
507            self.status.SetStringItem(event.data[0], event.data[1], event.data[2])
508
509    def OnTests(self,event):
510        self.OBDTests.SetStringItem(event.data[0], event.data[1], event.data[2])
511
512    def OnDebug(self,event):
513        self.TraceDebug(event.data[0],event.data[1])
514
515    def OnDtc(self,event):
516        if event.data == 0: #signal, that DTC was cleared
517          self.dtc.DeleteAllItems()
518        else:
519          self.dtc.Append(event.data)
520
521    def OnDisconnect(self,event): #disconnect connection to ECU
522        self.ThreadControl=666
523        self.sensor_control_off()
524
525    def OpenPort(self,e):
526
527        if self.senprod: # signal current producers to finish
528            self.senprod.stop()
529        self.ThreadControl = 0
530        self.senprod = self.sensorProducer(self,self.COMPORT,self.SERTIMEOUT,self.RECONNATTEMPTS,self.nb)
531        self.senprod.start()
532
533        self.sensor_control_on()
534
535    def GetDTC(self,e):
536        self.nb.SetSelection(3)
537        self.ThreadControl=2
538
539    def AddDTC(self, code):
540        self.dtc.InsertStringItem(0, "")
541        self.dtc.SetStringItem(0, 0, code[0])
542        self.dtc.SetStringItem(0, 1, code[1])
543
544
545    def CodeLookup(self,e = None):
546        id = 0
547        diag = wx.Frame(None, id, title="Diagnostic Trouble Codes")
548
549        tree = wx.TreeCtrl(diag, id, style = wx.TR_HAS_BUTTONS)
550
551        root = tree.AddRoot("Code Reference")
552        proot = root; # tree.AppendItem(root,"Powertrain (P) Codes")
553        codes = pcodes.keys()
554        codes.sort()
555        group = ""
556        for c in codes:
557            if c[:3] != group:
558                group_root = tree.AppendItem(proot, c[:3]+"XX")
559                group = c[:3]
560            leaf = tree.AppendItem(group_root, c)
561            tree.AppendItem(leaf, pcodes[c])
562
563        diag.SetSize((400,500))
564        diag.Show(True)
565
566
567    def QueryClear(self,e):
568        id = 0
569        diag = wx.Dialog(self.frame, id, title="Clear DTC?")
570
571        sizer = wx.BoxSizer(wx.VERTICAL)
572        sizer.Add(wx.StaticText(diag, -1, "Are you sure you wish to"),0)
573        sizer.Add(wx.StaticText(diag, -1, "clear all DTC codes and "),0)
574        sizer.Add(wx.StaticText(diag, -1, "freeze frame data?      "),0)
575        box  = wx.BoxSizer(wx.HORIZONTAL)
576        box.Add(wx.Button(diag,wx.ID_OK,     "Ok"    ),0)
577        box.Add(wx.Button(diag,wx.ID_CANCEL, "Cancel"),0)
578
579        sizer.Add(box, 0)
580        diag.SetSizer(sizer)
581        diag.SetAutoLayout(True)
582        sizer.Fit(diag)
583        r  = diag.ShowModal()
584        if r == wx.ID_OK:
585            self.ClearDTC()
586
587    def ClearDTC(self):
588        self.ThreadControl=1
589        self.nb.SetSelection(3)
590
591
592    def scanSerial(self):
593        """scan for available ports. return a list of serial names"""
594        available = []
595        available.append("/dev/ttyU0")
596
597        # ELM-USB shows up as /dev/tty.usbmodemXXXX, where XXXX is a changing hex string
598        # on connection; so we have to search through all 64K options
599        if len(platform.mac_ver()[0])!=0:  #search only on MAC
600          for i in range (65535):
601            extension = hex(i).replace("0x","", 1)
602            try:
603              s = serial.Serial("/dev/tty.usbmodem"+extension)
604              available.append(s.portstr)
605              s.close()
606            except serial.SerialException:
607              pass
608
609        return available
610
611    def Configure(self,e = None):
612        id = 0
613        diag = wx.Dialog(self.frame, id, title="Configure")
614        sizer = wx.BoxSizer(wx.VERTICAL)
615
616        ports = self.scanSerial()
617        rb = wx.RadioBox(diag, id, "Choose Serial Port",
618                        choices = ports, style = wx.RA_SPECIFY_COLS,
619                        majorDimension = 2)
620
621        sizer.Add(rb, 0)
622
623        #timeOut input control
624        timeoutPanel = wx.Panel(diag, -1)
625        timeoutCtrl = wx.TextCtrl(timeoutPanel, -1, '',pos=(140,0), size=(35, 25))
626        timeoutStatic = wx.StaticText(timeoutPanel,-1,'Timeout:',pos=(3,5),size=(140,20))
627        timeoutCtrl.SetValue(str(self.SERTIMEOUT))
628
629        #reconnect attempt input control
630        reconnectPanel = wx.Panel(diag, -1)
631        reconnectCtrl = wx.TextCtrl(reconnectPanel, -1, '',pos=(140,0), size=(35, 25))
632        reconnectStatic = wx.StaticText(reconnectPanel,-1,'Reconnect attempts:',pos=(3,5),size=(140,20))
633        reconnectCtrl.SetValue(str(self.RECONNATTEMPTS))
634
635        #web open link button
636        self.OpenLinkButton = wx.Button(diag,-1,"Click here to order ELM-USB interface",size=(260,30))
637        diag.Bind(wx.EVT_BUTTON,self.OnHelpOrder,self.OpenLinkButton)
638
639        #set actual serial port choice
640        if (self.COMPORT != 0) and (self.COMPORT in ports):
641          rb.SetSelection(ports.index(self.COMPORT))
642
643
644        sizer.Add(self.OpenLinkButton)
645        sizer.Add(timeoutPanel,0)
646        sizer.Add(reconnectPanel,0)
647
648        box  = wx.BoxSizer(wx.HORIZONTAL)
649        box.Add(wx.Button(diag,wx.ID_OK),0)
650        box.Add(wx.Button(diag,wx.ID_CANCEL),1)
651
652        sizer.Add(box, 0)
653        diag.SetSizer(sizer)
654        diag.SetAutoLayout(True)
655        sizer.Fit(diag)
656        r  = diag.ShowModal()
657        if r == wx.ID_OK:
658
659            #create section
660            if self.config.sections()==[]:
661              self.config.add_section("pyOBD")
662            #set and save COMPORT
663            self.COMPORT = ports[rb.GetSelection()]
664            self.config.set("pyOBD","COMPORT",self.COMPORT)
665
666            #set and save SERTIMEOUT
667            self.SERTIMEOUT = int(timeoutCtrl.GetValue())
668            self.config.set("pyOBD","SERTIMEOUT",self.SERTIMEOUT)
669            self.status.SetStringItem(3,1,self.COMPORT);
670
671            #set and save RECONNATTEMPTS
672            self.RECONNATTEMPTS = int(reconnectCtrl.GetValue())
673            self.config.set("pyOBD","RECONNATTEMPTS",self.RECONNATTEMPTS)
674
675            #write configuration to cfg file
676            self.config.write(open(self.configfilepath, 'wb'))
677
678
679    def OnExit(self,e = None):
680        import sys
681        sys.exit(0)
682
683app = MyApp(0)
684app.MainLoop()
685