Pool Monitor Device Yieryi BLE-YC01

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.

1 Like

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.

1 Like

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.

1 Like

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.

1 Like

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 :frowning:

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											

2 Likes
"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;	
}		

2 Likes

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 :wink:

Ruben

1 Like

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))
2 Likes

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.

2 Likes