Raspberry Pi Pool Monitor – Python Code

by | Aug 30, 2022 | 2 comments

HydroPi Pool Monitor Code

 

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 Raspian 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
  4. PHPMyAdmin (optional but recommended)

 

The Code

 

The program is available in both 2.x and 3.x in the MyHydropi Github Repository in the folder pool-monitor.

To get the pool monitor up and going there are several places where you need to change values to allow for your own specific setup. Once done the program 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 settings, reboot and the database will be updated.

Before running the program you will need to configure some details that will be specific to your setup. The first thing that you will need to configure is your login to the MySQL database, you need to provide your username (at setup this is root), your password to the database and the name you have chosen for your database (“hydropi” is the default).

 

# Define MySQL database login settings
servername = "localhost"
username = "YourMysqlUsername"
password = "YourMysqlPassword"
dbname = "hydropi"

 

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 setup 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 setup 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,
                            "ds18b20_file":
                            "/sys/bus/w1/devices/28-xxxxxxxxxxxx/w1_slave",
                            "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 same for each.
  • name” – user defined sensor name assigned to 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 dependant, the reference temperature sensor should be the one you have in the liquid that your 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 a parts per million value.

 

Note: through a bit of research 0.67 seems to be recommended but you may have adjust this to suit your own pool through trial and error.  It should also be noted that the EC sensor is setup 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,
     "ds18b20_file":
     "/sys/bus/w1/devices/28-yyyyyyyyyyyy/w1_slave",
     "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 = {"pool_size": 27000,  # Pool volume in litres
                "offset_percent": 2,
                "pause_readings": False,
                "read_sensor_delay": 300,  # 60x5 = 5 Minutes
                "pause_reset_delay": 1800,  # 60x30 = 30 Minutes
                "email_reset_delay": 172800,  # 60x60x24x2 = 2 Days
                "to_email": "ToEmail@Address.com"}

 

  • pool_size” – not used by the program itself but rather by the associated webpage to calculate chemical dosing amounts.
  • 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” – delays 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)
  • read_sensor_delay” – delay (in seconds) between sampling all the sensors.
  • 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.
  • to_email” – sets the email address to send the alerts to.

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.

Finally for the email alert to work correctly you need to enter you own relevant details.  If you need more information on how to setup and send an email with your Pi then you can find instructions here.

 

    # Build email and send
    fromaddr = "FromEmail@gmail.com"
    toaddr = all_settings["to_email"]
    alladdr = toaddr.split(",")
    msg = MIMEMultipart()
    msg['From'] = fromaddr
    msg['To'] = toaddr
    msg['Subject'] = "Pool Alert"
    body = ("HinnThis is your PoolnnYou should know that the following "
    "sensor(s) are indicating that there is a problem that needs your "
    "attentionn{}nPlease check this by logging intonnwww.yourwebsite.com"
    "nn RegardsnnYour HydroPi").format(out_of_limit_sensors.upper())
    msg.attach(MIMEText(body, 'plain'))
    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    server.login(fromaddr, "YourFromEmailPassword")
    text = msg.as_string()
    server.sendmail(fromaddr, alladdr, text)
    server.quit()
    return

 

In the above code you will need to enter your own details into the following lines

  • fromaddr = “FromEmail@gmail.com”
  • server.login(fromaddr, “YourFromEmailPassword”)

Once everything has been configured for your own personal setup the first time you run the program it should 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 some default data and then continue to run indefinitely.  The main program loop below basically just checks the relay settings every second and does all the timing and testing of conditions related to the other delays that have been configured and then calls the required functions.

 

#################
#               #
# Main Program  #
#               #
#################
# Sanity Checks
check_number_of_relays_equals_start_stop_pairs()
check_for_only_one_reference_temperature()
# Configure relay GPIO ports
set_GPIO_pins()
# Build/Remove MySQL Database Entries
create_database()
relay_timer_names = create_relay_tables()
create_timer_override_table()
create_sensors_table()
create_settings_table()
remove_excess_timer_override_and_relay_database_entries()
remove_excess_datetime_pairs()
remove_unused_sensors()
remove_unused_sensors_settings()
while True:  # Repeat the code indefinitely
    # Control the relays
    activate_deactivate_relays()
    sleep(1)
    # 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 loops == time_between_readings:
        loops = 0
        # Read delay values from settings table
        delays = get_settings_table_values()
        time_between_readings = delays["read_sensor_delay"]
        email_reset_loop = (delays["email_reset_delay"] //
                                    time_between_readings)
        pause_reset_loop = (delays["pause_reset_delay"] //
                                    time_between_readings)
        if delays["pause_readings"] == 0:
            alert_readings = read_sensors()
            if alert_check is True and email_sent is True:
                email_sent = reset_email_sent_flag_if_alerts_clear(email_sent)
                if email_sent is False:
                    alert_check is False
                    email_sent_reset = 0
            elif alert_check is False:
                alert_check = check_sensor_alert_limits(alert_check)
                if alert_check is True:
                    email_sent = send_email(alert_readings)
                    email_sent = True
                    email_sent_reset = 0
        elif delays["pause_readings"] == 1:
            pause_loops += 1
            if pause_loops == pause_reset_loop:
                reset_pause_readings()
                pause_loops = 0
        if email_sent is True:
            email_sent_reset += 1
            if email_sent_reset == email_reset_loop:
                alert_check = False
                email_sent_reset = 0
    loops += 1

 

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 program to just provide the basics for each.  All the python code is available in both 2.x and 3.x on the MyHydropi Github Repository in the folder pool-monitor.

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.

2 Comments

  1. Jeff

    Hello Dominic,
    I am using 3 DS18B20 Temperature Sensors, 1 for pool temp, 1 for air temp and 1 for heater temp. I created temp_2 and temp_3 in the sensors variable, but it only reads the temp_1 sensor and puts that value in all 3 rows in the db? How do I modify to read the temp_2 and temp_3 sensors. I gave them all unique names.

    Thanks,
    Jeff

    Reply
    • Dominic

      Hi Jeff,
      It’s not you it’s me, Looks like I still had the function – read_1_wire_temp_raw() hard coded with “temp_1” from the sensors dictionary, try changing the following lines and that should pass the right temp sensor name variable through, let me know if this helps.

      Line 468 – def read_1_wire_temp_raw(temp_num):
      Line 470 – f = open(sensors[temp_num][“ds18b20_file”], ‘r’)
      Line 479 – def read_1_wire_temp(temp_num):
      Line 481 – lines = read_1_wire_temp_raw(temp_num)
      Line 485 – lines = read_1_wire_temp_raw(temp_num)
      Line 534 – sensor_reading = (round(float(read_1_wire_temp(key)),

      Dom

Submit a Comment

Your email address will not be published. Required fields are marked *

 

Pin It on Pinterest

Share This