Compare commits

...

58 Commits

Author SHA1 Message Date
a03e12dfa7 Readd health scores 2025-06-29 13:04:46 -04:00
1f7df559b1 Only fire light_off on occupancy change
Avoids constantly turning off a held LED.
2025-06-23 23:38:11 -04:00
d75a7d26a6 Increase MWW gain factor to 8
Improves reliability.
2025-06-23 23:19:30 -04:00
e4c1ab1ac2 Fix typos and bugs in config 2025-06-22 00:45:41 -04:00
efd9fc8bfe Update designs for SGPXX change 2025-06-22 00:09:20 -04:00
9aebae647a Update README parts list and descriptions 2025-06-22 00:08:10 -04:00
3300ca2d8e Switch to SGP41 air quality sensor
My testing with the SGP30 sensor proved fruitless, as various sensors
would mis-report (under- and over-) levels and generally not be
consistent with each other or reality. These sensors are simply too
flaky with zero consistency.

Instead, switch to the SGP41, which so far seems more robust and is used
in other tools like the AirGradient One. We leverage their calculations
for VOC Index -> VOC levels, as well as a generalized isobutylene-based
eCO2 calculation.
2025-06-21 23:48:42 -04:00
0ba3b855d0 Add warning 2025-06-15 12:51:59 -04:00
4f6597d92f Switch logging to DEBUG during testing 2025-06-15 12:51:59 -04:00
985e7e20e5 Add two more icons 2025-06-08 21:04:45 -04:00
426684e9da Update icon of VOC 2025-06-07 19:03:36 -04:00
7fe3829544 Restore debug components for 2025.5.2
Now that the debug: components will work without debug-level logging,
re-enable the useful ones.
2025-06-03 10:21:33 -04:00
206691257d Add Computer wakeword and selector 2025-05-30 23:37:56 -04:00
aca7e16ed0 Add regular MWW state check
Ensures that if the micro_wake_word component stops, it will be
automatically restarted on a regular interval.
2025-05-28 23:05:53 -04:00
556f8564c4 Add ESPHome version sensor 2025-05-27 22:00:06 -04:00
8114d765f0 Adjust ideal humidity levels
I don't want to detract with 30-40 or 60-70% humidity.
2025-05-26 11:57:51 -04:00
d529f695a3 Adjust levels further 2025-05-25 02:18:29 -04:00
c0b5adecca Adjust gain levels 2025-05-25 02:14:21 -04:00
5c94e56847 Add icons to various sensors 2025-05-24 21:19:10 -04:00
fd7e438c62 Adjust AQ wordings 2025-05-24 20:00:30 -04:00
64cb43dec8 Rework startup MWW and gain levels 2025-05-24 18:15:17 -04:00
e224044de3 Remove debug components
Slows down the ESP too much
2025-05-24 17:33:51 -04:00
450ff085d2 Increase noise suppression level to 3 2025-05-24 17:24:41 -04:00
f3d7b16aa9 Remove workaround connectivity check 2025-05-24 16:56:45 -04:00
e62b9a8b91 Actually fix conditional logic 2025-05-24 01:27:12 -04:00
9555fd37dc Fix negation condition 2025-05-24 01:19:41 -04:00
b3b9fd7086 Add additional check for API connectivity 2025-05-24 01:17:15 -04:00
cec8d27a4a Improve detection of connection 2025-05-24 01:02:44 -04:00
40f0ae215a Adjust debug options for compatibility 2025-05-24 00:08:56 -04:00
90403f990b Add debug sensor components 2025-05-24 00:06:14 -04:00
1eb8e2134f Add log messages for API client connect/disconnect 2025-05-24 00:02:30 -04:00
6c32edc9a9 Fix models 2025-05-23 23:22:28 -04:00
7221213ac4 Rework Voice, Microphone, and MWW handling
Needed for recent changes in ESPHome and to properly align us with the
most recent developments. Includes support for multiple selectable wake
words as well.
2025-05-23 23:17:54 -04:00
28b76d0508 Adjust threshold values and set min_version 2025-05-23 22:57:36 -04:00
647e6711b8 Remove CPU frequency
Doesn't work on recent ESP-IDF versions it seems.
2025-05-21 16:57:38 -04:00
7d5d7da2e8 Remove esp-adf component
This causes numerous issues now, so remove it.
2025-05-21 16:54:32 -04:00
565df10fe0 Restore capitalization 2025-05-21 00:45:56 -04:00
21fc3436ee Adjust wording of BME680 section 2025-05-20 18:14:07 -04:00
91d9ff2a43 Add README note about initial set value 2025-05-20 18:09:36 -04:00
1600417026 Fix initial set value 2025-05-20 12:43:36 -04:00
decf2ad244 Add 5 minute moving average to Sensirion sensors
Avoids constant wild shifts over time by performing a sliding window
moving average over the last 20 results (at 15s intervals).
2025-05-20 12:40:44 -04:00
6695f9eb6b Set accuracy decimals of SHT45
Two decimal places is not required here.
2025-05-20 12:37:13 -04:00
3768589de2 Readd WiFi and LD2410 info
Was erroneously removed during cleanup.
2025-05-20 12:19:30 -04:00
713a693cef Explicitly set 0 max duty for SHT heater 2025-05-20 12:15:16 -04:00
d680735be3 Adjust capitalization of project name 2025-05-20 11:58:42 -04:00
6cdf340c5b Remove utemp module 2025-05-20 11:57:53 -04:00
4157ee2e65 Remove obsolete archives 2025-05-20 11:57:30 -04:00
e1840a37aa Update refs for new repo 2025-05-20 11:53:54 -04:00
a095936f3f Add SGP30 baseline sensors 2025-05-19 17:00:59 -04:00
547983173b Add compensation values to SGP30 2025-05-19 16:58:22 -04:00
2145d4eff1 Decrease Sensirion interval to 15s
60s felt like far too long.
2025-05-19 16:56:51 -04:00
9fc8ad7318 Adjust eCO2 threshold for Excellent 2025-05-19 16:49:33 -04:00
66573f2346 Adjust Sensirion update interval
15s is a little too frequent, so make it 60s
2025-05-17 00:14:02 -04:00
a2ff9491a3 Fix GPIO pin assignments 2025-05-17 00:11:03 -04:00
1ef3ba2e57 Fix micro wake word model selector 2025-05-17 00:10:54 -04:00
97dd3a9a90 Fix table formatting 2025-05-11 20:02:48 -04:00
993c3407ea Update board to 2.x revision 2025-05-11 20:00:35 -04:00
ae4713969d Rename and update dashboard ref 2025-05-11 19:40:54 -04:00
14 changed files with 26743 additions and 28889 deletions

193
README.md
View File

@ -1,7 +1,10 @@
# SuperSensor v2.x # SuperSensor v2.x
The SuperSensor is an all-in-one voice, motion, presence, temperature/humidity/ **NOTICE**: The Supersensor v2.x is still under development! Parts and configurations
air quality, and light sensor, built on an ESP32 with ESPHome, and inspired may change until the design is finalized.
The SuperSensor is an all-in-one voice, motion, presence, temperature/humidity/air
quality, and light sensor, built on an ESP32 with ESPHome, and inspired
heavily by the EverythingSmartHome Everything Presence One sensor and the heavily by the EverythingSmartHome Everything Presence One sensor and the
HomeAssistant "$13 Voice Assistant" project. HomeAssistant "$13 Voice Assistant" project.
@ -17,9 +20,8 @@ it bare if you like the "PCB on a wall" aesthetic.
To Use: To Use:
* Install the ESPHome configuration `supersensor-2.x.yaml` to a compatible ESP32 devkit (below). * Install the ESPHome configuration `supersensor.yaml` to a compatible ESP32 devkit (below).
* Install the ESP32 and sensors into the custom PCB (if desired). * Install the ESP32 and sensors into the custom PCB.
* [Optional] 3D Print the custom case.
* Power up the SuperSensor, connect to the WiFi AP, and connect it to your network. * Power up the SuperSensor, connect to the WiFi AP, and connect it to your network.
* Install the SuperSensor somewhere that makes sense. * Install the SuperSensor somewhere that makes sense.
* Add/adopt the SuperSensor to HomeAssistant using the automatic name. * Add/adopt the SuperSensor to HomeAssistant using the automatic name.
@ -35,21 +37,19 @@ Note: Once programmed, the output LED will flash continuously until connected
For more details, please [see my first blog post on the SuperSensor project](https://www.boniface.me/the-supersensor/) For more details, please [see my first blog post on the SuperSensor project](https://www.boniface.me/the-supersensor/)
and [my update post on version 2.0](https://www.boniface.me/the-supersensor-2.0). and [my update post on version 2.0](https://www.boniface.me/the-supersensor-2.0).
**NOTE: For those with v1.x hardware, see [the branch for that code instead](https://github.com/joshuaboniface/supersensor/tree/v1.x).** **NOTE: For those with v1.x hardware, see [the repository for that code instead](https://github.com/joshuaboniface/supersensor).**
## Major Changes from 1.x ## Major Changes from 1.x
1. Replaced the Bosch BME680 with the Sensirion SHT45 and Sensirion SGP30. 1. Replaced the Bosch BME680 with the Sensirion SHT45 and Sensirion SGP41.
The BME680 proved to be woefully unreliable in my testing. Temperature and humidity were wildly off of what other thermometers/hydrometers The BME680 proved to be woefully unreliable in my testing. Temperature was fairly accurate (internal heating and offset notwithstanding),
would report, including a known issue with self-heating rending the temperature anywhere from 2 to 5(!) degrees Celsius above the actual but humidity was wildly off of what other thermometers/hydrometers would report. In addition, the AQ functionality of the sensor was a
temperature. source of much frustration and I was never able to get it to work reliably, either with the official BSEC library or with my own attempts
at self-configuration.
In addition, the AQ functionality of the sensor was a source of much frustration and I was never able to get it to work reliably, either Thus, this sensor has been replaced with two Sensirion sensors which in my experience so far have been much more reliable and consistent.
with the official BSEC library or with my own attempts at self-configuration. There is a slight cost increase due to these sensors, but not signfigant enough to outweigh the benefit of reliable monitoring they confer.
Thus, this sensor has been replaced with two Sensirion sensors which in my experience so far have been much more reliable and consistent,
and the cost difference is negligible.
2. Replaced the SR602 PIR sensor with the AM312 PIR sensor. 2. Replaced the SR602 PIR sensor with the AM312 PIR sensor.
@ -63,16 +63,12 @@ and [my update post on version 2.0](https://www.boniface.me/the-supersensor-2.0)
3. Completely redesigned the custom PCB around the above sensor changes, which is now more compact in a 50x55mm almost-square configuration. 3. Completely redesigned the custom PCB around the above sensor changes, which is now more compact in a 50x55mm almost-square configuration.
4. Significantly cleaned up the ESPHome configuration, to support the above sensors and remove a lot of cruft that was caused by the BME680. 4. Significantly cleaned up the ESPHome configuration, to support the above sensors and remove a lot of cruft that was caused by the BME680.
This includes a new set of custom AQ calculations based on the SGP30 and SHT45 sensors that, while not necessarily following the full EPA
IAQI spec, should still give a reasonable view of the air quality conditions of an interior room and not deviate wildly and nonsensically
like the BME680 did. Details of the calculation are provided below.
## Parts List ## Parts List
|-------|--------------------|----------------------------------|-------|
| Qty | Component | Cost (2025/05 CAD, ex. shipping) | Links | | Qty | Component | Cost (2025/05 CAD, ex. shipping) | Links |
|-------|--------------------|----------------------------------|-------| |-------|--------------------|----------------------------------|-------|
| 1 | GY-SGP30 | $5.73 | [AliExpress](https://www.aliexpress.com/item/1005008473372972.html) | | 1 | GY-SGP41 | $11.08 | [AliExpress](https://www.aliexpress.com/item/1005006746827606.html) |
| 1 | GY-SHT45 | $5.67 | [AliExpress](https://www.aliexpress.com/item/1005008175340220.html)* | | 1 | GY-SHT45 | $5.67 | [AliExpress](https://www.aliexpress.com/item/1005008175340220.html)* |
| 1 | SR602 | $0.81 | [AliExpress](https://www.aliexpress.com/item/1005001572550300.html) | | 1 | SR602 | $0.81 | [AliExpress](https://www.aliexpress.com/item/1005001572550300.html) |
| 1 | TSL2591 | $4.59 | [AliExpress](https://www.aliexpress.com/item/1005008619462097.html) | | 1 | TSL2591 | $4.59 | [AliExpress](https://www.aliexpress.com/item/1005008619462097.html) |
@ -81,16 +77,59 @@ and [my update post on version 2.0](https://www.boniface.me/the-supersensor-2.0)
| 1 | ESP32 HW-395 | $6.67 | [AliExpress](https://www.aliexpress.com/item/1005006019875837.html)* | | 1 | ESP32 HW-395 | $6.67 | [AliExpress](https://www.aliexpress.com/item/1005006019875837.html)* |
| 2 | RBG LED | $0.09 ($9.12/100) | [Amazon](https://www.amazon.ca/dp/B09Y8M2PKS) | | 2 | RBG LED | $0.09 ($9.12/100) | [Amazon](https://www.amazon.ca/dp/B09Y8M2PKS) |
| 1 | 470Ω resistor | $0.08 ($7.99/100) | [Amazon](https://www.amazon.ca/dp/B08MKQX2XT) | | 1 | 470Ω resistor | $0.08 ($7.99/100) | [Amazon](https://www.amazon.ca/dp/B08MKQX2XT) |
| 1 | Female pin header† | $1.59 ($15.99/10) | [Amazon](https://www.amazon.ca/dp/B08CMNRXJ1) | | 2 | Female pin header† | $1.59 ($15.99/10) | [Amazon](https://www.amazon.ca/dp/B08CMNRXJ1) |
| 1 | Custom PCB (JLC) | $0.69 ($6.89/10) | [GitHub](https://github.com/joshuaboniface/supersensor) | | 1 | Custom PCB (JLC) | $0.69 ($6.89/10) | [GitHub](https://github.com/joshuaboniface/supersensor) |
| 1 | 3D Printed case | $?.??‡ | [GitHub](https://github.com/joshuaboniface/supersensor) | | **TOTAL** | | **$40.58** | |
|-------|--------------------|----------------------------------|-------|
| TOTAL | | $33.64 | |
|-------|--------------------|----------------------------------|-------|
`*` Ensure you select the correct device on the page as it shows multiple options. `*` Ensure you select the correct device on the page as it shows multiple options.
`†` This is optional and only required if you don't want to directly solder the ESP32 to the board, but I recommend it.
`` Providing a price is impossible due to the wide range of possible fillament types and brands, but should be negligible. `` One of these sets is optional, and is useful if you do not want to solder the individual sensors directly to the board (see below).
### To Solder or Not To Solder
Personally, for my Supersensor 1.x's and the initial batch of Supersensor 2.x's, I directly soldered
all the non-ESP components to the board. This proved to be a major mistake when I later decided
to switch from SGP30's to SGP41's after some testing and I had to desolder all of them, ruining
several PCBs in the process. It was also a hassle to desolder the existing sensors for reuse
during the 1.x to 2.x conversion.
As a result, I actually strongly encourage anyone building one of these units to leverage sockets
for all components, to allow for quick swapping if any turn out to be defective or if future changes
are warranted.
Note that due to the PCB design, you *must* socket at least one set of components - either the ESP32
or the sensors on the front. Due to the positioning and overlap, it would be impossible to solder
everything directly to the board, as the ESP covers several of the solder points of the front
sensors and vice versa.
You can use the provided 40-pin female headers exclusively if you wish, and cut them to length for
the individual sensors as needed, or you can use individually-sized female headers in the following
quantities should you wish for a slightly neater finish:
* 3x 3-pin (AM302, INMP441 x2)
* 2x 4-pin (SGP41, SHT45)
* 1x 5-pin (LD2410C)
* 1x 6-pin (TSL2591)
I will leave it up to the reader to source these specific sizes if they desire (I found all except
a 5-pin on Amazon, and just used a 6-pin with one pin removed).
I still directly solder the RGB LEDs and resistor to the board for simplicity as these very small
leads are not easily socketed, and these components are so inexpensive as to be effectively
disposable along with the PCB should that be required.
### Part Swaps
To save a little money, it is possible to swap out the two Sensirion sensors for their less-feature-
rich peers, with no code changes:
* SGP41 -> SGP40 - removes the NOx functionality
* SHT45 -> SHT40/41/43 - less accuracy
Personally, I do not find the minimal cost savings to be worth sacrificing the extra potential
functionality, so I recommend using the provided models, but this is up to the builder to decide.
No other parts can be easily swapped without code or PCB design changes.
## Configurable Options ## Configurable Options
@ -112,15 +151,19 @@ SuperSensors in a single room and only want one to respond to voice commands.
If enabled (the default), when overall presence is detected, the LEDs will If enabled (the default), when overall presence is detected, the LEDs will
glow "white" at 15% power to signal presence. glow "white" at 15% power to signal presence.
### Temperature Offset (selector, -7 to +3 @ 0.1) ### Temperature Offset (selector, -30 to +10 @ 0.1, -5 default)
Allows calibration of the SHT45 temperature sensor with an offset from -7 to +3 Allows calibration of the SHT45 temperature sensor with an offset from -30 to +10
degrees C. Useful if the sensor is misreporting actual ambient tempreatures. degrees C. Useful if the sensor is misreporting actual ambient tempreatures. Due
to internal heating of the SHT45 by the ESP32, this defaults to -5; further
calibration may be needed for your sensors and environment based on an external
reference.
### Humidity Offset (selector, -10 to +10 @ 0.1) ### Humidity Offset (selector, -20 to +20 @ 0.1)
Allows calibration of the SHT45 humidity sensor with an offset from -10 to +10 Allows calibration of the SHT45 humidity sensor with an offset from -10 to +10
percent relative humidity. Useful if the sensor is misreporting actual humidity. percent relative humidity. Useful if the sensor is misreporting actual humidity
based on an external reference.
### PIR Hold Time (selector, 0 to +60 @ 5, 0 default) ### PIR Hold Time (selector, 0 to +60 @ 5, 0 default)
@ -228,70 +271,28 @@ is likely not useful.
## AQ Details ## AQ Details
The SuperSensor 2.x provides 2 base air quality sensors (numeric), from which The SuperSensor 2.0 features an SGP41 air quality sensor by Sensirion. This is a powerful AQ
4 human-readable text sensors are derived. sensor which powers several commercial devices including the AirGradient One, which gave
us a lot of our configuration via their sharing of algorithms.
The goal of these sensors is to track general comfort and livability in a The sensor provides two base readings: a VOC Index, and a NOx Index. These values are both
room, not specific contaminants or conditions. Because the SGP30 can only floating references centered at 100 (VOC) and 1 (NOx), where that value represents "normal"
track TVOC and eCO2, we do not track particulates, CO, NOx, or CH2O, all air over the previous 24 hours. These sensors are very useful for any sort of quick-change
of which are required for a full EPA (I)AQI score. This means the best automations, e.g. turn on a fan if levels spike due to cooking.
we can do is approximate (I)AQI roughly, and since a scale of 0-500 based
on approximations seems pointless, I went with much simpler 1-4/5 scores
instead. I feel this does a good enough job to be useful for 99% of rooms.
We also cannot really debate whether the BME680 is actually any more accurate In addition, we leverage AirGradient's published forumulas to convert the VOC index into
in this regard, since their algorithms are proprietary and all that is exposed actual VOC quantities, in both µg/m³ and ppb. While this may drift due to the sensor's regular
normally is a single resistance value, so in my opinion this is actually internal recalibration, I feel that following what AirGradient does is sufficient enough
superior to that sensor anyways with two discrete datapoints (versus one), for any real-world home usage. Further, we use a very rough conversion of the aforementioned
even if it does still seem limited when compared to dedicated AQ sensors. VOC quantity into an eCO2 reading, using Isobutylene as a reference gas. These sensors are
And that is to say nothing of the issues with that sensor (constantly climbing more useful for display purposes, to show the current levels in a room in a dashboard or
IAQ values over time, poor calibration, etc.). other such place, for human consumption. Note that no such conversions are done for NOx as
there are no (that I can find) published empirical calculations for this conversion, unlike
for VOCs via AirGradient.
### Base Numeric Values Note however that like all MOx sensors, the SGP41 does not differentiate gasses, and as
such cannot tell the difference between normal, everyday natural VOCs like those in
#### IAQ Index (1-5) breath or from e.g. ripening fruit, and dangerous VOCs from e.g. construction materials.
These should be used only as a general indication of air quality over short periods, rather
The IAQ index is calculated based on the TVOC and eCO2 values from the SGP30 than an absolute reference over long periods (much to my own frustration but inevitable
sensor, to provide 5 levels of air quality. This corresponds approximately begruding acceptance).
to the levels provided by the BME680 (0-50, 50-100, 100-200, 200-300, 300+).
5 is "excellent": the TVOC is <65 ppb and the eCO2 is <600 ppm.
4 is "good": the TVOC is 65-220 ppb or the eCO2 is 600-800 ppm.
3 is "moderate": the TVOC is 220-660 ppb or the eCO2 is 800-1200 ppm.
2 is "poor": the TVOC is 660-2200 ppb or the eCO2 is 1200-2000 ppm.
1 is "unhealthy": the TVOC is >2200 ppb or the eCO2 is >2000 ppm.
#### Room Health Score (1-4)
The Room Health Score is calculated based on the IAQ, temperature, and humidity,
and is designed to show how "nice" a room is to be in. Generally a 4 is a nice
place to be, especially for someone with respiratory issues like myself, and lower
scores indicate more deviations from the norms or poor IAQ.
4 is "optimal": IAQ is >= 4 ("excellent" or "good"), temperature is between 18C and 24C, and humidity is between 40% and 60%.
3 is "fair": One of the above is not true, and IAQ is >= 3 ("moderate").
2 is "poor": Two of the above are not true, and IAQ is >= 2 ("poor").
1 is "bad": All of the above are not true or IAQ is 1 ("unhealthy") regardless of other values.
Note that IAQ levels hold a major sway over this level, and decreasing IAQ
scores will push the room score lower regardless of temperature or humidity.
It is best used together with the individual sensors to determine exactly
what is wrong with the room.
### Derived Text Sensors
#### VOC Level
This reports the VOC level alone, based on the scale under IAQ Index, in textual form ("Excellent, "Good", etc.).
#### CO2 Level
This reports the eCO2 level alone, based on the scale under IAQ Index, in textual form ("Excellent, Good", etc.).
#### IAQ Classification
This reports the IAQ Index in textual form ("Excellent", "Good", etc.).
#### Room Health
This reports the Room Health Score in textual form ("Optimal", "Fair", "Poor", "Bad").

5
archive/.gitignore vendored
View File

@ -1,5 +0,0 @@
# Gitignore settings for ESPHome
# This is an example and may include too much for your use-case.
# You can modify this file to suit your needs.
/.esphome/
/secrets.yaml

View File

@ -1,393 +0,0 @@
---
###############################################################################
# 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/>.
#
###############################################################################
substitutions:
# How long a PIR activation should be held for
pir_holdtime: "15s"
esphome:
name: supersensor
name_add_mac_suffix: true
friendly_name: "Supersensor"
project:
name: joshuaboniface.supersensor
version: "0.1"
includes:
- veml7700.h
libraries:
- "Wire"
- "Adafruit Unified Sensor"
- "SPI"
- "Adafruit BusIO"
- "Adafruit VEML7700 Library"
on_boot:
- priority: 600
then:
- light.turn_on:
id: output_led
effect: flash
red: 1
green: 1
blue: 1
dashboard_import:
package_import_url: github://joshuaboniface/supersensor/supersensor.yaml
import_full_config: false
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"
script:
- id: pir_handler
then:
- lambda: |-
id(pir_motion).publish_state(true);
- while:
condition:
binary_sensor.is_on: pir_gpio
then:
- delay: ${pir_holdtime}
- lambda: |-
id(pir_motion).publish_state(false);
# Enable logging only via web
logger:
level: DEBUG
baud_rate: 0
api:
encryption:
key: !secret api_encryption_key
on_client_disconnected:
- light.turn_on:
id: output_led
effect: flash
red: 1
green: 1
blue: 1
- switch.turn_off: use_wake_word
on_client_connected:
- switch.turn_on: use_wake_word
- delay: 2s
- switch.turn_off: use_wake_word
- delay: 2s
- switch.turn_on: use_wake_word
- delay: 2s
- light.turn_off:
id: output_led
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:
id: LD1115H_UART_BUS
rx_pin: GPIO3
tx_pin: GPIO1
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_error:
- light.turn_off:
id: output_led
transition_length: 1s
- logger.log: "Voice Assistant encountered an error; restarting it"
- switch.turn_off: use_wake_word
- delay: 1s
- switch.turn_on: use_wake_word
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
name: "PIR GPIO"
id: pir_gpio
pin: GPIO33
internal: false
device_class: motion
on_press:
- script.stop: pir_handler
- script.execute: pir_handler
- platform: template
name: "PIR Motion"
id: pir_motion
device_class: motion
- platform: gpio
name: "LD1115H Presence"
id: ld11115h_gpio
pin: GPIO23
internal: false
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: 5s
address: 0x76
- platform: custom
lambda: |-
auto veml7700 = new VEML7700CustomSensor();
App.register_component(veml7700);
return {veml7700->lux_sensor};
sensors:
- name: "VEML7700 Illumination"
unit_of_measurement: Lux
accuracy_decimals: 0
- platform: uptime
name: "ESP32 Uptime"
icon: mdi:clock-alert
update_interval: 5s
entity_category: "diagnostic"
- platform: wifi_signal
name: "ESP32 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"
number:
- platform: template
name: LD1115H TH1 #TH1 is Movement/Motion Sensitivity
id: LD1115H_TH1
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "120" #Default TH1 Setting
min_value: 20
max_value: 1200
step: 10
set_action:
then:
- uart.write:
id: LD1115H_UART_BUS
data: !lambda |-
std::string th1st = "th1=" + str_sprintf("%.0f",x) +" \n";
return std::vector<uint8_t>(th1st.begin(), th1st.end());
- platform: template
name: LD1115H TH2 #TH2 is Occupancy/Presence Sensitivity
id: LD1115H_TH2
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "250" #Default TH2 Setting
min_value: 50
max_value: 2500
step: 10
set_action:
then:
- uart.write:
id: LD1115H_UART_BUS
data: !lambda |-
std::string th2st = "th2=" + str_sprintf("%.0f",x) +" \n";
return std::vector<uint8_t>(th2st.begin(), th2st.end());

View File

@ -1,651 +0,0 @@
---
###############################################################################
# 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/>.
#
###############################################################################
#
# Compile-time Substitutions
#
substitutions:
ignored_radar_ranges: |-
{
{ 2.20, 3.30 },
{ 2.20, 2.75 }
}
#
# Core configuration
#
esphome:
name: supersensor
name_add_mac_suffix: true
on_boot:
- priority: 600
then:
- light.turn_on:
id: output_led
effect: flash
red: 1
green: 1
blue: 1
- priority: 100
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());
- priority: -500
then:
- wait_until: api.connected
- delay: 3s
- 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"
external_components:
- source:
type: git
url: https://github.com/ssieb/custom_components
components: [ serial ]
logger:
level: VERBOSE
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:
id: LD1125H_UART_BUS
rx_pin: GPIO18
tx_pin: GPIO19
setup_priority: 200
baud_rate: 115200
data_bits: 8
stop_bits: 1
parity: NONE
i2c:
sda: GPIO21
scl: GPIO22
scan: true
#
# Radar Globals
#
globals:
- id: LD1125H_Last_Update_Time
type: uint32_t
restore_value: no
initial_value: millis()
- id: LD1125H_Last_Time
type: time_t
restore_value: no
initial_value: time(NULL)
- id: LD1125H_Last_Mov_Time
type: time_t
restore_value: no
initial_value: time(NULL)
- id: LD1125H_Clearance_Status
type: bool
restore_value: no
initial_value: "false"
- id: ignored_ranges
type: std::vector<std::vector<float>>
restore_value: no
initial_value: ${ignored_radar_ranges}
#
# 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_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: "Voice 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);
#
# Motion Sensors
#
binary_sensor:
- platform: gpio
pin: GPIO33
name: "PIR Motion"
device_class: motion
- platform: template
name: "LD1125H Occupancy & Movement"
id: LD1125H_MovOcc_Binary
device_class: occupancy
- platform: template
name: "LD1125H Motion"
id: LD1125H_Mov_Binary
device_class: motion
#
# LD1125H Text Sensors
#
text_sensor:
- platform: serial
uart_id: LD1125H_UART_BUS
name: "LD1125H UART Text"
id: LD1125H_UART_Text
icon: mdi:format-text
internal: true
on_value:
then:
- lambda: |-
if ((millis() - id(LD1125H_Last_Update_Time)) < 1000) {
return;
} else {
id(LD1125H_Last_Update_Time) = millis();
}
for (const auto& row : id(ignored_ranges)) {
if ( ( atof(id(LD1125H_UART_Text).state.substr(9).c_str()) > row[0] ) && ( atof(id(LD1125H_UART_Text).state.substr(9).c_str()) < row[1] ) ) {
break;
} else {
if (id(LD1125H_UART_Text).state.substr(0,3) == "occ") {
id(LD1125H_Distance).publish_state(atof(id(LD1125H_UART_Text).state.substr(9).c_str()));
if ((time(NULL)-id(LD1125H_Last_Mov_Time))>id(LD1125H_Mov_Time).state) {
id(LD1125H_Occupancy).publish_state("Occupied");
if (id(LD1125H_MovOcc_Binary).state == false) {
id(LD1125H_MovOcc_Binary).publish_state(true);
}
if (id(LD1125H_Mov_Binary).state == true) {
id(LD1125H_Mov_Binary).publish_state(false);
}
}
if (id(LD1125H_MovOcc_Binary).state == false) {
id(LD1125H_MovOcc_Binary).publish_state(true);
}
id(LD1125H_Last_Time) = time(NULL);
if (id(LD1125H_Clearance_Status) == true) {
id(LD1125H_Clearance_Status) = false;
}
}
else if (id(LD1125H_UART_Text).state.substr(0,3) == "mov") {
id(LD1125H_Distance).publish_state(atof(id(LD1125H_UART_Text).state.substr(9).c_str()));
id(LD1125H_Occupancy).publish_state("Movement");
if (id(LD1125H_MovOcc_Binary).state == false) {
id(LD1125H_MovOcc_Binary).publish_state(true);
}
if (id(LD1125H_Mov_Binary).state == false) {
id(LD1125H_Mov_Binary).publish_state(true);
}
id(LD1125H_Last_Mov_Time) = time(NULL);
id(LD1125H_Last_Time) = time(NULL);
if (id(LD1125H_Clearance_Status) == true) {
id(LD1125H_Clearance_Status) = false;
}
}
}
}
- platform: template
name: "LD1125H Occupancy Status"
id: LD1125H_Occupancy
icon: "mdi:motion-sensor"
#
# Other Sensors
#
sensor:
# BME280 Temperature/Humidity/Pressure sensor
- platform: bme280
temperature:
name: "BME280 Temperature"
humidity:
name: "BME280 Humidity"
pressure:
name: "BME280 Pressure"
update_interval: 15s
address: 0x76
# BMP280 Temperature/Pressure sensor (TEMPORARY)
- platform: bmp280
temperature:
name: "BMP280 Temperature"
pressure:
name: "BMP280 Pressure"
update_interval: 15s
address: 0x76
# LD1125H Distance
- platform: template
name: LD1125H Detect Distance
id: LD1125H_Distance
icon: mdi:signal-distance-variant
unit_of_measurement: "m"
accuracy_decimals: 2
# Internal sensors
- 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
#
# Radar Clear Scan Time Interval
#
interval:
- interval: 1s
then:
lambda: |-
if ((time(NULL)-id(LD1125H_Last_Time))>id(LD1125H_Clearance_Time).state) {
if ((id(LD1125H_Clearance_Status) == false) || (id(LD1125H_Occupancy).state != "Clear")) {
id(LD1125H_Occupancy).publish_state("Clear");
id(LD1125H_Clearance_Status) = true;
}
if (id(LD1125H_MovOcc_Binary).state == true) {
id(LD1125H_MovOcc_Binary).publish_state(false);
}
if (id(LD1125H_Mov_Binary).state == true) {
id(LD1125H_Mov_Binary).publish_state(false);
}
}

View File

@ -1,480 +0,0 @@
---
###############################################################################
# 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/>.
#
###############################################################################
substitutions:
# How long a PIR activation should be held for
pir_holdtime: "15s"
esphome:
name: supersensor
name_add_mac_suffix: true
friendly_name: "Supersensor"
project:
name: joshuaboniface.supersensor
version: "0.1"
# includes:
# - veml7700.h
# libraries:
# - "Wire"
# - "Adafruit Unified Sensor"
# - "SPI"
# - "Adafruit BusIO"
# - "Adafruit VEML7700 Library"
on_boot:
- priority: 600
then:
- light.turn_on:
id: output_led
effect: flash
red: 1
green: 1
blue: 1
dashboard_import:
package_import_url: github://joshuaboniface/supersensor/supersensor.yaml
import_full_config: false
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"
script:
- id: pir_handler
then:
- lambda: |-
id(pir_motion).publish_state(true);
- while:
condition:
binary_sensor.is_on: pir_gpio
then:
- delay: ${pir_holdtime}
- lambda: |-
id(pir_motion).publish_state(false);
# Enable logging only via web
logger:
level: DEBUG
baud_rate: 115200
api:
encryption:
key: !secret api_encryption_key
on_client_disconnected:
- light.turn_on:
id: output_led
effect: flash
red: 1
green: 1
blue: 1
- switch.turn_off: use_wake_word
on_client_connected:
- switch.turn_on: use_wake_word
- delay: 2s
- switch.turn_off: use_wake_word
- delay: 2s
- switch.turn_on: use_wake_word
- delay: 2s
- light.turn_off:
id: output_led
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:
id: ld2410_uart
rx_pin: GPIO18
tx_pin: GPIO19
baud_rate: 256000
data_bits: 8
stop_bits: 1
parity: NONE
i2c:
sda: GPIO16
scl: GPIO17
scan: true
i2s_audio:
i2s_lrclk_pin: GPIO27
i2s_bclk_pin: GPIO26
microphone:
- platform: i2s_audio
id: mic
adc_type: external
i2s_din_pin: GPIO14
pdm: false
voice_assistant:
microphone: mic
use_wake_word: false
noise_suppression_level: 2
auto_gain: 31dBFS
volume_multiplier: 4.0
id: assist
on_error:
- light.turn_off:
id: output_led
transition_length: 1s
- logger.log: "Voice Assistant encountered an error; restarting it"
- switch.turn_off: use_wake_word
- delay: 1s
- switch.turn_on: use_wake_word
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: GPIO32
- platform: ledc
id: rgb_g
pin: GPIO33
- platform: ledc
id: rgb_b
pin: GPIO25
ld2410:
id: ld2410_radar
uart_id: ld2410_uart
# max_move_distance : 6m
# max_still_distance: 0.75m
# g0_move_threshold: 10
# g0_still_threshold: 20
# g1_move_threshold: 10
# g1_still_threshold: 20
# g2_move_threshold: 20
# g2_still_threshold: 21
# g3_move_threshold: 30
# g3_still_threshold: 31
# g4_move_threshold: 40
# g4_still_threshold: 41
# g5_move_threshold: 50
# g5_still_threshold: 51
# g6_move_threshold: 60
# g6_still_threshold: 61
# g7_move_threshold: 70
# g7_still_threshold: 71
# g8_move_threshold: 80
# g8_still_threshold: 81
binary_sensor:
- 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 Motion"
id: pir_motion
device_class: motion
- platform: ld2410
ld2410_id: ld2410_radar
has_target:
name: "LD2410C Presence"
has_moving_target:
name: "LD2410C Moving Target"
has_still_target:
name: "LD2410C Still Target"
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: 5s
address: 0x76
# - platform: custom
# lambda: |-
# auto veml7700 = new VEML7700CustomSensor();
# App.register_component(veml7700);
# return {veml7700->lux_sensor};
# sensors:
# - name: "VEML7700 Illumination"
# unit_of_measurement: Lux
# accuracy_decimals: 0
- platform: ld2410
ld2410_id: ld2410_radar
moving_distance:
name: "LD2410C Moving Distance"
id: moving_distance
still_distance:
name: "LD2410C Still Distance"
id: still_distance
moving_energy:
name: "LD2410C Move Energy"
still_energy:
name: "LD2410C Still Energy"
detection_distance:
name: "LD2410C Presence Distance"
- platform: uptime
name: "ESP32 Uptime"
icon: mdi:clock-alert
update_interval: 5s
entity_category: diagnostic
- platform: wifi_signal
name: "ESP32 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
text_sensor:
- platform: ld2410
version:
name: "LD2410C Firmware Version"
mac_address:
name: "LD2410C MAC Address"
button:
- platform: ld2410
restart:
name: "LD2410C Restart"
factory_reset:
name: "LD2410C Factory Reset"
- 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);
- platform: ld2410
engineering_mode:
name: "LD2410C Engineering Mode"
bluetooth:
name: "LD2410C Bluetooth"
number:
- 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:
- platform: ld2410
distance_resolution:
name: "LD2410C Distance Resolution"

View File

@ -1,365 +0,0 @@
substitutions:
devicename: "supersensor" #Rename the device what you want.
upper_devicename: ESP Radar #Rename the device what you want.
entity_name_prefix: ""
ignored_radar_ranges: |-
{
{2.20,3.30},
{2.20,2.75}
}
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"
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
logger:
level: DEBUG
baud_rate: 115200
logs:
text_sensor: INFO
esphome:
name: supersensor
name_add_mac_suffix: false
on_boot:
priority: 200
then:
- 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());
external_components:
- source:
type: git
url: https://github.com/ssieb/custom_components #Thanks for @ssieb components.
components: [ serial ]
uart:
id: LD1125H_UART_BUS
rx_pin: GPIO16
tx_pin: GPIO17
baud_rate: 115200
data_bits: 8
stop_bits: 1
parity: NONE
# debug:
# direction: BOTH
# dummy_receiver: false
# after:
# delimiter: "\n"
# sequence:
# - lambda: UARTDebug::log_string(direction, bytes);
status_led:
pin:
number: GPIO2
inverted: false
globals:
- id: LD1125H_Last_Time
type: time_t
restore_value: no
initial_value: time(NULL)
- id: LD1125H_Last_Mov_Time
type: time_t
restore_value: no
initial_value: time(NULL)
- id: LD1125H_Clearance_Status
type: bool
restore_value: no
initial_value: "false"
- id: ignored_ranges
type: std::vector<std::vector<float>>
restore_value: no
initial_value: ${ignored_radar_ranges}
interval:
- interval: 1s #Clearance Scan Time
setup_priority: 200
then:
lambda: |-
if ((time(NULL)-id(LD1125H_Last_Time))>id(LD1125H_Clearance_Time).state) {
if ((id(LD1125H_Clearance_Status) == false) || (id(LD1125H_Occupancy).state != "Clear")) {
id(LD1125H_Occupancy).publish_state("Clear");
id(LD1125H_Clearance_Status) = true;
}
if (id(LD1125H_MovOcc_Binary).state == true) {
id(LD1125H_MovOcc_Binary).publish_state(false);
}
if (id(LD1125H_Mov_Binary).state == true) {
id(LD1125H_Mov_Binary).publish_state(false);
}
}
number:
- platform: template
name: ${entity_name_prefix} 0-2.8m Movement Minimum Signal
id: LD1125H_mth1_mov
icon: "mdi:cogs"
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: ${entity_name_prefix} 2.8m-8m Movement Minimum Signal
id: LD1125H_mth2_mov
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
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: ${entity_name_prefix} 8m-∞ Movement Minimum Signal
id: LD1125H_mth3_mov
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "20"
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: ${entity_name_prefix} 0-2.8m Occupancy Minimum Signal
id: LD1125H_mth1_occ
icon: "mdi:cogs"
optimistic: true
restore_value: true
initial_value: "60"
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_occ=" + str_sprintf("%.0f",x) +"\r\n";
return std::vector<uint8_t>(mth1_occ_st.begin(), mth1_occ_st.end());
- platform: template
name: ${entity_name_prefix} 2.8m-8m Occupancy Minimum Signal
id: LD1125H_mth2_occ
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "55"
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: ${entity_name_prefix} 8m-∞ Occupancy Minimum Signal
id: LD1125H_mth3_occ
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "20"
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: ${entity_name_prefix} Max Detection Distance (m) #rmax is max detection distance.
id: LD1125H_rmax
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "8" #Default rmax Setting
min_value: 0.0
max_value: 60
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: ${entity_name_prefix} Seconds to Clear Mov/Occ
id: LD1125H_Clearance_Time
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "20" #LD1125H Mov/Occ > Clearance Time Here
min_value: 0
max_value: 60
step: 1
- platform: template
name: ${entity_name_prefix} Seconds to Detection
id: LD1125H_Mov_Time
icon: "mdi:cogs"
optimistic: true
restore_value: true #If you don't want to store the setting at ESP, set it to false.
initial_value: "1" #LD1125H Mov > Occ Time Here
min_value: 0.1
max_value: 10
step: 0.1
sensor:
- platform: template
name: ${entity_name_prefix} Objects Distance
id: LD1125H_Distance
icon: "mdi:signal-distance-variant"
unit_of_measurement: "m"
accuracy_decimals: 2
filters: # Use Filter To Debounce
- sliding_window_moving_average:
window_size: 200
send_every: 10
- heartbeat: 0.2s
text_sensor:
- platform: serial
uart_id: LD1125H_UART_BUS
name: ${entity_name_prefix} UART Text
id: LD1125H_UART_Text
icon: "mdi:format-text"
internal: True
on_value:
lambda: |-
for (const auto& row : id(ignored_ranges)) {
if ( ( atof(id(LD1125H_UART_Text).state.substr(9).c_str()) > row[0] ) && ( atof(id(LD1125H_UART_Text).state.substr(9).c_str()) < row[1] ) ) {
break;
} else {
if (id(LD1125H_UART_Text).state.substr(0,3) == "occ") {
id(LD1125H_Distance).publish_state(atof(id(LD1125H_UART_Text).state.substr(9).c_str()));
if ((time(NULL)-id(LD1125H_Last_Mov_Time))>id(LD1125H_Mov_Time).state) {
id(LD1125H_Occupancy).publish_state("Occupied");
if (id(LD1125H_MovOcc_Binary).state == false) {
id(LD1125H_MovOcc_Binary).publish_state(true);
}
if (id(LD1125H_Mov_Binary).state == true) {
id(LD1125H_Mov_Binary).publish_state(false);
}
}
if (id(LD1125H_MovOcc_Binary).state == false) {
id(LD1125H_MovOcc_Binary).publish_state(true);
}
id(LD1125H_Last_Time) = time(NULL);
if (id(LD1125H_Clearance_Status) == true) {
id(LD1125H_Clearance_Status) = false;
}
}
else if (id(LD1125H_UART_Text).state.substr(0,3) == "mov") {
id(LD1125H_Distance).publish_state(atof(id(LD1125H_UART_Text).state.substr(9).c_str()));
id(LD1125H_Occupancy).publish_state("Movement");
if (id(LD1125H_MovOcc_Binary).state == false) {
id(LD1125H_MovOcc_Binary).publish_state(true);
}
if (id(LD1125H_Mov_Binary).state == false) {
id(LD1125H_Mov_Binary).publish_state(true);
}
id(LD1125H_Last_Mov_Time) = time(NULL);
id(LD1125H_Last_Time) = time(NULL);
if (id(LD1125H_Clearance_Status) == true) {
id(LD1125H_Clearance_Status) = false;
}
}
}
}
- platform: template
name: ${entity_name_prefix} Occupancy Status
id: LD1125H_Occupancy
icon: "mdi:motion-sensor"
binary_sensor:
- platform: template
name: ${entity_name_prefix} Occupancy or Movement
id: LD1125H_MovOcc_Binary
device_class: occupancy
- platform: template
name: ${entity_name_prefix} Motion
id: LD1125H_Mov_Binary
device_class: motion

View File

@ -1,103 +0,0 @@
esphome:
name: supersensor
name_add_mac_suffix: true
friendly_name: "Supersensor"
project:
name: joshuaboniface.supersensor
version: "0.1"
logger:
level: DEBUG
baud_rate: 115200
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"
script:
- id: ledtest
then:
- light.turn_on:
id: output_led
effect: flash_white
- delay: 5s
- light.turn_off:
id: output_led
- delay: 5s
- light.turn_on:
id: output_led
red: 1
green: 0
blue: 0
- delay: 5s
- light.turn_on:
id: output_led
red: 0
green: 1
blue: 0
- delay: 5s
- light.turn_on:
id: output_led
red: 0
green: 0
blue: 1
interval:
- interval: 30s
then:
- script.stop: ledtest
- light.turn_off: output_led
- script.execute: ledtest
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: 50%
red: 40%
green: 50%
blue: 45%
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: GPIO32
max_power: 100%
- platform: ledc
id: rgb_g
pin: GPIO33
max_power: 100%
- platform: ledc
id: rgb_b
pin: GPIO25
max_power: 100%

View File

@ -1,99 +0,0 @@
---
###############################################################################
# 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: pirtester
friendly_name: "PIRTester"
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
binary_sensor:
- platform: gpio
pin: GPIO32
name: "PIR 32"
device_class: motion
- platform: gpio
pin: GPIO33
name: "PIR 33"
device_class: motion
- platform: gpio
pin: GPIO25
name: "PIR 25"
device_class: motion
- platform: gpio
pin: GPIO26
name: "PIR 26"
device_class: motion
- platform: gpio
pin: GPIO27
name: "PIR 27"
device_class: motion
- platform: gpio
pin: GPIO14
name: "PIR 14"
device_class: motion
- platform: gpio
pin: GPIO12
name: "PIR 12"
device_class: motion
- platform: gpio
pin: GPIO13
name: "PIR 13"
device_class: motion

View File

@ -1,866 +0,0 @@
---
###############################################################################
# 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"
project:
name: joshuaboniface.supersensor
version: "0.1"
on_boot:
- priority: 600
then:
- lambda: |-
id(supersensor_occupancy).publish_state(false);
id(pir_presence).publish_state(false);
id(light_presence).publish_state(false);
- light.turn_on:
id: output_led
effect: flash_white
dashboard_import:
package_import_url: github://joshuaboniface/supersensor/supersensor.yaml
import_full_config: false
esp32:
board: esp32dev
globals:
- id: pir_hold_time
type: int
restore_value: yes
initial_value: "15"
- id: light_presence_threshold
type: int
restore_value: yes
initial_value: "30"
- id: occupancy_detect_mode
type: int
restore_value: yes
initial_value: "0"
- id: occupancy_clear_mode
type: int
restore_value: yes
initial_value: "0"
script:
- 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
if (pir_counts & radar_counts & light_counts) {
// Logical AND of pir & radar & light
ESP_LOGD("occupancy_detect_handler", "PIR & Radar & Light: %i", pir & radar & light);
id(supersensor_occupancy).publish_state(pir & radar & light);
} else if (pir_counts & radar_counts) {
// Logical AND of pir & radar
ESP_LOGD("occupancy_detect_handler", "PIR & Radar: %i", pir & radar);
id(supersensor_occupancy).publish_state(pir & radar);
} else if (pir_counts & light_counts) {
// Logical AND of pir & light
ESP_LOGD("occupancy_detect_handler", "PIR & Light: %i", pir & light);
id(supersensor_occupancy).publish_state(pir & light);
} else if (radar_counts & light_counts) {
// Logical AND of radar & light
ESP_LOGD("occupancy_detect_handler", "Radar & Light: %i", radar & light);
id(supersensor_occupancy).publish_state(radar & light);
} else if (pir_counts) {
// Only pir
ESP_LOGD("occupancy_detect_handler", "PIR: %i", pir);
id(supersensor_occupancy).publish_state(pir);
} else if (radar_counts) {
// Only radar
ESP_LOGD("occupancy_detect_handler", "Radar: %i", radar);
id(supersensor_occupancy).publish_state(radar);
} else if (light_counts) {
// Only light
ESP_LOGD("occupancy_detect_handler", "Light: %i", light);
id(supersensor_occupancy).publish_state(light);
} else {
ESP_LOGD("occupancy_detect_handler", "None");
id(supersensor_occupancy).publish_state(false);
}
- 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
if (pir_counts & radar_counts & light_counts) {
// Logical AND of pir & radar & light
ESP_LOGD("occupancy_clear_handler", "PIR & Radar & Light: %i", pir & radar & light);
id(supersensor_occupancy).publish_state(pir & radar & light);
} else if (pir_counts & radar_counts) {
// Logical AND of pir & radar
ESP_LOGD("occupancy_clear_handler", "PIR & Radar: %i", pir & radar);
id(supersensor_occupancy).publish_state(pir & radar);
} else if (pir_counts & light_counts) {
// Logical AND of pir & light
ESP_LOGD("occupancy_clear_handler", "PIR & Light: %i", pir & light);
id(supersensor_occupancy).publish_state(pir & light);
} else if (radar_counts & light_counts) {
// Logical AND of radar & light
ESP_LOGD("occupancy_clear_handler", "Radar & Light: %i", radar & light);
id(supersensor_occupancy).publish_state(radar & light);
} else if (pir_counts) {
// Only pir
ESP_LOGD("occupancy_clear_handler", "PIR: %i", pir);
id(supersensor_occupancy).publish_state(pir);
} else if (radar_counts) {
// Only radar
ESP_LOGD("occupancy_clear_handler", "Radar: %i", radar);
id(supersensor_occupancy).publish_state(radar);
} else if (light_counts) {
// Only light
ESP_LOGD("occupancy_clear_handler", "Light: %i", light);
id(supersensor_occupancy).publish_state(light);
} else {
ESP_LOGD("occupancy_clear_handler", "None");
id(supersensor_occupancy).publish_state(false);
}
logger:
level: DEBUG
baud_rate: 115200
api:
encryption:
key: !secret api_encryption_key
on_client_disconnected:
- light.turn_on:
id: output_led
effect: flash_white
- switch.turn_off: use_wake_word
on_client_connected:
# This absolute absurdity is required to prevent
# "no wake word detected" failure loops.
- switch.turn_on: use_wake_word
- delay: 2s
- switch.turn_off: use_wake_word
- delay: 2s
- switch.turn_on: use_wake_word
- delay: 2s
- light.turn_off:
id: output_led
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:
id: ld2410_uart
rx_pin: GPIO19
tx_pin: GPIO18
baud_rate: 256000
data_bits: 8
stop_bits: 1
parity: NONE
i2c:
sda: GPIO16
scl: GPIO17
scan: true
i2s_audio:
i2s_lrclk_pin: GPIO26
i2s_bclk_pin: GPIO27
microphone:
- platform: i2s_audio
id: mic
adc_type: external
i2s_din_pin: GPIO14
pdm: false
voice_assistant:
microphone: mic
use_wake_word: false
noise_suppression_level: 2
auto_gain: 31dBFS
volume_multiplier: 4.0
id: assist
on_error:
- light.turn_off:
id: output_led
transition_length: 1s
- logger.log: "Voice Assistant encountered an error; restarting it"
- switch.turn_off: use_wake_word
- delay: 1s
- switch.turn_on: use_wake_word
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_white
colors:
- state: true
brightness: 50%
red: 100%
green: 100%
blue: 100%
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: GPIO32
- platform: ledc
id: rgb_g
pin: GPIO33
- platform: ledc
id: rgb_b
pin: GPIO25
bme680_bsec:
address: 0x77
id: bme680_sensor
sample_rate: lp
state_save_interval: 1h
iaq_mode: static
ld2410:
id: ld2410_radar
uart_id: ld2410_uart
# These default values are captured here for
# posterity. They are configured below.
# max_move_distance : 6m
# max_still_distance: 0.75m
# g0_move_threshold: 10
# g0_still_threshold: 20
# g1_move_threshold: 10
# g1_still_threshold: 20
# g2_move_threshold: 20
# g2_still_threshold: 21
# g3_move_threshold: 30
# g3_still_threshold: 31
# g4_move_threshold: 40
# g4_still_threshold: 41
# g5_move_threshold: 50
# g5_still_threshold: 51
# g6_move_threshold: 60
# g6_still_threshold: 61
# g7_move_threshold: 70
# g7_still_threshold: 71
# g8_move_threshold: 80
# g8_still_threshold: 81
binary_sensor:
- platform: template
name: "SuperSensor Occupancy"
id: supersensor_occupancy
device_class: occupancy
- 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
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"
sensor:
- platform: bme680_bsec
bme680_bsec_id: bme680_sensor
temperature:
name: "BME680 Temperature"
id: bme680_temperature
pressure:
name: "BME680 Pressure"
id: bme680_pressure
humidity:
name: "BME680 Relative Humidity"
id: bme680_humidity
iaq:
name: "BME680 IAQ"
id: bme680_iaq
co2_equivalent:
name: "BME680 CO2 Equivalent"
breath_voc_equivalent:
name: "BME680 Breath VOC Equivalent"
gas_resistance:
name: "BME680 Gas Resistance"
id: bme680_gas_resistance
internal: yes
- platform: absolute_humidity
name: "BME680 Absolute Humidity"
temperature: bme680_temperature
humidity: bme680_humidity
- 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: Lux
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
text_sensor:
- platform: bme680_bsec
bme680_bsec_id: bme680_sensor
iaq_accuracy:
name: "BME680 IAQ Accuracy"
- platform: template
name: "BME680 IAQ Classification"
icon: "mdi:air-filter"
update_interval: 15s
lambda: |-
int iaq = int(id(bme680_iaq).state);
if (iaq <= 50) {
return {"Excellent (" + to_string(iaq) + ")"};
}
else if (iaq <= 100) {
return {"Good (" + to_string(iaq) + ")"};
}
else if (iaq <= 150) {
return {"Fair (" + to_string(iaq) + ")"};
}
else if (iaq <= 200) {
return {"Moderate (" + to_string(iaq) + ")"};
}
else if (iaq <= 250) {
return {"Bad (" + to_string(iaq) + ")"};
}
else if (iaq <= 350) {
return {"Very Bad (" + to_string(iaq) + ")"};
}
else if (iaq <= 500) {
return {"Terrible (" + to_string(iaq) + ")"};
}
else {
return {"Unknown (" + to_string(iaq) + ")"};
}
- 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"
button:
- platform: ld2410
restart:
name: "LD2410C Restart"
icon: mdi:power-cycle
entity_category: diagnostic
factory_reset:
name: "LD2410C Factory Reset"
entity_category: diagnostic
- 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);
- platform: ld2410
engineering_mode:
name: "LD2410C Engineering Mode"
entity_category: diagnostic
bluetooth:
name: "LD2410C Bluetooth"
entity_category: diagnostic
number:
# 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: yes
restore_value: yes
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: yes
restore_value: yes
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"

View File

@ -1,861 +0,0 @@
---
###############################################################################
# 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"
project:
name: joshuaboniface.supersensor
version: "0.1"
on_boot:
- priority: 600
then:
- lambda: |-
id(supersensor_occupancy).publish_state(false);
id(pir_presence).publish_state(false);
id(light_presence).publish_state(false);
- light.turn_on:
id: output_led
effect: flash_white
dashboard_import:
package_import_url: github://joshuaboniface/supersensor/supersensor.yaml
import_full_config: false
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"
globals:
- id: pir_hold_time
type: int
restore_value: yes
initial_value: "15"
- id: light_presence_threshold
type: int
restore_value: yes
initial_value: "30"
- id: occupancy_detect_mode
type: int
restore_value: yes
initial_value: "0"
- id: occupancy_clear_mode
type: int
restore_value: yes
initial_value: "0"
script:
- 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
if (pir_counts & radar_counts & light_counts) {
// Logical AND of pir & radar & light
ESP_LOGD("occupancy_detect_handler", "PIR & Radar & Light: %i", pir & radar & light);
id(supersensor_occupancy).publish_state(pir & radar & light);
} else if (pir_counts & radar_counts) {
// Logical AND of pir & radar
ESP_LOGD("occupancy_detect_handler", "PIR & Radar: %i", pir & radar);
id(supersensor_occupancy).publish_state(pir & radar);
} else if (pir_counts & light_counts) {
// Logical AND of pir & light
ESP_LOGD("occupancy_detect_handler", "PIR & Light: %i", pir & light);
id(supersensor_occupancy).publish_state(pir & light);
} else if (radar_counts & light_counts) {
// Logical AND of radar & light
ESP_LOGD("occupancy_detect_handler", "Radar & Light: %i", radar & light);
id(supersensor_occupancy).publish_state(radar & light);
} else if (pir_counts) {
// Only pir
ESP_LOGD("occupancy_detect_handler", "PIR: %i", pir);
id(supersensor_occupancy).publish_state(pir);
} else if (radar_counts) {
// Only radar
ESP_LOGD("occupancy_detect_handler", "Radar: %i", radar);
id(supersensor_occupancy).publish_state(radar);
} else if (light_counts) {
// Only light
ESP_LOGD("occupancy_detect_handler", "Light: %i", light);
id(supersensor_occupancy).publish_state(light);
} else {
ESP_LOGD("occupancy_detect_handler", "None");
id(supersensor_occupancy).publish_state(false);
}
- 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
if (pir_counts & radar_counts & light_counts) {
// Logical AND of pir & radar & light
ESP_LOGD("occupancy_clear_handler", "PIR & Radar & Light: %i", pir & radar & light);
id(supersensor_occupancy).publish_state(pir & radar & light);
} else if (pir_counts & radar_counts) {
// Logical AND of pir & radar
ESP_LOGD("occupancy_clear_handler", "PIR & Radar: %i", pir & radar);
id(supersensor_occupancy).publish_state(pir & radar);
} else if (pir_counts & light_counts) {
// Logical AND of pir & light
ESP_LOGD("occupancy_clear_handler", "PIR & Light: %i", pir & light);
id(supersensor_occupancy).publish_state(pir & light);
} else if (radar_counts & light_counts) {
// Logical AND of radar & light
ESP_LOGD("occupancy_clear_handler", "Radar & Light: %i", radar & light);
id(supersensor_occupancy).publish_state(radar & light);
} else if (pir_counts) {
// Only pir
ESP_LOGD("occupancy_clear_handler", "PIR: %i", pir);
id(supersensor_occupancy).publish_state(pir);
} else if (radar_counts) {
// Only radar
ESP_LOGD("occupancy_clear_handler", "Radar: %i", radar);
id(supersensor_occupancy).publish_state(radar);
} else if (light_counts) {
// Only light
ESP_LOGD("occupancy_clear_handler", "Light: %i", light);
id(supersensor_occupancy).publish_state(light);
} else {
ESP_LOGD("occupancy_clear_handler", "None");
id(supersensor_occupancy).publish_state(false);
}
logger:
level: DEBUG
baud_rate: 115200
api:
encryption:
key: !secret api_encryption_key
on_client_disconnected:
- light.turn_on:
id: output_led
effect: flash_white
- switch.turn_off: use_wake_word
on_client_connected:
# This absolute absurdity is required to prevent
# "no wake word detected" failure loops.
- switch.turn_on: use_wake_word
- delay: 2s
- switch.turn_off: use_wake_word
- delay: 2s
- switch.turn_on: use_wake_word
- delay: 2s
- light.turn_off:
id: output_led
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:
id: ld2410_uart
rx_pin: GPIO19
tx_pin: GPIO18
baud_rate: 256000
data_bits: 8
stop_bits: 1
parity: NONE
i2c:
sda: GPIO16
scl: GPIO17
scan: true
i2s_audio:
i2s_lrclk_pin: GPIO26
i2s_bclk_pin: GPIO27
microphone:
- platform: i2s_audio
id: mic
adc_type: external
i2s_din_pin: GPIO14
pdm: false
voice_assistant:
microphone: mic
use_wake_word: false
noise_suppression_level: 2
auto_gain: 31dBFS
volume_multiplier: 4.0
id: assist
on_error:
- light.turn_off:
id: output_led
transition_length: 1s
- logger.log: "Voice Assistant encountered an error; restarting it"
- switch.turn_off: use_wake_word
- delay: 1s
- switch.turn_on: use_wake_word
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_white
colors:
- state: true
brightness: 50%
red: 100%
green: 100%
blue: 100%
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: GPIO32
- platform: ledc
id: rgb_g
pin: GPIO33
- platform: ledc
id: rgb_b
pin: GPIO25
ld2410:
id: ld2410_radar
uart_id: ld2410_uart
# These default values are captured here for
# posterity. They are configured below.
# max_move_distance : 6m
# max_still_distance: 0.75m
# g0_move_threshold: 10
# g0_still_threshold: 20
# g1_move_threshold: 10
# g1_still_threshold: 20
# g2_move_threshold: 20
# g2_still_threshold: 21
# g3_move_threshold: 30
# g3_still_threshold: 31
# g4_move_threshold: 40
# g4_still_threshold: 41
# g5_move_threshold: 50
# g5_still_threshold: 51
# g6_move_threshold: 60
# g6_still_threshold: 61
# g7_move_threshold: 70
# g7_still_threshold: 71
# g8_move_threshold: 80
# g8_still_threshold: 81
binary_sensor:
- platform: template
name: "SuperSensor Occupancy"
id: supersensor_occupancy
device_class: occupancy
- 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
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"
sensor:
- platform: bme680
temperature:
name: "BME680 Temperature"
id: bme680_temperature
pressure:
name: "BME680 Pressure"
id: bme680_pressure
humidity:
name: "BME680 Relative Humidity"
id: bme680_humidity
gas_resistance:
name: "BME680 Gas Resistance"
id: bme680_gas_resistance
address: 0x77
update_interval: 5s
- platform: absolute_humidity
name: "BME680 Absolute Humidity"
temperature: bme680_temperature
humidity: bme680_humidity
- platform: template
name: "BME680 Indoor Air Quality"
id: bme680_iaq
icon: "mdi:gauge"
# caulculation: comp_gas = log(R_gas[ohm]) + 0.04 log(Ohm)/%rh * hum[%rh]
lambda: |-
return float(log(id(bme680_gas_resistance).state) + 0.04 * id(bme680_humidity).state);
- 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: Lux
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
still_distance:
name: "LD2410C Still Distance"
id: still_distance
moving_energy:
name: "LD2410C Move Energy"
still_energy:
name: "LD2410C Still Energy"
detection_distance:
name: "LD2410C Presence Distance"
- 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
text_sensor:
- platform: template
name: "BME680 IAQ Classification"
icon: "mdi:air-filter"
update_interval: 15s
lambda: |-
int iaq = int(id(bme680_iaq).state);
if (iaq <= 50) {
return {"Excellent (" + to_string(iaq) + ")"};
}
else if (iaq <= 100) {
return {"Good (" + to_string(iaq) + ")"};
}
else if (iaq <= 150) {
return {"Fair (" + to_string(iaq) + ")"};
}
else if (iaq <= 200) {
return {"Moderate (" + to_string(iaq) + ")"};
}
else if (iaq <= 250) {
return {"Bad (" + to_string(iaq) + ")"};
}
else if (iaq <= 350) {
return {"Very Bad (" + to_string(iaq) + ")"};
}
else if (iaq <= 500) {
return {"Terrible (" + to_string(iaq) + ")"};
}
else {
return {"Unknown (" + to_string(iaq) + ")"};
}
- 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"
button:
- platform: ld2410
restart:
name: "LD2410C Restart"
icon: mdi:power-cycle
entity_category: diagnostic
factory_reset:
name: "LD2410C Factory Reset"
entity_category: diagnostic
- 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);
- platform: ld2410
engineering_mode:
name: "LD2410C Engineering Mode"
entity_category: diagnostic
bluetooth:
name: "LD2410C Bluetooth"
entity_category: diagnostic
number:
# 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: yes
restore_value: yes
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: yes
restore_value: yes
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"

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -27,6 +27,7 @@ esphome:
project: project:
name: joshuaboniface.supersensor name: joshuaboniface.supersensor
version: "2.0" version: "2.0"
min_version: 2025.5.0
on_boot: on_boot:
- priority: 600 - priority: 600
then: then:
@ -35,32 +36,24 @@ esphome:
id(pir_presence).publish_state(false); id(pir_presence).publish_state(false);
id(light_presence).publish_state(false); id(light_presence).publish_state(false);
id(radar_presence).publish_state(false); id(radar_presence).publish_state(false);
- light.turn_on: - priority: -100
id: output_led
effect: flash_white
- priority: -600
then: then:
- wait_until:
api.connected:
- delay: 5s
- if: - if:
condition: condition:
switch.is_on: enable_voice_support - switch.is_on: enable_voice_support
then: then:
- logger.log: "Initializing voice assistant on boot" - micro_wake_word.start:
- switch.turn_off: voice_support_active
- delay: 2s preferences:
- switch.turn_on: voice_support_active flash_write_interval: 15sec
dashboard_import: dashboard_import:
package_import_url: github://joshuaboniface/supersensor/supersensor-2.x.yaml package_import_url: github://joshuaboniface/supersensor2/supersensor.yaml
esp32: esp32:
board: esp32dev board: esp32dev
framework: framework:
type: esp-idf type: esp-idf
version: 4.4.8
platform_version: 5.4.0
sdkconfig_options: sdkconfig_options:
CONFIG_ESP32_DEFAULT_CPU_FREQ_240: "y" CONFIG_ESP32_DEFAULT_CPU_FREQ_240: "y"
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ: "240" CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ: "240"
@ -74,10 +67,11 @@ esp32:
CONFIG_OPTIMIZATION_LEVEL_RELEASE: "y" CONFIG_OPTIMIZATION_LEVEL_RELEASE: "y"
globals: globals:
# Defaults to -5 due to heating from the ESP
- id: temperature_offset - id: temperature_offset
type: float type: float
restore_value: true restore_value: true
initial_value: "0.0" initial_value: "-5.0"
- id: humidity_offset - id: humidity_offset
type: float type: float
@ -109,6 +103,11 @@ globals:
restore_value: no restore_value: no
initial_value: "0" initial_value: "0"
- id: current_wake_word
type: std::string
restore_value: yes
initial_value: '"mww_computer"'
script: script:
- id: light_off - id: light_off
then: then:
@ -258,28 +257,69 @@ script:
id(supersensor_occupancy).publish_state(new_state); id(supersensor_occupancy).publish_state(new_state);
} }
preferences: interval:
flash_write_interval: 15sec # Regular MWW state check every 30s
- interval: 30s
then:
- if:
condition:
- switch.is_on: enable_voice_support
- not:
micro_wake_word.is_running
then:
- micro_wake_word.start:
# Regular occupancy state reporting to HASS every 30s
- interval: 30s
then:
- lambda: |-
bool current_state = id(supersensor_occupancy).state;
ESP_LOGD("state_reporter", "Republishing occupancy state: %s", current_state ? "true" : "false");
id(supersensor_occupancy).publish_state(current_state);
# API watchdog every 5 minutes
- interval: 300s
then:
- lambda: |-
if (api::global_api_server->is_connected()) {
id(last_api_connected_time) = millis();
} else if (millis() - id(last_api_connected_time) > 300000) {
ESP_LOGE("api_watchdog", "API disconnected for too long, rebooting...");
App.safe_reboot();
}
logger: logger:
level: INFO level: DEBUG
baud_rate: 115200 baud_rate: 115200
api: api:
reboot_timeout: 15min reboot_timeout: 15min
services: # on_client_connected:
- service: restart_voice_assistant # - logger.log:
then: # format: "Client %s (IP %s) connected to API"
- logger.log: "Manually restarting voice assistant" # args: ["client_info.c_str()", "client_address.c_str()"]
- voice_assistant.stop: # - script.execute: light_off
- delay: 2s # - if:
- if: # condition:
condition: # lambda: |-
switch.is_on: enable_voice_support # return id(enable_voice_support).state &&
then: # !id(mww).is_running();
- switch.turn_off: voice_support_active # then:
- delay: 1s # - micro_wake_word.start:
- switch.turn_on: voice_support_active # 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: ota:
platform: esphome platform: esphome
@ -306,6 +346,9 @@ time:
then: then:
- logger.log: "Time synchronized with Home Assistant" - logger.log: "Time synchronized with Home Assistant"
debug:
update_interval: 15s
uart: uart:
id: ld2410_uart id: ld2410_uart
rx_pin: GPIO19 rx_pin: GPIO19
@ -321,100 +364,59 @@ i2c:
scan: true scan: true
i2s_audio: i2s_audio:
i2s_lrclk_pin: GPIO17 # WS - id: i2s_input
i2s_bclk_pin: GPIO16 # SCK i2s_lrclk_pin:
number: GPIO17 # WS
i2s_bclk_pin:
number: GPIO16 # SCK
microphone: microphone:
- platform: i2s_audio - platform: i2s_audio
id: mic id: mic
adc_type: external i2s_audio_id: i2s_input
i2s_din_pin: GPIO4 # SD i2s_din_pin: GPIO4 # SD
adc_type: external
pdm: false pdm: false
channel: left
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();
}
micro_wake_word: micro_wake_word:
model: hey_jarvis id: mww
microphone:
microphone: mic
gain_factor: 8
stop_after_detection: false
models:
- model: github://genehand/Custom_V2_MicroWakeWords/models/computer/computer.json@update-json
id: mww_computer
- model: github://esphome/micro-wake-word-models/models/v2/hey_jarvis.json
id: mww_hey_jarvis
- model: github://esphome/micro-wake-word-models/models/v2/hey_mycroft.json
id: mww_hey_mycroft
- model: github://esphome/micro-wake-word-models/models/v2/okay_nabu.json
id: mww_okay_nabu
- model: github://esphome/micro-wake-word-models/models/v2/alexa.json
id: mww_alexa
vad:
on_wake_word_detected: on_wake_word_detected:
then: - logger.log: "A wake word was detected!"
- voice_assistant.start: - if:
wake_word: !lambda return wake_word; condition:
voice_assistant.is_running:
# Include the Espressif Audio Development Framework for VAD support then:
esp_adf: voice_assistant.stop:
external_components: - voice_assistant.start:
- source: github://pr#5230 wake_word: !lambda return wake_word;
components:
- esp_adf
refresh: 0s
voice_assistant: voice_assistant:
id: va
microphone: mic microphone: mic
micro_wake_word: mww
use_wake_word: false use_wake_word: false
noise_suppression_level: 3 noise_suppression_level: 3
auto_gain: 31dBFS auto_gain: 31 dbfs
volume_multiplier: 8.0 volume_multiplier: 4
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: on_wake_word_detected:
- logger.log: "Wake word detected in VA pipeline"
- light.turn_on: - light.turn_on:
id: output_led id: output_led
brightness: 100% brightness: 100%
@ -422,6 +424,7 @@ voice_assistant:
green: 0 green: 0
blue: 1 blue: 1
on_listening: on_listening:
- logger.log: "Listening for commands"
- light.turn_on: - light.turn_on:
id: output_led id: output_led
brightness: 100% brightness: 100%
@ -429,14 +432,13 @@ voice_assistant:
green: 0 green: 0
blue: 1 blue: 1
on_stt_vad_end: on_stt_vad_end:
- logger.log: "Processing STT result"
- light.turn_on: - light.turn_on:
id: output_led id: output_led
brightness: 75% brightness: 75%
red: 0 red: 0
green: 1 green: 1
blue: 1 blue: 1
on_stt_end:
- script.execute: light_off
on_tts_start: on_tts_start:
- if: - if:
condition: condition:
@ -491,42 +493,139 @@ light:
output: output:
- platform: ledc - platform: ledc
id: rgb_r id: rgb_r
pin: GPIO32 pin: GPIO23
- platform: ledc - platform: ledc
id: rgb_g id: rgb_g
pin: GPIO33 pin: GPIO22
- platform: ledc - platform: ledc
id: rgb_b id: rgb_b
pin: GPIO25 pin: GPIO21
ld2410: ld2410:
id: ld2410_radar id: ld2410_radar
uart_id: ld2410_uart uart_id: ld2410_uart
sensor: sensor:
- platform: sgp30 - platform: uptime
eco2: name: "ESP32 Uptime"
name: "SGP30 eCO2" icon: mdi:clock-alert
id: sgp30_eco2 update_interval: 5s
accuracy_decimals: 1 entity_category: diagnostic
tvoc:
name: "SGP30 TVOC" - platform: wifi_signal
id: sgp30_tvoc name: "WiFi RSSI"
accuracy_decimals: 1 icon: mdi:wifi-strength-2
store_baseline: yes update_interval: 5s
update_interval: 15s entity_category: diagnostic
- platform: internal_temperature
name: "ESP32 Temperature"
icon: mdi:thermometer
unit_of_measurement: °C
device_class: TEMPERATURE
update_interval: 5s
entity_category: diagnostic
- platform: template
name: "ESP32 Free Memory"
icon: mdi:memory
unit_of_measurement: 'kB'
state_class: measurement
update_interval: 5s
lambda: |-
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024;
entity_category: diagnostic
- platform: debug
free:
name: "Heap Free"
block:
name: "Heap Max Block"
loop_time:
name: "Loop Time"
cpu_frequency:
name: "CPU Frequency"
- platform: sgp4x
voc:
name: "SGP41 VOC Index"
id: sgp41_voc_index
accuracy_decimals: 0
icon: mdi:waves-arrow-up
filters:
- sliding_window_moving_average: # We take a reading every 15 seconds, but calculate the sliding
window_size: 12 # average over 12 readings i.e. 60 seconds/1 minute to normalize
send_every: 3 # brief spikes while still sending a value every 15 seconds.
nox:
name: "SGP41 NOx Index"
id: sgp41_nox_index
accuracy_decimals: 0
icon: mdi:waves-arrow-up
filters:
- sliding_window_moving_average:
window_size: 12
send_every: 3
compensation:
temperature_source: sht45_temperature
humidity_source: sht45_humidity
store_baseline: true
update_interval: 5s
- platform: template
name: "SGP41 TVOC (µg/m³)"
id: sgp41_tvoc_ugm3
icon: mdi:molecule
lambda: |-
float i = id(sgp41_voc_index).state;
if (i < 1) return NAN;
float tvoc = (log(501.0 - i) - 6.24) * -878.53;
return tvoc;
unit_of_measurement: "µg/m³"
accuracy_decimals: 0
- platform: template
name: "SGP41 TVOC (ppb)"
id: sgp41_tvoc_ppb
icon: mdi:molecule
lambda: |-
float tvoc_ugm3 = id(sgp41_tvoc_ugm3).state;
float tvoc_ppm = tvoc_ugm3 * 0.436; // ppb estimated using isobutylene MW (56.1 g/mol)
return tvoc_ppm;
unit_of_measurement: "ppb"
accuracy_decimals: 0
- platform: template
name: "SGP41 eCO2 (appr.)"
id: sgp41_eco2_appr
icon: mdi:molecule-co2
lambda: |-
float tvoc_ppb = id(sgp41_tvoc_ppb).state;
float eco2_ppm = 400.0 + 1.5 * tvoc_ppb;
if (eco2_ppm > 2000) eco2_ppm = 2000;
return eco2_ppm;
unit_of_measurement: "ppm"
accuracy_decimals: 0
- platform: sht4x - platform: sht4x
temperature: temperature:
name: "SHT45 Temperature" name: "SHT45 Temperature"
id: sht45_temperature id: sht45_temperature
accuracy_decimals: 1
filters: filters:
- offset: !lambda return id(temperature_offset); - offset: !lambda return id(temperature_offset);
- sliding_window_moving_average:
window_size: 20
send_every: 1
humidity: humidity:
name: "SHT45 Relative Humidity" name: "SHT45 Relative Humidity"
id: sht45_humidity id: sht45_humidity
accuracy_decimals: 1
filters: filters:
- offset: !lambda return id(humidity_offset); - offset: !lambda return id(humidity_offset);
- sliding_window_moving_average:
window_size: 20
send_every: 1
heater_max_duty: 0.0
update_interval: 15s update_interval: 15s
- platform: absolute_humidity - platform: absolute_humidity
@ -535,9 +634,9 @@ sensor:
humidity: sht45_humidity humidity: sht45_humidity
id: sht45_absolute_humidity id: sht45_absolute_humidity
# Dew Point
- platform: template - platform: template
name: "SHT45 Dew Point" name: "SHT45 Dew Point"
icon: mdi:thermometer-water
id: sht45_dew_point id: sht45_dew_point
unit_of_measurement: "°C" unit_of_measurement: "°C"
lambda: |- lambda: |-
@ -549,47 +648,38 @@ sensor:
return (b * alpha) / (a - alpha); return (b * alpha) / (a - alpha);
update_interval: 15s 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 || eco2 > 600) return 4; // Good
return 5; // Excellent
update_interval: 15s
# Room Health Score (1-4, 4=Optimal)
- platform: template - platform: template
name: "Room Health Score" name: "Room Health Score"
id: room_health id: room_health_score
unit_of_measurement: "%"
accuracy_decimals: 0
icon: mdi:home-heart
lambda: |- lambda: |-
float voc_index = id(sgp41_voc_index).state;
float temp = id(sht45_temperature).state; float temp = id(sht45_temperature).state;
float rh = id(sht45_humidity).state; float humidity = id(sht45_humidity).state;
int iaq = id(iaq_index).state;
// VOC Score (0100)
bool temp_ok = (temp >= 18 && temp <= 24); float voc_score = 0;
bool hum_ok = (rh >= 40 && rh <= 60); if (voc_index <= 100) voc_score = 100;
bool iaq_ok = (iaq >= 4); else if (voc_index <= 200) voc_score = 80;
else if (voc_index <= 300) voc_score = 60;
int conditions_met = 0; else if (voc_index <= 400) voc_score = 40;
if (temp_ok) conditions_met++; else if (voc_index <= 500) voc_score = 50;
if (hum_ok) conditions_met++; else voc_score = 0;
if (iaq_ok) conditions_met++;
// Temperature Score (0100)
if (iaq_ok && temp_ok && hum_ok) { float temp_score = 100.0 - abs(temp - 23.0) * 10.0;
return 4; // Optimal: All conditions met and IAQ is excellent/good if (temp_score < 0) temp_score = 0;
} else if (iaq >= 3 && conditions_met >= 2) {
return 3; // Fair: IAQ is moderate and at least 2 conditions met // Humidity Score (0100), ideal range 3555%
} else if (iaq >= 2 && conditions_met >= 1) { float humidity_score = 100.0 - abs(humidity - 50.0) * 3.0;
return 2; // Poor: IAQ is poor and at least 1 condition met if (humidity_score < 0) humidity_score = 0;
} else {
return 1; // Bad: All conditions failed or IAQ is unhealthy // Weighted average
} float overall_score = (voc_score * 0.5 + temp_score * 0.25 + humidity_score * 0.25);
return round(overall_score);
update_interval: 15s update_interval: 15s
- platform: tsl2591 - platform: tsl2591
@ -637,60 +727,21 @@ sensor:
name: "LD2410C Presence Distance" name: "LD2410C Presence Distance"
icon: mdi:signal-distance-variant 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: binary_sensor:
- platform: template - platform: template
name: "SuperSensor Occupancy" name: "SuperSensor Occupancy"
id: supersensor_occupancy id: supersensor_occupancy
device_class: occupancy device_class: occupancy
on_press: on_state:
- script.execute: light_off then:
on_release: - script.execute: light_off
- script.execute: light_off
- platform: gpio - platform: gpio
name: "PIR GPIO" name: "PIR GPIO"
id: pir_gpio id: pir_gpio
pin: GPIO13 pin:
number: GPIO32
mode: INPUT_PULLUP
internal: false internal: false
device_class: motion device_class: motion
on_press: on_press:
@ -731,6 +782,61 @@ binary_sensor:
has_still_target: has_still_target:
name: "LD2410C Still Target" name: "LD2410C Still Target"
text_sensor:
- platform: version
name: "ESPHome Version"
entity_category: diagnostic
- platform: wifi_info
ip_address:
name: "WiFi IP Address"
ssid:
name: "WiFi SSID"
bssid:
name: "WiFi BSSID"
mac_address:
name: "WiFi MAC Address"
- platform: debug
device:
name: "Device Info"
reset_reason:
name: "Reset Reason"
- platform: ld2410
version:
name: "LD2410C Firmware Version"
mac_address:
name: "LD2410C MAC Address"
- platform: template
name: "Chemical Pollution"
id: sgp41_chemical_pollution
icon: mdi:molecule
lambda: |-
float voc_index = id(sgp41_voc_index).state;
if (voc_index < 1 || voc_index > 500) return {"Unknown"};
if (voc_index <= 100) return {"Excellent"};
else if (voc_index <= 200) return {"Good"};
else if (voc_index <= 300) return {"Moderate"};
else if (voc_index <= 400) return {"Unhealthy"};
else return {"Hazardous"};
update_interval: 15s
- platform: template
name: "Room Health"
id: room_health_text
icon: mdi:home-heart
lambda: |-
float score = id(room_health_score).state;
if (score < 0) return {"Unknown"};
else if (score >= 90.0) return {"Great"};
else if (score >= 80.0) return {"Good"};
else if (score >= 60.0) return {"Fair"};
else if (score >= 40.0) return {"Poor"};
else return {"Bad"};
update_interval: 15s
button: button:
- platform: ld2410 - platform: ld2410
restart: restart:
@ -761,26 +867,9 @@ switch:
optimistic: true optimistic: true
restore_mode: RESTORE_DEFAULT_OFF restore_mode: RESTORE_DEFAULT_OFF
on_turn_on: on_turn_on:
- switch.turn_on: voice_support_active - micro_wake_word.start:
on_turn_off: on_turn_off:
- switch.turn_off: voice_support_active - micro_wake_word.stop:
# 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 # Global enable/disable for presence LED
- platform: template - platform: template
@ -804,12 +893,12 @@ switch:
number: number:
# Temperature offset: # Temperature offset:
# A calibration from -7 to +3 for the temperature sensor # A calibration from -30 to +5 for the temperature sensor
- platform: template - platform: template
name: "Temperature Offset" name: "Temperature Offset"
id: temperature_offset_setter id: temperature_offset_setter
min_value: -7 min_value: -30
max_value: 3 max_value: 10
step: 0.1 step: 0.1
lambda: |- lambda: |-
return id(temperature_offset); return id(temperature_offset);
@ -820,12 +909,12 @@ number:
value: !lambda 'return float(x);' value: !lambda 'return float(x);'
# Humidity offset: # Humidity offset:
# A calibration from -10 to +10 for the humidity sensor # A calibration from -20 to +20 for the humidity sensor
- platform: template - platform: template
name: "Humidity Offset" name: "Humidity Offset"
id: humidity_offset_setter id: humidity_offset_setter
min_value: -10 min_value: -20
max_value: 10 max_value: 20
step: 0.1 step: 0.1
lambda: |- lambda: |-
return id(humidity_offset); return id(humidity_offset);
@ -923,7 +1012,6 @@ number:
name: "LD2410C Gate8 Still Threshold" name: "LD2410C Gate8 Still Threshold"
select: select:
# Occupancy Detect Mode: # Occupancy Detect Mode:
# This selector defines the detection mode for the integrated occupancy sensor. Depending on the # 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. # selected option, only the given sensor(s) will be used to judge when occupancy begins (i.e.
@ -1052,49 +1140,101 @@ select:
distance_resolution: distance_resolution:
name: "LD2410C Distance Resolution" name: "LD2410C Distance Resolution"
text_sensor:
# VOC Level
- platform: template - platform: template
name: "VOC Level" name: "Wake Word Selector"
lambda: |- id: wake_word_selector
int tvoc = id(sgp30_tvoc).state; options:
if (tvoc < 65) return {"Excellent"}; - "Computer"
if (tvoc < 220) return {"Good"}; - "Hey Jarvis"
if (tvoc < 660) return {"Moderate"}; - "Hey Mycroft"
if (tvoc < 2200) return {"Poor"}; - "Okay Nabu"
return {"Unhealthy"}; - "Alexa"
update_interval: 15s initial_option: "Computer"
optimistic: true
restore_value: true
set_action:
# Disable models that aren't selected
- if:
condition:
lambda: 'return x != "Computer";'
then:
- micro_wake_word.disable_model: mww_computer
- if:
condition:
lambda: 'return x != "Hey Jarvis";'
then:
- micro_wake_word.disable_model: mww_hey_jarvis
- if:
condition:
lambda: 'return x != "Hey Mycroft";'
then:
- micro_wake_word.disable_model: mww_hey_mycroft
- if:
condition:
lambda: 'return x != "Okay Nabu";'
then:
- micro_wake_word.disable_model: mww_okay_nabu
- if:
condition:
lambda: 'return x != "Alexa";'
then:
- micro_wake_word.disable_model: mww_alexa
# Enable model we selected
- if:
condition:
lambda: 'return x == "Computer";'
then:
- micro_wake_word.enable_model: mww_computer
- if:
condition:
lambda: 'return x == "Hey Jarvis";'
then:
- micro_wake_word.enable_model: mww_hey_jarvis
- if:
condition:
lambda: 'return x == "Hey Mycroft";'
then:
- micro_wake_word.enable_model: mww_hey_mycroft
- if:
condition:
lambda: 'return x == "Okay Nabu";'
then:
- micro_wake_word.enable_model: mww_okay_nabu
- if:
condition:
lambda: 'return x == "Alexa";'
then:
- micro_wake_word.enable_model: mww_alexa
# CO2 Level
- platform: template - platform: template
name: "CO2 Level" name: "Wake Word Sensitivity"
lambda: |- optimistic: true
int eco2 = id(sgp30_eco2).state; initial_option: Default
if (eco2 < 800) return {"Good"}; restore_value: true
if (eco2 < 1200) return {"Moderate"}; options:
if (eco2 < 2000) return {"Poor"}; - Default
return {"Unhealthy"}; - More sensitive
update_interval: 15s - Very sensitive
set_action:
# IAQ Classification # Sets specific wake word probabilities computed for each particular model
- platform: template # Note probability cutoffs are set as a quantized uint8 value, each comment has the corresponding floating point cutoff
name: "IAQ Classification" - lambda: |-
lambda: |- if (x == "Default") {
int iaq = id(iaq_index).state; id(mww_computer).set_probability_cutoff(168); // 0.66 (default)
if (iaq == 5) return {"Excellent"}; id(mww_hey_jarvis).set_probability_cutoff(247); // 0.97 (default)
if (iaq == 4) return {"Good"}; id(mww_hey_mycroft).set_probability_cutoff(242); // 0.95 (default)
if (iaq == 3) return {"Moderate"}; id(mww_okay_nabu).set_probability_cutoff(217); // 0.85 (default)
if (iaq == 2) return {"Poor"}; id(mww_alexa).set_probability_cutoff(217); // 0.85 (default)
return {"Unhealthy"}; } else if (x == "More sensitive") {
update_interval: 15s id(mww_computer).set_probability_cutoff(153); // 0.60
id(mww_hey_jarvis).set_probability_cutoff(235); // 0.92
# Room Health id(mww_hey_mycroft).set_probability_cutoff(237); // 0.93
- platform: template id(mww_okay_nabu).set_probability_cutoff(176); // 0.69
name: "Room Health" id(mww_alexa).set_probability_cutoff(176); // 0.69
lambda: |- } else if (x == "Very sensitive") {
int score = id(room_health).state; id(mww_computer).set_probability_cutoff(138); // 0.54
if (score == 4) return {"Optimal"}; id(mww_hey_jarvis).set_probability_cutoff(212); // 0.83
if (score == 3) return {"Fair"}; id(mww_hey_mycroft).set_probability_cutoff(230); // 0.90
if (score == 2) return {"Poor"}; id(mww_okay_nabu).set_probability_cutoff(143); // 0.56
return {"Bad"}; id(mww_alexa).set_probability_cutoff(143); // 0.56
update_interval: 15s }

View File

@ -1,139 +0,0 @@
---
#
# utemp is a tiny sensor with a BMP280 and an SGP30 on it that I use for basic temperature/AQ monitoring
# in my rack. It's here so I can use it as a package but I don't really expect anyone else to build one.
#
esphome:
name: utemp
name_add_mac_suffix: true
friendly_name: "µtemp Sensor"
project:
name: "Joshua Boniface.µtemp"
version: "1.1"
dashboard_import:
package_import_url: github://joshuaboniface/supersensor/utemp.yaml
esp8266:
board: esp01_1m
restore_from_flash: true
preferences:
flash_write_interval: 15sec
globals:
- id: temperature_offset
type: float
restore_value: true
initial_value: "0.0"
logger:
level: INFO
baud_rate: 0
api:
reboot_timeout: 15min
ota:
platform: esphome
web_server:
port: 80
captive_portal:
mdns:
disabled: false
wifi:
ap: {}
domain: ""
output_power: 8.5dB
reboot_timeout: 15min
power_save_mode: none
i2c:
- id: i2c_bus
sda: GPIO5
scl: GPIO4
scan: true
sensor:
- platform: sgp30
eco2:
name: "eCO2"
accuracy_decimals: 1
tvoc:
name: "TVOC"
accuracy_decimals: 1
store_baseline: yes
address: 0x58
update_interval: 15s
- platform: bmp280_i2c
temperature:
name: "Temperature"
oversampling: 16x
filters:
- offset: !lambda return id(temperature_offset);
pressure:
name: "Pressure"
address: 0x76
update_interval: 15s
iir_filter: 16x
- platform: wifi_signal
name: "WiFi Signal"
update_interval: 60s
entity_category: diagnostic
- platform: uptime
name: "Uptime"
update_interval: 60s
entity_category: diagnostic
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"
button:
- platform: restart
name: "ESP8266 Restart"
icon: mdi:power-cycle
entity_category: diagnostic
- platform: factory_reset
name: "ESP8266 Factory Reset"
icon: mdi:restart-alert
entity_category: diagnostic
number:
# Temperature offset:
# A calibration from -10 to +6 for the temperature sensor of the BMP280
- platform: template
name: "Temperature Offset"
id: temperature_offset_setter
min_value: -10
max_value: 6
step: 0.1
lambda: |-
return id(temperature_offset);
set_action:
then:
- globals.set:
id: temperature_offset
value: !lambda 'return float(x);'