With the physical limitations of the SuperSensor, we were able to compensate for the temperature differential due to internal heating from the ESP, but this compensation was not being taken into account by the humidity sensor. Add a compensation factor based on the offset to ensure humidity properly tracks the filtered temperature value.
1508 lines
43 KiB
YAML
1508 lines
43 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:
|
||
- light.turn_on:
|
||
id: output_led
|
||
- delay: 0.25s
|
||
- light.turn_off:
|
||
id: output_led
|
||
- delay: 0.25s
|
||
- light.turn_on:
|
||
id: output_led
|
||
- delay: 0.25s
|
||
- light.turn_off:
|
||
id: output_led
|
||
- 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: room_health_temperature_min
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "21.0"
|
||
|
||
- id: room_health_temperature_max
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "24.0"
|
||
|
||
- id: room_health_temperature_penalty
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "10.0"
|
||
|
||
- id: room_health_humidity_min
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "40.0"
|
||
|
||
- id: room_health_humidity_max
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "60.0"
|
||
|
||
- id: room_health_humidity_penalty
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "5.0"
|
||
|
||
- id: room_health_voc_weight
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "0.4"
|
||
|
||
- id: room_health_temperature_weight
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "0.3"
|
||
|
||
- id: room_health_humidity_weight
|
||
type: float
|
||
restore_value: true
|
||
initial_value: "0.3"
|
||
|
||
- 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"'
|
||
|
||
- id: light_is_holding
|
||
type: bool
|
||
restore_value: no
|
||
initial_value: 'false'
|
||
|
||
script:
|
||
- id: light_off
|
||
then:
|
||
- if:
|
||
condition:
|
||
lambda: 'return !id(light_is_holding);'
|
||
then:
|
||
- if:
|
||
condition:
|
||
- binary_sensor.is_on: supersensor_occupancy
|
||
- switch.is_on: enable_presence_led
|
||
then:
|
||
- light.turn_on:
|
||
id: output_led
|
||
brightness: 25%
|
||
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
|
||
|
||
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
|
||
# # Dummy I2S audio pipeline for "speaker"
|
||
# - id: i2s_dummy
|
||
# i2s_lrclk_pin:
|
||
# number: GPIO15 # WS
|
||
# ignore_strapping_warning: true # This isn't connected to anything anyways
|
||
# i2s_bclk_pin:
|
||
# number: GPIO14 # SCK
|
||
|
||
microphone:
|
||
- platform: i2s_audio
|
||
id: mic
|
||
i2s_audio_id: i2s_input
|
||
i2s_din_pin: GPIO4 # SD
|
||
adc_type: external
|
||
pdm: false
|
||
channel: left
|
||
|
||
#speaker:
|
||
# # Dummy speaker to fix home-assistant/core#142363
|
||
# - platform: i2s_audio
|
||
# id: dummy_speaker
|
||
# i2s_audio_id: i2s_dummy
|
||
# i2s_dout_pin: GPIO25
|
||
# dac_type: external
|
||
# bits_per_sample: 16bit
|
||
# sample_rate: 16000
|
||
# channel: mono #mono will have bad performance, however, we are not using the on device speaker
|
||
# buffer_duration: 60ms
|
||
|
||
micro_wake_word:
|
||
id: mww
|
||
microphone:
|
||
microphone: mic
|
||
gain_factor: 2
|
||
stop_after_detection: false
|
||
models:
|
||
- model: github://joshuaboniface/Custom_V2_MicroWakeWords/models/computer/computer.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
|
||
# speaker: dummy_speaker
|
||
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
|
||
- voice_assistant.stop:
|
||
else:
|
||
- logger.log: "Command successful!"
|
||
- light.turn_on:
|
||
id: output_led
|
||
effect: hold
|
||
brightness: 100%
|
||
red: 0
|
||
green: 1
|
||
blue: 0
|
||
- voice_assistant.stop:
|
||
on_tts_end:
|
||
- logger.log: "Finished STT result"
|
||
- voice_assistant.stop:
|
||
|
||
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: 25%
|
||
red: 100%
|
||
green: 90%
|
||
blue: 90%
|
||
duration: 0.5s
|
||
- state: false
|
||
duration: 0.5s
|
||
- automation:
|
||
name: hold
|
||
sequence:
|
||
- globals.set:
|
||
id: light_is_holding
|
||
value: "true"
|
||
- delay: 5s
|
||
- globals.set:
|
||
id: light_is_holding
|
||
value: "false"
|
||
- 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: "ESP32 Heap Free"
|
||
block:
|
||
name: "ESP32 Heap Max Block"
|
||
loop_time:
|
||
name: "ESP32 Loop Time"
|
||
cpu_frequency:
|
||
name: "ESP32 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
|
||
update_interval: 15s
|
||
|
||
- 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
|
||
update_interval: 15s
|
||
|
||
- 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
|
||
update_interval: 15s
|
||
|
||
- 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:
|
||
- lambda: |-
|
||
// Grab measured and corrected temperatures
|
||
float t_meas = id(sht45_temperature).state - id(temperature_offset);
|
||
float t_corr = id(sht45_temperature).state;
|
||
float rh_meas = x;
|
||
|
||
// Compute saturation vapor pressures (Magnus formula)
|
||
auto es = [](float T) { return 6.112 * exp((17.62 * T) / (243.12 + T)); };
|
||
float rh_corr = rh_meas * es(t_meas) / es(t_corr);
|
||
|
||
// Clamp to 0–100 %
|
||
if (rh_corr < 0) rh_corr = 0;
|
||
if (rh_corr > 100) rh_corr = 100;
|
||
return rh_corr + id(humidity_offset);
|
||
- 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
|
||
|
||
- 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: template
|
||
name: "Room Health Score"
|
||
id: room_health_score
|
||
unit_of_measurement: "%"
|
||
icon: mdi:home-heart
|
||
lambda: |-
|
||
float voc = id(sgp41_tvoc_ppb).state;
|
||
if (isnan(voc) || voc < 1) voc = 1;
|
||
float temp = id(sht45_temperature).state;
|
||
float humidity = id(sht45_humidity).state;
|
||
|
||
float temp_min = id(room_health_temperature_min);
|
||
float temp_max = id(room_health_temperature_max);
|
||
float temp_penalty = id(room_health_temperature_penalty);
|
||
float humid_min = id(room_health_humidity_min);
|
||
float humid_max = id(room_health_humidity_max);
|
||
float humid_penalty = id(room_health_humidity_penalty);
|
||
float voc_weight = id(room_health_voc_weight);
|
||
float temp_weight = id(room_health_temperature_weight);
|
||
float humid_weight = id(room_health_humidity_weight);
|
||
|
||
// VOC score (0–100) mapped to categories from Chemical Pollution levels below
|
||
float voc_score;
|
||
if (voc <= 200) {
|
||
voc_score = 100.0;
|
||
} else if (voc <= 400) {
|
||
// 200–400: 100 → 90
|
||
voc_score = 100.0 - (voc - 200) * (10.0 / 200.0);
|
||
} else if (voc <= 600) {
|
||
// 400–600: 90 → 70
|
||
voc_score = 90.0 - (voc - 400) * (20.0 / 200.0);
|
||
} else if (voc <= 1500) {
|
||
// 600–1500: 70 → 40
|
||
voc_score = 70.0 - (voc - 600) * (30.0 / 900.0);
|
||
} else if (voc <= 3000) {
|
||
// 1500–3000: 40 → 0
|
||
voc_score = 40.0 - (voc - 1500) * (40.0 / 1500.0);
|
||
} else {
|
||
voc_score = 0.0;
|
||
}
|
||
|
||
// Temperature score
|
||
float temp_score = 100;
|
||
if (temp < temp_min) temp_score = 100 - (temp_min - temp) * temp_penalty;
|
||
else if (temp > temp_max) temp_score = 100 - (temp - temp_max) * temp_penalty;
|
||
if (temp_score < 0) temp_score = 0;
|
||
|
||
// Humidity score
|
||
float humidity_score = 100;
|
||
if (humidity < humid_min) humidity_score = 100 - (humid_min - humidity) * humid_penalty;
|
||
else if (humidity > humid_max) humidity_score = 100 - (humidity - humid_max) * humid_penalty;
|
||
if (humidity_score < 0) humidity_score = 0;
|
||
|
||
// Weighted average
|
||
float total_weights = voc_weight + temp_weight + humid_weight;
|
||
if (total_weights <= 0) total_weights = 1.0;
|
||
voc_weight /= total_weights;
|
||
temp_weight /= total_weights;
|
||
humid_weight /= total_weights;
|
||
float overall_score = (voc_score * voc_weight + temp_score * temp_weight + humidity_score * humid_weight);
|
||
|
||
return (int) round(overall_score);
|
||
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_state:
|
||
then:
|
||
- script.execute: light_off
|
||
|
||
- platform: gpio
|
||
name: "PIR GPIO"
|
||
id: pir_gpio
|
||
pin:
|
||
number: GPIO32
|
||
mode:
|
||
input: true
|
||
pulldown: true
|
||
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"
|
||
|
||
- platform: template
|
||
name: "Chemical Pollution"
|
||
id: sgp41_chemical_pollution
|
||
icon: mdi:molecule
|
||
lambda: |-
|
||
float voc = id(sgp41_tvoc_ppb).state;
|
||
if (isnan(voc) || voc < 1) return {"Unknown"};
|
||
else if (voc <= 200) return {"Excellent"};
|
||
else if (voc <= 400) return {"Good"};
|
||
else if (voc <= 600) return {"Moderate"};
|
||
else if (voc <= 1500) return {"Unhealthy"};
|
||
else return {"Hazardous"};
|
||
update_interval: 15s
|
||
|
||
- platform: template
|
||
name: "Room Health"
|
||
id: room_health_text
|
||
icon: mdi:home-heart
|
||
lambda: |-
|
||
float score = id(room_health_score).state;
|
||
if (score < 0) return {"Unknown"};
|
||
else if (score >= 95.0) return {"Great"};
|
||
else if (score >= 90.0) return {"Good"};
|
||
else if (score >= 80.0) return {"Fair"};
|
||
else if (score >= 60.0) return {"Poor"};
|
||
else return {"Bad"};
|
||
update_interval: 15s
|
||
|
||
button:
|
||
- platform: ld2410
|
||
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 +10 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 -50 to +50 for the humidity sensor
|
||
- platform: template
|
||
name: "Humidity Offset"
|
||
id: humidity_offset_setter
|
||
min_value: -50
|
||
max_value: 50
|
||
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);'
|
||
|
||
# Room Health Calibration Values
|
||
# These values allow the user to tweak the values of the room health calculation
|
||
- platform: template
|
||
name: "Room Health Min Temperature"
|
||
id: room_health_temperature_min_setter
|
||
min_value: 15
|
||
max_value: 30
|
||
step: 0.5
|
||
lambda: |-
|
||
return id(room_health_temperature_min);
|
||
set_action:
|
||
then:
|
||
- globals.set:
|
||
id: room_health_temperature_min
|
||
value: !lambda 'return float(x);'
|
||
- platform: template
|
||
name: "Room Health Max Temperature"
|
||
id: room_health_temperature_max_setter
|
||
min_value: 15
|
||
max_value: 30
|
||
step: 0.5
|
||
lambda: |-
|
||
return id(room_health_temperature_max);
|
||
set_action:
|
||
then:
|
||
- globals.set:
|
||
id: room_health_temperature_max
|
||
value: !lambda 'return float(x);'
|
||
- platform: template
|
||
name: "Room Health Temperature Penalty"
|
||
id: room_health_temperature_penalty_setter
|
||
min_value: 1
|
||
max_value: 20
|
||
step: 1
|
||
lambda: |-
|
||
return int(id(room_health_temperature_penalty));
|
||
set_action:
|
||
then:
|
||
- globals.set:
|
||
id: room_health_temperature_penalty
|
||
value: !lambda 'return float(x);'
|
||
- platform: template
|
||
name: "Room Health Min Humidity"
|
||
id: room_health_humidity_min_setter
|
||
min_value: 20
|
||
max_value: 80
|
||
step: 1.0
|
||
lambda: |-
|
||
return id(room_health_humidity_min);
|
||
set_action:
|
||
then:
|
||
- globals.set:
|
||
id: room_health_humidity_min
|
||
value: !lambda 'return float(x);'
|
||
- platform: template
|
||
name: "Room Health Max Humidity"
|
||
id: room_health_humidity_max_setter
|
||
min_value: 20
|
||
max_value: 80
|
||
step: 1.0
|
||
lambda: |-
|
||
return id(room_health_humidity_max);
|
||
set_action:
|
||
then:
|
||
- globals.set:
|
||
id: room_health_humidity_max
|
||
value: !lambda 'return float(x);'
|
||
- platform: template
|
||
name: "Room Health Humidity Penalty"
|
||
id: room_health_humidity_penalty_setter
|
||
min_value: 1
|
||
max_value: 10
|
||
step: 1
|
||
lambda: |-
|
||
return int(id(room_health_humidity_penalty));
|
||
set_action:
|
||
then:
|
||
- globals.set:
|
||
id: room_health_humidity_penalty
|
||
value: !lambda 'return float(x);'
|
||
- platform: template
|
||
name: "Room Health VOC Weight"
|
||
id: room_health_voc_weight_setter
|
||
min_value: 0.00
|
||
max_value: 1.00
|
||
step: 0.01
|
||
lambda: |-
|
||
return id(room_health_voc_weight);
|
||
set_action:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
float total = x + id(room_health_temperature_weight) + id(room_health_humidity_weight);
|
||
return (total > 0.0) && (total <= 1.0);
|
||
then:
|
||
- globals.set:
|
||
id: room_health_voc_weight
|
||
value: !lambda 'return float(x);'
|
||
else:
|
||
- logger.log:
|
||
format: "Rejected VOC weight %.2f (total would be out of range: must be > 0.0 and ≤ 1.0)"
|
||
args: [ 'x' ]
|
||
- platform: template
|
||
name: "Room Health Temperature Weight"
|
||
id: room_health_temperature_weight_setter
|
||
min_value: 0.00
|
||
max_value: 1.00
|
||
step: 0.01
|
||
lambda: |-
|
||
return id(room_health_temperature_weight);
|
||
set_action:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
float total = x + id(room_health_voc_weight) + id(room_health_humidity_weight);
|
||
return (total > 0.0) && (total <= 1.0);
|
||
then:
|
||
- globals.set:
|
||
id: room_health_temperature_weight
|
||
value: !lambda 'return float(x);'
|
||
else:
|
||
- logger.log:
|
||
format: "Rejected Temperature weight %.2f (total would be out of range: must be > 0.0 and ≤ 1.0)"
|
||
args: [ 'x' ]
|
||
- platform: template
|
||
name: "Room Health Humidity Weight"
|
||
id: room_health_humidity_weight_setter
|
||
min_value: 0.00
|
||
max_value: 1.00
|
||
step: 0.01
|
||
lambda: |-
|
||
return id(room_health_humidity_weight);
|
||
set_action:
|
||
- if:
|
||
condition:
|
||
lambda: |-
|
||
float total = x + id(room_health_temperature_weight) + id(room_health_voc_weight);
|
||
return (total > 0.0) && (total <= 1.0);
|
||
then:
|
||
- globals.set:
|
||
id: room_health_humidity_weight
|
||
value: !lambda 'return float(x);'
|
||
else:
|
||
- logger.log:
|
||
format: "Rejected Humidity weight %.2f (total would be out of range: must be > 0.0 and ≤ 1.0)"
|
||
args: [ 'x' ]
|
||
|
||
# LD2410c configuration values
|
||
- 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
|
||
}
|