Skip to content

Commit

Permalink
Support for ENS160 digital multi-gas sensor with multiple IAQ data (T…
Browse files Browse the repository at this point in the history
…VOC, eCO2, AQI) (#371)

* ENS160 sensor based on adafruit circultpython
  • Loading branch information
linucks authored Jul 9, 2024
1 parent fef3239 commit 88cff85
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Hardware support is provided by specific GPIO, Sensor and Stream modules. It's e
- BME680 temperature, humidity and pressure sensor (`bme680`)
- DHT11/DHT22/AM2302 temperature and humidity sensors (`dht22`)
- DS18S20/DS1822/DS18B20/DS1825/DS28EA00/MAX31850K temperature sensors (`ds18b`)
- ENS160 digital multi-gas sensor with multiple IAQ data (TVOC, eCO2, AQI) (`ens160`)
- HCSR04 ultrasonic range sensor (connected to the Raspberry Pi on-board GPIO) (`hcsr04`)
- INA219 DC current sensor (`ina219`)
- LM75 temperature sensor (`lm75`)
Expand Down
125 changes: 125 additions & 0 deletions mqtt_io/modules/sensor/ens160.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""
ENS160 Air Quality Sensor
sensor_modules:
- name: ens160
module: ens160
chip_addr: 0x53
temperature_compensation: 25
humidity_compensation: 50
sensor_inputs:
- name: air_quality
module: ens160
interval: 10
digits: 0
type: aqi
- name: volatile_organic_compounds
module: ens160
interval: 10
digits: 0
type: tvoc
- name: eco2
module: ens160
interval: 10
digits: 0
type: eco2
"""

from typing import cast

from ...types import CerberusSchemaType, ConfigType
from . import GenericSensor

DEFAULT_CHIP_ADDR = 0x53
DEFAULT_TEMPERATURE_COMPENSATION = 25
DEFAULT_HUMIDITY_COMPENSATION = 50


REQUIREMENTS = ("adafruit-circuitpython-ens160",)
CONFIG_SCHEMA: CerberusSchemaType = {
"chip_addr": dict(
type="integer", required=False, empty=False, default=DEFAULT_CHIP_ADDR
),
"temperature_compensation": dict(
type="float",
required=False,
empty=False,
default=DEFAULT_TEMPERATURE_COMPENSATION,
),
"humidity_compensation": dict(
type="float",
required=False,
empty=False,
default=DEFAULT_HUMIDITY_COMPENSATION,
),
}


class Sensor(GenericSensor):
"""
Implementation of Sensor class for the ENS160 sensor using adafruit-circuitpython-ens160.
Mesures:
AQI: The air quality index calculated on the basis of UBA
Return value: 1-Excellent, 2-Good, 3-Moderate, 4-Poor, 5-Unhealthy
TVOC: Total Volatile Organic Compounds concentration
Return value range: 0–65000, unit: ppb
CO2 equivalent concentration calculated according to the detected data of VOCs and hydrogen
Return value range: 400–65000, unit: ppm
Five levels: Excellent(400 - 600), Good(600 - 800), Moderate(800 - 1000),
Poor(1000 - 1500), Unhealthy(> 1500)
NB: Need to think about how to handle the ambient_temp and relative_humidity values as
they are currently hard-coded defaults that can be overridden by user configuration.
Ideally these values would be read from a separate temperature/humdity sensor.
"""

SENSOR_SCHEMA: CerberusSchemaType = {
"type": dict(
type="string",
required=False,
empty=False,
default="aqi",
allowed=["aqi", "tvoc", "eco2"],
),
}

def setup_module(self) -> None:
# pylint: disable=import-outside-toplevel,import-error
import adafruit_ens160 # type: ignore
import board # type: ignore

self.adafruit_ens160_module = adafruit_ens160
i2c = board.I2C() # uses board.SCL and board.SDA
self.ens160 = adafruit_ens160.ENS160(i2c, address=self.config["chip_addr"])
self.ens160.temperature_compensation = self.config["temperature_compensation"]
self.ens160.humidity_compensation = self.config["humidity_compensation"]

def get_value(self, sens_conf: ConfigType) -> float:
"""Return the sensor value in the configured type."""

# data_validity values:
# NORMAL_OP - Normal operation,
# WARM_UP - Warm-Up phase, first 3 minutes after power-on.
# START_UP - Initial Start-Up phase, first full hour of operation after initial power-on.
# Only once in the sensor’s lifetime.
# INVALID_OUT - Invalid output
# note: Note that the status will only be stored in the non-volatile memory after an initial
# 24h of continuous operation. If unpowered before conclusion of said period, the
# ENS160 will resume "Initial Start-up" mode after re-powering.
if self.ens160.data_validity == self.adafruit_ens160_module.INVALID_OUT:
raise RuntimeError("ENS160 sensor is returning invalid output")

sens_type = sens_conf["type"]
return cast(
int,
dict(aqi=self.ens160.AQI, tvoc=self.ens160.TVOC, eco2=self.ens160.eCO2)[
sens_type
],
)

0 comments on commit 88cff85

Please sign in to comment.