From ec7f3298072d9a92615086fd8b8fa5cbe87fc08a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 19 Oct 2020 22:07:31 +0200 Subject: [PATCH] Add support for Tasmota status sensor (#41782) * Add sensor attributes, remove useless test. * Fix tests * Rework handling of sensor attributes * Remove unused sensor attributes * Hide status sensors * Bump hatasmota to 0.0.19 * Use DEVICE_CLASS_SIGNAL_STRENGTH for WiFi signal sensor * Improve test coverage * Fix tests --- homeassistant/components/tasmota/discovery.py | 1 + .../components/tasmota/manifest.json | 2 +- homeassistant/components/tasmota/sensor.py | 20 ++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/tasmota/conftest.py | 15 ++++++ tests/components/tasmota/test_discovery.py | 33 ------------ tests/components/tasmota/test_sensor.py | 52 +++++++++++++++++++ 8 files changed, 90 insertions(+), 37 deletions(-) diff --git a/homeassistant/components/tasmota/discovery.py b/homeassistant/components/tasmota/discovery.py index 7773cacd42f..e137692445e 100644 --- a/homeassistant/components/tasmota/discovery.py +++ b/homeassistant/components/tasmota/discovery.py @@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__) SUPPORTED_PLATFORMS = [ "binary_sensor", "light", + "sensor", "switch", ] diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json index aa51a06725b..4157c416eca 100644 --- a/homeassistant/components/tasmota/manifest.json +++ b/homeassistant/components/tasmota/manifest.json @@ -3,7 +3,7 @@ "name": "Tasmota (beta)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/tasmota", - "requirements": ["hatasmota==0.0.18"], + "requirements": ["hatasmota==0.0.19"], "dependencies": ["mqtt"], "mqtt": ["tasmota/discovery/#"], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/tasmota/sensor.py b/homeassistant/components/tasmota/sensor.py index 189aa5731b3..660c0efc1e6 100644 --- a/homeassistant/components/tasmota/sensor.py +++ b/homeassistant/components/tasmota/sensor.py @@ -33,6 +33,7 @@ from hatasmota.const import ( SENSOR_PRESSUREATSEALEVEL, SENSOR_PROXIMITY, SENSOR_REACTIVE_POWERUSAGE, + SENSOR_STATUS_SIGNAL, SENSOR_TEMPERATURE, SENSOR_TODAY, SENSOR_TOTAL, @@ -50,8 +51,10 @@ from homeassistant.const import ( DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, ) +from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity @@ -95,6 +98,7 @@ SENSOR_DEVICE_CLASS_ICON_MAP = { SENSOR_PRESSUREATSEALEVEL: {DEVICE_CLASS: DEVICE_CLASS_PRESSURE}, SENSOR_PROXIMITY: {ICON: "mdi:ruler"}, SENSOR_REACTIVE_POWERUSAGE: {DEVICE_CLASS: DEVICE_CLASS_POWER}, + SENSOR_STATUS_SIGNAL: {DEVICE_CLASS: DEVICE_CLASS_SIGNAL_STRENGTH}, SENSOR_TEMPERATURE: {DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE}, SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_POWER}, SENSOR_TOTAL: {DEVICE_CLASS: DEVICE_CLASS_POWER}, @@ -131,13 +135,19 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity): def __init__(self, **kwds): """Initialize the Tasmota sensor.""" - self._state = False + self._state = None super().__init__( discovery_update=self.discovery_update, **kwds, ) + @callback + def state_updated(self, state, **kwargs): + """Handle state updates.""" + self._state = state + self.async_write_ha_state() + @property def device_class(self) -> Optional[str]: """Return the device class of the sensor.""" @@ -146,6 +156,14 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity): ) return class_or_icon.get(DEVICE_CLASS) + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + # Hide status sensors to not overwhelm users + if self._tasmota_entity.quantity == SENSOR_STATUS_SIGNAL: + return False + return True + @property def icon(self): """Return the icon.""" diff --git a/requirements_all.txt b/requirements_all.txt index 34f96cd09ea..e06d09e920d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -729,7 +729,7 @@ hass-nabucasa==0.37.1 hass_splunk==0.1.1 # homeassistant.components.tasmota -hatasmota==0.0.18 +hatasmota==0.0.19 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e53e36fd3d6..c02bb72fe86 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -364,7 +364,7 @@ hangups==0.4.11 hass-nabucasa==0.37.1 # homeassistant.components.tasmota -hatasmota==0.0.18 +hatasmota==0.0.19 # homeassistant.components.jewish_calendar hdate==0.9.5 diff --git a/tests/components/tasmota/conftest.py b/tests/components/tasmota/conftest.py index 3f58d651b04..456d157547d 100644 --- a/tests/components/tasmota/conftest.py +++ b/tests/components/tasmota/conftest.py @@ -1,5 +1,6 @@ """Test fixtures for Tasmota component.""" +from hatasmota.discovery import get_status_sensor_entities import pytest from homeassistant import config_entries @@ -43,6 +44,20 @@ def disable_debounce(): yield +@pytest.fixture +def status_sensor_disabled(): + """Fixture to allow overriding MQTT config.""" + return True + + +@pytest.fixture(autouse=True) +def disable_status_sensor(status_sensor_disabled): + """Disable Tasmota status sensor.""" + wraps = None if status_sensor_disabled else get_status_sensor_entities + with patch("hatasmota.discovery.get_status_sensor_entities", wraps=wraps): + yield + + async def setup_tasmota_helper(hass): """Set up Tasmota.""" hass.config.components.add("tasmota") diff --git a/tests/components/tasmota/test_discovery.py b/tests/components/tasmota/test_discovery.py index 9af22636c53..f337b062839 100644 --- a/tests/components/tasmota/test_discovery.py +++ b/tests/components/tasmota/test_discovery.py @@ -2,8 +2,6 @@ import copy import json -import pytest - from homeassistant.components.tasmota.const import DEFAULT_PREFIX from homeassistant.components.tasmota.discovery import ALREADY_DISCOVERED @@ -174,37 +172,6 @@ async def test_device_update( assert device_entry.sw_version == "v6.6.6" -@pytest.mark.no_fail_on_log_exception -async def test_discovery_broken(hass, mqtt_mock, caplog, device_reg, setup_tasmota): - """Test handling of exception when creating discovered device.""" - config = copy.deepcopy(DEFAULT_CONFIG) - mac = config["mac"] - data = json.dumps(config) - - # Trigger an exception when the entity is added - with patch( - "hatasmota.discovery.get_device_config_helper", - return_value=object(), - ): - async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", data) - await hass.async_block_till_done() - - # Verify device entry is not created - device_entry = device_reg.async_get_device(set(), {("mac", mac)}) - assert device_entry is None - assert ( - "Exception in async_discover_device when dispatching 'tasmota_discovery_device'" - in caplog.text - ) - - async_fire_mqtt_message(hass, f"{DEFAULT_PREFIX}/{mac}/config", data) - await hass.async_block_till_done() - - # Verify device entry is created - device_entry = device_reg.async_get_device(set(), {("mac", mac)}) - assert device_entry is not None - - async def test_device_remove( hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota ): diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index 40dcbe69201..859c0f735ee 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -7,6 +7,7 @@ from hatasmota.utils import ( get_topic_tele_sensor, get_topic_tele_will, ) +import pytest from homeassistant.components import sensor from homeassistant.components.tasmota.const import DEFAULT_PREFIX @@ -217,6 +218,57 @@ async def test_indexed_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): assert state.state == "7.8" +@pytest.mark.parametrize("status_sensor_disabled", [False]) +async def test_status_sensor_state_via_mqtt(hass, mqtt_mock, setup_tasmota): + """Test state update via MQTT.""" + entity_reg = await hass.helpers.entity_registry.async_get_registry() + + # Pre-enable the status sensor + entity_reg.async_get_or_create( + sensor.DOMAIN, + "tasmota", + "00000049A3BC_status_sensor_status_sensor_status_signal", + suggested_object_id="tasmota_status", + disabled_by=None, + ) + + config = copy.deepcopy(DEFAULT_CONFIG) + mac = config["mac"] + + async_fire_mqtt_message( + hass, + f"{DEFAULT_PREFIX}/{mac}/config", + json.dumps(config), + ) + await hass.async_block_till_done() + await hass.async_block_till_done() + + state = hass.states.get("sensor.tasmota_status") + 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_status") + assert state.state == STATE_UNKNOWN + assert not state.attributes.get(ATTR_ASSUMED_STATE) + + # Test pushed state update + async_fire_mqtt_message( + hass, "tasmota_49A3BC/tele/STATE", '{"Wifi":{"Signal":20.5}}' + ) + state = hass.states.get("sensor.tasmota_status") + assert state.state == "20.5" + + # Test polled state update + async_fire_mqtt_message( + hass, + "tasmota_49A3BC/stat/STATUS11", + '{"StatusSTS":{"Wifi":{"Signal":20.0}}}', + ) + state = hass.states.get("sensor.tasmota_status") + assert state.state == "20.0" + + async def test_attributes(hass, mqtt_mock, setup_tasmota): """Test correct attributes for sensors.""" config = copy.deepcopy(DEFAULT_CONFIG)