Viron Astral Pool ChlorinatorGo integration

@DunkyDaMonkey,
Firstly, sorry for the rubbish code - I’m new to python. And, please excuse the hasty code. There’s no exception handling, and many places where it is quite brittle.

Yeah, the 1 minute polling was pretty arbitrary…
Ideally, the python api would handle trying harder to connect before failing. As I understand it, bleak_retry_connector does a lot of this handling somehow (as described here). It’s beyond me though; the documentation is a bit sparse.

When HA first loads the chlorinator entry on startup, in async_setup_entry, it gets a BLEDevice from the saved MAC address. This is passed to the instance of the chlorinator API and reused ‘forever’. If this fails, HA automatically retries (I think).

The problem occurs later in the polling event of the coordinator: _async_update_data, which calls async_gatherdata, which creates a BleakClient from the saved BLEDevice. This is where it actually tries to connect to the device, and is liable to fail to connect without any retry (I think). This needs something more resilient. I raked over lots of different repos using bleak, but struggled to find an obvious way to do it.

This characterises the most common failure mode:

2023-04-05 06:03:20.893 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Unexpected error fetching astralpool_chlorinator data: [org.bluez.Error.Failed] le-connection-abort-by-local
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/update_coordinator.py", line 239, in _async_refresh
    self.data = await self._async_update_data()
  File "/config/custom_components/astralpool_chlorinator/coordinator.py", line 39, in _async_update_data
    data = await self.chlorinator.async_gatherdata()
  File "/usr/local/lib/python3.10/site-packages/pychlorinator/chlorinator.py", line 136, in async_gatherdata
    async with BleakClient(self._ble_device, timeout=3) as client:
  File "/usr/local/lib/python3.10/site-packages/bleak/__init__.py", line 433, in __aenter__
    await self.connect()
  File "/usr/src/homeassistant/homeassistant/components/bluetooth/wrappers.py", line 249, in connect
    connected = await super().connect(**kwargs)
  File "/usr/local/lib/python3.10/site-packages/bleak/__init__.py", line 471, in connect
    return await self._backend.connect(**kwargs)
  File "/usr/local/lib/python3.10/site-packages/bleak/backends/bluezdbus/client.py", line 190, in connect
    assert_reply(reply)
  File "/usr/local/lib/python3.10/site-packages/bleak/backends/bluezdbus/utils.py", line 20, in assert_reply
    raise BleakDBusError(reply.error_name, reply.body)
bleak.exc.BleakDBusError: [org.bluez.Error.Failed] le-connection-abort-by-local

No need to be sorry!!! I think what you have created is fantastic. Even as some who’s job is 50% coding in python, I’m a complete novice and I’m not about the judge anyone code (I mean I still actively avoid class structures where possible as I originally learn to code in top down compliers)

Without going down the rabbit hole too far, as by the time you get to bleak you are about 3 layers deep in the libraries. The “fix” I deployed to my own integration is to just put a simple try except statement in around the API, in the even that we get nothing or we error we retain the last values and carry on. (if you wanted to we could initiate a wait and retry in the function but may require a bit of a restructure change)

This isn’t the best fix for what we are seeing, but it does make it more resilient. To be honest I’m a little too lazy to fork your repo to make the changes, if you want to give me perms I can branch my changes or I can just post my code blocks in an issue on git for you to have a look at.

And finally if it reassures you, I’ve written and reviewed much worse code than what you’ve put together.

v0.1.9 now has @DunkyDaMonkey’s proposed changes. Logbook looks much cleaner now!

3 Likes

Thank you to all who have contributed. I will update and test now.

I like the added log errors you added :slight_smile:

I also used it when I cleaned my pool earlier today, I found the selector can get our of sync with the manual buttons on the unit. I’ll raise an issue on git, but I haven’t had a look to digest the selector function. (the issue was I had turned it off manually to do a backwash and while I was turning my selector valve the pump kicked back on to auto from the app).

No harm done, it didn’t do it after i pulled out my phone and pushed the selector to off while I used the maint backwash function.

Thanks for all you efforts.

Yikes, that’s not ideal.
I’ll do some testing on mine to see if I can reproduce.
Will follow up on github.

Oh in case anyone else was interested, I record my acid feed tank level using a load cell, and temp and level of the pool using a water proof thermocouple and dual waterproof ultrasonic sensor. I plan on moving those things back onto the ESP32 from the ESP8266, then basically all my Pool stuff will come back via the one chip housed in a IP65 rated box next to the chlorinator.

Food for thought. I’m game for more ideas, but I can’t think of any more.

The load cell for the acid is awesome!

How about voltage and current sensing on the cell?
Filter pressure?
Pump RPM?

I had a look at some filter pressure 1/4 BSP fittings, I didn’t find any 3V version, there was a few arduino 5V option most were outside of the pressure rating of the current gauge, but one was up to 1MPa so only 4 times what I’d need, I’ll dig a little deeper and see what i can find but being analogue and 5V i’d have to voltage divide into an ADC input.

I don’t see much value in the pump rpm, the astral integration gives me low/med/high already.

As for the Cell, isn’t there a statistic page on the app that gives you some of the details about the cell outputs?

Do you have a Viron XT pump integrated into the eq?
There are controls for that which I haven’t implemented… Would be fairly ‘straightforward’.

	public enum AppActions
	{
		None,
		Off,
		Auto,
		Manual,
		Low,
		Medium,
		High,
		Pool,
		Spa,
		DismissInfoMessage,
		DisableAcidDosingIndefinitely,
		DisableAcidDosingForPeriod,
		ResetStatistics,
		TriggerCellReversal
	}

Yeah, I have the variable speed pump attached to mine, the pump speed mode already comes back. I mean that would be a nice to have, but not strictly necessary as the only time I would use it is when I want the pump on but pull the suction plate out.

image

Amazing the progress so far. Awesome work mate. Just wondering what I need to do after the HACs integration install. When I go to HA integrations and look up Astral it says to add the device via configuration.yaml. I open the doco to see what I need to do and the github repo says the config is done on the UI? I must be missing something. Do I need to add some lines to the yaml file? :slight_smile:

Hi @MarioAus,
OK, so it sounds like you have added the AstralpoolChlorinator integration. Pretty sure it wouldn’t come up when you add an integration in the UI. If you want, you can double check by looking in the file editor, under /custom_components/, you should see an astralpool_chlorinator folder in there.

This means that now HA is on the lookout for the service UUID of the eQuilibrium. If it discovers it via any BT device, then the ‘new device discovered’ thing will happen in HA.

If you aren’t using a bluetooth proxy, then you’ll need a bluetooth device attached to your HA box. Make sure the bluetooth integration is added to HA.

If it’s not popping up, then odds on that the machine is out of bluetooth range.

I highly recommend using a Bluetooth proxy, I flashed one of my ESP32-s chips that I brought years ago to attempt to do room presences detections. It’s relatively cheap and simple to do with esphome now.

Thanks for the advice.

I sorted the range issue with the bluetooth proxy (pool chlorinator is a looooong way from house).

As soon as the range issue was sorted then the auto discovery kicked in.

I am now prompted for something (a required field).

What do I need to put in there?

Nevermind. Silly me… Reading above comments it seems it is the service UUID.

It seemed to work once I added that but not showing any values. Should my service UUID be different?

Ok, that’s a pairing code that you need to read from the screen of the Chlorinator. Can’t remember where it is in the menu, but it’s the same code as when you added it to the mobile app. 4 characters.

Delete the integration so that you get another go at entering it

Edit:
This:

Thanks for that. Got the code and re-setup the chlorinator.

It looks like I am still not getting the data.

2023-04-15 18:42:51.334 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 366
2023-04-15 18:42:51.341 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 366
2023-04-15 18:43:11.349 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 367
2023-04-15 18:43:11.354 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 367
2023-04-15 18:43:31.341 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 368
2023-04-15 18:43:31.351 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 368
2023-04-15 18:43:51.329 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 369
2023-04-15 18:43:51.341 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 369
2023-04-15 18:44:11.324 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 370
2023-04-15 18:44:11.331 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 370
2023-04-15 18:44:18.000 ERROR (MainThread) [homeassistant.config_entries] Error unloading entry POOL01 for astralpool_chlorinator
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 539, in async_unload
    result = await component.async_unload_entry(hass, self)
  File "/config/custom_components/astralpool_chlorinator/__init__.py", line 57, in async_unload_entry
    await hass.config_entries.async_forward_entry_unload(entry, PLATFORMS)
  File "/usr/src/homeassistant/homeassistant/config_entries.py", line 1432, in async_forward_entry_unload
    if domain not in self.hass.config.components:
TypeError: unhashable type: 'list'
2023-04-15 18:44:31.324 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 371
2023-04-15 18:44:31.336 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 371
2023-04-15 18:44:51.323 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] _data_age: 372
2023-04-15 18:44:51.324 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 372
2023-04-15 18:44:51.331 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 372
2023-04-15 18:44:51.339 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] Finished fetching astralpool_chlorinator data in 0.016 seconds (success: False)
2023-04-15 18:45:11.334 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] _data_age: 373
2023-04-15 18:45:11.339 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 373
2023-04-15 18:45:11.352 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 373
2023-04-15 18:45:11.353 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] Finished fetching astralpool_chlorinator data in 0.019 seconds (success: False)
2023-04-15 18:45:31.329 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] _data_age: 374
2023-04-15 18:45:31.329 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 374
2023-04-15 18:45:31.336 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 374
2023-04-15 18:45:31.339 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] Finished fetching astralpool_chlorinator data in 0.010 seconds (success: False)
2023-04-15 18:45:51.330 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] _data_age: 375
2023-04-15 18:45:51.331 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 375
2023-04-15 18:45:51.340 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 375
2023-04-15 18:45:51.344 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] Finished fetching astralpool_chlorinator data in 0.014 seconds (success: False)
2023-04-15 18:46:11.324 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] _data_age: 376
2023-04-15 18:46:11.324 WARNING (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata: 376
2023-04-15 18:46:11.334 ERROR (MainThread) [custom_components.astralpool_chlorinator.coordinator] Failed _gatherdata, giving up: 376
2023-04-15 18:46:11.340 DEBUG (MainThread) [custom_components.astralpool_chlorinator.coordinator] Finished fetching astralpool_chlorinator data in 0.016 seconds (success: False)

The above is an extract of the types of errors coming up.

I can see the bluetooth proxy on ESP Home and see the chlorinator in integrations.

I will keep looking… any ideas on what it could be? Really appreciate the help! :slight_smile:

I would guess its either got the wrong code or there’s something up with your Bluetooth.

That data age count comes from the try except code, it basically means the async request through the Bluetooth lib has failed somewhere and it’s come back out to the catch statement. If you connect to the log on the Bluetooth proxy, you might get more insights to what’s bouncing back at that point.

Also, did you esp-ifs or arduino lib on the Bluetooth proxy? I don’t know if it would make a difference, but everything I read said they esp-ifs one is superior.

Hi, @DunkyDaMonkey

I will retry and put the code in again… maybe try lowercase this time. Could it be case sensitive?

All I find in the logs in ESPHome is the following:

I am not sure about esp-ifs but found this when validating the bluetooth proxy in ESPHome:

platform_version: platformio/espressif32 @ 5.3.0

Also noticed this in the validation yaml:

bluetooth_proxy:
  active: true
  cache_services: true
  connections:
  - {}
  - {}
  - {}

Does this mean it is not connected to the Astral chlorinator bluetooth?

I have postman and wondering if I could test the api directly. Anyone know what is needed in the headers etc.?