Note: This refers to the 2026 Zodiac Blue Connect Gold which has a rechargeable battery and wifi. It is not the same device or protocol as the original - there is a thread for that here.
Note 2: I haven't tried the silver model - I expect it might be the same, but I only have the gold.
Background: I bought one of these devices assuming I could integrate it with HA with the existing addon. However, it was quickly obvious that this new generation is not compatible with the old. It uses a different app and has a completely different Bluetooth protocol.
Good news: I've reverse engineered the new BLE protocol and it's much simpler than the previous generations. It works entirely over passive bluetooth - no connection, no pairing, GATT access, or cloud required.
Protocol
All multi-byte fields are 16-bit big-endian: value = (high << 8) | low. Offsets are into the manufacturer data, i.e. the bytes after the 03 6A company ID.
| Offset | Field | Decode | Confirmed against |
|---|---|---|---|
| 0–1 | Measurement counter | raw | +X per new measurement |
| 2 | unknown | — | varies non-monotonically (likely checksum/nonce) |
| 3–4 | Temperature °C | ÷ 100 | 34.5 / 30.3 / 30.4 / 33.3 |
| 5–6 | pH | ÷ 10 | 7.3 / 7.5 / 8.8 |
| 7–8 | ORP (redox) mV | raw | 455 / 520 / 659 / 748 |
| 9–10 | Salinity ppm | raw | 709 / 857 / 1572 |
| 11–13 | conductivity / derived | — | tracks salinity; not fully decoded |
| 14–15 | Battery mV | ÷ 1000 → V | ~3.6 V across all captures |
| 16–17 | tail | — | byte 17 constant 0x0C; byte 16 varies |
Example payload: 3C 19 E3 0B E2 00 49 02 0B 02 C5 00 1F 2F 0E 0E B0 0C yields:
- Counter
0x3C19= 15385 - Temp
0x0BE2= 3042 ÷ 100 = 30.42 °C - pH
0x0049= 73 ÷ 10 = 7.3 - ORP
0x020B= 523 mV - Salinity
0x02C5= 709 ppm - Battery
0x0E0E= 3598 ÷ 1000 = 3.598 V
I am confident in the temperature, pH, ORP, and Salinity measurements. I am uncertain on the battery side as I can't easily test that.
ESPHome Config
I used an M5Atom plugged in next to the tub. Mine's monitoring a hot tub so I set it up for that. It should be as simple as dropping this onto your device, sticking it nearby, and the readings should come in more or less right away. The device appears to run a test every hour or so but sends a small ping much more frequently which always contains the latest readings. Nifty!
substitutions:
name: hot-tub-bt-proxy
friendly_name: Hot Tub Bluetooth Proxy
# Battery alarm thresholds (volts). Tune these after watching one cycle:
# note the reading straight off a full charge, and when the app flags low.
batt_low_v: "3.40" # trip "Battery low" at or below this
batt_ok_v: "3.70" # clear it once charged back above this (hysteresis gap)
esphome:
name: ${name}
friendly_name: ${friendly_name}
# Sub-device so the readings appear as their own "Hot Tub" device in HA,
# not buried under the proxy. Needs ESPHome 2025.5 or newer.
devices:
- id: hot_tub
name: Hot Tub
esp32:
board: m5stack-atom
framework:
type: arduino
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
platform: esphome
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# --- The Bluetooth Proxy Magic ---
esp32_ble_tracker:
scan_parameters:
interval: 1100ms
window: 1100ms
active: true
on_ble_advertise:
- then:
- lambda: |-
// Blue Connect broadcasts its readings in manufacturer data
// under company ID 0x6A03 (27139). We match on that rather than
// the MAC, so it survives the device's random address changing.
for (auto data : x.get_manufacturer_datas()) {
if (data.uuid == esp32_ble_tracker::ESPBTUUID::from_uint16(0x6A03)) {
auto d = data.data;
// Readings live in the first 11 bytes after the company ID,
// whether or not the device's name is appended afterwards.
if (d.size() >= 11) {
// All fields are 16-bit big-endian: (high << 8) | low
id(ht_counter).publish_state((d[0] << 8) | d[1]);
id(ht_temp).publish_state(((d[3] << 8) | d[4]) / 100.0f);
id(ht_ph).publish_state(((d[5] << 8) | d[6]) / 10.0f);
id(ht_orp).publish_state((d[7] << 8) | d[8]);
id(ht_salt).publish_state((d[9] << 8) | d[10]);
}
if (d.size() >= 16) {
// Battery voltage in mV (~3.6 V on a healthy lithium cell)
id(ht_batt_v).publish_state(((d[14] << 8) | d[15]) / 1000.0f);
}
}
}
# Forward everything else to Home Assistant as usual
bluetooth_proxy:
active: true
sensor:
- platform: template
name: Temperature
id: ht_temp
device_id: hot_tub
device_class: temperature
unit_of_measurement: "°C"
state_class: measurement
accuracy_decimals: 2
icon: mdi:pool-thermometer
- platform: template
name: pH
id: ht_ph
device_id: hot_tub
# If ESPHome rejects "ph" on your version, just delete this device_class line.
device_class: ph
state_class: measurement
accuracy_decimals: 1
icon: mdi:ph
- platform: template
name: ORP
id: ht_orp
device_id: hot_tub
device_class: voltage
unit_of_measurement: mV
state_class: measurement
accuracy_decimals: 0
icon: mdi:alpha-v-circle
- platform: template
name: Salinity
id: ht_salt
device_id: hot_tub
unit_of_measurement: ppm
state_class: measurement
accuracy_decimals: 0
icon: mdi:shaker-outline
- platform: template
name: Measurement counter
id: ht_counter
device_id: hot_tub
state_class: measurement
accuracy_decimals: 0
entity_category: diagnostic
icon: mdi:counter
- platform: template
name: Battery voltage
id: ht_batt_v
device_id: hot_tub
device_class: voltage
unit_of_measurement: V
state_class: measurement
accuracy_decimals: 3
entity_category: diagnostic
icon: mdi:battery
Not Tested
I haven't tested the silver version - if anyone has that would be good to see if it's the same. As mentioned before I don't know battery levels. I don't have a salt water tub and although salinity seems to be right it would be good to verify this.