sok-ble 0.1.1__py3-none-any.whl → 0.1.3__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.
@@ -9,10 +9,14 @@ import struct
9
9
  import logging
10
10
  import statistics
11
11
 
12
+ import async_timeout
13
+ from bleak import BleakError
14
+
12
15
  from bleak.backends.device import BLEDevice
13
16
 
14
17
  from sok_ble.const import UUID_RX, UUID_TX, _sok_command
15
18
  from sok_ble.sok_parser import SokParser
19
+ from sok_ble.exceptions import BLEConnectionError
16
20
 
17
21
  logger = logging.getLogger(__name__)
18
22
 
@@ -48,51 +52,86 @@ class SokBluetoothDevice:
48
52
  async def _connect(self) -> AsyncIterator[BleakClientWithServiceCache]:
49
53
  """Connect to the device and yield a BLE client."""
50
54
  logger.debug("Connecting to %s", self._ble_device.address)
51
- if establish_connection:
52
- client = await establish_connection(
53
- BleakClientWithServiceCache,
54
- self._ble_device,
55
- self._ble_device.name or self._ble_device.address,
56
- adapter=self._adapter,
57
- )
58
- else:
59
- client = BleakClientWithServiceCache(
60
- self._ble_device,
61
- adapter=self._adapter,
62
- )
63
- await client.connect()
64
- try:
65
- yield client
66
- finally:
67
- await client.disconnect()
68
- logger.debug("Disconnected from %s", self._ble_device.address)
55
+ last_err: Exception | None = None
56
+ for attempt in range(3):
57
+ try:
58
+ if establish_connection:
59
+ client = await establish_connection(
60
+ BleakClientWithServiceCache,
61
+ self._ble_device,
62
+ self._ble_device.name or self._ble_device.address,
63
+ adapter=self._adapter,
64
+ )
65
+ else:
66
+ client = BleakClientWithServiceCache(
67
+ self._ble_device, adapter=self._adapter
68
+ )
69
+ await client.connect()
70
+
71
+ # Force service discovery
72
+ async with async_timeout.timeout(5):
73
+ _ = client.services
74
+ await asyncio.sleep(0.15)
75
+
76
+ try:
77
+ yield client
78
+ finally:
79
+ await client.disconnect()
80
+ logger.debug(
81
+ "Disconnected from %s", self._ble_device.address
82
+ )
83
+ return
84
+ except (BleakError, asyncio.TimeoutError) as err:
85
+ last_err = err
86
+ logger.debug(
87
+ "BLE connect attempt %s failed for %s: %s",
88
+ attempt + 1,
89
+ self._ble_device.address,
90
+ err,
91
+ )
92
+ await asyncio.sleep(0.5)
93
+
94
+ raise BLEConnectionError(
95
+ f"Unable to establish GATT connection to {self._ble_device.address}"
96
+ ) from last_err
69
97
 
70
98
  async def _send_command(
71
99
  self, client: BleakClientWithServiceCache, cmd: int, expected: int
72
100
  ) -> bytes:
73
101
  """Send a command and return the response bytes with the given header."""
74
102
 
75
- # If the client supports notifications (real BLE client), prefer that
76
- start_notify = getattr(client, "start_notify", None)
77
- if start_notify is None:
78
- await client.write_gatt_char(UUID_TX, _sok_command(cmd))
79
- data = bytes(await client.read_gatt_char(UUID_RX))
80
- return data
81
-
82
- queue: asyncio.Queue[bytes] = asyncio.Queue()
83
-
84
- def handler(_: int, data: bytearray) -> None:
85
- queue.put_nowait(bytes(data))
86
-
87
- await client.start_notify(UUID_RX, handler)
88
- try:
89
- await client.write_gatt_char(UUID_TX, _sok_command(cmd))
90
- while True:
91
- data = await asyncio.wait_for(queue.get(), 5.0)
92
- if struct.unpack_from(">H", data)[0] == expected:
103
+ for attempt in range(2):
104
+ try:
105
+ start_notify = getattr(client, "start_notify", None)
106
+ if start_notify is None:
107
+ await client.write_gatt_char(UUID_TX, _sok_command(cmd))
108
+ data = bytes(await client.read_gatt_char(UUID_RX))
93
109
  return data
94
- finally:
95
- await client.stop_notify(UUID_RX)
110
+
111
+ queue: asyncio.Queue[bytes] = asyncio.Queue()
112
+
113
+ def handler(_: int, data: bytearray) -> None:
114
+ queue.put_nowait(bytes(data))
115
+
116
+ await client.start_notify(UUID_RX, handler)
117
+ try:
118
+ await client.write_gatt_char(UUID_TX, _sok_command(cmd))
119
+ while True:
120
+ data = await asyncio.wait_for(queue.get(), 5.0)
121
+ if struct.unpack_from(">H", data)[0] == expected:
122
+ return data
123
+ finally:
124
+ await client.stop_notify(UUID_RX)
125
+ except BleakError as err:
126
+ if attempt == 0:
127
+ logger.debug(
128
+ "BLE command attempt failed for %s: %s",
129
+ self._ble_device.address,
130
+ err,
131
+ )
132
+ await asyncio.sleep(0.2)
133
+ continue
134
+ raise
96
135
 
97
136
  async def async_update(self) -> None:
98
137
  """Poll the device for all telemetry and update attributes."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sok-ble
3
- Version: 0.1.1
3
+ Version: 0.1.3
4
4
  Summary: SOK BLE battery interface library
5
5
  Author-email: Mitchell Carlson <mitchell.carlson.pro@gmail.com>
6
6
  License: Apache-2.0
@@ -12,8 +12,9 @@ Classifier: License :: OSI Approved :: Apache Software License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.11
14
14
  Description-Content-Type: text/markdown
15
+ Requires-Dist: async-timeout>=5.0.1
15
16
  Requires-Dist: bleak>=0.22.3
16
- Requires-Dist: bleak-retry-connector>=3.10.0
17
+ Requires-Dist: bleak-retry-connector>=3.9.0
17
18
 
18
19
  # SOK BLE
19
20
 
@@ -38,3 +39,7 @@ async def main() -> None:
38
39
 
39
40
  asyncio.run(main())
40
41
  ```
42
+
43
+ ## References
44
+ [@zuccaro's comment](https://github.com/Louisvdw/dbus-serialbattery/issues/350#issuecomment-1500658941)
45
+ [Bluetooth-Devices/inkbird-ble](https://github.com/Bluetooth-Devices/inkbird-ble)
@@ -1,9 +1,9 @@
1
1
  sok_ble/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sok_ble/const.py,sha256=mHtJTbWz_dG3v1lhZrLDzMf-QAG9v5QWlHU9ZKsyYdg,998
3
3
  sok_ble/exceptions.py,sha256=7H1yUqqnrKyi3LwxRCMF7RkuGp_rgdy9j35nrMOgz44,247
4
- sok_ble/sok_bluetooth_device.py,sha256=ig11wLwjZFC6HEFi7hzB7_Jy1T8syyNDOTYWUbkJR4Y,6353
4
+ sok_ble/sok_bluetooth_device.py,sha256=DlTqxtw5ZZtN-Y8gN3Gm8xGeiRVLQ_IpIxtXtHEBuWo,7878
5
5
  sok_ble/sok_parser.py,sha256=KXYxsR4868vWFs2AAI7o7XfzjNZiDgfpsmz3dFgjLco,4323
6
- sok_ble-0.1.1.dist-info/METADATA,sha256=Hub0gJn6lH2WJVXpSFaiYQ2vKv4nVdDvypZyNITBN2g,1233
7
- sok_ble-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- sok_ble-0.1.1.dist-info/top_level.txt,sha256=WTVtlZ2sADYAxKEBkDJPUn4hFAH9NfIZ9Q2HP2RKpbw,8
9
- sok_ble-0.1.1.dist-info/RECORD,,
6
+ sok_ble-0.1.3.dist-info/METADATA,sha256=m-hMy2a7HnPx3w40ViluiR5B56tNF7RsqG9PeroSe6I,1469
7
+ sok_ble-0.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ sok_ble-0.1.3.dist-info/top_level.txt,sha256=WTVtlZ2sADYAxKEBkDJPUn4hFAH9NfIZ9Q2HP2RKpbw,8
9
+ sok_ble-0.1.3.dist-info/RECORD,,