Transmogrifier Source

#!/usr/bin/env python
 
'''
transmogrifier.py: beta version
dependencies: Python2.5, wxPython, pyserial2.3+, simpleosc0.2.5, ftdi driver
authors: Matt Nelson, Robb Drinkwater, Ed Bennett
last edited: Thu Aug 1 13:30:22 2008
http://artbus.info
 
CHANGE LOG:
1.1 Beta - Removed || USB from MainFrame getDevices method, not needed because of -i & will throw an error on Linux (rd)
1.2 Beta - Changed the SocketThread inbound method to build an osc ab message with spaces (mn)
         - Removed raise from Transmogrify try except block (mn)
1.3 Beta - Changed the SocketThread inbound method to be more pythonic (rd, mn)
         - Corrected spelling error (mn)
1.4 Beta - Changed name to just transmogrifier.py. (mn)
1.5 Beta - Changed getDevices method, it now checks if ports can be opened prior to adding them to dev_lt. (mn)
'''
__version__ = '1.5 Beta'
 
import wx, wx.html, string, os, sys, serial, Queue, time, threading, socket, osc
 
class WorkerThread(threading.Thread):
    '''This class creates a tread that sets up the sockets and waits for a 
    connection to be made to the server.  It then does a callback to main gui 
    thread to start the other threads.'''
    def __init__(self, threadNum, window, host, port, baud, dev, protocol):
        threading.Thread.__init__(self)
        self.threadNum = threadNum
        self.window = window
        self.host = host
        self.port = port
        self.baud = baud
        self.dev = dev
        self.protocol = protocol
        self.clientsock = 0
 
    def stop(self):
        '''Called when thread stops.  Closes socket and calls the ugly hack when 
        thread is stopped.'''
        if self.clientsock == 0:
            self.uglyHack()
 
        # TCP
        if self.protocol == 'tcp':
            self.sock.close()
 
    def run(self):
        '''Called when thread runs.  Sets up socket and serial ports, 
        then does a callback to the gui thread.'''
        #print 'workerThread: Started', currentThread().getName()
 
        # Statusbar update
        msg = 'Status: Running'
        wx.CallAfter(self.window.updateStatus, msg)    
 
        # Check protocol and setup socket
        if self.protocol == 'tcp':
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.sock.bind((self.host, self.port))
            self.sock.listen(1)
 
        if self.protocol == 'osc':
            osc.init()
            self.clientsock = osc.createListener(self.host, self.port)
            self.clientsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 
        # Serial setup
        dataQueue = Queue.Queue(128)
        ser = serial.Serial()
        ser.port = self.dev
        ser.baudrate = self.baud
        ser.rtscts  = False
        ser.xonxoff = False
        ser.timeout = 1
 
        # Callback to update statusbar and gauge
        msg = dataQueue.qsize() # Return queue size
        wx.CallAfter(self.window.updateQueue, msg) # Callback to gui thread to update statusbar and gauge    
 
        # Check protocol and set socket
        if self.protocol == 'tcp':
            try:
                self.clientsock, clientaddr = self.sock.accept()
            except KeyboardInterrupt:
                raise
            except:
                #traceback.print_exc()
                pass 
 
        try:
            ser.open()
        except KeyboardInterrupt:
            raise
        except:
            #traceback.print_exc()
            pass
 
        # Callback to gui thread
        wx.CallAfter(self.window.setThreads, self.clientsock, ser, dataQueue)
 
    def uglyHack(self):
        '''This is an ugly hack to allow the socket to close cleanly.  It connects 
        to the socket and sends foo data and closes the port.  I just swallowed a
        small amount of barf.'''
        hackData = 'nill'
        hackOut = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            hackOut.connect((self.host, self.port))
            hackOut.send(hackData)
        finally:
            hackOut.close()
 
 
class SocketThread(threading.Thread):
    '''This class creates a tread that manages the data coming into the server.  
    It then puts it in a queue.'''
    def __init__(self, threadNum, window, clientsock, dataQueue, protocol, message='nill'):
        threading.Thread.__init__(self)
        self.threadNum = threadNum
        self.window = window
        self.clientsock = clientsock
        self.dataQueue = dataQueue
        self.protocol = protocol
        self.message = message
        self.abortFlag = 0
 
    def stop(self):
        '''Called when thread stops.  Closes socket and calls the ugly hack when 
        thread is stopped.'''
        self.abortFlag = 1
        self.clientsock.close()
 
    def inbound(self, *msg):
        '''Strips data from osc message and puts in queue'''
        # All data to end of message 
        osc_msg = msg[0][2:]
        data = ''
        # This will build the osc message if it contains spaces
        for d in osc_msg:
            if not str(d).startswith("!"): # don't add a leading space to the command
                data += " " 
            data += str(d)
        self.dataQueue.put(data) # Put data into the queue
 
    def run(self):
        '''Called when thread runs.  Waits for connection, then waits for data, 
        then puts the data in the queue.'''
        #print 'handleTCP: Started', currentThread().getName()
        #print 'Got connection from', self.clientsock.getpeername()
 
        # OSC
        if self.protocol == 'osc':
            osc.bind(self.inbound, self.message)
 
        while(self.abortFlag == 0):
 
            # TCP
            if self.protocol == 'tcp':
                data = self.clientsock.recv(4096) # Blocking until client connection
                self.dataQueue.put(data) # Put data into the queue
 
            # OSC
            if self.protocol == 'osc':
                osc.getOSC(self.clientsock)
 
            time.sleep(0.0001) # Sleep to prevent thread come consuming cpu cycles
 
 
class SerThread(threading.Thread):
    '''This class creates a tread that waits for data to be put into the queue to be 
    set.  It then pops data of the queue and sends it through the serial port.  If the 
    command is one that returns data it then reads the data from the serial port.'''
    def __init__(self, threadNum, window, clientsock, ser, dataQueue, protocol, oscHost='nill', oscPort=0):
        threading.Thread.__init__(self)
        self.threadNum = threadNum
        self.window = window
        self.clientsock = clientsock
        self.ser = ser
        self.dataQueue = dataQueue
        self.protocol = protocol
        self.oscHost = oscHost
        self.oscPort = oscPort
        self.abortFlag = 0
 
    def stop(self):
        '''Called when thread stops.  Sets the abort flag.'''
        self.abortFlag = 1
 
    def run(self):
        '''Called when thread runs.  Gets data from queue, then sends through serial 
        port and returns data through tcp if needed.  Calls the transmogrifier class 
        to check and formate data as needed.'''
        #print 'handleSer: Started', currentThread().getName()
        while(self.abortFlag == 0):
            _message = Transmogrify() # Instance of transmogrifier class
            socketReceived = self.dataQueue.get() # Blocking statement that gets data from queue and send to transmogrifier class
            serialSend = _message.inputData(socketReceived)
            #serialSend = _message.inputData(self.dataQueue.get()) # Blocking statement that gets data from queue and send to transmogrifier class
 
            # Callback to update statusbar and gauge
            msg = self.dataQueue.qsize() # Return queue size
            wx.CallAfter(self.window.updateQueue, msg) # Callback to gui thread to update statusbar and gauge
 
            # Serial
            try:
                self.ser.write(serialSend) # Write to serial port
            except:
                pass
 
            # Check if ArtBus command expects return data
            if _message.checkData(serialSend):
                serialReturn = self.ser.readline()
                socketReturn = _message.outputData(serialReturn, serialSend) # Read data from serial port and send to transmogrifier class
 
                # Check if return data from transmogrifier class was nill
                if socketReturn != 'nill':
                    try:
 
                        # TCP
                        if self.protocol == 'tcp':
                            self.clientsock.sendall(socketReturn) # Send data back throuch tcp connection
 
                        # OSC
                        if self.protocol == 'osc':
                            oscAddress, oscValue = _message.createOscMessage(serialSend, serialReturn)
                            osc.sendMsg(oscAddress, [int(oscValue)], self.oscHost, int(self.oscPort))
 
                    except(ValueError, IndexError):
                        raise
                    except:
                        pass
                else:
                    # If return data was nill flush serial ports to prevent data salad
                    time.sleep(0.1); # this is the key in preventing data salad
                    self.ser.flushInput() # flush input buffer
                    self.ser.flushOutput() # flush output buffer
 
            time.sleep(0.0001) # Sleep to prevent thread come consuming cpu cycles
 
 
class Transmogrify():
    '''This class is what chews on the data and spits it back out.  This is where 
    the ArtBus specific protocol is carried out for the server to work with all 
    the fancy media apps we all love.'''
    def inputData(self, socketReceived):
        '''This checks the input from the tcp connection.'''
        if socketReceived.startswith('!'): # Only accept ab commands
            socketReceived = socketReceived.strip() # Stip newline
            #print "TM input: %s" % socketReceived
 
            # I'm wondering if we should have this at all.
            if socketReceived.endswith(';'):
                serialMessage = socketReceived
            else:
                serialMessage = socketReceived + ';'
            return serialMessage
 
 
    def outputData(self, serialReturn, socketSend):
        '''This checks the data returned from the ArtBus and formates it to be returned  
        through the tcp connection.  This also traps unwanted data from ArtBus resets, 
        accidental or not.  This is where the tags or tokens will be implemented in the 
        near future.  When that happens this all will be restructed.  The main that to 
        know is if the return data from the ArtBus is not valid: startup, reset, error, 
        or config mode, it must be set to 'nill'.  It is 'nill' and not the python None 
        object so that the same datatype is expected.'''
 
        # Checks if the returned data has a length greater than 0 and less than 5, this will change in the future
        try:
            if serialReturn[0].isdigit() or serialReturn[0].isalpha():
                try:
                    serialReturn = serialReturn.strip().split()[0] # Get rid of newlines etc
                    # Check if the returned data starts with a character: startup, reset, error, or config mode.  This will be easier with tags or tokens.
                    if serialReturn[0].isalpha():
                        socketReturn = 'nill'
                    else:
                        socketSend = socketSend.strip().split()[0] # Get rid of newlines etc
                        socketReturn = socketSend + serialReturn
                        #print "TM output: %s" % socketReturn
                except: #(ValueError, IndexError):
                    socketReturn = 'nill'
            else:
                socketReturn = 'nill'
        except(ValueError, IndexError):
            socketReturn = 'nill'
        return socketReturn
 
    def checkData(self, serialSend):
        '''This checks if the ArtBus command expects data to be returned.'''
        abCommand = serialSend[2:3]
        if (abCommand >= 'a' and abCommand <= 'f'):
            result = 1
        else:
            result = 0
        return result
 
    def createOscMessage(self, serialSend, serialReturn):
        '''This formates the OSC message.'''
        abAddress = serialSend[1:2]
        abCommand = serialSend[2:]
        abCommand = abCommand.replace(";","")
        serialReturn = serialReturn.strip()
        result = ("/" + abAddress + "/" + abCommand, str(serialReturn)) # OSC compliant slashed format
        return result
 
 
class CharValidator(wx.PyValidator):
    '''This class subclasses wx.PyValidator and is used to 
    validate the input for the controls.'''
    def __init__(self, flag):
         wx.PyValidator.__init__(self)
         self.flag = flag
         self.Bind(wx.EVT_CHAR, self.onChar)  # Bind to wx event.
 
    def onChar(self, evt):
        '''Validates as user types.'''
        key = chr(evt.GetKeyCode())
        if self.flag == 'no-alpha' and key in string.letters:
            return
        if self.flag == 'no-digit' and key in string.digits:
            return
        evt.Skip()
 
 
class MainFrame(wx.Frame):
    '''Main gui class.  This draws the widgets and starts everything.  The following
    functions will be documented in the near future.  The statusbar is not all that 
    helpful right now, but I'm keeping it in.'''
    def __init__(self):
        wx.Frame.__init__(self, None, -1, 'Transmogrifier (%s)' % __version__, size=(345,325),
            style=wx.DEFAULT_FRAME_STYLE ^ (wx.MAXIMIZE_BOX | wx.RESIZE_BORDER))
 
        self.threads = []
        self.count = 0
        self.dev_lt = []
        self.baud_lt = '115200 19200'
        self.protocol_lt = 'osc tcp'
        self.serverLabel = '  Receive: '
        self.getDevices()
        self.frame = (self)
        self.initStatusBar()
        #self.createMenuBar()
        self.panel = wx.Panel(self)
 
        # Create static text controls
        self.address_l = wx.StaticText(self.panel, -1, self.serverLabel)
        port_l    = wx.StaticText(self.panel, -1, 'Port: ')
        oscAddress_l = wx.StaticText(self.panel, -1, '      Send: ')
        oscPort_l    = wx.StaticText(self.panel, -1, 'Port: ')
        message_l = wx.StaticText(self.panel, -1, 'Message: ')
        #server_l  = wx.StaticText(self.panel, -1, 'Address, Port:')
        #queue_l = wx.StaticText(self.panel, -1, 'Queue:')
        device_l  = wx.StaticText(self.panel, -1, 'Serial Device: ')
        baud_l    = wx.StaticText(self.panel, -1, '    Baud Rate: ')
 
        # Create text controls
        self.address_t = wx.TextCtrl(self.panel, validator=CharValidator('any'), size=(80, -1))
        self.port_t    = wx.TextCtrl(self.panel, validator=CharValidator('no-alpha'), size=(50, -1))
        self.oscAddress_t = wx.TextCtrl(self.panel, validator=CharValidator('any'), size=(80, -1))
        self.oscPort_t    = wx.TextCtrl(self.panel, validator=CharValidator('no-alpha'), size=(50, -1))
        self.message_t = wx.TextCtrl(self.panel, validator=CharValidator('any'), size=(175, -1))
        self.device_cb = wx.Choice(self.panel, -1, choices=self.dev_lt)
        self.baud_cb   = wx.Choice(self.panel, -1, choices=self.baud_lt.split())
 
        # Create radio controls
        self.radio = wx.RadioBox(self.panel, -1, 'Protocol', (10,10), (62,80), self.protocol_lt.split(), 1, wx.RA_SPECIFY_COLS)
 
        # Create buttons
        self.start_btn = wx.Button(self.panel, label = 'Start')
        self.stop_btn  = wx.Button(self.panel, label = 'Stop')
 
        # Create gauge
        self.gauge = wx.Gauge(self.panel, -1, 128, size=(200, 15), style=wx.GA_HORIZONTAL)
        self.gauge.SetBezelFace(3)
        self.gauge.SetShadowWidth(3)
 
        # Set control defaults
        self.protocol = 'osc'
        self.address_t.SetValue('127.0.0.1')
        self.port_t.SetValue('9000')
        self.oscAddress_t.SetValue('127.0.0.1')
        self.oscPort_t.SetValue('9001')
        self.message_t.SetValue('/toAB')
        self.statusbar.SetStatusText('Queue: 0', 0)
        self.statusbar.SetStatusText('Status: Configure settings', 1)
 
        # Bind events to controls and functions
        self.frame.Bind(wx.EVT_RADIOBOX, self.onRadio, self.radio)
        #self.frame.Bind(wx.EVT_TEXT, self.onAddressText, self.address_t)
        #self.frame.Bind(wx.EVT_TEXT, self.onPortText, self.port_t)
        self.frame.Bind(wx.EVT_TEXT, self.onMessage, self.message_t)
        #self.frame.Bind(wx.EVT_CHOICE, self.onDeviceChoice, self.device_cb)
        #self.frame.Bind(wx.EVT_CHOICE, self.onBaudChoice, self.baud_cb)
        self.frame.Bind(wx.EVT_BUTTON, self.onStartBtn, self.start_btn)
        self.frame.Bind(wx.EVT_BUTTON, self.onStopBtn, self.stop_btn)
 
        # Create main sizer
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        #mainSizer.Add(wx.StaticLine(self.panel), 0, wx.EXPAND|wx.ALL, 5)
 
        # Create sub sizer
        subSizer = wx.BoxSizer(wx.HORIZONTAL)
 
        # Create radio sizer
        radioSizer = wx.BoxSizer(wx.VERTICAL)
        radioSizer.Add(self.radio)
 
        # Create server sizer
        serverSizer = wx.BoxSizer(wx.HORIZONTAL)
        serverSizer.Add((10,10), 0)
        serverSizer.Add(self.address_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        serverSizer.Add(self.address_t, 0)
        serverSizer.Add((10,10), 0)
        serverSizer.Add(port_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        serverSizer.Add(self.port_t, 0)
        serverSizer.Add((10,10), 0)
 
        # Create osc send sizer
        oscSizer = wx.BoxSizer(wx.HORIZONTAL)
        oscSizer.Add((10,10), 0)
        oscSizer.Add(oscAddress_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        oscSizer.Add(self.oscAddress_t, 0)
        oscSizer.Add((10,10), 0)
        oscSizer.Add(oscPort_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        oscSizer.Add(self.oscPort_t, 0)
        oscSizer.Add((10,10), 0)
 
        # Create message sizer
        messageSizer = wx.BoxSizer(wx.HORIZONTAL)
        messageSizer.Add((10,10), 0)
        messageSizer.Add(message_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        messageSizer.Add(self.message_t, 0)
 
        # Create serial sizer
        #serialSizer = wx.FlexGridSizer(1, 2, 5, 5) # rows, cols, vgap, hgap
        #serialBox = wx.StaticBox(self.panel, -1, 'Serial:')
        #serialSizer = wx.StaticBoxSizer(serialBox, wx.VERTICAL)
        #serialSizer.Add(device_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        #serialSizer.Add(self.device_cb, 0, wx.EXPAND)
        #serialSizer.Add(baud_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        #serialSizer.Add(self.baud_cb, 0, wx.EXPAND)
        #mainSizer.Add(serialSizer, 0, wx.EXPAND|wx.ALL, 5)
 
        # Create device sizer
        deviceSizer = wx.BoxSizer(wx.HORIZONTAL)
        deviceSizer.Add((10,10), 0)
        deviceSizer.Add(device_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        deviceSizer.Add(self.device_cb, 0, wx.EXPAND, 5)
        deviceSizer.Add((10,10), 1)
 
        # Create baud sizer
        baudSizer = wx.BoxSizer(wx.HORIZONTAL)
        baudSizer.Add((10,10), 0)
        baudSizer.Add(baud_l, 0, wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL)
        baudSizer.Add(self.baud_cb, 0, wx.EXPAND)
        baudSizer.Add((10,10), 1)
 
        # Create gauge sizer
        gaugeSizer = wx.BoxSizer(wx.HORIZONTAL)
        gaugeSizer.Add((10,10), 1)
        gaugeSizer.Add(self.gauge, 0, wx.ALIGN_CENTER_VERTICAL)
        gaugeSizer.Add((10,10), 1)
        #mainSizer.Add(gaugeSizer, 0, wx.EXPAND|wx.ALL, 5)
 
        # Create bag sizer
        bagSizer = wx.GridBagSizer(hgap=8, vgap=8)
        bagSizer.Add(radioSizer, pos=(0,1), span=(3,1), flag=wx.EXPAND)
        bagSizer.Add(serverSizer, pos=(0,0), span=(1,1), flag=wx.EXPAND)
        bagSizer.Add(oscSizer, pos=(1,0), span=(1,1), flag=wx.EXPAND)
        bagSizer.Add(messageSizer, pos=(2,0), span=(1,1), flag=wx.EXPAND)
        #bagSizer.Add(wx.StaticLine(self.panel), pos=(3,0), span=(1,2), flag=wx.EXPAND)
        #bagSizer.Add(serialSizer, pos=(4,0), span=(1,1), flag=wx.EXPAND)
        #bagSizer.Add(gaugeSizer, pos=(0,4), span=(5,1), flag=wx.EXPAND)
        #subSizer.Add(bagSizer, 0, wx.EXPAND|wx.ALL, 5)
        #subSizer.Add((10,10), 0)
        #subSizer.Add(gaugeSizer, 0, wx.EXPAND, 5)
        mainSizer.Add((10,10), 0, wx.EXPAND|wx.ALL, 0)
        mainSizer.Add(bagSizer, 0, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(wx.StaticLine(self.panel), 0, wx.EXPAND|wx.ALL, 5)
        #mainSizer.Add(serialSizer, 0, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(deviceSizer, 0, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(baudSizer, 0, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(gaugeSizer, 0, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(subSizer, 0, wx.EXPAND|wx.ALL, 5)
        mainSizer.Add(wx.StaticLine(self.panel), 0, wx.EXPAND|wx.ALL, 10)
 
        # Create sub-sizer
        #subSizer = wx.BoxSizer(wx.HORIZONTAL)
        #subSizer.Add(self.address_t, 1)
        #subSizer.Add(self.port_t, 0, wx.LEFT|wx.RIGHT, 5)
        #serverSizer.Add(subSizer, 0, wx.EXPAND)
        #mainSizer.Add(wx.StaticLine(self.panel), 0, wx.EXPAND|wx.ALL, 5)
 
        # Create button sizer
        btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        btnSizer.Add((10,10), 1)
        btnSizer.Add(self.start_btn)
        btnSizer.Add((10,10), 0)
        btnSizer.Add(self.stop_btn)
        btnSizer.Add((10,10), 1)
        mainSizer.Add(btnSizer, 0, wx.EXPAND|wx.BOTTOM, 10)
 
        self.panel.SetSizer(mainSizer)
 
        self.allowInput(1)
 
    # Event functions
    def onRadio(self, event):
        '''Called when protocol is changed.  Sets protocol and configures the gui.'''
        self.protocol = self.radio.GetStringSelection()
        self.setInput(self.protocol)
        #self.changeServerLabel(self.protocol)
 
    def onAddressText(self, event):
        self.statusbar.SetStatusText('Status: Enter a valid IP address', 1)
 
    def onPortText(self, event):
        self.statusbar.SetStatusText('Status: Enter a valid port number', 1)
        if self.port_t.GetValue() == '':
            #self.port_t.SetValue('9000')
            pass
 
    def onMessage(self, event):
        if self.message_t.GetValue() == '':
            self.message_t.SetValue('/')
 
    def onDeviceChoice(self, event):
        self.statusbar.SetStatusText('Status: Choose a serial device', 1)
 
    def onBaudChoice(self, event):
        self.statusbar.SetStatusText('Status: Choose a baud rate', 1)
 
    def onStartBtn(self, event):
        error = self.configSelections()
        if error == 1:
            self.allowInput(1)
        else:
            self.allowInput(0)
            self.statusbar.SetStatusText('Status: Starting', 1)
            self.statusbar.SetStatusText('Queue: 0', 0)
            self.count += 1
            thread = WorkerThread(self.count, self, self.host, self.port, self.baud, self.dev, self.protocol)
            self.threads.append(thread)
            thread.setDaemon(1)
            thread.start()
 
    def onStopBtn(self, event):
        self.statusbar.SetStatusText('Status: Stopping', 1)
        self.stopThreads()
        self.allowInput(1)
        self.setInput(self.protocol)
        self.updateQueue(0)
 
    def onCloseWindow(self, event):
        self.Destroy()
 
    # Gui functions
    def configSelections(self):
        error = 0
        self.dev = self.device_cb.GetStringSelection()
        self.dev = '/dev/' + self.dev
        self.baud = self.baud_cb.GetStringSelection()
        self.host = self.address_t.GetValue()
        self.port = int(self.port_t.GetValue())
        self.oscHost = self.oscAddress_t.GetValue()
        self.oscPort = self.oscPort_t.GetValue()
        self.message = self.message_t.GetValue()
 
        if self.dev == '':
            error = 1
            msg='Status: Choose serial device'
        elif self.dev == ('/dev/none availible') or (self.dev == '/dev/'):
            error = 1
            msg='Status: No serial device'
        elif self.baud == '':
            error = 1
            msg='Status: Choose baud rate'
        elif self.host == '':
            error = 1
            msg='Status: Enter a valid IP address'
        elif self.port == '':
            error = 1
            msg='Status: Enter a valid port'
        if error == 1:
            self.updateStatus(msg)
 
        #print 'error:', msg
        return error
 
    def setInput(self, protocol):
        # Disable server input based on protocol
 
        # TCP
        if protocol == 'tcp':
            self.address_t.Enable()
            self.port_t.Enable()
            self.oscAddress_t.Disable()
            self.oscPort_t.Disable()
            self.message_t.Disable()
 
        # OSC
        if protocol == 'osc':
            self.address_t.Enable()
            self.port_t.Enable()
            self.oscAddress_t.Enable()
            self.oscPort_t.Enable()
            self.message_t.Enable()
 
    def allowInput(self, status):
        # Disable input when running
        if status == 0:
            self.radio.Disable()
            self.address_t.Disable()
            self.port_t.Disable()
            self.oscAddress_t.Disable()
            self.oscPort_t.Disable()
            self.message_t.Disable()
            self.device_cb.Disable()
            self.baud_cb.Disable()
            self.start_btn.Disable()
            self.stop_btn.Enable()
        else:
            self.radio.Enable()
            self.address_t.Enable()
            self.port_t.Enable()
            self.oscAddress_t.Enable()
            self.oscPort_t.Enable()
            self.message_t.Enable()
            self.device_cb.Enable()
            self.baud_cb.Enable()
            self.start_btn.Enable()
            self.stop_btn.Disable()
 
    def changeServerLabel(self, protocol):
        # Change server label based on protocol
 
        # TCP
        if protocol == 'tcp':
            self.serverLabel = '   Server: '
            #address_l = wx.StaticText(self.panel, -1, '   Server:')
 
        # OSC
        if protocol == 'osc':
            self.serverLabel = '  Receive: '
            #address_l = wx.StaticText(self.panel, -1, '  Receive:')
 
        self.address_l.Update()
        self.address_l.Refresh()
 
    def getDevices(self):
        # This will only work on a *inux system
        foo_lt = []
        dev_tmp = os.popen('ls /dev | grep -i usb')
        foo = dev_tmp.readlines()
        if len(foo) > 0:
            for f in (foo):
                f = f.strip('\n')
                foo_lt.append(f)
        for i in foo_lt:
            try:
                s = serial.Serial('/dev/%s' % i)
                self.dev_lt.append(i)
                s.close()
            except serial.SerialException:
                pass
        if self.dev_lt == []:
            self.dev_lt.append('none availible')
 
    # Thread functions
    def stopThreads(self):
        while self.threads:
            tNum = len(self.threads) - 1
            thread = self.threads[tNum]
            thread.stop()
            self.threads.remove(thread)
            self.updateQueue(0)
            self.updateStatus('Status: Stopped')
 
    def setThreads(self, clientsock, ser, dataQueue):
        self.setSocket(clientsock, dataQueue)
        self.setSer(clientsock, ser, dataQueue)
 
    def setSocket(self, clientsock, dataQueue):
        self.count += 1
 
        # TCP
        if self.protocol == 'tcp':
            thread = SocketThread(self.count, self, clientsock, dataQueue, self.protocol)
 
        # OSC
        if self.protocol == 'osc':
            thread = SocketThread(self.count, self, clientsock, dataQueue, self.protocol, self.message)
 
        self.threads.append(thread)
        thread.setDaemon(1)
        thread.start()
 
    def setSer(self, clientsock, ser, dataQueue):
        self.count += 1
 
        # TCP
        if self.protocol == 'tcp':
            thread = SerThread(self.count, self, clientsock, ser, dataQueue, self.protocol)
 
        # OSC
        if self.protocol == 'osc':
            thread = SerThread(self.count, self, clientsock, ser, dataQueue, self.protocol, self.oscHost, self.oscPort)
 
        self.threads.append(thread)
        thread.setDaemon(1)
        thread.start()
 
    # Status bar functions
    def initStatusBar(self):
        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetFieldsCount(2)
        self.statusbar.SetStatusWidths([-1, -2])
 
    def updateStatus(self, msg):
        self.statusbar.SetStatusText(msg, 1)
 
    def updateQueue(self, msg):
        self.gauge.SetValue(msg)
        msg = 'Queue: %s' % str(msg)
        self.statusbar.SetStatusText(msg, 0)
 
    # Menu functions
    def menuData(self):
        return [('&File', (('', '', ''), 
                    ('About...', 'About', self.createAbout), 
                    ('&Quit', 'Quit', self.onCloseWindow)))]
 
    def createMenuBar(self):
        menuBar = wx.MenuBar()
        for eachMenuData in self.menuData():
            menuLabel = eachMenuData[0]
            menuItems = eachMenuData[1]
            menuBar.Append(self.createMenu(menuItems), menuLabel)
        self.SetMenuBar(menuBar)
 
    def createMenu(self, menuData):
        menu = wx.Menu()
        for eachItem in menuData:
            if len(eachItem) == 2:
                label = eachItem[0]
                subMenu = self.createMenu(eachItem[1])
                menu.AppendMenu(wx.NewId(), label, subMenu)
            else:
                self.createMenuItem(menu, *eachItem)
        return menu
 
    def createMenuItem(self, menu, label, status, handler, 
                       kind=wx.ITEM_NORMAL):
        if not label:
            menu.AppendSeparator()
            return
        menuItem = menu.Append(-1, label, status, kind)
        self.Bind(wx.EVT_MENU, handler, menuItem)
 
    def createAbout(self, event):
        dlg = About(self)
        dlg.ShowModal()
        dlg.Destroy()
 
 
class About(wx.Dialog):
    text = '''
<html>
<body>
<p><b>Transmogrifier</b> is a application for the <b>ArtBus</b></p>
<p>For more information about ArtBus visit: <a href="http://artbus.info">artbus.info</a></p>
<p>ArtBus by <b>Ed Bennett</b><br/>
<p>Authors:<br/>
<ul>
<li><b>Matthew Nelson</b></li>
<li><b>Rob Drinkwater</b></li>
<li><b>Ed Bennett</b></li>
</ul></p>
</body>
</html>
'''
 
    def __init__(self, parent):
        wx.Dialog.__init__(self, parent, -1, 'About TCP to Serial', size=(250, 275))
 
        html = wx.html.HtmlWindow(self)
        html.SetPage(self.text)
 
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(html, 1, wx.EXPAND|wx.ALL, 5)
 
        self.SetSizer(sizer)
        self.Layout()
 
 
if __name__ == '__main__':
    app = wx.PySimpleApp()
    MainFrame().Show()
    app.MainLoop()