Skip to content
Snippets Groups Projects
Unverified Commit 8423d18d authored by Christian Ferbar's avatar Christian Ferbar Committed by GitHub
Browse files

Add Miflora go_unavailable_timeout (#31156)

* Clear state on exception

Clear state if querying the device fails. The state is then set to unknown, so it can be tracked if a miflora device isn't responding any more.

* Add available()

Signal valid data via available()

* miflora: add timeout to go unavailable

* code cleanup

* miflora: tox cleanup
parent 9d87c1ab
No related branches found
No related tags found
No related merge requests found
"""Support for Xiaomi Mi Flora BLE plant sensor."""
from datetime import timedelta
import logging
......@@ -20,6 +21,7 @@ from homeassistant.const import (
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
import homeassistant.util.dt as dt_util
try:
import bluepy.btle # noqa: F401 pylint: disable=unused-import
......@@ -32,14 +34,18 @@ _LOGGER = logging.getLogger(__name__)
CONF_ADAPTER = "adapter"
CONF_MEDIAN = "median"
CONF_GO_UNAVAILABLE_TIMEOUT = "go_unavailable_timeout"
DEFAULT_ADAPTER = "hci0"
DEFAULT_FORCE_UPDATE = False
DEFAULT_MEDIAN = 3
DEFAULT_NAME = "Mi Flora"
DEFAULT_GO_UNAVAILABLE_TIMEOUT = timedelta(seconds=7200)
SCAN_INTERVAL = timedelta(seconds=1200)
ATTR_LAST_SUCCESSFUL_UPDATE = "last_successful_update"
# Sensor types are defined like: Name, units, icon
SENSOR_TYPES = {
"temperature": ["Temperature", "°C", "mdi:thermometer"],
......@@ -59,6 +65,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
vol.Optional(CONF_MEDIAN, default=DEFAULT_MEDIAN): cv.positive_int,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
vol.Optional(CONF_ADAPTER, default=DEFAULT_ADAPTER): cv.string,
vol.Optional(
CONF_GO_UNAVAILABLE_TIMEOUT, default=DEFAULT_GO_UNAVAILABLE_TIMEOUT
): cv.time_period,
}
)
......@@ -78,6 +87,8 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
force_update = config.get(CONF_FORCE_UPDATE)
median = config.get(CONF_MEDIAN)
go_unavailable_timeout = config.get(CONF_GO_UNAVAILABLE_TIMEOUT)
devs = []
for parameter in config[CONF_MONITORED_CONDITIONS]:
......@@ -90,7 +101,16 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
name = f"{prefix} {name}"
devs.append(
MiFloraSensor(poller, parameter, name, unit, icon, force_update, median)
MiFloraSensor(
poller,
parameter,
name,
unit,
icon,
force_update,
median,
go_unavailable_timeout,
)
)
async_add_entities(devs)
......@@ -99,7 +119,17 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
class MiFloraSensor(Entity):
"""Implementing the MiFlora sensor."""
def __init__(self, poller, parameter, name, unit, icon, force_update, median):
def __init__(
self,
poller,
parameter,
name,
unit,
icon,
force_update,
median,
go_unavailable_timeout,
):
"""Initialize the sensor."""
self.poller = poller
self.parameter = parameter
......@@ -107,9 +137,10 @@ class MiFloraSensor(Entity):
self._icon = icon
self._name = name
self._state = None
self._available = False
self.data = []
self._force_update = force_update
self.go_unavailable_timeout = go_unavailable_timeout
self.last_successful_update = dt_util.utc_from_timestamp(0)
# Median is used to filter out outliers. median of 3 will filter
# single outliers, while median of 5 will filter double outliers
# Use median_count = 1 if no filtering is required.
......@@ -136,8 +167,16 @@ class MiFloraSensor(Entity):
@property
def available(self):
"""Return True if entity is available."""
return self._available
"""Return True if did update since 2h."""
return self.last_successful_update > (
dt_util.utcnow() - self.go_unavailable_timeout
)
@property
def device_state_attributes(self):
"""Return the state attributes of the device."""
attr = {ATTR_LAST_SUCCESSFUL_UPDATE: self.last_successful_update}
return attr
@property
def unit_of_measurement(self):
......@@ -165,13 +204,12 @@ class MiFloraSensor(Entity):
data = self.poller.parameter_value(self.parameter)
except (OSError, BluetoothBackendException) as err:
_LOGGER.info("Polling error %s: %s", type(err).__name__, err)
self._available = False
return
if data is not None:
_LOGGER.debug("%s = %s", self.name, data)
self._available = True
self.data.append(data)
self.last_successful_update = dt_util.utcnow()
else:
_LOGGER.info("Did not receive any data from Mi Flora sensor %s", self.name)
# Remove old data from median list or set sensor value to None
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment