The near-complete, decoded pHin BLE Interface¶
Summary¶
As a result of this study, here is an almost complete, stand-alone description of the BLE Interface.
This is still a work in progress. So far, it is possible to do a BLE Scan (GAP) and decode the BLE Advertisments to retrieve:
pH
ORP
Temperature (with maybe a small bug that needs to be solve)
Battery Voltage (which can give us an idea of the residual energy)
Measurement Sequence number
A Unique ID
There are some fields which I have not decoded in the payload, which are probably unrelated to the pool measurements.
I have also found a way to set the device to winter mode, and back to normal mode, which makes use of a BLE Connection (GATT).
BLE Behavior¶
During the first 90s after booting¶
The pHin monitor is a connectable BLE peripheral during its first 90s of execution. It sends BLE Advertisements periodically, responds to Scan Requests with a Scan Response, and accepts BLE connections.
Note
The pHin monitor can be forced to reboot by bringing a magnet close to the water drop on the casing of the monitor.
The GATT mode might be used by the original pHin application for a number of maintenance-related operations. What I have managed to reverse engineer so far for the GATT mode, is:
Writing
0x04
to the3206125C-76FD-4996-952B-2A1BE2CB9450
characteristic sets the pHin monitor in winter mode.Writing
0x05
to the3206125C-76FD-4996-952B-2A1BE2CB9450
characteristic sets the pHin monitor back to normal mode.
The difference between winter mode and normal mode comes after the initial 90s period.
Beyond the 90s mark¶
After the first minute and a half:
When in winter mode, the pHin monitor switches off its BLE Interface (and probably the microcontroller itself enters the lowest possible sleep mode)
When in normal mode, the pHin monitor becomes a scannable, non-connectable BLE peripheral. It send BLE Advertisements periodically, and responds to Scan Requests with a Scan Response. It does not use the GATT protocol: there is no more connected mode.
You can download a wireshark dump of a BLE Advertisement and BLE Scan Reponse here:
Note
the BLE Advertise wireshark capture was made with the sensors disconnected from the pHin monitor. It is provided only for documentating the general format of the packet. Don’t expect to get anything meaningful from the sensor data in the Manufacturer Specific Data in this capture.
The general format of each packet is described in the following sections.
General GAP-mode Packet Formats¶
In GAP mode, we have two types of packets:
BLE Advertisements. This is sent periodically by the pHin monitor.
BLE Scan Responses. This is sent by the pHin monitor when it detects that there is a BLE Central performing a scan.
BLE Advertisement Format¶
The BLE Advertisement packet format is described below.
pHin BLE Advertisement Packet - General Format |
||||
---|---|---|---|---|
Field Name |
Offset |
Length |
Value |
|
Flags |
Option Len |
0 |
1 |
0x02 |
Option Type |
1 |
1 |
0x01 |
|
Option Data |
2 |
1 |
0x06 |
|
Service UUID |
Option Len |
3 |
1 |
0x03 |
Option Type |
4 |
1 |
0x03 |
|
Option Data |
5 |
2 |
0xFE63 |
|
Manufacturer Specific Data |
Option Len |
7 |
1 |
0x17 |
Option Type |
8 |
1 |
0xFF |
|
Option Data |
9 |
22 |
Custom |
BLE Scan Response Format¶
The Scan Response packet is formatted as follows.
pHin BLE Scan Response Packet - General Format |
||||
---|---|---|---|---|
Field Name |
Offset |
Length |
Value |
|
Shortened Device Local Name |
Option Len |
0 |
1 |
0x09 |
Option Type |
1 |
1 |
0x08 |
|
Option Data |
2 |
1 |
“pHin1234” |
This Shortened Device Local Name is what appears as Device Name when you perform a BLE Scan with any smartphone / BLE Scanning software. It contains 8 ASCII-encoded characters, formatted as:
pHin1234
Where pHin
is a fixed prefix, and 1234
are 2 middle bytes of the Bluetooth MAC Address in hex-string. For example, if the Bluetooth Address is dc:52:a0:87:69:79
, then the Device Name is pHinA087
.
Note
notice that the hex-string conversion uses all-caps for the A-F characters.
This identifier:
Can be used to differentiate one pHin from another nearby pHin
Cannot be used to uniquely identify the pHin among all existing pHin monitors (for that, see the Bluetooth Address field in the Manufacturer Specific Data Option)
Manufacturer Specific Data Option¶
The Manufacturer Specific Data Option, part of the BLE Advertisement Format, contains all the custom data related to the pHin and the sampled pool water.
To facilitate the presentation, we represent the 22 bytes of the Manufacturer Specific data by groups of 4 bytes. Each byte is further divided into 2 parts, since a lot of fields are represented using 3 half-bytes (12 bits).
Here is the specification (click to enlarge):

In the following sections, we see how to extract useful information from the packet.
Let’s first review the notation used to designate bytes and bits in the manufacturer data. Each row of the mapping contains 4 bytes.
To designate fields related to the start of the packet, I used the notation BN[a:b]
where:
N is the offset from the start of the byte (starts at 0)
a is the most significant bit of Byte N that we are interested in
b is the least significant bit of Byte N that we are interested in.
For example, to designated the 4 most significant bits of Byte 11, we can write B11[7:4]
.
To reconstruct a decoded value, we often need to fetch information from different parts of the packet. Let’s take the pH as example:
The 12-bit decoded VPH field can be written as
B7[3:0] | B6[7:0]
This literally means that, to reconstruct VPH:
Take the 4 least significant bits of Byte 7 and shift them 8 bits to the left
Take the full Byte 6 and apply a bitwise-OR operation to the previous result
In C, one could decode it as:
uint16_t ph = ((buffer[7] & 0x0f) << 8) | buffer[6];
Manufacturer ID¶
The manufacturer ID is a constant value. It is the Connected Yard Inc. BLE Manufacturer ID (0x02E7), encoded in little endian (E7 02
). It is located in Bytes 0 and 1.
pH¶
First, recover the probe ADC Voltage (VPH) and the Probe Bias Voltage (VCOMMON). Each of these is an unsigned 12-bit integer.
Read VPH from the above table =
B7[3:0] | B6[7:0]
. We call thisRead VCOMMON from the above table =
B12[7:0] | B11[7:4]
. We call this
The pH Probe can be found by subtracting these values:
To convert to the intrinsic pH value on a scale from 0 to 14, since we don’t have any calibration data we must assume the following parameters:
pH7 Offset = 0mV (i.e. Y-intersect = 414.12mV)
Sensitivity = -59.16mV/pH
To compute the intrinsic value without calibration, we can use this simple linear formula:
ORP¶
The probe ORP value can be recovered directly from the packet as an unsigned 12-bit integer:
Read VORP from the above table:
B8[7:0] | B7[7:4]
Since we don’t have calibration data, there is no other transformation required. Should calibration data be available, it is usually a 1-point calibration taking the form of a fixed offset that must be added to the measured value.
Temperature¶
- Compute the thermistor’s resistance, in Ohms
First, compute the value in Ohm, using the following method:
Read
from the above table:
B2[7:0] | B3[3:0]
as an unsigned 12-bit integerRead
from the above table:
B4[7:0] | B3[7:4]
as an unsigned 12-bit integer
Then use the voltage divider formed by
and
to recover
:
- Convert from Ohms to Kelvins
We can finally use the Beta-model of the thermistor to compute the temperature in °C. We use the following thermistor parameters:
The temperature in Kelvin is given by:
Where:
It should then be converted to
°C
or°F
as needed by the application.
Battery Voltage¶
The battery voltage is given in millivolts, as a 12-bit unsigned integer.
Read VBATT from the above table =
B11[3:0] | B10[7:0]
.
This can be translated to a more user-friendly battery indicator. The battery used is the CR2450 3V coin cell, which can be approximated to be 100% at or above 3V, and 0% at or below 2.5V. The discharge curve is not linear, as you can see from the datasheet. I don’t recommend attempting to convert this value to a “% remaining”, but simply reporting it on a gross scale from 0 to 5, or even 0 to 3, mapping a certain range of voltage to an interval in a custom manner that I won’t attempt to propose here.
Sequence Number¶
There is a 1-byte field that is incremented each time a new measurement occurs. Although we didn’t leave a sniffer running for long enough, we assume that this field cycles back to 0 after reaching 255. It is not meant to uniquely identify a measurement, it serves simply to allow the BLE Scanner to differentiate successive measurements without having to decode the packet further.
This sequence number is located in Byte 9.
Bluetooth Address¶
The Bluetooth Address is contained in Bytes 14 to 19. This can be used to uniquely identify the pHin among all the existing pHin monitors, worldwide. You might wonder why the Bluetooth Address needs to be sent at application level, when it is already part of the stacked protocol layers. The reason for that comes from iOS: Apple decided to block applications in iOS from accessing the Bluetooth Address of a remote BLE Peripheral.