Philips bluetooth shaver monitoring

So, late last year I replaced my old Philips shaver with a new one, and found it had bluetooth. To access it you need to install the “GroomTribe” app - and then you can use the app to do things like monitor the battery as well as give hints on how to get a better shave based on the shaver motion etc etc. All kinda nice to have, but the big one is the battery.

Now, I prefer to not leave my shaver on charge constantly both because it’s annoying to have to unplug/plug the thing each day but also the risk of slowly reducing the lifespan of the battery. My desire was to get an alert once the battery drops below a certain level so I am reminded to put the thing on charge but whilst it still has more than enough charge for the morning shave. I actually have the same thing for my toothbrush via the OralB integration. But I digress.

I had a search in the forums, and could not find any mention of direct or even indirect support for the Philips shavers. Searching further afield I found someone on reddit who had done this. So big shout out to max24688. :wink:

If you want to do this, then you will need:

  • A Philips shaver that supports bluetooth. In my case I have a 7000 series S7887, but this should work for 9000 and 8000 series shavers, although it is likely some of UUID info might be different. This general process would of course work with pretty much any bluetooth device, although might need to tweak the connection settings as well as the UUID info.
  • An ESP32 that supports bluetooth. In my case I used an ESP32-C3 SuperMini (my current favourite as it’s tiny, is quick, has a built in RGB light, and can be bought with an external antenna that provides decent wifi pickup)
  • Far too much spare time on your hands

The steps are optionally:

  1. Connect to the shaver using the GroomTribe app
  2. Use the nRF app to confirm the mac address of the device and that the various UUIDs are correct, collecting any additional ones you might be interested i
  3. Remove the shaver from GroomTribe and then remove the saved bluetooth connection from the mobile
  4. Remove the connection from the shaver (process is described in the GroomTribe app, but basically it’s hold the power button down for ten seconds). Note that the shaver only allows one device to pair with it.

I say the above is optional, as it is quite possible that (other than the mac address that you can collect in other ways) all the settings in the following code will be fine - just need to modify to match your environment eg board etc. You may also not be interested in some of the other info like serial number - frankly I’ve just included that because I could. :slight_smile:

So the steps after that are:

  1. Install the code onto your ESP32, modified to suit your setup (board, secrets, shaver mac address etc)
  2. Start up the shaver and leave it running near the ESP32 (probably don’t absolutely need to leave it running, but rules out the possibility of pairing issues)
  3. Start the ESP32, watching the log for any errors
  4. Enjoy
# Compiled and tested on esphome 2025.2.0 and HA 2025.2.5
# Thanks to max24688 - https://www.reddit.com/r/Esphome/comments/1fyenty/philips_shaver_7000/

substitutions:
  name: ble01
  friendly_name: ble01
  devicename: ble01
  location: master

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: zagnut.ble
    version: '1.0'
  comment: BLE Sensor ESP32-C3 SuperMini $location
  
esp32:
  variant: ESP32C3 # ESP32-C3 Supermini Plus Black Gold
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf
#    version: recommended
    version: latest

# Enable logging
logger:
  baud_rate: 0  # disable serial uart logging to maybe save a little ram
  logs:
    component: ERROR

api:
  encryption:
    key: !secret esphome_encryption_key

ota:
  password: !secret ota_password
  platform: esphome

wifi:
  networks:
  - ssid: !secret wifIoT_ssid
    password: !secret wifIoT_password
    priority: 2
# Backup SSID just in case
  - ssid: !secret wifi_ssid
    password: !secret wifi_password
    priority: 1
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "$devicename Fallback Hotspot"
    password: !secret ota_password

# The following is for when using esp-idf framework. Note if using the arduino framework, can change
light:
  - platform: esp32_rmt_led_strip
    id: led
    rgb_order: GRB
    pin:
      number: GPIO8
      ignore_strapping_warning: true
    num_leds: 1
    chipset: ws2812
    name: "Status LED"
    default_transition_length: 0s
    effects:
      - pulse:
          name: "extra_slow_pulse"
          transition_length: 800ms
          update_interval: 800ms
          min_brightness: 0%
          max_brightness: 30%
      - pulse:
          name: "slow_pulse"
          transition_length: 250ms
          update_interval: 250ms
          min_brightness: 50%
          max_brightness: 100%
      - pulse:
          name: "fast_pulse"
          transition_length: 100ms
          update_interval: 100ms
          min_brightness: 50%
          max_brightness: 100%

esp32_ble_tracker:
  scan_parameters:
#    continuous: True
    active: True
    interval: 211ms # default 320ms
    window: 120ms # default 30ms

# Enable this for Bermuda BLE Trilateration
#bluetooth_proxy:
#  active: true

globals:
  - id: ble_client_connected
    type: bool
    initial_value: 'false'

ble_client:
  - mac_address: F4:E6:DE:AD:ME:AT
    id: shaver
    auto_connect: true
    on_connect:
      then:
        - logger.log: "Connected"
        - lambda: |-
            id(ble_client_connected) = true;
    on_disconnect:
      then:
        - lambda: |-
            id(ble_client_connected) = false;

binary_sensor:
  - platform: template
    name: 'Shaver Connection'
    id: shaver_connection
    lambda: 'return id(ble_client_connected);'
    icon: 'mdi:bluetooth-connect'

sensor:
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    id: wifisignal
    update_interval: 60s
    unit_of_measurement: dBm
    accuracy_decimals: 0
    device_class: signal_strength
    state_class: measurement
    entity_category: diagnostic
  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifisignal
    id: wifipercent
    name: "WiFi Signal Percent"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "Signal %"
    entity_category: "diagnostic"
  - platform: uptime
    id: uptime_s
    name: "$devicename Uptime"
    update_interval: 60s          
  - platform: template
    name: $devicename free memory
    lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
    icon: "mdi:memory"
    entity_category: diagnostic
    state_class: measurement
    unit_of_measurement: "b"
    update_interval: 60s
# The following were added to track the shaver
# Confirmed the UUIDs via nRF Connect app
#
# Note - UUID can be in
# 16 bit 'XXXX',
# 32 bit 'XXXXXXXX', 
# or 128 bit 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' format.
#
# eg
#    service_uuid: '0000180f-0000-1000-8000-00805f9b34fb'
#    characteristic_uuid: '00002a19-0000-1000-8000-00805f9b34fb' 
# is the same as:
#    service_uuid: '180f'
#    characteristic_uuid: '2a19' 
#
  - platform: ble_client
    type: characteristic
    ble_client_id: shaver
    name: "Shaver battery level"
    service_uuid: '180f'
    characteristic_uuid: '2a19' 
    notify: true
    icon: 'mdi:battery'
    accuracy_decimals: 0
    unit_of_measurement: '%'
  - platform: ble_client
    type: rssi
    ble_client_id: shaver
    name: "Shaver RSSI"
  - platform: ble_client
    type: characteristic
    ble_client_id: shaver
    name: "Shaver head remaining"
    service_uuid: '8d560100-3cb9-4387-a7e8-b79d826a7025'
    characteristic_uuid: '8d560117-3cb9-4387-a7e8-b79d826a7025'
    icon: 'mdi:saw-blade'
    accuracy_decimals: 0
    unit_of_measurement: '%'
  - platform: ble_client
    type: characteristic
    ble_client_id: shaver
    name: "Days since last used"
    service_uuid: '8d560100-3cb9-4387-a7e8-b79d826a7025'
    characteristic_uuid: '8d560108-3cb9-4387-a7e8-b79d826a7025' 
    accuracy_decimals: 0

text_sensor:
  - platform: ble_client
    ble_client_id: shaver
    name: "Shaver Model"
    service_uuid: '0000180a'
    characteristic_uuid: '00002a24' 
  - platform: ble_client
    ble_client_id: shaver
    name: "Shaver serial"
    service_uuid: '0000180a'
    characteristic_uuid: '00002a25'
    icon: 'mdi:barcode' 

Once installed, then hopefully you should start to see info such as the battery level,

allowing you to setup an automation eg:

alias: Alert - shaver battery below 30%
description: ""
triggers:
  - entity_id:
      - sensor.ble01_shaver_battery_level
    for:
      hours: 0
      minutes: 5
      seconds: 0
    below: 30
    trigger: numeric_state
conditions: []
actions:
  - data:
      title: Shaver battery getting low!
      message: Shaver battery getting low!
    action: persistent_notification.create
  - metadata: {}
    data:
      message: Shaver battery getting low!
      title: Shaver battery getting low!
      target: [email protected]
    action: notify.zagnut_test_com
  - data:
      message: Shaver battery getting low!
      title: Shaver battery getting low!
    action: notify.mobile_app_sm_g990e
mode: single

If you find that info is not appearing, then standard troubleshooting:

  1. Make sure that the shaver is close to the ESP32 when starting the initial connection
  2. Make sure that the ESP32 is not too far away from the shaver - the signal is not strong, so ideally it should be in the same room or the one next to where the shaver lives.
  3. It might not be connecting initially - if so change the code that says
    on_connect:
      then:
        - logger.log: "Connected"
        - lambda: |-
            id(ble_client_connected) = true;

to

    on_connect:
      then:
        - logger.log: "Connected"
        - lambda: |-
            id(ble_client_connected) = true;
            ESP_LOGE("custom", "Connected to shaver, trying to pair");
            id(shaver)->pair();

Once the initial connection has been established, you probably don’t need those extra two lines
7. Reset the shaver - again just hold the power button down for ten seconds or so
8. Keep the shaver running and restart the ESP32 device, monitoring the log whilst it starts for any obvious errors

Final notes:

The ESP32 uses memory & processor cycles on the ESP32 when connected - probably would not want to connect too many devices like this. This code also keeps the connection alive constantly - having said that, this is sort of the case if connected via a mobile, although the mobile is only connected when near it. Even so, I might change this to connect and retrieve info once every hour or similar, rather than being constantly connected - which would lower the risk of issues if you start to monitor more devices like this. I would note that, so far, being constantly connected does not seem to be causing any significant drain on the shaver battery.

Enjoy!