AquaPic - Aquarium Controller

Reef tank controller used to monitor various parameters such as temp and pH, and also control equipment such as pump and lights.

Similar projects worth following
Reef tank controller used to monitor various parameters such as temp and pH, and also control equipment such as pump and lights.

All firmware/software is GPL, and probably not tested and completely full of bugs.
All hardware is OSH.

HMI and Main Controller

The main controller consists of Raspberry Pi and a 7" touchscreen.


There's several reasons I want to have this thing connect to the internet, but mainly I want to be able to control my AquaPic from anywhere with my phone.

Power Supply and Communication Bus Connectors

I plan on using a power supply for the 5Vdc and 12Vdc. The different slave modules will be mounted to a DIN rail. RS485 will be used for serial communication.

4-Channel 0-10V or PWM Dimming

The AquaPic will include both PWM and 0-10V dimming references for lighting. To accomplish this I'm using a micro to generate the PWM signals and then programmable switching that straight out or through a filter/gain circuit for 0-10V.

6-Channel Input

General inputs used for float switches, flood detection, button, etc. The front on the input card will have blue LEDs to indicate which switch is closed.

pH and ORP Input, Gain and Isolation

This card will incorporate two galvatically isolated circuits for pH and ORP measurement. The first input will only measure pH and the second will measure pH or ORP.

Temperature and Level Monitoring

I will be using both temperature and level sensors that output linear voltage, between 0-5Vdc. This way I can plug either a temperature sensor or a level sensor into any of the 4 channels this card will have.

Power Strips

Each power strip will include 8 outlets controlled by either a solid state or mechanical relay. I would also like to measure the current on each of the outlets.

Please visit my build log website for more information.

  • 1 × Raspberry Pi 2
  • 1 × Waveshare 7in HDMI Touchscreen
  • 1 × Edimax EW-7811Un WiFi Dongle
  • 1 × JBtek USB to RS485 ch340T Dongle
  • 1 × MDR-20-5 Power Supplies / AC-DC Power Supplies, 5Vdc 20W

View all 7 components

  • November 24, 2017

    Skyler Brandt11/24/2017 at 08:02 0 comments

    I've been continuing the trend of cleaning up the project by moving all the board designs and firmware into a single repository. The first reason for this is that the board designs are now revision controlled with git instead of copying directories. The second is that hopefully this reduces the confusion between what firmware is meant to run on which board revision.

    I also started replacing all the PIC16F microcontrollers with PIC32MM micros. So far the only card/module that is using a PIC32MM is the analog input card. The new PIC32MMs are about the same price as the older PIC16Fs but with a lot more power. More power probably isn't necessary but the analog input card performs quite of bit of 32 bit math include multiplication and division. The PIC16Fs sport an ALU that doesn't include any hardware multiplication and are kind of terrible at even 8 bit multiplication. The PIC32MMs shouldn't have any issue since the architecture has a single cycle multiplication unit. I briefly thought about using an ARM Cortex-M, but ARM is an entirely different ballgame than I'm used to.

    While redesigning the analog input card I decided to go with a dedicated ADC with 16 bit signed resolution. All of the analog temperature sensors I've looked at have a rather small resolution, i.e. voltage per degree C, so I decided that 16 bits was the easiest solution to overcome the small resolution and avoid implementing some sort of amplifier circuity. I also added protection on each of the inputs. Its pretty standard input protection with the exception of the potentiometer. The pot is used to vary the value of voltage divider and thus vary the input voltage to the ADC. This allows for increasing the ADC input voltage for devices that have a limited output. For example, the MCP9701 outputs a voltage around 2.8Vdc at 125°C. With the pot bypassed the ADC input voltage will be 1.89Vdc. Fairly close to its full scale voltage of 2.048Vdc. However, the pressure sensor I'm using to measure level will output almost 5Vdc at full scale. In this instance the pot can be used to add resistance to limit the voltage such that it doesn't go about 2.048Vdc but also maintain an ADC input voltage of 2.048Vdc at the sensor's full scale output.

    Read more »

  • August 12, 2017

    Skyler Brandt08/12/2017 at 22:31 0 comments

    In the past 10 months since my last update I've been working on a lot of things other than the AquaPic, but I've found some time off and on to mostly cleanup parts of the project. Both the main controller and card firmware can now be built without the need for a graphical IDE. I added the GPLv3 license to both the repositories, and fixed the README's as well. I also cleaned up quite a few things in the code as well. I refactored the water level module in the main controller to include water level groups similar to the temperature groups. This allows for multiple water levels to be monitored with less headache and hopefully a little more future proof. I refactored the way the AquaPicBus slave driver works. In not so many words, I abstracted the way responses are constructed. I also abstracted a few of the other functions/modules in the card firmware, such as the ADC, pins, and PWM. Lastly, back in July of last year I thought I bricked my RPi. However, it turned out that building the Linux kernel from source replaces the config file that resides in the root directory that tells the RPi how to start some modules such as the HDMI output. I don't know how I came to that conclusion but at some point last year I ended up fixing that as well. 

  • October 29, 2016

    Skyler Brandt10/29/2016 at 23:07 0 comments

    This project has been on the back burner for the last few months, and for a while there the pot was completely off the stove. I haven't shelved the project though, and I've made some progress. I finalized the layout for the analog input and digital input cards. I finished the layout for the pH and ORP card, and I redesigned the power strip for the third time. I was trying to replace the Chinese SSR with my own triac design, but come to the realization that it was going to be a lot of testing and work that I didn't have time for. I did however combine everything onto one PCB. I fit everything onto a 10cm by 10cm board but I had to go to 4 layers to do so. The ph/ORP card also ended up being 4 layer. Lastly, I started to work on data logging for the main controller and finally have line graphs on the home screen on the controller.

  • July 22, 2016

    Skyler Brandt07/23/2016 at 03:03 0 comments

    I title this picture "So Much Winning!" It has been a reoccurring theme with this build that my RS485 protocol is not functioning as intended but I'm optimistic that a small but somewhat large change will finally end my troubles. There is no longer a 9th bit used for address detection, and thus no longer a need to deal with parity. Backing up a bit, its been a rough road to this point. Just as before, when I thought that I had the communication figured out, my computer goes and updates itself, or I find out that building modules for the Raspberry Pi is an excellent way to make oneself mentally unstable. Back in March I patched the driver for the CH341 driver and had 9bit RS485 communication working on my main computer. However, since then, the CH341 driver has been updated back to kernel default with no parity support. Awesome. Why those few lines of code aren't part of the default driver is kind of dumb in my opinion. However, that wasn't the final nail in parity's coffin and I could re-patch the driver. It isn't difficult, its just a lot of waiting for things to download, and irritation.

    My tipping point was attempting to build the CH341 driver for the Raspberry Pi. That was unnecessarily frustrating. I don't know everything I tried, but my bash history is full of thousands of commands I issued in an effort to get one little patch added to a driver. Most of my issues stemmed from the fact that the any compiled CH341 module I built didn't match the version of the Linux kernel itself or some other dependent module. If you don't already know, the header files for the Raspbian kernel are proprietary so there are multiple irritating hoops to jump through to get the exact right branch from Raspberry Pi's Github cloned. There are several forum threads and instructions online that detail ways to get it to work and I tried every one I could find. One person said he had to complete rebuild the entire kernel any time he wanted to update or add a module. That is completely asinine but in a last ditch effort I tried it, and brick the RPi. It still boots but its not right. That was the point when I decided that that 9th bit just wasn't going to happen.

    So the question became, what do I do to initiate an address detection. A common RS485 protocol is Modbus, and has two operating modes, ASCII and RTU. ASCII operates just like it sounds and sends ASCII characters. The message is basically a string. 192 would be 0x31, 0x39, 0x32. That mode is initiated, or framed, by sending a semicolon character, ':', to start and a CRLF to end. Simple ASCII characters. The issue with ASCII is that the number of bytes sent in a message can be high, so RTU is used instead. RTU sends the binary representation of message. 192 would be 0xC0. However, start and end framing can't be initiated by a character or number because that number can and will be in the regular message. RTU instead frames its messages with a 3.5 character delay. AquaPic Bus also uses binary messages, so I decided to try framing with a time delay. I thought it was going to be difficult to implement, but it turned out to be fairly easy. Most of the changes happened in the slave code. It has a simple counter that counts up every millisecond but is reset once a byte is received, so that when the serial lines are quiet for at least 10 milliseconds the slave modules start waiting for a address. The next byte is assumed to be the address and if it matches, it handles the message. If it doesn't, it waits for the next delay. The master code was even easier. I simple commented out a few lines and added a 12 millisecond delay before I write out a message. Once I made those few changes, I downloaded the code to the two slave modules, hit debug on the master program, flipped to the AquaPic Bus status window and was met with the glorious sight of "Read/Write was successful". Queue the happy dance. Woo Woo.

  • March 19, 2016

    Skyler Brandt03/19/2016 at 22:07 0 comments

    Progress has again been waylaid by life. My wife and I just moved to our new place and everything is in boxes. Probably will be in boxes for awhile longer. Moving sucks. A huge positive to our new house is that it has a 300 gallon aquarium built in in the basement. The tank is healthy, but it does need some work. There is a lot of coraline algae growth on the viewing glass. There is also not enough flow, too much sand, too small of skimmer, and the list goes on. The pictures suck, but in my defense, tanks are fairly difficult to photograph. Especially with a phone. Currently I'm running two different kinds of lights; 10K T5 florescents on the right and two LED features on the left. That's why the colors are a little off from side to side.

    And a close up on left side with all coral.

    I've been making good use of my free time though by making the user interface pretty.

    New Home Screen. This is a work in progress. I'm not sure about the placement of "widgets", or the overall look.

    New Side Menu:

    New Power Screen:

    And New Lighting Screen:

    Just a few screens I've been changing. Everything else is just boring or looks similar to the video I made a few months ago. I can't wait to get everything unpacked so I can get back to finishing this project. The good new is that I now have a fish room, so mounting everything is so much easier.

  • March 1, 2016

    Skyler Brandt03/01/2016 at 23:11 0 comments

    If I’d known what was all involved with 9bit communication, I probably would have chosen a different option. However, despite all the bugs, issues, and lack of support, I finally have a fairly reliable communication protocol. After my last post I realized that I could test my hypothesis that it was the USB hub on the RPi. I could simply unplugged the WiFi dongle, and see if the problem persistent. As expected, there was no improvement. The USB hub wasn’t my issue. I don’t remember exactly what all I did after that but there was a lot of beating my head against my desk. At some point I realized I could reproduce communication faults on my Windows PC when I was using the parity hack, counting the bits in each byte and setting either even or odd parity to get the desired mark or space parity. I also found that the power strip slave was hanging, and restarting it would get it working for a while. I concluded that both the parity hack and the slave code were causing issues and set out to address both.

    I expected that fixing the hanging slave module would be an easy fix, and for the most part it was except for one small detail I overlooked. To address the issue I added a simple check of the 9th bit as part of the received byte handler to double check that it wasn’t an address byte. However, I introduced one seemly small bug when implementing that. When I first made the change I was reading the 9th bit after I got the received byte off the microcontroller’s internal FIFO buffer. This is a big oversight as the microcontroller updates the status word containing the state of the 9th bit as the FIFO buffer is read, so when I checked the 9th bit it was actually for the next byte. That took me a while to find. Also, since I’m checking every byte now for the 9th bit, I removed the auto-address detection.

    Fixing the parity hack was a chore. I decided that I needed to get mark and space parity to work in the Mono framework. The reason mark and space parity aren’t currently implemented in the Mono framework is because it requires the CMSPAR flag to be defined, which it isn’t with the POSIX source. Since I didn’t want to recompile the MonoPosixHelper library, I set out writing my own helper library that could be used to set the parity. It was actually pretty easy to write, pretty much copy and paste. I used a lot of Mono’s existing C code and a blog post by Thomas Lochmatter to modified a few lines to include the CMSPAR, mark/space parity, flag. Code after break.

    I built the parity library and using Mono’s documentation for PInvoking native libraries, tried to get it working on my Linux machine, but was only met with DllNotFoundExpection. I had pulled a rooky mistake and compiled the source as an object instead of a shared library. I also didn’t realize that you have to include a lib prefix to the library file name in order for the Mono framework to find that it. Running the application with the log level set to debug help find both these issues pretty quick.

    $ MONO_LOG_LEVEL="debug" mono AquaPic.exe
    I also find out some other useful information. I had gone through all the steps that the docs lists to add a shared library so that dlopen() can find, but that isn’t required. The first place that the Mono framework searches is the application launch directory. Simply add the library in the same directory as the application .exe. After I fixed the lib prefix and built the source as a shared library, Mono was able to use my parity library, but it still didn’t work

    At this point I was completely lost and went back to my Windows machine and using the parity hack. I wanted to try to get the parity hack to work, since it still wasn’t working under either Linux or Windows. Backing up a little bit, when I was first looking though the serial C code for Mono, I noticed that the termios flags are set immediately instead of waiting for the write buffer to be empty. Based on that code, I made the assumption that Windows closed magic probably also set the parity immediately,...

    Read more »

  • February 6, 2016

    Skyler Brandt02/06/2016 at 10:31 0 comments

    The following update post was the ramblings of an extremely frustrated, under-equipped, and marginally trained hobbyist, and probably completely off base. Please disregard anything I said. I'm fairly certain I missed diagnosed the issue and am hopefully headed in the right direction now. Those words haven't been muttered before. Update to follow soon.

    Read more »

  • January 30, 2016

    Skyler Brandt01/30/2016 at 22:58 0 comments

    The little boost DC-DC converter fixed all my low voltage issues for the RPi and touchscreen. I was surprised that was such an easy fix. Getting a MOSFET paralleled with the backlight controls on the touchscreen was not and turned out to be a bit more involved than I expected. Most of the difficulty was self induced though. The best solution I could come up with for bodging a SMD SOT-23 was three pads on a SOIC breakout board. It was ugly but it probably would have worked. However, problems arose when I referenced the MOSFET and driver circuit off neutral of the power supply I would normally use but then powered the RPi from a different power source. I didn't catch my mistake at first, and instead concluded that my bastardized SMD work was the culprit. So I ordered a proper through-hole FET and once again it didn't work. That was when I finally realized my mistake, switched my power to the correctly referenced source, and everything worked as expected. Now when the screensaver come on, the backlight turns off and then when I touch the screen the screensaver goes off and the backlight switches on. I designed the gate circuit a little different than most other p-channel gate control. The gate is normally connected to ground though a resistor. Then a transistor is placed on the high side to pull the gate up and shut the MOSFET off. That way while the RPi is booting or something happens to the script that controls the GPIO, the backlight will fail on.

  • December 30, 2015

    Skyler Brandt12/31/2015 at 01:31 0 comments

    Finals and the Christmas season have not been conducive to progress. I really need two solid days to rearrange and organize the underside of my tank to make room for the controller. Regardless, I have managed to get a little bit done in my free time. I laid out the analog and digital input boards. They are both really simple, and I'll send them out to get made after I get what I have done now up and running. I also wrote all the firmware for both cards. The code was extremely simple as well since most of it was copy and pasted from the other bits I've already finished. I haven't decided whether I'm going breadboard the cards and test the code that way, or just wait until I have PCB's and test then. The age old struggle between the right way and laziness.

    The Raspberry Pi supply voltage is a little on the low side because of all the shotty connections between the power supply and the Raspberry Pi, so I order a little boost power puck thing to get the voltage back up to 5Vdc. I also wanted a way to automatically turn off the backlight when the screensaver came on, so I started re-purposed ramses0's xscreensaver-pi-hdmi script to control a GPIO pin to fire a MOSFET. The MOSFET will be paralleled with a switch that is already on the back of the touchscreen but is normally inaccessible. I still need to find a p-channel MOSFET to get this to work. I have quite a few SMD p-channel but no though-hole, so I've yet to find a good way, haven't investivataged all that hard yet though, to bodge one onto some protoboard or another sort of prototyping option.

  • November 25, 2015

    Skyler Brandt11/25/2015 at 23:53 0 comments

    I'm 95% done with my first prototype. Everything is assembled, and mostly working. I had quite a few issues and bugs to get to this point though. As I highlighted in my post two days ago, my RS485 protocol didn't work on the Raspberry Pi because mark and space parity isn't implemented under the Mono frame work. I took the easy way out and simply counting the number of high bits in the byte. Then I set the parity to either odd or even to get the desired 9th bit.

    The next issue was all because I sometimes buy cheap "quality" Chinese parts. The original power supply I ordered to power the controller didn't work like I wanted it to. It was a dual supply, +5Vdc and +12Vdc, but for some reason I couldn't tie the commons together. I didn't look into, or care why, and instead ordered two separate power supplies. Another nail in the coffin of the original power supply was the switching frequency. It's in audible range, probably around 10ish KHz, and extremely annoying.

    The power strip went together fairly easy. I don't really like the case but it works. Its made out of ABS plastic sheets. I cut all the pieces out, then bent over tabs on the edges to make a surface to glue everything together.

    I then used pipe cement to glue everything together. The top and bottom are held on by 6-32 machine screws. I threaded the plastic so I didn't have to use any nuts. It's definitely not elegant, but I don't cool machines to do anything prettier, like a CNC machine or a lazer cutter. And no, a home hobby 3D printer is not cool.

    Before I wired all the outlets up to mains, I wired up the control circuity and tested that. I had to parallel 10K resisters between the relay control and ground pins of every relay board, because I left the gate of the MOSFET floating. The terminal strips are really close together so I layered the resisters between pieces of electrical tape to keep anything from shorting together. Added bonus though, it looks even more cobbled together.

    I still need to figure out how to show the status, power, and AC power available (currently not wired up) LEDs on the outside of the enclosure. This will also probably require relocating the LEDs to somewhere else on the control board or to a separate LED board. Also, the power LED is soldered on backwards. Those are really hard to tell which end is the cathode or anode. Once all the control stuff was working. I moved onto the mains wiring.

    When I first built the enclosure I thought I made it a little too tall, but it proved to the perfect size after I shoved all the wires in there and screwed it together. And with it plugged into mains and hooked up to the controller, everything worked like it was supposed to.

    The dimming card gave me a little more trouble than the power strip. First, I complete screwed up the gain feedback for the filter opamp. The dimming card is supposed to take a 0-5Vdc PWM signal and turning it into 0-10Vdc constant voltage. However, I was only getting a filtered 0-5Vdc. Luckily, I found the issue fairly quickly. It's a pretty good indicator I did something wrong when the voltage on the inverting pin is the same as the output pin.

    I was able to fix the problem witj the existing board I already had, but it required cutting traces and soldering bodge wires. After I had that fixed though I still couldn't get the output to go all the way to 10Vdc. Whenever I went above ~55% duty cycle the output would automatically clamped to ~7Vdc and stayed there until around 35% duty cycle. This issue turned out to be the little switch IC I was using. My original plan was to have either 0-10Vdc or PWM output, and this switch would be used to software select between the two. However, this plan had several plan in it. First, the switch I was using is only rated for 5Vdc and its normally-opened pin is tied to the output of the opamp. So anytime the output of the opamp went above ~5.5Vdc, weird things happen inside that switch. Hence the weird voltage outputs. Second issue I ran into was that when... Read more »

View all 26 project logs

Enjoy this project?



seilerjacinda925 wrote 11/19/2019 at 16:23 point

Nice to meet you after viewing your profile i am Jacinda, from (jakarta) indonesia,

i have a project discussion with you please email me on: (

  Are you sure? yes | no

patchartrand wrote 08/12/2019 at 13:20 point

Absolutly gorgous user interface! Very clean work, your workmaship is definely spot on.

  Are you sure? yes | no

jez-rsv wrote 07/24/2017 at 22:01 point

Great Project, As a fish keeper and designer i have found your project very interesting, great work

  Are you sure? yes | no

Skyler Brandt wrote 08/13/2017 at 17:32 point

Thank you

  Are you sure? yes | no

FrankenPC wrote 08/04/2014 at 22:03 point
This looks great! I just finished an aquarium controller. It's nowhere near this nice. I wish I had some previous designs to work from! Mines based on a arduino mega. I took a cheap 8 channel 110V DJ power box and rigged up I2C relay control. To talk to everything including the sensors, I built a RJ45 form factor bus for power + serial + I2C + 1 wire all ganged together. Made it a lot easier to daisy chain control modules.

  Are you sure? yes | no

Adam Fabio wrote 07/14/2014 at 03:12 point
Great project Skyler! Thanks for entering The Hackaday Prize! I'd love to see some pictures of your controller and the aquariums it controls! Keeping a salt water system going can be a big job! Don't forget to give us a video for the first deadline coming up (just 22 days!)

  Are you sure? yes | no

Kelli Brandt wrote 07/08/2014 at 01:20 point
Fantastic project. I agree about the pictures. You must be smart, and handsome, but primarily intelligent.

  Are you sure? yes | no

dltruchon wrote 07/01/2014 at 22:39 point
Cool aquarium project- I would put some pictures and some of your notes/descriptions in here (rather than just the link) to grab more attention for the contest....

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates