Add customizable room health options and tweak
Permits the user customization of the parameters of the Room Health Score, enabling fine-grained control over this metric instead of hardcoding values that may not be suitable for each person or room.
This commit is contained in:
		
							
								
								
									
										308
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										308
									
								
								README.md
									
									
									
									
									
								
							@@ -118,144 +118,7 @@ functionality, so I recommend using the provided models, but this is up to the b
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
No other parts can be easily swapped without code or PCB design changes.
 | 
					No other parts can be easily swapped without code or PCB design changes.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Configurable Options
 | 
					## Air Quality Handling
 | 
				
			||||||
 | 
					 | 
				
			||||||
There are several UI-configurable options with the SuperSensor to help you
 | 
					 | 
				
			||||||
get the most out of the sensor for your particular use-case.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
**Note:** Configuration of the LD2410C is excluded here, as it is extensively
 | 
					 | 
				
			||||||
configurable. See [the documentation](https://esphome.io/components/sensor/ld2410.html) for more details on its options.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Enable Voice Support (switch)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If enabled (the default), the SuperSensor's voice functionality including
 | 
					 | 
				
			||||||
wake word will be started, or it can be disabled to use the SuperSensor
 | 
					 | 
				
			||||||
purely as a presence/environmental sensor.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Enable Presence LED (switch)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
If enabled (the default), when overall presence is detected, the LEDs will
 | 
					 | 
				
			||||||
glow "white" at 15% power to signal presence.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Temperature Offset (selector, -30 to +10 @ 0.1, -5 default)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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. 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, -20 to +20 @ 0.1)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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
 | 
					 | 
				
			||||||
based on an external reference.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### PIR Hold Time (selector, 0 to +60 @ 5, 0 default)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The SuperSensor uses an AM312 PIR sensor, which has a stock hold time of ~2.5
 | 
					 | 
				
			||||||
seconds. This setting allows increasing that value, with retrigger support, to
 | 
					 | 
				
			||||||
up to 60 seconds, allowing the PIR detection to report for longer. 0 represents
 | 
					 | 
				
			||||||
"as long as the AM312 fires".
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Light Threshold Control (selector, 0 to +200 @ 5, 30 default)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The SuperSensor features a "light presence" binary sensor based on the light
 | 
					 | 
				
			||||||
level reported by the TSL2591 sensor. This control defines the minimum lux
 | 
					 | 
				
			||||||
value from the sensor to be considered "presence". For instance, if you have
 | 
					 | 
				
			||||||
a room that is usually dark at 0-5 lux, but illuminated to 100 lux when a
 | 
					 | 
				
			||||||
(non-automated) light switch is turned on, you could set a threshold here
 | 
					 | 
				
			||||||
of say 30 lux: then, while the light is on, "light presence" is detected,
 | 
					 | 
				
			||||||
and when the light is off, "light presence" is cleared. Light presence can
 | 
					 | 
				
			||||||
be used standalone or as part of the integrated occupancy sensor (below).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### Integrated Occupancy Sensor (Selector)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
The SuperSensor features a fully integrated "occupancy" sensor, which can be
 | 
					 | 
				
			||||||
configured to provide exactly the sort of occupancy detection you may want
 | 
					 | 
				
			||||||
for your room.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
There are 7 options (plus "None"/disabled), with both "detect" and "clear"
 | 
					 | 
				
			||||||
handled separately:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### PIR + Radar + Light
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Occupancy is detected when all 3 sensors report detected, and occupancy is
 | 
					 | 
				
			||||||
cleared when any of the sensors report cleared.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For detect, this provides the most "safety" against misfires, but requires
 | 
					 | 
				
			||||||
a normally-dark room with a non-automated light source and clear PIR
 | 
					 | 
				
			||||||
detection positioning.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For clear, this option is probably not very useful as it is likely to clear
 | 
					 | 
				
			||||||
quite frequently from the PIR, but is provided for completeness.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### PIR + Radar
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Occupancy is detected when both sensors report detected, and occupancy is
 | 
					 | 
				
			||||||
cleared when either of the sensors report cleared.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For detect, this provides good "safety" against PIR misfires without
 | 
					 | 
				
			||||||
needing a normally-dark room, though detection may be slightly delayed
 | 
					 | 
				
			||||||
from either sensor.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For clear, this option is probably not very useful as it is likely to clear
 | 
					 | 
				
			||||||
quite frequently from the PIR, but is provided for completeness.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### PIR + Light
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Occupancy is detected when both sensors report detected, and occupancy is
 | 
					 | 
				
			||||||
cleared when either of the sensors report cleared.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For detect, this provides some "safety" against PIR misfires, but requires
 | 
					 | 
				
			||||||
a normally-dark room with a non-automated light source and clear PIR
 | 
					 | 
				
			||||||
detection positioning.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For clear, this option is probably not very useful as it is likely to clear
 | 
					 | 
				
			||||||
quite frequently from the PIR, but is provided for completeness.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Radar + Light
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Occupancy is detected when both sensors report detected, and occupancy is
 | 
					 | 
				
			||||||
cleared when either of the sensors report cleared.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For detect, this allows for radar detection while suppressing occupancy
 | 
					 | 
				
			||||||
without light, for instance in a hallway where one might not want a late
 | 
					 | 
				
			||||||
night bathroom visit to turn on the lights, or something to that effect.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For clear, this option can provide a useful option to clear presence
 | 
					 | 
				
			||||||
quickly if the lights go out, while still providing Radar presence.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### PIR Only
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Occupancy is based entirely on the PIR sensor for both detect and clear.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Prone to misfires, but otherwise a good option for quick detection and
 | 
					 | 
				
			||||||
clearance in a primarily-moving zone (e.g. hallway).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Radar Only
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Occupancy is based entirely on the Radar sensor for both detect and clear.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Useful for an area with no consistent motion or light level.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### Light Only
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Occupancy is based entirely on the Light sensor for both detect and clear.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Useful for full dependence on an external light source.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#### None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Disable the functionality in either direction.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For detect, no occupancy will ever fire.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
For clear, no states will clear occupancy; with any detect option, this
 | 
					 | 
				
			||||||
means that occupancy will be detected only once and never clear, which
 | 
					 | 
				
			||||||
is likely not useful.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## AQ Details
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
The SuperSensor 2.0 features an SGP41 air quality sensor by Sensirion. This is a powerful AQ
 | 
					The SuperSensor 2.0 features an SGP41 air quality sensor by Sensirion. This is a powerful AQ
 | 
				
			||||||
sensor which powers several commercial devices including the AirGradient One, which gave
 | 
					sensor which powers several commercial devices including the AirGradient One, which gave
 | 
				
			||||||
@@ -283,3 +146,172 @@ It also reacts strongly to heavy humidity, resulting in higher values in such en
 | 
				
			|||||||
These should be used only as a general indication of air quality over short periods, rather
 | 
					These should be used only as a general indication of air quality over short periods, rather
 | 
				
			||||||
than an absolute reference over long periods (much to my own frustration but inevitable
 | 
					than an absolute reference over long periods (much to my own frustration but inevitable
 | 
				
			||||||
begruding acceptance).
 | 
					begruding acceptance).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Room Health
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The SuperSensor 2.0 leverages the outputs of the SHT45 and SGP41 sensors to calculate a
 | 
				
			||||||
 | 
					"Room Health", expressed as a percentage, which represents how "healthy" i.e. comfortable
 | 
				
			||||||
 | 
					a room is for a person to be in.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The room health is calculated based on the VOC level, temperature, and relative humidity.
 | 
				
			||||||
 | 
					First, the raw value is converted to a per-sensor 100-0 scale as follows:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  * For VOC levels, there is a set of linear scales based on common VOC level
 | 
				
			||||||
 | 
					    mappings, such that less than 200 ppb is 100, 200-400 maps to 100-90,
 | 
				
			||||||
 | 
					    400-600 maps to 90-70, 600-1500 maps to 70-40, 1500-3000 maps to 40-0, and
 | 
				
			||||||
 | 
					    greater than 3000 is 0.
 | 
				
			||||||
 | 
					  * For temperature and humidity, there is a single linear scale based on a
 | 
				
			||||||
 | 
					    configurable penalty value, such that a value between the configurable
 | 
				
			||||||
 | 
					    minimum and maximum is 100, and each degree C or %RH outside of that range
 | 
				
			||||||
 | 
					    decreases the value by the penalty value.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Next, each indivdual per-sensor value is applied to the total 100-0 value by a configurable
 | 
				
			||||||
 | 
					weight, with the defaults being 40% to VOC level, 30% to temperature, and 30% to humidity. The
 | 
				
			||||||
 | 
					values can never total more than 100% or total to 0% but are otherwise normalized (i.e. decrease
 | 
				
			||||||
 | 
					others before increasing one, or the values will not be accepted; and at least one weight
 | 
				
			||||||
 | 
					must be >0).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The final result is thus a 100-0% range that, in broad strokes, describes the overall
 | 
				
			||||||
 | 
					health of the room. For some examples, assuming all of the default values below:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   * Perfect: Temp 23C, humidity 50%RH, and VOC level 150ppb = 100% health
 | 
				
			||||||
 | 
					   * A little warm: Temp 25C (+1), humidity 50%RH, and VOC level 250ppb = 97% health
 | 
				
			||||||
 | 
					   * Dry: Temp 22C, humidity 30%RH (-10), VOC level 150ppb = 91% health
 | 
				
			||||||
 | 
					   * Dirty air: Temp 23C, humidity 50%RH, VOC level 800ppb = 85% health
 | 
				
			||||||
 | 
					   * Hot & humid: Temp 28C, humidity 70%RH, VOC level 250ppb = 84% health
 | 
				
			||||||
 | 
					   * All-around bad: Temp 30C, humidity 30%RH, VOC level 2000ppb = 52% health
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					These are then mapped to textual values as well with the following bands:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   * 100%-95%: Great
 | 
				
			||||||
 | 
					   * 95%-90%: Good
 | 
				
			||||||
 | 
					   * 90%-80%: Fair
 | 
				
			||||||
 | 
					   * 80%-60%: Poor
 | 
				
			||||||
 | 
					   * 60%-0%: Bad
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					As mentioned above, most portions of this are configurable; see the section below for
 | 
				
			||||||
 | 
					specific details of each configuration value.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Configurable Options
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					There are several UI-configurable options with the SuperSensor to help you
 | 
				
			||||||
 | 
					get the most out of the sensor for your particular use-case.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Note:** Configuration of the LD2410C is excluded here, as it is extensively
 | 
				
			||||||
 | 
					configurable. See [the documentation](https://esphome.io/components/sensor/ld2410.html) for more details on its options.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Enable Voice Support (switch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If enabled (the default), the SuperSensor's voice functionality including
 | 
				
			||||||
 | 
					wake word will be started, or it can be disabled to use the SuperSensor
 | 
				
			||||||
 | 
					purely as a presence/environmental sensor.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Enable Presence LED (switch)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					If enabled (the default), when overall presence is detected, the LEDs will
 | 
				
			||||||
 | 
					glow "white" at 15% power to signal presence.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Temperature Offset (number, -30 to +10 @ 0.1, -5 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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. 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 (number, -20 to +20 @ 0.1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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
 | 
				
			||||||
 | 
					based on an external reference.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### PIR Hold Time (number, 0 to +60 @ 5, 0 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The SuperSensor uses an AM312 PIR sensor, which has a stock hold time of ~2.5
 | 
				
			||||||
 | 
					seconds. This setting allows increasing that value, with retrigger support, to
 | 
				
			||||||
 | 
					up to 60 seconds, allowing the PIR detection to report for longer. 0 represents
 | 
				
			||||||
 | 
					"as long as the AM312 fires".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Light Threshold Control (number, 0 to +200 @ 5, 30 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The SuperSensor features a "light presence" binary sensor based on the light
 | 
				
			||||||
 | 
					level reported by the TSL2591 sensor. This control defines the minimum lux
 | 
				
			||||||
 | 
					value from the sensor to be considered "presence". For instance, if you have
 | 
				
			||||||
 | 
					a room that is usually dark at 0-5 lux, but illuminated to 100 lux when a
 | 
				
			||||||
 | 
					(non-automated) light switch is turned on, you could set a threshold here
 | 
				
			||||||
 | 
					of say 30 lux: then, while the light is on, "light presence" is detected,
 | 
				
			||||||
 | 
					and when the light is off, "light presence" is cleared. Light presence can
 | 
				
			||||||
 | 
					be used standalone or as part of the integrated occupancy sensor (below).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Integrated Occupancy Sensor (selector)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The SuperSensor features a fully integrated "occupancy" sensor, which can be
 | 
				
			||||||
 | 
					configured to provide exactly the sort of occupancy detection you may want
 | 
				
			||||||
 | 
					for your room.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					There are 7 options (plus "None"/disabled), with both "detect" and "clear"
 | 
				
			||||||
 | 
					handled separately. Occupancy is always detected when ALL of the selected
 | 
				
			||||||
 | 
					sensors report detection, and occupancy is always cleared when ANY of the
 | 
				
			||||||
 | 
					selected sensors stop reporting detection (logical AND in, logical OR out).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   * PIR + Radar + Light
 | 
				
			||||||
 | 
					   * PIR + Radar
 | 
				
			||||||
 | 
					   * PIR + Light
 | 
				
			||||||
 | 
					   * Radar + Light
 | 
				
			||||||
 | 
					   * PIR Only
 | 
				
			||||||
 | 
					   * Radar Only
 | 
				
			||||||
 | 
					   * Light Only
 | 
				
			||||||
 | 
					   * None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Room Health Sensor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Minimum Temperature (number, 15 to 30 @ 0.5, 21 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The lower bounds of a fully comfortable temperature; temperature values below
 | 
				
			||||||
 | 
					this value will begin decreasing the room health score.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Maximum Temperature (number, 15 to 30 @ 0.5, 24 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The upper bounds of a fully comfortable temperature; temperature values above
 | 
				
			||||||
 | 
					this value will begin decreasing the room health score.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Temperature Penalty (number, 1 to 20 @ 1, 10 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The penalty value per degree of temperature deviation from ideal levels, applied
 | 
				
			||||||
 | 
					to the pre-weighting value for temperature.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Minimum Humidity (number, 20 to 80 @ 1, 40 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The lower bounds of a fully comfortable relative humidity level; relative
 | 
				
			||||||
 | 
					humidity values below this value will begin decreasing the room health score.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Maximum Humidity (number, 20 to 80 @ 1, 60 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The upper bounds of a fully comfortable relative humidity level; relative
 | 
				
			||||||
 | 
					humidity values above this value will begin decreasing the room health score.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Humidity Penalty (number, 1 to 10 @ 1, 5 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The penalty value per % of relative humidity deviation from ideal levels, applied
 | 
				
			||||||
 | 
					to the pre-weighting value for humidity.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### VOC Weight (number, 0.0 to 1.0, 0.4 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The weighting value of the VOC score relative to the other two for calculating
 | 
				
			||||||
 | 
					the total room health.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note: Cannot exceed 0.4 without first decreasing one of the other weights (total max of 1.0).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Temperature Weight (number, 0.0 to 1.0, 0.3 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The weighting value of the Temperature score relative to the other two for
 | 
				
			||||||
 | 
					calculating the total room health.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note: Cannot exceed 0.3 without first decreasing one of the other weights (total max of 1.0).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Humidity Weight (number, 0.0 to 1.0, 0.3 default)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The weighting value of the Humidity score relative to the other two for calculating
 | 
				
			||||||
 | 
					the total room health.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note: Cannot exceed 0.3 without first decreasing one of the other weights (total max of 1.0).
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										265
									
								
								supersensor.yaml
									
									
									
									
									
								
							
							
						
						
									
										265
									
								
								supersensor.yaml
									
									
									
									
									
								
							@@ -89,6 +89,51 @@ globals:
 | 
				
			|||||||
    restore_value: true
 | 
					    restore_value: true
 | 
				
			||||||
    initial_value: "0.0"
 | 
					    initial_value: "0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_temperature_min
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "21.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_temperature_max
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "24.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_temperature_penalty
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "10.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_humidity_min
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "40.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_humidity_max
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "60.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_humidity_penalty
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "5.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_voc_weight
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "0.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_temperature_weight
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "0.3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  - id: room_health_humidity_weight
 | 
				
			||||||
 | 
					    type: float
 | 
				
			||||||
 | 
					    restore_value: true
 | 
				
			||||||
 | 
					    initial_value: "0.3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - id: pir_hold_time
 | 
					  - id: pir_hold_time
 | 
				
			||||||
    type: int
 | 
					    type: int
 | 
				
			||||||
    restore_value: true
 | 
					    restore_value: true
 | 
				
			||||||
@@ -666,25 +711,55 @@ sensor:
 | 
				
			|||||||
      float temp = id(sht45_temperature).state;
 | 
					      float temp = id(sht45_temperature).state;
 | 
				
			||||||
      float humidity = id(sht45_humidity).state;
 | 
					      float humidity = id(sht45_humidity).state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // VOC Score (0–100)
 | 
					      float temp_min = id(room_health_temperature_min);
 | 
				
			||||||
      float voc_score = 0;
 | 
					      float temp_max = id(room_health_temperature_max);
 | 
				
			||||||
      if (voc_index <= 100) voc_score = 100;
 | 
					      float temp_penalty = id(room_health_temperature_penalty);
 | 
				
			||||||
      else if (voc_index <= 200) voc_score = 80;
 | 
					      float humid_min = id(room_health_humidity_min);
 | 
				
			||||||
      else if (voc_index <= 300) voc_score = 60;
 | 
					      float humid_max = id(room_health_humidity_max);
 | 
				
			||||||
      else if (voc_index <= 400) voc_score = 40;
 | 
					      float humid_penalty = id(room_health_humidity_penalty);
 | 
				
			||||||
      else if (voc_index <= 500) voc_score = 50;
 | 
					      float voc_weight = id(room_health_voc_weight);
 | 
				
			||||||
      else voc_score = 0;
 | 
					      float temp_weight = id(room_health_temperature_weight);
 | 
				
			||||||
 | 
					      float humid_weight = id(room_health_humidity_weight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Temperature Score (0–100)
 | 
					      // VOC score (0–100) mapped to categories from Chemical Pollution levels below
 | 
				
			||||||
      float temp_score = 100.0 - abs(temp - 23.0) * 10.0;
 | 
					      float voc_score;
 | 
				
			||||||
 | 
					      if (voc_index <= 200) {
 | 
				
			||||||
 | 
					        voc_score = 100.0;
 | 
				
			||||||
 | 
					      } else if (voc_index <= 400) {
 | 
				
			||||||
 | 
					        // 200–400: 100 → 90
 | 
				
			||||||
 | 
					        voc_score = 100.0 - (voc_index - 200) * (10.0 / 200.0);
 | 
				
			||||||
 | 
					      } else if (voc_index <= 600) {
 | 
				
			||||||
 | 
					        // 400–600: 90 → 70
 | 
				
			||||||
 | 
					        voc_score = 90.0 - (voc_index - 400) * (20.0 / 200.0);
 | 
				
			||||||
 | 
					      } else if (voc_index <= 1500) {
 | 
				
			||||||
 | 
					        // 600–1500: 70 → 40
 | 
				
			||||||
 | 
					        voc_score = 70.0 - (voc_index - 600) * (30.0 / 900.0);
 | 
				
			||||||
 | 
					      } else if (voc_index <= 3000) {
 | 
				
			||||||
 | 
					        // 1500–3000: 40 → 0
 | 
				
			||||||
 | 
					        voc_score = 40.0 - (voc_index - 1500) * (40.0 / 1500.0);
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        voc_score = 0.0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Temperature score
 | 
				
			||||||
 | 
					      float temp_score = 100;
 | 
				
			||||||
 | 
					      if (temp < temp_min) temp_score = 100 - (temp_min - temp) * temp_penalty;
 | 
				
			||||||
 | 
					      else if (temp > temp_max) temp_score = 100 - (temp - temp_max) * temp_penalty;
 | 
				
			||||||
      if (temp_score < 0) temp_score = 0;
 | 
					      if (temp_score < 0) temp_score = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Humidity Score (0–100), ideal range 35–55%
 | 
					      // Humidity score
 | 
				
			||||||
      float humidity_score = 100.0 - abs(humidity - 50.0) * 3.0;
 | 
					      float humidity_score = 100;
 | 
				
			||||||
 | 
					      if (humidity < humid_min) humidity_score = 100 - (humid_min - humidity) * humid_penalty;
 | 
				
			||||||
 | 
					      else if (humidity > humid_max) humidity_score = 100 - (humidity - humid_max) * humid_penalty;
 | 
				
			||||||
      if (humidity_score < 0) humidity_score = 0;
 | 
					      if (humidity_score < 0) humidity_score = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Weighted average
 | 
					      // Weighted average
 | 
				
			||||||
      float overall_score = (voc_score * 0.5 + temp_score * 0.25 + humidity_score * 0.25);
 | 
					      float total_weights = voc_weight + temp_weight + humid_weight;
 | 
				
			||||||
 | 
					      if (total_weights <= 0) total_weights = 1.0;
 | 
				
			||||||
 | 
					      voc_weight /= total;
 | 
				
			||||||
 | 
					      temp_weight /= total;
 | 
				
			||||||
 | 
					      humid_weight /= total;
 | 
				
			||||||
 | 
					      float overall_score = (voc_score * voc_weight + temp_score * temp_weight + humidity_score * humid_weight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return (int) round(overall_score);
 | 
					      return (int) round(overall_score);
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
@@ -825,10 +900,10 @@ text_sensor:
 | 
				
			|||||||
    lambda: |-
 | 
					    lambda: |-
 | 
				
			||||||
      float voc_index = id(sgp41_voc_index).state;
 | 
					      float voc_index = id(sgp41_voc_index).state;
 | 
				
			||||||
      if (voc_index < 1 || voc_index > 500) return {"Unknown"};
 | 
					      if (voc_index < 1 || voc_index > 500) return {"Unknown"};
 | 
				
			||||||
      if (voc_index <= 100) return {"Excellent"};
 | 
					      if (voc_index <= 200) return {"Excellent"};
 | 
				
			||||||
      else if (voc_index <= 200) return {"Good"};
 | 
					      else if (voc_index <= 400) return {"Good"};
 | 
				
			||||||
      else if (voc_index <= 300) return {"Moderate"};
 | 
					      else if (voc_index <= 600) return {"Moderate"};
 | 
				
			||||||
      else if (voc_index <= 400) return {"Unhealthy"};
 | 
					      else if (voc_index <= 1500) return {"Unhealthy"};
 | 
				
			||||||
      else return {"Hazardous"};
 | 
					      else return {"Hazardous"};
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -839,10 +914,10 @@ text_sensor:
 | 
				
			|||||||
    lambda: |-
 | 
					    lambda: |-
 | 
				
			||||||
      float score = id(room_health_score).state;
 | 
					      float score = id(room_health_score).state;
 | 
				
			||||||
      if (score < 0) return {"Unknown"};
 | 
					      if (score < 0) return {"Unknown"};
 | 
				
			||||||
      else if (score >= 90.0) return {"Great"};
 | 
					      else if (score >= 95.0) return {"Great"};
 | 
				
			||||||
      else if (score >= 80.0) return {"Good"};
 | 
					      else if (score >= 90.0) return {"Good"};
 | 
				
			||||||
      else if (score >= 60.0) return {"Fair"};
 | 
					      else if (score >= 80.0) return {"Fair"};
 | 
				
			||||||
      else if (score >= 40.0) return {"Poor"};
 | 
					      else if (score >= 60.0) return {"Poor"};
 | 
				
			||||||
      else return {"Bad"};
 | 
					      else return {"Bad"};
 | 
				
			||||||
    update_interval: 15s
 | 
					    update_interval: 15s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -965,6 +1040,154 @@ number:
 | 
				
			|||||||
            id: light_presence_threshold
 | 
					            id: light_presence_threshold
 | 
				
			||||||
            value: !lambda 'return int(x);'
 | 
					            value: !lambda 'return int(x);'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Room Health Calibration Values
 | 
				
			||||||
 | 
					  # These values allow the user to tweak the values of the room health calculation
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health Min Temperature"
 | 
				
			||||||
 | 
					    id: room_health_temperature_min_setter
 | 
				
			||||||
 | 
					    min_value: 15
 | 
				
			||||||
 | 
					    max_value: 30
 | 
				
			||||||
 | 
					    step: 0.5
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return id(room_health_temperature_min);
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      then:
 | 
				
			||||||
 | 
					        - globals.set:
 | 
				
			||||||
 | 
					            id: room_health_temperature_min
 | 
				
			||||||
 | 
					            value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health Max Temperature"
 | 
				
			||||||
 | 
					    id: room_health_temperature_max_setter
 | 
				
			||||||
 | 
					    min_value: 15
 | 
				
			||||||
 | 
					    max_value: 30
 | 
				
			||||||
 | 
					    step: 0.5
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return id(room_health_temperature_max);
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      then:
 | 
				
			||||||
 | 
					        - globals.set:
 | 
				
			||||||
 | 
					            id: room_health_temperature_max
 | 
				
			||||||
 | 
					            value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health Temperature Penalty"
 | 
				
			||||||
 | 
					    id: room_health_temperature_penalty_setter
 | 
				
			||||||
 | 
					    min_value: 1
 | 
				
			||||||
 | 
					    max_value: 20
 | 
				
			||||||
 | 
					    step: 1
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return int(id(room_health_temperature_penalty));
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      then:
 | 
				
			||||||
 | 
					        - globals.set:
 | 
				
			||||||
 | 
					            id: room_health_temperature_penalty
 | 
				
			||||||
 | 
					            value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health Min Humidity"
 | 
				
			||||||
 | 
					    id: room_health_humidity_min_setter
 | 
				
			||||||
 | 
					    min_value: 20
 | 
				
			||||||
 | 
					    max_value: 80
 | 
				
			||||||
 | 
					    step: 1.0
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return id(room_health_humidity_min);
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      then:
 | 
				
			||||||
 | 
					        - globals.set:
 | 
				
			||||||
 | 
					            id: room_health_humidity_min
 | 
				
			||||||
 | 
					            value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health Max Humidity"
 | 
				
			||||||
 | 
					    id: room_health_humidity_max_setter
 | 
				
			||||||
 | 
					    min_value: 20
 | 
				
			||||||
 | 
					    max_value: 80
 | 
				
			||||||
 | 
					    step: 1.0
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return id(room_health_humidity_max);
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      then:
 | 
				
			||||||
 | 
					        - globals.set:
 | 
				
			||||||
 | 
					            id: room_health_humidity_max
 | 
				
			||||||
 | 
					            value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health Humidity Penalty"
 | 
				
			||||||
 | 
					    id: room_health_humidity_penalty_setter
 | 
				
			||||||
 | 
					    min_value: 1
 | 
				
			||||||
 | 
					    max_value: 10
 | 
				
			||||||
 | 
					    step: 1
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return int(id(room_health_humidity_penalty));
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      then:
 | 
				
			||||||
 | 
					        - globals.set:
 | 
				
			||||||
 | 
					            id: room_health_humidity_penalty
 | 
				
			||||||
 | 
					            value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health VOC Weight"
 | 
				
			||||||
 | 
					    id: room_health_voc_weight_setter
 | 
				
			||||||
 | 
					    min_value: 0.00
 | 
				
			||||||
 | 
					    max_value: 1.00
 | 
				
			||||||
 | 
					    step: 0.01
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return id(room_health_voc_weight);
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      - if:
 | 
				
			||||||
 | 
					          condition:
 | 
				
			||||||
 | 
					            lambda: |-
 | 
				
			||||||
 | 
					              float total = x + id(room_health_temperature_weight) + id(room_health_humidity_weight);
 | 
				
			||||||
 | 
					              return (total > 0.0) && (total <= 1.0);
 | 
				
			||||||
 | 
					          then:
 | 
				
			||||||
 | 
					            - globals.set:
 | 
				
			||||||
 | 
					                id: room_health_voc_weight
 | 
				
			||||||
 | 
					                value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					          else:
 | 
				
			||||||
 | 
					            - logger.log:
 | 
				
			||||||
 | 
					                format: "Rejected VOC weight %.2f (total would be out of range: must be > 0.0 and ≤ 1.0)"
 | 
				
			||||||
 | 
					                args: [ 'x' ]
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health Temperature Weight"
 | 
				
			||||||
 | 
					    id: room_health_temperature_weight_setter
 | 
				
			||||||
 | 
					    min_value: 0.00
 | 
				
			||||||
 | 
					    max_value: 1.00
 | 
				
			||||||
 | 
					    step: 0.01
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return id(room_health_temperature_weight);
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      - if:
 | 
				
			||||||
 | 
					          condition:
 | 
				
			||||||
 | 
					            lambda: |-
 | 
				
			||||||
 | 
					              float total = x + id(room_health_voc_weight) + id(room_health_humidity_weight);
 | 
				
			||||||
 | 
					              return (total > 0.0) && (total <= 1.0);
 | 
				
			||||||
 | 
					          then:
 | 
				
			||||||
 | 
					            - globals.set:
 | 
				
			||||||
 | 
					                id: room_health_temperature_weight
 | 
				
			||||||
 | 
					                value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					          else:
 | 
				
			||||||
 | 
					            - logger.log:
 | 
				
			||||||
 | 
					                format: "Rejected Temperature weight %.2f (total would be out of range: must be > 0.0 and ≤ 1.0)"
 | 
				
			||||||
 | 
					                args: [ 'x' ]
 | 
				
			||||||
 | 
					  - platform: template
 | 
				
			||||||
 | 
					    name: "Room Health Humitity Weight"
 | 
				
			||||||
 | 
					    id: room_health_humidity_weight_setter
 | 
				
			||||||
 | 
					    min_value: 0.00
 | 
				
			||||||
 | 
					    max_value: 1.00
 | 
				
			||||||
 | 
					    step: 0.01
 | 
				
			||||||
 | 
					    lambda: |-
 | 
				
			||||||
 | 
					      return id(room_health_humidity_weight);
 | 
				
			||||||
 | 
					    set_action:
 | 
				
			||||||
 | 
					      - if:
 | 
				
			||||||
 | 
					          condition:
 | 
				
			||||||
 | 
					            lambda: |-
 | 
				
			||||||
 | 
					              float total = x + id(room_health_temperature_weight) + id(room_health_voc_weight);
 | 
				
			||||||
 | 
					              return (total > 0.0) && (total <= 1.0);
 | 
				
			||||||
 | 
					          then:
 | 
				
			||||||
 | 
					            - globals.set:
 | 
				
			||||||
 | 
					                id: room_health_humidity_weight
 | 
				
			||||||
 | 
					                value: !lambda 'return float(x);'
 | 
				
			||||||
 | 
					          else:
 | 
				
			||||||
 | 
					            - logger.log:
 | 
				
			||||||
 | 
					                format: "Rejected Humidity weight %.2f (total would be out of range: must be > 0.0 and ≤ 1.0)"
 | 
				
			||||||
 | 
					                args: [ 'x' ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # LD2410c configuration values
 | 
				
			||||||
  - platform: ld2410
 | 
					  - platform: ld2410
 | 
				
			||||||
    timeout:
 | 
					    timeout:
 | 
				
			||||||
      name: "LD2410C Timeout"
 | 
					      name: "LD2410C Timeout"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user