+#----------------------------------------------------------------------------
+
+
+class JoyGauge(wx.Panel):
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+        size = (100,100)
+        
+        wx.Panel.__init__(self, parent, -1, size=size)
+
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
+
+        self.buffer = wx.EmptyBitmap(*size)
+        dc = wx.BufferedDC(None, self.buffer)
+        self.DrawFace(dc)
+        self.DrawJoystick(dc)
+       
+
+    def OnSize(self, event):
+        # The face Bitmap init is done here, to make sure the buffer is always
+        # the same size as the Window
+        w, h  = self.GetClientSize()
+        self.buffer = wx.EmptyBitmap(w,h)
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawJoystick(dc)
+
+
+    def DrawFace(self, dc):
+        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
+        dc.Clear()
+
+
+    def OnPaint(self, evt):
+        # When dc is destroyed it will blit self.buffer to the window,
+        # since no other drawing is needed we'll just return and let it
+        # do it's thing
+        dc = wx.BufferedPaintDC(self, self.buffer)
+
+
+    def DrawJoystick(self, dc):
+        # draw the guage as a maxed square in the center of this window.
+        w, h = self.GetClientSize()
+        edgeSize = min(w, h)
+
+        xorigin = (w - edgeSize) / 2
+        yorigin = (h - edgeSize) / 2
+        center = edgeSize / 2
+
+        # Restrict our drawing activities to the square defined
+        # above.
+        dc.SetClippingRegion((xorigin, yorigin), (edgeSize, edgeSize))
+
+        # Optimize drawing a bit (for Win)
+        dc.BeginDrawing()
+
+        dc.SetBrush(wx.Brush(wx.Colour(251, 252, 237)))
+        dc.DrawRectangle((xorigin, yorigin), (edgeSize, edgeSize))
+
+        dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH))
+
+        dc.DrawLine((xorigin, yorigin + center), (xorigin + edgeSize, yorigin + center))
+        dc.DrawLine((xorigin + center, yorigin), (xorigin + center, yorigin + edgeSize))
+
+        if self.stick:
+            # Get the joystick position as a float
+            joyx =  float(self.stick.GetPosition().x)
+            joyy =  float(self.stick.GetPosition().y)
+
+            # Get the joystick range of motion
+            xrange = self.stick.GetXMax() - self.stick.GetXMin()
+            yrange = self.stick.GetYMax() - self.stick.GetYMin()
+
+            # calc a ratio of our range versus the joystick range
+            xratio = float(edgeSize) / xrange
+            yratio = float(edgeSize) / yrange
+
+            # calc the displayable value based on position times ratio
+            xval = int(joyx * xratio)
+            yval = int(joyy * xratio)
+
+            # and normalize the value from our brush's origin
+            x = xval + xorigin
+            y = yval + yorigin
+
+            # Now to draw it.
+            dc.SetPen(wx.Pen(wx.RED, 2))
+            dc.CrossHair((x, y))
+
+        # Turn off drawing optimization
+        dc.EndDrawing()
+
+
+    def Update(self):
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawJoystick(dc)
+
+
+#----------------------------------------------------------------------------
+
+class JoyPanel(wx.Panel):
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+
+        wx.Panel.__init__(self, parent, -1)
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+
+        fn = wx.Font(
+                parent.GetFont().GetPointSize() + 3,
+                parent.GetFont().GetFamily(),
+                parent.GetFont().GetStyle(),
+                wx.BOLD
+                )
+
+        t = wx.StaticText(self, -1, "X - Y Axes", style = wx.ALIGN_CENTRE)
+        t.SetFont(fn)
+        sizer.Add(t, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1)
+
+        self.control = JoyGauge(self, self.stick)
+        sizer.Add(self.control, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER | wx.ALIGN_CENTER_HORIZONTAL, 1)
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+    def Update(self):
+        self.control.Update()
+
+
+#----------------------------------------------------------------------------
+
+class POVGauge(wx.Panel):
+    #
+    # Display the current postion of the POV control
+    #
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+        self.size = (100, 100)
+        self.avail = False
+        self.fourDir = False
+        self.cts = False
+
+        wx.Panel.__init__(self, parent, -1, size=self.size)
+
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
+
+        self.buffer = wx.EmptyBitmap(*self.size)
+        dc = wx.BufferedDC(None, self.buffer)
+        self.DrawFace(dc)
+        self.DrawPOV(dc)
+
+
+    def OnSize(self, event):
+        # calculate the size of our display and make a buffer for it.
+        w, h  = self.GetClientSize()
+        s = min(w, h)
+        self.size = (s, s)
+        self.buffer = wx.EmptyBitmap(w,h)
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawPOV(dc)
+
+    
+    def DrawFace(self, dc):
+        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
+        dc.Clear()
+
+
+    def OnPaint(self, evt):
+        # When dc is destroyed it will blit self.buffer to the window,
+        # since no other drawing is needed we'll just return and let it
+        # do it's thing
+        dc = wx.BufferedPaintDC(self, self.buffer)
+
+
+    def DrawPOV(self, dc):
+        # draw the guage as a maxed circle in the center of this window.
+        w, h = self.GetClientSize()
+        diameter = min(w, h)
+
+        xorigin = (w - diameter) / 2
+        yorigin = (h - diameter) / 2
+        xcenter = xorigin + diameter / 2
+        ycenter = yorigin + diameter / 2
+
+        # Optimize drawing a bit (for Win)
+        dc.BeginDrawing()
+
+        # our 'raster'.
+        dc.SetBrush(wx.Brush(wx.WHITE))
+        dc.DrawCircle((xcenter, ycenter), diameter/2)
+        dc.SetBrush(wx.Brush(wx.BLACK))
+        dc.DrawCircle((xcenter, ycenter), 10)
+
+        # fancy decorations
+        dc.SetPen(wx.Pen(wx.BLACK, 1, wx.DOT_DASH))
+        dc.DrawLine((xorigin, ycenter), (xorigin + diameter, ycenter))
+        dc.DrawLine((xcenter, yorigin), (xcenter, yorigin + diameter))
+
+        if self.stick:
+            if self.avail:
+
+                pos = -1
+
+                # use the appropriate function to get the POV position
+                if self.fourDir:
+                    pos = self.stick.GetPOVPosition()
+
+                if self.cts:
+                    pos = self.stick.GetPOVCTSPosition()
+
+                # trap invalid values
+                if 0 <= pos <= 36000:
+                    vector = 30
+                else:
+                    vector = 0
+
+                # rotate CCW by 90 so that 0 is up.
+                pos = (pos / 100) - 90
+
+                # Normalize
+                if pos < 0:
+                    pos = pos + 360
+
+                # Stolen from wx.lib.analogclock :-)
+                radiansPerDegree = math.pi / 180
+                pointX = int(round(vector * math.cos(pos * radiansPerDegree)))
+                pointY = int(round(vector * math.sin(pos * radiansPerDegree)))
+
+                # normalise value to match our actual center.
+                nx = pointX + xcenter
+                ny = pointY + ycenter
+
+                # Draw the line
+                dc.SetPen(wx.Pen(wx.BLUE, 2))
+                dc.DrawLine((xcenter, ycenter), (nx, ny))
+
+                # And a little thing to show the endpoint
+                dc.SetBrush(wx.Brush(wx.BLUE))
+                dc.DrawCircle((nx, ny), 8)
+
+        # Turn off drawing optimization
+        dc.EndDrawing()
+
+
+    def Update(self):
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawPOV(dc)
+
+
+    def Calibrate(self):
+        s = self.stick
+        self.avail = s.HasPOV()
+        self.fourDir = s.HasPOV4Dir()
+        self.cts = s.HasPOVCTS()
+
+
+#----------------------------------------------------------------------------
+
+class POVStatus(wx.Panel):
+    #
+    # Displays static info about the POV control
+    #
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+
+        wx.Panel.__init__(self, parent, -1, size=(100, 100))
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add((20,20))
+        
+        self.avail = wx.CheckBox(self, -1, "Available")
+        sizer.Add(self.avail, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
+
+        self.fourDir = wx.CheckBox(self, -1, "4-Way Only")
+        sizer.Add(self.fourDir, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
+
+        self.cts = wx.CheckBox(self, -1, "Continuous")
+        sizer.Add(self.cts, 0, wx.ALL | wx.EXPAND | wx.ALIGN_LEFT, 2)
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+        # Effectively makes the checkboxes read-only.
+        self.Bind(wx.EVT_CHECKBOX, self.Calibrate)
+
+
+    def Calibrate(self, evt=None):
+        s = self.stick
+        self.avail.SetValue(s.HasPOV())
+        self.fourDir.SetValue(s.HasPOV4Dir())
+        self.cts.SetValue(s.HasPOVCTS())
+
+
+#----------------------------------------------------------------------------
+
+class POVPanel(wx.Panel):
+    def __init__(self, parent, stick):
+
+        self.stick = stick
+
+        wx.Panel.__init__(self, parent, -1, size=(100, 100))
+
+        sizer = wx.BoxSizer(wx.HORIZONTAL)
+        gsizer = wx.BoxSizer(wx.VERTICAL)
+
+        sizer.Add((25,25))
+        
+        fn = wx.Font(
+                parent.GetFont().GetPointSize() + 3,
+                parent.GetFont().GetFamily(),
+                parent.GetFont().GetStyle(),
+                wx.BOLD
+                )
+        t = wx.StaticText(self, -1, "POV Control", style = wx.ALIGN_CENTER)
+        t.SetFont(fn)
+        gsizer.Add(t, 0, wx.ALL | wx.EXPAND, 1)
+        
+        self.display = POVGauge(self, stick)
+        gsizer.Add(self.display, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
+        sizer.Add(gsizer, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
+        
+        self.status = POVStatus(self, stick)
+        sizer.Add(self.status, 1, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER, 1)
+
+        self.SetSizer(sizer)
+        sizer.Fit(self)
+
+
+    def Calibrate(self):
+        self.display.Calibrate()
+        self.status.Calibrate()
+
+
+    def Update(self):
+        self.display.Update()
+
+
+#----------------------------------------------------------------------------
+
+class LED(wx.Panel):
+    def __init__(self, parent, number):
+
+        self.state = -1
+        self.size = (20, 20)
+        self.number = number
+
+        self.fn = wx.Font(
+                parent.GetFont().GetPointSize() - 1,
+                parent.GetFont().GetFamily(),
+                parent.GetFont().GetStyle(),
+                wx.BOLD
+                )
+
+        wx.Panel.__init__(self, parent, -1, size=self.size)
+
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        self.Bind(wx.EVT_ERASE_BACKGROUND, lambda e: None)
+
+        self.buffer = wx.EmptyBitmap(*self.size)
+        dc = wx.BufferedDC(None, self.buffer)
+        self.DrawFace(dc)
+        self.DrawLED(dc)
+
+
+    def OnSize(self, event):
+        # calculate the size of our display.
+        w, h  = self.GetClientSize()
+        s = min(w, h)
+        self.size = (s, s)
+        self.buffer = wx.EmptyBitmap(*self.size)
+        dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
+        self.DrawFace(dc)
+        self.DrawLED(dc)
+
+
+    def DrawFace(self, dc):
+        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
+        dc.Clear()
+
+
+    def OnPaint(self, evt):
+        # When dc is destroyed it will blit self.buffer to the window,
+        # since no other drawing is needed we'll just return and let it
+        # do it's thing
+        dc = wx.BufferedPaintDC(self, self.buffer)