Skip to content

Commit

Permalink
Add VOC index source (#115)
Browse files Browse the repository at this point in the history
  • Loading branch information
Limych committed Oct 1, 2023
1 parent 20d6881 commit 7b59d66
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 3 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,15 @@ You can create as many groups as you need. But each group must have an unique na
> **tvoc**:\
> _(string) (Optional)_\
> Room tVOC sensor entity ID.\
> Required sensor's unit of measurement: ppm, ppb, mg/m<sup>3</sup> or µg/m<sup>3</sup>
> Required sensor's unit of measurement: ppm, ppb, mg/m<sup>3</sup> or µg/m<sup>3</sup>\
> *Note:* Only one VOC source are allowed at once: **tvoc** or **voc_index**.
>
> **voc_index**:\
> _(string) (Optional)_\
> Room VOC sensor entity ID.\
> Required sensor's unit of measurement: None\
> Especially for SGP40 and SGP41 gas sensors that return a VOC value as an index from 0 to 500.\
> *Note:* Only one VOC source are allowed at once: **tvoc** or **voc_index**.
>
> **hcho**:\
> _(string) (Optional)_\
Expand Down
46 changes: 46 additions & 0 deletions custom_components/iaquk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
CONF_SOURCES,
CONF_TEMPERATURE,
CONF_TVOC,
CONF_VOC_INDEX,
DOMAIN,
LEVEL_EXCELLENT,
LEVEL_FAIR,
Expand All @@ -63,13 +64,26 @@

_LOGGER: Final = logging.getLogger(__name__)


def check_voc_keys(conf):
"""Ensure CONF_TVOC, CONF_VOC_INDEX or none of them are provided."""
keys: Final = [CONF_TVOC, CONF_VOC_INDEX]
count = sum(param in conf for param in keys)
if count > 1:
raise vol.Invalid(
"You must provide none or only one of the following: " ", ".join(keys)
)
return conf


SOURCES: Final = [
CONF_TEMPERATURE,
CONF_HUMIDITY,
CONF_CO2,
CONF_CO,
CONF_NO2,
CONF_TVOC,
CONF_VOC_INDEX,
CONF_HCHO,
CONF_RADON,
CONF_PM,
Expand All @@ -85,6 +99,7 @@
}
),
cv.has_at_least_one_key(*SOURCES),
check_voc_keys,
)

IAQ_SCHEMA: Final = vol.Schema(
Expand Down Expand Up @@ -437,6 +452,37 @@ def _tvoc_index(self) -> Optional[int]:
index = 2
return index

@property
def _voc_index_index(self) -> Optional[int]:
"""Transform indoor VOC index (0-500) values to IAQ points.
Especially for SGP40 and SGP41 gas sensors:
0-50 — Good
51-100 — Moderate
101-150 — Unhealthy for sensitive peoples
151-200 — Unhealthy
201-300 — Very unhealthy
301-500 — Hazardous
"""
entity_id = self._sources.get(CONF_VOC_INDEX)
if entity_id is None:
return None

value = self._get_number_state(entity_id, None, CONF_VOC_INDEX)
if value is None:
return None

index = 1
if value <= 50:
index = 5
elif value <= 115:
index = 4
elif value <= 180:
index = 3
elif value <= 260:
index = 2
return index

@property
def _pm_index(self) -> Optional[int]:
"""Transform indoor particulate matters values to IAQ points."""
Expand Down
3 changes: 2 additions & 1 deletion custom_components/iaquk/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# Base component constants
NAME: Final = "Indoor Air Quality UK Index"
DOMAIN: Final = "iaquk"
VERSION: Final = "1.6.4"
VERSION: Final = "1.6.5-alpha"
ISSUE_URL: Final = "https://github.com/Limych/ha-iaquk/issues"

STARTUP_MESSAGE: Final = f"""
Expand Down Expand Up @@ -39,6 +39,7 @@
CONF_HUMIDITY: Final = "humidity"
CONF_CO2: Final = "co2"
CONF_TVOC: Final = "tvoc"
CONF_VOC_INDEX: Final = "voc_index"
CONF_PM: Final = "pm"
CONF_NO2: Final = "no2"
CONF_CO: Final = "co"
Expand Down
2 changes: 1 addition & 1 deletion custom_components/iaquk/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"colorlog==6.7.0",
"ruff==0.0.291"
],
"version": "1.6.4"
"version": "1.6.5-alpha"
}
40 changes: 40 additions & 0 deletions tests/test__init.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from pytest import raises
from pytest_homeassistant_custom_component.common import assert_setup_component
from voluptuous import Invalid

from custom_components.iaquk import (
ATTR_SOURCE_INDEX_TPL,
Expand All @@ -18,6 +19,7 @@
CONF_SOURCES,
CONF_TEMPERATURE,
CONF_TVOC,
CONF_VOC_INDEX,
DOMAIN,
LEVEL_EXCELLENT,
LEVEL_FAIR,
Expand All @@ -26,6 +28,7 @@
LEVEL_POOR,
Iaquk,
_deslugify,
check_voc_keys,
)
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT,
Expand Down Expand Up @@ -64,6 +67,18 @@ async def async_mock_sensors(hass: HomeAssistant):
await hass.async_block_till_done()


async def test_check_voc_keys():
"""Test check_voc_keys function."""
_ = check_voc_keys({})
_ = check_voc_keys({CONF_TVOC: "qwe"})
_ = check_voc_keys({CONF_VOC_INDEX: "qwe"})
_ = check_voc_keys({"zxc": "qwe", CONF_VOC_INDEX: "asd"})
_ = check_voc_keys({CONF_TVOC: "qwe", "zxc": "asd"})

with raises(Invalid):
_ = check_voc_keys({CONF_TVOC: "qwe", CONF_VOC_INDEX: "asd"})


async def test__deslugify():
"""Test deslugifying entity id."""
assert _deslugify("test") == "Test"
Expand Down Expand Up @@ -315,6 +330,31 @@ async def test__tvoc_index(hass: HomeAssistant):
assert controller._tvoc_index == i + 1


async def test__voc_index_index(hass: HomeAssistant):
"""Test transform indoor VOC index values to IAQ points."""
await async_mock_sensors(hass)

entity_id = "sensor.test_monitored"

controller = Iaquk(hass, "test", "Test", {CONF_TEMPERATURE: entity_id})

assert controller._voc_index_index is None

controller = Iaquk(hass, "test", "Test", {CONF_VOC_INDEX: "sensor.nonexistent"})

assert controller._voc_index_index is None

controller = Iaquk(hass, "test", "Test", {CONF_VOC_INDEX: entity_id})

for i, value in enumerate([261, 181, 116, 51, 0]):
hass.states.async_set(entity_id, value)
assert controller._voc_index_index == i + 1

for i, value in enumerate([500, 260, 180, 115, 50]):
hass.states.async_set(entity_id, value)
assert controller._voc_index_index == i + 1


async def test__pm_index(hass: HomeAssistant):
"""Test transform indoor particulate matters values to IAQ points."""
await async_mock_sensors(hass)
Expand Down

0 comments on commit 7b59d66

Please sign in to comment.