From 0b211d4855745333d7159e3e89fea9a105499eb6 Mon Sep 17 00:00:00 2001 From: Joshua Boniface Date: Tue, 14 Nov 2023 16:43:23 -0500 Subject: [PATCH] Initial commit of SuperSensor version 1.0 --- .gitignore | 2 + README.md | 42 +++ supersensor.yaml | 288 +++++++++++++++++++ supersensor_HLK-LD1125H.yaml | 524 +++++++++++++++++++++++++++++++++++ 4 files changed, 856 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 supersensor.yaml create mode 100644 supersensor_HLK-LD1125H.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3ea772 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.esphome +secrets.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..036605c --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +# SuperSensor + +SuperSensor is an all-in-one voice, motion, presence, temperature/humidity/ +pressure, light, and BLE sensor, built on an ESP32 with ESPHome, and inspired +heavily by the EverythingSmartHome Everything Presence One sensor and the +HomeAssistant "$13 Voice Assistant" project. + +Use SuperSensors around your house to provide HomeAssistant Voice Assist +interfaces with wake word detection, as well as other sensor detection options +as you want them. + +Assist feedback is provided by a single common-cathode RGB LED. No speakers +or annoying TTS feedback here! With the optional 3D Printed case and a clear +diffuser insert, this LED can be turned into a sleek light bar on the bottom +of the unit for quick and easy confirmation of voice actions. + +To Use: + + * Fill out a "secrets.yaml" for your environment. + * Install this ESPHome configuration to a compatible ESP32 devkit (V4). + * Install the ESP32 and sensors into the custom PCB. + * [Optional] 3D Print our custom case. + * Install the SuperSensor somewhere that makes sense. + * Add the SuperSensor to HomeAssistant using the automatic name. + +Note: Once programmed, the output LED will flash continuously until connected + to HomeAssistant, and a bit longer to establish if the wake word + functionality is enabled. This is by design, so you know if your sensors + are connected or not. If you do not want this, comment out the + `light.turn_on` block starting on line 29 of the ESPHome configuration + to disable this functionality. + +## Parts List + +* 1x ESP32 devkit +* 1x Common-cathod RGB LED +* 1x Resistor for the common-cathod RGB LED @ 3.3v input (~33-1000Ω, depending on desired brightness and LED) +* 1x INMP441 MEMS microphone +* 1x BME280 temperature/humidity/pressure sensor (3.3v models) +* 1x VEML7700 light sensor +* 1x HLK-LD1115H-24G mmWave radar sensor +* 1x HC-SR501 (or similar) PIR sensor diff --git a/supersensor.yaml b/supersensor.yaml new file mode 100644 index 0000000..2cb302e --- /dev/null +++ b/supersensor.yaml @@ -0,0 +1,288 @@ +--- + +############################################################################### +# SuperSensor v1.0 ESPHome configuration +############################################################################### +# +# Copyright (C) 2023 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" + on_boot: + - priority: 600 + then: + - light.turn_on: + id: output_led + effect: flash + red: 1 + green: 1 + blue: 1 + + - priority: -500 + then: + - wait_until: api.connected + - delay: 2s + - switch.turn_on: use_wake_word + - delay: 2s + - switch.turn_off: use_wake_word + - delay: 2s + - switch.turn_on: use_wake_word + - light.turn_off: + id: output_led + +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" + +# Enable logging only via web +logger: + level: DEBUG + baud_rate: 115200 + +api: + encryption: + key: !secret api_encryption_key + +ota: + password: !secret ota_password + safe_mode: false + +web_server: + port: 80 + auth: + username: !secret web_auth_username + password: !secret web_auth_password + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + domain: !secret wifi_domain + power_save_mode: LIGHT + reboot_timeout: 5min + +uart: + rx_pin: GPIO16 + tx_pin: GPIO17 + baud_rate: 115200 + setup_priority: 200 + +i2c: + sda: GPIO21 + scl: GPIO22 + scan: true + +i2s_audio: + i2s_lrclk_pin: GPIO25 + i2s_bclk_pin: GPIO26 + +microphone: + - platform: i2s_audio + id: mic + adc_type: external + i2s_din_pin: GPIO27 + pdm: false + +voice_assistant: + microphone: mic + use_wake_word: false + noise_suppression_level: 2 + auto_gain: 31dBFS + volume_multiplier: 4.0 + id: assist + on_wake_word_detected: + - light.turn_off: + id: output_led + - light.turn_on: + id: output_led + red: 0 + green: 0 + blue: 1 + on_listening: + - light.turn_on: + id: output_led + red: 0 + green: 0 + blue: 1 + on_stt_end: + - light.turn_off: + id: output_led + transition_length: 1s + on_tts_start: + - light.turn_off: + id: output_led + transition_length: 1s + - if: + condition: + lambda: return x != "Sorry, I couldn't understand that"; + then: + - logger.log: "Command successful!" + - light.turn_on: + id: output_led + effect: hold + red: 0 + green: 1 + blue: 0 + else: + - logger.log: "Command failed!" + - light.turn_on: + id: output_led + effect: hold + red: 1 + green: 0 + 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 + colors: + - state: true + duration: 0.5s + - state: false + duration: 0.5s + - automation: + name: hold + sequence: + - delay: 5s + - light.turn_off: + id: output_led + transition_length: 1s + +output: + - platform: ledc + id: rgb_r + pin: GPIO14 + - platform: ledc + id: rgb_g + pin: GPIO12 + - platform: ledc + id: rgb_b + pin: GPIO13 + +button: + - platform: restart + name: "ESP32 Restart" + icon: mdi:power-cycle + entity_category: "diagnostic" + +switch: + - platform: template + name: "Enable Wake Word" + icon: mdi:account-voice + id: use_wake_word + optimistic: true + restore_mode: ALWAYS_OFF + entity_category: config + on_turn_on: + - lambda: id(assist).set_use_wake_word(true); + - if: + condition: + not: + - voice_assistant.is_running + then: + - voice_assistant.start_continuous + on_turn_off: + - voice_assistant.stop + - lambda: id(assist).set_use_wake_word(false); + +binary_sensor: + - platform: gpio + pin: + number: GPIO33 + mode: + input: true + pulldown: true + name: "PIR Motion" + device_class: motion + +sensor: + - platform: bme280 + temperature: + name: "BME280 Temperature" + pressure: + name: "BME280 Pressure" + humidity: + name: "BME280 Humidity" + update_interval: 15s + address: 0x76 + + - platform: bmp280 + temperature: + name: "BMP280 Temperature" + pressure: + name: "BMP280 Pressure" + update_interval: 15s + address: 0x76 + + - platform: uptime + name: "ESP32 Uptime" + icon: mdi:clock-alert + update_interval: 15s + entity_category: "diagnostic" + + - platform: wifi_signal + name: "ESP32 WiFi RSSI" + icon: mdi:wifi-strength-2 + update_interval: 15s + entity_category: "diagnostic" + + - platform: internal_temperature + name: "ESP32 Temperature" + icon: mdi:thermometer + unit_of_measurement: °C + device_class: TEMPERATURE + update_interval: 15s + entity_category: "diagnostic" + + - platform: template + name: "ESP32 CPU Frequency" + icon: mdi:cpu-32-bit + accuracy_decimals: 1 + unit_of_measurement: MHz + update_interval: 15s + 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: 15s + lambda: |- + return heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024; + entity_category: "diagnostic" diff --git a/supersensor_HLK-LD1125H.yaml b/supersensor_HLK-LD1125H.yaml new file mode 100644 index 0000000..c69e9c7 --- /dev/null +++ b/supersensor_HLK-LD1125H.yaml @@ -0,0 +1,524 @@ +--- + +############################################################################### +# SuperSensor v1.0 ESPHome configuration +############################################################################### +# +# Copyright (C) 2023 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 . +# +############################################################################### + +# +# Core configuration +# +esphome: + name: supersensor + name_add_mac_suffix: true + on_boot: + - priority: 200 + then: + # Configure LD1125H via UART + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth1_mov_st = "mth1_mov=" + str_sprintf("%.0f",id(LD1125H_mth1_mov).state) + "\r\n"; + return std::vector(mth1_mov_st.begin(), mth1_mov_st.end()); + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth2_mov_st = "mth2_mov=" + str_sprintf("%.0f",id(LD1125H_mth2_mov).state) + "\r\n"; + return std::vector(mth2_mov_st.begin(), mth2_mov_st.end()); + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth3_mov_st = "mth3_mov=" + str_sprintf("%.0f",id(LD1125H_mth3_mov).state) + "\r\n"; + return std::vector(mth3_mov_st.begin(), mth3_mov_st.end()); + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth1_occ_st = "mth1_occ=" + str_sprintf("%.0f",id(LD1125H_mth1_occ).state) + "\r\n"; + return std::vector(mth1_occ_st.begin(), mth1_occ_st.end()); + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth2_occ_st = "mth2_occ=" + str_sprintf("%.0f",id(LD1125H_mth2_occ).state) + "\r\n"; + return std::vector(mth2_occ_st.begin(), mth2_occ_st.end()); + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth3_occ_st = "mth3_occ=" + str_sprintf("%.0f",id(LD1125H_mth3_occ).state) + "\r\n"; + return std::vector(mth3_occ_st.begin(), mth3_occ_st.end()); + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string rmaxst = "rmax=" + str_sprintf("%.1f",id(LD1125H_rmax).state) + "\r\n"; + return std::vector(rmaxst.begin(), rmaxst.end()); + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string omst = "opt_mode=1\r\n"; + return std::vector(omst.begin(), omst.end()); + - priority: -100 + then: + - light.turn_on: + id: output_led + effect: flash + red: 1 + green: 1 + blue: 1 + - wait_until: api.connected + +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" + +logger: + level: VERBOSE + baud_rate: 115200 + +api: + encryption: + key: !secret api_encryption_key + +ota: + password: !secret ota_password + +web_server: + port: 80 + auth: + username: !secret web_auth_username + password: !secret web_auth_password + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + domain: !secret wifi_domain + power_save_mode: LIGHT + reboot_timeout: 5min + +uart: + id: LD1125H_UART_BUS + rx_pin: GPIO16 + tx_pin: GPIO17 + baud_rate: 115200 + data_bits: 8 + stop_bits: 1 + parity: NONE + +i2c: + sda: GPIO21 + scl: GPIO22 + scan: true + +# +# Voice Control +# +i2s_audio: + i2s_lrclk_pin: GPIO25 + i2s_bclk_pin: GPIO26 + +microphone: + - platform: i2s_audio + id: mic + adc_type: external + i2s_din_pin: GPIO27 + pdm: false + +voice_assistant: + microphone: mic + use_wake_word: false + noise_suppression_level: 2 + auto_gain: 31dBFS + volume_multiplier: 5.0 + id: assist + on_client_connected: + - if: + condition: + switch.is_on: use_wake_word + then: + - voice_assistant.start_continuous: + - wait_until: voice_assistant.is_running + - delay: 1s + - lambda: id(assist).set_use_wake_word(true); + - light.turn_off: + id: output_led + on_client_disconnected: + - if: + condition: + switch.is_on: use_wake_word + then: + - light.turn_on: + id: output_led + effect: flash + red: 1 + green: 1 + blue: 1 + - lambda: id(assist).set_use_wake_word(false); + - voice_assistant.stop: + on_wake_word_detected: + - light.turn_off: + id: output_led + - light.turn_on: + id: output_led + red: 0 + green: 0 + blue: 1 + on_listening: + - light.turn_on: + id: output_led + red: 0 + green: 0 + blue: 1 + on_stt_end: + - light.turn_off: + id: output_led + transition_length: 1s + on_tts_start: + - if: + condition: + lambda: return x != "Sorry, I couldn't understand that"; + then: + - logger.log: "Command successful!" + - light.turn_on: + id: output_led + effect: hold + red: 0 + green: 1 + blue: 0 + else: + - logger.log: "Command failed!" + - light.turn_on: + id: output_led + effect: hold + red: 1 + green: 0 + blue: 0 + +# +# Voice Response LED +# +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 + colors: + - state: true + duration: 0.5s + - state: false + duration: 0.5s + - automation: + name: hold + sequence: + - delay: 5s + - light.turn_off: + id: output_led + transition_length: 1s + +output: + - platform: ledc + id: rgb_r + pin: GPIO14 + - platform: ledc + id: rgb_g + pin: GPIO12 + - platform: ledc + id: rgb_b + pin: GPIO13 + +# +# Restart button +# +button: + - platform: restart + name: "ESP32 Restart" + icon: mdi:power-cycle + entity_category: "diagnostic" + +# +# Wake Word switch +# +switch: + - platform: template + name: "Enable Wake Word" + icon: mdi:account-voice + id: use_wake_word + optimistic: true + restore_mode: RESTORE_DEFAULT_ON + entity_category: config + on_turn_on: + - lambda: id(assist).set_use_wake_word(true); + - if: + condition: + not: + - voice_assistant.is_running + then: + - voice_assistant.start_continuous + on_turn_off: + - voice_assistant.stop + - lambda: id(assist).set_use_wake_word(false); + +# +# Motion Sensors +# +binary_sensor: + - platform: gpio + pin: GPIO33 + name: "PIR Motion" + device_class: motion + + - platform: gpio + pin: GPIO32 + name: "Radar Motion" + device_class: motion + +# +# Other Sensors +# +sensor: + - platform: bme280 + temperature: + name: "BME280 Temperature" + pressure: + name: "BME280 Pressure" + humidity: + name: "BME280 Humidity" + update_interval: 15s + address: 0x76 + + - platform: bmp280 + temperature: + name: "BMP280 Temperature" + pressure: + name: "BMP280 Pressure" + update_interval: 15s + address: 0x76 + + - platform: uptime + name: "ESP32 Uptime" + icon: mdi:clock-alert + update_interval: 15s + entity_category: "diagnostic" + + - platform: wifi_signal + name: "ESP32 WiFi RSSI" + icon: mdi:wifi-strength-2 + update_interval: 15s + entity_category: "diagnostic" + + - platform: internal_temperature + name: "ESP32 Temperature" + icon: mdi:thermometer + unit_of_measurement: °C + device_class: TEMPERATURE + update_interval: 15s + entity_category: "diagnostic" + + - platform: template + name: "ESP32 CPU Frequency" + icon: mdi:cpu-32-bit + accuracy_decimals: 1 + unit_of_measurement: MHz + update_interval: 15s + 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: 15s + lambda: |- + return heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024; + entity_category: "diagnostic" + +# +# LD1125H tunables +# +number: + - platform: template + name: "LD1125H Mov 0m-2.8m" + id: LD1125H_mth1_mov + icon: mdi:cogs + entity_category: "config" + optimistic: true + restore_value: true + initial_value: "80" + min_value: 0 + max_value: 1500 + step: 1 + set_action: + then: + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth1_mov_st = "mth1_mov=" + str_sprintf("%.0f",x) +"\r\n"; + return std::vector(mth1_mov_st.begin(), mth1_mov_st.end()); + + - platform: template + name: "LD1125H Mov 2.8m-8m" + id: LD1125H_mth2_mov + icon: mdi:cogs + entity_category: "config" + optimistic: true + restore_value: true + initial_value: "50" + min_value: 0 + max_value: 1500 + step: 1 + set_action: + then: + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth2_mov_st = "mth2_mov=" + str_sprintf("%.0f",x) +"\r\n"; + return std::vector(mth2_mov_st.begin(), mth2_mov_st.end()); + + - platform: template + name: "LD1125H Mov 8m-∞m" + id: LD1125H_mth3_mov + icon: mdi:cogs + entity_category: "config" + optimistic: true + restore_value: true + initial_value: "80" + min_value: 0 + max_value: 1500 + step: 1 + set_action: + then: + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth3_mov_st = "mth3_mov=" + str_sprintf("%.0f",x) +"\r\n"; + return std::vector(mth3_mov_st.begin(), mth3_mov_st.end()); + + - platform: template + name: "LD1125H Occ 0m-2.8m" + id: LD1125H_mth1_occ + icon: mdi:cogs + entity_category: "config" + optimistic: true + restore_value: true + initial_value: "80" + min_value: 0 + max_value: 1500 + step: 1 + set_action: + then: + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth1_occ_st = "mth1_mov=" + str_sprintf("%.0f",x) +"\r\n"; + return std::vector(mth1_occ_st.begin(), mth1_occ_st.end()); + + - platform: template + name: "LD1125H Occ 2.8m-8m" + id: LD1125H_mth2_occ + icon: mdi:cogs + entity_category: "config" + optimistic: true + restore_value: true + initial_value: "50" + min_value: 0 + max_value: 1500 + step: 1 + set_action: + then: + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth2_occ_st = "mth2_occ=" + str_sprintf("%.0f",x) +"\r\n"; + return std::vector(mth2_occ_st.begin(), mth2_occ_st.end()); + + - platform: template + name: "LD1125H Occ 8m-∞m" + id: LD1125H_mth3_occ + icon: mdi:cogs + entity_category: "config" + optimistic: true + restore_value: true + initial_value: "80" + min_value: 0 + max_value: 1500 + step: 1 + set_action: + then: + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string mth3_occ_st = "mth3_occ=" + str_sprintf("%.0f",x) +"\r\n"; + return std::vector(mth3_occ_st.begin(), mth3_occ_st.end()); + + - platform: template + name: "LD1125H Max Dist (m)" + id: LD1125H_rmax + icon: mdi:cogs + entity_category: "config" + optimistic: true + restore_value: true + initial_value: "4" + min_value: 0.0 + max_value: 20 + step: 0.1 + set_action: + then: + - uart.write: + id: LD1125H_UART_BUS + data: !lambda |- + std::string rmaxst = "rmax=" + str_sprintf("%.1f",x) +"\r\n"; + return std::vector(rmaxst.begin(), rmaxst.end()); + +# - platform: template +# name: "LD1125H Clear Time" +# id: LD1125H_Clearance_Time +# icon: mdi:cogs +# entity_category: "config" +# optimistic: true +# restore_value: true +# initial_value: "15" +# min_value: 0 +# max_value: 60 +# step: 1 + +# - platform: template +# name: "LD1125H Detect Time" +# id: LD1125H_Mov_Time +# icon: mdi:cogs +# entity_category: "config" +# optimistic: true +# restore_value: true +# initial_value: "0.1" +# min_value: 0.1 +# max_value: 10 +# step: 0.1