As you know, here at This Smart House we have an interest in aquaponics! In fact, we have an entire aquaponics garden in the basement providing us with fresh produce year-round! As our aquaponics system became established, it became clear the nutrients from our display tank / fish waste weren’t going to be enough to keep the plants happy and healthy. Micronutrients, such as iron, are an important part of the aquaponics ecosystem and for those supplements are needed. There are a lot of options out there, but I decided to go with a product from Seachem (Flourish) as it was designed specifically for a planted aquarium and therefore I could be comfortable it would also be safe for my fish. For my needs all it took was 1oz dose daily, and my plants are as happy! Now of course, it’s really not hard to do this but this is This Smart House, and anything we can automate we try to! I didn’t want to have to worry about remembering to dose the system, so I built myself a peristaltic dosing pump with WiFI support to do it for me instead. Before we get into the good part of how to build it, let’s take a minute to talk about what a peristaltic pump is. A peristaltic pump is a what’s known as a “positive displacement” pump, and works by continuously compressing and releasing a flexible tubing to move fluid through it.
It’s generally not a very fast pump, but it has a lot of great benefits. First great thing about a pump such as this is that it’s actually a pretty simple design, requiring a single motor, a case and some suitably flexible tubing. But even more interesting is when you combine the design with a stepper motor you can use it to move very exact amounts of fluid – making it ideal for an aquaponics system where I need to add a relatively exact amount of fluid to my system. As any good (lazy) maker is going to do, the first thing I did when I decided to build one of these pumps was go on Thingiverse to see if someone else had already solved the problem for me. There are quite a number of 3D printable peristaltic pumps to be found and I honed in on one in particular that seemed to show the most promise. For me the two biggest selling points were:
- More than just the pump mechanism itself, it provided an enclosure to house both an Arduino and a controller board to drive a stepper motor
- It used a very common (and cheap) stepper motor (the 28BYJ)
For my purposes however I didn’t want to use an Arduino to control the device, but rather I wanted to to be able to trigger the pump over MQTT and WiFi. Thus, my first course of action was to revise the base of the pump to mount an ESP8266-12E board. If you’re curious how that worked, you can check it out on Thingiverse. After putting the pump all together, programming my firmware, and testing — I found myself disappointed. While the 28BYJ motors are extremely affordable, that affordability comes at the cost of torque and overall power. The pump would often get stuck trying to rotate with the hose inserted. Moreover, I was constantly running into problems with the tubing slipping up until it stopped working due to an inadequate cover. In the end, after a brief conversation with the original designer (who shared his SketchUp design files that included what seemed to be a NEMA-17 compatible version) I decided if I wanted my pump to work to the standards I had in my head I was going to have to go back to the drawing board a bit and do some experimenting. In the end, after at least 5 failed iterations of prototypes, I came up with a design that worked totally reliably and is used today to dose my aquaponics system with the nutrients it needs without me lifting a finger!
Want to learn how to build one yourself? First, let’s go over the parts you will need:
1 | NodeMCU ESP8266-12ENodeMCU ESP8266-12E |
---|---|
1 | Female Screw-Tight DC Power Jack |
1 | 12V DC 0.5A Power supply |
1 | EasyDriver Stepper Controller (v4.4+) |
1 | NEMA-17 Compatible Stepper Motor |
4 | 608 Bearings |
Various M3 screws and nuts |
You can find the completed models over at our Thingiverse page. To get started you will obviously need to print all the parts. Each part for the most part stacks on top of the previous one and is then secured using various lengths of M3 nuts and bolts. While you’re waiting for all those parts to finish printing you can pull out your breadboard and start wiring up the hardware to make sure everything will work as expected! Here’s a diagram of how the circuit is wired for your reference:
First thing you’ll need to do to build this circuit is a little soldering. I am unaware of any EasyDriver driver boards that come with the leads pre-soldered! For our purposes, you’ll need to solder a header on the motor pins, the power pins, at least 2 of the stepper pins. The other pins aren’t used for this project (you can check out the EasyDriver project page for complete details of the various ways you can use this awesome driver board). Note however in the corner of the driver board you’ll find two pads right next to each other (in the lower left hand corner of the board in my image). These pads must be shorted out for this project, which will make the board function on 3.3V logic instead of 5V logic. Since the ESP8266 is a 3.3V board, this is necessary for it to function properly! You’ll also note that I am powering the NodeMCU board directly from the input power, which I’ve previously mentioned is 12V. You may also notice when you purchase your board that it clearly states “10V MAX” for input power. After a decent amount of research on the subject, I determined this maximum rating is less a hard rule than it sounds and the board works just fine using a 12V input (it may under some circumstances run a little hotter than normal) and the simplicity of using the direct 12V power instead of worrying about voltage dividers or other power regulation headaches for me was well worth the risk of ignoring that warning. As always do your own research and break the rules at your own risk!
Beyond the power, the rest is really pretty straightforward. The EasyDriver board has 4 pins for the stepper motor being used (please consult your stepper motor’s schematic to determine which leads correspond to which coil and wire accordingly), and then another two leads to control the stepper motor itself — one input being the direction the motor should turn, the second being how many steps should be taken. What’s a step you ask? Unlike a regular DC motor which simply spins (perhaps faster or slower), a Stepper motor allows you to move the motor at very precise increments in a given direction. To do this, each motor has a certain number of steps that constitutions a full 360 rotation (as defined by the specifications of the motor). Say for example your stepper motor has 360 steps, meaning each step rotates the motor 1 degree. The EasyDriver board is responsible for controlling the motor appropriately based on instructions from (in our case) the ESP8266 program. Moreover, the EasyDriver board can even “microstep” and move the motor a fraction of a step rather than a full step. The result is a motor that is capable of extremely precise and accurate motion which is why it is used for CNC machines and 3D Printers universally!
Anyway, once you have everything wired up it’s time for some coding. We of course will be using our very own CoogleIOT library to power the basics like WiFi management and MQTT (if you haven’t checked that library out you should).
To make our dosing pump as flexible as possible without having to change the code again, I chose to create a topic that another device could post to with an integer value to determine how many “turns” the pump would do. So for example if our pump is listening on the /aquarium/doser
topic we could have it pump 100 turns worth of fluid into our tank doing something like the following (using the Eclipse Mosquitto MQTT client):
$ mosquitto_pub -t '/aquarium/doser' -m '100'
If you want to skip ahead and browse the full sketch yourself, you can check it out from GitHub right now!
To get started of course we will need the CoogleIOT library for our sketch, but we will also need the AccelStepper library. This is a wonderful library that makes controlling various types of stepper motors pretty easy such as stepper acceleration/deceleration, speed control and more. With both of those installed let’s start our sketch by defining some basic values as pre-compiler constants we’ll be referencing later, as well as initializing some variables we’ll need:
// Include the CoogleIOT library #include <CoogleIOT.h> // Include the AccelStepper library #include <AccelStepper.h> // Define the serial baud rate as 115200 for checking out serial logs #ifndef SERIAL_BAUD #define SERIAL_BAUD 115200 #endif // Define our stepper motor speed as 1000 #ifndef STEPPER_SPEED #define STEPPER_SPEED 1000 #endif // Define our stepper motor acceleration as 500 #ifndef STEPPER_ACCEL #define STEPPER_ACCEL 500 #endif // Define the MQTT topic we will listen on to control the pump #define ACTION_TOPIC "/peristalic/freshwater/1" // Initialize pointers to the CoogleIOT object and MQTT object // for easy reference CoogleIOT *iot; PubSubClient *mqtt; // Create an instance of the AccelStepper object // Since we are using a driver board we use that, and define the // pins for that driver's STEP and DIRECTION pins, which map to // ESP8266-12E's D1 and D2 IO pins (GPIO 4 and 5) AccelStepper stepper(AccelStepper::DRIVER, D1, D2); // Create a string buffer, just to store random strings we manipulate char msg[150]; // Create an integer that defines how many turns still left to execute // we use this to persist the count every loop. As the motor completes // turns we will update this appropriately int turnsToExecute = 0;
Now let’s take a look at our setup()
function of the sketch. It’s a pretty straightforward piece of code that does a number of initialization tasks for us:
- Creates an instance of the CoogleIOT library, setting the status LED pin to
LED_BUILTIN
, which is the built-in LED of the board - Calls the initialization method for CoogleIOT, which handles all of the magic when booting up the device like connecting to the WiFi stored in memory, starting a webserver, etc.
- Send some pretty logging information we can see from the serial port (or web interface) to remind us of our MQTT topic
- Initialize the stepper motor
- Initialize the MQTT client if available
void setup() { // Create an instance of the CoogleIOT library iot = new CoogleIOT(LED_BUILTIN); // Enable Serial communication at the bps defined by the SERIAL_BAUD definition iot->enableSerial(SERIAL_BAUD); // Initialize CoogleIOT, which creates a management webserver, // connects to WiFI, provides a configuration hotspot // initializes MQTT, etc. iot->initialize(); // Output some logging data iot->info("CoogleDoser Initializing..."); iot->info("-=-=-=-=--=--=-=-=-=-=-=-=-=-=-=-=-=-"); iot->logPrintf(INFO, "MQTT Action Topic: %s", ACTION_TOPIC); iot->info("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-"); // Initialize the Stepper settings. We set Max speed and current speed to the same thing // and provide an acceleration rate stepper.setMaxSpeed(STEPPER_SPEED); stepper.setAcceleration(STEPPER_ACCEL); stepper.setSpeed(STEPPER_SPEED); iot->info(""); // The very first time you fire up the code, the ESP won't know how to connect to your // WiFi, nor where it can find the MQTT server once you've connected. // So let's make sure we have an MQTT client to use first if(iot->mqttActive()) { // Grab the instance of the MQTT client from CoogleIOT for easy reference mqtt = iot->getMQTTClient(); // Set a callback to be executed every time one of our subscribed topics // gets a message (defined later) mqtt->setCallback(mqttCallback); // Subscribe to the topic we defined at the top mqtt->subscribe(ACTION_TOPIC); iot->info("Coogle Doser Initialized!"); } else { iot->error("Initialization failure, invalid MQTT Server connection."); } }
As you can see, this code is pretty straightforward and should be relatively easy to figure out with the inline comments. One thing to note if this is your first CoogleIOT project is an important detail though. The very first time you compile and upload a CoogleIOT-powered sketch to your device it won’t know how to connect to your WiFi, where your MQTT server is, etc. These are details that are stored in the persistent memory of the ESP8266 and need to be first configured. By default, CoogleIOT will create a Hotspot called CoogleIOT_<random>
you can connect to with your smart phone or computer (default password: coogleiot
). Once connected you can go to 192.168.0.1 in a browser and access the configuration interface of the device.
From there, you can set up WiFI, MQTT server configurations, etc. and then restart the device. On the next boot it will automatically connect to WiFi, connect to your MQTT server, and you can start sending the device commands (be sure to review the Serial log or the log from the web interface if you are having problems for information as to what’s wrong).
With setup out of the way, let’s take a look at the mqttCallback()
function. This function was referenced in the setup()
function as the function that will be called when the MQTT client gets a message on the topic we’ve subscribed to:
void mqttCallback(char *topic, byte *payload, unsigned int length) { // Create a String object out of the payload // by casting it as a char * String input((char *)payload); // Call the toInt() method on the String, getting an integer // interpretation of it turnsToExecute = input.toInt(); // Dump some logs so we can see what's up iot->logPrintf(DEBUG, "MQTT Callback Triggered. Topic: %s\n", topic); iot->logPrintf(DEBUG, "Turns to execute: %d", turnsToExecute); iot->info("Action Complete!"); }
The mqttCallback()
function takes three parameters. The topic
parameter (which is the particular topic that triggered the callback), the payload
which is a collection of bytes containing the data that was sent to the topic, and length
which defines how many bytes we received.
The idea of course here is that the message we are sending to this topic will be an integer value, representing the number of turns we want the pump to execute. Since the callback’s payload is typed as byte *
however, we need to do some conversion to get the integer value we need.
Ultimately however, all we really need to do here is grab the number of turns we received in the callback and assign that value to the turnsToExecute
variable we defined at the top of the sketch. This will be used in the loop()
function we’ll discuss next.
One of the great things about the AccelStepper library is that it’s designed to be something called “non-blocking”. What does that mean? Well to understand why that’s important you need to understand a little bit about how Arduino’s or ESP8266 boards are different from your garden variety computer today. Unlike a modern computer that can do many things at effectively the same time (browse the web while downloading e-mail, for example), these boards execute their programs in an entirely linear fashion one thing at a time. Why is that important? Well we expect devices to be able to at least appear like they can do more than one thing at once (interact with an interface while it’s performing an action for example) so when developing for these boards it’s important to make sure no single task takes too much time. If something takes a long time and holds up the execution of other things we call that “blocking”, where as if the code is designed in such a way that it can function without holding everything else up we call that “non-blocking”.
This is really important for this particular project, because in my tests it took about 100 turns of the stepper motor to pump 1oz of fluid (what I needed), and that itself took around 60 seconds. If the stepper logic we used to control the motor was blocking, during the 60 seconds the pump was “on” nothing else on the board would work — including the web interface!
With that understood let’s take a look at the loop()
function:
void loop() { // call the loop() method of CoogleIOT, to handle any incoming // web requests or other various tasks needed to manage the background stuff iot->loop(); // Check to see if the stepper is currently moving or not if(stepper.distanceToGo() == 0) { // If it's not moving, check to see if it should be moving by looking // to see if we have any turns to execute if(turnsToExecute > 0) { // If we have turns, instruct the AccelStepper library to move the // stepper all the turns we needed stepper.moveTo(stepper.currentPosition() + (1600 * turnsToExecute)); turnsToExecute = 0; } } // Tell AccelStepper to run, which has the motor move a little bit // and then releases control for the next loop to execute stepper.run(); }
As you (probably) know already, the loop()
function of a sketch is the “main” part of the application. As the name implies, it is called over and over again in what amounts to a loop to execute code. For our case, we have three main tasks to accomplish during every loop:
- Give CoogleIOT control to handle webserver requests, etc.
- If the stepper isn’t moving, check to see if it should and instruct the AccelStepper library on how far to move if it needs to
- Give AccelStepper control to incrementally move the motor if necessary (thereby pumping our fluid)
We use the distanceToGo()
method of the AccelStepper class to check to see if AccelStepper has movement pending first, but if it doesn’t we check our state variable turnsToExecute
to see if there are any turns to execute. Remember from earlier, this variable is set when the mqttCallback()
function is called to however many turns were published to the MQTT topic. If there are turns to execute, we use the AccelStepper moveTo()
method to instruct the motor to move to the position that is defined as: current position + (position steps per turn * turns to execute from mqtt)
. I use 1600 for the number of steps per turn, but this is completely dependent on your stepper motor (check out the specs for how many steps makes up a turn).
And that’s it! I hope you’ve enjoyed learning how to build your very own open-source peristaltic pump. Be sure to check out the source code in GitHub and the 3D models on Thingiverse for everything you’ll need to make one yourself.
Why do you choose a nodeMCU over something like a d1 mini? Is it just the fact that it’s on prime? Cause the d1 mini is like 2.5USD if you search a tiny bit harder than I did for this link:
https://www.aliexpress.com/item/ESP8266-ESP12-ESP-12-WeMos-D1-Mini-WIFI-Dev-Kit-Development-Board-NodeMCU-Lua/32653918483.html
I like the NodeMCU because it has built in power regulation. They are rated at a max of 10V input but I run them all the time at 12V without a problem! This is especially true for a project like this where your driving stepper motors.
Oh cool, that would make a few things easier.
I like your wrapper for all the wifi and mqtt stuff by the way. Different approach than mine, always interesting to see how others do it.