--- ############################################################################### # SuperSensor v2.0 ESPHome configuration ############################################################################### # # Copyright (C) 2025 Joshua M. Boniface # # 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 . # ############################################################################### 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" 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 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: WARN 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" 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://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: sgp30 eco2: name: "SGP30 eCO2" id: sgp30_eco2 accuracy_decimals: 1 filters: - sliding_window_moving_average: window_size: 20 send_every: 1 tvoc: name: "SGP30 TVOC" id: sgp30_tvoc accuracy_decimals: 1 filters: - sliding_window_moving_average: window_size: 20 send_every: 1 eco2_baseline: name: "SGP30 Baseline eCO2" id: sgp30_baseline_ec02 tvoc_baseline: name: "SGP30 Baseline TVOC" id: sgp30_baseline_tvoc compensation: temperature_source: sht45_temperature humidity_source: sht45_humidity store_baseline: yes 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: - 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 # IAQ Index (1-5, 5=Great)) - platform: template name: "IAQ Index" icon: mdi:air-purifier id: iaq_index lambda: |- int tvoc = id(sgp30_tvoc).state; int eco2 = id(sgp30_eco2).state; if (tvoc > 2200 || eco2 > 2000) return 1; // Bad if (tvoc > 660 || eco2 > 1200) return 2; // Poor if (tvoc > 220 || eco2 > 800) return 3; // Fair if (tvoc > 65 || eco2 > 500) return 4; // Good return 5; // Great update_interval: 15s # Room Health Score (1-4, 4=Optimal) - platform: template name: "Room Health Score" icon: mdi:home-thermometer id: room_health lambda: |- float temp = id(sht45_temperature).state; float rh = id(sht45_humidity).state; int iaq = id(iaq_index).state; bool temp_ok = (temp >= 18 && temp <= 24); bool hum_ok = (rh >= 30 && rh <= 70); bool iaq_ok = (iaq >= 4); int conditions_met = 0; if (temp_ok) conditions_met++; if (hum_ok) conditions_met++; if (iaq_ok) conditions_met++; if (iaq_ok && temp_ok && hum_ok) { return 4; // Optimal: All conditions met and IAQ is excellent/good } else if (iaq >= 3 && conditions_met >= 2) { return 3; // Fair: IAQ is moderate and at least 2 conditions met } else if (iaq >= 2 && conditions_met >= 1) { return 2; // Poor: IAQ is poor and at least 1 condition met } else { return 1; // Bad: All conditions failed or IAQ is unhealthy } 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: ld2410 version: name: "LD2410C Firmware Version" mac_address: name: "LD2410C MAC Address" # VOC Level - platform: template name: "VOC Level" icon: mdi:radiator lambda: |- int tvoc = id(sgp30_tvoc).state; if (tvoc < 65) return {"Great"}; if (tvoc < 220) return {"Good"}; if (tvoc < 660) return {"Fair"}; if (tvoc < 2200) return {"Poor"}; return {"Bad"}; update_interval: 15s # CO2 Level - platform: template name: "CO2 Level" icon: mdi:molecule-co2 lambda: |- int eco2 = id(sgp30_eco2).state; if (eco2 < 500) return {"Great"}; if (eco2 < 800) return {"Good"}; if (eco2 < 1200) return {"Fair"}; if (eco2 < 2000) return {"Poor"}; return {"Bad"}; update_interval: 15s # IAQ Classification - platform: template name: "IAQ Classification" icon: mdi:air-purifier lambda: |- int iaq = id(iaq_index).state; if (iaq == 5) return {"Great"}; if (iaq == 4) return {"Good"}; if (iaq == 3) return {"Fair"}; if (iaq == 2) return {"Poor"}; return {"Bad"}; update_interval: 15s # Room Health - platform: template name: "Room Health" icon: mdi:home-thermometer lambda: |- int score = id(room_health).state; if (score == 4) return {"Optimal"}; if (score == 3) return {"Fair"}; if (score == 2) return {"Poor"}; 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 +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 sensitivity" optimistic: true initial_option: Moderately sensitive restore_value: true entity_category: config options: - Slightly sensitive - Moderately sensitive - Very sensitive on_value: # 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 # False Accepts per Hour values are tested against all units and channels from the Dinner Party Corpus. # These cutoffs apply only to the specific models included in the firmware: okay_nabu@20241226.3, hey_jarvis@v2, hey_mycroft@v2 lambda: |- if (x == "Slightly sensitive") { id(mww_hey_jarvis).set_probability_cutoff(247); // 0.97 -> 0.563 FAPH on DipCo (Manifest's default) id(mww_hey_mycroft).set_probability_cutoff(253); // 0.99 -> 0.567 FAPH on DipCo id(mww_okay_nabu).set_probability_cutoff(217); // 0.85 -> 0.000 FAPH on DipCo (Manifest's default) id(mww_alexa).set_probability_cutoff(217); // 0.85 -> 0.000 FAPH on DipCo (Manifest's default) } else if (x == "Moderately sensitive") { id(mww_hey_jarvis).set_probability_cutoff(235); // 0.92 -> 0.939 FAPH on DipCo id(mww_hey_mycroft).set_probability_cutoff(242); // 0.95 -> 1.502 FAPH on DipCo (Manifest's default) id(mww_okay_nabu).set_probability_cutoff(176); // 0.69 -> 0.376 FAPH on DipCo id(mww_alexa).set_probability_cutoff(176); // 0.69 -> 0.376 FAPH on DipCo } else if (x == "Very sensitive") { id(mww_hey_jarvis).set_probability_cutoff(212); // 0.83 -> 1.502 FAPH on DipCo id(mww_hey_mycroft).set_probability_cutoff(237); // 0.93 -> 1.878 FAPH on DipCo id(mww_okay_nabu).set_probability_cutoff(143); // 0.56 -> 0.751 FAPH on DipCo id(mww_alexa).set_probability_cutoff(143); // 0.56 -> 0.751 FAPH on DipCo }