Sharing enlightening moments of home automation

Smarter Swimming Pool 4: Liquid Chlorine Level

My pool requires a regular influx of liquid chlorine that is stored in a large drum next to the automated chlorinator. Every couple of weeks I have to replenish that drum, otherwise the chlorine concentration in the pool water gets too low, and algae starts growing resulting in green-ish pool water. Needless to say, this has already happened a couple of times in the past and each time I had to pour in more chemicals to clean up the mess and wait a few days until the water was clean again.

A simple solution to this problem may have been to just put a reminder into my calendar to go down to the pool every other week or so, and manually check the level of liquid chlorine in the drum and refill if necessary. But why not throw technology at this and try to measure the actual level of liquid chlorine left in the drum, and then send myself a notification when the drum runs empty?

Measuring Liquid Chlorine Level

The main challenge for measuring the liquid chlorine level is that it is a highly corrosive acid. I looked at this good summary describing various methods of measuring fluid levels. I pretty much discarded all methods that would require a sensor to be in contact with the fluid – too many sensors are made of metal or unknown types of plastic which would not survive the direct contact with the liquid chlorine very long. Out of the contact-less methods, I picked the ultrasonic sensor because it appeared to be a good compromise of promising to be up for the job and affordability. The alternatives would have been laser or radar based, but I could not find a simple and affordable sensor in a ready-to-go format.
I ended up buying a waterproof ultrasonic sensor which is somewhat encapsulated, but comes with the disadvantage of a ~25cm blind zone. My drum is about 60cm high, and since I am primarily interested in getting a notification if the drum is nearly empty, the blind zone was acceptable.

Building the device

This article describes another extension to the device that I had previously connected a temperature sensor and water level switches to.  The breadboard view below is just showing the basic connectivity; in reality I mounted all the bits onto a prototype PCB.

Connecting the ultrasonic sensor

The ultrasonic sensor I use is a JSN-SR04T which actually comes in two parts – a small PCB with connectors to power and control the sensor, and the actual sensor on a long cable connecting into the PCB.

The JSN-SR04T operates at 5V which the WEMOS D1 mini Pro luckily provides. The trigger works fine on a regular digital pin, but the echo must go through a voltage divider.

Sensor – Liquid chlorine level
Ultrasonic SensorESP
EchoD6 via voltage divider

The voltage divider between the echo pin and the ESP consists of two resistors: A 10kΩ resistor is connected between the sensor’s echo pin and D6, and a 22kΩ resistor is connected between D6 and ground.

Configuring ESPEasy

The general configuration of the device itself and its MQTT connectivity have already been covered in the previous post.

The sensor is added as a “P013 – Ultrasonic Sensor – HC-SR04” device which requires both pins to be configured – “1st GPIO” refers to the trigger pin, and “2nd GPIO” to the echo pin – and since I want to measure the distance I selected the mode “Value”.

ESPEasy liquid chlorine level ultrasonic sensor configuration

Since I already anticipated that some calculations had to be done in Home Assistant, I decided to just publish the measured distance value via MQTT, and called the sensor “liquidchlorinerawvalue”.

The delay of 30 seconds was my initial configuration, but have increased it to 60 seconds in the meantime.

Assembling the sensor

As usual I got most parts of the enclosure for the sensor from my local hardware store.

Liquid chlorine level sensor parts

The ultrasonic sensor is mounted into a 32mm pressure pipe cap.

Liquid chlorine level ultrasonic sensor in bottom cap

For the sensor’s cable I mounted a cable gland into a 40mm pressure pipe cap.

Liquid chlorine level cable gland in top cap

The previously assembled bottom cap with the sensor just fits into a 40mm x 1½” valve socket. The bottom cap still sticks out a couple of millimetres for the top cap to fit over it.

Liquid chlorine level sensor top and bottom cap before assembling
Liquid chlorine level ultrasonic sensor bottom view

Assembling the parts now makes a more or less waterproof enclosure with the ultrasonic sensor facing downwards, and its cable coming out of the top.

Liquid chlorine level sensor caps assembled

I then drilled a 38mm (or 1½”) hole into the lid of the drum. A rubber washer between the enclosure and the lid is used to prevent rain water from the surface of the lid to get into the drum.

Liquid chlorine level sensor mounted in drum lid

The cheapest option to fix the enclosure to the lid was to saw off the top of a 1½” BSP pipe cap and screw it onto the thread of the enclosure from the bottom of the lid.

Liquid chlorine level sensor in drum lid, bottom view

And this is how the fully assembled enclosure looks like with braided sleeving for the cable and a waterproof plug on the end.

Liquid chlorine level sensor in drum lid, fully assembled

Configuring the sensor in Home Assistant

Setting up the sensor

The following MQTT sensor definition just picks up the raw distance value in centimetres from the ultrasonic sensor.

  - platform: mqtt
    entity_namespace: espeasy_01_pool
    state_topic: 'espeasy/espeasy-01-pool/liquidchlorinerawvalue'
    force_update: true
    name: 'Liquid Chlorine Raw Value'
    unit_of_measurement: 'cm'

Smoothening the incoming data

After running the sensor for a while I soon realised that the values fluctuate quite a bit – even when the chlorinator was not running. Hence I wrapped the distance value into a statistics sensor with a high sampling size in an attempt to smoothen the values reported over time and avoid outliers that may eventually trigger false alerts.
The template sensor is then retrieving the mean value from the statistics sensor and turns it into a percentage value. The drum I have is 60cm high and has a volume of 60 litres. To keep things simple, the maths in the value template is assuming that the drum is a perfectly shaped cylinder, and for the purpose of this sensor that should be good enough.

  - platform: statistics
    entity_id: sensor.espeasy_01_pool_liquid_chlorine_raw_value
    name: Pool Liquid Chlorine Level
    sampling_size: 180
  - platform: template
        value_template: '{% if (states.sensor.pool_liquid_chlorine_level_mean.state | float >= 60) %}0.0{% else %}{{ ((60.0 - states.sensor.pool_liquid_chlorine_level_mean.state | float) | float / 60 * 100) | round(1) }}{% endif %}'
        unit_of_measurement: '%'
        friendly_name: 'Liquid Chlorine Level'

With the introduction of the new filter sensor in Home Assistant 0.65 I may revisit this setup to replace the statistics sensor with a suitable filter sensor configuration.

Displaying the percentage value in the UI

Finally, the sensors storing intermediate values can be hidden, and the template sensor gets a nice icon.

      hidden: true
      icon: mdi:barrel
      hidden: true

Displaying the liquid chlorine level in percentage is as straightforward as you would expect.

Pool liquid chlorine level panel


Now, the most important part is sending an alert if the drum needs to be refilled. The following automation sends an alert via Pushover if the liquid chlorine level falls under 15% for 1 hour.

  - alias: 'Liquid Chlorine Level Low'
      platform: numeric_state
      entity_id: sensor.pool_liquid_chlorine_level
      below: 15
        hours: 1
      service: notify.pushover
        message: "Pool Liquid Chlorine Level is low: {{states.sensor.pool_liquid_chlorine_level.state}}%."
        title: "Pool Liquid Chlorine Level"
          html: 1

Accuracy and verdict

The sensor is now running for a few weeks and while it is working fine in general, its accuracy is actually below expectations. The distance values measured by the ultrasonic sensor fluctuate a lot even though the actual level of liquid chlorine in the drum does not change.

The following sample graph shows the percentage values plotted out over a day. Over night the value almost does not change. The pool pump and chlorinator started at around 8am and that is when some small fluctuations can be observed. At about 11am I topped up the drum and the graph clearly shows that event. But shortly after that the values go up and down much more than expected.

The effect becomes even more apparent due to how Home Assistant compresses the y-axis based on the maximum range of values across a day.

Pool liquid chlorine level graph

I explored a few things that may contribute to the fluctuating values:

  • The chlorinator is fed through a small plastic tube that hangs from the top of the drum to the bottom. With the liquid chlorine slowly depleting from the drum that plastic tube moves a little bit and may cause false readings by the ultrasonic sensor.
  • The implementation of the ultrasonic sensor in ESPEasy makes the assumption that distances is measured in air at 20°C. In reality however, the speed of sound changes with the temperature, humidity and with the composition of the gas. In my case, the drum is outside and the liquid chlorine heats up in summer during the day and cools down at night; inside the drum the concentration of evaporating liquid chlorine is probably higher than normal and so may be the humidity. I tried to find a correlation between air temperature and distance value fluctuation but unfortunately couldn’t. The fluctuations are much more arbitrary than the temperature changes during the day. And the difference in the speed of sound at 20°C vs 35°C would only explain an error margin of about 2.5%.
  • The way I constructed the sensor and mounted it into the lid of the drum may make it prone to reflections, and thus cause false readings.

My overall verdict is that the liquid chlorine level sensor definitely helps in sending out alerts about the time when the liquid chlorine needs to be topped up. In terms of accuracy there is still some room for improvement though.


I have recently found a turbidity sensor that I am planning to build into the pool. This could help to either identify when the pool filter may need to be cleaned, or alert me if the pool water is in the process of turning green (again).


At the time of writing this post, I used:

  • Home Assistant 0.65.1 with Python 3.6.3
  • ESPEasy v2.0.0-dev12 (dev_4096 build)
  • Wemos D1 mini Pro

Smarter Swimming Pool Series

  1. Pool Pump
  2. Water Temperature
  3. Water Level
  4. Liquid Chlorine Level
  5. Improvements Under the Surface
  6. Chlorinator Refurbishment
  7. Pool Gate


One response to “Smarter Swimming Pool 4: Liquid Chlorine Level”

  1. Carlson Avatar

    Hi! nice project, i get here while researching the accuracy of the sensor, you can try using median for the “cleaning” part since its more robust to extreme readings.
    Try using the newping library.

Leave a Reply

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