diff --git a/apps/bench.py b/apps/bench.py index 19cdcfa..4708403 100644 --- a/apps/bench.py +++ b/apps/bench.py @@ -558,11 +558,13 @@ class GattServer: # Setup the GATT service self.speed_tx = Characteristic( SPEED_TX_UUID, - Characteristic.WRITE, + Characteristic.Properties.WRITE, Characteristic.WRITEABLE, CharacteristicValue(write=self.on_tx_write), ) - self.speed_rx = Characteristic(SPEED_RX_UUID, Characteristic.NOTIFY, 0) + self.speed_rx = Characteristic( + SPEED_RX_UUID, Characteristic.Properties.NOTIFY, 0 + ) speed_service = Service( SPEED_SERVICE_UUID, diff --git a/apps/console.py b/apps/console.py index 498e224..1522f08 100644 --- a/apps/console.py +++ b/apps/console.py @@ -873,7 +873,7 @@ class ConsoleApp: return # use write with response if supported - with_response = characteristic.properties & Characteristic.WRITE + with_response = characteristic.properties & Characteristic.Properties.WRITE await characteristic.write_value(value, with_response=with_response) async def do_local_write(self, params): diff --git a/apps/gg_bridge.py b/apps/gg_bridge.py index 17c1662..6506f7b 100644 --- a/apps/gg_bridge.py +++ b/apps/gg_bridge.py @@ -230,13 +230,13 @@ class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener): ) self.tx_characteristic = Characteristic( GG_GATTLINK_TX_CHARACTERISTIC_UUID, - Characteristic.NOTIFY, + Characteristic.Properties.NOTIFY, Characteristic.READABLE, ) self.tx_characteristic.on('subscription', self.on_tx_subscription) self.psm_characteristic = Characteristic( GG_GATTLINK_L2CAP_CHANNEL_PSM_CHARACTERISTIC_UUID, - Characteristic.READ | Characteristic.NOTIFY, + Characteristic.Properties.READ | Characteristic.Properties.NOTIFY, Characteristic.READABLE, bytes([psm, 0]), ) diff --git a/apps/pair.py b/apps/pair.py index 3729143..541e5b5 100644 --- a/apps/pair.py +++ b/apps/pair.py @@ -302,7 +302,8 @@ async def pair( [ Characteristic( '552957FB-CF1F-4A31-9535-E78847E1A714', - Characteristic.READ | Characteristic.WRITE, + Characteristic.Properties.READ + | Characteristic.Properties.WRITE, Characteristic.READABLE | Characteristic.WRITEABLE, CharacteristicValue( read=read_with_error, write=write_with_error diff --git a/bumble/device.py b/bumble/device.py index 25fa099..0fa8f16 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -1018,7 +1018,9 @@ class Device(CompositeEventEmitter): descriptors.append(new_descriptor) new_characteristic = Characteristic( uuid=characteristic["uuid"], - properties=characteristic["properties"], + properties=Characteristic.Properties.from_string( + characteristic["properties"] + ), permissions=characteristic["permissions"], descriptors=descriptors, ) diff --git a/bumble/gap.py b/bumble/gap.py index a4d5077..29df89f 100644 --- a/bumble/gap.py +++ b/bumble/gap.py @@ -41,14 +41,14 @@ class GenericAccessService(Service): def __init__(self, device_name, appearance=(0, 0)): device_name_characteristic = Characteristic( GATT_DEVICE_NAME_CHARACTERISTIC, - Characteristic.READ, + Characteristic.Properties.READ, Characteristic.READABLE, device_name.encode('utf-8')[:248], ) appearance_characteristic = Characteristic( GATT_APPEARANCE_CHARACTERISTIC, - Characteristic.READ, + Characteristic.Properties.READ, Characteristic.READABLE, struct.pack(' Characteristic.Properties: + property_names: List[str] = [] + for property in Characteristic.Properties: + if property.name is None: + raise TypeError() + property_names.append(property.name) - @staticmethod - def properties_as_string(properties): - return ','.join( - [ - Characteristic.property_name(p) - for p in Characteristic.PROPERTY_NAMES - if properties & p - ] - ) + def string_to_property(property_string) -> Characteristic.Properties: + for property in zip(Characteristic.Properties, property_names): + if property_string == property[1]: + return property[0] + raise TypeError(f"Unable to convert {property_string} to Property") - @staticmethod - def string_to_properties(properties_str: str): - return functools.reduce( - lambda x, y: x | get_dict_key_by_value(Characteristic.PROPERTY_NAMES, y), - properties_str.split(","), - 0, - ) + try: + return functools.reduce( + lambda x, y: x | string_to_property(y), + properties_str.split(","), + Characteristic.Properties(0), + ) + except TypeError: + raise TypeError( + f"Characteristic.Properties::from_string() error:\nExpected a string containing any of the keys, separated by commas: {','.join(property_names)}\nGot: {properties_str}" + ) + + # For backwards compatibility these are defined here + # For new code, please use Characteristic.Properties.X + BROADCAST = Properties.BROADCAST + READ = Properties.READ + WRITE_WITHOUT_RESPONSE = Properties.WRITE_WITHOUT_RESPONSE + WRITE = Properties.WRITE + NOTIFY = Properties.NOTIFY + INDICATE = Properties.INDICATE + AUTHENTICATED_SIGNED_WRITES = Properties.AUTHENTICATED_SIGNED_WRITES + EXTENDED_PROPERTIES = Properties.EXTENDED_PROPERTIES def __init__( self, uuid, - properties, + properties: Characteristic.Properties, permissions, value=b'', descriptors: Sequence[Descriptor] = (), ): super().__init__(uuid, permissions, value) self.uuid = self.type - if isinstance(properties, str): - self.properties = Characteristic.string_to_properties(properties) - else: - self.properties = properties + self.properties = properties self.descriptors = descriptors def get_descriptor(self, descriptor_type): @@ -327,18 +330,15 @@ class Characteristic(Attribute): return None - def has_properties(self, properties: Iterable[int]): - for prop in properties: - if self.properties & prop == 0: - return False - return True + def has_properties(self, properties: Characteristic.Properties) -> bool: + return self.properties & properties == properties def __str__(self): return ( f'Characteristic(handle=0x{self.handle:04X}, ' f'end=0x{self.end_group_handle:04X}, ' f'uuid={self.uuid}, ' - f'properties={Characteristic.properties_as_string(self.properties)})' + f'{self.properties!s})' ) @@ -365,8 +365,8 @@ class CharacteristicDeclaration(Attribute): return ( f'CharacteristicDeclaration(handle=0x{self.handle:04X}, ' f'value_handle=0x{self.value_handle:04X}, ' - f'uuid={self.characteristic.uuid}, properties=' - f'{Characteristic.properties_as_string(self.characteristic.properties)})' + f'uuid={self.characteristic.uuid}, ' + f'{self.characteristic.properties!s})' ) diff --git a/bumble/gatt_client.py b/bumble/gatt_client.py index 17f622b..d7a0666 100644 --- a/bumble/gatt_client.py +++ b/bumble/gatt_client.py @@ -27,7 +27,7 @@ from __future__ import annotations import asyncio import logging import struct -from typing import List, Optional +from typing import List, Optional, Dict, Any, Callable from pyee import EventEmitter @@ -138,9 +138,18 @@ class ServiceProxy(AttributeProxy): class CharacteristicProxy(AttributeProxy): + properties: Characteristic.Properties descriptors: List[DescriptorProxy] + subscribers: Dict[Any, Callable] - def __init__(self, client, handle, end_group_handle, uuid, properties): + def __init__( + self, + client, + handle, + end_group_handle, + uuid, + properties: Characteristic.Properties, + ): super().__init__(client, handle, end_group_handle, uuid) self.uuid = uuid self.properties = properties @@ -185,7 +194,7 @@ class CharacteristicProxy(AttributeProxy): return ( f'Characteristic(handle=0x{self.handle:04X}, ' f'uuid={self.uuid}, ' - f'properties={Characteristic.properties_as_string(self.properties)})' + f'properties={self.properties!s})' ) @@ -677,8 +686,8 @@ class Client: return if ( - characteristic.properties & Characteristic.NOTIFY - and characteristic.properties & Characteristic.INDICATE + characteristic.properties & Characteristic.Properties.NOTIFY + and characteristic.properties & Characteristic.Properties.INDICATE ): if prefer_notify: bits = ClientCharacteristicConfigurationBits.NOTIFICATION @@ -686,10 +695,10 @@ class Client: else: bits = ClientCharacteristicConfigurationBits.INDICATION subscribers = self.indication_subscribers - elif characteristic.properties & Characteristic.NOTIFY: + elif characteristic.properties & Characteristic.Properties.NOTIFY: bits = ClientCharacteristicConfigurationBits.NOTIFICATION subscribers = self.notification_subscribers - elif characteristic.properties & Characteristic.INDICATE: + elif characteristic.properties & Characteristic.Properties.INDICATE: bits = ClientCharacteristicConfigurationBits.INDICATION subscribers = self.indication_subscribers else: diff --git a/bumble/gatt_server.py b/bumble/gatt_server.py index e3529c8..51f3ec0 100644 --- a/bumble/gatt_server.py +++ b/bumble/gatt_server.py @@ -243,7 +243,10 @@ class Server(EventEmitter): # unless there is one already if ( characteristic.properties - & (Characteristic.NOTIFY | Characteristic.INDICATE) + & ( + Characteristic.Properties.NOTIFY + | Characteristic.Properties.INDICATE + ) and characteristic.get_descriptor( GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR ) diff --git a/bumble/profiles/asha_service.py b/bumble/profiles/asha_service.py index 1b1e93a..6898397 100644 --- a/bumble/profiles/asha_service.py +++ b/bumble/profiles/asha_service.py @@ -103,7 +103,7 @@ class AshaService(TemplateService): self.read_only_properties_characteristic = Characteristic( GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC, - Characteristic.READ, + Characteristic.Properties.READ, Characteristic.READABLE, bytes( [ @@ -120,19 +120,20 @@ class AshaService(TemplateService): self.audio_control_point_characteristic = Characteristic( GATT_ASHA_AUDIO_CONTROL_POINT_CHARACTERISTIC, - Characteristic.WRITE | Characteristic.WRITE_WITHOUT_RESPONSE, + Characteristic.Properties.WRITE + | Characteristic.Properties.WRITE_WITHOUT_RESPONSE, Characteristic.WRITEABLE, CharacteristicValue(write=on_audio_control_point_write), ) self.audio_status_characteristic = Characteristic( GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC, - Characteristic.READ | Characteristic.NOTIFY, + Characteristic.Properties.READ | Characteristic.Properties.NOTIFY, Characteristic.READABLE, bytes([0]), ) self.volume_characteristic = Characteristic( GATT_ASHA_VOLUME_CHARACTERISTIC, - Characteristic.WRITE_WITHOUT_RESPONSE, + Characteristic.Properties.WRITE_WITHOUT_RESPONSE, Characteristic.WRITEABLE, CharacteristicValue(write=on_volume_write), ) @@ -151,7 +152,7 @@ class AshaService(TemplateService): self.psm = self.device.register_l2cap_channel_server(self.psm, on_coc, 8) self.le_psm_out_characteristic = Characteristic( GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC, - Characteristic.READ, + Characteristic.Properties.READ, Characteristic.READABLE, struct.pack('