diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index 15b5501adce..cb869b6099c 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.2.13"], + "requirements": ["hatasmota==0.2.14"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"], diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 19c3e37fa57..e346f8f13ac 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -1,6 +1,8 @@ """Support for Tasmota sensors.""" from __future__ import annotations +import logging + from hatasmota import const as hc, status_sensor from homeassistant.components import sensor @@ -11,6 +13,7 @@ from homeassistant.const import ( CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_BATTERY, DEVICE_CLASS_CO2, + DEVICE_CLASS_ENERGY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, @@ -40,11 +43,14 @@ from homeassistant.const import ( ) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.util import dt as dt_util from .const import DATA_REMOVE_DISCOVER_COMPONENT from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW from .mixins import TasmotaAvailability, TasmotaDiscoveryUpdate +_LOGGER = logging.getLogger(__name__) + DEVICE_CLASS = "device_class" STATE_CLASS = "state_class" ICON = "icon" @@ -106,13 +112,16 @@ SENSOR_DEVICE_CLASS_ICON_MAP = { DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE, STATE_CLASS: STATE_CLASS_MEASUREMENT, }, - hc.SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_POWER}, - hc.SENSOR_TOTAL: {DEVICE_CLASS: DEVICE_CLASS_POWER}, + hc.SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_ENERGY}, + hc.SENSOR_TOTAL: { + DEVICE_CLASS: DEVICE_CLASS_ENERGY, + STATE_CLASS: STATE_CLASS_MEASUREMENT, + }, hc.SENSOR_TOTAL_START_TIME: {ICON: "mdi:progress-clock"}, hc.SENSOR_TVOC: {ICON: "mdi:air-filter"}, hc.SENSOR_VOLTAGE: {ICON: "mdi:alpha-v-circle-outline"}, hc.SENSOR_WEIGHT: {ICON: "mdi:scale"}, - hc.SENSOR_YESTERDAY: {DEVICE_CLASS: DEVICE_CLASS_POWER}, + hc.SENSOR_YESTERDAY: {DEVICE_CLASS: DEVICE_CLASS_ENERGY}, } SENSOR_UNIT_MAP = { @@ -166,6 +175,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, SensorEntity): """Representation of a Tasmota sensor.""" + _attr_last_reset = None + def __init__(self, **kwds): """Initialize the Tasmota sensor.""" self._state = None @@ -178,6 +189,18 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, SensorEntity): def state_updated(self, state, **kwargs): """Handle state updates.""" self._state = state + if "last_reset" in kwargs: + try: + last_reset = dt_util.as_utc( + dt_util.parse_datetime(kwargs["last_reset"]) + ) + if last_reset is None: + raise ValueError + self._attr_last_reset = last_reset + except ValueError: + _LOGGER.warning( + "Invalid last_reset timestamp '%s'", kwargs["last_reset"] + ) self.async_write_ha_state() @property diff --git a/requirements_all.txt b/requirements_all.txt index b02a6191c9a..6ecfed46076 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -738,7 +738,7 @@ hass-nabucasa==0.43.0 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.2.13 +hatasmota==0.2.14 # homeassistant.components.jewish_calendar hdate==0.10.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0fa3d2ced9b..8ebcfc3588b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -411,7 +411,7 @@ hangups==0.4.11 hass-nabucasa==0.43.0 # homeassistant.components.tasmota -hatasmota==0.2.13 +hatasmota==0.2.14 # homeassistant.components.jewish_calendar hdate==0.10.2 diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index 425fa3f26f6..e9aa291fe6d 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -233,6 +233,55 @@ async def test_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): assert state.state == "7.8" +async def test_indexed_sensor_state_via_mqtt2(hass, mqtt_mock, setup_tasmota): + """Test state update via MQTT for sensor with last_reset property.""" + config = copy.deepcopy(DEFAULT_CONFIG) + sensor_config = copy.deepcopy(INDEXED_SENSOR_CONFIG) + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/sensors", + json.dumps(sensor_config), + ) + await hass.async_block_till_done() + + state = hass.states.get("sensor.tasmota_energy_total") + assert state.state == "unavailable" + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") + state = hass.states.get("sensor.tasmota_energy_total") + assert state.state == STATE_UNKNOWN + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + # Test periodic state update + async_fire_mqtt_message( + hass, + "tasmota_49A3BC/tele/SENSOR", + '{"ENERGY":{"Total":1.2,"TotalStartTime":"2018-11-23T15:33:47"}}', + ) + state = hass.states.get("sensor.tasmota_energy_total") + assert state.state == "1.2" + assert state.attributes["last_reset"] == "2018-11-23T15:33:47+00:00" + + # Test polled state update + async_fire_mqtt_message( + hass, + "tasmota_49A3BC/stat/STATUS10", + '{"StatusSNS":{"ENERGY":{"Total":5.6,"TotalStartTime":"2018-11-23T16:33:47"}}}', + ) + state = hass.states.get("sensor.tasmota_energy_total") + assert state.state == "5.6" + assert state.attributes["last_reset"] == "2018-11-23T16:33:47+00:00" + + async def test_bad_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): """Test state update via MQTT where sensor is not matching configuration.""" config = copy.deepcopy(DEFAULT_CONFIG)