Merge pull request #160 from AlanRosenthal/alan/types

Add some missing types to apps/console.py, bumble/gatt_client.py
This commit is contained in:
Alan Rosenthal
2023-03-27 15:00:03 -04:00
committed by GitHub
3 changed files with 52 additions and 19 deletions

View File

@@ -24,6 +24,7 @@ import logging
import os import os
import random import random
import re import re
from typing import Optional
from collections import OrderedDict from collections import OrderedDict
import click import click
@@ -58,6 +59,7 @@ from bumble.device import ConnectionParametersPreferences, Device, Connection, P
from bumble.utils import AsyncRunner from bumble.utils import AsyncRunner
from bumble.transport import open_transport_or_link from bumble.transport import open_transport_or_link
from bumble.gatt import Characteristic, Service, CharacteristicDeclaration, Descriptor from bumble.gatt import Characteristic, Service, CharacteristicDeclaration, Descriptor
from bumble.gatt_client import CharacteristicProxy
from bumble.hci import ( from bumble.hci import (
HCI_Constant, HCI_Constant,
HCI_LE_1M_PHY, HCI_LE_1M_PHY,
@@ -119,6 +121,8 @@ def parse_phys(phys):
# Console App # Console App
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ConsoleApp: class ConsoleApp:
connected_peer: Optional[Peer]
def __init__(self): def __init__(self):
self.known_addresses = set() self.known_addresses = set()
self.known_attributes = [] self.known_attributes = []
@@ -490,7 +494,9 @@ class ConsoleApp:
self.show_attributes(attributes) self.show_attributes(attributes)
def find_characteristic(self, param): def find_characteristic(self, param) -> Optional[CharacteristicProxy]:
if not self.connected_peer:
return None
parts = param.split('.') parts = param.split('.')
if len(parts) == 2: if len(parts) == 2:
service_uuid = UUID(parts[0]) if parts[0] != '*' else None service_uuid = UUID(parts[0]) if parts[0] != '*' else None

View File

@@ -28,8 +28,8 @@ import struct
from pyee import EventEmitter from pyee import EventEmitter
from typing import Dict, Type, TYPE_CHECKING from typing import Dict, Type, TYPE_CHECKING
from bumble.core import UUID, name_or_number, get_dict_key_by_value from bumble.core import UUID, name_or_number, get_dict_key_by_value, ProtocolError
from bumble.hci import HCI_Object, key_with_value from bumble.hci import HCI_Object, key_with_value, HCI_Constant
from bumble.colors import color from bumble.colors import color
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -185,13 +185,18 @@ UUID_2_FIELD_SPEC = lambda x, y: UUID.parse_uuid_2(x, y) # noqa: E731
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Exceptions # Exceptions
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class ATT_Error(Exception): class ATT_Error(ProtocolError):
def __init__(self, error_code, att_handle=0x0000): def __init__(self, error_code, att_handle=0x0000, message=''):
self.error_code = error_code super().__init__(
error_code,
error_namespace='att',
error_name=ATT_PDU.error_name(self.error_code),
)
self.att_handle = att_handle self.att_handle = att_handle
self.message = message
def __str__(self): def __str__(self):
return f'ATT_Error({ATT_PDU.error_name(self.error_code)})' return f'ATT_Error(error={self.error_name}, handle={self.att_handle:04X}): {self.message}'
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------

View File

@@ -23,9 +23,11 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Imports # Imports
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
from __future__ import annotations
import asyncio import asyncio
import logging import logging
import struct import struct
from typing import List, Optional
from pyee import EventEmitter from pyee import EventEmitter
@@ -50,6 +52,7 @@ from .att import (
ATT_Read_Request, ATT_Read_Request,
ATT_Write_Command, ATT_Write_Command,
ATT_Write_Request, ATT_Write_Request,
ATT_Error,
) )
from . import core from . import core
from .core import UUID, InvalidStateError, ProtocolError from .core import UUID, InvalidStateError, ProtocolError
@@ -59,6 +62,7 @@ from .gatt import (
GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE, GATT_PRIMARY_SERVICE_ATTRIBUTE_TYPE,
GATT_REQUEST_TIMEOUT, GATT_REQUEST_TIMEOUT,
GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE, GATT_SECONDARY_SERVICE_ATTRIBUTE_TYPE,
Service,
Characteristic, Characteristic,
ClientCharacteristicConfigurationBits, ClientCharacteristicConfigurationBits,
) )
@@ -73,6 +77,8 @@ logger = logging.getLogger(__name__)
# Proxies # Proxies
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class AttributeProxy(EventEmitter): class AttributeProxy(EventEmitter):
client: Client
def __init__(self, client, handle, end_group_handle, attribute_type): def __init__(self, client, handle, end_group_handle, attribute_type):
EventEmitter.__init__(self) EventEmitter.__init__(self)
self.client = client self.client = client
@@ -101,6 +107,9 @@ class AttributeProxy(EventEmitter):
class ServiceProxy(AttributeProxy): class ServiceProxy(AttributeProxy):
uuid: UUID
characteristics: List[CharacteristicProxy]
@staticmethod @staticmethod
def from_client(service_class, client, service_uuid): def from_client(service_class, client, service_uuid):
# The service and its characteristics are considered to have already been # The service and its characteristics are considered to have already been
@@ -130,6 +139,8 @@ class ServiceProxy(AttributeProxy):
class CharacteristicProxy(AttributeProxy): class CharacteristicProxy(AttributeProxy):
descriptors: List[DescriptorProxy]
def __init__(self, client, handle, end_group_handle, uuid, properties): def __init__(self, client, handle, end_group_handle, uuid, properties):
super().__init__(client, handle, end_group_handle, uuid) super().__init__(client, handle, end_group_handle, uuid)
self.uuid = uuid self.uuid = uuid
@@ -201,6 +212,8 @@ class ProfileServiceProxy:
# GATT Client # GATT Client
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class Client: class Client:
services: List[ServiceProxy]
def __init__(self, connection): def __init__(self, connection):
self.connection = connection self.connection = connection
self.mtu_exchange_done = False self.mtu_exchange_done = False
@@ -306,7 +319,7 @@ class Client:
if not already_known: if not already_known:
self.services.append(service) self.services.append(service)
async def discover_services(self, uuids=None): async def discover_services(self, uuids=None) -> List[ServiceProxy]:
''' '''
See Vol 3, Part G - 4.4.1 Discover All Primary Services See Vol 3, Part G - 4.4.1 Discover All Primary Services
''' '''
@@ -332,8 +345,10 @@ class Client:
'!!! unexpected error while discovering services: ' '!!! unexpected error while discovering services: '
f'{HCI_Constant.error_name(response.error_code)}' f'{HCI_Constant.error_name(response.error_code)}'
) )
# TODO raise appropriate exception raise ATT_Error(
return error_code=response.error_code,
message='Unexpected error while discovering services',
)
break break
for ( for (
@@ -349,7 +364,7 @@ class Client:
logger.warning( logger.warning(
f'bogus handle values: {attribute_handle} {end_group_handle}' f'bogus handle values: {attribute_handle} {end_group_handle}'
) )
return return []
# Create a service proxy for this service # Create a service proxy for this service
service = ServiceProxy( service = ServiceProxy(
@@ -452,7 +467,9 @@ class Client:
# TODO # TODO
return [] return []
async def discover_characteristics(self, uuids, service): async def discover_characteristics(
self, uuids, service: Optional[ServiceProxy]
) -> List[CharacteristicProxy]:
''' '''
See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2 See Vol 3, Part G - 4.6.1 Discover All Characteristics of a Service and 4.6.2
Discover Characteristics by UUID Discover Characteristics by UUID
@@ -465,12 +482,12 @@ class Client:
services = [service] if service else self.services services = [service] if service else self.services
# Perform characteristic discovery for each service # Perform characteristic discovery for each service
discovered_characteristics = [] discovered_characteristics: List[CharacteristicProxy] = []
for service in services: for service in services:
starting_handle = service.handle starting_handle = service.handle
ending_handle = service.end_group_handle ending_handle = service.end_group_handle
characteristics = [] characteristics: List[CharacteristicProxy] = []
while starting_handle <= ending_handle: while starting_handle <= ending_handle:
response = await self.send_request( response = await self.send_request(
ATT_Read_By_Type_Request( ATT_Read_By_Type_Request(
@@ -491,8 +508,10 @@ class Client:
'!!! unexpected error while discovering characteristics: ' '!!! unexpected error while discovering characteristics: '
f'{HCI_Constant.error_name(response.error_code)}' f'{HCI_Constant.error_name(response.error_code)}'
) )
# TODO raise appropriate exception raise ATT_Error(
return error_code=response.error_code,
message='Unexpected error while discovering characteristics',
)
break break
# Stop if for some reason the list was empty # Stop if for some reason the list was empty
@@ -535,8 +554,11 @@ class Client:
return discovered_characteristics return discovered_characteristics
async def discover_descriptors( async def discover_descriptors(
self, characteristic=None, start_handle=None, end_handle=None self,
): characteristic: Optional[CharacteristicProxy] = None,
start_handle=None,
end_handle=None,
) -> List[DescriptorProxy]:
''' '''
See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
''' '''
@@ -549,7 +571,7 @@ class Client:
else: else:
return [] return []
descriptors = [] descriptors: List[DescriptorProxy] = []
while starting_handle <= ending_handle: while starting_handle <= ending_handle:
response = await self.send_request( response = await self.send_request(
ATT_Find_Information_Request( ATT_Find_Information_Request(