Close

PC code

A project log for Microfluidics control system

An all-in-one solution for controlling microfluidic chips

Craig WatsonCraig Watson 10/20/2017 at 23:340 Comments

Now that most of the hardware- and microcontroller-related stuff has been covered, let's take a look at the PC code.

You can find the repository here: https://github.com/freeheel3r/ufcs-pc

As a reminder, the requirements for this part were, mainly, to be able to communicate with the ESP32 over USB (and eventually bluetooth), to show a usable and if possible not-too-ugly user interface, and to be cross-platform, including Android and/or iOS.

I have quite a lot of experience with Qt, and it happens to be one of the best frameworks for this application: first of all, it is cross-platform, making it very easy to adapt the code to run on Windows, Linux or Mac but also mobile devices; it has libraries for serial communication (which are, again, cross-platform) and some good libraries for GUI creation. Another important aspect is that is coded in C++, just like the ESP32 (so there is no need to know two different programming languages so start working on this project).

Here again, I tried to make everything as modular as possible. The serial communication, routines, and GUI back-end are almost completely independent of one-another. So if one part needs to be rewritten, there is no need to refactor the whole codebase.

GUI

Right now, everything but the routines (i.e., pre-programmed experiments) is functional. Serial communication works, and the user interface allows to both send commands to the ESP32, and display status of all the components. The interface still needs some work, but it is at least functional and close to a final design. Here's what it looks like:

Behind the scenes, the main classes are:

ApplicationController

The main class. This provides the interface between the various components of the backend (serial communication, routine controller) and the GUI, written in QML.

Communicator

Handles serial communication. A few functions can be called to set a given component to a given value (for example, setValve()); it also reads incoming data to get status updates on components. When that happens, a signal is emitted. Signals and slots are very useful to communicate between different classes.

PCHelper, ValveSwitchHelper 

These two little classes make it easier to interface between the QML front-end and the rest of the backend. They are essentially a backend for the buttons shown on screen. Without going into too many details here, these classes make it possible to keep the QML code very simple, while also allowing the backend (ApplicationController) to keep track of which button on the screen corresponds to which physical component.

For example, to define the buttons (1-23) shown in the above screenshot, one simply has to declare:

ValveSwitch {
    valveNumber: 17
}

And at runtime, the backend will be informed of the existence of this button, and update its status whenever the Communicator class tells it that valve number 17 has been toggled.

Routines

I am a firm believer that anything that can be automated should be automated, and so I intend to have my control system run my experiments for me. This will be accomplished by the RoutineController class, which is partially coded but not yet accessible from the GUI.

The comments in the source code explain the concept quite thoroughly, which I'll just summarize here.

As a compromise between user-friendlyness and ease of development, I decided to have routines in a text format, which is parsed at run-time. (The two ends of the spectrum would be a complete GUI-based solution, to be super user-friendly, on one end, and a routine defined entirely in C++ on the other).

The format is easy to understand and edit:

# Comments begin with a number sign.
# Empty lines are ignored
pressure 1 7.5 psi # This sets pressure regulator 1 to 7.5 psi
pressure 2 20 # If units aren't specified, the default is used. Here, it is pounds per square inch (PSI)
valve 2 close
valve 3 close
valve 4 close
valve 14 open
wait 30 seconds # pauses can be in units of seconds, minutes or even hours
valve 14 close
valve 15 open
wait 2 minutes

The idea is that you would write out your experiment in this way, save the text file, and load it in the app. Currently, I can parse these routine files, check for errors and execute the instructions. I even wrote a few rudimentary unit tests for this, which turns out to be very handy.

Before I can integrate this functionality into the application, I will need to have this run in its own thread. Once that is done, I can figure out the best way to load it from the user interface, as well as display the contents of the file, and current progress of the routine while it's running, and other details...

Discussions