[Zodiac / 2026 version] Blue Connect Pool / Water Quality Monitor

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.

A ready-to-use HA integration for this sensor

Building on mattsday’s protocol work in this thread (thank you!), I’ve published a custom integration for the 2026 Blue Connect Gold:

GitHub - nledenyi/blueconnect-ble: Local, cloud-free Home Assistant integration for the 2026 Zodiac Blue Connect Gold pool sensor (passive BLE, HACS custom repository) · GitHub (HACS custom repository)

Fully local and passive: no cloud account, no app, no MQTT, no connection to the device. It decodes the 18-byte 0x6A03 advertisement into 6 entities: pH, ORP, temperature, conductivity (µS/cm), salinity (g/L) and battery voltage. Works
through any Bluetooth proxy, including non-connectable Shelly Gen2+ gateways. Auto-discovered, no MAC entry needed (the device rotates its MAC; identity comes from the serial in the advertised name). Per-reading calibration offsets are
supported, like the app.

A few protocol findings on top of the original byte map, validated against a real salt-water pool and cross-checked with the iAqualink+ app (pH exact, ORP within 4 mV):

  • bytes 9-10 are conductivity in µS/cm (raw), and bytes 11-12 are salinity in g/L (÷100)
  • bytes 14-15 look like battery voltage (÷1000), unconfirmed, so that entity ships disabled by default
  • the device also broadcasts a separate 27-byte 0x6A03 beacon that is opaque/rolling and carries no readings; only the 18-byte advert decodes

Full byte map with per-field confidence is in docs/PROTOCOL.md (blueconnect-ble/docs/PROTOCOL.md at fc7b9326328230a42a098eb7ddacf0f760abe886 · nledenyi/blueconnect-ble · GitHub). So far it’s validated on a single unit, so reports from other pools (especially salinity values and
battery) are very welcome on the issue tracker (Issues · nledenyi/blueconnect-ble · GitHub).