bumble 0.0.178__py3-none-any.whl → 0.0.180__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
bumble/gatt_client.py CHANGED
@@ -38,6 +38,7 @@ from typing import (
38
38
  Any,
39
39
  Iterable,
40
40
  Type,
41
+ Set,
41
42
  TYPE_CHECKING,
42
43
  )
43
44
 
@@ -128,7 +129,7 @@ class ServiceProxy(AttributeProxy):
128
129
  included_services: List[ServiceProxy]
129
130
 
130
131
  @staticmethod
131
- def from_client(service_class, client, service_uuid):
132
+ def from_client(service_class, client: Client, service_uuid: UUID):
132
133
  # The service and its characteristics are considered to have already been
133
134
  # discovered
134
135
  services = client.get_services_by_uuid(service_uuid)
@@ -206,11 +207,11 @@ class CharacteristicProxy(AttributeProxy):
206
207
 
207
208
  return await self.client.subscribe(self, subscriber, prefer_notify)
208
209
 
209
- async def unsubscribe(self, subscriber=None):
210
+ async def unsubscribe(self, subscriber=None, force=False):
210
211
  if subscriber in self.subscribers:
211
212
  subscriber = self.subscribers.pop(subscriber)
212
213
 
213
- return await self.client.unsubscribe(self, subscriber)
214
+ return await self.client.unsubscribe(self, subscriber, force)
214
215
 
215
216
  def __str__(self) -> str:
216
217
  return (
@@ -246,8 +247,12 @@ class ProfileServiceProxy:
246
247
  class Client:
247
248
  services: List[ServiceProxy]
248
249
  cached_values: Dict[int, Tuple[datetime, bytes]]
249
- notification_subscribers: Dict[int, Callable[[bytes], Any]]
250
- indication_subscribers: Dict[int, Callable[[bytes], Any]]
250
+ notification_subscribers: Dict[
251
+ int, Set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
252
+ ]
253
+ indication_subscribers: Dict[
254
+ int, Set[Union[CharacteristicProxy, Callable[[bytes], Any]]]
255
+ ]
251
256
  pending_response: Optional[asyncio.futures.Future[ATT_PDU]]
252
257
  pending_request: Optional[ATT_PDU]
253
258
 
@@ -257,10 +262,8 @@ class Client:
257
262
  self.request_semaphore = asyncio.Semaphore(1)
258
263
  self.pending_request = None
259
264
  self.pending_response = None
260
- self.notification_subscribers = (
261
- {}
262
- ) # Notification subscribers, by attribute handle
263
- self.indication_subscribers = {} # Indication subscribers, by attribute handle
265
+ self.notification_subscribers = {} # Subscriber set, by attribute handle
266
+ self.indication_subscribers = {} # Subscriber set, by attribute handle
264
267
  self.services = []
265
268
  self.cached_values = {}
266
269
 
@@ -682,8 +685,8 @@ class Client:
682
685
  async def discover_descriptors(
683
686
  self,
684
687
  characteristic: Optional[CharacteristicProxy] = None,
685
- start_handle=None,
686
- end_handle=None,
688
+ start_handle: Optional[int] = None,
689
+ end_handle: Optional[int] = None,
687
690
  ) -> List[DescriptorProxy]:
688
691
  '''
689
692
  See Vol 3, Part G - 4.7.1 Discover All Characteristic Descriptors
@@ -789,7 +792,12 @@ class Client:
789
792
 
790
793
  return attributes
791
794
 
792
- async def subscribe(self, characteristic, subscriber=None, prefer_notify=True):
795
+ async def subscribe(
796
+ self,
797
+ characteristic: CharacteristicProxy,
798
+ subscriber: Optional[Callable[[bytes], Any]] = None,
799
+ prefer_notify: bool = True,
800
+ ) -> None:
793
801
  # If we haven't already discovered the descriptors for this characteristic,
794
802
  # do it now
795
803
  if not characteristic.descriptors_discovered:
@@ -826,6 +834,7 @@ class Client:
826
834
  subscriber_set = subscribers.setdefault(characteristic.handle, set())
827
835
  if subscriber is not None:
828
836
  subscriber_set.add(subscriber)
837
+
829
838
  # Add the characteristic as a subscriber, which will result in the
830
839
  # characteristic emitting an 'update' event when a notification or indication
831
840
  # is received
@@ -833,7 +842,18 @@ class Client:
833
842
 
834
843
  await self.write_value(cccd, struct.pack('<H', bits), with_response=True)
835
844
 
836
- async def unsubscribe(self, characteristic, subscriber=None):
845
+ async def unsubscribe(
846
+ self,
847
+ characteristic: CharacteristicProxy,
848
+ subscriber: Optional[Callable[[bytes], Any]] = None,
849
+ force: bool = False,
850
+ ) -> None:
851
+ '''
852
+ Unsubscribe from a characteristic.
853
+
854
+ If `force` is True, this will write zeros to the CCCD when there are no
855
+ subscribers left, even if there were already no registered subscribers.
856
+ '''
837
857
  # If we haven't already discovered the descriptors for this characteristic,
838
858
  # do it now
839
859
  if not characteristic.descriptors_discovered:
@@ -847,31 +867,45 @@ class Client:
847
867
  logger.warning('unsubscribing from characteristic with no CCCD descriptor')
848
868
  return
849
869
 
870
+ # Check if the characteristic has subscribers
871
+ if not (
872
+ characteristic.handle in self.notification_subscribers
873
+ or characteristic.handle in self.indication_subscribers
874
+ ):
875
+ if not force:
876
+ return
877
+
878
+ # Remove the subscriber(s)
850
879
  if subscriber is not None:
851
880
  # Remove matching subscriber from subscriber sets
852
881
  for subscriber_set in (
853
882
  self.notification_subscribers,
854
883
  self.indication_subscribers,
855
884
  ):
856
- subscribers = subscriber_set.get(characteristic.handle, [])
857
- if subscriber in subscribers:
885
+ if (
886
+ subscribers := subscriber_set.get(characteristic.handle)
887
+ ) and subscriber in subscribers:
858
888
  subscribers.remove(subscriber)
859
889
 
860
890
  # Cleanup if we removed the last one
861
891
  if not subscribers:
862
892
  del subscriber_set[characteristic.handle]
863
893
  else:
864
- # Remove all subscribers for this attribute from the sets!
894
+ # Remove all subscribers for this attribute from the sets
865
895
  self.notification_subscribers.pop(characteristic.handle, None)
866
896
  self.indication_subscribers.pop(characteristic.handle, None)
867
897
 
868
- if not self.notification_subscribers and not self.indication_subscribers:
898
+ # Update the CCCD
899
+ if not (
900
+ characteristic.handle in self.notification_subscribers
901
+ or characteristic.handle in self.indication_subscribers
902
+ ):
869
903
  # No more subscribers left
870
904
  await self.write_value(cccd, b'\x00\x00', with_response=True)
871
905
 
872
906
  async def read_value(
873
907
  self, attribute: Union[int, AttributeProxy], no_long_read: bool = False
874
- ) -> Any:
908
+ ) -> bytes:
875
909
  '''
876
910
  See Vol 3, Part G - 4.8.1 Read Characteristic Value
877
911
 
@@ -1067,7 +1101,7 @@ class Client:
1067
1101
  def on_att_handle_value_notification(self, notification):
1068
1102
  # Call all subscribers
1069
1103
  subscribers = self.notification_subscribers.get(
1070
- notification.attribute_handle, []
1104
+ notification.attribute_handle, set()
1071
1105
  )
1072
1106
  if not subscribers:
1073
1107
  logger.warning('!!! received notification with no subscriber')
@@ -1081,7 +1115,9 @@ class Client:
1081
1115
 
1082
1116
  def on_att_handle_value_indication(self, indication):
1083
1117
  # Call all subscribers
1084
- subscribers = self.indication_subscribers.get(indication.attribute_handle, [])
1118
+ subscribers = self.indication_subscribers.get(
1119
+ indication.attribute_handle, set()
1120
+ )
1085
1121
  if not subscribers:
1086
1122
  logger.warning('!!! received indication with no subscriber')
1087
1123