Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
run: pip install -r requirements.txt
- name: Install development dependencies
run: pip install -r requirements_dev.txt
- name: Install module self
run: pip install -e .
- name: Test with pytest
run: |
pytest tests --junitxml=junit/test-results-${{ matrix.os }}-${{ matrix.python-version }}.xml --cov=com --cov-report=xml --cov-report=html
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Added
~~~~~
* Allow 16-bit UUID string arguments to ``get_service()`` and ``get_characteristic()``.
* Added ``register_uuids()`` to augment the uuid-to-description mapping.
* Support interface removal on bluez backends

Fixed
~~~~~
Expand Down
16 changes: 16 additions & 0 deletions bleak/backends/bluezdbus/characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ def add_descriptor(self, descriptor: BleakGATTDescriptor):
"""
self.__descriptors.append(descriptor)

def del_descriptor(self, handle: int):
"""Remove a :py:class:`~BleakGATTDescriptor` to the characteristic.

Should not be used by end user, but rather by `bleak` itself.
"""
for index, desc in enumerate(self.__descriptors):
if desc.handle == handle:
del self.__descriptors[index]
break
else:
raise ValueError(
"Descriptor {} doesn't exists on characteristics {}".format(
handle, self.__service_handle
)
)

@property
def path(self) -> str:
"""The DBus path. Mostly needed by `bleak`, not by end user"""
Expand Down
19 changes: 19 additions & 0 deletions bleak/backends/bluezdbus/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from bleak.backends.bluezdbus.signals import MatchRules, add_match, remove_match
from bleak.backends.bluezdbus.utils import (
assert_reply,
extract_handles_from_path,
extract_service_handle_from_path,
unpack_variants,
)
Expand Down Expand Up @@ -1068,6 +1069,24 @@ def _parse_msg(self, message: Message):
)
elif message.member == "InterfacesRemoved":
path, interfaces = message.body
(
_,
service_handle,
characteristic_handle,
descriptor_handle,
) = extract_handles_from_path(path)

if defs.GATT_SERVICE_INTERFACE in interfaces:
assert service_handle is not None
self.services.del_service(service_handle)

if defs.GATT_CHARACTERISTIC_INTERFACE in interfaces:
assert characteristic_handle is not None
self.services.del_characteristic(characteristic_handle)

if defs.GATT_DESCRIPTOR_INTERFACE in interfaces:
assert descriptor_handle is not None
self.services.del_descriptor(descriptor_handle)

elif message.member == "PropertiesChanged":
interface, changed, _ = message.body
Expand Down
16 changes: 16 additions & 0 deletions bleak/backends/bluezdbus/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ def add_characteristic(self, characteristic: BleakGATTCharacteristicBlueZDBus):
"""
self.__characteristics.append(characteristic)

def del_characteristic(self, handle: int):
"""Remove a :py:class:`~BleakGATTCharacteristicBlueZDBus` characteristics from service.

Should not be used by end user, but rather by `bleak` itself.
"""
for index, desc in enumerate(self.__characteristics):
if desc.handle == handle:
del self.__characteristics[index]
break
else:
raise ValueError(
"Characteristics {} doesn't exists on service {}".format(
handle, self.__handle
)
)

@property
def path(self):
"""The DBus path. Mostly needed by `bleak`, not by end user"""
Expand Down
30 changes: 30 additions & 0 deletions bleak/backends/bluezdbus/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,33 @@ def extract_service_handle_from_path(path):
return int(path[-4:], 16)
except Exception as e:
raise BleakError(f"Could not parse service handle from path: {path}") from e


def extract_handles_from_path(path: str):
segments = reversed(path.split("/"))
segment = next(segments)
if segment.startswith("desc"):
desc = int(segment[4:], 16)
segment = next(segments)
else:
desc = None

if segment.startswith("char"):
char = int(segment[4:], 16)
segment = next(segments)
else:
char = None

if segment.startswith("service"):
service = int(segment[7:], 16)
segment = next(segments)
else:
service = None

if segment.startswith("dev_"):
device = segment[4:]
segment = next(segments)
else:
device = None

return device, service, char, desc
8 changes: 8 additions & 0 deletions bleak/backends/characteristic.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@ def add_descriptor(self, descriptor: BleakGATTDescriptor):
Should not be used by end user, but rather by `bleak` itself.
"""
raise NotImplementedError()

@abc.abstractmethod
def del_descriptor(self, handle: int):
"""Remove a :py:class:`~BleakGATTDescriptor` to the characteristic.

Should not be used by end user, but rather by `bleak` itself.
"""
raise NotImplementedError()
22 changes: 22 additions & 0 deletions bleak/backends/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ def add_characteristic(self, characteristic: BleakGATTCharacteristic):
"""
raise NotImplementedError()

@abc.abstractmethod
def del_characteristic(self, characteristic: BleakGATTCharacteristic):
"""Add a :py:class:`~BleakGATTCharacteristic` to the service.

Should not be used by end user, but rather by `bleak` itself.
"""
raise NotImplementedError()

def get_characteristic(
self, uuid: Union[str, UUID]
) -> Union[BleakGATTCharacteristic, None]:
Expand Down Expand Up @@ -152,6 +160,10 @@ def get_service(self, specifier: Union[int, str, UUID]) -> BleakGATTService:
else:
return x[0] if x else None

def del_service(self, handle: int):
"""Remove service by integer handle."""
self.__services.pop(handle)

def add_characteristic(self, characteristic: BleakGATTCharacteristic):
"""Add a :py:class:`~BleakGATTCharacteristic` to the service collection.

Expand All @@ -167,6 +179,11 @@ def add_characteristic(self, characteristic: BleakGATTCharacteristic):
"This characteristic is already present in this BleakGATTServiceCollection!"
)

def del_characteristic(self, handle: int):
"""Remove a characteristic by integer handle."""
characteristic = self.__characteristics.pop(handle)
self.__services[characteristic.service_handle].del_characteristic(handle)

def get_characteristic(
self, specifier: Union[int, str, UUID]
) -> BleakGATTCharacteristic:
Expand Down Expand Up @@ -203,6 +220,11 @@ def add_descriptor(self, descriptor: BleakGATTDescriptor):
"This descriptor is already present in this BleakGATTServiceCollection!"
)

def del_descriptor(self, handle: int):
"""Remove a descriptor by integer handle."""
descriptor = self.__descriptors.pop(handle)
self.__characteristics[descriptor.characteristic_handle].del_descriptor(handle)

def get_descriptor(self, handle: int) -> BleakGATTDescriptor:
"""Get a descriptor by integer handle"""
return self.descriptors.get(handle, None)
12 changes: 12 additions & 0 deletions tests/backends/bluezdbus/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import pytest
from unittest.mock import patch, Mock


@pytest.fixture(autouse=True)
def fixture_popen():
with patch("subprocess.Popen") as mock_subproc_popen:
process_mock = Mock()
attrs = {"communicate.return_value": (b"bluetoothctl: 5.55", b"")}
process_mock.configure_mock(**attrs)
mock_subproc_popen.return_value = process_mock
yield process_mock
126 changes: 126 additions & 0 deletions tests/backends/bluezdbus/test_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from bleak.backends.device import BLEDevice
from dbus_next.constants import MessageType

from dbus_next.message import Message
from bleak.backends.bluezdbus.client import BleakClientBlueZDBus
from bleak.backends.bluezdbus.defs import (
GATT_CHARACTERISTIC_INTERFACE,
GATT_DESCRIPTOR_INTERFACE,
OBJECT_MANAGER_INTERFACE,
GATT_SERVICE_INTERFACE,
)


MOCK_DEVICE_MAC = "00_11_22_33_44_55"
MOCK_DEVICE_PATH = "/org/bluez/hci0/dev_{}".format(MOCK_DEVICE_MAC)
MOCK_SERVICE_1_HANDLE = 1
MOCK_SERVICE_1_PATH = "{}/service{:04x}".format(MOCK_DEVICE_PATH, MOCK_SERVICE_1_HANDLE)
MOCK_SERVICE_1_UUID = "49789295-e090-452e-8d6c-23899f08119a"
MOCK_SERVICE_1_DATA = {
"Device": MOCK_DEVICE_PATH,
"UUID": MOCK_SERVICE_1_UUID,
}


MOCK_CHARACTERISTICS_1_HANDLE = 1
MOCK_CHARACTERISTICS_1_UUID = "ec2db680-c38f-422d-8fa6-6474f556c0ae"
MOCK_CHARACTERISTICS_1_PATH = "{}/char{:04}".format(
MOCK_SERVICE_1_PATH, MOCK_CHARACTERISTICS_1_HANDLE
)
MOCK_CHARACTERISTICS_1_DATA = {
"Service": MOCK_SERVICE_1_PATH,
"Flags": [],
"UUID": MOCK_CHARACTERISTICS_1_UUID,
}

MOCK_DESCRIPTOR_1_HANDLE = 1
MOCK_DESCRIPTOR_1_UUID = "05fc05d1-4de7-4f5f-a931-806a7fab6db8"
MOCK_DESCRIPTOR_1_PATH = "{}/desc{:04}".format(
MOCK_CHARACTERISTICS_1_PATH, MOCK_DESCRIPTOR_1_HANDLE
)
MOCK_DESCRIPTOR_1_DATA = {
"Characteristic": MOCK_CHARACTERISTICS_1_PATH,
"UUID": MOCK_DESCRIPTOR_1_UUID,
}


def device_message(member, body):
return Message(
destination=None,
path=MOCK_DEVICE_PATH,
interface=OBJECT_MANAGER_INTERFACE,
member=member,
message_type=MessageType.SIGNAL,
body=body,
)


def _interfaces_added(path, interface, data):
return device_message(
"InterfacesAdded",
[
path,
{interface: data},
],
)


def _interfaces_removed(path, interface):
return device_message("InterfacesRemoved", [path, [interface]])


def test_remove_service():

device = BLEDevice(MOCK_DEVICE_MAC, "", details={"path": MOCK_DEVICE_PATH})
client = BleakClientBlueZDBus(device)

client._parse_msg(
_interfaces_added(
MOCK_SERVICE_1_PATH, GATT_SERVICE_INTERFACE, MOCK_SERVICE_1_DATA
)
)
service_1 = client.services.services.get(MOCK_SERVICE_1_HANDLE)
assert service_1

client._parse_msg(
_interfaces_added(
MOCK_CHARACTERISTICS_1_PATH,
GATT_CHARACTERISTIC_INTERFACE,
MOCK_CHARACTERISTICS_1_DATA,
)
)
characteristics_1 = client.services.characteristics.get(
MOCK_CHARACTERISTICS_1_HANDLE
)
assert characteristics_1
assert characteristics_1.service_handle == MOCK_SERVICE_1_HANDLE
assert characteristics_1.service_uuid == MOCK_SERVICE_1_UUID
assert characteristics_1 in service_1.characteristics

client._parse_msg(
_interfaces_added(
MOCK_DESCRIPTOR_1_PATH,
GATT_DESCRIPTOR_INTERFACE,
MOCK_DESCRIPTOR_1_DATA,
)
)
descriptor_1 = client.services.descriptors.get(MOCK_DESCRIPTOR_1_HANDLE)
assert descriptor_1
assert descriptor_1.characteristic_handle == MOCK_CHARACTERISTICS_1_HANDLE
assert descriptor_1.characteristic_uuid == MOCK_CHARACTERISTICS_1_UUID
assert descriptor_1 in characteristics_1.descriptors

client._parse_msg(
_interfaces_removed(MOCK_DESCRIPTOR_1_PATH, GATT_DESCRIPTOR_INTERFACE)
)
assert MOCK_DESCRIPTOR_1_HANDLE not in client.services.descriptors
assert descriptor_1 not in characteristics_1.descriptors

client._parse_msg(
_interfaces_removed(MOCK_CHARACTERISTICS_1_PATH, GATT_CHARACTERISTIC_INTERFACE)
)
assert MOCK_CHARACTERISTICS_1_HANDLE not in client.services.characteristics
assert characteristics_1 not in service_1.characteristics

client._parse_msg(_interfaces_removed(MOCK_SERVICE_1_PATH, GATT_SERVICE_INTERFACE))
assert MOCK_SERVICE_1_HANDLE not in client.services.services
7 changes: 7 additions & 0 deletions tests/backends/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import sys

collect_ignore = []

if not sys.platform.startswith("linux"):
"""Ignore bluez tests if module won't exist"""
collect_ignore.append("bluezdbus")