Skip to content

Commit

Permalink
Checking Xiaomi Aqara devices unavailability states (home-assistant#1…
Browse files Browse the repository at this point in the history
…1631)

* 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
  • Loading branch information
PaulAnnekov authored and Danielhiversen committed Jan 23, 2018
1 parent 95592d9 commit 3417c6a
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 21 deletions.
22 changes: 15 additions & 7 deletions homeassistant/components/binary_sensor/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def device_state_attributes(self):
attrs.update(super().device_state_attributes)
return attrs

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if DENSITY in data:
self._density = int(data.get(DENSITY))
Expand Down Expand Up @@ -139,8 +139,16 @@ def device_state_attributes(self):
attrs.update(super().device_state_attributes)
return attrs

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""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
if NO_MOTION in data: # handle push from the hub
self._no_motion_since = data[NO_MOTION]
Expand Down Expand Up @@ -186,7 +194,7 @@ def device_state_attributes(self):
attrs.update(super().device_state_attributes)
return attrs

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
self._should_poll = False
if NO_CLOSE in data: # handle push from the hub
Expand Down Expand Up @@ -219,7 +227,7 @@ def __init__(self, device, xiaomi_hub):
XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor',
xiaomi_hub, 'status', 'moisture')

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
self._should_poll = False

Expand Down Expand Up @@ -256,7 +264,7 @@ def device_state_attributes(self):
attrs.update(super().device_state_attributes)
return attrs

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if DENSITY in data:
self._density = int(data.get(DENSITY))
Expand Down Expand Up @@ -293,7 +301,7 @@ def device_state_attributes(self):
attrs.update(super().device_state_attributes)
return attrs

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
value = data.get(self._data_key)
if value is None:
Expand Down Expand Up @@ -343,7 +351,7 @@ def device_state_attributes(self):
attrs.update(super().device_state_attributes)
return attrs

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if 'status' in data:
self._hass.bus.fire('cube_action', {
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/cover/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ def set_cover_position(self, position, **kwargs):
"""Move the cover to a specific 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."""
if ATTR_CURTAIN_LEVEL in data:
self._pos = int(data[ATTR_CURTAIN_LEVEL])
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/light/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def is_on(self):
"""Return true if it is on."""
return self._state

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
value = data.get(self._data_key)
if value is None:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/sensor/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def state(self):
"""Return the state of the sensor."""
return self._state

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
value = data.get(self._data_key)
if value is None:
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/switch/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def turn_off(self):
self._state = False
self.schedule_update_ha_state()

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if IN_USE in data:
self._in_use = int(data[IN_USE])
Expand Down
61 changes: 52 additions & 9 deletions homeassistant/components/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@
import asyncio
import logging

from datetime import timedelta

import voluptuous as vol

from homeassistant.components.discovery import SERVICE_XIAOMI_GW
from homeassistant.const import (
ATTR_BATTERY_LEVEL, CONF_HOST, CONF_MAC, CONF_PORT,
EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
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__)

Expand All @@ -33,7 +38,9 @@

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_STOP_RINGTONE = 'stop_ringtone'
Expand Down Expand Up @@ -201,20 +208,35 @@ class XiaomiDevice(Entity):
def __init__(self, device, name, xiaomi_hub):
"""Initialize the Xiaomi device."""
self._state = None
self._is_available = True
self._sid = device['sid']
self._name = '{}_{}'.format(name, self._sid)
self._write_to_hub = xiaomi_hub.write_to_hub
self._get_from_hub = xiaomi_hub.get_from_hub
self._device_state_attributes = {}
xiaomi_hub.callbacks[self._sid].append(self.push_data)
self.parse_data(device['data'])
self._remove_unavailability_tracker = None
xiaomi_hub.callbacks[self._sid].append(self._add_push_data_job)
self.parse_data(device['data'], device['raw_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
def name(self):
"""Return the name of the device."""
return self._name

@property
def available(self):
"""Return True if entity is available."""
return self._is_available

@property
def should_poll(self):
"""Return the polling state. No polling needed."""
Expand All @@ -225,13 +247,34 @@ def device_state_attributes(self):
"""Return the 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."""
_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)
if is_data or is_voltage:
self.schedule_update_ha_state()
if is_data or is_voltage or was_unavailable:
self.async_schedule_update_ha_state()

def parse_voltage(self, data):
"""Parse battery level data sent by gateway."""
Expand All @@ -246,7 +289,7 @@ def parse_voltage(self, data):
self._device_state_attributes[ATTR_BATTERY_LEVEL] = round(percent, 1)
return True

def parse_data(self, data):
def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
raise NotImplementedError()

Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ PyMVGLive==1.1.4
PyMata==2.14

# homeassistant.components.xiaomi_aqara
PyXiaomiGateway==0.7.1
PyXiaomiGateway==0.8.0

# homeassistant.components.rpi_gpio
# RPi.GPIO==0.6.1
Expand Down

0 comments on commit 3417c6a

Please sign in to comment.