blob: 646c2727704b8f5f311e27c6ae9ee48f637116e6 [file] [log] [blame]
cliechti80a0ed12003-10-03 23:53:42 +00001#!/usr/bin/env python
2# generated by wxGlade 0.3.1 on Fri Oct 03 23:23:45 2003
3
cliechtid5d51982008-04-10 23:48:55 +00004#from wxPython.wx import *
5import wx
cliechti80a0ed12003-10-03 23:53:42 +00006import wxSerialConfigDialog
7import serial
8import threading
9
cliechti40d71f62004-07-09 22:14:17 +000010#----------------------------------------------------------------------
11# Create an own event type, so that GUI updates can be delegated
12# this is required as on some platforms only the main thread can
13# access the GUI without crashing. wxMutexGuiEnter/wxMutexGuiLeave
14# could be used too, but an event is more elegant.
15
cliechtid5d51982008-04-10 23:48:55 +000016SERIALRX = wx.NewEventType()
cliechtibc318d42004-11-13 03:13:12 +000017# bind to serial data receive events
cliechtid5d51982008-04-10 23:48:55 +000018EVT_SERIALRX = wx.PyEventBinder(SERIALRX, 0)
cliechti40d71f62004-07-09 22:14:17 +000019
cliechtid5d51982008-04-10 23:48:55 +000020class SerialRxEvent(wx.PyCommandEvent):
cliechti40d71f62004-07-09 22:14:17 +000021 eventType = SERIALRX
22 def __init__(self, windowID, data):
cliechtid5d51982008-04-10 23:48:55 +000023 wx.PyCommandEvent.__init__(self, self.eventType, windowID)
cliechti40d71f62004-07-09 22:14:17 +000024 self.data = data
25
26 def Clone(self):
27 self.__class__(self.GetId(), self.data)
28
29#----------------------------------------------------------------------
30
cliechtid5d51982008-04-10 23:48:55 +000031ID_CLEAR = wx.NewId()
32ID_SAVEAS = wx.NewId()
33ID_SETTINGS = wx.NewId()
34ID_TERM = wx.NewId()
35ID_EXIT = wx.NewId()
cliechti80a0ed12003-10-03 23:53:42 +000036
37NEWLINE_CR = 0
38NEWLINE_LF = 1
39NEWLINE_CRLF = 2
40
41class TerminalSetup:
cliechti0eb86712003-10-04 00:49:04 +000042 """Placeholder for various terminal settings. Used to pass the
43 options to the TerminalSettingsDialog."""
cliechti80a0ed12003-10-03 23:53:42 +000044 def __init__(self):
45 self.echo = False
46 self.unprintable = False
47 self.newline = NEWLINE_CRLF
48
cliechtid5d51982008-04-10 23:48:55 +000049class TerminalSettingsDialog(wx.Dialog):
cliechti0eb86712003-10-04 00:49:04 +000050 """Simple dialog with common terminal settings like echo, newline mode."""
cliechti80a0ed12003-10-03 23:53:42 +000051
52 def __init__(self, *args, **kwds):
53 self.settings = kwds['settings']
54 del kwds['settings']
55 # begin wxGlade: TerminalSettingsDialog.__init__
cliechtid5d51982008-04-10 23:48:55 +000056 kwds["style"] = wx.DEFAULT_DIALOG_STYLE
57 wx.Dialog.__init__(self, *args, **kwds)
58 self.checkbox_echo = wx.CheckBox(self, -1, "Local Echo")
59 self.checkbox_unprintable = wx.CheckBox(self, -1, "Show unprintable characters")
60 self.radio_box_newline = wx.RadioBox(self, -1, "Newline Handling", choices=["CR only", "LF only", "CR+LF"], majorDimension=0, style=wx.RA_SPECIFY_ROWS)
61 self.button_ok = wx.Button(self, -1, "OK")
62 self.button_cancel = wx.Button(self, -1, "Cancel")
cliechti80a0ed12003-10-03 23:53:42 +000063
64 self.__set_properties()
65 self.__do_layout()
66 # end wxGlade
67 self.__attach_events()
68 self.checkbox_echo.SetValue(self.settings.echo)
69 self.checkbox_unprintable.SetValue(self.settings.unprintable)
70 self.radio_box_newline.SetSelection(self.settings.newline)
71
72 def __set_properties(self):
73 # begin wxGlade: TerminalSettingsDialog.__set_properties
74 self.SetTitle("Terminal Settings")
75 self.radio_box_newline.SetSelection(0)
76 self.button_ok.SetDefault()
77 # end wxGlade
78
79 def __do_layout(self):
80 # begin wxGlade: TerminalSettingsDialog.__do_layout
cliechtid5d51982008-04-10 23:48:55 +000081 sizer_2 = wx.BoxSizer(wx.VERTICAL)
82 sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
83 sizer_4 = wx.StaticBoxSizer(wx.StaticBox(self, -1, "Input/Output"), wx.VERTICAL)
84 sizer_4.Add(self.checkbox_echo, 0, wx.ALL, 4)
85 sizer_4.Add(self.checkbox_unprintable, 0, wx.ALL, 4)
cliechti80a0ed12003-10-03 23:53:42 +000086 sizer_4.Add(self.radio_box_newline, 0, 0, 0)
cliechtid5d51982008-04-10 23:48:55 +000087 sizer_2.Add(sizer_4, 0, wx.EXPAND, 0)
cliechti80a0ed12003-10-03 23:53:42 +000088 sizer_3.Add(self.button_ok, 0, 0, 0)
89 sizer_3.Add(self.button_cancel, 0, 0, 0)
cliechtid5d51982008-04-10 23:48:55 +000090 sizer_2.Add(sizer_3, 0, wx.ALL|wx.ALIGN_RIGHT, 4)
cliechti80a0ed12003-10-03 23:53:42 +000091 self.SetAutoLayout(1)
92 self.SetSizer(sizer_2)
93 sizer_2.Fit(self)
94 sizer_2.SetSizeHints(self)
95 self.Layout()
96 # end wxGlade
97
98 def __attach_events(self):
cliechtid5d51982008-04-10 23:48:55 +000099 self.Bind(wx.EVT_BUTTON, self.OnOK, id = self.button_ok.GetId())
100 self.Bind(wx.EVT_BUTTON, self.OnCancel, id = self.button_cancel.GetId())
cliechti80a0ed12003-10-03 23:53:42 +0000101
102 def OnOK(self, events):
cliechti0eb86712003-10-04 00:49:04 +0000103 """Update data wil new values and close dialog."""
cliechti80a0ed12003-10-03 23:53:42 +0000104 self.settings.echo = self.checkbox_echo.GetValue()
105 self.settings.unprintable = self.checkbox_unprintable.GetValue()
106 self.settings.newline = self.radio_box_newline.GetSelection()
cliechtid5d51982008-04-10 23:48:55 +0000107 self.EndModal(wx.ID_OK)
cliechti80a0ed12003-10-03 23:53:42 +0000108
109 def OnCancel(self, events):
cliechti0eb86712003-10-04 00:49:04 +0000110 """Do not update data but close dialog."""
cliechtid5d51982008-04-10 23:48:55 +0000111 self.EndModal(wx.ID_CANCEL)
cliechti80a0ed12003-10-03 23:53:42 +0000112
113# end of class TerminalSettingsDialog
114
115
cliechtid5d51982008-04-10 23:48:55 +0000116class TerminalFrame(wx.Frame):
cliechti80a0ed12003-10-03 23:53:42 +0000117 """Simple terminal program for wxPython"""
118
119 def __init__(self, *args, **kwds):
120 self.serial = serial.Serial()
cliechtibc318d42004-11-13 03:13:12 +0000121 self.serial.timeout = 0.5 #make sure that the alive event can be checked from time to time
cliechti0eb86712003-10-04 00:49:04 +0000122 self.settings = TerminalSetup() #placeholder for the settings
cliechti80a0ed12003-10-03 23:53:42 +0000123 self.thread = None
cliechtibc318d42004-11-13 03:13:12 +0000124 self.alive = threading.Event()
cliechti80a0ed12003-10-03 23:53:42 +0000125 # begin wxGlade: TerminalFrame.__init__
cliechtid5d51982008-04-10 23:48:55 +0000126 kwds["style"] = wx.DEFAULT_FRAME_STYLE
127 wx.Frame.__init__(self, *args, **kwds)
128 self.text_ctrl_output = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.TE_READONLY)
cliechti80a0ed12003-10-03 23:53:42 +0000129
130 # Menu Bar
cliechtid5d51982008-04-10 23:48:55 +0000131 self.frame_terminal_menubar = wx.MenuBar()
cliechti80a0ed12003-10-03 23:53:42 +0000132 self.SetMenuBar(self.frame_terminal_menubar)
cliechtid5d51982008-04-10 23:48:55 +0000133 wxglade_tmp_menu = wx.Menu()
134 wxglade_tmp_menu.Append(ID_CLEAR, "&Clear", "", wx.ITEM_NORMAL)
135 wxglade_tmp_menu.Append(ID_SAVEAS, "&Save Text As...", "", wx.ITEM_NORMAL)
cliechti80a0ed12003-10-03 23:53:42 +0000136 wxglade_tmp_menu.AppendSeparator()
cliechtid5d51982008-04-10 23:48:55 +0000137 wxglade_tmp_menu.Append(ID_SETTINGS, "&Port Settings...", "", wx.ITEM_NORMAL)
138 wxglade_tmp_menu.Append(ID_TERM, "&Terminal Settings...", "", wx.ITEM_NORMAL)
cliechti80a0ed12003-10-03 23:53:42 +0000139 wxglade_tmp_menu.AppendSeparator()
cliechtid5d51982008-04-10 23:48:55 +0000140 wxglade_tmp_menu.Append(ID_EXIT, "&Exit", "", wx.ITEM_NORMAL)
cliechti80a0ed12003-10-03 23:53:42 +0000141 self.frame_terminal_menubar.Append(wxglade_tmp_menu, "&File")
142 # Menu Bar end
cliechti80a0ed12003-10-03 23:53:42 +0000143
144 self.__set_properties()
145 self.__do_layout()
146 # end wxGlade
cliechti0eb86712003-10-04 00:49:04 +0000147 self.__attach_events() #register events
cliechti80a0ed12003-10-03 23:53:42 +0000148 self.OnPortSettings(None) #call setup dialog on startup, opens port
cliechtibc318d42004-11-13 03:13:12 +0000149 if not self.alive.isSet():
cliechti0eb86712003-10-04 00:49:04 +0000150 self.Close()
cliechti80a0ed12003-10-03 23:53:42 +0000151
152 def StartThread(self):
cliechtibc318d42004-11-13 03:13:12 +0000153 """Start the receiver thread"""
cliechti80a0ed12003-10-03 23:53:42 +0000154 self.thread = threading.Thread(target=self.ComPortThread)
155 self.thread.setDaemon(1)
cliechtibc318d42004-11-13 03:13:12 +0000156 self.alive.set()
cliechtif7776692005-10-02 21:51:42 +0000157 self.thread.start()
cliechti80a0ed12003-10-03 23:53:42 +0000158
159 def StopThread(self):
cliechti0eb86712003-10-04 00:49:04 +0000160 """Stop the receiver thread, wait util it's finished."""
cliechti80a0ed12003-10-03 23:53:42 +0000161 if self.thread is not None:
cliechtibc318d42004-11-13 03:13:12 +0000162 self.alive.clear() #clear alive event for thread
cliechti80a0ed12003-10-03 23:53:42 +0000163 self.thread.join() #wait until thread has finished
164 self.thread = None
165
166 def __set_properties(self):
167 # begin wxGlade: TerminalFrame.__set_properties
168 self.SetTitle("Serial Terminal")
169 self.SetSize((546, 383))
cliechti80a0ed12003-10-03 23:53:42 +0000170 # end wxGlade
171
172 def __do_layout(self):
173 # begin wxGlade: TerminalFrame.__do_layout
cliechtid5d51982008-04-10 23:48:55 +0000174 sizer_1 = wx.BoxSizer(wx.VERTICAL)
175 sizer_1.Add(self.text_ctrl_output, 1, wx.EXPAND, 0)
cliechti80a0ed12003-10-03 23:53:42 +0000176 self.SetAutoLayout(1)
177 self.SetSizer(sizer_1)
178 self.Layout()
179 # end wxGlade
180
181 def __attach_events(self):
cliechti0eb86712003-10-04 00:49:04 +0000182 #register events at the controls
cliechtid5d51982008-04-10 23:48:55 +0000183 self.Bind(wx.EVT_MENU, self.OnClear, id = ID_CLEAR)
184 self.Bind(wx.EVT_MENU, self.OnSaveAs, id = ID_SAVEAS)
185 self.Bind(wx.EVT_MENU, self.OnExit, id = ID_EXIT)
186 self.Bind(wx.EVT_MENU, self.OnPortSettings, id = ID_SETTINGS)
187 self.Bind(wx.EVT_MENU, self.OnTermSettings, id = ID_TERM)
188 self.text_ctrl_output.Bind(wx.EVT_CHAR, self.OnKey)
cliechtibc318d42004-11-13 03:13:12 +0000189 self.Bind(EVT_SERIALRX, self.OnSerialRead)
cliechtid5d51982008-04-10 23:48:55 +0000190 self.Bind(wx.EVT_CLOSE, self.OnClose)
cliechti80a0ed12003-10-03 23:53:42 +0000191
192 def OnExit(self, event):
193 """Menu point Exit"""
194 self.Close()
195
196 def OnClose(self, event):
cliechti0eb86712003-10-04 00:49:04 +0000197 """Called on application shutdown."""
cliechti80a0ed12003-10-03 23:53:42 +0000198 self.StopThread() #stop reader thread
199 self.serial.close() #cleanup
200 self.Destroy() #close windows, exit app
201
202 def OnSaveAs(self, event):
203 """Save contents of output window."""
204 filename = None
cliechtid5d51982008-04-10 23:48:55 +0000205 dlg = wx.FileDialog(None, "Save Text As...", ".", "", "Text File|*.txt|All Files|*", wx.SAVE)
206 if dlg.ShowModal() == wx.ID_OK:
cliechti80a0ed12003-10-03 23:53:42 +0000207 filename = dlg.GetPath()
208 dlg.Destroy()
209
210 if filename is not None:
211 f = file(filename, 'w')
212 text = self.text_ctrl_output.GetValue()
213 if type(text) == unicode:
214 text = text.encode("latin1") #hm, is that a good asumption?
215 f.write(text)
216 f.close()
217
218 def OnClear(self, event):
219 """Clear contents of output window."""
220 self.text_ctrl_output.Clear()
221
cliechti0eb86712003-10-04 00:49:04 +0000222 def OnPortSettings(self, event=None):
223 """Show the portsettings dialog. The reader thread is stopped for the
224 settings change."""
cliechtibc318d42004-11-13 03:13:12 +0000225 if event is not None: #will be none when called on startup
cliechti0eb86712003-10-04 00:49:04 +0000226 self.StopThread()
227 self.serial.close()
228 ok = False
229 while not ok:
230 dialog_serial_cfg = wxSerialConfigDialog.SerialConfigDialog(None, -1, "",
231 show=wxSerialConfigDialog.SHOW_BAUDRATE|wxSerialConfigDialog.SHOW_FORMAT|wxSerialConfigDialog.SHOW_FLOW,
232 serial=self.serial
233 )
234 result = dialog_serial_cfg.ShowModal()
235 dialog_serial_cfg.Destroy()
236 #open port if not called on startup, open it on startup and OK too
cliechtid5d51982008-04-10 23:48:55 +0000237 if result == wx.ID_OK or event is not None:
cliechti0eb86712003-10-04 00:49:04 +0000238 try:
239 self.serial.open()
240 except serial.SerialException, e:
cliechtid5d51982008-04-10 23:48:55 +0000241 dlg = wx.MessageDialog(None, str(e), "Serial Port Error", wx.OK | wx.ICON_ERROR)
cliechti0eb86712003-10-04 00:49:04 +0000242 dlg.ShowModal()
243 dlg.Destroy()
244 else:
245 self.StartThread()
246 self.SetTitle("Serial Terminal on %s [%s, %s%s%s%s%s]" % (
247 self.serial.portstr,
248 self.serial.baudrate,
249 self.serial.bytesize,
250 self.serial.parity,
251 self.serial.stopbits,
252 self.serial.rtscts and ' RTS/CTS' or '',
253 self.serial.xonxoff and ' Xon/Xoff' or '',
254 )
255 )
256 ok = True
257 else:
258 #on startup, dialog aborted
cliechtibc318d42004-11-13 03:13:12 +0000259 self.alive.clear()
cliechti0eb86712003-10-04 00:49:04 +0000260 ok = True
cliechti80a0ed12003-10-03 23:53:42 +0000261
262 def OnTermSettings(self, event):
263 """Menu point Terminal Settings. Show the settings dialog
264 with the current terminal settings"""
265 dialog = TerminalSettingsDialog(None, -1, "", settings=self.settings)
266 result = dialog.ShowModal()
267 dialog.Destroy()
cliechtibc318d42004-11-13 03:13:12 +0000268
cliechti80a0ed12003-10-03 23:53:42 +0000269 def OnKey(self, event):
270 """Key event handler. if the key is in the ASCII range, write it to the serial port.
271 Newline handling and local echo is also done here."""
272 code = event.GetKeyCode()
273 if code < 256: #is it printable?
274 if code == 13: #is it a newline? (check for CR which is the RETURN key)
275 if self.settings.echo: #do echo if needed
276 self.text_ctrl_output.AppendText('\n')
277 if self.settings.newline == NEWLINE_CR:
278 self.serial.write('\r') #send CR
279 elif self.settings.newline == NEWLINE_LF:
280 self.serial.write('\n') #send LF
281 elif self.settings.newline == NEWLINE_CRLF:
282 self.serial.write('\r\n') #send CR+LF
283 else:
284 char = chr(code)
285 if self.settings.echo: #do echo if needed
286 self.text_ctrl_output.WriteText(char)
287 self.serial.write(char) #send the charcater
cliechti0eb86712003-10-04 00:49:04 +0000288 else:
289 print "Extra Key:", code
cliechti80a0ed12003-10-03 23:53:42 +0000290
cliechti40d71f62004-07-09 22:14:17 +0000291 def OnSerialRead(self, event):
cliechti0eb86712003-10-04 00:49:04 +0000292 """Handle input from the serial port."""
cliechti40d71f62004-07-09 22:14:17 +0000293 text = event.data
cliechti80a0ed12003-10-03 23:53:42 +0000294 if self.settings.unprintable:
295 text = ''.join([(c >= ' ') and c or '<%d>' % ord(c) for c in text])
296 self.text_ctrl_output.AppendText(text)
297
298 def ComPortThread(self):
cliechti0eb86712003-10-04 00:49:04 +0000299 """Thread that handles the incomming traffic. Does the basic input
cliechti40d71f62004-07-09 22:14:17 +0000300 transformation (newlines) and generates an SerialRxEvent"""
cliechtibc318d42004-11-13 03:13:12 +0000301 while self.alive.isSet(): #loop while alive event is true
cliechti80a0ed12003-10-03 23:53:42 +0000302 text = self.serial.read(1) #read one, with timout
303 if text: #check if not timeout
304 n = self.serial.inWaiting() #look if there is more to read
305 if n:
306 text = text + self.serial.read(n) #get it
307 #newline transformation
308 if self.settings.newline == NEWLINE_CR:
309 text = text.replace('\r', '\n')
310 elif self.settings.newline == NEWLINE_LF:
311 pass
312 elif self.settings.newline == NEWLINE_CRLF:
313 text = text.replace('\r\n', '\n')
cliechti40d71f62004-07-09 22:14:17 +0000314 event = SerialRxEvent(self.GetId(), text)
315 self.GetEventHandler().AddPendingEvent(event)
316 #~ self.OnSerialRead(text) #output text in window
cliechti80a0ed12003-10-03 23:53:42 +0000317
318# end of class TerminalFrame
319
320
cliechtid5d51982008-04-10 23:48:55 +0000321class MyApp(wx.App):
cliechti80a0ed12003-10-03 23:53:42 +0000322 def OnInit(self):
cliechtid5d51982008-04-10 23:48:55 +0000323 wx.InitAllImageHandlers()
cliechti80a0ed12003-10-03 23:53:42 +0000324 frame_terminal = TerminalFrame(None, -1, "")
325 self.SetTopWindow(frame_terminal)
326 frame_terminal.Show(1)
327 return 1
328
329# end of class MyApp
330
331if __name__ == "__main__":
332 app = MyApp(0)
333 app.MainLoop()