dexter-controller 0.2.0__tar.gz → 0.2.2__tar.gz
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.
- {dexter_controller-0.2.0 → dexter_controller-0.2.2}/PKG-INFO +1 -1
- {dexter_controller-0.2.0 → dexter_controller-0.2.2}/pyproject.toml +1 -1
- {dexter_controller-0.2.0 → dexter_controller-0.2.2}/src/dexter_controller/ble_loadcell_device.py +32 -17
- {dexter_controller-0.2.0 → dexter_controller-0.2.2}/src/dexter_controller/dexter_hand_controller.py +19 -1
- {dexter_controller-0.2.0 → dexter_controller-0.2.2}/README.md +0 -0
- {dexter_controller-0.2.0 → dexter_controller-0.2.2}/src/dexter_controller/__init__.py +0 -0
- {dexter_controller-0.2.0 → dexter_controller-0.2.2}/src/dexter_controller/finger.py +0 -0
- {dexter_controller-0.2.0 → dexter_controller-0.2.2}/src/dexter_controller/loadcell_device.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: dexter-controller
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Dexter Controller for the HARP Load Cells
|
|
5
5
|
Author: Hardware and Software Platform, Champalimaud Foundation
|
|
6
6
|
Author-email: Hardware and Software Platform, Champalimaud Foundation <software@research.fchampalimaud.org>
|
{dexter_controller-0.2.0 → dexter_controller-0.2.2}/src/dexter_controller/ble_loadcell_device.py
RENAMED
|
@@ -26,10 +26,13 @@ class BLELoadCellEvent:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
class BLELoadCellDevice:
|
|
29
|
-
def __init__(self):
|
|
29
|
+
def __init__(self, scan_timeout=1.0):
|
|
30
30
|
self.identifier = f"BLE:{DEVICE_NAME}"
|
|
31
31
|
self._queue = deque()
|
|
32
32
|
self._queue_lock = threading.Lock()
|
|
33
|
+
self._recent_timestamps = deque(maxlen=NUM_READINGS * 2)
|
|
34
|
+
self._recent_timestamps_set = set()
|
|
35
|
+
self._scan_timeout = scan_timeout
|
|
33
36
|
|
|
34
37
|
self._stop_event = threading.Event()
|
|
35
38
|
self._ready_event = threading.Event()
|
|
@@ -59,29 +62,40 @@ class BLELoadCellDevice:
|
|
|
59
62
|
|
|
60
63
|
async def _discover_target(self):
|
|
61
64
|
target = DEVICE_NAME.lower()
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
f"BLE:{candidate.name or DEVICE_NAME} ({candidate.address})"
|
|
69
|
-
)
|
|
70
|
-
return candidate
|
|
71
|
-
|
|
72
|
-
return None
|
|
65
|
+
return await BleakScanner.find_device_by_filter(
|
|
66
|
+
lambda device, adv_data: (
|
|
67
|
+
target in (device.name or adv_data.local_name or "").lower()
|
|
68
|
+
),
|
|
69
|
+
timeout=self._scan_timeout,
|
|
70
|
+
)
|
|
73
71
|
|
|
74
72
|
def _notification_callback(self, _, data: bytearray):
|
|
75
73
|
if len(data) < PACKET_SIZE:
|
|
76
74
|
return
|
|
77
75
|
|
|
78
76
|
counter = struct.unpack_from("<i", data, 0)[0]
|
|
77
|
+
# make sure timestamps are monotonically increasing
|
|
78
|
+
readings = []
|
|
79
|
+
for reading_index in range(NUM_READINGS):
|
|
80
|
+
base = 4 + (reading_index * BLOCK_SIZE)
|
|
81
|
+
timestamp_us = struct.unpack_from("<Q", data, base)[0]
|
|
82
|
+
load_cells = list(struct.unpack_from(f"<{LOAD_CELLS}H", data, base + 8))
|
|
83
|
+
readings.append((timestamp_us, load_cells))
|
|
84
|
+
|
|
85
|
+
readings.sort(key=lambda x: x[0])
|
|
79
86
|
|
|
80
87
|
with self._queue_lock:
|
|
81
|
-
for
|
|
82
|
-
|
|
83
|
-
timestamp_us
|
|
84
|
-
|
|
88
|
+
for timestamp_us, load_cells in readings:
|
|
89
|
+
# ignore if it is a repeated timestamp
|
|
90
|
+
if timestamp_us in self._recent_timestamps_set:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if len(self._recent_timestamps) == self._recent_timestamps.maxlen:
|
|
94
|
+
expired_timestamp = self._recent_timestamps.popleft()
|
|
95
|
+
self._recent_timestamps_set.discard(expired_timestamp)
|
|
96
|
+
|
|
97
|
+
self._recent_timestamps.append(timestamp_us)
|
|
98
|
+
self._recent_timestamps_set.add(timestamp_us)
|
|
85
99
|
self._queue.append(
|
|
86
100
|
BLELoadCellEvent(
|
|
87
101
|
payload=load_cells,
|
|
@@ -93,6 +107,7 @@ class BLELoadCellDevice:
|
|
|
93
107
|
async def _run(self):
|
|
94
108
|
print("Searching for BLE device...")
|
|
95
109
|
device = await self._discover_target()
|
|
110
|
+
print(f"Found {self.identifier}")
|
|
96
111
|
if not device:
|
|
97
112
|
self._connection_error = RuntimeError("No matching BLE peripheral found")
|
|
98
113
|
self._ready_event.set()
|
|
@@ -102,7 +117,7 @@ class BLELoadCellDevice:
|
|
|
102
117
|
device,
|
|
103
118
|
disconnected_callback=lambda _: self._stop_event.set(),
|
|
104
119
|
)
|
|
105
|
-
|
|
120
|
+
print(f"Connecting to {self.identifier}")
|
|
106
121
|
try:
|
|
107
122
|
await self._client.connect()
|
|
108
123
|
await self._client.start_notify(CHAR_UUID, self._notification_callback)
|
{dexter_controller-0.2.0 → dexter_controller-0.2.2}/src/dexter_controller/dexter_hand_controller.py
RENAMED
|
@@ -12,12 +12,30 @@ class DexterHandController:
|
|
|
12
12
|
self,
|
|
13
13
|
mapping: dict | None = None, # {com_port: [Finger, ...]}
|
|
14
14
|
use_ble=False, # use BLE instead of serial
|
|
15
|
+
ble_scan_timeout=1.0, # seconds to scan for BLE devices in each connection attempt
|
|
15
16
|
):
|
|
17
|
+
"""Controller for Dexter hand load cells, supporting both serial and BLE devices.
|
|
18
|
+
|
|
19
|
+
Attributes
|
|
20
|
+
----------
|
|
21
|
+
mapping : dict
|
|
22
|
+
Optional mapping of serial COM ports to lists of Fingers. Ignored if use_ble is True. Example:
|
|
23
|
+
mapping = {
|
|
24
|
+
"COM3": [Finger.THUMB, Finger.INDEX],
|
|
25
|
+
"COM4": [Finger.MIDDLE, Finger.RING, Finger.PINKY],
|
|
26
|
+
}
|
|
27
|
+
use_ble : bool
|
|
28
|
+
If True, ignore mapping and connect to the first available BLE device with the expected service/characteristic.
|
|
29
|
+
ble_scan_timeout : float
|
|
30
|
+
Seconds to scan for BLE devices in each connection attempt (only relevant if use_ble is True).
|
|
31
|
+
"""
|
|
32
|
+
|
|
16
33
|
if not use_ble and not mapping:
|
|
17
34
|
raise ValueError("mapping cannot be empty when use_ble is False")
|
|
18
35
|
|
|
19
36
|
self._mapping = dict(mapping or {})
|
|
20
37
|
self._use_ble = use_ble
|
|
38
|
+
self._ble_scan_timeout = ble_scan_timeout
|
|
21
39
|
self._lock = threading.Lock()
|
|
22
40
|
|
|
23
41
|
self._finger_to_binding = {} # {Finger: (device, start, stop)}
|
|
@@ -103,7 +121,7 @@ class DexterHandController:
|
|
|
103
121
|
self._reset_connections()
|
|
104
122
|
|
|
105
123
|
try:
|
|
106
|
-
device = BLELoadCellDevice()
|
|
124
|
+
device = BLELoadCellDevice(scan_timeout=self._ble_scan_timeout)
|
|
107
125
|
except Exception as e:
|
|
108
126
|
self.unavailable_ports.append("BLE:Dexter")
|
|
109
127
|
print(f"Failed to connect to BLE device: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{dexter_controller-0.2.0 → dexter_controller-0.2.2}/src/dexter_controller/loadcell_device.py
RENAMED
|
File without changes
|