Poll state when Tasmota device becomes available (#41401)
This commit is contained in:
parent
b51a160cce
commit
35287828bc
6 changed files with 163 additions and 4 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
):
|
):
|
||||||
|
|
53
tests/components/tasmota/test_mixins.py
Normal file
53
tests/components/tasmota/test_mixins.py
Normal 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,
|
||||||
|
)
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Reference in a new issue