Poll state when Tasmota device becomes available (#41401)

This commit is contained in:
Erik Montnemery 2020-10-08 11:17:23 +02:00 committed by GitHub
parent b51a160cce
commit 35287828bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 4 deletions

View file

@ -96,6 +96,8 @@ class TasmotaAvailability(TasmotaEntity):
@callback @callback
def availability_updated(self, available: bool) -> None: def availability_updated(self, available: bool) -> None:
"""Handle updated availability.""" """Handle updated availability."""
if available and not self._available:
self._tasmota_entity.poll_status()
self._available = available self._available = available
self.async_write_ha_state() self.async_write_ha_state()
@ -103,13 +105,13 @@ class TasmotaAvailability(TasmotaEntity):
def async_mqtt_connected(self, _): def async_mqtt_connected(self, _):
"""Update state on connection/disconnection to MQTT broker.""" """Update state on connection/disconnection to MQTT broker."""
if not self.hass.is_stopping: if not self.hass.is_stopping:
if not mqtt_connected(self.hass):
self._available = False
self.async_write_ha_state() self.async_write_ha_state()
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return if the device is available.""" """Return if the device is available."""
if not mqtt_connected(self.hass) and not self.hass.is_stopping:
return False
return self._available return self._available

View file

@ -9,6 +9,7 @@ from homeassistant.components.tasmota.const import (
DOMAIN, DOMAIN,
) )
from tests.async_mock import patch
from tests.common import MockConfigEntry, mock_device_registry, mock_registry from tests.common import MockConfigEntry, mock_device_registry, mock_registry
@ -24,6 +25,13 @@ def entity_reg(hass):
return mock_registry(hass) return mock_registry(hass)
@pytest.fixture(autouse=True)
def disable_debounce():
"""Set MQTT debounce timer to zero."""
with patch("hatasmota.mqtt.DEBOUNCE_TIMEOUT", 0):
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")

View file

@ -25,6 +25,7 @@ from .test_common import (
DEFAULT_CONFIG, DEFAULT_CONFIG,
help_test_availability, help_test_availability,
help_test_availability_discovery_update, help_test_availability_discovery_update,
help_test_availability_poll_state,
help_test_availability_when_connection_lost, help_test_availability_when_connection_lost,
help_test_discovery_device_remove, help_test_discovery_device_remove,
help_test_discovery_removal, help_test_discovery_removal,
@ -162,6 +163,18 @@ async def test_availability_discovery_update(hass, mqtt_mock, setup_tasmota):
) )
async def test_availability_poll_state(
hass, mqtt_client_mock, mqtt_mock, setup_tasmota
):
"""Test polling after MQTT connection (re)established."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["swc"][0] = 1
poll_topic = "tasmota_49A3BC/cmnd/STATUS"
await help_test_availability_poll_state(
hass, mqtt_client_mock, mqtt_mock, binary_sensor.DOMAIN, config, poll_topic, "8"
)
async def test_discovery_removal_binary_sensor(hass, mqtt_mock, caplog, setup_tasmota): async def test_discovery_removal_binary_sensor(hass, mqtt_mock, caplog, setup_tasmota):
"""Test removal of discovered binary_sensor.""" """Test removal of discovered binary_sensor."""
config1 = copy.deepcopy(DEFAULT_CONFIG) config1 = copy.deepcopy(DEFAULT_CONFIG)

View file

@ -59,22 +59,27 @@ DEFAULT_CONFIG = {
async def help_test_availability_when_connection_lost( async def help_test_availability_when_connection_lost(
hass, mqtt_client_mock, mqtt_mock, domain, config hass, mqtt_client_mock, mqtt_mock, domain, config
): ):
"""Test availability after MQTT disconnection.""" """Test availability after MQTT disconnection.
This is a test helper for the TasmotaAvailability mixin.
"""
async_fire_mqtt_message( async_fire_mqtt_message(
hass, hass,
f"{DEFAULT_PREFIX}/{config[CONF_MAC]}/config", f"{DEFAULT_PREFIX}/{config[CONF_MAC]}/config",
json.dumps(config), json.dumps(config),
) )
await hass.async_block_till_done() await hass.async_block_till_done()
# Device online
async_fire_mqtt_message( async_fire_mqtt_message(
hass, hass,
get_topic_tele_will(config), get_topic_tele_will(config),
config_get_state_online(config), config_get_state_online(config),
) )
state = hass.states.get(f"{domain}.test") state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE assert state.state != STATE_UNAVAILABLE
# Disconnected from MQTT server -> state changed to unavailable
mqtt_mock.connected = False mqtt_mock.connected = False
await hass.async_add_executor_job(mqtt_client_mock.on_disconnect, None, None, 0) await hass.async_add_executor_job(mqtt_client_mock.on_disconnect, None, None, 0)
await hass.async_block_till_done() await hass.async_block_till_done()
@ -83,12 +88,22 @@ async def help_test_availability_when_connection_lost(
state = hass.states.get(f"{domain}.test") state = hass.states.get(f"{domain}.test")
assert state.state == STATE_UNAVAILABLE assert state.state == STATE_UNAVAILABLE
# Reconnected to MQTT server -> state still unavailable
mqtt_mock.connected = True mqtt_mock.connected = True
await hass.async_add_executor_job(mqtt_client_mock.on_connect, None, None, None, 0) await hass.async_add_executor_job(mqtt_client_mock.on_connect, None, None, None, 0)
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done() await hass.async_block_till_done()
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(f"{domain}.test") state = hass.states.get(f"{domain}.test")
assert state.state == STATE_UNAVAILABLE
# Receive LWT again
async_fire_mqtt_message(
hass,
get_topic_tele_will(config),
config_get_state_online(config),
)
state = hass.states.get(f"{domain}.test")
assert state.state != STATE_UNAVAILABLE assert state.state != STATE_UNAVAILABLE
@ -194,6 +209,61 @@ async def help_test_availability_discovery_update(
assert state.state != STATE_UNAVAILABLE assert state.state != STATE_UNAVAILABLE
async def help_test_availability_poll_state(
hass, mqtt_client_mock, mqtt_mock, domain, config, poll_topic, poll_payload
):
"""Test polling of state when device is available.
This is a test helper for the TasmotaAvailability mixin.
"""
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{config[CONF_MAC]}/config",
json.dumps(config),
)
await hass.async_block_till_done()
mqtt_mock.async_publish.reset_mock()
# Device online, verify poll for state
async_fire_mqtt_message(
hass,
get_topic_tele_will(config),
config_get_state_online(config),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(poll_topic, poll_payload, 0, False)
mqtt_mock.async_publish.reset_mock()
# Disconnected from MQTT server
mqtt_mock.connected = False
await hass.async_add_executor_job(mqtt_client_mock.on_disconnect, None, None, 0)
await hass.async_block_till_done()
await hass.async_block_till_done()
await hass.async_block_till_done()
assert not mqtt_mock.async_publish.called
# Reconnected to MQTT server
mqtt_mock.connected = True
await hass.async_add_executor_job(mqtt_client_mock.on_connect, None, None, None, 0)
await hass.async_block_till_done()
await hass.async_block_till_done()
await hass.async_block_till_done()
assert not mqtt_mock.async_publish.called
# Device online, verify poll for state
async_fire_mqtt_message(
hass,
get_topic_tele_will(config),
config_get_state_online(config),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_called_once_with(poll_topic, poll_payload, 0, False)
async def help_test_discovery_removal( async def help_test_discovery_removal(
hass, mqtt_mock, caplog, domain, config1, config2 hass, mqtt_mock, caplog, domain, config1, config2
): ):

View file

@ -0,0 +1,53 @@
"""The tests for the Tasmota mixins."""
import copy
import json
from hatasmota.const import CONF_MAC
from hatasmota.utils import config_get_state_online, get_topic_tele_will
from homeassistant.components.tasmota.const import DEFAULT_PREFIX
from .test_common import DEFAULT_CONFIG
from tests.async_mock import call
from tests.common import async_fire_mqtt_message
async def test_availability_poll_state_once(
hass, mqtt_client_mock, mqtt_mock, setup_tasmota
):
"""Test several entities send a single message to update state."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["rl"][0] = 1
config["rl"][1] = 1
config["swc"][0] = 1
config["swc"][1] = 1
poll_payload_relay = ""
poll_payload_switch = "8"
poll_topic_relay = "tasmota_49A3BC/cmnd/STATE"
poll_topic_switch = "tasmota_49A3BC/cmnd/STATUS"
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{config[CONF_MAC]}/config",
json.dumps(config),
)
await hass.async_block_till_done()
mqtt_mock.async_publish.reset_mock()
# Device online, verify poll for state
async_fire_mqtt_message(
hass,
get_topic_tele_will(config),
config_get_state_online(config),
)
await hass.async_block_till_done()
await hass.async_block_till_done()
await hass.async_block_till_done()
mqtt_mock.async_publish.assert_has_calls(
[
call(poll_topic_relay, poll_payload_relay, 0, False),
call(poll_topic_switch, poll_payload_switch, 0, False),
],
any_order=True,
)

View file

@ -10,6 +10,7 @@ from .test_common import (
DEFAULT_CONFIG, DEFAULT_CONFIG,
help_test_availability, help_test_availability,
help_test_availability_discovery_update, help_test_availability_discovery_update,
help_test_availability_poll_state,
help_test_availability_when_connection_lost, help_test_availability_when_connection_lost,
help_test_discovery_device_remove, help_test_discovery_device_remove,
help_test_discovery_removal, help_test_discovery_removal,
@ -124,6 +125,18 @@ async def test_availability_discovery_update(hass, mqtt_mock, setup_tasmota):
) )
async def test_availability_poll_state(
hass, mqtt_client_mock, mqtt_mock, setup_tasmota
):
"""Test polling after MQTT connection (re)established."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["rl"][0] = 1
poll_topic = "tasmota_49A3BC/cmnd/STATE"
await help_test_availability_poll_state(
hass, mqtt_client_mock, mqtt_mock, switch.DOMAIN, config, poll_topic, ""
)
async def test_discovery_removal_switch(hass, mqtt_mock, caplog, setup_tasmota): async def test_discovery_removal_switch(hass, mqtt_mock, caplog, setup_tasmota):
"""Test removal of discovered switch.""" """Test removal of discovered switch."""
config1 = copy.deepcopy(DEFAULT_CONFIG) config1 = copy.deepcopy(DEFAULT_CONFIG)