Viron Astral Pool ChlorinatorGo integration

A quick scan, and i’m by no means a bluetooth expert.

Your pool name is POOL01

The setup of connection between your device and the EQ seems to happen at packets # 915-918

Packets 919 → 930 seem to be setting up the bluetooth link-layer agreements (versions, features, length with the REQ - Request followed by the RSP - Response).

From there I did a quick google to try and filter some noise out, and found this as a copy paste straight into the filter bar:

btle.data_header.length > 0 || btle.advertising_header.pdu_type == 0x05

This shows only connection requests and non-zero data packets. (brings 3288 captured packets down to 182)

For reference the “Slave_0xaf9a8dad” is the EQ and the “Master_0xaf9a8dad” is the bluetooth device/application.

it would be nice to know what you are doing within the app at various time through the capture. But applying a tiny bit of ethernet experience (which may be way wrong), it doesn’t look like data payload was captured.

eg: packet request 1206 and response 1209 seem to be a fetch of what your Device Name is which is POOL01

So focusing on 1206 & 1209 for a little bit of learning, and this “Bluetooth Assigned Numbers” document ( Assigned Numbers (windows.net) )

We see in packet 1209, that it has inside Bluetooth Attribute Protocol, “Attribute Data” Handle 0x0003. The “Device Name” POOL01 is responded with 0x2a00. Which from the document link above is on page 83/393 “device Name” “0x2A00”.

But I cannot make out much else, unless it’s buried in the “unknown” space of the bluetooth spectrum, or if we don’t know what you were doing during the capture. Hope thats a start?

Thanks for that @ianmcginley!
I’m deep down the rabbit hole decompiling the dlls from the android app.

So far I’ve found some juicy snippets, such as:

				if (base.DeviceProtocolRevision == 1)
				{
					ICharacteristic characteristic = GetCharacteristic("45000203-98b7-4e29-a03f-160174643001");
					if (characteristic != null)
					{
						WriteEncryptedCharacteristic(characteristic, appActionCharacteristic2);
					}
				}
				else
				{
					WriteAppAction(appActionCharacteristic2);
				}

Which is inside a function that is called to do stuff such as changing modes.
Looks like there must be a couple of protocol versions, one plain and one encrypted.
Judging by the payloads in the sniffed packets, the device I have is using the encrypted protocol. When you add a chlorinator to the app for the first time, you have to put in a pass code that you read from the device’s screen.

Anyhow, I’ll keep pulling at threads in the decompiled code.

Update:
Seems that it is using the PCLCrypto library, using AES encryption and ECB block mode.
I need to work out how the secret key is determined.

Update:
Here’s the sequence of events when connecting:

  • StartConnectionProcedure → ConnectToDevice
  • OnDeviceConnected → DiscoverServices
  • OnServiceDiscovered
    if service UUID = 45000001-98b7-4e29-a03f-160174643001 or 45000001-98b7-4e29-a03f-160174643001
    → DiscoverCharacteristics
  • OnCharacteristicsDiscovered
    if characteristic UUID = 45000002-98b7-4e29-a03f-160174643001 or 45000001-98b7-4e29-a03f-160174643002
    → ReadCharacteristic
  • OnReadCharacteristic (actually called OnSlaveSessionValueUpdated)

In here ^ is where the SlaveSessionKey is read from the characteristic, and used along with the AccessCode to encrypt the MAC…

happy to help where i can… I cannot code, but can extract wireshark messaging

Quick update for anyone who might be interested:

A BLE characteristic is read from the Chlorinator immediately after connecting. This data is then manipulated in a variety of different ways to generate a SessionKey, which is used for encrypting/decrypting subsequent messages.

There is a ‘SecretKey’ which is instantiated in the code, which appears impossible to determine from the decompiled code that I have. Without this, no progress can be made. I’ve asked on StackOverflow for some help. I really need someone experienced in this kind of hacking.

So, not looking good at this stage. Will post any further updates.

1 Like

Thanks for it all so far. I’m really hoping not to pay my way into a connect10 system which will solve this. But it may be the most likely option.

Update:
Glimmer of hope on the horizon!

I’m managed to find the ‘SecretKey’ tucked way in a deep dark corner of the disassembly.
Also this string: “AstralPoolService.BusinessObjects.Chlorinator.ChlorinatorDevice”, “ChlorinatorApp.App”, which gets flipped around, converted to a byte array and a SHA1 hash is calculated from said array.
The hash is then XORed with SecretKey to derive the actual key used for the AES encoding/decoding.

The first attribute that gets read from the chlorinator (characteristic uuid 45000002) is the ‘sessionKey’.

Then the sessionKey gets XORed with the AccessCode (read from the screen of the chlorinator), and the result is AES encrypted using the key mentioned above. This forms the MAC key which is written back to the chlorinator (characteristic uuid 45000003).

Anyway, long story short, I can successfully generate the MAC key from the received sessionKey.
AND, I can successfully decode a received packet (!).

For example, this received attribute response from the chlorinator:

[ MANY CALCULATION STEPS ]

:partying_face:
Anyway, that was a pretty short explanation, but it was quite a few hours of tail chasing.

2 Likes

that’s amazing… great work. question though (from a person who half understands all that you have just stated above) does this mean that each person who will get to play with any tool which may come of this discovery, then have to identify the encryption key somehow?

Next question will be to Viron - why so encrypted… its a chlorinator FFS!!!

The only information that needs to come from the outside world is the Access Code. Everything else is either known, read from the device, or calculated.

and that access code is the one displayed on the unit at the time you first try to link the chlorinator with the device? That’s awesome… great work

Update:

I’ve made a proof-of-concept using Arduino on the esp32, and it was successfully able to connect, exchange keys, fetch the device name and decrypt it (:partying_face:).

I don’t really want to do the whole thing in arduino c++ as it will take me years (theres all the BLE stuff, Wi-Fi stuff, home assistant stuff). So I’m in the process of exploring what I can do with the ESPHome custom components. I’ve got zero experience with ESPHome, so I’m really struggling.

What I want to do is make an ESPHome sensor component, that uses the BLE_Client component, and pulls in a library for the crypto. Will start with some basic experiments and see how it goes.

3 Likes

I really interested in this too!!

Just got into Home assistant and would really like what you guys are developing. I have some knowledge of programming but Bluetooth is quite new to me.

I’ll see if I can find anything to help :wink:

Update:

I’m deep down the rabbit hole on this one.
After some advice on the HA discord bluetooth channel chat, it seems the best path is going to be to implement a custom HA integration, then use a esp32 as a bt proxy (if out of bt range).
So, I’m learning python, and then I’ll learn to make a python module, and then I’ll learn to make a custom HA integration… A voyage of discovery.

So far I’ve managed to make a python script that will connect to the chlorinator using the bleak library, and decrypt a message. That was step one.

Now I need to develop that into a proper module that handles connecting, fetching and decoding all the interesting info and returning it. I’m going to focus on retrieving info for now. Once that’s all working, I’ll hopefully turn my attention to implementing controlling the chlorinator (e.g. off/manual/auto).

Edit: been having issues with my chlorinator spontaneously resetting for quite a while, so it’s in for repairs. Will need to wait to get that back before I can do much…

4 Likes

Thats awesome - let me know if you want a second tester

You’ve made great headway. Are you working with what you are doing through a public or accessible git? Ideally including not just your code but the hardware use and setup documented also?

I would be very happy to test. i have spare ESP32s and a Viron Astral ChlorinatorGo system well within bluetooth range to my HA/RPi. I also have internal and external wifi networks so I could test a few scenarios.

Update:

Progress have been slow. Huge learning curve for me, but have made some small incremental progress:

Next step is to figure how to do the polling, where to save the state, manage the actual connection, and so forth.

4 Likes

You are a hero to many… I wish I knew how to do the things you do. Thank you for all the time and effort you are putting into this.

Haha, save your gratitude for when you have something that actually works! :smiley:

1 Like

Update:
Many hours, little (but not no) progress:


image

3 Likes

It’s still progress! Looking great!

That is amazing. Has it been a steep learning curve?