Skip to content

Commit ca191c4

Browse files
authored
Merge pull request #29 from TapWithUs/28-native-raw-sensor-data-scaling
optional scaling for raw data
2 parents c816774 + ef02a9d commit ca191c4

8 files changed

Lines changed: 65 additions & 17 deletions

File tree

History.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,9 @@ ______________________
77
* Mac and Linux backends unified to posix backend.
88

99
### Known Issues
10-
* Windows backend -
11-
* Raw sensor data rate might be lower than expected.
12-
* Sometimes a Tap strap wouldn't be detected upon connection. In this case try restarting your Tap and/or the Python application. In worst case scenario re-pair your Tap.
13-
* Spatial features are still not available for Windows backend.
14-
* MacOS & Linux backends -
15-
* Doesn't support multiple Tap strap connections.
16-
* Raw sensor data is given unscaled (i.e. unitless), thereforein order to scale to physical units need to multiply by the relevant scale factor
10+
* Doesn't support multiple Tap strap connections.
11+
* Windows backend can only connect with a Tap that is not connected to the PC.
12+
1713

1814
## 0.5.1 (2024-01-01)
1915
______________________

Readme.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,9 @@ For example
8585
from tapsdk import TapInputMode
8686
tap_device.set_input_mode(TapInputMode("controller"))
8787
```
88-
Also, when instantiating a ```TapInputMode``` for raw sensors mode, additional argument ```sensitivity``` (list with 3 integers) is optional for example
88+
Also, when instantiating a ```TapInputMode``` for raw sensors mode, additional arguments ```sensitivity``` (list with 3 integers) and ```scaled``` are optional. When ```scaled``` is set to ```True```, the IMU values will be converted to dps and g according to the given sensitivity.
8989
```python
90-
tap_device.set_input_mode(TapInputMode("raw", sensitivity=[2,1,4]))
90+
tap_device.set_input_mode(TapInputMode("raw", sensitivity=[2,1,4], scaled=True))
9191

9292
2. ```set_input_type(self, input_type:InputType, identifier):```
9393
> **Only for TapXR and with Spatial Control experimental firmware**

examples/basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async def run():
7777

7878
logger.info("Set Raw Mode for 5 seconds")
7979
await asyncio.sleep(2)
80-
await client.set_input_mode(TapInputMode("raw", sensitivity=[0, 0, 0]))
80+
await client.set_input_mode(TapInputMode("raw", sensitivity=[0, 0, 0], scaled=True))
8181
await asyncio.sleep(5)
8282

8383

tapsdk/inputmodes.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,19 @@
55

66

77
class TapInputMode:
8-
def __init__(self, mode, sensitivity=[0, 0, 0]):
8+
def __init__(self, mode, sensitivity=None, scaled=False):
99
self._modes = {
1010
"text": {"name": "Text Mode", "code": bytearray([0x3, 0xc, 0x0, 0x0])},
1111
"controller": {"name": "Controller Mode", "code": bytearray([0x3, 0xc, 0x0, 0x1])},
1212
"controller_text": {"name": "Controller and Text Mode", "code": bytearray([0x3, 0xc, 0x0, 0x3])},
1313
"raw": {"name": "Raw sensors Mode", "code": bytearray([0x3, 0xc, 0x0, 0xa])}
1414
}
15-
self.sensitivity = sensitivity
15+
self.sensitivity = sensitivity or [0, 0, 0]
16+
self.scaled = scaled
1617
if mode in self._modes.keys():
1718
self.mode = mode
1819
if mode == "raw":
19-
self._register_sensitivity(sensitivity)
20+
self._register_sensitivity(self.sensitivity)
2021
else:
2122
logger.warning("Invalid mode \"%s\". Set to \"text\"", mode)
2223
self.mode = "text"

tapsdk/parsers.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ def tap_data_msg(data: bytearray):
1717
return [data[0]]
1818

1919

20-
def raw_data_msg(data: bytearray):
20+
def raw_data_msg(data: bytearray, scaled: bool = False, sensitivity=None):
2121
'''
22-
raw data is packed into messages with the following structure:
22+
Parses raw data messages into structured data with optional scaling.
23+
Raw data is packed into messages with the following structure:
2324
[msg_type (1 bit)][timestamp (31 bit)][payload (12 - 30 bytes)]
2425
* msg type - '0' for imu message
2526
- '1' for accelerometers message
@@ -34,6 +35,13 @@ def raw_data_msg(data: bytearray):
3435
...]
3536
3637
'''
38+
if sensitivity is None:
39+
sensitivity = [0, 0, 0]
40+
41+
finger_acc_scales = [31.25, 3.91, 7.81, 15.62, 31.25]
42+
gyro_scales = [17.5, 4.375, 8.75, 17.5, 35, 70]
43+
imu_acc_scales = [0.122, 0.061, 0.122, 0.244, 0.488]
44+
3745
L = len(data)
3846
ptr = 0
3947
messages = []
@@ -56,8 +64,19 @@ def raw_data_msg(data: bytearray):
5664
# parse payload
5765
payload = []
5866
for i in range(num_of_samples):
59-
payload.append(int.from_bytes(data[ptr:ptr+2], "little", signed=True))
67+
val = int.from_bytes(data[ptr:ptr+2], "little", signed=True)
6068
ptr += 2
69+
payload.append(val)
70+
71+
if scaled:
72+
if msg == "imu":
73+
g_scale = gyro_scales[sensitivity[1]] / 1000.0
74+
xl_scale = imu_acc_scales[sensitivity[2]] / 1000.0
75+
payload = [payload[j] * g_scale if j < 3 else payload[j] * xl_scale
76+
for j in range(num_of_samples)]
77+
else: # accl message
78+
acc_scale = finger_acc_scales[sensitivity[0]] / 1000.0
79+
payload = [v * acc_scale for v in payload]
6180

6281
messages.append({"type": msg, "ts": ts, "payload": payload})
6382
return messages

tapsdk/tap.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,12 @@ def on_tapped(self, identifier, data):
178178

179179
def on_raw_data(self, identifier, data):
180180
if self.raw_data_event_cb:
181-
args = parsers.raw_data_msg(data)
181+
scale = False
182+
sensitivity = [0, 0, 0]
183+
if isinstance(self.input_mode, TapInputMode) and self.input_mode.mode == "raw":
184+
scale = getattr(self.input_mode, "scaled", scale)
185+
sensitivity = getattr(self.input_mode, "sensitivity", sensitivity)
186+
args = parsers.raw_data_msg(data, scaled=scale, sensitivity=sensitivity)
182187
self.raw_data_event_cb(identifier, args)
183188

184189
def on_air_gesture(self, identifier, data):

tests/test_inputmodes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,11 @@ def test_input_mode_raw_with_sensitivity():
1313
assert mode.get_command() == bytearray([0x3, 0xc, 0x0, 0xa, 1, 2, 3])
1414

1515

16+
def test_input_mode_raw_scaled():
17+
mode = TapInputMode("raw", scaled=True)
18+
assert mode.scaled is True
19+
assert mode.get_command() == bytearray([0x3, 0xc, 0x0, 0xa, 0, 0, 0])
20+
21+
1622
def test_input_type_command():
1723
assert input_type_command(InputType.MOUSE) == bytearray([0x3, 0xd, 0x0, InputType.MOUSE.value])

tests/test_parsers.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,24 @@ def test_raw_data_msg():
5353
{'type': 'imu', 'ts': 123, 'payload': imu_samples},
5454
{'type': 'accl', 'ts': 456, 'payload': accl_samples}
5555
]
56+
57+
58+
def test_raw_data_msg_scaled():
59+
ts = 123
60+
imu_ts = ts
61+
imu_bytes = imu_ts.to_bytes(4, 'little', signed=False)
62+
imu_samples = [100, -100, 200, -200, 300, -300]
63+
payload = b''
64+
for v in imu_samples:
65+
payload += v.to_bytes(2, 'little', signed=True)
66+
packet = bytearray(imu_bytes + payload)
67+
result = parsers.raw_data_msg(packet, scaled=True, sensitivity=[0, 0, 0])
68+
g_scale = 17.5 / 1000
69+
a_scale = 0.122 / 1000
70+
expected = [imu_samples[i] * g_scale if i < 3 else imu_samples[i] * a_scale
71+
for i in range(6)]
72+
assert result == [{
73+
'type': 'imu',
74+
'ts': 123,
75+
'payload': expected
76+
}]

0 commit comments

Comments
 (0)