Pool Monitor Device Yieryi BLE-YC01

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…

Thanks for this feedback. I will go change my battery and measure it.
For the Ph, I’m afraid the only option is to recalibrate with the special pouder you received with it.

That I already did initially. Now I need to get a new one somewhere. I should have stored the solutions, but I don’t know how long they remain stable…