Initial commit of SuperSensor version 1.0

This commit is contained in:
Joshua Boniface
2023-11-14 16:43:23 -05:00
commit 0b211d4855
4 changed files with 856 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.esphome
secrets.yaml

42
README.md Normal file
View File

@ -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

288
supersensor.yaml Normal file
View File

@ -0,0 +1,288 @@
---
###############################################################################
# SuperSensor v1.0 ESPHome configuration
###############################################################################
#
# Copyright (C) 2023 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"
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"

View File

@ -0,0 +1,524 @@
---
###############################################################################
# SuperSensor v1.0 ESPHome configuration
###############################################################################
#
# Copyright (C) 2023 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/>.
#
###############################################################################
#
# 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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(rmaxst.begin(), rmaxst.end());
- uart.write:
id: LD1125H_UART_BUS
data: !lambda |-
std::string omst = "opt_mode=1\r\n";
return std::vector<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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<uint8_t>(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