Checking Xiaomi Aqara devices unavailability states (#11631)
* added unavailability tracker, updated sensor component * change hass argument position according to position in binary_sensor * added hass argument to binary_sensor, updated is_on(), it can be UNAVAILABLE now * updated switch component to support unavailability feature * updated light component to support unavailability feature * updated cover component to support unavailability feature * set _hass property * added unavailability tracker, updated sensor component * change hass argument position according to position in binary_sensor * added hass argument to binary_sensor, updated is_on(), it can be UNAVAILABLE now * updated switch component to support unavailability feature * updated light component to support unavailability feature * updated cover component to support unavailability feature * set _hass property * fixed error with wrong arguments number during callback call * reset unavailability state on new message received from device * use locks to fix race condition during managing _state property * overriden state() method for some components to check for STATE_UNAVAILABLE and return it instead e.g. STATE_OFF * fixed linter * removed blank line * use available() method instead of changing _state * filter motion sensors 'heartbeat', was removed from PyXiaomiGateway * remove self._hass, use self.hass set by HA on attach * self.push_data now running in the event loop, use async_schedule_update_ha_state() * merge fix * removed accidentally added home-assistant-polymer * bump PyXiaomiGateway version to 0.8.0 * bump PyXiaomiGateway to 0.8.0 * updated methods names and annotations
This commit is contained in:
parent
95592d9283
commit
3417c6ad8d
7 changed files with 72 additions and 21 deletions
|
@ -101,7 +101,7 @@ class XiaomiNatgasSensor(XiaomiBinarySensor):
|
||||||
attrs.update(super().device_state_attributes)
|
attrs.update(super().device_state_attributes)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
if DENSITY in data:
|
if DENSITY in data:
|
||||||
self._density = int(data.get(DENSITY))
|
self._density = int(data.get(DENSITY))
|
||||||
|
@ -139,8 +139,16 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||||
attrs.update(super().device_state_attributes)
|
attrs.update(super().device_state_attributes)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
|
if raw_data['cmd'] == 'heartbeat':
|
||||||
|
_LOGGER.debug(
|
||||||
|
'Skipping heartbeat of the motion sensor. '
|
||||||
|
'It can introduce an incorrect state because of a firmware '
|
||||||
|
'bug (https://github.com/home-assistant/home-assistant/pull/'
|
||||||
|
'11631#issuecomment-357507744).')
|
||||||
|
return
|
||||||
|
|
||||||
self._should_poll = False
|
self._should_poll = False
|
||||||
if NO_MOTION in data: # handle push from the hub
|
if NO_MOTION in data: # handle push from the hub
|
||||||
self._no_motion_since = data[NO_MOTION]
|
self._no_motion_since = data[NO_MOTION]
|
||||||
|
@ -186,7 +194,7 @@ class XiaomiDoorSensor(XiaomiBinarySensor):
|
||||||
attrs.update(super().device_state_attributes)
|
attrs.update(super().device_state_attributes)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
self._should_poll = False
|
self._should_poll = False
|
||||||
if NO_CLOSE in data: # handle push from the hub
|
if NO_CLOSE in data: # handle push from the hub
|
||||||
|
@ -219,7 +227,7 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor):
|
||||||
XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor',
|
XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor',
|
||||||
xiaomi_hub, 'status', 'moisture')
|
xiaomi_hub, 'status', 'moisture')
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
self._should_poll = False
|
self._should_poll = False
|
||||||
|
|
||||||
|
@ -256,7 +264,7 @@ class XiaomiSmokeSensor(XiaomiBinarySensor):
|
||||||
attrs.update(super().device_state_attributes)
|
attrs.update(super().device_state_attributes)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
if DENSITY in data:
|
if DENSITY in data:
|
||||||
self._density = int(data.get(DENSITY))
|
self._density = int(data.get(DENSITY))
|
||||||
|
@ -293,7 +301,7 @@ class XiaomiButton(XiaomiBinarySensor):
|
||||||
attrs.update(super().device_state_attributes)
|
attrs.update(super().device_state_attributes)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
value = data.get(self._data_key)
|
value = data.get(self._data_key)
|
||||||
if value is None:
|
if value is None:
|
||||||
|
@ -343,7 +351,7 @@ class XiaomiCube(XiaomiBinarySensor):
|
||||||
attrs.update(super().device_state_attributes)
|
attrs.update(super().device_state_attributes)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
if 'status' in data:
|
if 'status' in data:
|
||||||
self._hass.bus.fire('cube_action', {
|
self._hass.bus.fire('cube_action', {
|
||||||
|
|
|
@ -59,7 +59,7 @@ class XiaomiGenericCover(XiaomiDevice, CoverDevice):
|
||||||
"""Move the cover to a specific position."""
|
"""Move the cover to a specific position."""
|
||||||
self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)})
|
self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)})
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
if ATTR_CURTAIN_LEVEL in data:
|
if ATTR_CURTAIN_LEVEL in data:
|
||||||
self._pos = int(data[ATTR_CURTAIN_LEVEL])
|
self._pos = int(data[ATTR_CURTAIN_LEVEL])
|
||||||
|
|
|
@ -39,7 +39,7 @@ class XiaomiGatewayLight(XiaomiDevice, Light):
|
||||||
"""Return true if it is on."""
|
"""Return true if it is on."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
value = data.get(self._data_key)
|
value = data.get(self._data_key)
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|
|
@ -61,7 +61,7 @@ class XiaomiSensor(XiaomiDevice):
|
||||||
"""Return the state of the sensor."""
|
"""Return the state of the sensor."""
|
||||||
return self._state
|
return self._state
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
value = data.get(self._data_key)
|
value = data.get(self._data_key)
|
||||||
if value is None:
|
if value is None:
|
||||||
|
|
|
@ -107,7 +107,7 @@ class XiaomiGenericSwitch(XiaomiDevice, SwitchDevice):
|
||||||
self._state = False
|
self._state = False
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
if IN_USE in data:
|
if IN_USE in data:
|
||||||
self._in_use = int(data[IN_USE])
|
self._in_use = int(data[IN_USE])
|
||||||
|
|
|
@ -7,17 +7,22 @@ https://home-assistant.io/components/xiaomi_aqara/
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components.discovery import SERVICE_XIAOMI_GW
|
from homeassistant.components.discovery import SERVICE_XIAOMI_GW
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_BATTERY_LEVEL, CONF_HOST, CONF_MAC, CONF_PORT,
|
ATTR_BATTERY_LEVEL, CONF_HOST, CONF_MAC, CONF_PORT,
|
||||||
EVENT_HOMEASSISTANT_STOP)
|
EVENT_HOMEASSISTANT_STOP)
|
||||||
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.helpers import discovery
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||||
|
from homeassistant.util.dt import utcnow
|
||||||
|
|
||||||
REQUIREMENTS = ['PyXiaomiGateway==0.7.1']
|
REQUIREMENTS = ['PyXiaomiGateway==0.8.0']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -33,7 +38,9 @@ CONF_KEY = 'key'
|
||||||
|
|
||||||
DOMAIN = 'xiaomi_aqara'
|
DOMAIN = 'xiaomi_aqara'
|
||||||
|
|
||||||
PY_XIAOMI_GATEWAY = 'xiaomi_gw'
|
PY_XIAOMI_GATEWAY = "xiaomi_gw"
|
||||||
|
|
||||||
|
TIME_TILL_UNAVAILABLE = timedelta(minutes=150)
|
||||||
|
|
||||||
SERVICE_PLAY_RINGTONE = 'play_ringtone'
|
SERVICE_PLAY_RINGTONE = 'play_ringtone'
|
||||||
SERVICE_STOP_RINGTONE = 'stop_ringtone'
|
SERVICE_STOP_RINGTONE = 'stop_ringtone'
|
||||||
|
@ -201,20 +208,35 @@ class XiaomiDevice(Entity):
|
||||||
def __init__(self, device, name, xiaomi_hub):
|
def __init__(self, device, name, xiaomi_hub):
|
||||||
"""Initialize the Xiaomi device."""
|
"""Initialize the Xiaomi device."""
|
||||||
self._state = None
|
self._state = None
|
||||||
|
self._is_available = True
|
||||||
self._sid = device['sid']
|
self._sid = device['sid']
|
||||||
self._name = '{}_{}'.format(name, self._sid)
|
self._name = '{}_{}'.format(name, self._sid)
|
||||||
self._write_to_hub = xiaomi_hub.write_to_hub
|
self._write_to_hub = xiaomi_hub.write_to_hub
|
||||||
self._get_from_hub = xiaomi_hub.get_from_hub
|
self._get_from_hub = xiaomi_hub.get_from_hub
|
||||||
self._device_state_attributes = {}
|
self._device_state_attributes = {}
|
||||||
xiaomi_hub.callbacks[self._sid].append(self.push_data)
|
self._remove_unavailability_tracker = None
|
||||||
self.parse_data(device['data'])
|
xiaomi_hub.callbacks[self._sid].append(self._add_push_data_job)
|
||||||
|
self.parse_data(device['data'], device['raw_data'])
|
||||||
self.parse_voltage(device['data'])
|
self.parse_voltage(device['data'])
|
||||||
|
|
||||||
|
def _add_push_data_job(self, *args):
|
||||||
|
self.hass.async_add_job(self.push_data, *args)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_added_to_hass(self):
|
||||||
|
"""Start unavailability tracking."""
|
||||||
|
self._async_track_unavailable()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return self._is_available
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
"""Return the polling state. No polling needed."""
|
"""Return the polling state. No polling needed."""
|
||||||
|
@ -225,13 +247,34 @@ class XiaomiDevice(Entity):
|
||||||
"""Return the state attributes."""
|
"""Return the state attributes."""
|
||||||
return self._device_state_attributes
|
return self._device_state_attributes
|
||||||
|
|
||||||
def push_data(self, data):
|
@callback
|
||||||
|
def _async_set_unavailable(self, now):
|
||||||
|
"""Set state to UNAVAILABLE."""
|
||||||
|
self._remove_unavailability_tracker = None
|
||||||
|
self._is_available = False
|
||||||
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_track_unavailable(self):
|
||||||
|
if self._remove_unavailability_tracker:
|
||||||
|
self._remove_unavailability_tracker()
|
||||||
|
self._remove_unavailability_tracker = async_track_point_in_utc_time(
|
||||||
|
self.hass, self._async_set_unavailable,
|
||||||
|
utcnow() + TIME_TILL_UNAVAILABLE)
|
||||||
|
if not self._is_available:
|
||||||
|
self._is_available = True
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def push_data(self, data, raw_data):
|
||||||
"""Push from Hub."""
|
"""Push from Hub."""
|
||||||
_LOGGER.debug("PUSH >> %s: %s", self, data)
|
_LOGGER.debug("PUSH >> %s: %s", self, data)
|
||||||
is_data = self.parse_data(data)
|
was_unavailable = self._async_track_unavailable()
|
||||||
|
is_data = self.parse_data(data, raw_data)
|
||||||
is_voltage = self.parse_voltage(data)
|
is_voltage = self.parse_voltage(data)
|
||||||
if is_data or is_voltage:
|
if is_data or is_voltage or was_unavailable:
|
||||||
self.schedule_update_ha_state()
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
def parse_voltage(self, data):
|
def parse_voltage(self, data):
|
||||||
"""Parse battery level data sent by gateway."""
|
"""Parse battery level data sent by gateway."""
|
||||||
|
@ -246,7 +289,7 @@ class XiaomiDevice(Entity):
|
||||||
self._device_state_attributes[ATTR_BATTERY_LEVEL] = round(percent, 1)
|
self._device_state_attributes[ATTR_BATTERY_LEVEL] = round(percent, 1)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def parse_data(self, data):
|
def parse_data(self, data, raw_data):
|
||||||
"""Parse data sent by gateway."""
|
"""Parse data sent by gateway."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ PyMVGLive==1.1.4
|
||||||
PyMata==2.14
|
PyMata==2.14
|
||||||
|
|
||||||
# homeassistant.components.xiaomi_aqara
|
# homeassistant.components.xiaomi_aqara
|
||||||
PyXiaomiGateway==0.7.1
|
PyXiaomiGateway==0.8.0
|
||||||
|
|
||||||
# homeassistant.components.rpi_gpio
|
# homeassistant.components.rpi_gpio
|
||||||
# RPi.GPIO==0.6.1
|
# RPi.GPIO==0.6.1
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue