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.
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:
- Connect to the shaver using the GroomTribe app
- 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
- Remove the shaver from GroomTribe and then remove the saved bluetooth connection from the mobile
- 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.
So the steps after that are:
- Install the code onto your ESP32, modified to suit your setup (board, secrets, shaver mac address etc)
- 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)
- Start the ESP32, watching the log for any errors
- 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:
- Make sure that the shaver is close to the ESP32 when starting the initial connection
- 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.
- 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!