Pool Monitor

One of the downsides of owning a pool is keeping on top of the water balancing and ensuring chemical levels are right in order to avoid the pool going green or your eyes from stinging due to too much Chlorine or PH level wrong.

Our pool is a Saltwater pool with a Salt Cell and a separate dosing Liquid Chlorine and Acid pump to keep track of things. The downside is those devices are in a shed which I can never be bothered going into in order to check things out.

I decided I wanted to build out a project using a spare raspi that I had. From there I purchased a number of sensors:

I had some spare Cablegaurd CG-1500 plastic housing cases from my Xmas Lights Projects so I was able to use for this project as well.

 

As the house is a wee distance from the device I wanted to ensure I had decent wifi reception so fitted the RasPi with a USB wifi dongle and an externally mounted 16dbi antenna.

To power the device I thought I would repurpose an old garden lights 12v AC transformer. This comes with a few problems, firstly its AC and I need DC and secondly it’s 12v not 5v required for the Pi.

To solve this I used a Bridge rectifier to get it to DC however this only gets the polarity right, but due to the sine wave of AC I needed to through a Capacitor over the top of it to try and normalize the signal. From there I used a UBEC used in radio control to drop down to 5v and was capabile of handling the now higher than 12v input voltage.

As we are using the ds18b20 we need to enable 1-wire on the GPIO. This can be done by editing the following.
sudo nano /boot/config.txt

then add this line to the bottom of the file and save
dtoverlay=w1-gpio

Next load the following modules and then reboot
sudo modprobe w1-gpio
sudo modprobe w1-therm

 

 

From there I needed to write a script that would calibrate some of the devices using the temp reading and then post the values back to Homeseer.

This was done in python and setup to run on boot. Items in red would need to modified to suite your installation.

#!/usr/bin/python
############################################
#           Pool monitoring Read data from a DS18B20 sensor and a float sensor Author : Mike Owen Date : 11/12/2016
############################################
import time
import os
import sys
import urllib # URL functions
import urllib2 # URL functions
import io # used to create file streams
import fcntl # used to access I2C parameters like addresses
import time # used for sleep delay and timestamps
import string # helps parse strings
################# Default Constants #################
# These can be changed if required
HOMESEERURL = ‘http://10.1.1.20/
tmp_sensor_name = ’28-031605ec35ff’
base_dir = ‘/sys/bus/w1/devices/
#####################################################
################# ORP Sensor Class #################
class AtlasI2C:
long_timeout = 1.5 # the timeout needed to query readings and calibrations
short_timeout = .5 # timeout for regular commands
default_bus = 1 # the default bus for I2C on the newer Raspberry Pis, certain older boards use bus 0
default_address = 98 # the default address for the sensor
def __init__(self, address=default_address, bus=default_bus):
# open two file streams, one for reading and one for writing the specific I2C channel is selected with bus it
# is usually 1, except for older revisions where its 0 wb and rb indicate binary read and write
self.file_read = io.open(“/dev/i2c-“+str(bus), “rb”, buffering=0)
self.file_write = io.open(“/dev/i2c-“+str(bus), “wb”, buffering=0)
# initializes I2C to either a user specified or default address
self.set_i2c_address(address)
    def set_i2c_address(self, addr):
I2C_SLAVE = 0x703
fcntl.ioctl(self.file_read, I2C_SLAVE, addr)
fcntl.ioctl(self.file_write, I2C_SLAVE, addr)
    def write(self, cmd):
cmd += “\00”
self.file_write.write(cmd)
    def read(self, num_of_bytes=31):
# reads a specified number of bytes from I2C, then parses and displays the result
res = self.file_read.read(num_of_bytes) # read from the board
response = filter(lambda x: x != ‘\x00’, res) # remove the null characters to get the response
if ord(response[0]) == 1: # if the response isn’t an error
# change MSB to 0 for all received characters except the first and get a list of characters
char_list = map(lambda x: chr(ord(x) & ~0x80), list(response[1:]))
# NOTE: having to change the MSB to 0 is a glitch in the raspberry pi, and you shouldn’t have to do this!
return ”.join(char_list) # convert the char list to a string and returns it
else:
return “Error ” + str(ord(response[0]))
    def query(self, string):
# write a command to the board, wait the correct timeout, and read the response
self.write(string)
# the read and calibration commands require a longer timeout
if((string.upper().startswith(“R”)) or
(string.upper().startswith(“CAL”))):
time.sleep(self.long_timeout)
elif string.upper().startswith(“SLEEP”):
return “sleep mode”
else:
time.sleep(self.short_timeout)
return self.read()
def close(self):
self.file_read.close()
self.file_write.close()
#####################################################
# sample url that works including device http://10.1.1.20/JSON?request=controldevicebyvalue&ref=2299&value=21
os.system(‘modprobe w1-gpio’)
os.system(‘modprobe w1-therm’)
def read_temp_raw():
global device_file
f = open(device_file, ‘r’)
lines = f.readlines()
f.close()
return lines
def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != ‘YES’:
time.sleep(0.2)
lines = read_temp_raw()
equals_pos = lines[1].find(‘t=’)
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
temp_f = temp_c * 9.0 / 5.0 + 32.0
#return temp_c, temp_f
return temp_c
device_folder = base_dir + tmp_sensor_name
device_file = device_folder + ‘/w1_slave’
while True:
device = AtlasI2C()
temperature=read_temp()
print(‘Temp: ‘,temperature)
response=urllib2.urlopen(‘http://10.1.1.20/JSON?request=controldevicebyvalue&ref=2299&value=’ + str(temperature))
# print response.info()
html=response.read()
response.close()
sys.stdout.flush()
# send ORP data
device.set_i2c_address(98)
orp=device.query(“R”)
print(‘ORP: ‘,orp)
response=urllib2.urlopen(‘http://10.1.1.20/JSON?request=controldevicebyvalue&ref=2334&value=’ + str(orp))
html=response.read()
response.close()
sys.stdout.flush()
# send TDS
device.set_i2c_address(100)
device.query(“T,” + str(temperature))
ecq=device.query(“R”)
EC=ecq.split(“,”)[0].strip()
TDS=ecq.split(“,”)[1].strip()
Salinity=ecq.split(“,”)[2].strip()
SG=ecq.split(“,”)[3].strip()
print(‘TDS: ‘,TDS)
print(‘EC: ‘,EC)
print(‘Salinity: ‘,Salinity)
print(‘Specific Gravity of Sea Water: ‘,SG)
response=urllib2.urlopen(‘http://10.1.1.20/JSON?request=controldevicebyvalue&ref=2338&value=’ + str(TDS))
html=response.read()
response.close()
sys.stdout.flush()
response=urllib2.urlopen(‘http://10.1.1.20/JSON?request=controldevicebyvalue&ref=2337&value=’ + str(EC))
html=response.read()
response.close()
sys.stdout.flush()
response=urllib2.urlopen(‘http://10.1.1.20/JSON?request=controldevicebyvalue&ref=2339&value=’ + str(Salinity))
html=response.read()
response.close()
sys.stdout.flush()
response=urllib2.urlopen(‘http://10.1.1.20/JSON?request=controldevicebyvalue&ref=2340&value=’ + str(SG))
html=response.read()
response.close()
sys.stdout.flush()
# send pH
device.set_i2c_address(99)
device.query(“T,” + str(temperature))
pH=device.query(“R”)
print(‘pH: ‘,pH)
response=urllib2.urlopen(‘http://10.1.1.20/JSON?request=controldevicebyvalue&ref=2335&value=’ + str(pH))
html=response.read()
response.close()
sys.stdout.flush()
 time.sleep(300)

I had a second script within Homeseer to write the values back into an access db.

Public Sub Main(ByVal parm as Object) 
Imports System.Data.OleDb
Dim PoolORPJ2 as Decimal
Dim PoolPH as Decimal
Dim PoolEC as Decimal
Dim PoolSalinity as Decimal
Dim PoolTDS as Decimal
Dim PoolSG as Decimal
Dim vDate
vDate = Now()
PoolORPJ2 = iGetDevValByAddr(“J2”)
PoolPH = iGetDevValByAddr(“J3”)
PoolEC = iGetDevValByAddr(“J4”)
PoolTDS = iGetDevValByAddr(“J5”)
PoolSalinity = iGetDevValByAddr(“J6”)
PoolSG = iGetDevValByAddr(“J7”)
Dim Cmd As OleDbCommand 
Dim SQL As String
Dim objCmd As New OleDbCommand 
Dim Con = New OleDbConnection(“Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Program Files (x86)\HomeSeer HS3\data\Graphs\Graphs.mdb”) 
‘POOL
‘===================================================
SQL = “INSERT INTO PoolTemp([Date],PH,Salinity,ORP,SG,TDS,EC,PoolTemp) VALUES (‘” & Now &”‘,'” & PoolPH &”‘,'” & PoolSalinity &”‘,'” & PoolORPJ2 &”‘,'” & PoolSG &”‘,'” & PoolTDS &”‘,'” & PoolEC &”‘,'” & TempNodeI3 &”‘)”
Cmd = New OleDbCommand(SQL, Con) 
Con.Open() 
objCmd = New OleDbCommand(SQL, Con) 
objCmd.ExecuteNonQuery() 
Con.Close() 
GC.Collect()
End Sub
‘——————————————————————-
‘ iGetDevValByAddr: returns a device’s value as an integer
‘——————————————————————-
private Function iGetDevValByAddr(ByVal DevAddrStr As String ) as Decimal
        Dim intDevRef as Integer = hs.GetDeviceRef(DevAddrStr)
 iGetDevValByAddr = -1
 if intDevRef <= 0 Then
     hs.writelog( “Error”, “iGetDevValByAddr: device not found =” & DevAddrstr )
 else
     iGetDevValByAddr = hs.DeviceValueEx( intDevRef )
 end If
End Function

After that I needed to build out some graphs so that I could display it on a tablet

Graph Asp

These were built with transparent background so that they would display nicely on my wall mounted tablets

Leave a Reply

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