Automation is quite an important part in instrumentation, espicially for those who are working in the field of biomedical engineering. With a nice coordination between multiple hardwares (i.e., syringe pump, micromanipulator, temperature control, multiposition actuators) through a programmed script, one can make his or her experimental workflow much more robust and less noisy. (Elegancy is also important!)

Previously, I have been using LabVIEW for this purpose. LabVIEW is a graphical programming environment in which you can visualize your code readily and design an intuitive user interface. LabVIEW VISA library is specifically designed for easily-handled serial communication. However, LabVIEW is not free and the coding style is not as flexible as other programming languages. Furthermore, it is extremely hard to explain the code to others because of its graphical nature. (Everything is wired and placed all around…)

An example of LabVIEW code. In the block diagram, one can define some common parameters (i.e., COM port, baud rate, etc.) and then control write and read operations. In the front panel, dropdown menus, text boxes, and buttons could be defined for user interaction.

These are why I gradually switched to Python programming. With well-defined packages and good compatibility under multiple coding environments, I found automation based on Python much more efficient than LabVIEW. In this post, I will give a quick demo of how to smoothy realize automation with Python.

Environment setup

Here I will use jupyter notebook as the environment for running the code, and vscode as the editor. The reason of such a mixture of selections is that jupyter notebook has a better GUI, while vscode has a smarter code editor. Programming simultaneously in both environments wouldn’t be a bad idea if you have a good git habit.

Jupyter Notebook

Here we need to install and import following packages. If you are using Anaconda, you can create a conda environment and install the packages in the command line.

The detailed installation guide and documentation can be found in the following links.

After installation, we import them in the code.

import serial # for serial communication
import time # for time delay
import ipywidgets as widgets    # for GUI
import functools    # for callback function
from IPython.display import display # for GUI
from ipywidgets import Layout, HBox, VBox   # for arrangement of widgets

Next, let’s prepared the vscode environment.

Visual Studio Code

All we need are the following extensions.

Interesting thing is that you could actually implement Jupyter notebook in vscode. You can refer to this tutorial for more details. I found installing python packages in vscode is a bit complicated in vscode, so I still implement the code in jupyter notebook.

Code demo

Set up serial communication

Let’s start with connecting our hardware to the computer. Let’s say we already have a device connected to COM port 4 through a USB cable. A successful connection can be found in device manager in Windows. We can use the following code to construct the serial communication.

# define an open event (connect to the device)
global ser_device
def open_event(obj):
    ser_device = serial.Serial(
        port='COM4',
        baudrate=115200,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        bytesize=serial.EIGHTBITS
    )
    ser_device.isOpen()

The actual parameters depend on the device you are using. Besides, we also need to define a close event.

# define a close event (disconnect from the device)
def close_event(obj):
    ser_device.close()

Set up GUI

Now we can design the GUI for the user to connect and disconnect the device using the above functions.

# define buttons
bon = widgets.Button(description="Connect")
boff = widgets.Button(description="Disconnect")
# define the event if the button is clicked
bon.on_click(open_event)
boff.on_click(close_event)
# display the buttons
print("The expected output: ")
display(HBox([bon, boff]))

As you run the code, you will see the following GUI.

Send and receive data

We move on to give a command input. Let’s say you wanna send a command go to position 1 to your device. The command is entered in the text box by the user.

# define a text box
text = widgets.Text(
    value='',  # default value
    placeholder='Type something',   # grey, default text
    description='Command:',
    disabled=False
)
# define a send command button
bsend = widgets.Button(description="Send")

Then we can define a function to send the command to the device. Normally, the device will return some data after receiving the command.

# define a send and receive event
def send_and_receive_event(obj):
    # send the command
    ser_device.write(text.value.encode())
    # wait for 0.1s, and then read the response
    time.sleep(0.1)
    out = ''
    while ser_device.inWaiting() > 0:
        out += ser_device.read(1).decode()
    if out != '':
        # if the response is not empty, print it
        print(">>" + out) 

Notice that the command is simply text.value, the value entered in the text box. The encode() function is used to convert the string to bytes. Conversely, the decode() function is used to convert the bytes to string.

# link the send button to the event
bsend.on_click(send_and_receive_event)
# display the buttons
display(HBox([text, bsend]))

With all the above, we have a functional GUI for sending and receiving data. The output is shown as follows.

Now you can try to turn on the device, and send some commands to it. The response should be printed in the console.

One-event-multiple-functions

In the above example, send_and_receive_event() is defined for implementing one specific function. However, we may want to implement multiple functions with one event. For example, it would be nice if we have two buttons, one for go to position 1, and another for make the rotation clockwise, both linked to send_and_receive_event(). This is more concise design than having two separate events.

We can use functools.partial() to achieve this. The following code is an example.

# revise the send_and_receive_event
def send_and_receive_event(obj, option):
    # option = 1: go to position 1
    if option == 1:
        ser_device.write("Go to position 1".encode())
    # option = 2: make the rotation clockwise
    elif option == 2:
        ser_device.write("Make the rotation clockwise".encode())
    # wait for 0.1s, and then read the response
    time.sleep(0.1)
    out = ''
    while ser_device.inWaiting() > 0:
        out += ser_device.read(1).decode()
    if out != '':
        print(">>" + out)

# define two buttons
b1 = widgets.Button(description="Go to position 1")
b2 = widgets.Button(description="Make the rotation clockwise")
# define the event if the button is clicked
b1.on_click(functools.partial(send_and_receive_event, option=1))
b2.on_click(functools.partial(send_and_receive_event, option=2))
# display the buttons
display(HBox([b1, b2]))

Here the functools.partial() function allows us to put the option argument into the send_and_receive_event() function. The option argument is used to determine which command to send to the device.

Speed up with Github Copilot

With all these basics, you can start to build your own GUI for your device. During the process, you can rely on Github Copilot to help you overcome some difficult formatting issues. For example, let’s say the device requires you to send a command in the following format.

# Command configuration
# Command format: [@][00][cmd type][cmd code][data][fcs][*][CR]
# [@] - start of command
# [00] - address of the device, normally 00 for the cube
# [cmd type] - command type, [1] parameter read, [2] paramerter write, [3] special command
# [cmd code] - command code, normally parameter number
# [data] - specify the set value or setting content
# [fcs] - frame check sequence, the sum of all bytes from [@] and [data]
# [*][CR] - end of command. [CR] is carriage return.

You can simply put all these information into the comment part of the code, and let Github Copilot to format it for you. It wouldn’t be perfect, but line by line, it will help you to save a lot of time.

Summary

In this post, we have learned how to build an automation code with user GUI for your device using jupyter notebook and vscode. For more information, please check the references. I hope you find this post useful. Thanks for reading!

Good luck with your project!

References