From 1bdeb48a7e5f3cc3dea44645c1468ac954be59a6 Mon Sep 17 00:00:00 2001 From: mschlenstedt Date: Sun, 18 Aug 2024 22:05:02 +0200 Subject: [PATCH 1/3] Add tsl2561 light sensor (#393) * Add tsl2561 light sensor --- README.md | 5 +- mqtt_io/modules/sensor/tsl2561.py | 107 ++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 mqtt_io/modules/sensor/tsl2561.py diff --git a/README.md b/README.md index 4e67401d..d8497fb5 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ Hardware support is provided by specific GPIO, Sensor and Stream modules. It's e - 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`) + - FREQUENCYCOUNTER Counts pulses from GPIOs and return the frequency in Hz (`frequencycounterr`) + - FLOWSENSOR generic flow rate sensor like YF-S201, YF-DN50 or others (`flowsensor`) - HCSR04 ultrasonic range sensor (connected to the Raspberry Pi on-board GPIO) (`hcsr04`) - INA219 DC current sensor (`ina219`) - LM75 temperature sensor (`lm75`) @@ -45,9 +47,8 @@ Hardware support is provided by specific GPIO, Sensor and Stream modules. It's e - ADXl345 3-axis accelerometer up to ±16g (`adxl345`) - PMS5003 particulate sensor (`pms5003`) - SHT40/SHT41/SHT45 temperature and humidity sensors (`sht4x`) + - TLSl2561 light level sensor (`tsl2561`) - YF-S201 flow rate sensor (`yfs201`) - - FREQUENCYCOUNTER Counts pulses from GPIOs and return the frequency in Hz (frequencycounter) - - FLOWSENSOR generic flow rate sensor like YF-S201 or YF-DN50 (`flowsensor`) ### Streams diff --git a/mqtt_io/modules/sensor/tsl2561.py b/mqtt_io/modules/sensor/tsl2561.py new file mode 100644 index 00000000..dd13c387 --- /dev/null +++ b/mqtt_io/modules/sensor/tsl2561.py @@ -0,0 +1,107 @@ +""" +TSL2561 luminosity sensor +""" +from typing import cast + +from ...types import CerberusSchemaType, ConfigType, SensorValueType +from . import GenericSensor + +REQUIREMENTS = ("adafruit-circuitpython-tsl2561",) +CONFIG_SCHEMA: CerberusSchemaType = { + "chip_addr": { + "type": 'integer', + "required": False, + "empty": False, + "default": '0x48'}, + "integration_time": { + "required": False, + "empty": False, + "allowed": [13.7, 101, 402], + "default": 101, + }, + "gain": { + "required": False, + "empty": False, + "allowed": [1, 16], + "default": 1, + }, +} + + +class Sensor(GenericSensor): + """ + Implementation of Sensor class for the Adafruit_ADS1x15. + """ + + SENSOR_SCHEMA: CerberusSchemaType = { + "type": { + "type": 'string', + "required": False, + "empty": False, + "allowed": ['broadband', 'infrared', 'lux'], + "default": 'lux', + }, + } + + def setup_module(self) -> None: + # pylint: disable=import-outside-toplevel,attribute-defined-outside-init + # pylint: disable=import-error,no-member + import board # type: ignore + import busio # type: ignore + import adafruit_tsl2561 # type: ignore + # Create the I2C bus + self.i2c = busio.I2C(board.SCL, board.SDA) + + # Convert sensor address from hex to dec + self.address = int(0x48) + if 'chip_addr' in self.config: + self.address = int(self.config['chip_addr']) + + self.tsl = adafruit_tsl2561.TSL2561(self.i2c, self.address) + + # Set gain 0=1x, 1=16x + gains = { + "1": 0, + "16": 1, + } + if 'gain' in self.config: + self.tsl.gain = gains[str(self.config["gain"])] + + # Set integration time (0=13.7ms, 1=101ms, 2=402ms, or 3=manual) + ints = { + "13.7": 0, + "101": 1, + "402": 2, + } + if 'integration_time' in self.config: + self.tsl.integration_time = ints[str(self.config["integration_time"])] + + self.tsl.enabled = True + + #print("tsl2561 Enabled = {}".format(self.tsl.enabled)) + #print("tsl2561 Gain = {}".format(self.tsl.gain)) + #print("tsl2561 Integration time = {}".format(self.tsl.integration_time)) + + def get_value(self, sens_conf: ConfigType) -> SensorValueType: + # pylint: disable=import-outside-toplevel,attribute-defined-outside-init + # pylint: disable=import-error,no-member + import time + self.tsl.enabled = True + time.sleep(1) + + sens_type = sens_conf["type"] + data = { + "broadband": self.tsl.broadband, + "infrared": self.tsl.infrared, + "lux": self.tsl.lux + } + + self.tsl.enabled = False + + if data[sens_type] is None: # Possible sensor underrange or overrange. + data[sens_type] = -1 + + return cast( + float, + data[sens_type], + ) From 19cfb173a84026369879417de0c661892c4cbaee Mon Sep 17 00:00:00 2001 From: Guy Martin Date: Sun, 18 Aug 2024 22:06:57 +0200 Subject: [PATCH 2/3] Add support for veml6075 sensors. (#376) * Add support for veml6075 sensors. --- mqtt_io/modules/sensor/veml6075.py | 90 ++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 mqtt_io/modules/sensor/veml6075.py diff --git a/mqtt_io/modules/sensor/veml6075.py b/mqtt_io/modules/sensor/veml6075.py new file mode 100644 index 00000000..14817d4f --- /dev/null +++ b/mqtt_io/modules/sensor/veml6075.py @@ -0,0 +1,90 @@ +""" +VEML 6075 UV sensor +""" + +import logging +from mqtt_io.types import ConfigType, SensorValueType +from . import GenericSensor + +REQUIREMENTS = ("smbus2", "veml6075",) + +CONFIG_SCHEMA = { + "i2c_bus_num": {"type": "integer", "required": True, "empty": False}, +} + + +# UV COEFFICIENTS AND RESPONSIVITY +# More details here : +# https://web.archive.org/web/20190416120825/http://www.vishay.com/docs/84339/designingveml6075.pdf +# For more details +################################################################################## +# Configuration # a # b # c # d # UVAresp # UVBresp # +################################################################################## +# No teflon (open air) # 2.22 # 1.33 # 2.95 # 1.74 # 0.001461 # 0.002591 # +# 0.1 mm teflon 4.5 mm window # 2.22 # 1.33 # 2.95 # 1.74 # 0.002303 # 0.004686 # +# 0.1 mm teflon 5.5 mm window # 2.22 # 1.33 # 2.95 # 1.74 # 0.002216 # 0.005188 # +# 0.1 mm teflon 10 mm window # 2.22 # 1.33 # 2.95 # 1.74 # 0.002681 # 0.004875 # +# 0.25 mm teflon 10 mm window # 2.22 # 1.33 # 2.95 # 1.74 # 0.002919 # 0.009389 # +# 0.4 mm teflon 10 mm window # 2.22 # 1.17 # 2.95 # 1.58 # 0.004770 # 0.006135 # +# 0.7 mm teflon 10 mm window # 2.22 # 1.17 # 2.95 # 1.58 # 0.007923 # 0.008334 # +# 1.0 mm teflon 5.5 mm window # 2.55 # 1.00 # 3.80 # 1.10 # 0.006000 # 0.003100 # +################################################################################## + +_LOG = logging.getLogger(__name__) + + +class Sensor(GenericSensor): + """ + Implementation of Sensor class for the VEML 6075 UV sensor. + """ + + SENSOR_SCHEMA = { + "a": {"type": "float", "required": False, "empty": False, "default": 2.22}, + "b": {"type": "float", "required": False, "empty": False, "default": 1.33}, + "c": {"type": "float", "required": False, "empty": False, "default": 2.95}, + "d": {"type": "float", "required": False, "empty": False, "default": 1.74}, + "UVAresp": {"type": "float", "required": False, "empty": False, "default": 0.001461}, + "UVBresp": {"type": "float", "required": False, "empty": False, "default": 0.002591}, + } + + def setup_module(self) -> None: + # pylint: disable=import-outside-toplevel,import-error + from smbus2 import SMBus # type: ignore + from veml6075 import VEML6075 # type: ignore + + self.bus = SMBus(self.config["i2c_bus_num"]) + self.sensor = VEML6075(i2c_dev=self.bus) + self.sensor.set_shutdown(True) + self.sensor.set_high_dynamic_range(False) + self.sensor.set_integration_time('100ms') + self.sensor.set_shutdown(False) + + + def calculate_uv_index(self, sens_conf: ConfigType, \ + uva: float, uvb: float, uv_comp1: float, uv_comp2: float) -> float: + + """ + Calculate the UV index from received values. + """ + + _LOG.debug("UVA: %f UVB: %f UV_comp1: %f UV_comp2: %f)", uva, uvb, uv_comp1, uv_comp2) + uva_calc = uva - (sens_conf["a"] * uv_comp1) - (sens_conf["b"] * uv_comp2) + uvb_calc = uvb - (sens_conf["c"] * uv_comp1) - (sens_conf["d"] * uv_comp2) + _LOG.debug("uva_calc: %f uvb_calc: %f", uva_calc, uvb_calc) + uva_index = uva_calc * sens_conf["UVAresp"] + uvb_index = uvb_calc * sens_conf["UVBresp"] + _LOG.debug("uva_index: %f uvb_index: %f", uva_index, uvb_index) + uv_index: float = (uva_index + uvb_index) / 2.0 + return uv_index + + def get_value(self, sens_conf: ConfigType) -> SensorValueType: + """ + Get the UV index from the sensor + """ + + # Fetch the values + uva, uvb = self.sensor.get_measurements() + uv_comp1, uv_comp2 = self.sensor.get_comparitor_readings() + + # Calculate and return the UV index + return self.calculate_uv_index(sens_conf, uva, uvb, uv_comp1, uv_comp2) From 57b1b21f1553d2baa6a5e1a36b02286a2896e9be Mon Sep 17 00:00:00 2001 From: Jens Thomas Date: Mon, 2 Sep 2024 22:22:57 +0100 Subject: [PATCH 3/3] Remove yfs201 sensor (#396) Also fix incorrect example config for flowsensor --- mqtt_io/modules/sensor/flowsensor.py | 18 +++--- mqtt_io/modules/sensor/yfs201.py | 94 ---------------------------- 2 files changed, 10 insertions(+), 102 deletions(-) delete mode 100644 mqtt_io/modules/sensor/yfs201.py diff --git a/mqtt_io/modules/sensor/flowsensor.py b/mqtt_io/modules/sensor/flowsensor.py index b0eaf2b6..392d0e42 100644 --- a/mqtt_io/modules/sensor/flowsensor.py +++ b/mqtt_io/modules/sensor/flowsensor.py @@ -10,7 +10,7 @@ sensor_inputs: - name: flow_rate1 - module: flowsensor + module: yfs201 pin: 0 digits: 0 interval: 10 @@ -45,7 +45,7 @@ class FLOWSENSOR: Multiple instances support multiple sensors on different pins """ - def __init__(self, gpiozero, name: str, pin: int) -> None: # type: ignore[no-untyped-def] + def __init__(self, gpiozero, name: str, pin: int) -> None: # type: ignore[no-untyped-def] self.name = name self.pin = gpiozero.DigitalInputDevice(pin) self.pin.when_activated = self.count_pulse @@ -75,7 +75,7 @@ def flow_rate(self, sample_window: int, factor: float) -> float: def get_value(self, interval: int, factor: float) -> float: """Return flow rate in L/min over interval seconds and reset count.""" - flow_rate = self.flow_rate(interval,factor) + flow_rate = self.flow_rate(interval, factor) self.reset_count() return flow_rate @@ -87,20 +87,20 @@ class Sensor(GenericSensor): SENSOR_SCHEMA: CerberusSchemaType = { "pin": { - "type": 'integer', + "type": "integer", "required": True, "empty": False, }, "interval": { - "type": 'integer', + "type": "integer", "required": True, "empty": False, }, "factor": { - "type": 'float', + "type": "float", "required": True, "empty": False, - } + }, } def setup_module(self) -> None: @@ -117,4 +117,6 @@ def setup_sensor(self, sens_conf: ConfigType) -> None: self.sensors[sensor.name] = sensor def get_value(self, sens_conf: ConfigType) -> SensorValueType: - return self.sensors[sens_conf["name"]].get_value(sens_conf["interval"],sens_conf["factor"]) + return self.sensors[sens_conf["name"]].get_value( + sens_conf["interval"], sens_conf["factor"] + ) diff --git a/mqtt_io/modules/sensor/yfs201.py b/mqtt_io/modules/sensor/yfs201.py deleted file mode 100644 index 39534c66..00000000 --- a/mqtt_io/modules/sensor/yfs201.py +++ /dev/null @@ -1,94 +0,0 @@ -""" - -YF-S201 Flow Rate Sensor - -Example configuration: - -sensor_modules: - - name: yfs201 - module: yfs201 - -sensor_inputs: - - name: flow_rate1 - module: yfs201 - pin: 0 - digits: 0 - interval: 10 -""" - -from typing import Dict -from ...types import CerberusSchemaType, ConfigType, SensorValueType -from . import GenericSensor - -REQUIREMENTS = ("gpiozero",) - - -class YFS201: - """ - YF-S201 Flow Rate Sensor class - Multiple instances support multiple sensors on different pins - """ - - def __init__(self, gpiozero, name: str, pin: int) -> None: # type: ignore[no-untyped-def] - self.name = name - self.pin = gpiozero.DigitalInputDevice(pin) - self.pin.when_activated = self.count_pulse - self.count = 0 - - def count_pulse(self) -> None: - """Increment pulse count.""" - self.count += 1 - - def reset_count(self) -> None: - """Reset pulse count.""" - self.count = 0 - - def flow_rate(self, sample_window: int) -> float: - """Return flow rate in liters per minute. - - From YF-S201 manual: - Pluse Characteristic:F=7Q(L/MIN). - 2L/MIN=16HZ 4L/MIN=32.5HZ 6L/MIN=49.3HZ 8L/MIN=65.5HZ 10L/MIN=82HZ - - Pulse frequency (Hz) / 7.0 = flow rate in L/min - - sample_window is in seconds, so hz is pulse_count / sample_window - """ - hertz = self.count / sample_window - return hertz / 7.0 - - def get_value(self, interval: int) -> float: - """Return flow rate in L/min over interval seconds and reset count.""" - flow_rate = self.flow_rate(interval) - self.reset_count() - return flow_rate - - -class Sensor(GenericSensor): - """ - YF-S201 Flow Rate Sensor - """ - - SENSOR_SCHEMA: CerberusSchemaType = { - "pin": { - "type": 'integer', - "required": True, - "empty": False, - } - } - - def setup_module(self) -> None: - # pylint: disable=import-outside-toplevel,import-error - import gpiozero # type: ignore - - self.gpiozero = gpiozero - self.sensors: Dict[str, YFS201] = {} - - def setup_sensor(self, sens_conf: ConfigType) -> None: - sensor = YFS201( - gpiozero=self.gpiozero, name=sens_conf["name"], pin=sens_conf["pin"] - ) - self.sensors[sensor.name] = sensor - - def get_value(self, sens_conf: ConfigType) -> SensorValueType: - return self.sensors[sens_conf["name"]].get_value(sens_conf["interval"])