From 707bd667f70bd6c5fd9e6254bea447b1473717ba Mon Sep 17 00:00:00 2001 From: lichtteil Date: Thu, 11 Feb 2021 14:38:55 +0100 Subject: [PATCH 01/17] Update manifest.json --- custom_components/local_luftdaten/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/local_luftdaten/manifest.json b/custom_components/local_luftdaten/manifest.json index 653c2d8..5f358c7 100644 --- a/custom_components/local_luftdaten/manifest.json +++ b/custom_components/local_luftdaten/manifest.json @@ -6,5 +6,5 @@ "dependencies": [], "codeowners": ["@lichtteil"], "requirements": [], - "version": "1.7" + "version": "1.8" } From 1ffc606df161c3134204593502e8f819df794a82 Mon Sep 17 00:00:00 2001 From: lichtteil Date: Thu, 1 Apr 2021 18:43:35 +0200 Subject: [PATCH 02/17] Add support for Honeywell sensor (HPM) --- README.md | 2 ++ custom_components/local_luftdaten/sensor.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 181a463..82478eb 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ At the moment following sensor data can be read: - PMS_P2 - HECA_temperature - HECA_humidity +- HPM_P1 +- HPM_P2 Sensor type `signal` gives the wifi signal strength of the sensor device. diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index 2f3382c..64f03c0 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -58,6 +58,8 @@ SENSOR_PMS_P2 = 'PMS_P2' SENSOR_HECA_TEMPERATURE = 'HECA_temperature' SENSOR_HECA_HUMIDITY = 'HECA_humidity' +SENSOR_HPM_P1 = 'HPM_P1' +SENSOR_HPM_P2 = 'HPM_P2' SENSOR_TYPES = { @@ -84,6 +86,8 @@ SENSOR_PMS_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], SENSOR_HECA_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], SENSOR_HECA_HUMIDITY: ['Humidity', '%', 'humidity'], + SENSOR_HPM_PM1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_HPM_PM2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], } DEFAULT_NAME = 'Luftdaten Sensor' From eb7f348a1b2dc2d3dd54a79e1bd3fea51e5fd21d Mon Sep 17 00:00:00 2001 From: lichtteil Date: Thu, 1 Apr 2021 18:43:53 +0200 Subject: [PATCH 03/17] Bump version to 1.9 --- custom_components/local_luftdaten/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/local_luftdaten/manifest.json b/custom_components/local_luftdaten/manifest.json index 5f358c7..4d85ee2 100644 --- a/custom_components/local_luftdaten/manifest.json +++ b/custom_components/local_luftdaten/manifest.json @@ -6,5 +6,5 @@ "dependencies": [], "codeowners": ["@lichtteil"], "requirements": [], - "version": "1.8" + "version": "1.9" } From 9c75a94df6a6b64ffb980231b3c49ab15b464b75 Mon Sep 17 00:00:00 2001 From: lichtteil Date: Thu, 1 Apr 2021 19:44:17 +0200 Subject: [PATCH 04/17] Fix sensor name --- custom_components/local_luftdaten/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index 64f03c0..08ca1eb 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -86,8 +86,8 @@ SENSOR_PMS_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], SENSOR_HECA_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], SENSOR_HECA_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_HPM_PM1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_HPM_PM2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_HPM_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_HPM_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], } DEFAULT_NAME = 'Luftdaten Sensor' From 74d55a67ebb4c3e2852acb08167792ce6d5f5b07 Mon Sep 17 00:00:00 2001 From: Joachim Engelmann Date: Mon, 19 Apr 2021 17:42:04 +0200 Subject: [PATCH 05/17] Add customizable scan_interval The default scan_interval will stay at 3 minutes (180 seconds). In addition it is possible to define a custom scan_interval in the sensor entity definition (configuration.yaml). --- README.md | 9 +++++++++ custom_components/local_luftdaten/sensor.py | 15 +++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 82478eb..d4ab5f3 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,19 @@ In the end your file structure should look like that: ## Configuration Create a new sensor entry in your `configuration.yaml` and adjust the host name or the ip address. +|Parameter |Type | Necessity | Description +|:----------------------|:-------|:------------ |:------------ +|`host` | number | required | The IP-Address of the sensor +|`scan_interval` | number | default: 180 | The frequency (in seconds) between data updates. +|`name` | string | required | Name of the sensor +|`monitored_conditions` | list | required | List of the monitored sensors + + ```yaml sensor: - platform: local_luftdaten host: 192.168.0.123 + scan_interval: 180 name: Feinstaubsensor monitored_conditions: - SDS_P1 diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index 08ca1eb..286f9a4 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -22,8 +22,13 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, CONF_RESOURCE, CONF_VERIFY_SSL, CONF_MONITORED_CONDITIONS, - TEMP_CELSIUS) + CONF_NAME, + CONF_RESOURCE, + CONF_VERIFY_SSL, + CONF_MONITORED_CONDITIONS, + CONF_SCAN_INTERVAL, + TEMP_CELSIUS +) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv @@ -93,10 +98,11 @@ DEFAULT_NAME = 'Luftdaten Sensor' DEFAULT_RESOURCE = 'http://{}/data.json' DEFAULT_VERIFY_SSL = True +DEFAULT_SCAN_INTERVAL = timedelta(minutes=3) CONF_HOST = 'host' -SCAN_INTERVAL = timedelta(minutes=3) + PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_HOST): cv.string, @@ -104,7 +110,8 @@ vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_RESOURCE, default=DEFAULT_RESOURCE): cv.string, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): cv.time_period }) From 040f56da3460703af926c432e205651b06c22871 Mon Sep 17 00:00:00 2001 From: Joachim Engelmann Date: Mon, 19 Apr 2021 18:11:38 +0200 Subject: [PATCH 06/17] Outsource const variables to const.py - All constants are defined in const.py - Use CONF_HOST from homeassistant.const --- custom_components/local_luftdaten/const.py | 76 ++++++++++++++++++++ custom_components/local_luftdaten/sensor.py | 79 +-------------------- 2 files changed, 78 insertions(+), 77 deletions(-) create mode 100644 custom_components/local_luftdaten/const.py diff --git a/custom_components/local_luftdaten/const.py b/custom_components/local_luftdaten/const.py new file mode 100644 index 0000000..53853ce --- /dev/null +++ b/custom_components/local_luftdaten/const.py @@ -0,0 +1,76 @@ +from datetime import timedelta + +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_RESOURCE, + CONF_VERIFY_SSL, + CONF_MONITORED_CONDITIONS, + CONF_SCAN_INTERVAL, + TEMP_CELSIUS +) + +DOMAIN = "local_luftdaten" + +DEFAULT_NAME = 'Luftdaten Sensor' +DEFAULT_RESOURCE = 'http://{}/data.json' +DEFAULT_VERIFY_SSL = True +DEFAULT_SCAN_INTERVAL = timedelta(minutes=3) + +#Units of measurement +VOLUME_MICROGRAMS_PER_CUBIC_METER = 'µg/m3' + +#Sensors +SENSOR_TEMPERATURE = 'temperature' +SENSOR_HUMIDITY = 'humidity' +SENSOR_BME280_TEMPERATURE = 'BME280_temperature' +SENSOR_BME280_HUMIDITY = 'BME280_humidity' +SENSOR_BME280_PRESSURE = 'BME280_pressure' +SENSOR_BMP_TEMPERATURE = 'BMP_temperature' +SENSOR_BMP_PRESSURE = 'BMP_pressure' +SENSOR_BMP280_TEMPERATURE = 'BMP280_temperature' +SENSOR_BMP280_PRESSURE = 'BMP280_pressure' +SENSOR_PM1 = 'SDS_P1' +SENSOR_PM2 = 'SDS_P2' +SENSOR_WIFI_SIGNAL = 'signal' +SENSOR_HTU21D_TEMPERATURE = 'HTU21D_temperature' +SENSOR_HTU21D_HUMIDITY = 'HTU21D_humidity' +SENSOR_SPS30_P0 = 'SPS30_P0' +SENSOR_SPS30_P2 = 'SPS30_P2' +SENSOR_SPS30_P4 = 'SPS30_P4' +SENSOR_SPS30_P1 = 'SPS30_P1' +SENSOR_PMS_P0 = 'PMS_P0' +SENSOR_PMS_P1 = 'PMS_P1' +SENSOR_PMS_P2 = 'PMS_P2' +SENSOR_HECA_TEMPERATURE = 'HECA_temperature' +SENSOR_HECA_HUMIDITY = 'HECA_humidity' +SENSOR_HPM_P1 = 'HPM_P1' +SENSOR_HPM_P2 = 'HPM_P2' + +SENSOR_TYPES = { + SENSOR_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], + SENSOR_HUMIDITY: ['Humidity', '%', 'humidity'], + SENSOR_BME280_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], + SENSOR_BME280_HUMIDITY: ['Humidity', '%', 'humidity'], + SENSOR_BME280_PRESSURE: ['Pressure', 'Pa', 'pressure'], + SENSOR_BMP_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], + SENSOR_BMP_PRESSURE: ['Pressure', 'Pa', 'pressure'], + SENSOR_BMP280_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], + SENSOR_BMP280_PRESSURE: ['Pressure', 'Pa', 'pressure'], + SENSOR_PM1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_PM2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_WIFI_SIGNAL: ['Wifi signal', 'dBm', 'signal_strength'], + SENSOR_HTU21D_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], + SENSOR_HTU21D_HUMIDITY: ['Humidity', '%', 'humidity'], + SENSOR_SPS30_P0: ['PM1', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_SPS30_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_SPS30_P4: ['PM4', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_SPS30_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_PMS_P0: ['PM1', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_PMS_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_PMS_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_HECA_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], + SENSOR_HECA_HUMIDITY: ['Humidity', '%', 'humidity'], + SENSOR_HPM_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_HPM_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], +} diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index 286f9a4..c53941d 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -15,93 +15,18 @@ import json -from datetime import timedelta - import requests import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_NAME, - CONF_RESOURCE, - CONF_VERIFY_SSL, - CONF_MONITORED_CONDITIONS, - CONF_SCAN_INTERVAL, - TEMP_CELSIUS -) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import Entity import homeassistant.helpers.config_validation as cv +from .const import * -_LOGGER = logging.getLogger(__name__) - -VOLUME_MICROGRAMS_PER_CUBIC_METER = 'µg/m3' - -DOMAIN = "local_luftdaten" - -SENSOR_TEMPERATURE = 'temperature' -SENSOR_HUMIDITY = 'humidity' -SENSOR_BME280_TEMPERATURE = 'BME280_temperature' -SENSOR_BME280_HUMIDITY = 'BME280_humidity' -SENSOR_BME280_PRESSURE = 'BME280_pressure' -SENSOR_BMP_TEMPERATURE = 'BMP_temperature' -SENSOR_BMP_PRESSURE = 'BMP_pressure' -SENSOR_BMP280_TEMPERATURE = 'BMP280_temperature' -SENSOR_BMP280_PRESSURE = 'BMP280_pressure' -SENSOR_PM1 = 'SDS_P1' -SENSOR_PM2 = 'SDS_P2' -SENSOR_WIFI_SIGNAL = 'signal' -SENSOR_HTU21D_TEMPERATURE = 'HTU21D_temperature' -SENSOR_HTU21D_HUMIDITY = 'HTU21D_humidity' -SENSOR_SPS30_P0 = 'SPS30_P0' -SENSOR_SPS30_P2 = 'SPS30_P2' -SENSOR_SPS30_P4 = 'SPS30_P4' -SENSOR_SPS30_P1 = 'SPS30_P1' -SENSOR_PMS_P0 = 'PMS_P0' -SENSOR_PMS_P1 = 'PMS_P1' -SENSOR_PMS_P2 = 'PMS_P2' -SENSOR_HECA_TEMPERATURE = 'HECA_temperature' -SENSOR_HECA_HUMIDITY = 'HECA_humidity' -SENSOR_HPM_P1 = 'HPM_P1' -SENSOR_HPM_P2 = 'HPM_P2' - - -SENSOR_TYPES = { - SENSOR_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_BME280_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_BME280_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_BME280_PRESSURE: ['Pressure', 'Pa', 'pressure'], - SENSOR_BMP_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_BMP_PRESSURE: ['Pressure', 'Pa', 'pressure'], - SENSOR_BMP280_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_BMP280_PRESSURE: ['Pressure', 'Pa', 'pressure'], - SENSOR_PM1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_PM2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_WIFI_SIGNAL: ['Wifi signal', 'dBm', 'signal_strength'], - SENSOR_HTU21D_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_HTU21D_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_SPS30_P0: ['PM1', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_SPS30_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_SPS30_P4: ['PM4', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_SPS30_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_PMS_P0: ['PM1', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_PMS_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_PMS_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_HECA_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_HECA_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_HPM_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_HPM_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], -} - -DEFAULT_NAME = 'Luftdaten Sensor' -DEFAULT_RESOURCE = 'http://{}/data.json' -DEFAULT_VERIFY_SSL = True -DEFAULT_SCAN_INTERVAL = timedelta(minutes=3) - -CONF_HOST = 'host' +_LOGGER = logging.getLogger(__name__) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ From cbd1bf3471ffe0cc83df7eb4b5d05c572713331c Mon Sep 17 00:00:00 2001 From: Joachim Engelmann Date: Mon, 19 Apr 2021 20:21:10 +0200 Subject: [PATCH 07/17] Use homeassistant.const for device class and units --- custom_components/local_luftdaten/const.py | 66 ++++++++++++---------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/custom_components/local_luftdaten/const.py b/custom_components/local_luftdaten/const.py index 53853ce..6592756 100644 --- a/custom_components/local_luftdaten/const.py +++ b/custom_components/local_luftdaten/const.py @@ -1,13 +1,24 @@ from datetime import timedelta from homeassistant.const import ( + #Config CONF_HOST, CONF_NAME, CONF_RESOURCE, CONF_VERIFY_SSL, CONF_MONITORED_CONDITIONS, CONF_SCAN_INTERVAL, - TEMP_CELSIUS + #Units of measurement + TEMP_CELSIUS, + PERCENTAGE, + PRESSURE_PA, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, + #Device classes + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_SIGNAL_STRENGTH ) DOMAIN = "local_luftdaten" @@ -17,9 +28,6 @@ DEFAULT_VERIFY_SSL = True DEFAULT_SCAN_INTERVAL = timedelta(minutes=3) -#Units of measurement -VOLUME_MICROGRAMS_PER_CUBIC_METER = 'µg/m3' - #Sensors SENSOR_TEMPERATURE = 'temperature' SENSOR_HUMIDITY = 'humidity' @@ -48,29 +56,29 @@ SENSOR_HPM_P2 = 'HPM_P2' SENSOR_TYPES = { - SENSOR_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_BME280_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_BME280_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_BME280_PRESSURE: ['Pressure', 'Pa', 'pressure'], - SENSOR_BMP_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_BMP_PRESSURE: ['Pressure', 'Pa', 'pressure'], - SENSOR_BMP280_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_BMP280_PRESSURE: ['Pressure', 'Pa', 'pressure'], - SENSOR_PM1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_PM2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_WIFI_SIGNAL: ['Wifi signal', 'dBm', 'signal_strength'], - SENSOR_HTU21D_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_HTU21D_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_SPS30_P0: ['PM1', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_SPS30_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_SPS30_P4: ['PM4', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_SPS30_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_PMS_P0: ['PM1', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_PMS_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_PMS_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_HECA_TEMPERATURE: ['Temperature', TEMP_CELSIUS, 'temperature'], - SENSOR_HECA_HUMIDITY: ['Humidity', '%', 'humidity'], - SENSOR_HPM_P1: ['PM10', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], - SENSOR_HPM_P2: ['PM2.5', VOLUME_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_TEMPERATURE: ['Temperature', TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE], + SENSOR_HUMIDITY: ['Humidity', PERCENTAGE, DEVICE_CLASS_HUMIDITY], + SENSOR_BME280_TEMPERATURE: ['Temperature', TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE], + SENSOR_BME280_HUMIDITY: ['Humidity', PERCENTAGE, DEVICE_CLASS_HUMIDITY], + SENSOR_BME280_PRESSURE: ['Pressure', PRESSURE_PA, DEVICE_CLASS_PRESSURE], + SENSOR_BMP_TEMPERATURE: ['Temperature', TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE], + SENSOR_BMP_PRESSURE: ['Pressure', PRESSURE_PA, DEVICE_CLASS_PRESSURE], + SENSOR_BMP280_TEMPERATURE: ['Temperature', TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE], + SENSOR_BMP280_PRESSURE: ['Pressure', PRESSURE_PA, DEVICE_CLASS_PRESSURE], + SENSOR_PM1: ['PM10', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_PM2: ['PM2.5', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_WIFI_SIGNAL: ['Wifi signal', SIGNAL_STRENGTH_DECIBELS_MILLIWATT, DEVICE_CLASS_SIGNAL_STRENGTH], + SENSOR_HTU21D_TEMPERATURE: ['Temperature', TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE], + SENSOR_HTU21D_HUMIDITY: ['Humidity', PERCENTAGE, DEVICE_CLASS_HUMIDITY], + SENSOR_SPS30_P0: ['PM1', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_SPS30_P2: ['PM2.5', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_SPS30_P4: ['PM4', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_SPS30_P1: ['PM10', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_PMS_P0: ['PM1', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_PMS_P1: ['PM10', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_PMS_P2: ['PM2.5', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_HECA_TEMPERATURE: ['Temperature', TEMP_CELSIUS, DEVICE_CLASS_TEMPERATURE], + SENSOR_HECA_HUMIDITY: ['Humidity', PERCENTAGE, DEVICE_CLASS_HUMIDITY], + SENSOR_HPM_P1: ['PM10', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], + SENSOR_HPM_P2: ['PM2.5', CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, None], } From a1a46d160e089d579c562ae9bc4bbb258664555f Mon Sep 17 00:00:00 2001 From: Joachim Engelmann Date: Mon, 19 Apr 2021 20:29:15 +0200 Subject: [PATCH 08/17] Provide icon for particulate matter sensor Provides icons for PM10 and PM2.5 sensor. Other not so spread sensor values are not considered. --- custom_components/local_luftdaten/sensor.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index c53941d..af2a3b1 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -94,7 +94,11 @@ def device_class(self): @property def icon(self): """Icon of the sensor, if class is None.""" - if SENSOR_TYPES[self.sensor_type][2] == None: + if SENSOR_TYPES[self.sensor_type][0] == "PM2.5": + return 'mdi:thought-bubble-outline' + elif SENSOR_TYPES[self.sensor_type][0] == "PM10": + return 'mdi:thought-bubble' + elif SENSOR_TYPES[self.sensor_type][2] == None: return 'mdi:cloud-search-outline' async def async_update(self): From 2fdf9eca2ace58256847bf522be0bffd7de8744b Mon Sep 17 00:00:00 2001 From: Joachim Engelmann Date: Mon, 19 Apr 2021 22:26:24 +0200 Subject: [PATCH 09/17] Add entity unique_id Entities can now be configured in Configuration->Entities --- custom_components/local_luftdaten/sensor.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index af2a3b1..54139ce 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -41,7 +41,7 @@ @asyncio.coroutine -async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Luftdaten sensor.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) @@ -56,7 +56,7 @@ async def async_setup_platform(hass, config, async_add_devices, discovery_info=N for variable in config[CONF_MONITORED_CONDITIONS]: devices.append(LuftdatenSensor(rest_client, name, variable)) - async_add_devices(devices, True) + async_add_entities(devices, True) class LuftdatenSensor(Entity): @@ -70,6 +70,7 @@ def __init__(self, rest_client, name, sensor_type): self.sensor_type = sensor_type self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] self._device_class = SENSOR_TYPES[sensor_type][2] + self._unique_id = '{} {}'.format(self._name, SENSOR_TYPES[self.sensor_type][0]) @property def name(self): @@ -101,6 +102,10 @@ def icon(self): elif SENSOR_TYPES[self.sensor_type][2] == None: return 'mdi:cloud-search-outline' + @property + def unique_id(self): + return self._unique_id + async def async_update(self): """Get the latest data from REST API and update the state.""" try: From 32f9dc0c766bd91b0a9d997f4bf61cf16cb23048 Mon Sep 17 00:00:00 2001 From: lichtteil Date: Sun, 2 May 2021 15:07:25 +0200 Subject: [PATCH 10/17] Update readme --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d4ab5f3..2bea091 100644 --- a/README.md +++ b/README.md @@ -26,8 +26,8 @@ Create a new sensor entry in your `configuration.yaml` and adjust the host name |Parameter |Type | Necessity | Description |:----------------------|:-------|:------------ |:------------ -|`host` | number | required | The IP-Address of the sensor -|`scan_interval` | number | default: 180 | The frequency (in seconds) between data updates. +|`host` | string | required | IP address of the sensor +|`scan_interval` | number | default: 180 | Frequency (in seconds) between updates |`name` | string | required | Name of the sensor |`monitored_conditions` | list | required | List of the monitored sensors @@ -84,7 +84,8 @@ Please open an issue if you want to see other attributes and provide me with a s ### Rounding and offset -Use [Template Sensors](https://www.home-assistant.io/integrations/template/) to round the values or to give them a offset. +Use [Template Sensors](https://www.home-assistant.io/integrations/template/) to round the values or to give them an offset. + ``` sensor: - platform: template From 603c5f05ae1535befe9c4df77069ba9334f804e5 Mon Sep 17 00:00:00 2001 From: lichtteil Date: Sun, 2 May 2021 15:10:48 +0200 Subject: [PATCH 11/17] =?UTF-8?q?Add=20=E2=80=9Ciot=5Fclass=E2=80=9D=20to?= =?UTF-8?q?=20manifest.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/local_luftdaten/manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/custom_components/local_luftdaten/manifest.json b/custom_components/local_luftdaten/manifest.json index 4d85ee2..3ff5701 100644 --- a/custom_components/local_luftdaten/manifest.json +++ b/custom_components/local_luftdaten/manifest.json @@ -6,5 +6,6 @@ "dependencies": [], "codeowners": ["@lichtteil"], "requirements": [], - "version": "1.9" + "version": "1.9", + "iot_class": "local_polling" } From a0da40a8611852d1c992d152ed8e1cd26047d934 Mon Sep 17 00:00:00 2001 From: lichtteil Date: Sun, 2 May 2021 15:11:17 +0200 Subject: [PATCH 12/17] Bump version to 1.10 --- custom_components/local_luftdaten/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/local_luftdaten/manifest.json b/custom_components/local_luftdaten/manifest.json index 3ff5701..f07e9c8 100644 --- a/custom_components/local_luftdaten/manifest.json +++ b/custom_components/local_luftdaten/manifest.json @@ -6,6 +6,6 @@ "dependencies": [], "codeowners": ["@lichtteil"], "requirements": [], - "version": "1.9", + "version": "1.10", "iot_class": "local_polling" } From b572258c798afc0d44f6f40a94acc4c278069f61 Mon Sep 17 00:00:00 2001 From: Joachim Engelmann Date: Sun, 2 May 2021 16:53:44 +0200 Subject: [PATCH 13/17] Query local sensor once per scan_interval The object LuftdatenSensor->async_update will be called by HA simultaniously for all defined sensors. This function will call LuftdatenClient->async_update for each defined sensor (at the same time). This causes multiple REST requests to the local sensor that are not necessary. Update the local sensor data only once per scan_interval, every other call will get the previous fetched and parsed data. This commit saves bandwith and cpu time by fetching and parsing the local sensor data only once. --- custom_components/local_luftdaten/sensor.py | 88 +++++++++++++-------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index 54139ce..1b78daa 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -12,6 +12,7 @@ import asyncio import aiohttp import async_timeout +import datetime import json @@ -45,12 +46,15 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """Set up the Luftdaten sensor.""" name = config.get(CONF_NAME) host = config.get(CONF_HOST) + scan_interval = config.get(CONF_SCAN_INTERVAL) + verify_ssl = config.get(CONF_VERIFY_SSL) + resource = config.get(CONF_RESOURCE).format(host) session = async_get_clientsession(hass, verify_ssl) - rest_client = LuftdatenClient(hass.loop, session, resource) + rest_client = LuftdatenClient(hass.loop, session, resource, scan_interval) devices = [] for variable in config[CONF_MONITORED_CONDITIONS]: @@ -111,24 +115,14 @@ async def async_update(self): try: await self.rest_client.async_update() except LuftdatenError: - value = None - return - value = self.rest_client.data - - try: - parsed_json = json.loads(value) - if not isinstance(parsed_json, dict): - _LOGGER.warning("JSON result was not a dictionary") - return - except ValueError: - _LOGGER.warning("REST result could not be parsed as JSON") - _LOGGER.debug("Erroneous JSON: %s", value) return + parsed_json = self.rest_client.data - sensordata_values = parsed_json['sensordatavalues'] - for sensordata_value in sensordata_values: - if sensordata_value['value_type'] == self.sensor_type: - self._state = sensordata_value['value'] + if parsed_json != None: + sensordata_values = parsed_json['sensordatavalues'] + for sensordata_value in sensordata_values: + if sensordata_value['value_type'] == self.sensor_type: + self._state = sensordata_value['value'] class LuftdatenError(Exception): @@ -138,26 +132,58 @@ class LuftdatenError(Exception): class LuftdatenClient(object): """Class for handling the data retrieval.""" - def __init__(self, loop, session, resource): + def __init__(self, loop, session, resource, scan_interval): """Initialize the data object.""" self._loop = loop self._session = session self._resource = resource + self.lastUpdate = datetime.datetime.now() + self.scan_interval = scan_interval self.data = None + self.lock = asyncio.Lock() async def async_update(self): """Get the latest data from Luftdaten service.""" _LOGGER.debug("Get data from %s", str(self._resource)) - try: - with async_timeout.timeout(30, loop=self._loop): - response = await self._session.get(self._resource) - self.data = await response.text() - _LOGGER.debug("Received data: %s", str(self.data)) - except aiohttp.ClientError as err: - _LOGGER.warning("REST request error: {0}".format(err)) - self.data = None - raise LuftdatenError - except asyncio.TimeoutError: - _LOGGER.warning("REST request timeout") - self.data = None - raise LuftdatenError + + async with self.lock: + # Time difference since last data update + callTimeDiff = datetime.datetime.now() - self.lastUpdate + # Fetch sensor values only once per scan_interval + if (callTimeDiff < self.scan_interval): + if self.data != None: + return + + # Handle calltime differences: substract 5 second from current time + self.lastUpdate = datetime.datetime.now() - timedelta(seconds=5) + + # Query local device + responseData = None + try: + with async_timeout.timeout(30, loop=self._loop): + response = await self._session.get(self._resource) + responseData = await response.text() + _LOGGER.debug("Received data: %s", str(self.data)) + except aiohttp.ClientError as err: + _LOGGER.warning("REST request error: {0}".format(err)) + self.data = None + raise LuftdatenError + except asyncio.TimeoutError: + _LOGGER.warning("REST request timeout") + self.data = None + raise LuftdatenError + + # Parse REST response + try: + parsed_json = json.loads(responseData) + if not isinstance(parsed_json, dict): + _LOGGER.warning("JSON result was not a dictionary") + self.data = None + return + # Set parsed json as data + self.data = parsed_json + except ValueError: + _LOGGER.warning("REST result could not be parsed as JSON") + _LOGGER.debug("Erroneous JSON: %s", responseData) + self.data = None + return From d8108c74d7d1dd49fb28d5e25d2b55219e4d7045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Wed, 5 May 2021 21:09:42 +0200 Subject: [PATCH 14/17] Use sensor type in uuid to reduce collision risk --- custom_components/local_luftdaten/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index 54139ce..3439272 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -70,7 +70,7 @@ def __init__(self, rest_client, name, sensor_type): self.sensor_type = sensor_type self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] self._device_class = SENSOR_TYPES[sensor_type][2] - self._unique_id = '{} {}'.format(self._name, SENSOR_TYPES[self.sensor_type][0]) + self._unique_id = '{}-{}'.format(self._name, self.sensor_type) @property def name(self): From f8b11e813489c1d1d205dfaa7cb5525b30caca3d Mon Sep 17 00:00:00 2001 From: Joachim Engelmann <32760573+JoachimEngelmann@users.noreply.github.com> Date: Mon, 17 May 2021 12:12:05 +0200 Subject: [PATCH 15/17] Update README.md Add additional file to the file structure in manual installation --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2bea091..397acfa 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Download and unzip or clone this repository and copy `custom_components/local_lu In the end your file structure should look like that: ``` ~/.homeassistant/custom_components/local_luftdaten/__init__.py +~/.homeassistant/custom_components/local_luftdaten/const.py ~/.homeassistant/custom_components/local_luftdaten/manifest.json ~/.homeassistant/custom_components/local_luftdaten/sensor.py ``` From 783c329e7bb02533e56c3503863e5df603df3ac6 Mon Sep 17 00:00:00 2001 From: lichtteil Date: Mon, 7 Jun 2021 18:00:25 +0200 Subject: [PATCH 16/17] Move debug message --- custom_components/local_luftdaten/sensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/local_luftdaten/sensor.py b/custom_components/local_luftdaten/sensor.py index ed2594c..2bac5d3 100644 --- a/custom_components/local_luftdaten/sensor.py +++ b/custom_components/local_luftdaten/sensor.py @@ -144,7 +144,6 @@ def __init__(self, loop, session, resource, scan_interval): async def async_update(self): """Get the latest data from Luftdaten service.""" - _LOGGER.debug("Get data from %s", str(self._resource)) async with self.lock: # Time difference since last data update @@ -153,13 +152,14 @@ async def async_update(self): if (callTimeDiff < self.scan_interval): if self.data != None: return - + # Handle calltime differences: substract 5 second from current time self.lastUpdate = datetime.datetime.now() - timedelta(seconds=5) # Query local device responseData = None try: + _LOGGER.debug("Get data from %s", str(self._resource)) with async_timeout.timeout(30, loop=self._loop): response = await self._session.get(self._resource) responseData = await response.text() From f009c797316f98f00084bd8041e8ce9184197fca Mon Sep 17 00:00:00 2001 From: lichtteil Date: Mon, 7 Jun 2021 18:10:28 +0200 Subject: [PATCH 17/17] Bump version to 2.0.0 --- custom_components/local_luftdaten/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/local_luftdaten/manifest.json b/custom_components/local_luftdaten/manifest.json index f07e9c8..243010a 100644 --- a/custom_components/local_luftdaten/manifest.json +++ b/custom_components/local_luftdaten/manifest.json @@ -6,6 +6,6 @@ "dependencies": [], "codeowners": ["@lichtteil"], "requirements": [], - "version": "1.10", + "version": "2.0.0", "iot_class": "local_polling" }