home-assistant-config
home-assistant-config copied to clipboard
Hot tub water quality
Objective
My hot tub requires pH, alkalinity and free chlorine levels to be maintained. The levels of these need to be tested daily using dip test strips and chemicals added to maintain appropriate water quality. I want to add sensors to help me monitor the quality of the water in my hot tub. As part of this exercise, I also want to learn about water quality and the chemicals I am adding to my hot tub.
So my plan is to try and monitor the following:
- [x] Temperature
- [x] TDS
- [x] pH
- [ ] ORP (free chlorine)
- [ ] Alkalinity
- [ ] Fill with freshwater solenoid (tap switch)
- [x] ESPHome configuration
- [ ] Home Assistant configuration and automation
- [x] Enclosure and sensor cable management
Ingredients
- ESP32 dev board
- Dallas temperature probe
- TDS sensor & DFRobot
- pH sensor
- ORP sensor
- Waterproof box
Hot tub specifics
My hot tub is a Lay-z Spa Hawaii Hydrojet Pro.
Type | Measurement |
---|---|
pH | 7.4-7.6 |
Total Alkalinity | 80-120ppm |
Free Chlorine | 2-4ppm |
Volume | 795L |
Other considerations
To get the water temperature I'm using a Dallas temperature probe with the native ESPHome integration. This is an example of using this in the ESPHome config.
dallas:
- pin: GPIO21
sensor:
- platform: dallas
address: 0x770316457608FF28
name: "${system_name}_temperature"
I'm using a single ESP32 for all of these sensors.
esphome:
name: ${system_name}
platform: ESP32
board: pico32
The TDS sensor is of the same kind available from Seed Studio. They have a great blog article about the sensor. Amongst the sample sketch, there is a calculation which converts the value returned from the sensor (v) to the TDS ppm value.
Voltage = sensorValue*5/1024.0;
tdsValue=(133.42*Voltage*Voltage*Voltage - 255.86*Voltage*Voltage + 857.39*Voltage)*0.5;
I converted this using a lambda filter in ESPHome.
filters:
- lambda: return (133.42*x*x*x - 255.86*x*x + 857.39*x)*0.5;
To integrate this with ESPHome I am using the ADC integration and a filter to allow me to capture a moving average while also capturing data regularly. This prevents fluctuations in the recorded data.
sensor:
- platform: adc
pin: GPIO34
name: "${system_name}_tds"
update_interval: 10s
unit_of_measurement: "ppm"
icon: "mdi:water-percent"
filters:
- lambda: return (133.42*x*x*x - 255.86*x*x + 857.39*x)*0.5;
- sliding_window_moving_average:
window_size: 15
send_every: 15
Here are the test results I captured as soon as I got it working with some repeatable tests (milk and my tap water). I plan to get a TDS pen tester to validate these tests.
TDS water quality test results
- 0.5m lead from the breakout board
- Milk = 400ppm
- Tap water = 40ppm
- 5m lead from the breakout board
- Milk = 405ppm
- Water = 360ppm
- Hot tub water (few weeks old and chemicals) = 400ppm
Hands-on for the first time with the TDS sensor. This arrived 23rd April 2020 and I had it up and running in ESPHome within 30 minutes. I was testing it with some milk and fresh tap water. I figured I could easily repeat these tests if I needed to.
Really useful information about the sensor can be found on the DF Robot site.
Last weekend I added too much foam remover to the hot tub. To avoid poor water quality and remove the weird white bits that also formed in the water I added some fresh water to the hot tub. The intention was to leave the hose on for 30 minutes or so and let the bad water overfill except I forgot I was running water into the hot tub and I woke a 2am in a panic the hose was still running. I promptly got up, disarmed the house, went downstairs, unlocked the patio and went out and turned the tap off. I then reversed all this and went back to bed. Whilst struggling to sleep I decided I wasn't going to be doing this again. Two things that will help me.
One, an automation to notify me if the temperature drops in the hot tub which is an indication although I plan to make this a little more sophisticated than it currently is and two a solenoid to turn the water tap on and off.
alias: 'Hot Tub - Water temperature falling'
trigger:
# trigger when the water temperature falls below 35c
- platform: numeric_state
entity_id: sensor.esph_hot_tub_water_temperature
below: '35'
condition:
# check the automation has not run within the last 60 minutes
- condition: template
value_template: "{{ (as_timestamp(states.sensor.date_time.last_changed) - (as_timestamp(state_attr('automation.hot_tub_water_temperature_falling','last_triggered')))) > 3600 }}"
action:
- service: notify.html5_notification
data_template:
title: "Hot Tub water temperature"
message: "The hot tub water is at {{ states('sensor.esph_hot_tub_water_temperature') }} and falling."
- service: notify.ios_james_iphone
data_template:
title: "Hot Tub water temperature"
message: "The hot tub water is at {{ states('sensor.esph_hot_tub_water_temperature') }} and falling."
Since extending the sensor on ~5m cable I'm getting different results from the tests I ran last night. This could be power or a signal issue. I'm going to doing some more testing to ensure the sensor is calibrated correctly.
To validate the TDS sensor I attached to an ESP32 board and installed in my hot tub I brought a handheld TDS test pen. I will capture a sample of tests and compare them against my ESP readings.
Pen TDS Readings
Sample | Result 1 |
---|---|
Tap water | 58 ppm |
Milk (fridge temp) | 1362 ppm |
Milk (room temp) | |
Hot tub at 40c | 480 ppm |
Hot tub
Sample | ESP32 TDS Sensor | Test Pen TDS |
---|---|---|
26 April | 405ppm | 480 ppm |
The pH sensor finally arrived today. Guess I should hook it up and see how well it works.
This evening I hooked up the pH sensor up to the ESP32 board I am using to track my hot tub water quality through ESPHome. The sensor was providing me with data but not with results I expected based on my test samples. The voltage from the sensor was always maxing out with a low voltage 1.1V. With a little more reading I discovered ESP32 boards have an attenuation property which defaults to 1.1V (0db). When I switched this to 3.9V (11db) I was able to get results that I expected.
I used the following two guides to help me with the conversion from volts to pH and including temperature in the pH calculation.
I also found some libraries that were useful to understand how the sensor works.
- https://www.electroniclinic.com/wp-content/uploads/2019/08/AnalogPHMeter.zip
- https://www.atlas-scientific.com/_files/arduino_pH_meter.zip
- https://www.atlas-scientific.com/_files/code/ino_files/atlas_gravity.zip
According to the Instructables guide, the sensor has an accuracy of +/- 0.2%. Therefore the sensor will operate within this accuracy in the temperature range of 7 - 46°C. As the hot tub heats the water to 40°C no temperature compensation or offset is required
Test samples
I filled espresso cups with samples to test the pH levels with liquids I could easily repeat again if needed.
- Malt vinegar
- Fresh semi-skimmed milk
- Cold tap water
- Bleach
Tests
- Calibration and test samples
- Test samples alongside TDS sensor
- Testing in hot-tub water
Observations
Sample | Expected | Test 1 | Test 2 | Test 3 |
---|---|---|---|---|
Vinegar | 2 | 2.09 pH (0.58V) | 2.09 ph (0.59V) | N/A |
Milk | 6 | 6.82 pH (1.96V) | 6.65 pH (1.90V) | N/A |
Water | 7 | 7.74 pH (2.21V) | 7.19 (2.06V) | 7.58 pH (2.16 V) |
Bleach | 13 | 13.65 pH (3.90V) | 13.65 pH (3.90V) | N/A |
I suspect the subtle differences between Test 1 and Test 2 were due to the milk ageing. I also noticed the sensor takes a few minutes to return to a neutral level when added to an acidic or alkaline substance
Hot tub
Sample | ESP32 pH Sensor | Test Pen pH |
---|---|---|
2 May | 6.7 pH | 6.7 pH |
pH Sample Charts
Some interesting reading on pH sensors. Age/lifetime of the sensor and how the sensor is affected by temperatures.
Time to squeeze all the the sensor boards into an enclosure. I want the unit to live beside the hot tub. This is mainly driven by the range of leads and not wanting to run them over a long lead.
This is a pre-production version. This summer I plan to build a wooden frame around my hot tub. I will house the sensor boards amongst this frame and that will be my production board.
pH and temperature values are spot on. I have a pH tester arriving today but I don't think there will be any discrepancies with the results I'm getting. What I'm not confident with is the TDS value. My TDS test pen is reporting a value that is 200 points higher than my TDS sensor. I have a feeling this is related to the attenuation or the calculation from volts to TDS. I will carry on reviewing this.
pH test pen confirms my sensor is calibrated and reporting correctly. Both the pen and sensor had identical readings.
I found a really some helpful articles on pool chemistry which pointed me towards the need for an ORP sensor. This will allow me to measure my chlorine levels.
- https://sensorex.com/pool-chemistry/
- https://blog.intheswim.com/total-dissolved-solids-in-swimming-pools/
This DF Robot blog post about creating an automatic water sensor calls out a few points for me to consider. Sensors should not be in the water 24/7 as it affects their life.
A random thought came as writing this. What if the pH sensor was in a tube and could be lowered in the hot-tub for a regular sample.
Also when using multiple sensors an analog signal isolator should be used to avoid sensors from affecting other sensors readings.
Alkalinity
There isn't a sensor for alkalinity which made me think, could I build a sensor to read the colors from a test strip.
This is all really great stuff. I'm getting a new hot tub is a few weeks. (I have an inflatable one at the moment).
I'd be really interested to replicate what you've done.
Is it still working sucessfully for you? any learnings ?
Just found this as I just started getting into ESP stuff to integrate with my HA. What an amazing write up, I may look into giving this a go myself!
https://twitter.com/pvizeli/status/1392532069427863560
https://ufire.co/shop/
“I found some nice hardware on @ufire_co. My order comes next Friday, time to start adding upstream support for @esphome_ and make the sensor data easily available on @home_assistant ❤️”
Looks interesting. Will be following with interest.
I found this super useful as I wanted to use esp home to program a sensor for TDS and Im more familiar with raw arduino code. I found that you were struggling with accurate values from your TDS. I believe it may be related to the boards analog port measuring between 0 and 1 volt at the chip level is what is outputted so you have to relate that voltage to a converted value of 3.3 volts at least for a wemos D1 mini thats what I had to do.... heres the rteference on the esp homesite Note This component prints the voltage as seen by the chip pin. On the ESP8266, this is always 0.0V to 1.0V Some development boards like the Wemos D1 mini include external voltage divider circuitry to scale down a 3.3V input signal to the chip-internal 1.0V. If your board has this circuitry, add a multiply filter to get correct values:
sensor:
- platform: adc
# ...
filters:
- multiply: 3.3
so my sensor looks like this and it now matches my handheld tds meter very very close
sensor:
- platform: adc
pin: A0
unit_of_measurement: 'PPM'
name: "Gary TDS"
update_interval: 10s
icon: "mdi:water-percent"
filters:
- multiply: 3.3
- lambda: return (133.42*x*x*x - 255.86*x*x + 857.39*x)*0.5;
- sliding_window_moving_average:
window_size: 15
send_every: 15
This looks awesome, and I'm curious if it means no more need for test strips or anything. How's it been working?
Do you have a full build manual, rather than having to piece it together from each of the different updates?
Interesting project!
How did you integrate the ORP sensor?
filters: - lambda: return (133.42*x*x*x - 255.86*x*x + 857.39*x)*0.5;
Doesn't the TDS reading require temperature compensation?
```yaml filters: - lambda: return (133.42*x*x*x - 255.86*x*x + 857.39*x)*0.5;
Doesn't the TDS reading require temperature compensation?
Yes and here’s how I solved that dilema if it helps. In a nutshell I take a raw reading of analog for tds then temp compensate that reading before plugging into the formula as per documentation on tds sensors.
# Raw TDS Reading
- platform: adc
pin: A0
unit_of_measurement: 'V'
name: "Gary Aquarium Raw Voltage"
id: gary_raw
update_interval: 60s
icon: "mdi:eye"
accuracy_decimals: 3
filters:
- multiply: 3.0
# Temperature Compensated Voltage
- platform: template
name: "Gary Aquarium Compensated Voltage"
icon: "mdi:eye"
id: gary_comp
unit_of_measurement: 'V'
accuracy_decimals: 3
lambda: 'return ((id(gary_raw).state) / (1 + (0.02 * ((id(gary_temp).state) - 25.0))));'
update_interval: 60s
# Temperature Compensated TDS
- platform: template
name: "Gary Aquarium TDS"
icon: "mdi:water-percent"
unit_of_measurement: 'PPM'
accuracy_decimals: 0
lambda: return (133.42*(id(gary_comp).state)*(id(gary_comp).state)*(id(gary_comp).state) - 255.86*(id(gary_comp).state)*(id(gary_comp).state) + 857.39*(id(gary_comp).state))*0.5;
Amazing, are the readings comparable to a pen test?, when I use your formula my reading is more than 100ppm less, see below
The first reading is the result of (133.42*x*x*x - 255.86*x*x + 857.39*x)*0.5;
without any compensation, which is closer to my TDS pen reading, and the following is the reading after applying your templates.
Came across the following which doesn't use any temperature compensation. https://www.seeedstudio.com/blog/2020/01/19/tds-in-water-what-is-tds-and-how-do-you-measure-tds-in-water/
Amazing, are the readings comparable to a pen test?, when I use your formula my reading is more than 100ppm less, see below
The first reading is the result of
(133.42*x*x*x - 255.86*x*x + 857.39*x)*0.5;
without any compensation, which is closer to my TDS pen reading, and the following is the reading after applying your templates.
Came across the following which doesn't use any temperature compensation. https://www.seeedstudio.com/blog/2020/01/19/tds-in-water-what-is-tds-and-how-do-you-measure-tds-in-water/
Hmmm it was closer for me using the pen one I had to compare. What are you using for the temperature? Celsius it should be if it isn’t already that as that could cause a big difference in the two.
What are you using for the temperature? Celsius it should be if it isn’t already that as that could cause a big difference in the two.
O, Fahrenheit, I'll switch it and check.