BLE GATT Interface¶
Context¶
In A naĂŻve attempt at decoding the pHin BLE GAP Interface, we saw that the pHin uses two types of advertisements:
During an initial period of 90s, it announces itself as “connectable”
After the 90s period, it shifts to “scannable” (i.e. not connectable)
With the unit I was working with, the pHin with the black probe and soft potting, it was not possible to connect. I didn’t take the time to understand the reason for this, assuming that the developers had protected the connection and also because I was confident that all the read-only data I wanted to extract from the probe would be contained in the advertisements.
I’m re-opening this topic now (2 weeks later), because I read that the newly-released pHinished app was capable of setting the pHin monitor in winter mode, and back to normal mode. This implies a transmission of information from the application to the pHin monitor, which can only be achieved by entering the connected mode. This idea is reinforced by the fact that the wintering operation requires the user to use the magnet: it must be connection-related, since trigerring a hardware reset of the microcontroller is the only way to make it return to the 90s connectable period. Since the whole point of this reverse-engineering was to allow people to continue using their pHin monitors as long as possible and since the power consumption is probably much lower in winter mode, I can’t say the reverse is complete until I document how to set the device in winter mode.
Now that I have found a good excuse to re-open this project, here is the current situation:
I’m unable to connect to my pHin monitor for whatever reason
The pHinished app presumably uses a BLE connection to put the device in winter mode. I’m assuming that the pHinished application was developed without any proprietary information from Hayward, so it should be possible to establish a connection to a pHin without any secret information (or, secret information that is reasonably easy to reverse-engineer)
I installed the pHinished application and it turns out that it’s unable to interact with my pHin monitor. Could I be in possession of an older version of pHin which doesn’t support connections? Or did I somehow break something while reverse-engineering the other parts? Is there a specific requirement on the RF part that is specific to the BLE Connected mode which I might have damaged?
Remembering that I still have a second pHin monitor as backup, I decided to start by verifying if connections are possible on that one.
Establishing a BLE Connection on the “blue probe” pHin¶
Replacing the battery on this second pHin was a lot more difficult as the potting material is extremely hard. I managed to hook up a power supply on the battery contacts, but I will definately not attempt to access any other parts of the PCB:

Powering this over a 3V regulated power supply brings up the device immediately in a BLE Scanner, and I was relieved to see that with this one, it’s possible to connect over BLE.
In CySmart, while establishing a BLE Connection, I get the following trace:
[11:34:09:220] : 'Resolve and Set Peer Device BD Address' request sent
[11:34:09:220] : BD Address Type: RANDOM_ADDRESS
[11:34:09:220] : BD Address: E7:B8:BE:71:B0:C7:00:00
[11:34:09:222] : 'Command Status' event received
[11:34:09:222] : Status: BLE_STATUS_OK
[11:34:09:222] : 'Resolve and Set Peer Device BD Address Response' event received
[11:34:09:222] : Status: 0x01
[11:34:09:223] : 'Command Complete' event received
[11:34:09:223] : Status: BLE_STATUS_OK
[11:34:09:227] : 'Establish Connection' request sent
[11:34:09:227] : BD Address Type: RANDOM_ADDRESS
[11:34:09:227] : BD Address: E7:B8:BE:71:B0:C7:00:00
[11:34:09:230] : 'Command Status' event received
[11:34:09:230] : Status: BLE_STATUS_OK
[11:34:09:250] : 'Establish Connection Response' event received
[11:34:09:864] : 'Enhanced connection complete' event received
[11:34:09:864] : Status: 0x00
[11:34:09:864] : Role: 0x00
[11:34:09:864] : BD Address Type: RANDOM_ADDRESS
[11:34:09:864] : BD Address: E7:B8:BE:71:B0:C7:00:00
[11:34:09:864] : Local resolvable address: 00:00:00:00:00:00:00:00
[11:34:09:864] : Peer resolvable address: 00:00:00:00:00:00:00:00
[11:34:09:864] : Connection Interval: 8.75 ms
[11:34:09:864] : Slave Latency: 0
[11:34:09:864] : Supervision Timeout: 100 ms
[11:34:09:868] : 'Command Complete' event received
[11:34:09:868] : Status: BLE_STATUS_OK
The trace continues below, but I’m stopping here to note that up until this point, the trace is identical to what I had with the other pHin. At this point, the connection attempt to the other pHin model is terminated (and yet there are still common parts with the trace that follows):
// Trace from the other pHin model, which fails to connect
[13:24:24:794] : 'Connection Terminated Notification' event received
[13:24:24:794] : Reason: CONNECTION_FAILED_TO_BE_ESTABLISHED
I might come back to the previous model and attempt to understand what causes this disconnection, but for now I will continue working on the pHin that I can connect to without issues. Here is the remainder of the trace of the working pHin monitor::
[11:34:09:868] : 'Get local device security keys' request sent
[11:34:09:872] : 'Data length changed notification' event received
[11:34:09:872] : Connection maximum Tx octet: 27
[11:34:09:872] : Connection maximum Tx time: 328 µs
[11:34:09:872] : Connection maximum Rx octet: 27
[11:34:09:872] : Connection maximum Rx time: 328 µs
[11:34:09:885] : 'Command Status' event received
[11:34:09:885] : Status: BLE_STATUS_OK
Here are some keys generated at the establishment of the connection. It seems that the connection uses “Just Works”, without authentification, since we didn’t need to provide any keys:
[11:34:09:885] : 'Get local device security keys response' event received
[11:34:09:885] : Key flags: INITIATOR_ENCRYPTION_INFORMATION, INITIATOR_IDENTITY_INFORMATION, INITIATOR_SIGNATURE_KEY, RESPONDER_ENCRYPTION_INFORMATION, RESPONDER_IDENTITY_INFORMATION, RESPONDER_SIGNATURE_KEY
[11:34:09:885] : Long Term Key (LTK): [D6:93:E8:A4:23:55:48:99:1D:77:61:E6:63:2B:10:8E]
[11:34:09:885] : Encrypted Diversifier (EDIV) and Random Number: [99:1F:26:1E:F6:09:97:2E:AD:7E]
[11:34:09:885] : Identity Resolving Key (IRK): [0A:2D:F4:65:E3:BD:7B:49:1E:B4:C0:95:95:13:46:73]
[11:34:09:885] : Identification Address: [0x00A050504320, PUBLIC_ADDRESS]
[11:34:09:885] : Connection Signature Resolving Key (CSRK): [90:D5:06:95:92:ED:91:D7:A8:9E:2C:DC:4A:93:5B:F9]
[11:34:09:886] : 'Command Complete' event received
[11:34:09:886] : Status: BLE_STATUS_OK
[11:34:09:892] : 'Set OOB data' request sent
[11:34:09:892] : OOB flag: Disable
[11:34:09:892] : OOB Key: [00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00]
[11:34:09:892] : OOB Data: [00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00]
[11:34:09:895] : 'Command Status' event received
[11:34:09:895] : Status: BLE_STATUS_OK
[11:34:09:896] : 'Command Complete' event received
[11:34:09:896] : Status: BLE_STATUS_OK
Here, MTU is requested and the default value of 0x17 (23) is returned:
[11:34:09:906] : 'Exchange GATT MTU Size' request sent
[11:34:09:906] : MTU: 0x0200
[11:34:09:910] : 'Command Status' event received
[11:34:09:910] : Status: BLE_STATUS_OK
[11:34:09:920] : 'Exchange GATT MTU Size Response' event received
[11:34:09:920] : MTU: 0x0017
[11:34:09:920] : 'Command Complete' event received
[11:34:09:920] : Status: BLE_STATUS_OK
Finally, the BLE Central reads characteristic UUID 0x2A00, which is the standard 16-bit UUID for the “Device Name”. The value returned is the hex representation of the ASCII characters “pHin71BE”, our pHin’s device name:
[11:34:09:933] : 'Read using Characteristic UUID' request sent
[11:34:09:933] : Start Handle: 0x0001
[11:34:09:933] : End Handle: 0xFFFF
[11:34:09:933] : UUID: 0x2A00
[11:34:09:938] : 'Command Status' event received
[11:34:09:938] : Status: BLE_STATUS_OK
[11:34:09:969] : 'Read using Characteristic UUID Response' event received
[11:34:09:969] : Characteristic value 1:
[11:34:09:969] : --------------------------------
[11:34:09:969] : Attribute Handle: 0x0003
[11:34:09:969] : Value: [70:48:69:6E:37:31:42:45]
[11:34:09:972] : 'Command Complete' event received
[11:34:09:972] : Status: BLE_STATUS_OK
Exactly 2 minutes after the connection is established, the pHin monitor terminates the connection:
[11:36:08:655] : 'Connection Terminated Notification' event received
[11:36:08:655] : Reason: REMOTE_USER_TERMINATED_CONNECTION
Making the peripheral force a disconnection after a certain delay is good practice:
It saves energy, as maintaining this BLE Connection could be more energy demanding than sending periodic Advertisements (this depends on the GATT connection parameters, which we will be able to analyse a bit later).
It ensures that the device is freed from the connection, allowing it to send periodic BLE Advertisements. This prevents any BLE Central (malicious or not) to monopolize the pHin monitor.
BLE Connection Parameters¶
Now that we are able to establish a connection, let’s look at the connection parameters. With CySmart, we notice the following connection parameters are established:
Parameter |
Value |
---|---|
Connection Interval |
8.75 ms |
Connection Latency |
0 |
Supervision Timeout |
100 ms |
This is the first time I actually use CySmart to look at the connection parameters. I assume that by “Connection Latency”, they mean “Slave Latency”. If these settings are the actual settings used, we can see that there has not been any attempt to save power during the BLE connection. Also, I note that we attempting to connect to the pHin monitor, I didn’t receive any request from the pHin to update the connection parameters. The pHin did not attempt to request parameters which are different from the default ones and which could result in a huge power saving. This might have been seen as unnecessary by the developers, since the connection is closed after 120 seconds, and a connection is very rarely used during the life of the product.
Let’s use our sniffer connected wireshark to confirm this. Here is a capture starting from the last BLE Advertisement sent by the pHin just before the connection, up until the connection is established and a few connection PDUs are exchanged:

We can see that the pairs of Master –> Slave and Slave –> Master packets are exchanged at roughly 8-10ms intervals.
To confirm that these are indeed connection parameters selected by the initiator, we modified the default connection parameters in the CySmart settings and estblished a new connection with the pHin monitor.
I selected:
Connection Interval Minimum value: 20ms (was 7.5ms)
Connection Interval Maximum value: 40ms (was 10ms)
Supervision Timeout: 500ms (was 100ms)
After establishing a connection, the actual parameters used followed the requested parameters:
Parameter |
Value |
---|---|
Connection Interval |
30 ms |
Connection Latency |
0 |
Supervision Timeout |
500 ms |
A wireshark capture confirms that the pHin monitor accepted these new parameters. We can see the exchanges are less frequent, at time intervals oscillating around 30ms:

To conclude:
The pHin does not impose strict connection parameters
The pHin does not exploit any slave latency, which would allow it to omit a certain number of consecutive connection intervals, thus reducing power
There has been no effort at optimising power during the connection, but it makes sense: there is no need to optimize a mode which is seldom used.
BLE Services and Characteristics¶
Once connected, we discover the following services and characteristics. These are listed in the order in which they appear in the GATT Attribute Table.
To understand how an Attribute Table is constructed, it can be useful to read a simplified version of the BLE standard such as explained by certain manufacturers. NordicSemi’s documentation is a good starting point.
Generic Access Service (0x1800)¶
This is a standard BLE service, mandatory for all GATT servers. There are 2 mandatory characteristics in this profile:
Device Name = “pHin71BE”
Appearance =
0x0000
= “Unknown”
We see also a third, optional characteristic:
Peripheral Preferred Connection Parameters:
40:01:08:02:00:00:90:01
.
So it looks like the device does have some preferred connection parameters. Let’s try to decode them according to the official BLE specifications. This is an 8-byte field formatted as follows:
Minimum connection interval (2 bytes, little-endian, multiple of 1.25ms): 0x0140 * 1.25ms = 320 * 1.25ms = 400ms.
Maximum connection interval (2 bytes, little-endian, multiple of 1.25ms): 0x0208 * 1.25ms = 520 * 1.25ms = 650ms.
Slave latency (2 bytes) = 0x0000
Connection Supervision Timeout (2 bytes, little-endian, multiple of 10ms): 0x0190 * 10ms = 400 * 10ms = 4s.
So it appears that the peripheral does have preferred connection parameters, but these are not used when we connect using CySmart.
Handle |
UUID |
UUID Description |
Value |
Properties |
---|---|---|---|---|
0x0001 |
0x2800 |
Primary Service Declaration |
00:18 |
0x00 |
0x0002 |
0x2803 |
Characteristic Declaration |
02:03:00:00:2A |
0x00 |
0x0003 |
0x2A00 |
Device Name |
“pHin71BE” |
0x02 |
0x0004 |
0x2803 |
Characteristic Declaration |
02:05:00:01:2A |
0x00 |
0x0005 |
0x2A01 |
Appearance |
00:00 |
0x02 |
0x0006 |
0x2803 |
Characteristic Declaration |
02:07:00:04:2A |
0x00 |
0x0007 |
0x2A04 |
Peripheral Preferred Connection Parameters |
40:01:08:02:00:00:90:01 |
0x02 |
Generic Attribute Service (0x1801)¶
This service provides a characteristic which allows the GATT Server (the pHin) to announce when the attribute table has changed during a connection. I doubt that this is actually used by the pHin.
Handle |
UUID |
UUID Description |
Value |
Properties |
---|---|---|---|---|
0x0008 |
0x2800 |
Primary Service Declaration |
01:18 |
0x00 |
0x0009 |
0x2803 |
Characteristic Declaration |
20:0A:00:05:2A |
0x00 |
0x000A |
0x2A05 |
Service Changed |
0x20 |
|
0x000B |
0x2902 |
Client Characteristic Configuration |
0x00 |
Connected Yard, Inc. Service (0xFE63)¶
This service uses a registered 16-bit UUID of 0xFE63 dedicated to Connected Yard, Inc. This is where the custom pHin characteristics lie, and this is where we need to look for the characteristic to send commands to the pHin.
There are several 128-bit UUID characteristics, all sharing a common format:
3206XXXX-76FD-4996-952B-2A1BE2CB9450
All characteristics share the same format except for the 16-bit XXXX
section, which is different for each characteristic. In the table below, the characteristic UUIDs are given by their variable 16-bit part, but it is implicit that they are surrounded by the other fixed 112 bits.
Handle |
UUID |
UUID Description |
Value |
Properties |
---|---|---|---|---|
0x000C |
0x2800 |
Primary Service Declaration |
63:FE |
0x00 |
0x000D |
0x2803 |
Characteristic Declaration |
0A:0E:…:27:15:06:32 |
0x00 |
0x000E |
0x1527 |
0x0A |
||
0x000F |
0x2901 |
Characteristic User Description |
0x00 |
|
0x0010 |
0x2803 |
Characteristic Declaration |
08:11:…:2C:15:06:32 |
0x00 |
0x0011 |
0x152C |
0x08 |
||
0x0012 |
0x2901 |
Characteristic User Description |
0x00 |
|
0x0013 |
0x2803 |
Characteristic Declaration |
08:14:…:2D:15:06:32 |
0x00 |
0x0014 |
0x152D |
0x08 |
||
0x0015 |
0x2901 |
Characteristic User Description |
0x00 |
|
0x0016 |
0x2803 |
Characteristic Declaration |
12:17:…:2E:15:06:32 |
0x00 |
0x0017 |
0x152E |
0x12 |
||
0x0018 |
0x2901 |
Characteristic User Description |
0x00 |
|
0x0019 |
0x2902 |
Client Characteristic Configuration |
0x00 |
|
0x001A |
0x2803 |
Characteristic Declaration |
02:1B:…:30:15:06:32 |
0x00 |
0x001B |
0x1530 |
0x02 |
||
0x001C |
0x2901 |
Characteristic User Description |
0x00 |
|
0x001D |
0x2803 |
Characteristic Declaration |
02:1E:…:31:15:06:32 |
0x00 |
0x001E |
0x1531 |
0x02 |
||
0x001F |
0x2901 |
Characteristic User Description |
0x00 |
|
0x0020 |
0x2803 |
Characteristic Declaration |
02:21:…:32:15:06:32 |
0x00 |
0x0021 |
0x1532 |
0x02 |
||
0x0022 |
0x2901 |
Characteristic User Description |
0x00 |
Let’s look at the characteristics in more detail in the following subsections.
Characteristic 0x1527¶
Access Modes: Read, Write
Initial Value:
00:00:00:00:00:00:00:00
Characteristic 0x152C¶
Access Modes: Write
Characteristic 0x152D¶
Access Modes: Write
Characteristic 0x152E¶
Access Modes: Read, Notify
Initial Value:
FB:02:21:00:00:FB:FE:10
Activating notifications on this characteristic does not yield any unsollicited notifications. It’s possible that notifications are sent here as a response to another kind of interaction.
Characteristic 0x1530¶
Access Modes: Read
Initial Value:
04:00:00:00:42:00:00:00:02:00:00:00:00:00:00:00:00:00:00:00:00:00
Characteristic 0x1531¶
Access Modes: Read
Initial Value:
00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
Characteristic 0x1532¶
Access Modes: Read
Initial Value:
00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
Device Information Service (0x180A)¶
This contains read-only information about the device.
Handle |
UUID |
UUID Description |
Value |
Properties |
---|---|---|---|---|
0x0023 |
0x2800 |
Primary Service Declaration |
0A:18 |
0x00 |
0x0024 |
0x2803 |
Characteristic Declaration |
02:25:00:29:2A |
0x00 |
0x0025 |
0x2A29 |
Manufacturer Name String |
“Connected Yard Inc.” |
0x02 |
0x0026 |
0x2803 |
Characteristic Declaration |
02:27:00:24:2A |
0x00 |
0x0027 |
0x2A24 |
Model Number String |
“CY-PM1510-A1” |
0x02 |
0x0028 |
0x2803 |
Characteristic Declaration |
02:29:00:25:2A |
0x00 |
0x0029 |
0x2A25 |
Serial Number String |
“71BEB8E7CF9F87B0” |
0x02 |
0x002A |
0x2803 |
Characteristic Declaration |
02:2B:00:27:2A |
0x00 |
0x002B |
0x2A27 |
Hardware Revision String |
“3” |
0x02 |
0x002C |
0x2803 |
Characteristic Declaration |
02:2D:00:26:2A |
0x00 |
0x002D |
0x2A26 |
Firmware Revision String |
“1.7.15” |
0x02 |
0x002E |
0x2803 |
Characteristic Declaration |
02:2F:00:28:2A |
0x00 |
0x002F |
0x2A28 |
Software Revision String |
“1.7.15; Build 87; dKRs” |
0x02 |
Brute-Forcing the Write Characteristics¶
Seeing that there are 2 characteristics which are write-only, it seems like good candidates for characteristics which accept user commands. A BLE characteristic can accept upto 20 bytes in this mode, because the MTU established in this connection is 23 bytes (this is default behavior for BLE). However, it is up to the GATT Server (pHin) to define how many actual byte can be written, and this can be any value upto 20 bytes.
I started by checking how many bytes can be sent on each of the write-only characteristics:
0x152C
accepts 1, 2, 3 and 4 bytes. Anything above 4 bytes results in an “Invalid Attribute Length”.0x152D
accepts only 1 byte. Anything above 1 byte results in an “Invalid Attribute Length”.
This is good news: if we’re going to be brute-forcing values, we know that we have only 255 values to test on 0x152D. This is definately where I’m going to start.
To automate this, I use the following setup:

A short Python script on the test controller automates the interactions with the Power Supply and the BLE Dongle. The metholology is as follows:
Turn on the pHin power supply
Scan over BLE and connect
Write the next value to the 0x152D characteristic
Turn off and on the pHin power supply to trigger a reset
Wait 100 seconds for the initial 90-second connectable period to expire
Scan over BLE and see if the pHin is still advertising
Append the result to a log file
If the pHin is still advertising, it means it has not been put in winter mode. On the other hand, if the pHin is not advertising after those 100 seconds, it could very well have been passed in winter mode.
The test takes about 120 seconds, so it takes 8.5 hours to test all the 255 possible values of the 0x152D characteristic. This is already quite long, but worth a try. If it fails, it is worthwhile to do the same thing on the 0x152C characteristic (writing 1 byte only), but it would be impossible to test all the possible 2-byte, 3-byte and 4-byte combinations on 0x152C, since it accepts upto 4 bytes.
Brute Force Results: 0x152D¶
After testing the entire 255 possible values for the 0x152D, I was disappointed to see that none of the write operations resulted in a winter mode. Here is the full log file:
Brute Force Results: 0x152C¶
I set the brute-forcing script to the 0x152C characteristic, using the same methodology as for the previous charactersitic. I checked the first few values after starting the script, and I directly found something interesting. Here is the beginning of the log:
0:1
1:1
2:1
3:1
4:1
5:0
6:0
7:0
8:0
9:0
10:0
So it seems that after value 0x05, the advertisements are not active anymore. I stopped the script at this point and continued with manual tests. For some unknown reason, when I stopped the script I was unable to access the CY5677 BLE Dongle and had to revert to reprogramming the dongle and reboot my computer. Thinking I might have just had bad luck with the dongle, I re-ran the script again from the start, and saw the following initial results:
0:0
1:0
2:0
3:0
4:1
5:0
6:0
So it seems that there was something persistent after writing 0x05, which got reset when writing 0x04. I played around manually, repeating writing 0x04 and 0x05 manually and looking at the device’s behavior, and what I observe now seems to be the opposite of what the script outputs:
After writing 0x04 on 0x125C, the pHin monitor’s BLE Interface turns off after the initial 90s period
After writing 0x05 on 0x125C, the pHin monitor is back to normal advertising after the initial 90s period
I checked my scripts again to try to understand why I was getting the opposite, but I didn’t manage to spot any errors. I’m going to trust the manual tests…
Conclusion¶
Our brute-forcing of the 3206125C-76FD-4996-952B-2A1BE2CB9450
characteristic show that the values 0x04
and 0x05
are important. Manually testing these values brings me to the following conclusion:
Write
0x04
to3206125C-76FD-4996-952B-2A1BE2CB9450
to set the pHin monitor in winter mode.Write
0x05
to3206125C-76FD-4996-952B-2A1BE2CB9450
to set the pHin monitor back to normal mode.