Pool Monitor Device Yieryi BLE-YC01

He uses this one:

1 Like

Hey @shelby197888 , mine CH is now again ā€” , no readings, while PH is 7.2
Seems ORP is als important, if its above 850 , then again the meter doesnt read CH
Mine ORP = 911

Thanks for this. Iā€™ve been playing with it on an Olimex esp32-gateway with ESPHome version 2023.7.0.

I started by adding id(ble_switch).turn_off() to the end of the ble_sensor lambda which parses the values. So it turns on every half hour, then turns off immediately after getting a readingā€¦ or after two minutes if not. Since this is the only BLE device used with this esphome, I also start/stop the scan when the ble_switch turns on/off:

  - platform: ble_client
    id: ble_switch
    ble_client_id: ble_yc01
    name: "Enable BLE-YC01"
    restore_mode: ALWAYS_ON
    on_turn_on:
        - esp32_ble_tracker.start_scan:
            continuous: true
    on_turn_off:
        - esp32_ble_tracker.stop_scan:

I tried triggering the update immediately from the ble_client on_connect lambda right after the ->get_characteristic() call; wouldnā€™t it be nice if we could read the values once there, and not have the periodic ā€˜Cannot poll, not connectedā€™ warnings?

        #### Official connection to the BLE device with the desired characteristic ####
        - lambda: |-
            ESP_LOGD("ble_client_lambda", "Connected to BLE-YC01");
            id(ble_yc01_ble_connected).publish_state(true);
            id(led_output).turn_on();

            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.");
            }

            esphome::ble_client::BLESensor *s = id(ble_yc01_sensor);
            s->update();

It doesnā€™t work thoughā€¦ the sensor isnā€™t in state espbt::ClientState::ESTABLISHED yetā€¦

[16:20:15][D][ble_adv:144]: New BLE device
[16:20:15][D][ble_adv:145]:   address: C0:00:00:01:67:D7
[16:20:15][D][ble_adv:146]:   name: BLE-YC01
[16:20:15][D][ble_adv:147]:   Advertised service UUIDs:
[16:20:15][D][ble_adv:149]:     - 0xFF01
[16:20:15][D][ble_adv:151]:   Advertised service data:
[16:20:15][D][ble_adv:155]:   Advertised manufacturer data:
[16:20:15][D][esp32_ble_client:048]: [0] [C0:00:00:01:67:D7] Found device
[16:20:15][D][esp32_ble_tracker:213]: Pausing scan to make connection...
[16:20:15][W][component:204]: Component esp32_ble_tracker took a long time for an operation (0.13 s).
[16:20:15][W][component:205]: Components should block for at most 20-30ms.
[16:20:15][V][esp32_ble:178]: (BLE) gap_event_handler - 18
[16:20:15][I][esp32_ble_client:064]: [0] [C0:00:00:01:67:D7] 0x00 Attempting BLE connection
[16:20:18][VV][scheduler:226]: Running interval 'update' with interval=60000 last_execution=4294944760 (now=37465)
[16:20:19][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 40
[16:20:19][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=40 gattc_if=4
[16:20:19][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 2
[16:20:19][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=2 gattc_if=4
[16:20:19][V][esp32_ble_client:129]: [0] [C0:00:00:01:67:D7] ESP_GATTC_OPEN_EVT
[16:20:19][I][ble_rssi_sensor:027]: [BLE-YC01 RSSI] Connected successfully!
[16:20:19][I][ble_sensor:031]: [ble_yc01_sensor] Connected successfully!
[16:20:20][W][ble_sensor:117]: [ble_yc01_sensor] Cannot poll, not connected
[16:20:20][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 46
[16:20:20][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=46 gattc_if=4
[16:20:20][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 7
[16:20:20][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=7 gattc_if=4
[16:20:20][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 7
[16:20:20][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=7 gattc_if=4
[16:20:20][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 7
[16:20:20][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=7 gattc_if=4
[16:20:20][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 6
[16:20:20][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=6 gattc_if=4
[16:20:20][V][esp32_ble_client:189]: [0] [C0:00:00:01:67:D7] ESP_GATTC_SEARCH_CMPL_EVT
[16:20:20][V][esp32_ble_client:192]: [0] [C0:00:00:01:67:D7] Service UUID: 0x1800
[16:20:20][V][esp32_ble_client:194]: [0] [C0:00:00:01:67:D7]  start_handle: 0x1  end_handle: 0x7
[16:20:20][V][esp32_ble_client:192]: [0] [C0:00:00:01:67:D7] Service UUID: 0x1801
[16:20:20][V][esp32_ble_client:194]: [0] [C0:00:00:01:67:D7]  start_handle: 0x8  end_handle: 0xb
[16:20:20][V][esp32_ble_client:192]: [0] [C0:00:00:01:67:D7] Service UUID: 0xFF01
[16:20:20][V][esp32_ble_client:194]: [0] [C0:00:00:01:67:D7]  start_handle: 0xc  end_handle: 0xffff
[16:20:20][I][esp32_ble_client:196]: [0] [C0:00:00:01:67:D7] Connected
[16:20:20][W][ble_client:208]: on_connect lambda
[16:20:20][V][esp32_ble_client:069]: [0] [C0:00:00:01:67:D7]  characteristic 0xFF02, handle 0xe, properties 0x1a
[16:20:20][V][esp32_ble_client:069]: [0] [C0:00:00:01:67:D7]  characteristic 0xFF10, handle 0x12, properties 0x2
[16:20:20][W][ble_sensor:117]: [ble_yc01_sensor] Cannot poll, not connected
[16:20:20][W][ble_client:215]: [0xFF01] Characteristic found!
[16:20:20][D][ble_client_lambda:223]: Connected to BLE-YC01
[16:20:20][D][binary_sensor:036]: 'BLE Connected': Sending state ON
[16:20:20][V][mqtt:486]: Publish(topic='pool/binary_sensor/ble_connected/state' payload='ON' retain=1)
[16:20:20][W][ble_sensor:117]: [ble_yc01_sensor] Cannot poll, not connected
[16:20:20][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 18
[16:20:20][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=18 gattc_if=4
[16:20:20][V][esp32_ble_client:160]: [0] [C0:00:00:01:67:D7] cfg_mtu status 0, mtu 32
[16:20:20][W][component:204]: Component esp32_ble took a long time for an operation (0.21 s).
[16:20:20][W][component:205]: Components should block for at most 20-30ms.
[16:20:20][D][esp32_ble_tracker:245]: Starting scan...
 ... scan results...
[16:20:30][VV][scheduler:226]: Running interval 'update' with interval=10000 last_execution=39662 (now=49663)
[16:20:30][V][esp32_ble:206]: (BLE) gattc_event [esp_gatt_if: 4] - 3
[16:20:30][V][esp32_ble_client:114]: [0] [C0:00:00:01:67:D7] gattc_event_handler: event=3 gattc_if=4
[16:20:30][D][ble_client.receive:399]: value received with 29 bytes: [\xff\xa1\xfc\xf2\xfd-\xfc3\xfd-U\xaa"\xfdxfa\xa9\xfc)\xff\xbe\xff\xff\xff\xff_T]
[16:20:30][V][sensor:043]: 'BLE-YC01 Temperature': Received new state 20.400000
[16:20:30][D][sensor:094]: 'BLE-YC01 Temperature': Sending state 20.40000 Ā°C with 2 decimals of accuracy

Can we make it call the sensorā€™s update() method as soon as it becomes ready? What event is it waiting for? Those calls to ->get_characteristic() donā€™t seem to be making any difference; if I take them out completely, it still connects to the device, and the next periodic update of the sensor works.

Ahaā€¦ the sensor doesnā€™t go into its ESTABLISHED state until it receives a ESP_GATTC_SEARCH_CMPL_EVT event. So we can do it like thisā€¦

esp32_ble_tracker: #### Stop the active scan ####
  scan_parameters: 
    active: false
    continuous: true
  on_scan_end:
    - lambda: |-
         if (id(ble_yc01_ble_connected).state)
            id (ble_yc01_sensor).update();

ā€¦ and ditch all the stuff in the ble_client lambda except for setting the flag to show that itā€™s connected:

ble_client:
  - mac_address: C0:00:00:01:67:d7 #Use the MAC address of your BLE device
    id: ble_yc01
    on_connect: #### Actions to perform when connecting to the BLE device ####
      then:
        - lambda: |-
            ESP_LOGD("ble_client_lambda", "Connected to BLE-YC01");
            id(ble_yc01_ble_connected).publish_state(true);
            id(led_output).turn_on();

    on_disconnect:
      then:
        - lambda: |-
            ESP_LOGD("ble_client", "Disconnected from BLE-YC01");
            id(ble_yc01_ble_connected).publish_state(false);
            id(led_output).turn_off();

And I can set the update_interval on the ble_yc01_sensor to never since we do it manually now.

[16:47:10][D][ble_adv:144]: New BLE device
[16:47:10][D][ble_adv:145]:   address: C0:00:00:01:67:D7
[16:47:10][D][ble_adv:146]:   name: BLE-YC01
[16:47:10][D][ble_adv:147]:   Advertised service UUIDs:
[16:47:10][D][ble_adv:149]:     - 0xFF01
[16:47:10][D][ble_adv:151]:   Advertised service data:
[16:47:10][D][ble_adv:155]:   Advertised manufacturer data:
[16:47:10][D][esp32_ble_client:048]: [0] [C0:00:00:01:67:D7] Found device
[16:47:10][D][esp32_ble_tracker:213]: Pausing scan to make connection...
[16:47:10][I][esp32_ble_client:064]: [0] [C0:00:00:01:67:D7] 0x00 Attempting BLE connection
[16:47:11][I][ble_rssi_sensor:027]: [BLE-YC01 RSSI] Connected successfully!
[16:47:11][I][ble_sensor:031]: [ble_yc01_sensor] Connected successfully!
[16:47:12][I][esp32_ble_client:196]: [0] [C0:00:00:01:67:D7] Connected
[16:47:12][D][ble_client_lambda:205]: Connected to BLE-YC01
[16:47:12][D][binary_sensor:036]: 'BLE Connected': Sending state ON
[16:47:12][D][esp32_ble_tracker:245]: Starting scan...
[16:47:12][D][ble_client.receive:368]: value received with 29 bytes: [\xff\xa1\xfc\xf2\xfd-\xfc3\xfd-U\xaa"\xfd\xfa\xad\xfc)\xff\xbb\xff\xff\xff\xffUP]
[16:47:12][D][sensor:094]: 'BLE-YC01 Temperature': Sending state 20.40000 Ā°C with 2 decimals of accuracy
[16:47:12][D][sensor:094]: 'BLE-YC01 pH': Sending state 4.20000 pH with 2 decimals of accuracy
[16:47:12][D][sensor:094]: 'BLE-YC01 ORP': Sending state 457.00000 mV with 0 decimals of accuracy
[16:47:12][D][sensor:094]: 'BLE-YC01 battery': Sending state 95.95612 % with 0 decimals of accuracy
[16:47:12][D][sensor:094]: 'BLE-YC01 EC': Sending state 969.00000 ĀµS/cm with 0 decimals of accuracy
[16:47:12][D][sensor:094]: 'BLE-YC01 TDS': Sending state 484.00000 ppm with 0 decimals of accuracy
[16:47:12][D][sensor:094]: 'BLE-YC01 CL': Sending state 6553.50000 ppm with 1 decimals of accuracy
[16:47:12][D][switch:016]: 'Enable BLE-YC01' Turning OFF.
[16:47:12][I][ble_client:041]: [C0:00:00:01:67:D7] Disabling BLE client.
[16:47:12][D][switch:055]: 'Enable BLE-YC01': Sending state OFF
[16:47:12][D][esp32_ble_tracker:232]: Stopping scan.
[16:47:12][D][sensor:094]: 'ble_yc01_sensor': Sending state 0.00000  with 0 decimals of accuracy
[16:47:12][D][ble_client:212]: Disconnected from BLE-YC01

Hi, you said:

Since this is the only BLE device used with this esphome, I also start/stop the scan when the ble_switch turns on/off:

WHy do you do that? to improve battery? I see you did some update on the code? what are the improvements? Can you maybe give a full update of your code? whats better now?

Indeed, preserving battery would be crucial. I tried mine keeping it connected all the time, and the original battery lasted for 2 weeks, while replacement batteries only one week.

The problem is if I use the 30min/2min scan interval, after a couple of hours it doesnā€™t reconnect again, and I lose all the values inbetween. I have to press the on-key on the device for it to work againā€¦

i also had reconnect issues, but now placed my ESP in shorter distance, no fallout anymore since then

this seems to work. for completeness, here is the code I used. As suggested, I turn the ble_client of upon successfully reading the values. I increased the window size a bit to 240ms. I donā€™t turn of scanning because I usually use the esp32 also as bluetooth proxy.

esp32_ble_tracker:
  scan_parameters:
    active: false
   # tried this to have a larger reception window:
    window: 240ms 
  on_scan_end:
    - lambda: |-
         if (id(ble_yc01_ble_connected).state)
            id (ble_yc01_sensor).update();

#enable if you also want to proxy:
#bluetooth_proxy:
  
binary_sensor:
- platform: status
  name: "ESP32 Status"
- platform: template
  id: ble_yc01_ble_connected
  icon: mdi:bluetooth-connect
  name: "BLE Connected"

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


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

ble_client: 
  - mac_address: C0:00:00:01:XX:XX #Use the MAC address of your BLE device
    id: ble_yc01
    on_connect: #### Actions to perform when connecting to the BLE device ####
      then:
        #### Official connection to the BLE device with the desired characteristic ####
        - lambda: |- 
            ESP_LOGD("ble_client_lambda", "Connected to BLE-YC01");
            id(ble_yc01_ble_connected).publish_state(true);
        
    on_disconnect:
      then:
        - lambda: |-
            ESP_LOGD("ble_client", "Disconnected from BLE-YC01");
            id(ble_yc01_ble_connected).publish_state(false);
            
######################################################
##                                                  ##
##     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: "C"
    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: 1
    state_class: measurement
    icon: mdi:ph

  - platform: template
    name: "BLE-YC01 battery"
    id: ble_yc01_battery
    unit_of_measurement: "%"
    accuracy_decimals: 0
    state_class: measurement
    device_class: battery
    icon: mdi:battery

  - platform: template
    name: "BLE-YC01 CL"
    id: ble_yc01_cloro
    unit_of_measurement: "mg/L"
    accuracy_decimals: 1
    state_class: measurement
    icon: mdi:water-opacity  
    
  - platform: ble_client ####  Sensor required to manage values coming from the BLE device ####
    ble_client_id: ble_yc01
    type: characteristic
    id: ble_yc01_sensor
    #update_interval: 30s
    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]);
      auto cloro = ((message[11]<<8) + message[12]);
      
      // #### Sensors updated with new values
      id(ble_yc01_temperature_sensor).publish_state(temp/10.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/31.9);
      id(ble_yc01_ec_sensor).publish_state(ec);
      id(ble_yc01_tds_sensor).publish_state(tds);
      id(ble_yc01_cloro).publish_state(cloro/10.0);
      
      id(ble_switch).turn_off(); 
      
      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"
2 Likes

However, now the sensors frequently switch to ā€œUnavailableā€, which is quite annoying, and I have no idea whyā€¦

I wonder, according to the documentation, on_scan_end is triggered when a scan is completed, which happens after ā€œdurationā€, which defaults to 5min. Iā€™m not sure this is actually doing anything, because when the device is connecting, the update actually happens immediately after a few secondsā€¦

So I tried to set the update_interval to ā€œneverā€, and this didnā€™t work, it didnā€™t get any values. If I leave it empty or set it to 30s, it seems to work fine. I can also remove the ā€œon_scan_endā€ lambda and it still works fine.

The problem, however, remains that sometimes, the device seems to go into a state where it doesnā€™t connect anymoreā€¦ Only pushing the on button seems to help in this case.

I did have some connectivity issues with my device. Some were just down to range; I have a shed next to the pool and the esp32 device was at the far side of that shed (with the table and the power and the Ethernet connection). Iā€™ve now placed it closer to the pool.

My BLE-YC01 also kept turning itself off. But seems to have stopped doing that after I opened it up, poured the water out, dried it (gently!) with a heat gun and resealed it tightly with some PTFE tape this time. The readings are still nonsense though ā€” that pool definitely doesnā€™t have a pH of 3.0 as itā€™s saying! It seemed sane for a few days but then started reporting nonsense. Hopefully recalibrating it will fix that.

Other than that it seems to be working fairly reliably now, so Iā€™ll post the version Iā€™m using. Note Iā€™m using Domoticz not HA. So Iā€™m using NTP, and thereā€™s also an example of how to submit values to Domoticz over MQTT which you can strip out if you donā€™t want it.

So every 30 minutes it turns on the BLE scanning (since I only have the one BLE device as I said). When it connects to the BLE-YC01 it just sets a flag, the ble_yc01_ble_connected sensor state.

For me, the ā€˜scan completeā€™ event does happen fairly much immediately after the connection; thereā€™s no 5-minute delay. At which point that on_scan_end lambda runs, and triggers the ble_yc01_rssi and ble_yc01_sensors sensors to update immediately.

The BLE turns off either after two minutes, or after a successful reading of the ble_yc01_sensor.

Logs:

time:
  - platform: sntp
    id: sntp_time
    on_time:
        # Turn on BLE client every 30 minutes for 2 minutes
        # (or until it turns itself off after a successful reading)
      - seconds: 0
        minutes: /10
        then:
          - switch.turn_on: ble_switch
          - delay: 2min
          - switch.turn_off: ble_switch

esp32_ble_tracker: #### Stop the active scan ####
  scan_parameters:
    active: false
    continuous: true
  on_scan_end:
    - lambda: |-
         if (id(ble_yc01_ble_connected).state) {
            ESP_LOGD("ble_yc01", "Scan complete, triggering update");
            id (ble_yc01_rssi).update();
            id (ble_yc01_sensor).update();
         }

binary_sensor:
- platform: status
  name: "ESP32 Status"
- platform: template
  id: ble_yc01_ble_connected
  icon: mdi:bluetooth-connect
  name: "BLE Connected"


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

ble_client:
  - mac_address: C0:00:00:xx:xx:xx #Use the MAC address of your BLE device
    id: ble_yc01
    on_connect: #### Actions to perform when connecting to the BLE device ####
      then:
        - lambda: |-
            ESP_LOGD("ble_client_lambda", "Connected to BLE-YC01");
            id(ble_yc01_ble_connected).publish_state(true);

    on_disconnect:
      then:
        - lambda: |-
            ESP_LOGD("ble_client", "Disconnected from BLE-YC01");
            id(ble_yc01_ble_connected).publish_state(false);


######################################################
##                                                  ##
##     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: "Ā°C"
    accuracy_decimals: 2
    state_class: measurement
    device_class: temperature
    on_value:
      then:
        lambda: |-
           id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
                                                           root["command"] = "udevice";
                                                           root["idx"] = 510;
                                                           root["svalue"] = std::to_string(x);
                                        });

  - 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
    on_value:
      then:
        lambda: |-
           id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
                                                           root["command"] = "udevice";
                                                           root["idx"] = 511;
                                                           root["svalue"] = std::to_string(x);
                                        });

  - platform: template
    name: "BLE-YC01 battery"
    id: ble_yc01_battery
    unit_of_measurement: "%"
    accuracy_decimals: 0
    state_class: measurement
    device_class: battery
    icon: mdi:battery

  - platform: template
    name: "BLE-YC01 CL"
    id: ble_yc01_cloro
    unit_of_measurement: "ppm"
    accuracy_decimals: 1
    state_class: measurement
    icon: mdi:water-opacity
    on_value:
      then:
        lambda: |-
           id(mqtt_client).publish_json("domoticz/in", [=](JsonObject root) {
                                                           root["command"] = "udevice";
                                                           root["idx"] = 509;
                                                           root["svalue"] = std::to_string(x);
                                        });

  - platform: ble_client
    type: rssi
    id: ble_yc01_rssi
    ble_client_id: ble_yc01
    update_interval: never
    name: "BLE-YC01 RSSI"

  - platform: ble_client ####  Sensor required to manage values coming from the BLE device ####
    ble_client_id: ble_yc01
    type: characteristic
    id: ble_yc01_sensor
    update_interval: never
    internal: true
    service_uuid: FF01
    characteristic_uuid: FF02
    #### Lambda to decode values and push to the associated sensors ####
    lambda: |-

      if (x.size() == 0) return NAN;

      std::string rawmsg;
      for (int i = 0; i < x.size(); i++) {
         char buf[7];
         snprintf(buf, 7, " 0x%02x", (unsigned char) x[i]);
         rawmsg = rawmsg + buf;
      }
      ESP_LOGD("ble_client.receive", "raw value received with %d bytes: [%s]", x.size(), rawmsg.c_str()); // ####  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);

      }

      rawmsg = "";
      for (int i = 0; i < message.size(); i++) {
         char buf[7];
         snprintf(buf, 7, " 0x%02x", (unsigned char) message[i]);
         rawmsg = rawmsg + buf;
      }
      ESP_LOGD("ble_client.receive", "value received with %d bytes: [%s]", message.size(), rawmsg.c_str()); // #### 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]);
      auto cloro = ((message[11]<<8) + message[12]);

      // #### Sensors updated with new values
      id(ble_yc01_temperature_sensor).publish_state(temp/10.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/31.9);
      id(ble_yc01_ec_sensor).publish_state(ec);
      id(ble_yc01_tds_sensor).publish_state(tds);
      if (cloro == 65535)
          id(ble_yc01_cloro).publish_state(NAN);
      else
          id(ble_yc01_cloro).publish_state(cloro/10.0);

      // Once we have a single reading, turn off until the next attempt
      id(ble_switch).turn_off();
      return NAN; // 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"
    restore_mode: ALWAYS_ON
    on_turn_on:
        - esp32_ble_tracker.start_scan:
            continuous: true
    on_turn_off:
        - esp32_ble_tracker.stop_scan:


1 Like

I also start/stop the scan when the blue_switch turns on/off

Why do you do that? to improve battery?

Yes, this way the ESP32 disconnects and doesnā€™t keep the BLE-YC01 connected. Perhaps thereā€™s a way to keep BLE enabled on the ESP32, but just disconnect from the BLE-YC01 and refrain from reconnecting to it on every scan? Since this is my only BLE device used with this ESP32, as I said, I donā€™t have to work that part out :slight_smile:

1 Like

I pushed this to git.infradead.org Git - users/dwmw2/esp32-pool.git/summary

3 Likes

This is awesome. I just ordered one from Aliexpress. With your research, I can try to make a native BLE integration which works with ESPHome proxies when mine arrives. I have made a couple integrations and it is not too hard. My coding is super bad, but also super simple so should be easy to learn if anyone else wants to try themselves. A good one to modify is: https://github.com/jdeath/Opal_NuggetIce_ble/ as it seems all you need to do is a read_gatt_char to get the values. Should be straight forward to replace the config_flow.py line that finds the device on the network and change the sensors in the parser. The nice thing about proxy is it automatically connects, reads, then disconnects based on a timer. Seems @anasm2010 bleak script has everything you need to start with for the proxy code

1 Like

Hello everyone,
I donā€™t use HomeAssistant, but maybe you can still help me.

I also bought the BLE-YC01 pool meter and wrote an ESP32 Arduino sketch.

I read the value of the characteristic, but cannot decode it.
I used the code snipps from the forum, also read the positions from the string. However, the correct data is not displayed to me.

greeting
think

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEServer.h>
#include <BLEClient.h>

static BLEAddress serverAddress("c0:00:00:01:da:1e");
static BLEUUID serviceUUID("0000ff01-0000-1000-8000-00805f9b34fb");
static BLEUUID charUUID("0000ff02-0000-1000-8000-00805f9b34fb");

BLEClient* pClient;
boolean doConnect = false;
boolean connected = false;
BLERemoteCharacteristic* pRemoteCharacteristic;

class MyClientCallbacks : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
    Serial.println("Verbunden mit BLE-YC01...");
  }

  void onDisconnect(BLEClient* pclient) {
    Serial.println("Verbindung zum BLE-YC01 getrennt...");
    connected = false;
    pRemoteCharacteristic = nullptr;
  }
};

// Dekodierungsfunktion fĆ¼r Temperatur
float decodeTemperature(uint8_t highByte, uint8_t lowByte) {
  int16_t rawTemp = (highByte << 8) | lowByte;
  float temperature = rawTemp / 100.0; // Beispielhafte Dekodierung (angenommen, die Temperatur wird in Hundertsteln von Grad Ć¼bertragen)
  return temperature;
}

void setup() {
  Serial.begin(115200);
  Serial.println("Starte Verbindung mit BLE YC01...");

  BLEDevice::init("");
  pClient = BLEDevice::createClient();
  pClient->setClientCallbacks(new MyClientCallbacks());

  doConnect = true;
}

void loop() {
  if (connected) {
    if (!pRemoteCharacteristic) {
      pRemoteCharacteristic = pClient->getService(serviceUUID)->getCharacteristic(charUUID);
    }
    if (pRemoteCharacteristic->canRead()) {

      std::string value = pRemoteCharacteristic->readValue();
      String hexValue = "";

      uint8_t tempHighByte = value[13];
      uint8_t tempLowByte = value[14];
      float Temperatur = decodeTemperature(tempHighByte, tempLowByte);
      Serial.print("Temperatur: ");
      Serial.println(Temperatur);

      // ... (Dekodierung und Anzeige von pH, ORP, Batteriestatus)

      //int ph = ((value[3] << 8) + value[4]);
      //float pHWert = (ph);
      //Serial.print("pH-Wert: ");
      //Serial.println(pHWert);

      //int orp = ((value[7] << 8) + value[8]);
      //Serial.print("ORP-Wert: ");
      //Serial.println(orp);

      //int battery = ((value[5] << 8) + value[6]);
      //float battStatus = (battery / 45);
      //Serial.print("Batterie: ");
      //Serial.println(battStatus);

      for (size_t i = 0; i < value.length(); i++) {
        if (i > 0) hexValue += ":";
        hexValue += String(value[i], HEX);
      }
      Serial.print("Charakteristik Wert (Hex): ");
      Serial.println(hexValue);
      delay(20000);
    }
  }
  if (!connected && doConnect) {
    if (pClient->connect(serverAddress)) {
      connected = true;
    } else {
      connected = false;
      doConnect = false;
    }
  }
}

It looks like you may have forgotten to do the ā€˜decodeā€™ step?

See git.infradead.org Git - users/dwmw2/esp32-pool.git/blob - pool.yaml :

      std::string rawmsg;
      for (int i = 0; i < x.size(); i++) {
         char buf[7];
         snprintf(buf, 7, " 0x%02x", (unsigned char) x[i]);
         rawmsg = rawmsg + buf;
      }
      ESP_LOGD("ble_client.receive", "raw value received with %d bytes: [%s]", x.size(), rawmsg.c_str()); // ####  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);

      }

      rawmsg = "";
      for (int i = 0; i < message.size(); i++) {
         char buf[7];
         snprintf(buf, 7, " 0x%02x", (unsigned char) message[i]);
         rawmsg = rawmsg + buf;
      }
      ESP_LOGD("ble_client.receive", "value received with %d bytes: [%s]", message.size(), rawmsg.c_str()); // #### For debug ####


Sample logs:

Jul 31 19:25:31 pool ble_client.receive ļ»æ[D][ble_client.receive:374]: raw value received with 29 bytes: [ 0xff 0xa1 0xfc 0xf2 0xfd 0x0f 0xfc 0xc3 0xfd 0x2b 0x55 0x14 0xaa 0x22 0xfd 0x31 0xfa 0xa9 0x7f 0xfc 0x2b 0xff 0xbe 0xff 0xff 0xff 0xff 0xf7 0x00]
Jul 31 19:25:31 pool ble_client.receive ļ»æ[D][ble_client.receive:403]: value received with 29 bytes: [ 0x01 0x02 0x0f 0x01 0xa4 0x03 0x78 0x01 0xbc 0x01 0xc2 0xff 0xff 0x00 0xcc 0x0b 0xed 0x00 0x03 0xe8 0x01 0xc2 0x00 0x00 0x00 0x00 0x00 0x00 0xae]
Jul 31 19:25:31 pool sensor ļ»æ[D][sensor:094]: 'BLE-YC01 Temperature': Sending state 20.40000 Ā°C with 2 decimals of accuracy
Jul 31 19:25:31 pool sensor ļ»æ[D][sensor:094]: 'BLE-YC01 pH': Sending state 4.20000 pH with 2 decimals of accuracy
Jul 31 19:25:31 pool sensor ļ»æ[D][sensor:094]: 'BLE-YC01 ORP': Sending state 450.00000 mV with 0 decimals of accuracy
Jul 31 19:25:31 pool sensor ļ»æ[D][sensor:094]: 'BLE-YC01 battery': Sending state 95.70533 % with 0 decimals of accuracy
Jul 31 19:25:31 pool sensor ļ»æ[D][sensor:094]: 'BLE-YC01 EC': Sending state 888.00000 ĀµS/cm with 0 decimals of accuracy
Jul 31 19:25:31 pool sensor ļ»æ[D][sensor:094]: 'BLE-YC01 TDS': Sending state 444.00000 ppm with 0 decimals of accuracy
Jul 31 19:25:31 pool sensor ļ»æ[D][sensor:094]: 'BLE-YC01 CL': Sending state nan ppm with 1 decimals of accuracy

Please show the bytes youā€™re receiving, and the values you are extracting from them?

Thank you David!
Now it works for me too.
I modified and pasted your code.
Thank you very much for your note.


21:28:25.365 -> Temperatur: 28.3 Ā°C
21:28:25.365 -> pH - Wert: 3.7 pH
21:28:25.365 -> orp - Wert: 120 mV
21:28:25.365 -> Batterie - Status: 92.5 %
21:28:25.365 -> ec - Wert: 0 ĀµS/cm
21:28:25.410 -> tds - Wert: 0 ppm
21:28:25.410 -> Chlor - Wert: 65535 (Fehler, Wert nicht lesbar!)

1 Like

hello,
Iā€™m facing a new problem with the device but not the code.
Actual temperature is 20.2Ā°C but a classic meter indicate 25.5Ā°C
Also battery is 58% in the esp sensor, but 0% on the Yinmik app
Iā€™d like to know if any of you have seen this problem ?

I havenā€™t seen a problem with the temperature reporting. The pH and other values seem very inaccurate but the temperature is the only thing I did trust.

The above recipes have different multipliers for the battery, and Iā€™ve used 31.9 which seemed about right, but itā€™s a bit weird. Doesnā€™t explain the app reporting zero though. Do you believe it? Is the battery almost flat?

Hi,
this is exactly the behavior Iā€™m seeing. When the battery approaches 60%, it canā€™t sustain enough voltage anymore and the temperature reading drops dramatically. You need to replace the battery.
This means that the battery reading is not really a percentage of the full charge, but itā€™s related to the voltage. One would have to check at what voltage it usually breaks down, and the renormalize the percentage to that rangeā€¦

On another note: my device suddenly started to report a pH of around 8.5 out of nowhere (the water is still at 7.4 or so with other test equipment). I wonder whether there is a way to reset it to the previous calibration valuesā€¦