Raspberry Pi Pool Monitor – Python Code

by | Aug 30, 2016

This Raspberry Pi pool monitor code will allow you to manage multiple sensors and relays and send email alerts among other things. This program pulls together several other posts that I have written, if you are curious then I suggest you look at this overview of the project.

The program relies on several prerequisites to have been previously configured on your Pi. I am assuming that you are running the latest version of Raspbian and have the ability to connect to your Pi either through SSH with putty and FTP with Filezilla, or directly with a keyboard and monitor if you haven’t set-up your Pi yet then check out my getting started section.

The program requires that you have the following installed

  1.  At least one connected sensor
  2. At least one relay
  3. MySQL (MariaDB)
  4. PHPMyAdmin (optional but recommended)


The Code


This whole program is available in the MyHydropi Github Repository in the folder pool-monitor.

The program is broken up into the following modules to make it easier to understand and then imported into the main program

  • hydropi_main.py
  • hydropi_variables.py
  • database_manager.py
  • email_manager.py
  • power_manager.py
  • sensor_manager.py

It should be noted that all of these files need to be located in the same folder on your Pi.

To get the pool monitor up and going you will need to change several of the values in the “hydropi_variables.py” file to allow for your own specific setup. Once configured, provided you have MariaDB installed the program on start-up will create the MySQL database automatically for you. If you add or remove sensors, relays, or change the number of timer pairs at a later date you can just change the variables, reboot and the database will be updated.

After opening “hydropi_variables.py” the first thing that you will need to configure is your login to the MySQL database, you need to provide your username, your password to the database and the name you have chosen for your database (“hydropidb” is the default).


# Define MySQL database login settings

servername = "localhost"
username = "YourMysqlUsername"
password = "YourMysqlPassword"
dbname = "hydropidb"


The next variables that you will need to configure are your email settings, here you will need to set the email address that you will be sending the alerts from, your email login password, the email server that you will be using and it’s port (as a default it is set to Gmail). Finally, enter the path to the HTML and text files that will be used to fill the body of the email (eg. /home/pi/hydropi/body.txt). I have included templates for both of these files on GitHub. 

If you need more information on how to set up and send an email with your Pi then you can find instructions here.


# Define Email Server login settings

fromaddress = "yourFromEmailAddress"
emailpassword = "yourFromEmailPassword"
emailserver = 'smtp.gmail.com'
emailserverport = 587
textfile = "path/ToEmail/Textfile.txt"
htmlfile = "path/ToEmail/Htmlfile.html"


Next, we will configure the relays, here we will define the GPIO ports that the relays are connected to and the number of date-time pairs that we are going to be using for each relay.

More information on how I have set up date-time pairs for timing can be found here.


# Define Relay Settings

outputpins = [22, 23, 24, 25]  # Specifiy a RPi GPIO Pin for each relay
numdtpairs = [4, 3, 2, 4]  # Number of Start/Stop pairs for each relay
relaycount = range(1, (len(outputpins) + 1))

The “outputpins” variable lists the GPIO pins on the Pi that you have connected to your relays, for pin numbering I am using the following convention

Raspberry Pi GPIO pin number layout

The “numdtpairs” variable sets the number of start/stop times for each individual relay, in theory, you could set multiple start/stop time for every day of the year but there is no real limit. Finally, the “relaycount” variable will count the number of “outputpins” you have added and assume that this is the number of relays connected.

Each relay must have a value entered in “numdtpairs” even if it is just a 0, when you run the program it will check to see that you have done this and provide a warning if something goes wrong. At startup, all the relays will be set to off and the date-time pairs will be empty, these will need to be added via the MySQL command line or PHPMyAdmin. I have also created some webpages that will allow you to see and update these values easily through your browser.


Note: If you increase the number of relays or date-time pairs and then restart, the program will add to the tables in the database but will not remove any of the existing values.  If you decrease the number of relays or date-time pairs then any previous values in the associated table/column will be removed.


The program is written to use DB18B20 temperature sensors and various Atlas Scientific sensors, in order to configure each sensor for your specific set up the following code needs to be edited.


# Configuration Settings

sensors = OrderedDict([("temp_1", {  # DS18B20 Temperature Sensor
                            "sensor_type": "1_wire_temp",
                            "name": "ds18b20_temp",
                            "is_connected": True,
                            "is_ref": False,
                            "accuracy": 1,
                            "test_for_alert": False,
                            "upper_alert_name": "ds18b20_temp_hi",
                            "upper_alert_value": 50,
                            "lower_alert_name": "ds18b20_temp_low",
                            "lower_alert_value": 10}),

                       ("atlas_sensor_1", {  # Atlas Scientific Temp Sensor
                            "sensor_type": "atlas_scientific_temp",
                            "name": "atlas_temp",
                            "is_connected": True,
                            "is_ref": True,
                            "i2c": 102,
                            "accuracy": 1,
                            "test_for_alert": False,
                            "upper_alert_name": "atlas_temp_hi",
                            "upper_alert_value": 40,
                            "lower_alert_name": "atlas_temp_low",
                            "lower_alert_value": 25}),

                       ("atlas_sensor_3", {  # Atlas Scientific EC Sensor
                            "sensor_type": "atlas_scientific_ec",
                            "name": "ec",
                            "is_connected": True,
                            "is_ref": False,
                            "i2c": 100,
                            "accuracy": 0,
                            "ppm_multiplier": 0.67,  # Convert EC to PPM
                            "test_for_alert": True,
                            "upper_alert_name": "ec_hi",
                            "upper_alert_value": 6000,
                            "lower_alert_name": "ec_low",
                            "lower_alert_value": 4500}),

                       ("atlas_sensor_4", {  # pH/ORP Atlas Scientific Sensor
                            "sensor_type": "atlas_scientific",
                            "name": "ph",
                            "is_connected": True,
                            "is_ref": False,
                            "i2c": 99,
                            "accuracy": 2,
                            "test_for_alert": True,
                            "upper_alert_name": "ph_hi",
                            "upper_alert_value": 7.4,
                            "lower_alert_name": "ph_low",
                            "lower_alert_value": 7}),

                       ("atlas_sensor_5", {  # pH/ORP Atlas Scientific Sensor
                            "sensor_type": "atlas_scientific",
                            "name": "orp",
                            "is_connected": True,
                            "is_ref": False,
                            "i2c": 98,
                            "accuracy": 0,
                            "test_for_alert": True,
                            "upper_alert_name": "orp_hi",
                            "upper_alert_value": 700,
                            "lower_alert_name": "orp_low",
                            "lower_alert_value": 550})])

This code describes each type of sensor that you can attach, while there are common elements each is slightly different, the following are common elements to each

  • sensor_type” – the program treats each sensor type slightly differently so this should not be changed if you have multiple sensors of the same type this setting should be the same for each.
  • name” – user-defined sensor name assigned to the database, if you have more than one of the same sensor then different names are required.
  • is_connected” – sets if the sensor is in use, if set to false then that sensor will be ignored allowing you to remove a sensor without removing all the code.
  • is_ref” – to read accurately some of the sensors are temperature dependent, the reference temperature sensor should be the one you have in the liquid that you are monitoring if no reference is set then 25C will be used.  Only one reference temperature sensor may be set, on startup, the program will check this is true and give you a warning if it is incorrect. All other sensor types should be set to false.
  • accuracy” – This sets how many decimal points of accuracy are recorded when reading the sensors.
  • test_for_alert” – this sets whether an email is sent based on the alert values for this sensor, if set to false then no email will be sent if the alert values are exceeded.
  • upper_alert_name” – database column name for the upper alert value.
  • upper_alert_value” – upper limit value for that sensor, will trigger an email alert if exceeded.
  • lower_alert_name” – database column name for the lower alert value.
  • lower_alert_value” – lower limit value for that sensor, will trigger an email alert if exceeded.


Atlas Scientific Sensor specific:

  • i2c” – Defines the specific i2c address for each of the Atlas Scientific sensors.


DB18B20 specific:

  • ds18b20_file” – defines the location of the data received from the ds18b20 temperature sensor.


Electrical Conductivity specific:

  • ppm_multiplier” – used to convert the electrical conductivity reading to parts per million.


Note: through a bit of research 0.67 seems to be recommended but you may have to adjust this to suit your own pool through trial and error.  It should also be noted that the EC sensor is set up to only output the electrical conductivity reading and not the various other possible values.  Information on setting the outputs of the sensor are available here.


If you want to add more than one of a specific sensor type then you simply need to make a copy of the existing sensor setting and add it into the “sensor” variable above and give it a new “name” variable.

eg. adding the following to the “sensors” variable would create a 2nd temperature sensor in the database


("temp_2", {  # DS18B20 Temperature Sensor
     "sensor_type": "1_wire_temp",
     "name": "ds18b20_temp_2",
     "is_connected": True,
     "is_ref": False,
     "accuracy": 1,
     "test_for_alert": False,
     "upper_alert_name": "ds18b20_temp_hi_2",
     "upper_alert_value": 100,
     "lower_alert_name": "ds18b20_temp_low_2",
     "lower_alert_value": 75}),

There are also some miscellaneous settings


# Define other settings

misc_setting = {"offset_percent": 2,  # Stop toggling when close to alert value
                "pause_readings": False, # Initially set to False on startup
                "email_reset_delay": 172800,  # in seconds 60x60x24x2 = 2 Days
                "read_sensor_delay": 300,  # in seconds 60x5 = 5 Minutes
                "pause_reset_delay": 1800,  # in seconds 60x30 = 30 Minutes
                "to_email": "yourToEmailAddress", # destination email address
                "pool_size" : 27000} # pool volume in litres


  • offset_percent” – to prevent the alert email being sent multiple times when a sensor is reading near a limit this sets a slightly higher or lower reset limit.
  • pause_readings” – delay sensor readings for a set amount of time (in seconds) while adding chemicals to prevent spikes in the readings. (helpful when displaying the readings in a graph)
  • email_reset_delay” – sets the delay (in seconds) between reminder emails when the sensors are out of limits and corrective action has not been taken.
  • read_sensor_delay” – delay (in seconds) between sampling all the sensors.
  • pause_reset_delay” – sets the pause time (in seconds) when pausing sensor readings, used with the website to prevent erroneous readings when you are dosing the pool with chemicals.
  • to_email” – sets the email address to send the alerts to.
  • pool_size” – not used by the program itself but rather by the associated webpage to calculate chemical dosing amounts.

All of the above settings are written to the database so that once the program is up and running changes can be made manually and the program will operate with the new values without the need to stop and restart the software.

Once everything has been configured for your own personal set up, run the program, it will perform some basic checks to ensure that the data entered is suitable and then proceed to create the MySQL database, tables, rows, and columns, input the initial variable data and then continue to run indefinitely. 


#               #
# Main Program  #
#               #

# Start Logging

logging.basicConfig(filename = '/Path/To/Log/Folder/hydropi_error.log',
        format = '%(asctime)s -  %(levelname)s - %(message)s',
        datefmt = '%d/%m/%Y %I:%M:%S %p', 
        level = logging.INFO)

# Sanity Checks


# Configure relay GPIO ports


# Build/Remove MySQL Database Entries


    while True:  # Repeat the code indefinitely

        # Control the relays



        # Check if sensor readings have been paused, if not then read and store
        # sensor values and check against alert values, send an email if required

        if datetime.datetime.now() >= (var.sensor_ref_time + 
            var.sensor_ref_time = datetime.datetime.now()

            # Read delay values from settings table

            delays = dbman.get_settings_table_values()
            var.time_between_readings = delays["read_sensor_delay"]
            email_time_delay = delays["email_reset_delay"]
            pause_time = delays["pause_reset_delay"]

            if delays["pause_readings"] == 0:
                alert_readings = sensors.read_sensors()

                if var.alert_check is True and var.email_sent is True:
                    var.email_sent = emman.reset_email_sent_flag_if_alerts_clear(alert_readings, var.email_sent)
                    if var.email_sent is False:
                        var.alert_check is False
                        email_sent_time = datetime.datetime.now()

                elif var.alert_check is False:
                    var.alert_check = emman.check_sensor_alert_limits(alert_readings, var.alert_check)
                    if var.alert_check is True:
                        all_settings = dbman.get_settings_table_values()
                        emman.send_email(alert_readings, all_settings)
                        logging.info("Email has been sent")
                        var.email_sent = True
                        email_sent_time = datetime.datetime.now()

            elif delays["pause_readings"] == 1:
                if var.new_pause is True:
                    logging.info("Readings have been paused")
                    pause_start_time = datetime.datetime.now()
                    var.new_pause = False
                if datetime.datetime.now() >= (pause_start_time + 
                    var.new_pause = True
                    logging.info("Reading sensors again")

            if var.email_sent is True:
                if datetime.datetime.now() >= (email_sent_time + 
                    var.alert_check = False
                    email_sent_time = datetime.datetime.now()

except KeyboardInterrupt:
    # catches the ctrl-c command, breaks the loop above 
    # and turns the relays off


The program is designed to continue to operate in the event of minor errors such as a failed sensor reading, in order to be able to track these errors and fault find any issues they will be logged and stored in an automatically created file called “hydropi_error.log”, you need to set the file path in line 9 above. The error log is handled by the built-in python “logging” module.

Finally, in order to run the program automatically at startup you will need to configure a cronjob, from the terminal enter the following command:


sudo crontab -e


This will open the root users crontab file ready for editing, scroll to the bottom of the file and add the following line using your own file path to where you have saved the “hydropi_main.py” program (eg.  /home/pi/hydropi_main.py).


@reboot python3 /Path/To/hydropi_main.py


This tells Cron that on every start-up we want to run “hydropi_main.py”.

Once updated select Ctrl + X, then Y, and finally Enter to save the changes, then perform a reboot.


sudo reboot


Once the Pi has restarted confirm that the program is running using the following command, again using the file path to the folder that you have saved “hydropi_main.py” in.  


ps aux | grep /Path/to/hydropi_main.py

This should give you a line starting with “root” and ending in the path to your script. Immediately after the “root” should be a process number. If you want to stop the program use the following command and enter the process number that was produced by the previous command.

sudo kill 1234


While I wrote this program as a solution to my own needs I have tried to make it as versatile as possible so that if you just want to try it out you only need a single relay and sensor to get it going. However, if you are looking for something to just control relays or just read sensors then I have also broken down the software to just provide the basics for each in a single program.

All the python code is available on the MyHydropi Github Repository.

If you have any thought’s about this article, improvements or errors let me know in the comments below and if you found this helpful, why not share it with others.

Pin It on Pinterest

Share This