Pool Monitor Device Yieryi BLE-YC01

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

Hi,

Here is a functional code for an ESP32 with Esphome associated with Home Assistant. Every 30 minutes, the BLE device is polled for new datas.

time:
  - platform: homeassistant
    id: homeassistant_time
    on_time:
      # Turn on BLE client every 30 minutes for 2 minutes
      - seconds: 0
        minutes: /30
        then:
          - switch.turn_on: ble_switch
          - delay: 2min
          - switch.turn_off: ble_switch


esp32_ble_tracker: #### Stop the active scan ####
  scan_parameters: 
    active: false

######################################################
##                                                  ##
##   To initiate to connection with the BLE device  ##
##                                                  ##
######################################################

ble_client: 
  - mac_address: YO:UR:_A:DD:RE:SS #Use the MAC address of your BLE device
    id: ble_yc01
    on_connect: #### Actions to perform when connecting to the BLE device ####
      then:
      #### Wait until characteristic is discovered ####
        - wait_until:   
            lambda: |-
              esphome::ble_client::BLEClient* client = id(ble_yc01);
            
              auto service_uuid = 0xFF01;
              auto char_uuid = 0xFF02;
              
              //#### When waiting for connection, we extract the available characteristics ####
              esphome::ble_client::BLECharacteristic* chr = client->get_characteristic(service_uuid, char_uuid);
              
              return chr != nullptr;
              
        #### Official connection to the BLE device with the desired characteristic ####
        - lambda: |- 
            ESP_LOGD("ble_client_lambda", "Connected to BLE-YC01");
            
            esphome::ble_client::BLEClient* client = id(ble_yc01);
            
            auto service_uuid = 0xFF01; 
            auto char_uuid = 0xFF02;
            
            esphome::ble_client::BLECharacteristic* chr = client->get_characteristic(service_uuid, char_uuid);
            
            if (chr == nullptr) {
                ESP_LOGW("ble_client", "[0xFF01] Characteristic not found.  State update can not be written.");
            }
        
    on_disconnect:
      then:
        - lambda: |-
            ESP_LOGD("ble_client", "Disconnected from BLE-YC01");
            
######################################################
##                                                  ##
##     Sensors associated with the BLE device       ##
##                                                  ##
######################################################
            
sensor: #### Template sensor as their values are publish from a lambda or the BLE client ####

  - platform: template
    name: "BLE-YC01 EC"
    id: ble_yc01_ec_sensor
    unit_of_measurement: "µS/cm"
    accuracy_decimals: 0
    state_class: measurement
    icon: mdi:water-opacity
    
  - platform: template
    name: "BLE-YC01 TDS"
    id: ble_yc01_tds_sensor
    unit_of_measurement: "ppm"
    accuracy_decimals: 0
    state_class: measurement
    icon: mdi:water-opacity
    
  - platform: template
    name: "BLE-YC01 Temperature"
    id: ble_yc01_temperature_sensor
    unit_of_measurement: "F"
    accuracy_decimals: 1
    state_class: measurement
    device_class: temperature
    
  - platform: template
    name: "BLE-YC01 ORP"
    id: ble_yc01_orp_sensor
    unit_of_measurement: "mV"
    accuracy_decimals: 0
    state_class: measurement
    device_class: voltage
    
  - platform: template
    name: "BLE-YC01 pH"
    id: ble_yc01_ph_sensor
    unit_of_measurement: "pH"
    accuracy_decimals: 2
    state_class: measurement
    icon: mdi:ph

  - platform: template
    name: "BLE-YC01 batterie"
    id: ble_yc01_battery
    unit_of_measurement: "%"
    accuracy_decimals: 0
    state_class: measurement
    device_class: battery
    icon: mdi:battery
    
  - platform: ble_client ####  Sensor required to manage values coming from the BLE device ####
    ble_client_id: ble_yc01
    id: ble_yc01_sensor
    internal: true
    service_uuid: FF01
    characteristic_uuid: FF02
    notify: true
    #### Lambda to decode values and push to the associated sensors ####
    lambda: |-
    
      if (x.size() == 0) return NAN;
      
      //ESP_LOGD("ble_client.receive", "value received with %d bytes: [%.*s]", x.size(), x.size(), &x[0]); // ####  Useful for debugging ####
 
 
      // ### DECODING ###
      uint8_t tmp = 0;
      uint8_t hibit = 0;
      uint8_t lobit = 0;
      uint8_t hibit1 = 0;
      uint8_t lobit1 = 0;
      auto message = x;
      
      for (int i = x.size() -1 ; i > 0; i--) {
        tmp=message[i];
        hibit1=(tmp&0x55)<<1;
        lobit1=(tmp&0xAA)>>1;
        tmp=message[i-1];	
        hibit=(tmp&0x55)<<1;
        lobit=(tmp&0xAA)>>1;
        
        message[i]=~(hibit1|lobit);
        message[i-1]=~(hibit|lobit1);

      }
      
      //ESP_LOGD("ble_client.receive", "value received with %d bytes: [%.*s]", message.size(), message.size(), &message[0]); // #### For debug ####


      // #### Extraction of individual values ####
      auto temp = ((message[13]<<8) + message[14]);
      auto ph = ((message[3]<<8) + message[4]);
      auto orp = ((message[20]<<8) + message[21]);
      auto battery = ((message[15]<<8) + message[16]);
      auto ec = ((message[5]<<8) + message[6]);
      auto tds = ((message[7]<<8) + message[8]);
      
      // #### Sensors updated with new values
      id(ble_yc01_temperature_sensor).publish_state(((temp/10.0) * (9.0/5.0)) + 32.0);
      id(ble_yc01_ph_sensor).publish_state(ph/100.0);
      id(ble_yc01_orp_sensor).publish_state(orp);
      id(ble_yc01_battery).publish_state(battery/45);
      id(ble_yc01_ec_sensor).publish_state(ec);
      id(ble_yc01_tds_sensor).publish_state(tds);
       
      return 0.0; // this sensor isn't actually used other than to hook into raw value and publish to template sensors


switch:  #### To switch on and off the communication with the BLE device ####
  - platform: ble_client
    id: ble_switch
    ble_client_id: ble_yc01
    name: "Enable BLE-YC01"

Here is a way to calculate free chlorine in HA. For now, I can’t figure out how to integrate it directly in Esphome.

The formula comes from raspipool github: https://github.com/segalion/raspipool

sensors:
  - platform: template
    sensors:
      chlore_libre_spa:
        value_template: "{{ ( 0.23 * (1 + 0 / 100 ) * ( 14 - states('sensor.ble_yc01_ph')|float(0.0)) ** ( 1 / (400 - states('sensor.ble_yc01_orp')|float(0.0)) ) * ( states('sensor.ble_yc01_ph')|float(0.0) -4.1) ** ( ( states('sensor.ble_yc01_orp')|float(0.0) - 516)/145) + 10.0 ** ( (states('sensor.ble_yc01_orp')|float(0.0) + states('sensor.ble_yc01_ph')|float(0.0) * 70 -1282) / 40 ) ) |round(1) }}"
        unit_of_measurement: ppm
        icon_template: mdi:react
        friendly_name: "Chlore libre estimé spa"
5 Likes

Be careful to switch off the comm with the BLE device before uploading in the ESP32. The BLE device seems to freeze sometimes if the connection isn’t properly disconnected. You will have to remove the batteries to restart the device… A little bit annoying.

2 Likes

Thank you bro. It Work for me.

1 Like

Great!!!
I’m looking for a orp and ph sensor for my swimming pool, I’m automating everything. do you recomend it? It looks so good but in amazon there are terrible descriptions.
Please let me know.
Thanks

I would personally take a look to the Blueriiot products. I bought the BLE-YC01 just for testing given its price and can see massive differences in the measures even enough I made the calibration (which I am pending of repeating just in case). The Blueriiiot is perfectly integrated with HA too although it is far more expensive. Take a look to the integration and forum and make your own decision. I have been running the Blueriiot for +2y and my pool have never been better. I have a full automation of the pumps for filtering and chemical’s (bleach, etc).

Thanks, I’ve built an automated system with injectors of ph and chrorine,pumps with vvd and also ph sensor but with orp sensor I have some problems and after checking 2 orp electrodes nothing works, i used drobot kit but after several hours working, values get crazy.
This is why i’m looking for something different without invest too much money.
I’ve taked a look blueriot devices but they are quite expensive and the probe need to be changed every 2 years with more than 100€ for this part.

If the values of ble-yc01 are correct and it works at least 1 year it deserves it.

Thanks