Skip to content
Closed
115 changes: 113 additions & 2 deletions homeassistant/components/binary_sensor/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import logging

from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY,
XiaomiDevice)
from homeassistant.components.xiaomi_aqara import (
PY_XIAOMI_GATEWAY, XiaomiDevice, DOMAIN_CONFIG, CONF_LOCKUIDS)

_LOGGER = logging.getLogger(__name__)

Expand All @@ -15,18 +15,29 @@
ATTR_LAST_ACTION = 'last_action'
ATTR_NO_MOTION_SINCE = 'No motion since'

VERIFIED_WRONG = 'verified_wrong'
FING_VERIFIED = 'fing_verified'
CARD_VERIFIED = 'card_verified'
PSW_VERIFIED = 'psw_verified'

DENSITY = 'density'
ATTR_DENSITY = 'Density'


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Perform the setup for Xiaomi devices."""
if PY_XIAOMI_GATEWAY not in hass.data:
return
devices = []
for (_, gateway) in hass.data[PY_XIAOMI_GATEWAY].gateways.items():
for device in gateway.devices['binary_sensor']:
model = device['model']
if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']:
devices.append(XiaomiMotionSensor(device, hass, gateway))
elif model in ['lock.aq1']:
devices.append(XiaomiUnlockSensor(hass.data[DOMAIN_CONFIG],
devices, device,
hass, gateway))
elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']:
devices.append(XiaomiDoorSensor(device, gateway))
elif model == 'sensor_wleak.aq1':
Expand Down Expand Up @@ -64,6 +75,8 @@ class XiaomiBinarySensor(XiaomiDevice, BinarySensorDevice):
def __init__(self, device, name, xiaomi_hub, data_key, device_class):
"""Initialize the XiaomiSmokeSensor."""
self._data_key = data_key
self._real_sid = \
None if 'real_sid' not in device else device['real_sid']
self._device_class = device_class
self._should_poll = False
self._density = 0
Expand All @@ -86,6 +99,8 @@ def device_class(self):

def update(self):
"""Update the sensor state."""
if self._real_sid is not None:
return
_LOGGER.debug('Updating xiaomi sensor by polling')
self._get_from_hub(self._sid)

Expand Down Expand Up @@ -187,6 +202,102 @@ def parse_data(self, data, raw_data):
return True


class XiaomiUnlockSensor(XiaomiBinarySensor):
"""Representation of a XiaomiUnlockSensor."""

def __init__(self, config, devices, device, hass, xiaomi_hub):
"""Initialize the XiaomiUnlockSensor."""
self._hass = hass
self._no_motion_since = 0
self.sub_devices = {}
users = config[CONF_LOCKUIDS] if CONF_LOCKUIDS in config else []
users.append({
'uid': 'unsecu'
})
for user in users:
self.sub_devices[str(user['uid'])] = XiaomiUnlockSubSensor({
'sid': str(user['uid']),
'real_sid': device['sid'],
'model': 'motion',
'data': {
},
'raw_data': {
'cmd': 'report'
}
}, hass, xiaomi_hub)
devices.append(self.sub_devices[str(user['uid'])])
XiaomiBinarySensor.__init__(self, device, 'Unlock Sensor', xiaomi_hub,
'status', 'motion')

def push_sub_devices(self, uid=None):
"""Parse data sent by gateway."""
for key in self.sub_devices:
self.sub_devices[key].push_data({
'status':
MOTION if uid is not None and uid == key else NO_MOTION
}, {
'sid': key,
'cmd': 'report',
'status':
MOTION if uid is not None and uid == key else NO_MOTION
})

def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if raw_data['cmd'] in ['heartbeat', 'read_ack', 'read_rsp']:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you really want to skip the heartbeat? This was/is a workaround for motion sensors.

return

self._should_poll = False
if VERIFIED_WRONG in data: # handle push from the hub
self.push_sub_devices('unsecu')
self._state = False
return True

value = data.get(FING_VERIFIED)
if value is None:
value = data.get(CARD_VERIFIED)
if value is None:
value = data.get(PSW_VERIFIED)
if value is None:
self.push_sub_devices()
self._state = False
return False

self.push_sub_devices(str(value))

self._should_poll = True
if self.entity_id is not None:
self._hass.bus.fire('motion', {
'entity_id': self.entity_id
})
self._state = True
return True


class XiaomiUnlockSubSensor(XiaomiBinarySensor):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this and why it's called motion?

"""Representation of a XiaomiUnlockSubSensor."""

def __init__(self, device, hass, xiaomi_hub):
"""Initialize the XiaomiUnlockSubSensor."""
self._hass = hass
self._no_motion_since = 0
if 'proto' not in device or int(device['proto'][0:1]) == 1:
data_key = 'status'
else:
data_key = 'motion_status'
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why motion_status?

XiaomiBinarySensor.__init__(self, device, 'UnlockSub Sensor',
xiaomi_hub, data_key, 'motion')
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why motion?


def parse_data(self, data, raw_data):
"""Parse data sent by gateway."""
if raw_data['cmd'] == 'heartbeat':
return

value = data.get(self._data_key)
self._state = value is not None and value == MOTION
return True


class XiaomiDoorSensor(XiaomiBinarySensor):
"""Representation of a XiaomiDoorSensor."""

Expand Down
5 changes: 4 additions & 1 deletion homeassistant/components/sensor/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,10 @@ def parse_data(self, data, raw_data):
if self._data_key in ['temperature', 'humidity', 'pressure']:
value /= 100
elif self._data_key in ['illumination']:
value = max(value - 300, 0)
if self._model in 'acpartner.v3':
value = max(value, 0)
else:
value = max(value - 300, 0)
if self._data_key == 'temperature' and (value < -50 or value > 60):
return False
elif self._data_key == 'humidity' and (value <= 0 or value > 100):
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/xiaomi_aqara.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@

CONF_DISCOVERY_RETRY = 'discovery_retry'
CONF_GATEWAYS = 'gateways'
CONF_LOCKUIDS = 'lockuids'
CONF_INTERFACE = 'interface'
CONF_KEY = 'key'

DOMAIN = 'xiaomi_aqara'
DOMAIN_CONFIG = 'xiaomi_aqara_config'

PY_XIAOMI_GATEWAY = "xiaomi_gw"

Expand Down Expand Up @@ -95,6 +97,7 @@ def _fix_conf_defaults(config):
DOMAIN: vol.Schema({
vol.Optional(CONF_GATEWAYS, default={}):
vol.All(cv.ensure_list, [GATEWAY_CONFIG], [_fix_conf_defaults]),
vol.Optional(CONF_LOCKUIDS, default={}): vol.All(cv.ensure_list),
vol.Optional(CONF_INTERFACE, default='any'): cv.string,
vol.Optional(CONF_DISCOVERY_RETRY, default=3): cv.positive_int
})
Expand All @@ -110,6 +113,7 @@ def setup(hass, config):
gateways = config[DOMAIN][CONF_GATEWAYS]
interface = config[DOMAIN][CONF_INTERFACE]
discovery_retry = config[DOMAIN][CONF_DISCOVERY_RETRY]
hass.data[DOMAIN_CONFIG] = config[DOMAIN]

@asyncio.coroutine
def xiaomi_gw_discovered(service, discovery_info):
Expand Down Expand Up @@ -209,6 +213,7 @@ def __init__(self, device, device_type, xiaomi_hub):
self._state = None
self._is_available = True
self._sid = device['sid']
self._model = device['model']
self._name = '{}_{}'.format(device_type, self._sid)
self._type = device_type
self._write_to_hub = xiaomi_hub.write_to_hub
Expand Down