ESPHome configuration for connecting the CONTEC PC-60FW Bluetooth pulse 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'