Files
supersensor2/supersensor.yaml
Joshua M. Boniface 3300ca2d8e Switch to SGP41 air quality sensor
My testing with the SGP30 sensor proved fruitless, as various sensors
would mis-report (under- and over-) levels and generally not be
consistent with each other or reality. These sensors are simply too
flaky with zero consistency.

Instead, switch to the SGP41, which so far seems more robust and is used
in other tools like the AirGradient One. We leverage their calculations
for VOC Index -> VOC levels, as well as a generalized isobutylene-based
eCO2 calculation.
2025-06-21 23:48:42 -04:00

1179 lines
33 KiB
YAML

---
###############################################################################
# SuperSensor v2.0 ESPHome configuration
###############################################################################
#
# Copyright (C) 2025 Joshua M. Boniface <joshua@boniface.me>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
###############################################################################
esphome:
name: supersensor
name_add_mac_suffix: true
friendly_name: "Supersensor"
project:
name: joshuaboniface.supersensor
version: "2.0"
min_version: 2025.5.0
on_boot:
- priority: 600
then:
- lambda: |-
id(supersensor_occupancy).publish_state(false);
id(pir_presence).publish_state(false);
id(light_presence).publish_state(false);
id(radar_presence).publish_state(false);
- priority: -100
then:
- if:
condition:
- switch.is_on: enable_voice_support
then:
- micro_wake_word.start:
preferences:
flash_write_interval: 15sec
dashboard_import:
package_import_url: github://joshuaboniface/supersensor2/supersensor.yaml
esp32:
board: esp32dev
framework:
type: esp-idf
sdkconfig_options:
CONFIG_ESP32_DEFAULT_CPU_FREQ_240: "y"
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ: "240"
CONFIG_ESP32_DATA_CACHE_64KB: "y"
CONFIG_ESP32_DATA_CACHE_LINE_64B: "y"
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ: "240"
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
CONFIG_SPIRAM_CACHE_WORKAROUND: "y"
CONFIG_OPTIMIZATION_LEVEL_RELEASE: "y"
globals:
# Defaults to -5 due to heating from the ESP
- id: temperature_offset
type: float
restore_value: true
initial_value: "-5.0"
- id: humidity_offset
type: float
restore_value: true
initial_value: "0.0"
- id: pir_hold_time
type: int
restore_value: true
initial_value: "15"
- id: light_presence_threshold
type: int
restore_value: true
initial_value: "30"
- id: occupancy_detect_mode
type: int
restore_value: true
initial_value: "0"
- id: occupancy_clear_mode
type: int
restore_value: true
initial_value: "0"
- id: last_api_connected_time
type: uint32_t
restore_value: no
initial_value: "0"
- id: current_wake_word
type: std::string
restore_value: yes
initial_value: '"mww_computer"'
script:
- id: light_off
then:
if:
condition:
- binary_sensor.is_on: supersensor_occupancy
- switch.is_on: enable_presence_led
then:
- light.turn_on:
id: output_led
brightness: 15%
red: 1
green: 1
blue: 1
transition_length: 1s
else:
- light.turn_off:
id: output_led
transition_length: 1s
- id: pir_handler
then:
- lambda: |-
id(pir_presence).publish_state(true);
- while:
condition:
binary_sensor.is_on: pir_gpio
then:
- delay: !lambda 'return(id(pir_hold_time) * 1000);'
- lambda: |-
id(pir_presence).publish_state(false);
- id: light_handler
then:
- lambda: |-
if (id(tsl2591_lux).state >= id(light_presence_threshold)) {
id(light_presence).publish_state(true);
} else {
id(light_presence).publish_state(false);
}
- id: occupancy_detect_handler
then:
- lambda: |-
ESP_LOGD("occupancy_detect_handler", "Occupancy detect handler triggered");
// Get the current values of our presence sensors
bool pir = id(pir_presence).state;
bool radar = id(radar_presence).state;
bool light = id(light_presence).state;
// Determine if PIR counts (2nd bit of presence_type)
int pir_counts = (id(occupancy_detect_mode) & ( 1 << 2 )) >> 2;
// Determine if Radar counts (1st bit of presence_type)
int radar_counts = (id(occupancy_detect_mode) & ( 1 << 1 )) >> 1;
// Determine if Light counts (0th bit of presence_type)
int light_counts = (id(occupancy_detect_mode) & ( 1 << 0 )) >> 0;
// Determine our results
bool new_state = false;
if (pir_counts & radar_counts & light_counts) {
// Logical AND of pir & radar & light
new_state = pir & radar & light;
} else if (pir_counts & radar_counts) {
// Logical AND of pir & radar
new_state = pir & radar;
} else if (pir_counts & light_counts) {
// Logical AND of pir & light
new_state = pir & light;
} else if (radar_counts & light_counts) {
// Logical AND of radar & light
new_state = radar & light;
} else if (pir_counts) {
// Only pir
new_state = pir;
} else if (radar_counts) {
// Only radar
new_state = radar;
} else if (light_counts) {
// Only light
new_state = light;
}
ESP_LOGD("occupancy_detect_handler", "New state: %s", new_state ? "true" : "false");
// Force update even if state hasn't changed
id(supersensor_occupancy).publish_state(new_state);
// Add a delayed re-publish to ensure state propagation
if (new_state) {
id(supersensor_occupancy).publish_state(new_state);
}
- id: occupancy_clear_handler
then:
- lambda: |-
ESP_LOGD("occupancy_clear_handler", "Occupancy clear handler triggered");
// Get the current values of our presence sensors
bool pir = id(pir_presence).state;
bool radar = id(radar_presence).state;
bool light = id(light_presence).state;
// Determine if PIR counts (2nd bit of presence_type)
int pir_counts = (id(occupancy_clear_mode) & ( 1 << 2 )) >> 2;
// Determine if Radar counts (1st bit of presence_type)
int radar_counts = (id(occupancy_clear_mode) & ( 1 << 1 )) >> 1;
// Determine if Light counts (0th bit of presence_type)
int light_counts = (id(occupancy_clear_mode) & ( 1 << 0 )) >> 0;
// Determine our results
bool new_state = false;
if (pir_counts & radar_counts & light_counts) {
// Logical AND of pir & radar & light
new_state = pir & radar & light;
} else if (pir_counts & radar_counts) {
// Logical AND of pir & radar
new_state = pir & radar;
} else if (pir_counts & light_counts) {
// Logical AND of pir & light
new_state = pir & light;
} else if (radar_counts & light_counts) {
// Logical AND of radar & light
new_state = radar & light;
} else if (pir_counts) {
// Only pir
new_state = pir;
} else if (radar_counts) {
// Only radar
new_state = radar;
} else if (light_counts) {
// Only light
new_state = light;
}
ESP_LOGD("occupancy_clear_handler", "New state: %s", new_state ? "true" : "false");
// Force update even if state hasn't changed
id(supersensor_occupancy).publish_state(new_state);
// Add a delayed re-publish to ensure state propagation
if (!new_state) {
id(supersensor_occupancy).publish_state(new_state);
}
interval:
# Regular MWW state check every 30s
- interval: 30s
then:
- if:
condition:
- switch.is_on: enable_voice_support
- not:
micro_wake_word.is_running
then:
- micro_wake_word.start:
# Regular occupancy state reporting to HASS every 30s
- interval: 30s
then:
- lambda: |-
bool current_state = id(supersensor_occupancy).state;
ESP_LOGD("state_reporter", "Republishing occupancy state: %s", current_state ? "true" : "false");
id(supersensor_occupancy).publish_state(current_state);
# API watchdog every 5 minutes
- interval: 300s
then:
- lambda: |-
if (api::global_api_server->is_connected()) {
id(last_api_connected_time) = millis();
} else if (millis() - id(last_api_connected_time) > 300000) {
ESP_LOGE("api_watchdog", "API disconnected for too long, rebooting...");
App.safe_reboot();
}
logger:
level: DEBUG
baud_rate: 115200
api:
reboot_timeout: 15min
# on_client_connected:
# - logger.log:
# format: "Client %s (IP %s) connected to API"
# args: ["client_info.c_str()", "client_address.c_str()"]
# - script.execute: light_off
# - if:
# condition:
# lambda: |-
# return id(enable_voice_support).state &&
# !id(mww).is_running();
# then:
# - micro_wake_word.start:
# on_client_disconnected:
# - logger.log:
# format: "Client %s (IP %s) disconnected from API"
# args: ["client_info.c_str()", "client_address.c_str()"]
# - if:
# condition:
# lambda: |-
# return id(enable_voice_support).state &&
# id(mww).is_running();
# then:
# - micro_wake_word.stop:
# - light.turn_on:
# id: output_led
# effect: flash_white
ota:
platform: esphome
web_server:
port: 80
captive_portal:
mdns:
disabled: false
wifi:
ap: {}
domain: ""
output_power: 8.5dB
reboot_timeout: 15min
power_save_mode: none
time:
- platform: homeassistant
id: homeassistant_time
on_time_sync:
then:
- logger.log: "Time synchronized with Home Assistant"
debug:
update_interval: 15s
uart:
id: ld2410_uart
rx_pin: GPIO19
tx_pin: GPIO18
baud_rate: 256000
data_bits: 8
stop_bits: 1
parity: NONE
i2c:
sda: GPIO27
scl: GPIO26
scan: true
i2s_audio:
- id: i2s_input
i2s_lrclk_pin:
number: GPIO17 # WS
i2s_bclk_pin:
number: GPIO16 # SCK
microphone:
- platform: i2s_audio
id: mic
i2s_audio_id: i2s_input
i2s_din_pin: GPIO4 # SD
adc_type: external
pdm: false
channel: left
micro_wake_word:
id: mww
microphone:
microphone: mic
gain_factor: 4
stop_after_detection: false
models:
- model: github://genehand/Custom_V2_MicroWakeWords/models/computer/computer.json@update-json
id: mww_computer
- model: github://esphome/micro-wake-word-models/models/v2/hey_jarvis.json
id: mww_hey_jarvis
- model: github://esphome/micro-wake-word-models/models/v2/hey_mycroft.json
id: mww_hey_mycroft
- model: github://esphome/micro-wake-word-models/models/v2/okay_nabu.json
id: mww_okay_nabu
- model: github://esphome/micro-wake-word-models/models/v2/alexa.json
id: mww_alexa
vad:
on_wake_word_detected:
- logger.log: "A wake word was detected!"
- if:
condition:
voice_assistant.is_running:
then:
voice_assistant.stop:
- voice_assistant.start:
wake_word: !lambda return wake_word;
voice_assistant:
id: va
microphone: mic
micro_wake_word: mww
use_wake_word: false
noise_suppression_level: 3
auto_gain: 31 dbfs
volume_multiplier: 4
on_wake_word_detected:
- logger.log: "Wake word detected in VA pipeline"
- light.turn_on:
id: output_led
brightness: 100%
red: 0
green: 0
blue: 1
on_listening:
- logger.log: "Listening for commands"
- light.turn_on:
id: output_led
brightness: 100%
red: 0
green: 0
blue: 1
on_stt_vad_end:
- logger.log: "Processing STT result"
- light.turn_on:
id: output_led
brightness: 75%
red: 0
green: 1
blue: 1
on_tts_start:
- if:
condition:
- lambda: |-
ESP_LOGI("tts_response", "%s", x.c_str());
return x.rfind("Sorry", 0) == 0;
then:
- logger.log: "Command failed!"
- light.turn_on:
id: output_led
effect: hold
brightness: 100%
red: 1
green: 0
blue: 0
else:
- logger.log: "Command successful!"
- light.turn_on:
id: output_led
effect: hold
brightness: 100%
red: 0
green: 1
blue: 0
light:
- platform: rgb
id: output_led
red: rgb_r
green: rgb_g
blue: rgb_b
default_transition_length: 0.15s
flash_transition_length: 0.15s
effects:
- strobe:
name: flash_white
colors:
- state: true
brightness: 15%
red: 100%
green: 90%
blue: 90%
duration: 0.5s
- state: false
duration: 0.5s
- automation:
name: hold
sequence:
- delay: 5s
- script.execute: light_off
output:
- platform: ledc
id: rgb_r
pin: GPIO23
- platform: ledc
id: rgb_g
pin: GPIO22
- platform: ledc
id: rgb_b
pin: GPIO21
ld2410:
id: ld2410_radar
uart_id: ld2410_uart
sensor:
- platform: uptime
name: "ESP32 Uptime"
icon: mdi:clock-alert
update_interval: 5s
entity_category: diagnostic
- platform: wifi_signal
name: "WiFi RSSI"
icon: mdi:wifi-strength-2
update_interval: 5s
entity_category: diagnostic
- platform: internal_temperature
name: "ESP32 Temperature"
icon: mdi:thermometer
unit_of_measurement: °C
device_class: TEMPERATURE
update_interval: 5s
entity_category: diagnostic
- platform: template
name: "ESP32 Free Memory"
icon: mdi:memory
unit_of_measurement: 'kB'
state_class: measurement
update_interval: 5s
lambda: |-
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024;
entity_category: diagnostic
- platform: debug
free:
name: "Heap Free"
block:
name: "Heap Max Block"
loop_time:
name: "Loop Time"
cpu_frequency:
name: "CPU Frequency"
- platform: sgp4x
voc:
name: "SGP41 VOC Index"
id: sgp41_voc_index
accuracy_decimals: 0
icon: mdi:waves-arrow-up
filters:
- sliding_window_moving_average: # We take a reading every 15 seconds, but calculate the sliding
window_size: 12 # average over 12 readings i.e. 60 seconds/1 minute to normalize
send_every: 3 # brief spikes while still sending a value every 15 seconds.
nox:
name: "SGP41 NOx Index"
id: sgp41_nox_index
accuracy_decimals: 0
icon: mdi:waves-arrow-up
filters:
- sliding_window_moving_average:
window_size: 12
send_every: 3
compensation:
temperature_source: sht45_temperature
humidity_source: sht45_humidity
store_baseline: true
update_interval: 5s
- platform: template
name: "SGP41 TVOC (µg/m³)"
id: sgp41_tvoc_ugm3
icon: mdi:molecule
lambda: |-
float i = id(sgp41_voc_index).state;
if (i < 1) return NAN;
float tvoc = (log(501.0 - i) - 6.24) * -878.53;
return tvoc;
unit_of_measurement: "µg/m³"
accuracy_decimals: 0
- platform: template
name: "SGP41 TVOC (ppb)"
id: sgp41_tvoc_ppb
icon: mdi:molecule
lambda: |-
float tvoc_ugm3 = id(sgp41_tvoc_ugm3).state;
float tvoc_ppm = tvoc_ugm3 * 0.436; // ppb estimated using isobutylene MW (56.1 g/mol)
return tvoc_ppm;
unit_of_measurement: "ppb"
accuracy_decimals: 0
- platform: template
name: "SGP41 eCO2 (appr.)"
id: sgp41_eco2_appr
icon: mdi:molecule-co2
lambda: |-
float tvoc_ppb = id(sgp41_tvoc_ppb).state;
float eco2_ppm = 400.0 + 1.5 * tvoc_ppb;
if (eco2_ppm > 2000) eco2_ppm = 2000;
return eco2_ppm;
unit_of_measurement: "ppm"
accuracy_decimals: 0
- platform: sht4x
temperature:
name: "SHT45 Temperature"
id: sht45_temperature
accuracy_decimals: 1
filters:
- offset: !lambda return id(temperature_offset);
- sliding_window_moving_average:
window_size: 20
send_every: 1
humidity:
name: "SHT45 Relative Humidity"
id: sht45_humidity
accuracy_decimals: 1
filters:
- offset: !lambda return id(humidity_offset);
- sliding_window_moving_average:
window_size: 20
send_every: 1
heater_max_duty: 0.0
update_interval: 15s
- platform: absolute_humidity
name: "SHT45 Absolute Humidity"
temperature: sht45_temperature
humidity: sht45_humidity
id: sht45_absolute_humidity
# Dew Point
- platform: template
name: "SHT45 Dew Point"
icon: mdi:thermometer-water
id: sht45_dew_point
unit_of_measurement: "°C"
lambda: |-
float temp = id(sht45_temperature).state;
float rh = id(sht45_humidity).state;
if (isnan(temp) || isnan(rh)) return NAN;
float a = 17.27, b = 237.7;
float alpha = ((a * temp) / (b + temp)) + log(rh / 100.0);
return (b * alpha) / (a - alpha);
update_interval: 15s
- platform: tsl2591
address: 0x29
update_interval: 1s
integration_time: 200ms
power_save_mode: no
gain: auto
device_factor: 53
glass_attenuation_factor: 7.7
visible:
name: "TSL2591 Raw Visible"
infrared:
name: "TSL2591 Raw Infrared"
full_spectrum:
name: "TSL2591 Raw Full Spectrum"
calculated_lux:
id: tsl2591_lux
name: "TSL2591 Illumination"
unit_of_measurement: lx
accuracy_decimals: 1
on_value:
- script.execute: light_handler
actual_gain:
id: "actual_gain"
name: "TSL2591 Gain"
- platform: ld2410
ld2410_id: ld2410_radar
moving_distance:
name: "LD2410C Moving Distance"
id: moving_distance
icon: mdi:signal-distance-variant
still_distance:
name: "LD2410C Still Distance"
id: still_distance
icon: mdi:signal-distance-variant
moving_energy:
name: "LD2410C Move Energy"
icon: mdi:flash
still_energy:
name: "LD2410C Still Energy"
icon: mdi:flash
detection_distance:
name: "LD2410C Presence Distance"
icon: mdi:signal-distance-variant
binary_sensor:
- platform: template
name: "SuperSensor Occupancy"
id: supersensor_occupancy
device_class: occupancy
on_press:
- script.execute: light_off
on_release:
- script.execute: light_off
- platform: gpio
name: "PIR GPIO"
id: pir_gpio
pin:
number: GPIO32
mode: INPUT_PULLUP
internal: false
device_class: motion
on_press:
- script.stop: pir_handler
- script.execute: pir_handler
- platform: template
name: "PIR Presence"
id: pir_presence
device_class: motion
on_press:
- script.execute: occupancy_detect_handler
on_release:
- script.execute: occupancy_clear_handler
- platform: template
name: "Light Presence"
id: light_presence
device_class: motion
on_press:
- script.execute: occupancy_detect_handler
on_release:
- script.execute: occupancy_clear_handler
- platform: ld2410
ld2410_id: ld2410_radar
has_target:
name: "LD2410C Presence"
id: radar_presence
icon: mdi:motion-sensor
device_class: motion
on_press:
- script.execute: occupancy_detect_handler
on_release:
- script.execute: occupancy_clear_handler
has_moving_target:
name: "LD2410C Moving Target"
has_still_target:
name: "LD2410C Still Target"
text_sensor:
- platform: version
name: "ESPHome Version"
entity_category: diagnostic
- platform: wifi_info
ip_address:
name: "WiFi IP Address"
ssid:
name: "WiFi SSID"
bssid:
name: "WiFi BSSID"
mac_address:
name: "WiFi MAC Address"
- platform: debug
device:
name: "Device Info"
reset_reason:
name: "Reset Reason"
- platform: ld2410
version:
name: "LD2410C Firmware Version"
mac_address:
name: "LD2410C MAC Address"
restart:
name: "LD2410C Restart"
icon: mdi:power-cycle
entity_category: diagnostic
factory_reset:
name: "LD2410C Factory Reset"
icon: mdi:restart-alert
entity_category: diagnostic
- platform: restart
name: "ESP32 Restart"
icon: mdi:power-cycle
entity_category: diagnostic
- platform: factory_reset
name: "ESP32 Factory Reset"
icon: mdi:restart-alert
entity_category: diagnostic
switch:
# Global enable/disable for voice support
- platform: template
name: "Enable Voice Support"
icon: mdi:account-voice
id: enable_voice_support
optimistic: true
restore_mode: RESTORE_DEFAULT_OFF
on_turn_on:
- micro_wake_word.start:
on_turn_off:
- micro_wake_word.stop:
# Global enable/disable for presence LED
- platform: template
name: "Enable Presence LED"
icon: mdi:lightbulb-alert
id: enable_presence_led
optimistic: true
restore_mode: RESTORE_DEFAULT_ON
on_turn_on:
- script.execute: light_off
on_turn_off:
- script.execute: light_off
- platform: ld2410
engineering_mode:
name: "LD2410C Engineering Mode"
entity_category: diagnostic
bluetooth:
name: "LD2410C Bluetooth"
entity_category: diagnostic
number:
# Temperature offset:
# A calibration from -30 to +5 for the temperature sensor
- platform: template
name: "Temperature Offset"
id: temperature_offset_setter
min_value: -30
max_value: 10
step: 0.1
lambda: |-
return id(temperature_offset);
set_action:
then:
- globals.set:
id: temperature_offset
value: !lambda 'return float(x);'
# Humidity offset:
# A calibration from -20 to +20 for the humidity sensor
- platform: template
name: "Humidity Offset"
id: humidity_offset_setter
min_value: -20
max_value: 20
step: 0.1
lambda: |-
return id(humidity_offset);
set_action:
then:
- globals.set:
id: humidity_offset
value: !lambda 'return float(x);'
# PIR Hold Time:
# The number of seconds after motion detection for the PIR sensor to remain held on
- platform: template
name: "PIR Hold Time"
id: pir_hold_time_setter
min_value: 0
max_value: 60
step: 5
lambda: |-
return id(pir_hold_time);
set_action:
then:
- globals.set:
id: pir_hold_time
value: !lambda 'return int(x);'
# Light Presence Threshold
# The minimum Lux value to consider presence based on the ambient light level
- platform: template
name: "Light Presence Threshold"
id: light_presence_threshold_setter
min_value: 0
max_value: 200
step: 5
lambda: |-
return id(light_presence_threshold);
set_action:
then:
- globals.set:
id: light_presence_threshold
value: !lambda 'return int(x);'
- platform: ld2410
timeout:
name: "LD2410C Timeout"
light_threshold:
name: "LD2410C Light Threshold"
max_move_distance_gate:
name: "LD2410C Max Move Distance Gate"
max_still_distance_gate:
name: "LD2410C Max Still Distance Gate"
g0:
move_threshold:
name: "LD2410C Gate0 Move Threshold"
still_threshold:
name: "LD2410C Gate0 Still Threshold"
g1:
move_threshold:
name: "LD2410C Gate1 Move Threshold"
still_threshold:
name: "LD2410C Gate1 Still Threshold"
g2:
move_threshold:
name: "LD2410C Gate2 Move Threshold"
still_threshold:
name: "LD2410C Gate2 Still Threshold"
g3:
move_threshold:
name: "LD2410C Gate3 Move Threshold"
still_threshold:
name: "LD2410C Gate3 Still Threshold"
g4:
move_threshold:
name: "LD2410C Gate4 Move Threshold"
still_threshold:
name: "LD2410C Gate4 Still Threshold"
g5:
move_threshold:
name: "LD2410C Gate5 Move Threshold"
still_threshold:
name: "LD2410C Gate5 Still Threshold"
g6:
move_threshold:
name: "LD2410C Gate6 Move Threshold"
still_threshold:
name: "LD2410C Gate6 Still Threshold"
g7:
move_threshold:
name: "LD2410C Gate7 Move Threshold"
still_threshold:
name: "LD2410C Gate7 Still Threshold"
g8:
move_threshold:
name: "LD2410C Gate8 Move Threshold"
still_threshold:
name: "LD2410C Gate8 Still Threshold"
select:
# Occupancy Detect Mode:
# This selector defines the detection mode for the integrated occupancy sensor. Depending on the
# selected option, only the given sensor(s) will be used to judge when occupancy begins (i.e.
# when the given sensor(s) detect, occupancy detects).
# * PIR + Radar + Light:
# All 3 sensors reporting detection simultaneously will begin occupancy
# * PIR + Radar
# Both PIR and Radar sensors reporting detection simultaneously will begin occupancy
# * PIR + Light
# Both PIR and Light sensors reporting detection simultaneously will begin occupancy
# * Radar + Light
# Both Radar and Light sensors reporting detection simultaneously will begin occupancy
# * PIR Only
# PIR sensor reporting detection will begin occupancy
# * Radar Only
# Radar sensor reporting detection will begin occupancy
# * Light Only
# Light sensor reporting detection will begin occupancy
# * None
# No sensors will begin occupancy and the integrated occupancy functionality is disabled
# Values are reported as integers using bitwise logic:
# Bit 2: PIR
# Bit 1: Radar
# Bit 0: Light
- platform: template
name: "Occupancy Detect Mode"
id: occupancy_detect_mode_setter
options:
- "PIR + Radar + Light" # 111 = 7
- "PIR + Radar" # 110 = 6
- "PIR + Light" # 101 = 5
- "Radar + Light" # 011 = 3
- "PIR Only" # 100 = 4
- "Radar Only" # 010 = 2
- "Light Only" # 001 = 1
- "None" # 000 = 0
initial_option: "None"
optimistic: true
restore_value: true
set_action:
- globals.set:
id: occupancy_detect_mode
value: !lambda |-
ESP_LOGD("occupancy_detect_mode_setter", x.c_str());
if (x == "PIR + Radar + Light") {
return 7;
} else if (x == "PIR + Radar") {
return 6;
} else if (x == "PIR + Light") {
return 5;
} else if (x == "Radar + Light") {
return 3;
} else if (x == "PIR Only") {
return 4;
} else if (x == "Radar Only") {
return 2;
} else if (x == "Light Only") {
return 1;
} else {
return 0;
}
# Occupancy Clear Mode:
# This selector defines the clear mode for the integrated occupancy sensor. Depending on the
# selected option, only the given sensor(s) will be used to judge when occupancy ends (i.e.
# when the given sensor(s) clear, occupancy clears).
# * PIR + Radar + Light:
# Any of the 3 sensors clearing will end occupancy
# * PIR + Radar:
# Either of the PIR or Radar sensors clearing will end occupancy
# * PIR + Light:
# Either of the PIR or Light sensors clearing will end occupancy
# * Radar + Light:
# Either of the Radar or Light sensors clearing will end occupancy
# * PIR Only
# PIR sensor clearing will end occupancy
# * Radar Only
# Radar sensor clearing will end occupancy
# * Light Only
# Light sensor clearing will end occupancy
# * None
# No sensors will end occupancy; state will persist indefinitely once triggered
# Values are reported as integers using bitwise logic:
# Bit 0: PIR
# Bit 1: Radar
# Bit 2: Light
- platform: template
name: "Occupancy Clear Mode"
id: occupancy_clear_mode_setter
options:
- "PIR + Radar + Light" # 111 = 7
- "PIR + Radar" # 110 = 6
- "PIR + Light" # 101 = 5
- "Radar + Light" # 011 = 3
- "PIR Only" # 100 = 4
- "Radar Only" # 010 = 2
- "Light Only" # 001 = 1
- "None" # 000 = 0
initial_option: "None"
optimistic: true
restore_value: true
set_action:
- globals.set:
id: occupancy_clear_mode
value: !lambda |-
ESP_LOGD("occupancy_detect_mode_setter", x.c_str());
if (x == "PIR + Radar + Light") {
return 7;
} else if (x == "PIR + Radar") {
return 6;
} else if (x == "PIR + Light") {
return 5;
} else if (x == "Radar + Light") {
return 3;
} else if (x == "PIR Only") {
return 4;
} else if (x == "Radar Only") {
return 2;
} else if (x == "Light Only") {
return 1;
} else {
return 0;
}
- platform: ld2410
distance_resolution:
name: "LD2410C Distance Resolution"
- platform: template
name: "Wake Word Selector"
id: wake_word_selector
options:
- "Computer"
- "Hey Jarvis"
- "Hey Mycroft"
- "Okay Nabu"
- "Alexa"
initial_option: "Computer"
optimistic: true
restore_value: true
set_action:
# Disable models that aren't selected
- if:
condition:
lambda: 'return x != "Computer";'
then:
- micro_wake_word.disable_model: mww_computer
- if:
condition:
lambda: 'return x != "Hey Jarvis";'
then:
- micro_wake_word.disable_model: mww_hey_jarvis
- if:
condition:
lambda: 'return x != "Hey Mycroft";'
then:
- micro_wake_word.disable_model: mww_hey_mycroft
- if:
condition:
lambda: 'return x != "Okay Nabu";'
then:
- micro_wake_word.disable_model: mww_okay_nabu
- if:
condition:
lambda: 'return x != "Alexa";'
then:
- micro_wake_word.disable_model: mww_alexa
# Enable model we selected
- if:
condition:
lambda: 'return x == "Computer";'
then:
- micro_wake_word.enable_model: mww_computer
- if:
condition:
lambda: 'return x == "Hey Jarvis";'
then:
- micro_wake_word.enable_model: mww_hey_jarvis
- if:
condition:
lambda: 'return x == "Hey Mycroft";'
then:
- micro_wake_word.enable_model: mww_hey_mycroft
- if:
condition:
lambda: 'return x == "Okay Nabu";'
then:
- micro_wake_word.enable_model: mww_okay_nabu
- if:
condition:
lambda: 'return x == "Alexa";'
then:
- micro_wake_word.enable_model: mww_alexa
- platform: template
name: "Wake Word Sensitivity"
optimistic: true
initial_option: Default
restore_value: true
options:
- Default
- More sensitive
- Very sensitive
set_action:
# Sets specific wake word probabilities computed for each particular model
# Note probability cutoffs are set as a quantized uint8 value, each comment has the corresponding floating point cutoff
- lambda: |-
if (x == "Default") {
id(mww_computer).set_probability_cutoff(168); // 0.66 (default)
id(mww_hey_jarvis).set_probability_cutoff(247); // 0.97 (default)
id(mww_hey_mycroft).set_probability_cutoff(242); // 0.95 (default)
id(mww_okay_nabu).set_probability_cutoff(217); // 0.85 (default)
id(mww_alexa).set_probability_cutoff(217); // 0.85 (default)
} else if (x == "More sensitive") {
id(mww_computer).set_probability_cutoff(153); // 0.60
id(mww_hey_jarvis).set_probability_cutoff(235); // 0.92
id(mww_hey_mycroft).set_probability_cutoff(237); // 0.93
id(mww_okay_nabu).set_probability_cutoff(176); // 0.69
id(mww_alexa).set_probability_cutoff(176); // 0.69
} else if (x == "Very sensitive") {
id(mww_computer).set_probability_cutoff(138); // 0.54
id(mww_hey_jarvis).set_probability_cutoff(212); // 0.83
id(mww_hey_mycroft).set_probability_cutoff(230); // 0.90
id(mww_okay_nabu).set_probability_cutoff(143); // 0.56
id(mww_alexa).set_probability_cutoff(143); // 0.56
}