From 28c38c49a7f0351b7189b1e90c3d30493af9a56a Mon Sep 17 00:00:00 2001 From: "Joshua M. Boniface" Date: Mon, 5 May 2025 02:15:45 -0400 Subject: [PATCH] Add 2.0 configuration --- supersensor-2.x.yaml | 1090 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1090 insertions(+) create mode 100644 supersensor-2.x.yaml diff --git a/supersensor-2.x.yaml b/supersensor-2.x.yaml new file mode 100644 index 0000000..72d78b9 --- /dev/null +++ b/supersensor-2.x.yaml @@ -0,0 +1,1090 @@ +--- + +############################################################################### +# 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" + 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); + - light.turn_on: + id: output_led + effect: flash_white + - priority: -600 + then: + - wait_until: + api.connected: + - delay: 5s + - if: + condition: + switch.is_on: enable_voice_support + then: + - logger.log: "Initializing voice assistant on boot" + - switch.turn_off: voice_support_active + - delay: 2s + - switch.turn_on: voice_support_active + +dashboard_import: + package_import_url: github://joshuaboniface/supersensor/supersensor-2.x.yaml + +esp32: + board: esp32dev + framework: + type: esp-idf + version: 4.4.8 + platform_version: 5.4.0 + 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: + - id: temperature_offset + type: float + restore_value: true + initial_value: "0.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); + } + +preferences: + flash_write_interval: 15sec + +logger: + level: INFO + baud_rate: 115200 + +api: + reboot_timeout: 15min + services: + - service: restart_voice_assistant + then: + - logger.log: "Manually restarting voice assistant" + - voice_assistant.stop: + - delay: 2s + - if: + condition: + switch.is_on: enable_voice_support + then: + - switch.turn_off: voice_support_active + - delay: 1s + - switch.turn_on: voice_support_active + +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: + i2s_lrclk_pin: GPIO17 # WS + i2s_bclk_pin: GPIO16 # SCK + +microphone: + - platform: i2s_audio + id: mic + adc_type: external + i2s_din_pin: GPIO4 # SD + pdm: false + +interval: + - interval: 5s + then: + - if: + condition: + and: + - switch.is_on: enable_voice_support + - switch.is_on: voice_support_active + - not: voice_assistant.is_running + then: + - logger.log: "Voice assistant not running but should be; restarting" + - voice_assistant.start_continuous: + + # Regular state reporting to HASS + - 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: 60s + 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(); + } + +# Add optional microWakeWord support (on-device wake word) +# Doesn't work well as of 2024-07-04 so leave disabled +#micro_wake_word: +# model: hey_jarvis +# on_wake_word_detected: +# then: +# - voice_assistant.start: +# wake_word: !lambda return wake_word; + +# Include the Espressif Audio Development Framework for VAD support +esp_adf: +external_components: + - source: github://pr#5230 + components: + - esp_adf + refresh: 0s + +voice_assistant: + microphone: mic + use_wake_word: false + noise_suppression_level: 3 + auto_gain: 31dBFS + volume_multiplier: 8.0 + id: assist + on_error: + - logger.log: "voice error" + - if: + condition: + and: + - switch.is_on: voice_support_active + - not: voice_assistant.is_running + then: + - voice_assistant.start_continuous: + on_end: + - logger.log: "voice ended" + - if: + condition: + and: + - switch.is_on: voice_support_active + - not: voice_assistant.is_running + then: + - voice_assistant.start_continuous: + on_client_connected: + - light.turn_off: + id: output_led + transition_length: 2s + - script.execute: light_off + - lambda: |- + id(voice_support_active).publish_state(true); + on_client_disconnected: + - light.turn_on: + id: output_led + effect: flash_white + on_wake_word_detected: + - light.turn_on: + id: output_led + brightness: 100% + red: 0 + green: 0 + blue: 1 + on_listening: + - light.turn_on: + id: output_led + brightness: 100% + red: 0 + green: 0 + blue: 1 + on_stt_vad_end: + - light.turn_on: + id: output_led + brightness: 75% + red: 0 + green: 1 + blue: 1 + on_stt_end: + - script.execute: light_off + 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: GPIO32 + - platform: ledc + id: rgb_g + pin: GPIO33 + - platform: ledc + id: rgb_b + pin: GPIO25 + +ld2410: + id: ld2410_radar + uart_id: ld2410_uart + +sensor: + - platform: sgp30 + eco2: + name: "SGP30 eCO2" + id: sgp30_eco2 + accuracy_decimals: 1 + tvoc: + name: "SGP30 TVOC" + id: sgp30_tvoc + accuracy_decimals: 1 + store_baseline: yes + update_interval: 15s + + - platform: sht4x + temperature: + name: "SHT45 Temperature" + id: sht45_temperature + filters: + - offset: !lambda return id(temperature_offset); + humidity: + name: "SHT45 Relative Humidity" + id: sht45_humidity + filters: + - offset: !lambda return id(humidity_offset); + 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" + 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=Excellent) + - platform: template + name: "IAQ Index" + id: iaq_index + lambda: |- + int tvoc = id(sgp30_tvoc).state; + int eco2 = id(sgp30_eco2).state; + if (tvoc > 2200 || eco2 > 2000) return 1; // Unhealthy + if (tvoc > 660 || eco2 > 1200) return 2; // Poor + if (tvoc > 220 || eco2 > 800) return 3; // Moderate + if (tvoc > 65) return 4; // Good + return 5; // Excellent + update_interval: 15s + + # Room Health Score (1-4, 4=Optimal) + - platform: template + name: "Room Health Score" + 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 >= 40 && rh <= 60); + bool aq_ok = (iaq >= 4); + if (temp_ok && hum_ok && aq_ok) return 4; // Optimal + if ((temp_ok && hum_ok) || (temp_ok && aq_ok) || (hum_ok && aq_ok)) return 3; // Good + if (temp_ok || hum_ok || aq_ok) return 2; // Fair + return 1; // Poor + 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 + + - 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 CPU Frequency" + icon: mdi:cpu-32-bit + accuracy_decimals: 1 + unit_of_measurement: MHz + update_interval: 5s + lambda: |- + return ets_get_cpu_frequency(); + 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 + +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: GPIO13 + 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" + +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: + - switch.turn_on: voice_support_active + on_turn_off: + - switch.turn_off: voice_support_active + + # Active voice support flag/switch + - platform: template + name: "Voice Support Active" + icon: mdi:account-voice + id: voice_support_active + optimistic: true + restore_mode: ALWAYS_OFF + entity_category: config + on_turn_on: + - lambda: id(assist).set_use_wake_word(true); + - voice_assistant.stop: + - delay: 1s + - voice_assistant.start_continuous: + on_turn_off: + - voice_assistant.stop: + - lambda: id(assist).set_use_wake_word(false); + + # 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 -7 to +3 for the temperature sensor + - platform: template + name: "Temperature Offset" + id: temperature_offset_setter + min_value: -7 + max_value: 3 + 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 -10 to +10 for the humidity sensor + - platform: template + name: "Humidity Offset" + id: humidity_offset_setter + min_value: -10 + max_value: 10 + 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" + +text_sensor: + # VOC Level + - platform: template + name: "VOC Level" + lambda: |- + int tvoc = id(sgp30_tvoc).state; + if (tvoc < 65) return {"Excellent"}; + if (tvoc < 220) return {"Good"}; + if (tvoc < 660) return {"Moderate"}; + if (tvoc < 2200) return {"Poor"}; + return {"Unhealthy"}; + update_interval: 15s + + # CO2 Level + - platform: template + name: "CO2 Level" + lambda: |- + int eco2 = id(sgp30_eco2).state; + if (eco2 < 800) return {"Good"}; + if (eco2 < 1200) return {"Moderate"}; + if (eco2 < 2000) return {"Poor"}; + return {"Unhealthy"}; + update_interval: 15s + + # IAQ Classification + - platform: template + name: "IAQ Classification" + lambda: |- + int iaq = id(iaq_index).state; + if (iaq == 5) return {"Excellent"}; + if (iaq == 4) return {"Good"}; + if (iaq == 3) return {"Moderate"}; + if (iaq == 2) return {"Poor"}; + return {"Unhealthy"}; + update_interval: 15s + + # Room Health + - platform: template + name: "Room Health" + lambda: |- + int score = id(room_health).state; + if (score == 4) return {"Optimal"}; + if (score == 3) return {"Good"}; + if (score == 2) return {"Fair"}; + return {"Poor"}; + update_interval: 15s