Hello,
I am looking for a pool monitor device and i found the below device:
Has anyone integrate this with Home Assistant? It is a bluetooth device. I download the app but it does not support any sharing option, mqtt or icloud storage.
Hello,
I am looking for a pool monitor device and i found the below device:
Has anyone integrate this with Home Assistant? It is a bluetooth device. I download the app but it does not support any sharing option, mqtt or icloud storage.
I bought the same floating tester but it’s branded Yinmik. I found that data are exchanged with Bluetooth Low Energy (BLE) between the app and the device. With the Esphome BLE client, I can establish a connection to the device but, for now, I can’t decode the value returned by the device.
With the Bluetooth logging profile on my iPhone and the help of WireShark, I found these steps:
There are two types of values exchanged: real-time values and historical values. When the connection is established, the host (app) sends what seems to be a time synchronization packet to begin the conversation. The floater responds with real-time values. After few seconds, the app asks for the historical values. Following each historical value received, the app asks for another historical value with always the same command until the floater « saids » it was the last one. After, the floater posts the real-time values.
I will post further the commands involved as I’m away of my computer.
So, sorry for the delay.
Here are the steps sniffed with WireShark:
The host (phone) find the device (BLE-YC01). The host sends a time sync packet like “0b0216051511160b03” where 0b02 is the header, 16051511160b is the time (yy-mm-dd hh:mm:ss in hex → May 22th 2022 17:22:11) and 03 is the checksum
The device answer with a packet to confirm the time packet received:
“ff0016051511160bf566fef6fd4fff66faa937fc27fefeffffffff5d44”
ff00 is the header, 16051511160b is the real time, but I don’t understand what is following the time. I know there is a checksum at the end but what is between seems to be encrypted.
Following the time confirmation packet received, the host sends a request for real time data with this command: “010200000003”
The device sends a confirmation packet like : “fea1ffdaffee7fffb3dffaf55fffebfd75feae77fffaefff77ffef5d40”. This confirmation packet always begin with “fea1”
After, the host listens for data from the device. The device sends packet like these: “ffa1fe5af623ffccfc64fefdfd4eff66faa937fc25feffffffffff5745”
“ffa1fcfaf6feff30fe7dfe88fd75ffe0fab835fe2dfeaaffffffff5f51”
“ffa1fcfaf6fcff33fe7dfe89fd75ffe0fab931fe2dfeabffffffff5f55”
“ffa1” header is for real time datas.
If the option is enable in the app, the host will ask for historical datas after few seconds. The host sends the command “02040006”. The device answers with one data in his memory like “fda176fa02f6f0ff60fcfdfe4cfdecf5bfe3d1f1ffefffffffffffdd51”. This packet should contain a timestamp but again, I think it’s encrypted.
The host will ask for historical data with the “02040006” again and again until the device will answer with a packet starting with “f7a1” like : “f7a15ffa23fc3cfc06ffe2fe76fdead5d7d6d9f4feefffffffffff7d01”
After the end of historical datas, the host listens for real time datas until the comm stop.
The above steps are what is involved when the device talks with a host where the Yinmik app is running.
Also, I found that I can connect to the device with any other BLE manager app (ex: nRF Connect) or with the BLE client sensor running on an ESP32 with ESPhome. In these cases, the device only sends real time datas like “ffa1fe72f6a2ff33fc5c5450a820ff4bfaac35fc0dfefaffffffff5710” (same “ffa1” header like above) . As I said before, the datas seem to be encrypted and I’m not able to understand the values.
The box saids it’s a 6-in-1 device as six values is measured :
pH
ORP
Temperature
TDS
EC
Cl (chlorine level)
But, I’m thinking Chlorine is a calculated value as they are 5 sensors tips on the floating device (for EC, the sensor tip has two separated parts). I don’t know if the device calculate the chlorine level by itself or it’s calculated in the app. Same thing for the Celcius/Fahrenheit temperature.
So, this is where I am. I plan to use an ESP32 with ESPhome BLE client to push the values in Home Assistant but I don’t know how to extract these values.
I have just bought the Yieryi device too in addition to another integration I use based on BlueRiott devices. I honestly do not have time at the moment to dig on this, but I have seen a sort of combine effort in between ESPHome and Passive BLE Monitor which seems to include a parsing of the messages received from BLE devices. Again, not time to deep dive and learn how magic it is, but maybe an option to look to.
Out of curiosity what else did you use to sniff the traffic ? I have just bought an NRF52840 to work on the same direction as you and try to decode the messages to integrate it with HA. Next week will give it i try if i find sometime.
Hi,
I’ve just bought the same product and interested in HA integration.
I also try to decode the BLE protocol used, actualy without success.
I found that the “Hold” function lock the BLE caracteristics making them available (withiout changes) for another reader. this could help to make matchin beetween BLE raw values and app values.
I set “Hold” through the android app (or via the buton), save record in the app. connect to the BLE-YC01 device with nRF Connect android app, Read the 0xFF01 service / 0xFF02 Characteristic
I’ll try to have many records with only 1 value change and find data location into the raw bytes.
maybe my experience helps.
BLE raw frames seems to be common on differents Yieryi devices. https://community.home-assistant.io/t/blue-connect-pool-measurements/118901/170
the app still the same with all the connected device they made.
Hi Jimenbar, I use the NRF52840 USB dongle with the associated WireShark profile as seen on this site : Overview | BLE Sniffer with nRF52840 | Adafruit Learning System on my Windows PC. Nothing else required
I tried to decompress the android app to understand the exchange but, seriously, I’m totally lost in the code…
i also tried to reverse engineer the android app, but it was created with flutter, so the actual app code is inside the libapp.so (resources/libs/…/libapp.so)
This seems to be very hard to decompile though
Hello, I am not from HA, but OpenHAB and I have a lot of Esp32 devices using EspHome to integrate with OH using MQTT. I found this topic searching for the BLE-YC01 device, that I just have bought some days ago (I still no have it physically), but very interested to integrate with OH.
Well, the case is that I ask directly to some of the brands of this device, and send me some information, that I would share with all of you, as could be better to develop between more than one!. I think that the best could be integrate with EspHome or C++ in Esp32 devices, and send the data to MQTT.
I don’t know how to share the information, as can’t attach the excel with the information. Probably better to send to interested people? The protocol doesn’t seems very complicated, but I am newbie with BT communications.
Ruben
PD: I post it in the next posts, is not so much, but probably enough
Bluetooth communication protocol
Basic Information
1 Bluetooth name: BEL-9100 i.e. product name(BLE-9100,BLE-Ph01,BLE-9909,…)
2 Service UUID FF01
3 Feature UUID FF02 read and write
read data
1 After the Bluetooth connection is successful, subscribe to the characteristic UUID FF02,
2 Read the characteristic UUID FF02 to return the data
3 The data format is as follows: (The data is a byte array, a total of 24) (requires decryption with deCode(), please refer to the encrpt data form)
serial number data significance
0 1 data
2 Calibration data
1 fixed at 2
2 0 BLE-None, Product name code
12 BLE-9100,
BLE-9100
3 DO(mg/L)Value high 8 bits
4 DO(mg/L)lower 8 bits of value
5 DO(%)Value high 8 bits
6 DO(%)lower 8 bits of value
7
8
9
10
11
12
13 8-bit high temperature value
14 Temperature value lower 8 bits
15 Battery voltage value high 8 bits
16 Battery voltage value lower 8 bits
17 flag bit 0b 0000 0000 7 6 5 4 3 2 1 0
hold reading Backlight status
18
19
20
21
22
23 Check Digit Please refer to the CheckSum form
data input
Write the characteristic UUID FF02 in the format (write does not require encryption)
byte array Control the backlight
0 0x01
1 0x00
2 check code
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"void enCode(uint8 *pValue,uint8 len){
uint8 tmp=0;
uint8 hibit=0;
uint8 lobit=0;
uint8 hibit1=0;
uint8 lobit1=0;
//uint8 bit=0;
//uint8 ecode[8]={1,3,4,5,2,6,3,4};
for(int i=0;i<len-1;i++)
{
tmp=~pValue[i];
hibit=(tmp&0xAA)>>1;
lobit=(tmp&0x55)<<1;
tmp=~pValue[i+1];
hibit1=(tmp&0xAA)>>1;
lobit1=(tmp&0x55)<<1;
pValue[i]=hibit|lobit1;
pValue[i+1]=hibit1|lobit;
}
}"
"void deCode(uint8 *pValue,uint8 len){
uint8 tmp=0;
uint8 hibit=0;
uint8 lobit=0;
uint8 hibit1=0;
uint8 lobit1=0;
//uint8 bit=0;
//uint8 ecode[8]={1,3,4,5,2,6,3,4};
for(int i=len-1;i>0;i--)
{
tmp=pValue[i];
hibit1=(tmp&0x55)<<1;
lobit1=(tmp&0xAA)>>1;
tmp=pValue[i-1];
hibit=(tmp&0x55)<<1;
lobit=(tmp&0xAA)>>1;
pValue[i]=~(hibit1|lobit);
pValue[i-1]=~(hibit|lobit1);
}
}"
uint8 checksum(const uint8* data, uint8 len)
{
uint8 i = 0;
uint8 chksum = 0;
for(i = 0; i < len; i ++)
chksum = chksum^data[i];
return chksum;
}
Hi @RubenKona ,
Suggestion. In order to share the excel you could probalby add an hyperlink in here to your public are of any public cloud you use where you could upload excel and from there any interested folk could download it.
I honestly do not have time (unfortunately) to dig on this and i am not a BLE expert although i am very interested, but i believe that with the above info plus ESPHome BLE functionality plus the lambda funtionality it could be done.
Is not necessary finally to post the Excel, as the above posts with code has all the same information that has the Excel.
I am also not expert in BLE, and with EspHOME lambda, and also don’t have yet the device, but I try to do it when my unit arrive. In any case, maybe some of the above, could dig a little more and find useful this information
Ruben
Hi @RubenKona
Was you able to do some tests ?
I have a similar sensor, the BLE-C600 with more inputs (EC / TDS / SALT / PH / ORP and temp).
I did convert your c++ code to python one and for now i’m only able to find the ORP value on the data received.
Anas
Hi @anasm2010 , can you share your python code? Maybe I can test it with my two floating devices.
Thanks
Here is a script example that can decode the full packet.
For now i’m locking for a solution to disable the auto-turn off function … Any idea?
import asyncio
from bleak import BleakClient
import time
from decodeInt import Messure
address = "C0:00:00:00:8A:D1"
# MODEL_NBR_UUID = "00002a24-0000-1000-8000-00805f9b34fb"
MODEL_NBR_UUID = "0000180D-0000-1000-8000-00805F9B34FB"
Battery_Service = "00002a19-0000-1000-8000-00805f9b34fb"
Vendor_specific = "0000ff10-0000-1000-8000-00805f9b34fb"
"""
00001800-0000-1000-8000-00805f9b34fb (Handle: 1): Generic Access Profile
00001801-0000-1000-8000-00805f9b34fb (Handle: 8): Generic Attribute Profile
0000180a-0000-1000-8000-00805f9b34fb (Handle: 12): Device Information
0000180f-0000-1000-8000-00805f9b34fb (Handle: 31): Battery Service
0000ff01-0000-1000-8000-00805f9b34fb (Handle: 36): Vendor specific
"""
class Messure(object):
def __init__(self, frame : str) -> None:
self.frame = frame
self.packet = self.decode(frame)
self.data = self.packet[0]
self.constant = self.packet[1]
self.product_name_code = self.packet[2]
self.hold_reading = self.packet[17] >> 4
self.backlight_status = (self.packet[17] & 0x0F) >> 3
self.battery = self.decode_position(15)
self.ec = self.decode_position(5)
self.tds = self.decode_position(7)
self.salt_tds = self.decode_position(9)
self.salt_sg = self.decode_position(11) ##TO DO
self.ph = self.decode_position(3)/100
self.orp = self.decode_position(20)
self.temperature = self.decode_position(13)/10
def decode(self, byte_frame : bytes ):
frame_array = [int(x) for x in byte_frame]
size = len(frame_array)
for i in range(size-1, 0 , -1):
tmp=frame_array[i]
hibit1=(tmp&0x55)<<1
lobit1=(tmp&0xAA)>>1
tmp=frame_array[i-1]
hibit=(tmp&0x55)<<1
lobit=(tmp&0xAA)>>1
frame_array[i]=0xff -(hibit1|lobit)
frame_array[i-1]= 0xff -(hibit|lobit1)
return frame_array
def reverse_bytes(self, bytes : list):
return (bytes[0] << 8) + bytes[1]
def decode_position(self,idx):
return self.reverse_bytes(self.packet[idx:idx+2])
def show_values(self):
return_string = f"h=[{self.hold_reading}/bl={self.backlight_status}/B={self.battery}] EC={self.ec:4} TDS={self.tds:4} SALT(TDS)={self.salt_tds:4} SALT(S.G.)={self.salt_sg:4} pH={self.ph:4} ORP(mV)={self.orp:4} Temperature(C)={self.temperature:4} "
return return_string
async def main(address):
async with BleakClient(address) as client:
print(f"Connected: {client.is_connected}")
paired = await client.pair(protection_level=2)
print(f"Paired: {paired}")
old_time = time.time()
print(time.time() - old_time)
while True:
model_number = await client.read_gatt_char(Vendor_specific)
print(Messure(model_number).show_values())
time.sleep(1)
asyncio.run(main(address))
Try to check the saved data buttom and assign an interval, it may keep it alive.
Yes thank you, it seems to work on the app. However, once I switch to the python script it goes off again after 5min. I assume the app is sending a request that force it to stay on … I need to find what.