From 4fbe9c5660290af70f4b2b52caa9211e8d1f0a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Seux?= Date: Thu, 26 Aug 2021 23:04:05 +0200 Subject: [PATCH 1/3] Support state class propoerty for sensor Fix lfasci/homeassistant-prometheus-query#5 Goal is to make a first pass to allow to be exposed in the energy dashboard. This requires usage of HA version 2021.9 (which is beta at time of writing) if we want to allow total_increase state_class --- README.md | 6 ++++++ custom_components/prometheus_query/sensor.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/README.md b/README.md index c49cebd..4412a85 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ sensor: prometheus_url: http://localhost:9090 prometheus_query: temperature{location="Pisa",province="PI",region="Tuscany"} unit_of_measurement: "°C" + state_class: total_increasing ``` ### Configuration Variables @@ -32,5 +33,10 @@ sensor: - unit_of_measurement (string)(Optional) Defines the unit of measurement of the sensor, if any. + +- state_class + + (string)(Optional) Defines the type of sensor. `measurement` for metrics that are gauges, + `total_increasing` for metrics that are counters. It's a custom component so it must be downloaded under /custom_components folder. diff --git a/custom_components/prometheus_query/sensor.py b/custom_components/prometheus_query/sensor.py index 98f4318..70aa2a7 100644 --- a/custom_components/prometheus_query/sensor.py +++ b/custom_components/prometheus_query/sensor.py @@ -12,11 +12,16 @@ from homeassistant.const import ( CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, ) +from homeassistant.components.sensor import ( + DEVICE_CLASSES_SCHEMA, + STATE_CLASSES_SCHEMA, +) from prometheus_client import Summary CONF_PROMETHEUS_URL = 'prometheus_url' CONF_PROMETHEUS_QUERY = 'prometheus_query' +CONF_STATE_CLASS = 'state_class' SCAN_INTERVAL = timedelta(seconds=600) _LOGGER = logging.getLogger(__name__) @@ -27,6 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_PROMETHEUS_QUERY): cv.string, vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, + vol.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA, }) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -36,6 +42,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): 'query': str(config.get(CONF_PROMETHEUS_QUERY)), 'name': str(config.get(CONF_NAME)), 'unit': str(config.get(CONF_UNIT_OF_MEASUREMENT)), + 'state_class': str(config.get(CONF_STATE_CLASS)), } add_entities([PrometheusQuery(prom_data)], True) @@ -49,6 +56,7 @@ class PrometheusQuery(Entity): self._name = prom_data["name"] self._state = None self._unit_of_measurement = prom_data["unit"] + self._state_class = prom_data["state_class"] @property def name(self): @@ -64,6 +72,11 @@ class PrometheusQuery(Entity): def unit_of_measurement(self): """Return the unit of measurement.""" return self._unit_of_measurement + + @property + def state_class(self): + """Return the state_class of the sensor""" + return self._state_class def update(self): """Fetch new state data for the sensor. From f40cf1bfecdd8a99aed661c5810741da6c6ebd0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Seux?= Date: Sun, 29 Aug 2021 10:53:33 +0200 Subject: [PATCH 2/3] Support unique_id Support is a bit fragile since we don't really have a serial number to base on. However we have a reasonnable default (source prometheus + expression) and a way for user to specify id easily if they to change the prometheus query. --- custom_components/prometheus_query/sensor.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/custom_components/prometheus_query/sensor.py b/custom_components/prometheus_query/sensor.py index 70aa2a7..f0a7850 100644 --- a/custom_components/prometheus_query/sensor.py +++ b/custom_components/prometheus_query/sensor.py @@ -22,6 +22,7 @@ from prometheus_client import Summary CONF_PROMETHEUS_URL = 'prometheus_url' CONF_PROMETHEUS_QUERY = 'prometheus_query' CONF_STATE_CLASS = 'state_class' +CONF_UNIQUE_ID = 'unique_id' SCAN_INTERVAL = timedelta(seconds=600) _LOGGER = logging.getLogger(__name__) @@ -33,6 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_STATE_CLASS): STATE_CLASSES_SCHEMA, + vol.Optional(CONF_UNIQUE_ID): cv.string, }) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -43,6 +45,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): 'name': str(config.get(CONF_NAME)), 'unit': str(config.get(CONF_UNIT_OF_MEASUREMENT)), 'state_class': str(config.get(CONF_STATE_CLASS)), + 'unique_id': str(config.get(CONF_UNIQUE_ID)), } add_entities([PrometheusQuery(prom_data)], True) @@ -57,6 +60,9 @@ class PrometheusQuery(Entity): self._state = None self._unit_of_measurement = prom_data["unit"] self._state_class = prom_data["state_class"] + self._attr_unique_id = f"${prom_data['url']}$${prom_data['query']}" + if prom_data["unique_id"] is not None: + self._attr_unique_id = prom_data["unique_id"] @property def name(self): From 85d4acb2293e98d47b8615d415c9e4a7f35eef77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Seux?= Date: Sun, 29 Aug 2021 21:00:35 +0200 Subject: [PATCH 3/3] Migrate to SensorEntity base class Goal is to be usable as a source of energy in the energy dashboard. Starting with HA 2021.9 (still in beta), sensors will be have long term stats and be usable in energy dashboard. It also gives a better integration with base classes of home assistant and decrease code in this custom component. Change-Id: Ide1967ac181c0cf7f093cff539aae2b1cdccd9b4 --- custom_components/prometheus_query/sensor.py | 37 ++++++-------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/custom_components/prometheus_query/sensor.py b/custom_components/prometheus_query/sensor.py index f0a7850..41c883c 100644 --- a/custom_components/prometheus_query/sensor.py +++ b/custom_components/prometheus_query/sensor.py @@ -6,6 +6,7 @@ import homeassistant.helpers.config_validation as cv from datetime import timedelta from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import Entity +from homeassistant.components.sensor import SensorEntity from homeassistant.util import Throttle from homeassistant.const import ( CONF_NAME, @@ -47,43 +48,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None): 'state_class': str(config.get(CONF_STATE_CLASS)), 'unique_id': str(config.get(CONF_UNIQUE_ID)), } + add_entities([PrometheusQuery(prom_data)], True) -class PrometheusQuery(Entity): - """Representation of a Sensor.""" +class PrometheusQuery(SensorEntity): + """Representation of a Sensor based on Prometheus""" def __init__(self, prom_data): """Initialize the sensor.""" self._url = prom_data["url"] self._query = prom_data["query"] - self._name = prom_data["name"] + self._attr_name = prom_data["name"] self._state = None - self._unit_of_measurement = prom_data["unit"] - self._state_class = prom_data["state_class"] + self._attr_native_unit_of_measurement = prom_data["unit"] + self._attr_state_class = prom_data["state_class"] self._attr_unique_id = f"${prom_data['url']}$${prom_data['query']}" if prom_data["unique_id"] is not None: self._attr_unique_id = prom_data["unique_id"] - @property - def name(self): - """Return the name of the sensor.""" - return self._name - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement.""" - return self._unit_of_measurement - - @property - def state_class(self): - """Return the state_class of the sensor""" - return self._state_class - def update(self): """Fetch new state data for the sensor. This is the only method that should fetch new data for Home Assistant. @@ -92,8 +74,9 @@ class PrometheusQuery(Entity): response = requests.get(self._url, params={'query': self._query}) if (response): results = response.json()['data']['result'] - self._state = results[0]['value'][1] + self._attr_native_value = results[0]['value'][1] else: - self._state = STATE_UNKNOWN + self._attr_native_value = STATE_UNKNOWN + self._attr_state = self._attr_native_value except URLCallError: _LOGGER.error("Error when retrieving update data")