From c66106ee98677768c9bcd06245967a6f486bc0a5 Mon Sep 17 00:00:00 2001 From: Rami Mosleh Date: Tue, 11 Feb 2020 01:02:14 +0200 Subject: [PATCH] Add Glances sensors dynamically (#28639) * Add temp_sensors to glances dynamically * unsub from updates when sensor is disabled * dynamicall add sensors * change "device_name" to "mnt_point" * remove unnecessary logging * update sensor.py * update test_config_flow.py * remove commented code --- homeassistant/components/glances/const.py | 38 +++--- homeassistant/components/glances/sensor.py | 131 +++++++++++++------ tests/components/glances/test_config_flow.py | 69 ++++++---- 3 files changed, 152 insertions(+), 86 deletions(-) diff --git a/homeassistant/components/glances/const.py b/homeassistant/components/glances/const.py index e47586ea245..b7f5a2d642b 100644 --- a/homeassistant/components/glances/const.py +++ b/homeassistant/components/glances/const.py @@ -14,23 +14,23 @@ DATA_UPDATED = "glances_data_updated" SUPPORTED_VERSIONS = [2, 3] SENSOR_TYPES = { - "disk_use_percent": ["Disk used percent", "%", "mdi:harddisk"], - "disk_use": ["Disk used", "GiB", "mdi:harddisk"], - "disk_free": ["Disk free", "GiB", "mdi:harddisk"], - "memory_use_percent": ["RAM used percent", "%", "mdi:memory"], - "memory_use": ["RAM used", "MiB", "mdi:memory"], - "memory_free": ["RAM free", "MiB", "mdi:memory"], - "swap_use_percent": ["Swap used percent", "%", "mdi:memory"], - "swap_use": ["Swap used", "GiB", "mdi:memory"], - "swap_free": ["Swap free", "GiB", "mdi:memory"], - "processor_load": ["CPU load", "15 min", "mdi:memory"], - "process_running": ["Running", "Count", "mdi:memory"], - "process_total": ["Total", "Count", "mdi:memory"], - "process_thread": ["Thread", "Count", "mdi:memory"], - "process_sleeping": ["Sleeping", "Count", "mdi:memory"], - "cpu_use_percent": ["CPU used", "%", "mdi:memory"], - "cpu_temp": ["CPU Temp", TEMP_CELSIUS, "mdi:thermometer"], - "docker_active": ["Containers active", "", "mdi:docker"], - "docker_cpu_use": ["Containers CPU used", "%", "mdi:docker"], - "docker_memory_use": ["Containers RAM used", "MiB", "mdi:docker"], + "disk_use_percent": ["fs", "used percent", "%", "mdi:harddisk"], + "disk_use": ["fs", "used", "GiB", "mdi:harddisk"], + "disk_free": ["fs", "free", "GiB", "mdi:harddisk"], + "memory_use_percent": ["mem", "RAM used percent", "%", "mdi:memory"], + "memory_use": ["mem", "RAM used", "MiB", "mdi:memory"], + "memory_free": ["mem", "RAM free", "MiB", "mdi:memory"], + "swap_use_percent": ["memswap", "Swap used percent", "%", "mdi:memory"], + "swap_use": ["memswap", "Swap used", "GiB", "mdi:memory"], + "swap_free": ["memswap", "Swap free", "GiB", "mdi:memory"], + "processor_load": ["load", "CPU load", "15 min", "mdi:memory"], + "process_running": ["processcount", "Running", "Count", "mdi:memory"], + "process_total": ["processcount", "Total", "Count", "mdi:memory"], + "process_thread": ["processcount", "Thread", "Count", "mdi:memory"], + "process_sleeping": ["processcount", "Sleeping", "Count", "mdi:memory"], + "cpu_use_percent": ["cpu", "CPU used", "%", "mdi:memory"], + "sensor_temp": ["sensors", "Temp", TEMP_CELSIUS, "mdi:thermometer"], + "docker_active": ["docker", "Containers active", "", "mdi:docker"], + "docker_cpu_use": ["docker", "Containers CPU used", "%", "mdi:docker"], + "docker_memory_use": ["docker", "Containers RAM used", "MiB", "mdi:docker"], } diff --git a/homeassistant/components/glances/sensor.py b/homeassistant/components/glances/sensor.py index 968081cfc43..f701dfdb741 100644 --- a/homeassistant/components/glances/sensor.py +++ b/homeassistant/components/glances/sensor.py @@ -14,13 +14,51 @@ _LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the Glances sensors.""" - glances_data = hass.data[DOMAIN][config_entry.entry_id] + client = hass.data[DOMAIN][config_entry.entry_id] name = config_entry.data[CONF_NAME] dev = [] - for sensor_type in SENSOR_TYPES: - dev.append( - GlancesSensor(glances_data, name, SENSOR_TYPES[sensor_type][0], sensor_type) - ) + + for sensor_type, sensor_details in SENSOR_TYPES.items(): + if not sensor_details[0] in client.api.data: + continue + if sensor_details[0] in client.api.data: + if sensor_details[0] == "fs": + # fs will provide a list of disks attached + for disk in client.api.data[sensor_details[0]]: + dev.append( + GlancesSensor( + client, + name, + disk["mnt_point"], + SENSOR_TYPES[sensor_type][1], + sensor_type, + SENSOR_TYPES[sensor_type], + ) + ) + elif sensor_details[0] == "sensors": + # sensors will provide temp for different devices + for sensor in client.api.data[sensor_details[0]]: + dev.append( + GlancesSensor( + client, + name, + sensor["label"], + SENSOR_TYPES[sensor_type][1], + sensor_type, + SENSOR_TYPES[sensor_type], + ) + ) + elif client.api.data[sensor_details[0]]: + dev.append( + GlancesSensor( + client, + name, + "", + SENSOR_TYPES[sensor_type][1], + sensor_type, + SENSOR_TYPES[sensor_type], + ) + ) async_add_entities(dev, True) @@ -28,19 +66,29 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class GlancesSensor(Entity): """Implementation of a Glances sensor.""" - def __init__(self, glances_data, name, sensor_name, sensor_type): + def __init__( + self, + glances_data, + name, + sensor_name_prefix, + sensor_name_suffix, + sensor_type, + sensor_details, + ): """Initialize the sensor.""" self.glances_data = glances_data - self._sensor_name = sensor_name + self._sensor_name_prefix = sensor_name_prefix + self._sensor_name_suffix = sensor_name_suffix self._name = name self.type = sensor_type self._state = None - self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] + self.sensor_details = sensor_details + self.unsub_update = None @property def name(self): """Return the name of the sensor.""" - return f"{self._name} {self._sensor_name}" + return f"{self._name} {self._sensor_name_prefix} {self._sensor_name_suffix}" @property def unique_id(self): @@ -50,12 +98,12 @@ class GlancesSensor(Entity): @property def icon(self): """Icon to use in the frontend, if any.""" - return SENSOR_TYPES[self.type][2] + return self.sensor_details[3] @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" - return self._unit_of_measurement + return self.sensor_details[2] @property def available(self): @@ -74,7 +122,7 @@ class GlancesSensor(Entity): async def async_added_to_hass(self): """Handle entity which will be added.""" - async_dispatcher_connect( + self.unsub_update = async_dispatcher_connect( self.hass, DATA_UPDATED, self._schedule_immediate_update ) @@ -82,22 +130,40 @@ class GlancesSensor(Entity): def _schedule_immediate_update(self): self.async_schedule_update_ha_state(True) + async def will_remove_from_hass(self): + """Unsubscribe from update dispatcher.""" + if self.unsub_update: + self.unsub_update() + self.unsub_update = None + async def async_update(self): """Get the latest data from REST API.""" value = self.glances_data.api.data + if value is None: + return if value is not None: - if self.type == "disk_use_percent": - self._state = value["fs"][0]["percent"] - elif self.type == "disk_use": - self._state = round(value["fs"][0]["used"] / 1024 ** 3, 1) - elif self.type == "disk_free": - try: - self._state = round(value["fs"][0]["free"] / 1024 ** 3, 1) - except KeyError: - self._state = round( - (value["fs"][0]["size"] - value["fs"][0]["used"]) / 1024 ** 3, 1 - ) + if self.sensor_details[0] == "fs": + for var in value["fs"]: + if var["mnt_point"] == self._sensor_name_prefix: + disk = var + break + if self.type == "disk_use_percent": + self._state = disk["percent"] + elif self.type == "disk_use": + self._state = round(disk["used"] / 1024 ** 3, 1) + elif self.type == "disk_free": + try: + self._state = round(disk["free"] / 1024 ** 3, 1) + except KeyError: + self._state = round( + (disk["size"] - disk["used"]) / 1024 ** 3, 1, + ) + elif self.type == "sensor_temp": + for sensor in value["sensors"]: + if sensor["label"] == self._sensor_name_prefix: + self._state = sensor["value"] + break elif self.type == "memory_use_percent": self._state = value["mem"]["percent"] elif self.type == "memory_use": @@ -126,25 +192,6 @@ class GlancesSensor(Entity): self._state = value["processcount"]["sleeping"] elif self.type == "cpu_use_percent": self._state = value["quicklook"]["cpu"] - elif self.type == "cpu_temp": - for sensor in value["sensors"]: - if sensor["label"] in [ - "amdgpu 1", - "aml_thermal", - "Core 0", - "Core 1", - "CPU Temperature", - "CPU", - "cpu-thermal 1", - "cpu_thermal 1", - "exynos-therm 1", - "Package id 0", - "Physical id 0", - "radeon 1", - "soc-thermal 1", - "soc_thermal 1", - ]: - self._state = sensor["value"] elif self.type == "docker_active": count = 0 try: diff --git a/tests/components/glances/test_config_flow.py b/tests/components/glances/test_config_flow.py index e5be52e6b33..8734ca0e60d 100644 --- a/tests/components/glances/test_config_flow.py +++ b/tests/components/glances/test_config_flow.py @@ -3,8 +3,8 @@ from unittest.mock import patch from glances_api import Glances -from homeassistant.components.glances import config_flow -from homeassistant.components.glances.const import DOMAIN +from homeassistant import data_entry_flow +from homeassistant.components import glances from homeassistant.const import CONF_SCAN_INTERVAL from tests.common import MockConfigEntry, mock_coro @@ -29,22 +29,22 @@ DEMO_USER_INPUT = { } -def init_config_flow(hass): - """Init a configuration flow.""" - flow = config_flow.GlancesFlowHandler() - flow.hass = hass - return flow - - async def test_form(hass): """Test config entry configured successfully.""" - flow = init_config_flow(hass) + + result = await hass.config_entries.flow.async_init( + glances.DOMAIN, context={"source": "user"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "user" with patch("glances_api.Glances"), patch.object( Glances, "get_data", return_value=mock_coro() ): - result = await flow.async_step_user(DEMO_USER_INPUT) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=DEMO_USER_INPUT + ) assert result["type"] == "create_entry" assert result["title"] == NAME @@ -53,10 +53,14 @@ async def test_form(hass): async def test_form_cannot_connect(hass): """Test to return error if we cannot connect.""" - flow = init_config_flow(hass) with patch("glances_api.Glances"): - result = await flow.async_step_user(DEMO_USER_INPUT) + result = await hass.config_entries.flow.async_init( + glances.DOMAIN, context={"source": "user"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=DEMO_USER_INPUT + ) assert result["type"] == "form" assert result["errors"] == {"base": "cannot_connect"} @@ -64,11 +68,15 @@ async def test_form_cannot_connect(hass): async def test_form_wrong_version(hass): """Test to check if wrong version is entered.""" - flow = init_config_flow(hass) user_input = DEMO_USER_INPUT.copy() user_input.update(version=1) - result = await flow.async_step_user(user_input) + result = await hass.config_entries.flow.async_init( + glances.DOMAIN, context={"source": "user"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=user_input + ) assert result["type"] == "form" assert result["errors"] == {"version": "wrong_version"} @@ -77,13 +85,16 @@ async def test_form_wrong_version(hass): async def test_form_already_configured(hass): """Test host is already configured.""" entry = MockConfigEntry( - domain=DOMAIN, data=DEMO_USER_INPUT, options={CONF_SCAN_INTERVAL: 60} + domain=glances.DOMAIN, data=DEMO_USER_INPUT, options={CONF_SCAN_INTERVAL: 60} ) entry.add_to_hass(hass) - flow = init_config_flow(hass) - result = await flow.async_step_user(DEMO_USER_INPUT) - + result = await hass.config_entries.flow.async_init( + glances.DOMAIN, context={"source": "user"} + ) + result = await hass.config_entries.flow.async_configure( + result["flow_id"], user_input=DEMO_USER_INPUT + ) assert result["type"] == "abort" assert result["reason"] == "already_configured" @@ -91,12 +102,20 @@ async def test_form_already_configured(hass): async def test_options(hass): """Test options for Glances.""" entry = MockConfigEntry( - domain=DOMAIN, data=DEMO_USER_INPUT, options={CONF_SCAN_INTERVAL: 60} + domain=glances.DOMAIN, data=DEMO_USER_INPUT, options={CONF_SCAN_INTERVAL: 60} ) entry.add_to_hass(hass) - flow = init_config_flow(hass) - options_flow = flow.async_get_options_flow(entry) - result = await options_flow.async_step_init({CONF_SCAN_INTERVAL: 10}) - assert result["type"] == "create_entry" - assert result["data"][CONF_SCAN_INTERVAL] == 10 + result = await hass.config_entries.options.async_init(entry.entry_id) + + assert result["type"] == data_entry_flow.RESULT_TYPE_FORM + assert result["step_id"] == "init" + + result = await hass.config_entries.options.async_configure( + result["flow_id"], user_input={glances.CONF_SCAN_INTERVAL: 10} + ) + + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["data"] == { + glances.CONF_SCAN_INTERVAL: 10, + }