Python how-to: AndroiDAQ and Serial Communications

Python and AndroiDAQ

One thing that sets the AndroiDAQ module from other data acquisition products on the market is its use of the industry standard FTDI UART to USB conversion integrated circuit for USB serial communications. This allows the designer to use one easy to use USB driver for programming with the AndroiDAQ module in the various popular programming languages and on various operating system platforms, which at times can be a very taunting task with other commercially available data acquisition products. This also makes it easy to develop your application for the AndroiDAQ module in not only Android, Java, and LabVIEW, but for other popular programming languages like Python, which can also be used on multiple platforms. This article is going to present to you how to set up Python for serial communication, via the USB port, so that you can add Python to your list of tools for controlling and reading the AndroiDAQ module. 

 

According to Wikipedia: 

“Python is a widely used, general-purposehigh level programming language. Its design philosophy emphasizes code readability, and its syntax allows programmers to express concepts in fewer lines of code than would be possible in languages such as C++ or Java. The language provides constructs intended to enable clear programs on both a small and large scale. Python supports multiple programming paradigms, including object-orientedimperative and functional programming or procedural styles. It features a dynamic type system and automatic memory management and has a large and comprehensive standard libraryPython interpreters are available for installation on many operating systems, allowing Python code execution on a majority of systems. Using third-party tools, such as Py2exe or Pyinstaller, Python code can be packaged into stand-alone executable programs for some of the most popular operating systems, allowing for the distribution of Python-based software for use on those environments without requiring the installation of a Python interpreter.”

I program with Python under the Eclipse integrated development environment (IDE). To use Python with Eclipse you need to install the pyDEV plug-in for Eclipse. The pyDEV plug-in instructions and download is available at:

http://pydev.org/

Of course you can use your preferred IDE for your Python development. Here is a list of integrated development environments for you to consider:

https://wiki.python.org/moin/IntegratedDevelopmentEnvironments

To use serial communications with Python, you need to install the pySerial module for Python for your operating system. pySerial encapsulates the access of serial ports by providing back-ends for Python that is running on Windows, Linux, and other operating systems. pySerial is available for download at the link below:

https://pypi.python.org/pypi/pyserial

Installation instructions and how to use pySerial is available here:

http://pythonhosted.org//pyserial/

After you install PySerial on your development machine you will also need to install the wxPython module for your operating system. wxPython is a cross platform Graphical User’s Interface toolkit so that you can display user interface items like text boxes, lists and other graphical widgets that can be used as inputs to gather user inputted information, or outputs for incoming messages from devices like the AndroiDAQ module. You can download the wxPython toolkit at:

http://www.wxpython.org/download.php

And learn how to use wxPython at:

http://wiki.wxpython.org/How_to_Learn_wxPython

We now have the necessary tools to develop a simple serial communication interface for the AndroiDAQ module using Python. Below is an image of the Python serial communications panel running the code that is provided in this article:

AndroiDAQ Python Serial Interface

Below is a simple example in Python, written as a serial application, which I use with the AndroiDAQ module here in our lab. This code is open source and is available for download here:

https://code.google.com/p/androidaq-demo-enhancement-project/

and here:

https://groups.google.com/forum/?fromgroups#!forum/androidaq-demo-enhancement

So now let’s review the code below to gain some understanding on how it all works together.

Serial interface for AndroiDAQ module. More information about
the AndroiDAQ module here: http://www.controlcapture.com/androiddaqmod
 
@author: Rick Fluck
'''
import time
import sys
import serial
from serial.tools import list_ports
import threading
import wx
import cv2
 
#capture = cv2.VideoCapture(0)
#capture.set(cv2.cv.CV_CAP_PROP_FRAME_WIDTH, 320)
#capture.set(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT, 240)
fps=15
 
class SerialComm(wx.Frame):
    
    def __init__(self, parent, title):
        # Override to set size and no resize allowed
        super(SerialComm, self).__init__(parent, title = title, size=(590,330))
        # Get serial port list on machine
        portList = list(self.serial_ports())
        # Insert hint for end user
        portList.insert(0, 'select port to connect')
        #Create users interface
        self.InitUI(portList)
        self.Centre()
        self.Show(True)
       
    def InitUI(self, myPorts):  
       
        #Create communications panel having +20 border size
        commPanel = wx.Panel(self)
        font = wx.SystemSettings_GetFont(wx.SYS_SYSTEM_FONT)
        font.SetPointSize(10)           
        commPanelSizer = wx.BoxSizer(wx.VERTICAL)
       
        #Create a ports list which is filled with the systems Serial Ports
        portsBox = wx.BoxSizer(wx.HORIZONTAL)
        portLabel = wx.StaticText(commPanel, label ='Comm Ports')
        portsBox.Add(portLabel, flag=wx.RIGHT, border = 8)
        portsListBox = wx.ComboBox(commPanel, pos = (50, 30), choices = myPorts)
        portsListBox.SetSelection(0)
        portsListBox.Bind(wx.EVT_COMBOBOX, self.OnSelect)
        portsBox.Add(portsListBox, proportion = 1)
        commPanelSizer.Add(portsBox, flag = wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border = 10)
       
        #Create an input text control for user to enter commands to Serial port           
        inBoxTag = wx.BoxSizer(wx.HORIZONTAL)
        inLabel = wx.StaticText(commPanel, label =' Comm Port Input:')
        inLabel.SetFont(font)
        inBoxTag.Add(inLabel)
        commPanelSizer.Add(inBoxTag, flag = wx.LEFT|wx.TOP, border = 10)
        inBox = wx.BoxSizer(wx.HORIZONTAL)
        self.inTextBox = wx.TextCtrl(commPanel, style = wx.TE_PROCESS_ENTER)
        self.inTextBox.Bind(wx.EVT_KEY_DOWN, self.onEnter)
        inBox.Add(self.inTextBox, proportion = 1)
        commPanelSizer.Add(inBox, flag = wx.EXPAND|wx.LEFT|wx.RIGHT, border = 10)
       
        #Create an output text control to view output from Serial port
        outBoxTag = wx.BoxSizer(wx.HORIZONTAL)
        outLabel = wx.StaticText(commPanel, label = 'Comm Port Output:')
        outLabel.SetFont(font)
        outBoxTag.Add(outLabel)
        commPanelSizer.Add(outBoxTag, flag = wx.LEFT | wx.TOP, border=10)
        outBox = wx.BoxSizer(wx.VERTICAL)
        self.outTextBox = wx.TextCtrl(commPanel, style = wx.TE_MULTILINE)
        font1 = wx.Font(8, wx.MODERN, wx.NORMAL, wx.NORMAL, False, u'Consolas')
        self.outTextBox.SetFont(font1)
        outBox.Add(self.outTextBox, proportion = 1, flag=wx.EXPAND)
        commPanelSizer.Add(outBox, 2, flag = wx.BOTTOM|wx.LEFT|wx.RIGHT|wx.EXPAND, border = 10)
        commPanel.SetSizer(commPanelSizer)
   
    def onEnter(self, event):
        # Triggers if an event happens
        keycode = event.GetKeyCode()
        # If either Enter key is pressed do...
        if keycode == wx.WXK_RETURN or keycode == wx.WXK_NUMPAD_ENTER: 
            # Get the user's input value from the input text control place in variable recv
            recv = self.inTextBox.GetValue()
            # If recv is exit, close Serial port and leave application
            if recv == 'exit':
                self.ser.close()
                self.Destroy()
            else:
                # send the characters to the device connected to Serial port
                # (note an added \r\n carriage return and line feed
                # to the characters - this is requested by AndroiDAQ)
                self.ser.write(str(recv).encode())
                self.ser.write('\r\n')
                # Clear the user input text control
                self.inTextBox.SetValue('')
   
        event.Skip()
           
    def OnSelect(self, e):
        # Triggered when an item is selected in the CommPorts list
        # Get the string name of the item in the list
        portName = e.GetString()
        # Send the item string name to connect to Serial port
        self.serial_port_connect(portName)
           
    def onClose(self, event):
        # Event trigger on closing of window
        # Close serial port
        self.ser.close()
        # Close window
        self.Destroy()   
 
    def receiving(self, ser):
        # Serial port receiving, ran under thread
        while True:
            # Small delay for CPU savings
            time.sleep(0.1)
            try:
                if ser.inWaiting() >0:
                    msg = ser.readline()
                    if msg:
                        self.handle_data(msg)
            except:
                pass
 
    def handle_data(self, data):
        # Outside caller to receive data and place in main thread GUI
        wx.CallAfter(self.outTextBox.AppendText, data)
        #print(data) 
             
    def serial_port_connect(self, portNum):       
        # Configure the serial connections, you may need to adjust these for other devices
        # this is set up for AndroiDAQ
        self.ser = serial.Serial(
            port = portNum,
            baudrate = 115200,
            parity = serial.PARITY_NONE,
            stopbits = serial.STOPBITS_ONE,
            bytesize = serial.EIGHTBITS,
            timeout = 0
        )
        # Check for open Serial port
        if self.ser.isOpen():
            # Show that port is open and that you are connected
            self.outTextBox.SetValue('Connected to AndroiDAQ.\r\nEnter your commands in Comm Port Input below.\r\nEnter "exit" to quit the application.\r\n')
        # Set focus to user input text control
        self.inTextBox.SetFocus()
        # Create and start Serial port receiving thread as daemon for auto stop and cleanup
        self.thread = threading.Thread(target=self.receiving, args=(self.ser,))
        self.thread.setDaemon(True)
        self.thread.start()
       
    def serial_ports(self):
        #Returns a list for all available serial ports
        # Check system platform as Windows is different than Linux/Unix
        if sys.platform == 'win32':
            # windows
            for i in range(256):
                try:
                    s = serial.Serial(i)
                    s.close()
                    yield 'COM' + str(i + 1)
                except serial.SerialException:
                    pass
        else:
            # unix
            for port in list_ports.comports():
                yield port[0]
   
app = wx.App()
SerialComm(None, title = 'AndroiDAQ Comm')
app.MainLoop()   

 

As you can see by reading through the above Python code, when the application is first started, the operating system is queried for all of its serial ports and the names of these ports. These returned port names are put into the portList list variable, which are then used to populate the wxComboBox on the user’s interface. Then the user interface is constructed and shown, with hints being used to instruct the user how to use the application. For example, the Comm Ports’ wxComboBox is initialized with a “select port to connect” string as the list’s first element, element zero. The “Comm Port Output:” and “Comm Port Input:” text labels are wxStaticText widgets and the accompanying text controls for each of these labels are wxTextCtrl widgets, which use events to control the flow of the application via user interaction. 

Events are user actions when using any application, such as selecting a name from a list or pressing the enter key after inputting text into a text control. The onSelect and onEnter functions are used in our example code to sense and run various other functions in reaction to these types of user event actions.

The onSelect function is triggered when the user selects a serial port name from the Comm Ports ComboBox list. Here the select serial port name string is parsed and then sent to the serial.port.connect function, which configures the port and uses this name to try to open the desired serial port for tow way communication.

The onEnter function triggers when the user presses either the main enter key or the keypad enter key on his computer system. This function parses the data from the Comm Port Input text control and runs the applicable functions. If the user entered “exit” in the Comm Port Input text control, the application will close the serial port and the application will be exited. If the user entered an AndroiDAQ menu item function, say a 05 to read the AndroiDAQ RTC time setting, this data is parsed to a string, a carriage and line feed character set is added to the inputted string, and then this entire string sequence is sent to the serial port, via the serial write command and to the AndroiDAQ module’s USB or Bluetooth port.

When AndroiDAQ receives this 05 string sequence, it will send to the serial port its reply of the date and time setting of its real time clock (RTC). Here the ‘receiving’ function of the Python application, which is running in its own thread, is used to display the data received by the serial port. The user’s interface is updated outside of the thread with this received data via the handle_data function.

We also utilize an onClose function, which is triggered when the application’s window, or panel, is closed by the user. This is done to ensure that the serial port is properly closed by the application and not left open, for good programming practice.

As you can see it is relatively easy to communicate with the AndroiDAQ module via a serial port under Python. The above Python code can be used with wired serial ports via USB, or with Bluetooth serial communication using Serial Port Protocol. I kept the Python code example simple, so that you can easily add your own functions to the code for more complex Python applications for the AndroiDAQ module. You may have noticed when you reviewed the code that I have some OpenCV code commented out. OpenCV is an image processing and video camera acquisition library, so someday I will expand this code, which will lead to another post to explore Python, OpenCV and AndroiDAQ; stay tuned.