[Custom Component] Pool Math sensors for pool chemicals and operations

This is a community support discussion thread for the Pool Math integration.

The integration has recently been updated (0.2.5) to support using the JSON feed from Pool Math.

Hi!

I get wrong value on pool temp.


But the data in the links is correct

F to C conversion? Looks like it assumes its in F and converts.

Yes, you are correct, did not think about that. @ryans is there a way to set that it is. C in the yaml code?

It looks like the json from pool math sets the units:

waterTempUnits = 1 = Celsius
waterTempUnits = 0 = Fahrenheit

Unfortunately, the code sets it to F. Then Home Assistant converts to to celsius again. So its getting double converted.

I wonder if its best to just drop the unit of measurement from the component and just use ā€˜degreesā€™ generically. Then HA will just display whatever you have set in the app. If the user wants to customize it, then can via the customize: functions in HA.

@calisro Thanks for spotting this. I will release a change that selects the appropriate unit based on this value (unless someone else wants to submit a PR?)

I just pushed a temporary hack to the master branch to use waterTempUnitDefault for unit_of_measurement for all temperature units. It isnā€™t a great solution, but given the units are not specified in the ā€œoverviewā€ part of the JSON document where the values are returned, it should at least get Celsius working.

Hi

Thanks for this integration.

First of all Iā€™m pretty new to HA. Got it to work but only for the Pool. My hot tub has another url and when I try to put 2 different urlā€™s in the config file I get an error.

Did put it in like this.

sensor:

I had to remove one url. Only the last one works.
Is it possible to get both of them in?
How is it supposed to look?

Thanks

@PerL that would be two different sensors:

sensor:
  - platform: poolmath
    url: https://api.poolmathapp.com/share/11111.json

  - platform: poolmath
    url: https://api.poolmathapp.com/share/22222.json

It worked. Thanks for a quick answer

Superb integration - just what I was looking forā€¦

I have built on top of this integration by doing a calculation to define what percentage I need to set my SWG. With the default update time I would need to wait up to 15 minutes for the calculation to update.
I work around that by setting the scan_interval to 1 minute which is a bit excessive.

So just one thought: is it possible to do a manual update, eg by using a button and calling a script? Then I could leave the default update time but be able to do a manual update whenever I need itā€¦

Thanks!

1 Like

@ryans, does the integration do anything different than the older rest API calls and template sensors donā€™t? Been using that for years and just noticed your integration.

Thanks

Anyone else having trouble with this integration recently? I think it started when they updated their app in the last few weeks.
I think they changed some pool URIs and also changed the JSON format. Iā€™ve reached out to them to check on it.

This is interesting - can you tell me a bit about how youā€™re calculating this?

If anyone has feature code contributions, happy to review code contributions and merge.

For instance, would love to see the feature @berniedp has with suggesting SWG % calcs just be part of the integration. I suspect the update interval can also be reduced now that the integration is using the JSON endpoint. The 15 minutes was originally the default when it was scraping TFP site before they exposed the JSON API. In fact, Iā€™ll go ahead change that default.

I never opened my pool last year and havenā€™t really worked on it in quite some time. Iā€™m likely opening my pool this Summer, but not sure yet (depends on how much Iā€™m on the road again), and if so will be able to test and do some improvements.

I am working on a write-up of how I achieved the SWG calculations - itā€™s not complex (few helpers, sensors and templates for the calculations). Hopefully can finish it by this weekend but will definitely share :slight_smile:

OK - here we go:

I have separate tab on my HA dashboard that has 3 cards:

1Ā° The Poolmath results (taken via the integration):

The green items is what comes back from the API.
The blue ones are memory aids (basedon TFP recommendations). All of these, except from the ā€˜Chlorine Rangeā€™, are basically just text to help me remind on the ā€˜A- = absolute minimumā€™, ā€˜T = target valuesā€™ and ā€˜A+ = absolute maximumā€™.

The ā€˜Chlorine Rangeā€™ is a sensor with the following template behind it:

  - sensor:
    - name: "Chlorine Range"
      unique_id: chlorine_range
      state: >
        {% set cya = states('sensor.pool_CYA')|float(0) %}
        {% if cya == 20 %} A- 2 / T 3-5
        {% elif cya == 30 %} A- 2 / T 3-6
        {% elif cya == 40 %} A- 2 / T 3-7
        {% elif cya == 50 %} A- 2 / T 3-8
        {% elif cya == 60 %} A- 3 / T 4-9
        {% elif cya == 70 %} A- 3 / T 5-10
        {% elif cya == 80 %} A- 4 / T 6-11
        {% elif cya == 90 %} A- 4 / T 6-12
        {% elif cya == 100 %} A- 5 / T 7-13
        {% else %} OUTSIDE TARGET
        {% endif %}

This basically defines the target for my FC based on the CYA in my pool.

2Ā° The SWG settings card:

Card 2

1 = Current chlorine (taken from poolmath app)

2 = my chlorine target based on CYA ā†’ this is a sensor I set with the following template:

  - sensor:
    - name: "Target Chlorine"
      unique_id: target_chlorine
      state: >
        {% set cya = states('sensor.pool_CYA')|float(0) %}
        {% if cya == 20 %} 4
        {% elif cya == 30 %} 4.5
        {% elif cya == 40 %} 5
        {% elif cya == 50 %} 5.5
        {% elif cya == 60 %} 6.5
        {% elif cya == 70 %} 7.5
        {% elif cya == 80 %} 8.5
        {% elif cya == 90 %} 9
        {% elif cya == 100 %} 10
        {% else %} 0
        {% endif %}

The value is basically the middle point of the ā€˜rangeā€™ (item 3) - e.g. at CYA 80 the range is 6-11 so I make that 8.5

3 = FC range (same value as in card 1) - again just a memory aid

4Ā° This is the estimated hours of sun expected today (taken from my Tempest weatherstation) that helps me to judge how sunny it will be - not very scientific but again an aid in making a decision for 5Ā°

5Ā° An input helper (numeric with slider) to define the amount of FC I expect to loose today - I know in winter, when the pool is covered, Iā€™m at 0 or .5 - in full summer seaon Iā€™m at 4.5 to 5.

6Ā° An input helper (numeric with slider) for the number of hours my pool pump & SWG will be on

7Ā° This is a calculation of the percentage I need to set my SWG at:

This is the logic template behind that sensor:

  - sensor:
    - name: "SWG Percentage"
      unique_id: swg_percentage
      state: >
        {% set current = states('sensor.pool_FC')|float(0.0) %}
        {% set target = states('sensor.target_chlorine')|float(0.0) %}
        {% set expected_consumption = states('input_number.pool_expected_chlorine_consumption')|float(0.0) %}
        {% set pump_hours = states('input_number.pool_pump_run_hours')|float(0.0) %}
        {# If current is lower than target, then top-up to target plus the expected consumption#}
        {% if  current < target %} {{ ((target - current + expected_consumption) / (0.7681 * pump_hours) * 100) | round() }}
        {# If current is equal to target, then just produce expected consumption#}
        {% elif  current == target %} {{ (expected_consumption / (0.7875 * pump_hours) * 100) | round() }}
        {# If over target, following cases apply#}
        {#  case 1 - over target MORE than or equal to expected consumption then produce 0#}
        {% elif  (current - target) >= expected_consumption %} 0
        {#  case 2 - over target LESS than expected consumption then produce difference#}
        {% elif  (current - target) < expected_consumption %} {{ ((expected_consumption - current + target) / (0.7875 * pump_hours) * 100) | round() }}
        {% else %} 100
        {% endif %}

Letā€™s expand a bit on this part:

((expected_consumption - current + target) / (0.7875 * pump_hours) * 100)

The formula came from an excel sheet (see here) created by a community member of TFP so all credit for the calculation goes to Matthew G. Moore.

The 0,7681 is a value I calculated specifically for my pool & SWG - as this wonā€™t change any time soon - and is achieved as follows (you can see that in the excel sheet as well, of course):

(SWG production lbs / day) * 16 / (pool size in gallons) * 7489,4 / 24

So for my pool having a Pentair iChlor30 and producing 1 lbs/day:

1 * 16 / 6340 * 7489,4 / 24 ā†’ thatā€™s 0,7875

That entire calculation then returns the percentage of my SWG.

3Ā° The ā€œSWG adjusted settingā€ card:

Card 3

This card basically does the exact same thing as the card 2Ā° except that I have added another helper (numeric with slider) that allows me to define the FC level I want (instead of using the calculated one).

This is then added in a new sensor calculated using the same formula above EXCEPT the second line now points to the helper:

{% set target = states('input_number.pool_adjusted_target_fc')|float(0.0) %}

So there, thatā€™s how I did thatā€¦ :grinning:

If you have any questions, please do let me know!
B.

1 Like

Jon,

Iā€™m having trouble too - much around the same time. Please do share your findings!

Ryan

Awesome, thanks for sharing!

I wanted to do this same thing for the solar heating estimates but Iā€™m not sure how to do it yet. I was just going to use the weather forecast for it.

So, it looks like they changed something.
The URI for my pool info changed.
And I look at the JSON my link now, it seems to show my whole account (which, also seems to include all of your account details - name, address/phone (if configured somewhere?), and email address. I reported this to them, but have not heard back yet.

Example JSON:

{
  "pools": [
    {
      "pool": {
        "type": "pool",
        "name": "Home",
        "overview": {
          "fc": 1.5,
          "cc": 0.5,
          "cya": 19,
          "ch": 350,
          "ph": 7.8,
          "ta": 100,
          "salt": 3600,
          "bor": null,
          "tds": null,
          "csi": -0.19,
          "flowRate": null,
          "pressure": null,
          "waterTemp": 77.1,
          "pumpRunTime": null,
          "swgCellPercent": null,
          "fcTs": "2024-06-11T19:32:56Z",
          "fcLogId": "X",
          "ccTs": "2024-05-21T02:52:11Z",
          "ccLogId": "X",
          "cyaTs": "2023-05-28T14:08:26Z",
          "cyaLogId": "X",
          "chTs": "2022-05-24T21:17:19Z",
          "chLogId": "X",
          "phTs": "2024-06-11T19:32:56Z",
          "phLogId": "X",
          "taTs": "2023-05-28T14:08:26Z",
          "taLogId": "X",
          "saltTs": "2024-05-21T02:52:11Z",
          "saltLogId": "X",
          "borTs": null,
          "borLogId": null,
          "tdsTs": null,
          "tdsLogId": null,
          "csiTs": "2019-08-19T12:35:02+00:00",
          "csiLogId": "X",
          "flowRateTs": null,
          "flowRateLogId": null,
          "pressureTs": null,
          "pressureLogId": null,
          "waterTempTs": "2024-05-25T17:31:05Z",
          "waterTempLogId": "X",
          "pumpRuntimeTs": null,
          "pumpRuntimeLogId": null,
          "swgCellPercentTs": null,
          "swgCellPercentLogId": null,
          "backwashedTs": "2023-04-21T15:00:34Z",
          "backwashedLogId": "X",
          "brushedTs": "2021-04-29T12:27:25Z",
          "brushedLogId": "X",
          "cleanedFilterTs": "2023-04-21T15:00:34Z",
          "cleanedFilterLogId": "X",
          "vacuumedTs": "2022-04-29T13:36:26Z",
          "vacuumedLogId": "X",
          "openedTs": "2023-04-21T15:00:34Z",
          "openedLogId": "X",
          "closedTs": "2020-10-05T20:13:33Z",
          "closedLogId": "X"
        },
        "contactName": "Jon",
        "contactPhone": null,
        "contactEmail": "...",
        "address": null,
        "lat": null,
        "lon": null,
        "notes": null,
        "volume": 20000,
        "poolVolumeUnit": 0,
        "buildType": 1,
        "chemType": 1,
        "sanitizerType": 1,
        "phUpType": 0,
        "phDownType": 6,
        "chType": 0,
        "cyaType": 0,
        "borType": 0,
        "bleachPercent": 6,
        "bleachJugSize": 128,
        "fcTarget": 3,
        "fcSLAMTarget": 10,
        "cyaTarget": 20,
        "phTarget": 7.5,
        "taTarget": 70,
        "chTarget": 400,
        "saltMin": 3000,
        "saltMax": 4000,
        "saltTarget": 3500,
        "borMin": null,
        "borMax": null,
        "borTarget": null,
        "cyaEntered": 20,
        "trackSalt": true,
        "trackBor": false,
        "trackCC": true,
        "trackCSI": false,
        "trackFlowRate": false,
        "trackPressure": true,
        "trackBackwashed": false,
        "trackBrushed": false,
        "trackVacuumed": false,
        "trackCleanedFilter": false,
        "trackWaterTemp": true,
        "trackSWGCellPercent": false,
        "trackPumpRunTime": false,
        "remindBackwashed": false,
        "remindBackwashedDays": 7,
        "remindBrushed": false,
        "remindBrushedDays": 14,
        "remindVacuumed": true,
        "remindVacuumedDays": 14,
        "remindCleanedFilter": true,
        "remindCleanedFilterDays": 14,
        "waterTempUnitDefault": 0,
        "logWeather": true,
        "weatherLocId": "X",
        "zip": "X",
        "alwaysShowPoolVolume": false,
        "hideIdealRangeAlerts": false,
        "hideRecommendedRangeAlerts": false,
        "shareDataOptOut": false,
        "shareWithCode": true,
        "shareCode": "A8DjEO1",
        "shareWithTfp": true,
        "swgLbsPerDay": 1.9,
        "swgModelId": "custom",
        "overrideFCTarget": null,
        "userId": "tfp-X",
        "origin": null,
        "id": "X",
        "_ts": 1718299086,
        "deleted": false
      },
      "recentLogs": [
        {
          "type": "testlog",
          "fc": 1.5,
          "cc": null,
          "cya": null,
          "ch": null,
          "ph": 7.8,
          "ta": null,
          "salt": null,
          "bor": null,
          "tds": null,
          "csi": 0.17,
          "waterTemp": null,
          "waterTempUnits": 0,
          "poolId": "X",
          "logTimestamp": "2024-06-11T19:32:56Z",
          "weather": {
            "timestamp": "2024-06-11T19:22:00Z",
            "desc": "Cloudy",
            "icon": 7,
            "temp": 20.5,
            "tempFeelsLike": 21.4,
            "relHumidity": 58,
            "windDir": 315,
            "windDirDesc": "NW",
            "windSpeed": 2.8,
            "windGustSpeed": 6.3,
            "uvIndex": 1,
            "uvIndexDesc": "Low",
            "visibility": 17.7,
            "cloudCover": 96,
            "pressure": 1013.5,
            "precipPast24Hours": 0
          },
          "weatherLogId": "X",
          "userId": "X",
          "origin": null,
          "id": "X",
          "_ts": 1718134390,
          "deleted": false
        },
...

Just found out about this integration and would love to replicate what people have done with adjusting their SWG! Used the new URI but not seeing any sensors populate. Any update on the changes they made and how to get it working again?