Skip to content

Commit

Permalink
Merge pull request #67 from Bluetooth-Devices/sleepy
Browse files Browse the repository at this point in the history
feat: add sleepy device bit
  • Loading branch information
Ernst79 authored May 4, 2023
2 parents a219278 + 26bc5d2 commit 6027f94
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 33 deletions.
3 changes: 0 additions & 3 deletions src/bthome_ble/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@
Units,
)

from .const import SLEEPY_BINARY_SENSORS, SLEEPY_SENSORS
from .parser import BTHomeBluetoothDeviceData

__version__ = "2.10.1"

__all__ = [
"SLEEPY_BINARY_SENSORS",
"SLEEPY_SENSORS",
"BinarySensorDeviceClass",
"BTHomeBluetoothDeviceData",
"DeviceClass",
Expand Down
18 changes: 0 additions & 18 deletions src/bthome_ble/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,21 +343,3 @@ class MeasTypes:
factor=0.001,
),
}


SLEEPY_BINARY_SENSORS = {
"door",
"garage_door",
"lock",
"opening",
"presence",
"tamper",
"vibration",
"window",
}


SLEEPY_SENSORS = {
"count",
"mass",
}
31 changes: 19 additions & 12 deletions src/bthome_ble/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ def __init__(self, bindkey: bytes | None = None) -> None:
# Encryption to expect, based on flags in the UUID.
self.encryption_scheme = EncryptionScheme.NONE

# If true, then we know the actual MAC of the device.
# If True, then we know the actual MAC of the device.
# On macOS, we don't unless the device includes it in the advertisement
# (CoreBluetooth uses UUID's generated by CoreBluetooth instead of the MAC)
self.mac_known = sys.platform != "darwin"

# If true then we have used the provided encryption key to decrypt at least
# If True then we have used the provided encryption key to decrypt at least
# one payload.
# If false then we have either not seen an encrypted payload, the key is wrong
# If False then we have either not seen an encrypted payload, the key is wrong
# or encryption is not in use
self.bindkey_verified = False

Expand All @@ -136,6 +136,9 @@ def __init__(self, bindkey: bytes | None = None) -> None:
# value with a new bindkey.
self.last_service_info: BluetoothServiceInfo | None = None

# If this is True, the device is not sending advertisements in a regular interval
self.sleepy_device = False

def supported(self, data: BluetoothServiceInfo) -> bool:
if not super().supported(data):
return False
Expand Down Expand Up @@ -255,6 +258,19 @@ def _parse_bthome_v2(
else:
self.encryption_scheme = EncryptionScheme.NONE

# If True, the first 6 bytes contain the mac address
mac_included = adv_info & (1 << 1) # bit 1
if mac_included:
bthome_mac_reversed = data[1:7]
mac_readable = to_mac(bthome_mac_reversed[::-1])
payload = data[7:]
else:
mac_readable = service_info.address
payload = data[1:]

# If True, the device is not updating regularly
self.sleepy_device = bool(adv_info & (1 << 2)) # bit 2

# Check BTHome version
sw_version = (adv_info >> 5) & 7 # 3 bits (5-7)
if sw_version == 2:
Expand Down Expand Up @@ -298,15 +314,6 @@ def _parse_bthome_v2(
self.set_title(f"{name} {identifier}")
self.set_device_type(device_type)

mac_included = adv_info & (1 << 1) # bit 1
if mac_included:
bthome_mac_reversed = data[1:7]
mac_readable = to_mac(bthome_mac_reversed[::-1])
payload = data[7:]
else:
mac_readable = service_info.address
payload = data[1:]

if self.encryption_scheme == EncryptionScheme.BTHOME_BINDKEY:
if len(mac_readable) != 17 and mac_readable[2] != ":":
# On macOS, we get a UUID, which is useless for BTHome sensors
Expand Down
30 changes: 30 additions & 0 deletions tests/test_parser_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,36 @@ def test_encryption_no_key_needed():
assert not device.bindkey_verified


def test_sleepy_device():
"""Test that we can detect that a device that doesn't update regularly."""
data_string = b"\x44\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
payload=data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)

device = BTHomeBluetoothDeviceData()

assert device.supported(advertisement)
assert device.sleepy_device


def test_no_sleepy_device():
"""Test that we can detect that a device that updates regularly."""
data_string = b"\x40\x04\x13\x8a\x01"
advertisement = bytes_to_service_info(
payload=data_string,
local_name="ATC_8D18B2",
address="A4:C1:38:8D:18:B2",
)

device = BTHomeBluetoothDeviceData()

assert device.supported(advertisement)
assert not device.sleepy_device


def test_mac_as_name():
"""
A sensor without a name gets its MAC address as name from BluetoothServiceInfo.
Expand Down

0 comments on commit 6027f94

Please sign in to comment.