-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a class-based GATT adapter #598
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,19 +28,23 @@ | |
import logging | ||
import struct | ||
from typing import ( | ||
Any, | ||
Callable, | ||
Dict, | ||
Iterable, | ||
List, | ||
Optional, | ||
Sequence, | ||
SupportsBytes, | ||
Type, | ||
Union, | ||
TYPE_CHECKING, | ||
) | ||
|
||
from bumble.colors import color | ||
from bumble.core import BaseBumbleError, UUID | ||
from bumble.att import Attribute, AttributeValue | ||
from bumble.utils import ByteSerializable | ||
|
||
if TYPE_CHECKING: | ||
from bumble.gatt_client import AttributeProxy | ||
|
@@ -343,7 +347,7 @@ class Service(Attribute): | |
def __init__( | ||
self, | ||
uuid: Union[str, UUID], | ||
characteristics: List[Characteristic], | ||
characteristics: Iterable[Characteristic], | ||
primary=True, | ||
included_services: Iterable[Service] = (), | ||
) -> None: | ||
|
@@ -362,7 +366,7 @@ def __init__( | |
) | ||
self.uuid = uuid | ||
self.included_services = list(included_services) | ||
self.characteristics = characteristics[:] | ||
self.characteristics = list(characteristics) | ||
self.primary = primary | ||
|
||
def get_advertising_data(self) -> Optional[bytes]: | ||
|
@@ -393,7 +397,7 @@ class TemplateService(Service): | |
|
||
def __init__( | ||
self, | ||
characteristics: List[Characteristic], | ||
characteristics: Iterable[Characteristic], | ||
primary: bool = True, | ||
included_services: Iterable[Service] = (), | ||
) -> None: | ||
|
@@ -490,7 +494,7 @@ def __init__( | |
uuid: Union[str, bytes, UUID], | ||
properties: Characteristic.Properties, | ||
permissions: Union[str, Attribute.Permissions], | ||
value: Union[str, bytes, CharacteristicValue] = b'', | ||
value: Any = b'', | ||
descriptors: Sequence[Descriptor] = (), | ||
): | ||
super().__init__(uuid, permissions, value) | ||
|
@@ -525,7 +529,11 @@ class CharacteristicDeclaration(Attribute): | |
|
||
characteristic: Characteristic | ||
|
||
def __init__(self, characteristic: Characteristic, value_handle: int) -> None: | ||
def __init__( | ||
self, | ||
characteristic: Characteristic, | ||
value_handle: int, | ||
) -> None: | ||
declaration_bytes = ( | ||
struct.pack('<BH', characteristic.properties, value_handle) | ||
+ characteristic.uuid.to_pdu_bytes() | ||
|
@@ -705,7 +713,7 @@ class MappedCharacteristicAdapter(PackedCharacteristicAdapter): | |
''' | ||
Adapter that packs/unpacks characteristic values according to a standard | ||
Python `struct` format. | ||
The adapted `read_value` and `write_value` methods return/accept aa dictionary which | ||
The adapted `read_value` and `write_value` methods return/accept a dictionary which | ||
is packed/unpacked according to format, with the arguments extracted from the | ||
dictionary by key, in the same order as they occur in the `keys` parameter. | ||
''' | ||
|
@@ -735,6 +743,24 @@ def decode_value(self, value: bytes) -> str: | |
return value.decode('utf-8') | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
class SerializableCharacteristicAdapter(CharacteristicAdapter): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to solve the problem that CharacteristicAdapter is not considered as a Characteristic? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think it's possible to do that, and it would be nice to improve type checking. Currently, the adapter classes are bit hard to deal with when it comes to types, because they are like dynamic decorators, they are polymorphic at runtime (that's convenient to reuse the code, but not convenient for typing). I have an idea about how to fix this, by splitting the adapter classes into separate classes for attributes and attribute proxies. That should solve the problem. But that's a larger change, so I'd like to do that in a separate PR. |
||
''' | ||
Adapter that converts any class to/from bytes using the class' | ||
`to_bytes` and `__bytes__` methods, respectively. | ||
''' | ||
|
||
def __init__(self, characteristic, cls: Type[ByteSerializable]): | ||
super().__init__(characteristic) | ||
self.cls = cls | ||
|
||
def encode_value(self, value: SupportsBytes) -> bytes: | ||
return bytes(value) | ||
|
||
def decode_value(self, value: bytes) -> Any: | ||
return self.cls.from_bytes(value) | ||
|
||
|
||
# ----------------------------------------------------------------------------- | ||
class Descriptor(Attribute): | ||
''' | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope this can be a TypeVar, but it might always require a type without PEP 696 which is annoying.