Files
fresh-home/esphome/bed-presence/sensor.yaml
T
2025-06-29 22:39:39 -04:00

240 lines
8.5 KiB
YAML

substitutions:
# Calibrate Trigger Percentile
# This controls how the trigger threshold is set during calibration. During calibration, the unoccupied and occupied
# pressure values are determined. This is the percentage of the difference between those values required to trigger
# the "occupied" state.
# - 0.75 Requires 75% of the occupied pressure to trigger (DEFAULT)
# - 0.50 Set trigger exactly in the middle of unoccupied and occupied pressures
# - 0.25 More sensitive, likely to be triggered by partner
trigger_percentile: '0.75'
# Window Averaging Samples
# Add a sliding window moving average to the incoming sensor samples. This specifies the number of samples to average
# over. Each sample is 1/2 second.
# - 1 Don't perform averaging
# - 5 Average over 5 samples (2.5 seconds) (DEFAULT)
# - 10 Average over 10 samples (5 seconds)
averaging_window_samples: '5'
# Fast Sensor Delay
# This controls how long the "Fast" sensor must register "unoccupied" or "occupied" before reporting. Averaging or
# delay is encouraged to prevent frequent false negatives as you shift in bed.
# - 2s Same response as standard sensor
# - 500ms Good starting point for getting speed without too many false negatives
# - 0ms No delay (DEFAULT)
fast_delayed_on: '0ms'
fast_delayed_off: '0ms'
# Standard Sensor Delay
# This controls how long the standard sensor must register "unoccupied" or "occupied" before reporting. Averaging or
# delay is enocuraged to ensure stability.
standard_delayed_on: '0s'
standard_delayed_off: '1s'
# Slow Sensor Delay
# This controls how long the "Slow" sensor must register "unoccupied" or "occupied" before reporting. Averaging or
# delay is encouraged to ensure stability.
slow_delayed_on: '2s'
slow_delayed_off: '5s'
# Reporting Delta
# The sensor must change by this amount before an update is sent (or the reporting_interval_max has been hit)
# - 10.0 Only report very large changes
# - 1.0 Relatively sensitive with much less reporting (DEFAULT)
# - 0.1 Very sensitive, will report frequently
reporting_delta: '1.0'
# Reporting Interval (Max)
# The max amount of time between sensor reports (even if the value hasn't changed)
reporting_interval_max: '180s'
# Linear Calibration Points
# Change the linear calibration points for the FSR sensor. These values correspond with the raw pulses per minute from
# the pulse_counter. Only mess with this if you know what you're doing. In standard mode (Full Range = False), the
# sensor is linearly calibrated between calibrate_0 (0%) and calibreate_100 (100%). When Full Range = True,
# calibrate_0 is set to 0.
calibrate_100: '408000'
calibrate_0: '276000'
binary_sensor:
- platform: template
name: Bed Occupied ${sensor_name} (Fast)
id: bed_occupied_${sensor_id}_fast
device_class: occupancy
icon: mdi:speedometer
disabled_by_default: true
filters:
- delayed_on: ${fast_delayed_on}
- delayed_off: ${fast_delayed_off}
lambda: return id(bed_sensor_${sensor_id}).state > id(val_trigger_${sensor_id}).state;
- platform: template
name: Bed Occupied ${sensor_name} (Normal)
id: bed_occupied_${sensor_id}_normal
device_class: occupancy
icon: mdi:speedometer-medium
disabled_by_default: true
filters:
- delayed_on: ${standard_delayed_on}
- delayed_off: ${standard_delayed_off}
lambda: return id(bed_sensor_${sensor_id}).state > id(val_trigger_${sensor_id}).state;
- platform: template
id: bed_occupied_${sensor_id}_slow
name: Bed Occupied ${sensor_name} (Slow)
device_class: occupancy
icon: mdi:speedometer-slow
disabled_by_default: true
filters:
- delayed_on: ${slow_delayed_on}
- delayed_off: ${slow_delayed_off}
lambda: return id(bed_sensor_${sensor_id}).state > id(val_trigger_${sensor_id}).state;
- platform: template
name: Bed Occupied ${sensor_name}
id: bed_occupied_${sensor_id}
device_class: occupancy
icon: mdi:bed
lambda: |- # Sensor response is user tunable via "Response Speed"
if (id(response_speed).state == "Fast") {
return id(bed_occupied_${sensor_id}_fast).state;
} else if (id(response_speed).state == "Slow") {
return id(bed_occupied_${sensor_id}_slow).state;
} else {
return id(bed_occupied_${sensor_id}_normal).state;
}
sensor:
- platform: pulse_counter
pin: ${sensor_gpio}
name: ${sensor_name} Pressure
id: bed_sensor_${sensor_id}
update_interval: 0.5s
unit_of_measurement: '%'
icon: mdi:gauge
filters:
- lambda: |- # scale pulses/minute to 0%->100%
float cal_0 = id(full_range).state ? 0 : ${calibrate_0};
float val = (x - cal_0) * 100;
float range = ${calibrate_100} - cal_0;
return val/range;
- clamp:
min_value: 0
- sliding_window_moving_average:
window_size: ${averaging_window_samples}
send_every: 1
- or:
- delta: ${reporting_delta} # only send if sensor changes by `reporting_delta` (eliminate sensor noise)
- throttle: ${reporting_interval_max} # but still update every `reporting_interval_max`
- platform: copy
source_id: bed_sensor_${sensor_id}
name: Calibrated ${sensor_name} Pressure
id: bed_sensor_${sensor_id}_calibrated
icon: mdi:scale-balance
disabled_by_default: true
filters:
- lambda: |- # scale between val_unoccupied and val_occupied
float cal_0 = id(val_unoccupied_${sensor_id}).state;
float cal_100 = id(val_occupied_${sensor_id}).state;
float val = (x - cal_0) * 100;
float range = cal_100 - cal_0;
return val/range;
- clamp:
max_value: 100
min_value: 0
number:
- platform: template
name: ${sensor_name} Unoccupied Pressure
id: val_unoccupied_${sensor_id}
icon: mdi:gauge-empty
unit_of_measurement: '%'
entity_category: diagnostic
optimistic: true
restore_value: true
initial_value: 0
min_value: 0
max_value: 120
step: 1.0
mode: box
on_value:
then:
- lambda: |- # Update trigger
id(update_trigger_${sensor_id})->execute();
- platform: template
name: ${sensor_name} Occupied Pressure
id: val_occupied_${sensor_id}
icon: mdi:gauge-full
unit_of_measurement: '%'
entity_category: diagnostic
optimistic: true
restore_value: true
initial_value: 100
min_value: 0
max_value: 120
step: 1.0
mode: box
on_value:
then:
- lambda: |- # Update trigger
id(update_trigger_${sensor_id})->execute();
- platform: template
name: ${sensor_name} Trigger Pressure
id: val_trigger_${sensor_id}
optimistic: true
restore_value: true
initial_value: 50
min_value: 0
max_value: 120
step: 1.0
mode: box
icon: mdi:gauge
unit_of_measurement: '%'
entity_category: config
button:
- platform: template
name: Calibrate ${sensor_name} Unoccupied
id: calibration_${sensor_id}_set_unoccupied
icon: mdi:bed-empty
entity_category: config
on_press:
then:
- number.set:
id: val_unoccupied_${sensor_id}
# round to 2 decimal places
value: !lambda return round(id(bed_sensor_${sensor_id}).state * 100)/100.0;
- platform: template
name: Calibrate ${sensor_name} Occupied
id: calibration_${sensor_id}_set_occupied
icon: mdi:bed
entity_category: config
on_press:
then:
- number.set:
id: val_occupied_${sensor_id}
# round to 2 decimal places
value: !lambda return round(id(bed_sensor_${sensor_id}).state * 100)/100.0;
script:
- id: update_trigger_${sensor_id}
mode: queued
then:
- lambda: |- # Set trigger to x% of difference between min/max
float unoccupied_pressure = id(val_unoccupied_${sensor_id}).state;
float occupied_pressure = id(val_occupied_${sensor_id}).state;
// calculate new trigger value
float trigger_pressure = unoccupied_pressure + ((occupied_pressure - unoccupied_pressure) * float(${trigger_percentile}));
// round to 2 decimal places
trigger_pressure = round(trigger_pressure * 100)/100.0;
// set value
auto call = id(val_trigger_${sensor_id}).make_call();
call.set_value(trigger_pressure);
call.perform();