How we built a bioreactor on top of the Raspberry Pi

If you don't know, a Raspberry Pi is a low-cost computer used both at home and in industry. The Raspberry Pi can run Linux, connect to the internet, and has lots of input/outputs, making it very useful for being the "brain" of your system. People have put the Raspberry Pi at the center of robots, LED matrices, home-automation systems, and more at home. And in industry, they are being adopted to run subsystems of factories and as deployed IoT devices. 

Here at Pioreactor, we are building our bioreactor system on top of the Raspberry Pi computer. If you've never used a Raspberry Pi, don't feel intimidated. We designed the Pioreactor so that using it is simple enough that our you don't need to be a software developer, statistician, electrical engineer, or biologist - just  curious!

Don't have time to read the entire article? Here's a quick snapshot of what we're talking about:

  • The core logic of the Pioreactor is bundled into Python scripts, and the hosted Node web server executes these scripts. 
  • Pioreactor uses SQLite and MQTT to build data pipelines and store data longterm, respectively. 
  • We consolidated all the necessary chips and drivers onto a single Raspberry Pi HAT 

The brains of the Pioreactor

The bioreactor behaviour is controlled by Python scripts. Okay, smart Python scripts, but still just Python scripts. Later we will see how these scripts can pass messages and events to each other. So why did we choose Python? Mainly because it's the language we are most familiar with. That being said, it's a language many developers are familiar with, which encourages more developers to contribute to Pioreactor directly or through our plugin system. We also like how Python has terrific libraries for data processing, analytics, and working with the Raspberry Pi. We are also happy with the performance of Python - our scripts are pretty light on compute and memory. In fact, we have lots of additional compute and memory available, even when running on a Raspberry Pi Zero. 

If you run scripts often, you'll find you want a shortcut. That's why we developed a command line interface (CLI) on top of our scripts. This CLI is the main entry point for working with the Pioreactor. See our docs on the CLI here. Most users won't use the CLI but instead will use our web interface. The CLI was developed for developers, or power users, as a consistent entry point for controlling the Pioreactor. 

Data storage

As data engineers, we have a strong opinion on how data should be handled. We picture Pioreactor's data as streams, and we needed to build pipelines to move the data to different sinks. Some of those sinks might be long-term storage, some sinks might be real-time analytics systems that produce their own streams of data. Data could be numbers, like an optical density reading, but it could also be events. These events might trigger further downstream events. 

We also wanted to display historical data in the UI, which meant we needed a storage system with all historical data and could be read quickly. Settling on a simple data architecture, the two core pieces of technology are SQLite and MQTT

SQLite is our long-term data storage option. It is a very reputable, lightweight, and a boring database. Perfect for us because boring is good. SQLite has a long history of working well with Raspberry Pis too, so there's a rich ecosystem of tools and support.

Many people haven't heard of MQTT, so I want to spend some time describing what it is and why it's useful. MQTT is our real-time component that defines most of our data pipelines. It does this by being a broker of data. It will accept new data points (or events) from anywhere and pass on these data points to those who want them. For example, a Python script (process) that reads the optical density measurement will publish data to MQTT, and MQTT will send that measurement to other processes that are subscribed to the latest optical density measurement. Another use case is how we add data to the SQLite database. Processes will publish data to MQTT, and a dedicated MQTT-to-SQL process will subscribe to all these data streams and write to SQLite as the data comes in. Below is a diagram of the relationship between MQTT, processes, and SQLite:

Relationship between Python scripts, MQTT, and SQLite
MQTT is also how processes can talk to other processes. For example, turbidostat needs to know the latest OD reading, so it subscribes to what OD reading script is publishing. MQTT goes beyond a single Pioreactor too. Other Pioreactors in your cluster will talk to the same MQTT broker. So Pioreactors can talk to other Pioreactors too through MQTT. This is how we know the status of each Pioreactor in a cluster and can issue commands to them.
Another Pioreactor accessing the original MQTT broker
Finally, when a user opens up pioreactor.local on their mobile or laptop, this connects to MQTT and SQLite to produce live-updating charts with back-filled historical data.
Users connecting to the webserver, MQTT and SQLite
What about data integrity? The SQLite database, being so lightweight, is backed up every few days, and copies are sent to other Pioreactors on the network, if available. This provides the necessary replication of your important experiment data (but also keep essential experiment data in different locations!)

The Pioreactor web interface

On each leader, Pioreactor is a Node.js webserver (source code here). That web server is only available on the local network and is accessible at pioreactor.local from any other devices on the same network. The web server serves our React app, which connects to the MQTT broker to provide real-time data to the website. It connects to the SQLite database to provide historical data (charts, logs, and summary statistics). This goes the other way, too. Buttons and actions in the UI send commands back to MQTT or the CLI, which start or modify the bioreactor's behaviour. 

Running external motors from the Pioreactor HAT

A Pioreactor can control up to four motors: one is dedicated to stirring, and three other for peripherals (peristaltic pumps, air pumps, a robot if you desire...).  An option we could have taken, early in the Pioreactor development, is to use inexpensive microcontrollers to control subcomponents. For example, using the RaspberryPi to send commends to a dedicated Arduino, and the Arduino controls stirring. This provides a nice separation of concerns: the Arduino can efficiently execute running a motor, and the Raspberry Pi only needs to send commands and doesn't care about how the stirring happens. This separation of concerns is analogous to what we call "microservices" in software engineering. Another benefit is by delegating control to microcontrollers, they are able to use external power that isn't available to the Raspberry Pi (recall the Raspberry Pi's power rail is limited to 5V and ~1.5 amps). 

Unfortunately, delegation to subsystems adds complexity to the overall system, for both users and developers. For users, they now have to deal with additional hardware components, connections, and more hence more surface area of failure. Costs also increase with more subsystems. For developers, the codebase would be split further. An unexpected stirring behaviour might be due to faulty code on the microcontroller, or in the command sent, or in the Raspberry Pi code - all in different programming languages, too.

The power constraints on the Raspberry Pi is solved by our HAT providing an auxiliary power input:

This auxiliary power source allows the Pioreactor to handle more demanding power sinks, and even able to scale the Pioreactor to larger volume bioreactors!


Like our motor system, we had a similar choice to make for our LEDs and our photodiodes. Do we want to use a microcontroller, or can we have the Raspberry Pi handle this? Using a LED driver allows us to delegate the state and brightness of the LED to a dedicated chip. The Raspberry Pi only needs to send state over I²C to the chip. And by capping each LED's current draw to a maximum of 50mA, we can safely tap into the Raspberry Pi's power rail.

The rest of our input-outputs

The RaspberryPi comes with 40 GPIO (general purpose input-output) pins, some with special functions. For example, two of the GPIO pins are assigned to be a I²C channel. This is incredibly useful for transferring data between Python and chips (our temperature sensor, LED driver, and ADCs use this channel). We also use the GPIO's pulse-width-modulation functionality to varying the speed of off-board motors (for example, how stirring RPM is controlled). Other uses of the GPIO pins include:

  • Turning the HAT's LED on.
  • Detecting when the broadcast button is pushed.
  • Detecting the RPM of the magnetic stirrer. 

We've purposely exposed the 40 GPIO pins on our hat so that users can add their own hardware to the Pioreactor. For example, suppose you have a digital pH meter that you want to integrate. This can be plugged into the I²C pins, and a Pioreactor plugin can be built to read and record the data. 


There's lots more to say about how we built the Pioreactor on a RaspberryPi. I haven't talked about the HAT specifications (or what that even means), EEPROM, the Linux utilities we are taking advantage of, or the installation process. Maybe we'll need to write a Part II to this! Stay tuned! 

Hi there! Interested in the Pioreactor?

The Pioreactor is not ready yet, but stay informed by signing up for our newsletter!