Skip to content

Commit

Permalink
Add support BLE locks
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexxIT committed Sep 16, 2020
1 parent 49c6165 commit 71008aa
Show file tree
Hide file tree
Showing 8 changed files with 385 additions and 122 deletions.
125 changes: 124 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ With GUI. Configuration > Integration > Xiaomi Gateway 3. And enter Gateway **IP

You need [obtain Mi Home token](https://github.com/Maxmudjon/com.xiaomi-miio/blob/master/docs/obtain_token.md). I am using the [method with Mi Home v5.4.54](https://github.com/Maxmudjon/com.xiaomi-miio/blob/master/docs/obtain_token.md#non-rooted-android-phones) for non-rooted Android. If you don't have an Android - you can install the [emulator on Windows](https://www.bignox.com/).

**Attention:** The component is under active development. Breaking changes may appear.

# Advanced config

Support custom occupancy timeout for motion sensor. Default 90 seconds.
Expand All @@ -91,6 +93,127 @@ xiaomi_gateway3:
occupancy_timeout: 90 # (optional) default 90 seconds
```
# Handle BLE Locks
<img src="bluetooth_lock.png" width="810">
BLE locks have an action entity, just like buttons.
The state changes to `door`, `lock`, `fingerprint`,` armed` when an event occurs. Details of the event are in the entity attributes.

`action`: **fingerprint**
- `key_id` - Key ID in full hex format
- `action_id`: 0, `message`: Match successful
- `action_id`: 1, `message`: Match failed
- `action_id`: 2, `message`: Timeout
- `action_id`: 3, `message`: Low quality
- `action_id`: 4, `message`: Insufficient area
- `action_id`: 5, `message`: Skin is too dry
- `action_id`: 5, `message`: Skin is too wet

`action`: **door**
- `action_id`: 0, `message`: Door is open
- `action_id`: 1, `message`: Door is closed
- `action_id`: 2, `message`: Timeout is not closed
- `action_id`: 3, `message`: Knock on the door
- `action_id`: 4, `message`: Breaking the door
- `action_id`: 5, `message`: Door is stuck

`action`: **lock**
- `key_id` - Key ID in short decimal format
- `action_id`: 0, `message`: Unlock outside the door
- `action_id`: 1, `message`: Lock
- `action_id`: 2, `message`: Turn on anti-lock
- `action_id`: 3, `message`: Turn off anti-lock
- `action_id`: 4, `message`: Unlock inside the door
- `action_id`: 5, `message`: Lock inside the door
- `action_id`: 6, `message`: Turn on child lock
- `action_id`: 7, `message`: Turn off child lock
- `method_id`: 0, `method`: bluetooth
- `method_id`: 1, `method`: password
- `method_id`: 2, `method`: biological
- `method_id`: 3, `method`: key
- `method_id`: 4, `method`: turntable
- `method_id`: 5, `method`: nfc
- `method_id`: 6, `method`: one-time password
- `method_id`: 7, `method`: two-step verification
- `method_id`: 8, `method`: coercion
- `method_id`: 10, `method`: manual
- `method_id`: 11, `method`: automatic
- `key_id`: 0xc0de0000, `error`: Frequent unlocking with incorrect password
- `key_id`: 0xc0de0001, `error`: Frequent unlocking with wrong fingerprints
- `key_id`: 0xc0de0002, `error`: Operation timeout (password input timeout)
- `key_id`: 0xc0de0003, `error`: Lock picking
- `key_id`: 0xc0de0004, `error`: Reset button is pressed
- `key_id`: 0xc0de0005, `error`: The wrong key is frequently unlocked
- `key_id`: 0xc0de0006, `error`: Foreign body in the keyhole
- `key_id`: 0xc0de0007, `error`: The key has not been taken out
- `key_id`: 0xc0de0008, `error`: Error NFC frequently unlocks
- `key_id`: 0xc0de0009, `error`: Timeout is not locked as required
- `key_id`: 0xc0de000a, `error`: Failure to unlock frequently in multiple ways
- `key_id`: 0xc0de000b, `error`: Unlocking the face frequently fails
- `key_id`: 0xc0de000c, `error`: Failure to unlock the vein frequently
- `key_id`: 0xc0de000d, `error`: Hijacking alarm
- `key_id`: 0xc0de000e, `error`: Unlock inside the door after arming
- `key_id`: 0xc0de000f, `error`: Palmprints frequently fail to unlock
- `key_id`: 0xc0de0010, `error`: The safe was moved
- `key_id`: 0xc0de1000, `error`: The battery level is less than 10%
- `key_id`: 0xc0de1001, `error`: The battery is less than 5%
- `key_id`: 0xc0de1002, `error`: The fingerprint sensor is abnormal
- `key_id`: 0xc0de1003, `error`: The accessory battery is low
- `key_id`: 0xc0de1004, `error`: Mechanical failure

Email me if the values are wrong somewhere. I translated from Chinese [documentation](https://iot.mi.com/new/doc/embedded-development/ble/object-definition).

Example of several automations:

```yaml
automation:
- alias: Doorbell
trigger:
platform: state
entity_id: sensor.ble_1010274797_action
to: door
condition:
condition: template
value_template: "{{ trigger.to_state.attributes['action_id'] == 3 }}"
action:
service: persistent_notification.create
data_template:
title: Doorbell
message: The doorbell is ringing
- alias: Lock Error
trigger:
platform: state
entity_id: sensor.ble_1010274797_action
to: lock
condition:
condition: template
value_template: "{{ trigger.to_state.attributes['error'] }}"
action:
service: persistent_notification.create
data_template:
title: Lock ERROR
message: "{{ trigger.to_state.attributes['error'] }}"
- alias: Open lock
trigger:
platform: state
entity_id: sensor.ble_1010274797_action
to: lock
condition:
condition: template
value_template: "{{ trigger.to_state.attributes['action_id'] == 0 }}"
action:
service: persistent_notification.create
data_template:
title: Lock is open
message: |
Opening method: {{ trigger.to_state.attributes['method'] }}
User ID: {{ trigger.to_state.attributes['key_id'] }}
```

# How it works

The component enables **Telnet** on Gateway via [Miio protocol](https://github.com/rytilahti/python-miio). Only this Gateway supports this command. Do not try to execute it on other Xiaomi/Aqara Gateways.
Expand All @@ -109,7 +232,7 @@ Demo video of my other component, but the idea is the same:

[![Control Sonoff Devices with eWeLink firmware over LAN from Home Assistant](https://img.youtube.com/vi/Lt5fT4N5Pm8/mqdefault.jpg)](https://www.youtube.com/watch?v=Lt5fT4N5Pm8)

With `debug: bluetooth` or debug `debug: mqtt` opntion you will get advanced log for raw BLE and MQTT data.
With `debug: bluetooth` or debug `debug: mqtt` option you will get advanced log for raw BLE and MQTT data.

With `debug: true` option you will get usual component logs.

Expand Down
Binary file added bluetooth_lock.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 10 additions & 7 deletions custom_components/xiaomi_gateway3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from homeassistant.const import STATE_UNKNOWN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
from homeassistant.helpers.entity import Entity

from . import utils
Expand Down Expand Up @@ -65,6 +64,7 @@ def __init__(self, gateway: Gateway3, device: dict, attr: str):
self.device = device

self._attr = attr
self._attrs = {}

self._unique_id = f"{self.device['mac']}_{self._attr}"
self._name = self.device['device_name'] + ' ' + self._attr.title()
Expand All @@ -91,27 +91,30 @@ def name(self):

@property
def device_info(self):
did: str = self.device['did']
if did == 'lumi.0':
"""
https://developers.home-assistant.io/docs/device_registry_index/
"""
type_ = self.device['type']
if type_ == 'gateway':
return {
'identifiers': {(DOMAIN, self.device['mac'])},
'manufacturer': self.device['device_manufacturer'],
'model': self.device['device_model'],
'name': self.device['device_name']
}
elif not did.startswith('blt'):
elif type_ == 'zigbee':
return {
'connections': {(CONNECTION_ZIGBEE, self.device['mac'])},
'connections': {(type_, self.device['mac'])},
'identifiers': {(DOMAIN, self.device['mac'])},
'manufacturer': self.device['device_manufacturer'],
'model': self.device['device_model'],
'name': self.device['device_name'],
'sw_version': self.device['zb_ver'],
'via_device': (DOMAIN, self.gw.device['mac'])
}
else:
elif type_ == 'ble':
return {
'connections': {('bluetooth', self.device['mac'])},
'connections': {(type_, self.device['mac'])},
'identifiers': {(DOMAIN, self.device['mac'])},
'name': self.device['device_name'],
'via_device': (DOMAIN, self.gw.device['mac'])
Expand Down
Loading

0 comments on commit 71008aa

Please sign in to comment.