From a133eae88e222af1a2b74ac904c98638972f79f7 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 27 Jul 2021 19:48:37 +0200 Subject: [PATCH] Add more mysensors sensor attributes (#53566) --- homeassistant/components/mysensors/sensor.py | 117 +++++++++++++----- homeassistant/components/mysensors/switch.py | 13 -- tests/components/mysensors/conftest.py | 14 +++ tests/components/mysensors/test_sensor.py | 21 ++++ .../mysensors/power_sensor_state.json | 21 ++++ 5 files changed, 141 insertions(+), 45 deletions(-) create mode 100644 tests/fixtures/mysensors/power_sensor_state.json diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index f2908567a14..c7755b13512 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -1,16 +1,27 @@ """Support for MySensors sensors.""" from __future__ import annotations +from datetime import datetime + from awesomeversion import AwesomeVersion from homeassistant.components import mysensors -from homeassistant.components.sensor import DOMAIN, SensorEntity +from homeassistant.components.sensor import ( + DOMAIN, + STATE_CLASS_MEASUREMENT, + SensorEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONDUCTIVITY, DEGREE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_ILLUMINANCE, + DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, ELECTRIC_CURRENT_AMPERE, ELECTRIC_POTENTIAL_MILLIVOLT, ELECTRIC_POTENTIAL_VOLT, @@ -30,42 +41,68 @@ from homeassistant.const import ( from homeassistant.core import HomeAssistant from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util.dt import utc_from_timestamp from .const import MYSENSORS_DISCOVERY, DiscoveryInfo from .helpers import on_unload SENSORS: dict[str, list[str | None] | dict[str, list[str | None]]] = { - "V_TEMP": [None, None, DEVICE_CLASS_TEMPERATURE], - "V_HUM": [PERCENTAGE, "mdi:water-percent", DEVICE_CLASS_HUMIDITY], - "V_DIMMER": [PERCENTAGE, "mdi:percent", None], - "V_PERCENTAGE": [PERCENTAGE, "mdi:percent", None], - "V_PRESSURE": [None, "mdi:gauge", None], - "V_FORECAST": [None, "mdi:weather-partly-cloudy", None], - "V_RAIN": [None, "mdi:weather-rainy", None], - "V_RAINRATE": [None, "mdi:weather-rainy", None], - "V_WIND": [None, "mdi:weather-windy", None], - "V_GUST": [None, "mdi:weather-windy", None], - "V_DIRECTION": [DEGREE, "mdi:compass", None], - "V_WEIGHT": [MASS_KILOGRAMS, "mdi:weight-kilogram", None], - "V_DISTANCE": [LENGTH_METERS, "mdi:ruler", None], - "V_IMPEDANCE": ["ohm", None, None], - "V_WATT": [POWER_WATT, None, None], - "V_KWH": [ENERGY_KILO_WATT_HOUR, None, None], - "V_LIGHT_LEVEL": [PERCENTAGE, "mdi:white-balance-sunny", None], - "V_FLOW": [LENGTH_METERS, "mdi:gauge", None], - "V_VOLUME": [f"{VOLUME_CUBIC_METERS}", None, None], + "V_TEMP": [None, None, DEVICE_CLASS_TEMPERATURE, STATE_CLASS_MEASUREMENT], + "V_HUM": [ + PERCENTAGE, + "mdi:water-percent", + DEVICE_CLASS_HUMIDITY, + STATE_CLASS_MEASUREMENT, + ], + "V_DIMMER": [PERCENTAGE, "mdi:percent", None, None], + "V_PERCENTAGE": [PERCENTAGE, "mdi:percent", None, None], + "V_PRESSURE": [None, "mdi:gauge", None, None], + "V_FORECAST": [None, "mdi:weather-partly-cloudy", None, None], + "V_RAIN": [None, "mdi:weather-rainy", None, None], + "V_RAINRATE": [None, "mdi:weather-rainy", None, None], + "V_WIND": [None, "mdi:weather-windy", None, None], + "V_GUST": [None, "mdi:weather-windy", None, None], + "V_DIRECTION": [DEGREE, "mdi:compass", None, None], + "V_WEIGHT": [MASS_KILOGRAMS, "mdi:weight-kilogram", None, None], + "V_DISTANCE": [LENGTH_METERS, "mdi:ruler", None, None], + "V_IMPEDANCE": ["ohm", None, None, None], + "V_WATT": [POWER_WATT, None, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT], + "V_KWH": [ + ENERGY_KILO_WATT_HOUR, + None, + DEVICE_CLASS_ENERGY, + STATE_CLASS_MEASUREMENT, + ], + "V_LIGHT_LEVEL": [PERCENTAGE, "mdi:white-balance-sunny", None, None], + "V_FLOW": [LENGTH_METERS, "mdi:gauge", None, None], + "V_VOLUME": [VOLUME_CUBIC_METERS, None, None, None], "V_LEVEL": { - "S_SOUND": [SOUND_PRESSURE_DB, "mdi:volume-high", None], - "S_VIBRATION": [FREQUENCY_HERTZ, None, None], - "S_LIGHT_LEVEL": [LIGHT_LUX, "mdi:white-balance-sunny", None], + "S_SOUND": [SOUND_PRESSURE_DB, "mdi:volume-high", None, None], + "S_VIBRATION": [FREQUENCY_HERTZ, None, None, None], + "S_LIGHT_LEVEL": [ + LIGHT_LUX, + "mdi:white-balance-sunny", + DEVICE_CLASS_ILLUMINANCE, + STATE_CLASS_MEASUREMENT, + ], }, - "V_VOLTAGE": [ELECTRIC_POTENTIAL_VOLT, "mdi:flash", None], - "V_CURRENT": [ELECTRIC_CURRENT_AMPERE, "mdi:flash-auto", None], - "V_PH": ["pH", None, None], - "V_ORP": [ELECTRIC_POTENTIAL_MILLIVOLT, None, None], - "V_EC": [CONDUCTIVITY, None, None], - "V_VAR": ["var", None, None], - "V_VA": [POWER_VOLT_AMPERE, None, None], + "V_VOLTAGE": [ + ELECTRIC_POTENTIAL_VOLT, + "mdi:flash", + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + ], + "V_CURRENT": [ + ELECTRIC_CURRENT_AMPERE, + "mdi:flash-auto", + DEVICE_CLASS_CURRENT, + STATE_CLASS_MEASUREMENT, + ], + "V_PH": ["pH", None, None, None], + "V_ORP": [ELECTRIC_POTENTIAL_MILLIVOLT, None, None, None], + "V_EC": [CONDUCTIVITY, None, None, None], + "V_VAR": ["var", None, None, None], + "V_VA": [POWER_VOLT_AMPERE, None, None, None], } @@ -124,6 +161,20 @@ class MySensorsSensor(mysensors.device.MySensorsEntity, SensorEntity): """Return the icon to use in the frontend, if any.""" return self._get_sensor_type()[1] + @property + def last_reset(self) -> datetime | None: + """Return the time when the sensor was last reset, if any.""" + set_req = self.gateway.const.SetReq + + if set_req(self.value_type).name == "V_KWH": + return utc_from_timestamp(0) + return None + + @property + def state_class(self) -> str | None: + """Return the state class of this entity.""" + return self._get_sensor_type()[3] + @property def unit_of_measurement(self) -> str | None: """Return the unit of measurement of this entity.""" @@ -148,10 +199,12 @@ class MySensorsSensor(mysensors.device.MySensorsEntity, SensorEntity): pres = self.gateway.const.Presentation set_req = self.gateway.const.SetReq - _sensor_type = SENSORS.get(set_req(self.value_type).name, [None, None, None]) + _sensor_type = SENSORS.get( + set_req(self.value_type).name, [None, None, None, None] + ) if isinstance(_sensor_type, dict): sensor_type = _sensor_type.get( - pres(self.child_type).name, [None, None, None] + pres(self.child_type).name, [None, None, None, None] ) else: sensor_type = _sensor_type diff --git a/homeassistant/components/mysensors/switch.py b/homeassistant/components/mysensors/switch.py index cdb4979d16b..8f8c759c364 100644 --- a/homeassistant/components/mysensors/switch.py +++ b/homeassistant/components/mysensors/switch.py @@ -1,7 +1,6 @@ """Support for MySensors switches.""" from __future__ import annotations -from contextlib import suppress from typing import Any import voluptuous as vol @@ -109,18 +108,6 @@ async def async_setup_entry( class MySensorsSwitch(mysensors.device.MySensorsEntity, SwitchEntity): """Representation of the value of a MySensors Switch child node.""" - @property - def current_power_w(self) -> float | None: - """Return the current power usage in W.""" - set_req = self.gateway.const.SetReq - value = self._values.get(set_req.V_WATT) - float_value: float | None = None - if value is not None: - with suppress(ValueError): - float_value = float(value) - - return float_value - @property def is_on(self) -> bool: """Return True if switch is on.""" diff --git a/tests/components/mysensors/conftest.py b/tests/components/mysensors/conftest.py index 8fbe9486352..49c32301442 100644 --- a/tests/components/mysensors/conftest.py +++ b/tests/components/mysensors/conftest.py @@ -156,3 +156,17 @@ def gps_sensor(gateway_nodes, gps_sensor_state) -> Sensor: nodes = update_gateway_nodes(gateway_nodes, gps_sensor_state) node = nodes[1] return node + + +@pytest.fixture(name="power_sensor_state", scope="session") +def power_sensor_state_fixture() -> dict: + """Load the power sensor state.""" + return load_nodes_state("mysensors/power_sensor_state.json") + + +@pytest.fixture +def power_sensor(gateway_nodes, power_sensor_state) -> Sensor: + """Load the power sensor.""" + nodes = update_gateway_nodes(gateway_nodes, power_sensor_state) + node = nodes[1] + return node diff --git a/tests/components/mysensors/test_sensor.py b/tests/components/mysensors/test_sensor.py index 69caeac9977..6edddc68592 100644 --- a/tests/components/mysensors/test_sensor.py +++ b/tests/components/mysensors/test_sensor.py @@ -1,6 +1,15 @@ """Provide tests for mysensors sensor platform.""" +from homeassistant.components.sensor import ATTR_STATE_CLASS, STATE_CLASS_MEASUREMENT +from homeassistant.const import ( + ATTR_DEVICE_CLASS, + ATTR_UNIT_OF_MEASUREMENT, + DEVICE_CLASS_POWER, + POWER_WATT, +) + + async def test_gps_sensor(hass, gps_sensor, integration): """Test a gps sensor.""" entity_id = "sensor.gps_sensor_1_1" @@ -8,3 +17,15 @@ async def test_gps_sensor(hass, gps_sensor, integration): state = hass.states.get(entity_id) assert state.state == "40.741894,-73.989311,12" + + +async def test_power_sensor(hass, power_sensor, integration): + """Test a power sensor.""" + entity_id = "sensor.power_sensor_1_1" + + state = hass.states.get(entity_id) + + assert state.state == "1200" + assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_POWER + assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == POWER_WATT + assert state.attributes[ATTR_STATE_CLASS] == STATE_CLASS_MEASUREMENT diff --git a/tests/fixtures/mysensors/power_sensor_state.json b/tests/fixtures/mysensors/power_sensor_state.json new file mode 100644 index 00000000000..40fcc4e4c74 --- /dev/null +++ b/tests/fixtures/mysensors/power_sensor_state.json @@ -0,0 +1,21 @@ +{ + "1": { + "sensor_id": 1, + "children": { + "1": { + "id": 1, + "type": 13, + "description": "", + "values": { + "17": "1200" + } + } + }, + "type": 17, + "sketch_name": "Power Sensor", + "sketch_version": "1.0", + "battery_level": 0, + "protocol_version": "2.3.2", + "heartbeat": 0 + } +}