diff options
Diffstat (limited to 'examples/wxTerminal.py')
-rw-r--r-- | examples/wxTerminal.py | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/examples/wxTerminal.py b/examples/wxTerminal.py new file mode 100644 index 0000000..646c272 --- /dev/null +++ b/examples/wxTerminal.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python +# generated by wxGlade 0.3.1 on Fri Oct 03 23:23:45 2003 + +#from wxPython.wx import * +import wx +import wxSerialConfigDialog +import serial +import threading + +#---------------------------------------------------------------------- +# Create an own event type, so that GUI updates can be delegated +# this is required as on some platforms only the main thread can +# access the GUI without crashing. wxMutexGuiEnter/wxMutexGuiLeave +# could be used too, but an event is more elegant. + +SERIALRX = wx.NewEventType() +# bind to serial data receive events +EVT_SERIALRX = wx.PyEventBinder(SERIALRX, 0) + +class SerialRxEvent(wx.PyCommandEvent): + eventType = SERIALRX + def __init__(self, windowID, data): + wx.PyCommandEvent.__init__(self, self.eventType, windowID) + self.data = data + + def Clone(self): + self.__class__(self.GetId(), self.data) + +#---------------------------------------------------------------------- + +ID_CLEAR = wx.NewId() +ID_SAVEAS = wx.NewId() +ID_SETTINGS = wx.NewId() +ID_TERM = wx.NewId() +ID_EXIT = wx.NewId() + +NEWLINE_CR = 0 +NEWLINE_LF = 1 +NEWLINE_CRLF = 2 + +class TerminalSetup: + """Placeholder for various terminal settings. Used to pass the + options to the TerminalSettingsDialog.""" + def __init__(self): + self.echo = False + self.unprintable = False + self.newline = NEWLINE_CRLF + +class TerminalSettingsDialog(wx.Dialog): + """Simple dialog with common terminal settings like echo, newline mode.""" + + def __init__(self, *args, **kwds): + self.settings = kwds['settings'] + del kwds['settings'] + # begin wxGlade: TerminalSettingsDialog.__init__ + kwds["style"] = wx.DEFAULT_DIALOG_STYLE + wx.Dialog.__init__(self, *args, **kwds) + self.checkbox_echo = wx.CheckBox(self, -1, "Local Echo") + self.checkbox_unprintable = wx.CheckBox(self, -1, "Show unprintable characters") + self.radio_box_newline = wx.RadioBox(self, -1, "Newline Handling", choices=["CR only", "LF only", "CR+LF"], majorDimension=0, style=wx.RA_SPECIFY_ROWS) + self.button_ok = wx.Button(self, -1, "OK") + self.button_cancel = wx.Button(self, -1, "Cancel") + + self.__set_properties() + self.__do_layout() + # end wxGlade + self.__attach_events() + self.checkbox_echo.SetValue(self.settings.echo) + self.checkbox_unprintable.SetValue(self.settings.unprintable) + self.radio_box_newline.SetSelection(self.settings.newline) + + def __set_properties(self): + # begin wxGlade: TerminalSettingsDialog.__set_properties + self.SetTitle("Terminal Settings") + self.radio_box_newline.SetSelection(0) + self.button_ok.SetDefault() + # end wxGlade + + def __do_layout(self): + # begin wxGlade: TerminalSettingsDialog.__do_layout + sizer_2 = wx.BoxSizer(wx.VERTICAL) + sizer_3 = wx.BoxSizer(wx.HORIZONTAL) + sizer_4 = wx.StaticBoxSizer(wx.StaticBox(self, -1, "Input/Output"), wx.VERTICAL) + sizer_4.Add(self.checkbox_echo, 0, wx.ALL, 4) + sizer_4.Add(self.checkbox_unprintable, 0, wx.ALL, 4) + sizer_4.Add(self.radio_box_newline, 0, 0, 0) + sizer_2.Add(sizer_4, 0, wx.EXPAND, 0) + sizer_3.Add(self.button_ok, 0, 0, 0) + sizer_3.Add(self.button_cancel, 0, 0, 0) + sizer_2.Add(sizer_3, 0, wx.ALL|wx.ALIGN_RIGHT, 4) + self.SetAutoLayout(1) + self.SetSizer(sizer_2) + sizer_2.Fit(self) + sizer_2.SetSizeHints(self) + self.Layout() + # end wxGlade + + def __attach_events(self): + self.Bind(wx.EVT_BUTTON, self.OnOK, id = self.button_ok.GetId()) + self.Bind(wx.EVT_BUTTON, self.OnCancel, id = self.button_cancel.GetId()) + + def OnOK(self, events): + """Update data wil new values and close dialog.""" + self.settings.echo = self.checkbox_echo.GetValue() + self.settings.unprintable = self.checkbox_unprintable.GetValue() + self.settings.newline = self.radio_box_newline.GetSelection() + self.EndModal(wx.ID_OK) + + def OnCancel(self, events): + """Do not update data but close dialog.""" + self.EndModal(wx.ID_CANCEL) + +# end of class TerminalSettingsDialog + + +class TerminalFrame(wx.Frame): + """Simple terminal program for wxPython""" + + def __init__(self, *args, **kwds): + self.serial = serial.Serial() + self.serial.timeout = 0.5 #make sure that the alive event can be checked from time to time + self.settings = TerminalSetup() #placeholder for the settings + self.thread = None + self.alive = threading.Event() + # begin wxGlade: TerminalFrame.__init__ + kwds["style"] = wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwds) + self.text_ctrl_output = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE|wx.TE_READONLY) + + # Menu Bar + self.frame_terminal_menubar = wx.MenuBar() + self.SetMenuBar(self.frame_terminal_menubar) + wxglade_tmp_menu = wx.Menu() + wxglade_tmp_menu.Append(ID_CLEAR, "&Clear", "", wx.ITEM_NORMAL) + wxglade_tmp_menu.Append(ID_SAVEAS, "&Save Text As...", "", wx.ITEM_NORMAL) + wxglade_tmp_menu.AppendSeparator() + wxglade_tmp_menu.Append(ID_SETTINGS, "&Port Settings...", "", wx.ITEM_NORMAL) + wxglade_tmp_menu.Append(ID_TERM, "&Terminal Settings...", "", wx.ITEM_NORMAL) + wxglade_tmp_menu.AppendSeparator() + wxglade_tmp_menu.Append(ID_EXIT, "&Exit", "", wx.ITEM_NORMAL) + self.frame_terminal_menubar.Append(wxglade_tmp_menu, "&File") + # Menu Bar end + + self.__set_properties() + self.__do_layout() + # end wxGlade + self.__attach_events() #register events + self.OnPortSettings(None) #call setup dialog on startup, opens port + if not self.alive.isSet(): + self.Close() + + def StartThread(self): + """Start the receiver thread""" + self.thread = threading.Thread(target=self.ComPortThread) + self.thread.setDaemon(1) + self.alive.set() + self.thread.start() + + def StopThread(self): + """Stop the receiver thread, wait util it's finished.""" + if self.thread is not None: + self.alive.clear() #clear alive event for thread + self.thread.join() #wait until thread has finished + self.thread = None + + def __set_properties(self): + # begin wxGlade: TerminalFrame.__set_properties + self.SetTitle("Serial Terminal") + self.SetSize((546, 383)) + # end wxGlade + + def __do_layout(self): + # begin wxGlade: TerminalFrame.__do_layout + sizer_1 = wx.BoxSizer(wx.VERTICAL) + sizer_1.Add(self.text_ctrl_output, 1, wx.EXPAND, 0) + self.SetAutoLayout(1) + self.SetSizer(sizer_1) + self.Layout() + # end wxGlade + + def __attach_events(self): + #register events at the controls + self.Bind(wx.EVT_MENU, self.OnClear, id = ID_CLEAR) + self.Bind(wx.EVT_MENU, self.OnSaveAs, id = ID_SAVEAS) + self.Bind(wx.EVT_MENU, self.OnExit, id = ID_EXIT) + self.Bind(wx.EVT_MENU, self.OnPortSettings, id = ID_SETTINGS) + self.Bind(wx.EVT_MENU, self.OnTermSettings, id = ID_TERM) + self.text_ctrl_output.Bind(wx.EVT_CHAR, self.OnKey) + self.Bind(EVT_SERIALRX, self.OnSerialRead) + self.Bind(wx.EVT_CLOSE, self.OnClose) + + def OnExit(self, event): + """Menu point Exit""" + self.Close() + + def OnClose(self, event): + """Called on application shutdown.""" + self.StopThread() #stop reader thread + self.serial.close() #cleanup + self.Destroy() #close windows, exit app + + def OnSaveAs(self, event): + """Save contents of output window.""" + filename = None + dlg = wx.FileDialog(None, "Save Text As...", ".", "", "Text File|*.txt|All Files|*", wx.SAVE) + if dlg.ShowModal() == wx.ID_OK: + filename = dlg.GetPath() + dlg.Destroy() + + if filename is not None: + f = file(filename, 'w') + text = self.text_ctrl_output.GetValue() + if type(text) == unicode: + text = text.encode("latin1") #hm, is that a good asumption? + f.write(text) + f.close() + + def OnClear(self, event): + """Clear contents of output window.""" + self.text_ctrl_output.Clear() + + def OnPortSettings(self, event=None): + """Show the portsettings dialog. The reader thread is stopped for the + settings change.""" + if event is not None: #will be none when called on startup + self.StopThread() + self.serial.close() + ok = False + while not ok: + dialog_serial_cfg = wxSerialConfigDialog.SerialConfigDialog(None, -1, "", + show=wxSerialConfigDialog.SHOW_BAUDRATE|wxSerialConfigDialog.SHOW_FORMAT|wxSerialConfigDialog.SHOW_FLOW, + serial=self.serial + ) + result = dialog_serial_cfg.ShowModal() + dialog_serial_cfg.Destroy() + #open port if not called on startup, open it on startup and OK too + if result == wx.ID_OK or event is not None: + try: + self.serial.open() + except serial.SerialException, e: + dlg = wx.MessageDialog(None, str(e), "Serial Port Error", wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + else: + self.StartThread() + self.SetTitle("Serial Terminal on %s [%s, %s%s%s%s%s]" % ( + self.serial.portstr, + self.serial.baudrate, + self.serial.bytesize, + self.serial.parity, + self.serial.stopbits, + self.serial.rtscts and ' RTS/CTS' or '', + self.serial.xonxoff and ' Xon/Xoff' or '', + ) + ) + ok = True + else: + #on startup, dialog aborted + self.alive.clear() + ok = True + + def OnTermSettings(self, event): + """Menu point Terminal Settings. Show the settings dialog + with the current terminal settings""" + dialog = TerminalSettingsDialog(None, -1, "", settings=self.settings) + result = dialog.ShowModal() + dialog.Destroy() + + def OnKey(self, event): + """Key event handler. if the key is in the ASCII range, write it to the serial port. + Newline handling and local echo is also done here.""" + code = event.GetKeyCode() + if code < 256: #is it printable? + if code == 13: #is it a newline? (check for CR which is the RETURN key) + if self.settings.echo: #do echo if needed + self.text_ctrl_output.AppendText('\n') + if self.settings.newline == NEWLINE_CR: + self.serial.write('\r') #send CR + elif self.settings.newline == NEWLINE_LF: + self.serial.write('\n') #send LF + elif self.settings.newline == NEWLINE_CRLF: + self.serial.write('\r\n') #send CR+LF + else: + char = chr(code) + if self.settings.echo: #do echo if needed + self.text_ctrl_output.WriteText(char) + self.serial.write(char) #send the charcater + else: + print "Extra Key:", code + + def OnSerialRead(self, event): + """Handle input from the serial port.""" + text = event.data + if self.settings.unprintable: + text = ''.join([(c >= ' ') and c or '<%d>' % ord(c) for c in text]) + self.text_ctrl_output.AppendText(text) + + def ComPortThread(self): + """Thread that handles the incomming traffic. Does the basic input + transformation (newlines) and generates an SerialRxEvent""" + while self.alive.isSet(): #loop while alive event is true + text = self.serial.read(1) #read one, with timout + if text: #check if not timeout + n = self.serial.inWaiting() #look if there is more to read + if n: + text = text + self.serial.read(n) #get it + #newline transformation + if self.settings.newline == NEWLINE_CR: + text = text.replace('\r', '\n') + elif self.settings.newline == NEWLINE_LF: + pass + elif self.settings.newline == NEWLINE_CRLF: + text = text.replace('\r\n', '\n') + event = SerialRxEvent(self.GetId(), text) + self.GetEventHandler().AddPendingEvent(event) + #~ self.OnSerialRead(text) #output text in window + +# end of class TerminalFrame + + +class MyApp(wx.App): + def OnInit(self): + wx.InitAllImageHandlers() + frame_terminal = TerminalFrame(None, -1, "") + self.SetTopWindow(frame_terminal) + frame_terminal.Show(1) + return 1 + +# end of class MyApp + +if __name__ == "__main__": + app = MyApp(0) + app.MainLoop() |