ESPHome configuration for connecting the Contec PC-60FW Bluetooth fingertip oximeter to Home Assistant. It’s a dumbed down copy of more sophisticated code originally written by Jeffrey Kosowsky (puterboy).
Sensor Outputs:
SpO2 - Blood oxygen saturation (0-100%)
Pulse - Heart rate (BPM)
Perfusion Index - (0.0-25.5%)
Battery Level - Device battery status (0-3 scale: 0=Low, 1=Medium, 2=High, 3=Full)
substitutions:
# Customize these
device_name: "pulsometer"
device_friendly_name: "Pulse Oximeter Bridge"
globals:
- id: record_to_ha
type: bool
restore_value: yes
initial_value: 'true'
- id: spo2_current
type: uint8_t
restore_value: no
- id: pulse_current
type: uint8_t
restore_value: no
- id: perf_index_current
type: uint8_t
restore_value: no
- id: battery_current
type: uint8_t
restore_value: no
esphome:
name: ${device_name}
friendly_name: ${device_friendly_name}
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
level: INFO
api:
# Generate your own key: esphome secrets
ota:
# Add password in secrets
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Optional: Configure static IP below if needed
# manual_ip:
# static_ip: 192.168.x.x
# gateway: 192.168.x.x
# subnet: 255.255.255.0
ap:
ssid: "${device_friendly_name} Fallback"
password: !secret fallback_password
captive_portal:
esp32_ble_tracker:
scan_parameters:
interval: 3200ms
window: 160ms
active: false
ble_client:
- mac_address: "00:00:00:03:10:4E" # Replace with your device MAC
id: pc_60fw
sensor:
- platform: ble_client
type: characteristic
id: internal_pulseox
internal: true
ble_client_id: pc_60fw
service_uuid: '6e400001-b5a3-f393-e0a9-e50e24dcca9e'
characteristic_uuid: '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
notify: true
lambda: |-
// Data packet (12 bytes): SpO2, Pulse, Perfusion Index
if(x.size() == 12 && x[0] == 0xAA && x[1] == 0x55 && x[2] == 0x0F && x[3] == 0x08) {
if(x[5] != 0) {
id(spo2_current) = x[5];
if(id(record_to_ha)) id(spo2).publish_state(id(spo2_current));
}
if(x[6] != 0) {
id(pulse_current) = x[6];
if(id(record_to_ha)) id(pulse).publish_state(id(pulse_current));
}
if(x[8] != 0) {
float pi_value = x[8] / 10.0;
id(perf_index_current) = x[8];
if(id(record_to_ha)) id(perfindex).publish_state(pi_value);
}
// Battery packet (7 bytes)
}else if (x.size() == 7 && x[0] == 0xAA && x[1] == 0x55 && x[2] == 0xF0 && x[3] == 0x3 && x[4] == 0x3) {
if(x[5] != 0) {
id(battery_current) = x[5];
if(id(record_to_ha)) id(battery_level).publish_state(id(battery_current));
}
}
return {};
- platform: template
id: spo2
name: "SpO2"
icon: 'mdi:lung'
unit_of_measurement: '%'
accuracy_decimals: 0
filters:
- delta: 0.5
- throttle: 1s
- platform: template
id: pulse
name: "Pulse"
icon: 'mdi:heart-pulse'
unit_of_measurement: 'bpm'
accuracy_decimals: 0
filters:
- delta: 0.5
- throttle: 1s
- platform: template
id: perfindex
name: "Perfusion Index"
icon: 'mdi:waves'
unit_of_measurement: '%'
accuracy_decimals: 1
filters:
- delta: 0.05
- throttle: 1s
- platform: template
id: battery_level
name: "Battery Level"
icon: 'mdi:battery'
unit_of_measurement: '%'
accuracy_decimals: 0
filters:
- delta: 0.5
- throttle: 1s
text_sensor:
- platform: wifi_info
ip_address:
name: "IP Address"
binary_sensor:
- platform: status
name: "Status"
switch:
- platform: template
name: "Record to Home Assistant"
id: record_to_ha_switch
lambda: |-
return id(record_to_ha);
turn_on_action:
- globals.set:
id: record_to_ha
value: 'true'
turn_off_action:
- globals.set:
id: record_to_ha
value: 'false'
My current configuration
name: pulsometer
friendly_name: pulsometer
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
logger:
level: INFO
api:
encryption:
key: "YOUR_ENCRYPTION_KEY"
ota:
- platform: esphome
password: "YOUR_OTA_PASSWORD"
wifi:
ssid: "YOUR_WIFI_SSID"
password: "YOUR_WIFI_PASSWORD"
manual_ip:
static_ip: YOUR_STATIC_IP
gateway: YOUR_GATEWAY
subnet: YOUR_SUBNET
dns1: YOUR_DNS
output_power: 9.5dB
fast_connect: true
passive_scan: false
globals:
- id: pulsometer_spo2
type: int
restore_value: yes
initial_value: '0'
- id: pulsometer_pulse
type: int
restore_value: yes
initial_value: '0'
- id: pulsometer_perf_index
type: float
restore_value: yes
initial_value: '0.0'
- id: pulsometer_battery_current
type: int
restore_value: yes
initial_value: '0'
- id: pulsometer_ble_connected
type: bool
restore_value: no
initial_value: 'false'
esp32_ble_tracker:
scan_parameters:
interval: 1200ms
window: 400ms
active: false
ble_client:
- mac_address: "YOUR_DEVICE_MAC_ADDRESS"
id: pc_60fw
auto_connect: false
on_connect:
then:
- lambda: |-
id(pulsometer_ble_connected) = true;
on_disconnect:
then:
- lambda: |-
id(pulsometer_ble_connected) = false;
interval:
- interval: 3s
then:
- text_sensor.template.publish:
id: pulsometer_status
state: !lambda |-
if (id(pulsometer_ble_connected)) {
return "Connected";
} else {
return "Waiting";
}
- lambda: |-
static uint64_t last_connect_attempt = 0;
uint64_t now = millis();
if (!id(pc_60fw)->connected() &&
id(pulsometer_detected).state &&
(now - last_connect_attempt > 10000)) {
last_connect_attempt = now;
id(pc_60fw)->connect();
}
if (id(pc_60fw)->connected() &&
!id(pulsometer_detected).state) {
static uint64_t disconnect_timer = 0;
if (disconnect_timer == 0) {
disconnect_timer = now;
} else if (now - disconnect_timer > 5000) {
id(pc_60fw)->disconnect();
disconnect_timer = 0;
}
} else {
static uint64_t disconnect_timer = 0;
disconnect_timer = 0;
}
sensor:
- platform: ble_client
type: characteristic
id: internal_pulseox
internal: true
ble_client_id: pc_60fw
service_uuid: '6e400001-b5a3-f393-e0a9-e50e24dcca9e'
characteristic_uuid: '6e400003-b5a3-f393-e0a9-e50e24dcca9e'
notify: true
lambda: |-
if(x.size() == 12 && x[0] == 0xAA && x[1] == 0x55 && x[2] == 0x0F && x[3] == 0x08) {
if(x[5] != 0) {
id(pulsometer_spo2) = x[5];
id(spo2).publish_state((float)id(pulsometer_spo2));
}
if(x[6] != 0) {
id(pulsometer_pulse) = x[6];
id(pulse).publish_state((float)id(pulsometer_pulse));
}
if(x[8] != 0) {
id(pulsometer_perf_index) = (float)x[8] / 10.0f;
id(perf_index).publish_state(id(pulsometer_perf_index));
}
}else if (x.size() == 7 && x[0] == 0xAA && x[1] == 0x55 && x[2] == 0xF0 && x[3] == 0x3 && x[4] == 0x3) {
if(x[5] != 0) {
id(pulsometer_battery_current) = x[5];
float battery_percent = ((float)id(pulsometer_battery_current) / 3.0f) * 100.0f;
if(battery_percent < 0.0f) battery_percent = 0.0f;
if(battery_percent > 100.0f) battery_percent = 100.0f;
id(pulsometer_battery).publish_state(battery_percent);
}
}
return {};
- platform: template
id: spo2
name: "SpO2"
icon: 'mdi:lung'
unit_of_measurement: '%'
accuracy_decimals: 0
filters:
- throttle: 1s
lambda: 'return id(pulsometer_spo2);'
- platform: template
id: pulse
name: "Pulse"
icon: 'mdi:heart-pulse'
unit_of_measurement: 'bpm'
accuracy_decimals: 0
filters:
- throttle: 1s
lambda: 'return id(pulsometer_pulse);'
- platform: template
id: perf_index
name: "Perf Index"
icon: 'mdi:waves'
unit_of_measurement: '%'
accuracy_decimals: 1
filters:
- throttle: 1s
lambda: 'return id(pulsometer_perf_index);'
- platform: template
id: pulsometer_battery
name: "Pulsometer Battery"
icon: 'mdi:battery'
unit_of_measurement: '%'
accuracy_decimals: 0
filters:
- throttle: 1s
lambda: |-
if (id(pulsometer_battery_current) == 0) {
return 0.0f;
}
float battery_percent = ((float)id(pulsometer_battery_current) / 3.0f) * 100.0f;
if (battery_percent < 0.0f) battery_percent = 0.0f;
if (battery_percent > 100.0f) battery_percent = 100.0f;
return battery_percent;
text_sensor:
- platform: template
id: pulsometer_status
name: "Pulsometer Status"
icon: 'mdi:bluetooth'
- platform: wifi_info
ip_address:
name: "IP Address"
ssid:
name: "SSID"
mac_address:
name: "Mac Address"
binary_sensor:
- platform: status
name: "Status"
- platform: ble_presence
mac_address: "YOUR_DEVICE_MAC_ADDRESS"
name: "Pulsometer Detected"
id: pulsometer_detected