mirror of
https://github.com/home-assistant/core.git
synced 2025-07-15 01:07:10 +00:00
Add gui config option consider device unavailable (#51218)
* Add gui config option consider device unavailable * Update tests
This commit is contained in:
parent
d1f0ec8db8
commit
ceadb0cba0
@ -137,10 +137,23 @@ CONF_RADIO_TYPE = "radio_type"
|
|||||||
CONF_USB_PATH = "usb_path"
|
CONF_USB_PATH = "usb_path"
|
||||||
CONF_ZIGPY = "zigpy_config"
|
CONF_ZIGPY = "zigpy_config"
|
||||||
|
|
||||||
|
CONF_CONSIDER_UNAVAILABLE_MAINS = "consider_unavailable_mains"
|
||||||
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS = 60 * 60 * 2 # 2 hours
|
||||||
|
CONF_CONSIDER_UNAVAILABLE_BATTERY = "consider_unavailable_battery"
|
||||||
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY = 60 * 60 * 6 # 6 hours
|
||||||
|
|
||||||
CONF_ZHA_OPTIONS_SCHEMA = vol.Schema(
|
CONF_ZHA_OPTIONS_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_DEFAULT_LIGHT_TRANSITION): cv.positive_int,
|
vol.Optional(CONF_DEFAULT_LIGHT_TRANSITION): cv.positive_int,
|
||||||
vol.Required(CONF_ENABLE_IDENTIFY_ON_JOIN, default=True): cv.boolean,
|
vol.Required(CONF_ENABLE_IDENTIFY_ON_JOIN, default=True): cv.boolean,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_CONSIDER_UNAVAILABLE_MAINS,
|
||||||
|
default=CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS,
|
||||||
|
): cv.positive_int,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
|
default=CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
|
): cv.positive_int,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -55,6 +55,10 @@ from .const import (
|
|||||||
CLUSTER_COMMANDS_SERVER,
|
CLUSTER_COMMANDS_SERVER,
|
||||||
CLUSTER_TYPE_IN,
|
CLUSTER_TYPE_IN,
|
||||||
CLUSTER_TYPE_OUT,
|
CLUSTER_TYPE_OUT,
|
||||||
|
CONF_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
|
CONF_CONSIDER_UNAVAILABLE_MAINS,
|
||||||
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS,
|
||||||
CONF_ENABLE_IDENTIFY_ON_JOIN,
|
CONF_ENABLE_IDENTIFY_ON_JOIN,
|
||||||
EFFECT_DEFAULT_VARIANT,
|
EFFECT_DEFAULT_VARIANT,
|
||||||
EFFECT_OKAY,
|
EFFECT_OKAY,
|
||||||
@ -70,8 +74,6 @@ from .const import (
|
|||||||
from .helpers import LogMixin, async_get_zha_config_value
|
from .helpers import LogMixin, async_get_zha_config_value
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
CONSIDER_UNAVAILABLE_MAINS = 60 * 60 * 2 # 2 hours
|
|
||||||
CONSIDER_UNAVAILABLE_BATTERY = 60 * 60 * 6 # 6 hours
|
|
||||||
_UPDATE_ALIVE_INTERVAL = (60, 90)
|
_UPDATE_ALIVE_INTERVAL = (60, 90)
|
||||||
_CHECKIN_GRACE_PERIODS = 2
|
_CHECKIN_GRACE_PERIODS = 2
|
||||||
|
|
||||||
@ -107,9 +109,20 @@ class ZHADevice(LogMixin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if self.is_mains_powered:
|
if self.is_mains_powered:
|
||||||
self._consider_unavailable_time = CONSIDER_UNAVAILABLE_MAINS
|
self.consider_unavailable_time = async_get_zha_config_value(
|
||||||
|
self._zha_gateway.config_entry,
|
||||||
|
ZHA_OPTIONS,
|
||||||
|
CONF_CONSIDER_UNAVAILABLE_MAINS,
|
||||||
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self._consider_unavailable_time = CONSIDER_UNAVAILABLE_BATTERY
|
self.consider_unavailable_time = async_get_zha_config_value(
|
||||||
|
self._zha_gateway.config_entry,
|
||||||
|
ZHA_OPTIONS,
|
||||||
|
CONF_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
|
)
|
||||||
|
|
||||||
keep_alive_interval = random.randint(*_UPDATE_ALIVE_INTERVAL)
|
keep_alive_interval = random.randint(*_UPDATE_ALIVE_INTERVAL)
|
||||||
self.unsubs.append(
|
self.unsubs.append(
|
||||||
async_track_time_interval(
|
async_track_time_interval(
|
||||||
@ -320,7 +333,7 @@ class ZHADevice(LogMixin):
|
|||||||
return
|
return
|
||||||
|
|
||||||
difference = time.time() - self.last_seen
|
difference = time.time() - self.last_seen
|
||||||
if difference < self._consider_unavailable_time:
|
if difference < self.consider_unavailable_time:
|
||||||
self.update_available(True)
|
self.update_available(True)
|
||||||
self._checkins_missed_count = 0
|
self._checkins_missed_count = 0
|
||||||
return
|
return
|
||||||
|
@ -77,12 +77,7 @@ from .const import (
|
|||||||
ZHA_GW_MSG_RAW_INIT,
|
ZHA_GW_MSG_RAW_INIT,
|
||||||
RadioType,
|
RadioType,
|
||||||
)
|
)
|
||||||
from .device import (
|
from .device import DeviceStatus, ZHADevice
|
||||||
CONSIDER_UNAVAILABLE_BATTERY,
|
|
||||||
CONSIDER_UNAVAILABLE_MAINS,
|
|
||||||
DeviceStatus,
|
|
||||||
ZHADevice,
|
|
||||||
)
|
|
||||||
from .group import GroupMember, ZHAGroup
|
from .group import GroupMember, ZHAGroup
|
||||||
from .registries import GROUP_ENTITY_DOMAINS
|
from .registries import GROUP_ENTITY_DOMAINS
|
||||||
from .store import async_get_registry
|
from .store import async_get_registry
|
||||||
@ -185,17 +180,15 @@ class ZHAGateway:
|
|||||||
delta_msg = "not known"
|
delta_msg = "not known"
|
||||||
if zha_dev_entry and zha_dev_entry.last_seen is not None:
|
if zha_dev_entry and zha_dev_entry.last_seen is not None:
|
||||||
delta = round(time.time() - zha_dev_entry.last_seen)
|
delta = round(time.time() - zha_dev_entry.last_seen)
|
||||||
if zha_device.is_mains_powered:
|
zha_device.available = delta < zha_device.consider_unavailable_time
|
||||||
zha_device.available = delta < CONSIDER_UNAVAILABLE_MAINS
|
|
||||||
else:
|
|
||||||
zha_device.available = delta < CONSIDER_UNAVAILABLE_BATTERY
|
|
||||||
delta_msg = f"{str(timedelta(seconds=delta))} ago"
|
delta_msg = f"{str(timedelta(seconds=delta))} ago"
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"[%s](%s) restored as '%s', last seen: %s",
|
"[%s](%s) restored as '%s', last seen: %s, consider_unavailable_time: %s seconds",
|
||||||
zha_device.nwk,
|
zha_device.nwk,
|
||||||
zha_device.name,
|
zha_device.name,
|
||||||
"available" if zha_device.available else "unavailable",
|
"available" if zha_device.available else "unavailable",
|
||||||
delta_msg,
|
delta_msg,
|
||||||
|
zha_device.consider_unavailable_time,
|
||||||
)
|
)
|
||||||
# update the last seen time for devices every 10 minutes to avoid thrashing
|
# update the last seen time for devices every 10 minutes to avoid thrashing
|
||||||
# writes and shutdown issues where storage isn't updated
|
# writes and shutdown issues where storage isn't updated
|
||||||
|
@ -33,7 +33,9 @@
|
|||||||
"zha_options": {
|
"zha_options": {
|
||||||
"title": "Global Options",
|
"title": "Global Options",
|
||||||
"enable_identify_on_join": "Enable identify effect when devices join the network",
|
"enable_identify_on_join": "Enable identify effect when devices join the network",
|
||||||
"default_light_transition": "Default light transition time (seconds)"
|
"default_light_transition": "Default light transition time (seconds)",
|
||||||
|
"consider_unavailable_mains": "Consider mains powered devices unavailable after (seconds)",
|
||||||
|
"consider_unavailable_battery": "Consider battery powered devices unavailable after (seconds)"
|
||||||
},
|
},
|
||||||
"zha_alarm_options": {
|
"zha_alarm_options": {
|
||||||
"title": "Alarm Control Panel Options",
|
"title": "Alarm Control Panel Options",
|
||||||
|
@ -43,6 +43,8 @@
|
|||||||
"zha_options": {
|
"zha_options": {
|
||||||
"default_light_transition": "Default light transition time (seconds)",
|
"default_light_transition": "Default light transition time (seconds)",
|
||||||
"enable_identify_on_join": "Enable identify effect when devices join the network",
|
"enable_identify_on_join": "Enable identify effect when devices join the network",
|
||||||
|
"consider_unavailable_mains": "Consider mains powered devices unavailable after (seconds)",
|
||||||
|
"consider_unavailable_battery": "Consider battery powered devices unavailable after (seconds)",
|
||||||
"title": "Global Options"
|
"title": "Global Options"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -8,7 +8,10 @@ import pytest
|
|||||||
import zigpy.profiles.zha
|
import zigpy.profiles.zha
|
||||||
import zigpy.zcl.clusters.general as general
|
import zigpy.zcl.clusters.general as general
|
||||||
|
|
||||||
import homeassistant.components.zha.core.device as zha_core_device
|
from homeassistant.components.zha.core.const import (
|
||||||
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY,
|
||||||
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS,
|
||||||
|
)
|
||||||
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
|
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
|
||||||
import homeassistant.helpers.device_registry as dr
|
import homeassistant.helpers.device_registry as dr
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -117,13 +120,13 @@ async def test_check_available_success(
|
|||||||
basic_ch.read_attributes.reset_mock()
|
basic_ch.read_attributes.reset_mock()
|
||||||
device_with_basic_channel.last_seen = None
|
device_with_basic_channel.last_seen = None
|
||||||
assert zha_device.available is True
|
assert zha_device.available is True
|
||||||
_send_time_changed(hass, zha_core_device.CONSIDER_UNAVAILABLE_MAINS + 2)
|
_send_time_changed(hass, zha_device.consider_unavailable_time + 2)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert zha_device.available is False
|
assert zha_device.available is False
|
||||||
assert basic_ch.read_attributes.await_count == 0
|
assert basic_ch.read_attributes.await_count == 0
|
||||||
|
|
||||||
device_with_basic_channel.last_seen = (
|
device_with_basic_channel.last_seen = (
|
||||||
time.time() - zha_core_device.CONSIDER_UNAVAILABLE_MAINS - 2
|
time.time() - zha_device.consider_unavailable_time - 2
|
||||||
)
|
)
|
||||||
_seens = [time.time(), device_with_basic_channel.last_seen]
|
_seens = [time.time(), device_with_basic_channel.last_seen]
|
||||||
|
|
||||||
@ -172,7 +175,7 @@ async def test_check_available_unsuccessful(
|
|||||||
assert basic_ch.read_attributes.await_count == 0
|
assert basic_ch.read_attributes.await_count == 0
|
||||||
|
|
||||||
device_with_basic_channel.last_seen = (
|
device_with_basic_channel.last_seen = (
|
||||||
time.time() - zha_core_device.CONSIDER_UNAVAILABLE_MAINS - 2
|
time.time() - zha_device.consider_unavailable_time - 2
|
||||||
)
|
)
|
||||||
|
|
||||||
# unsuccessfuly ping zigpy device, but zha_device is still available
|
# unsuccessfuly ping zigpy device, but zha_device is still available
|
||||||
@ -213,7 +216,7 @@ async def test_check_available_no_basic_channel(
|
|||||||
assert zha_device.available is True
|
assert zha_device.available is True
|
||||||
|
|
||||||
device_without_basic_channel.last_seen = (
|
device_without_basic_channel.last_seen = (
|
||||||
time.time() - zha_core_device.CONSIDER_UNAVAILABLE_BATTERY - 2
|
time.time() - zha_device.consider_unavailable_time - 2
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "does not have a mandatory basic cluster" not in caplog.text
|
assert "does not have a mandatory basic cluster" not in caplog.text
|
||||||
@ -246,38 +249,38 @@ async def test_ota_sw_version(hass, ota_zha_device):
|
|||||||
("zigpy_device", 0, True),
|
("zigpy_device", 0, True),
|
||||||
(
|
(
|
||||||
"zigpy_device",
|
"zigpy_device",
|
||||||
zha_core_device.CONSIDER_UNAVAILABLE_MAINS + 2,
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS + 2,
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"zigpy_device",
|
"zigpy_device",
|
||||||
zha_core_device.CONSIDER_UNAVAILABLE_BATTERY - 2,
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY - 2,
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"zigpy_device",
|
"zigpy_device",
|
||||||
zha_core_device.CONSIDER_UNAVAILABLE_BATTERY + 2,
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY + 2,
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
("zigpy_device_mains", 0, True),
|
("zigpy_device_mains", 0, True),
|
||||||
(
|
(
|
||||||
"zigpy_device_mains",
|
"zigpy_device_mains",
|
||||||
zha_core_device.CONSIDER_UNAVAILABLE_MAINS - 2,
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS - 2,
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"zigpy_device_mains",
|
"zigpy_device_mains",
|
||||||
zha_core_device.CONSIDER_UNAVAILABLE_MAINS + 2,
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_MAINS + 2,
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"zigpy_device_mains",
|
"zigpy_device_mains",
|
||||||
zha_core_device.CONSIDER_UNAVAILABLE_BATTERY - 2,
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY - 2,
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"zigpy_device_mains",
|
"zigpy_device_mains",
|
||||||
zha_core_device.CONSIDER_UNAVAILABLE_BATTERY + 2,
|
CONF_DEFAULT_CONSIDER_UNAVAILABLE_BATTERY + 2,
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -7,7 +7,6 @@ import zigpy.profiles.zha
|
|||||||
import zigpy.zcl.clusters.general as general
|
import zigpy.zcl.clusters.general as general
|
||||||
|
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
import homeassistant.components.zha.core.device as zha_core_device
|
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
@ -252,9 +251,7 @@ async def test_device_offline_fires(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert zha_device.available is True
|
assert zha_device.available is True
|
||||||
|
|
||||||
zigpy_device.last_seen = (
|
zigpy_device.last_seen = time.time() - zha_device.consider_unavailable_time - 2
|
||||||
time.time() - zha_core_device.CONSIDER_UNAVAILABLE_BATTERY - 2
|
|
||||||
)
|
|
||||||
|
|
||||||
# there are 3 checkins to perform before marking the device unavailable
|
# there are 3 checkins to perform before marking the device unavailable
|
||||||
future = dt_util.utcnow() + timedelta(seconds=90)
|
future = dt_util.utcnow() + timedelta(seconds=90)
|
||||||
@ -266,7 +263,7 @@ async def test_device_offline_fires(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
future = dt_util.utcnow() + timedelta(
|
future = dt_util.utcnow() + timedelta(
|
||||||
seconds=zha_core_device.CONSIDER_UNAVAILABLE_BATTERY + 100
|
seconds=zha_device.consider_unavailable_time + 100
|
||||||
)
|
)
|
||||||
async_fire_time_changed(hass, future)
|
async_fire_time_changed(hass, future)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user