mirror of
https://github.com/home-assistant/core.git
synced 2025-07-17 18:27:09 +00:00
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
This commit is contained in:
parent
4da6c22338
commit
ec7f329807
@ -25,6 +25,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
SUPPORTED_PLATFORMS = [
|
SUPPORTED_PLATFORMS = [
|
||||||
"binary_sensor",
|
"binary_sensor",
|
||||||
"light",
|
"light",
|
||||||
|
"sensor",
|
||||||
"switch",
|
"switch",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Tasmota (beta)",
|
"name": "Tasmota (beta)",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/tasmota",
|
"documentation": "https://www.home-assistant.io/integrations/tasmota",
|
||||||
"requirements": ["hatasmota==0.0.18"],
|
"requirements": ["hatasmota==0.0.19"],
|
||||||
"dependencies": ["mqtt"],
|
"dependencies": ["mqtt"],
|
||||||
"mqtt": ["tasmota/discovery/#"],
|
"mqtt": ["tasmota/discovery/#"],
|
||||||
"codeowners": ["@emontnemery"]
|
"codeowners": ["@emontnemery"]
|
||||||
|
@ -33,6 +33,7 @@ from hatasmota.const import (
|
|||||||
SENSOR_PRESSUREATSEALEVEL,
|
SENSOR_PRESSUREATSEALEVEL,
|
||||||
SENSOR_PROXIMITY,
|
SENSOR_PROXIMITY,
|
||||||
SENSOR_REACTIVE_POWERUSAGE,
|
SENSOR_REACTIVE_POWERUSAGE,
|
||||||
|
SENSOR_STATUS_SIGNAL,
|
||||||
SENSOR_TEMPERATURE,
|
SENSOR_TEMPERATURE,
|
||||||
SENSOR_TODAY,
|
SENSOR_TODAY,
|
||||||
SENSOR_TOTAL,
|
SENSOR_TOTAL,
|
||||||
@ -50,8 +51,10 @@ from homeassistant.const import (
|
|||||||
DEVICE_CLASS_ILLUMINANCE,
|
DEVICE_CLASS_ILLUMINANCE,
|
||||||
DEVICE_CLASS_POWER,
|
DEVICE_CLASS_POWER,
|
||||||
DEVICE_CLASS_PRESSURE,
|
DEVICE_CLASS_PRESSURE,
|
||||||
|
DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
)
|
)
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
@ -95,6 +98,7 @@ SENSOR_DEVICE_CLASS_ICON_MAP = {
|
|||||||
SENSOR_PRESSUREATSEALEVEL: {DEVICE_CLASS: DEVICE_CLASS_PRESSURE},
|
SENSOR_PRESSUREATSEALEVEL: {DEVICE_CLASS: DEVICE_CLASS_PRESSURE},
|
||||||
SENSOR_PROXIMITY: {ICON: "mdi:ruler"},
|
SENSOR_PROXIMITY: {ICON: "mdi:ruler"},
|
||||||
SENSOR_REACTIVE_POWERUSAGE: {DEVICE_CLASS: DEVICE_CLASS_POWER},
|
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_TEMPERATURE: {DEVICE_CLASS: DEVICE_CLASS_TEMPERATURE},
|
||||||
SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_POWER},
|
SENSOR_TODAY: {DEVICE_CLASS: DEVICE_CLASS_POWER},
|
||||||
SENSOR_TOTAL: {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):
|
def __init__(self, **kwds):
|
||||||
"""Initialize the Tasmota sensor."""
|
"""Initialize the Tasmota sensor."""
|
||||||
self._state = False
|
self._state = None
|
||||||
|
|
||||||
super().__init__(
|
super().__init__(
|
||||||
discovery_update=self.discovery_update,
|
discovery_update=self.discovery_update,
|
||||||
**kwds,
|
**kwds,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def state_updated(self, state, **kwargs):
|
||||||
|
"""Handle state updates."""
|
||||||
|
self._state = state
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def device_class(self) -> Optional[str]:
|
def device_class(self) -> Optional[str]:
|
||||||
"""Return the device class of the sensor."""
|
"""Return the device class of the sensor."""
|
||||||
@ -146,6 +156,14 @@ class TasmotaSensor(TasmotaAvailability, TasmotaDiscoveryUpdate, Entity):
|
|||||||
)
|
)
|
||||||
return class_or_icon.get(DEVICE_CLASS)
|
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
|
@property
|
||||||
def icon(self):
|
def icon(self):
|
||||||
"""Return the icon."""
|
"""Return the icon."""
|
||||||
|
@ -729,7 +729,7 @@ hass-nabucasa==0.37.1
|
|||||||
hass_splunk==0.1.1
|
hass_splunk==0.1.1
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
hatasmota==0.0.18
|
hatasmota==0.0.19
|
||||||
|
|
||||||
# homeassistant.components.jewish_calendar
|
# homeassistant.components.jewish_calendar
|
||||||
hdate==0.9.5
|
hdate==0.9.5
|
||||||
|
@ -364,7 +364,7 @@ hangups==0.4.11
|
|||||||
hass-nabucasa==0.37.1
|
hass-nabucasa==0.37.1
|
||||||
|
|
||||||
# homeassistant.components.tasmota
|
# homeassistant.components.tasmota
|
||||||
hatasmota==0.0.18
|
hatasmota==0.0.19
|
||||||
|
|
||||||
# homeassistant.components.jewish_calendar
|
# homeassistant.components.jewish_calendar
|
||||||
hdate==0.9.5
|
hdate==0.9.5
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Test fixtures for Tasmota component."""
|
"""Test fixtures for Tasmota component."""
|
||||||
|
|
||||||
|
from hatasmota.discovery import get_status_sensor_entities
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
@ -43,6 +44,20 @@ def disable_debounce():
|
|||||||
yield
|
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):
|
async def setup_tasmota_helper(hass):
|
||||||
"""Set up Tasmota."""
|
"""Set up Tasmota."""
|
||||||
hass.config.components.add("tasmota")
|
hass.config.components.add("tasmota")
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from homeassistant.components.tasmota.const import DEFAULT_PREFIX
|
from homeassistant.components.tasmota.const import DEFAULT_PREFIX
|
||||||
from homeassistant.components.tasmota.discovery import ALREADY_DISCOVERED
|
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"
|
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(
|
async def test_device_remove(
|
||||||
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
|
hass, mqtt_mock, caplog, device_reg, entity_reg, setup_tasmota
|
||||||
):
|
):
|
||||||
|
@ -7,6 +7,7 @@ from hatasmota.utils import (
|
|||||||
get_topic_tele_sensor,
|
get_topic_tele_sensor,
|
||||||
get_topic_tele_will,
|
get_topic_tele_will,
|
||||||
)
|
)
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import sensor
|
from homeassistant.components import sensor
|
||||||
from homeassistant.components.tasmota.const import DEFAULT_PREFIX
|
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"
|
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):
|
async def test_attributes(hass, mqtt_mock, setup_tasmota):
|
||||||
"""Test correct attributes for sensors."""
|
"""Test correct attributes for sensors."""
|
||||||
config = copy.deepcopy(DEFAULT_CONFIG)
|
config = copy.deepcopy(DEFAULT_CONFIG)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user