senswar 0.1.0__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.
- senswar-0.1.0/.gitignore +3 -0
- senswar-0.1.0/LICENSE +21 -0
- senswar-0.1.0/PKG-INFO +304 -0
- senswar-0.1.0/README.md +280 -0
- senswar-0.1.0/examples/play_haptic.py +47 -0
- senswar-0.1.0/examples/read_battery.py +33 -0
- senswar-0.1.0/examples/read_charger.py +36 -0
- senswar-0.1.0/examples/set_led.py +35 -0
- senswar-0.1.0/examples/stream_imu.py +44 -0
- senswar-0.1.0/examples/stream_temperature.py +38 -0
- senswar-0.1.0/pyproject.toml +40 -0
- senswar-0.1.0/src/senswar/__init__.py +36 -0
- senswar-0.1.0/src/senswar/client.py +227 -0
- senswar-0.1.0/src/senswar/exceptions.py +21 -0
- senswar-0.1.0/src/senswar/modules/__init__.py +25 -0
- senswar-0.1.0/src/senswar/modules/battery.py +124 -0
- senswar-0.1.0/src/senswar/modules/charger.py +194 -0
- senswar-0.1.0/src/senswar/modules/haptic.py +165 -0
- senswar-0.1.0/src/senswar/modules/imu.py +209 -0
- senswar-0.1.0/src/senswar/modules/led.py +150 -0
- senswar-0.1.0/src/senswar/modules/temperature.py +133 -0
- senswar-0.1.0/src/senswar/py.typed +1 -0
- senswar-0.1.0/src/senswar/uuids.py +21 -0
- senswar-0.1.0/tests/test_battery.py +41 -0
- senswar-0.1.0/tests/test_charger.py +41 -0
- senswar-0.1.0/tests/test_haptic.py +98 -0
- senswar-0.1.0/tests/test_imu.py +104 -0
- senswar-0.1.0/tests/test_led.py +78 -0
- senswar-0.1.0/tests/test_temperature.py +106 -0
senswar-0.1.0/.gitignore
ADDED
senswar-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sens Wear
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
senswar-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: senswar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for connecting to Senswar hardware over BLE.
|
|
5
|
+
Project-URL: Homepage, https://github.com/SenseraTechnologies
|
|
6
|
+
Author: Sensera Technologies
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: battery,ble,sensewear,senswar,wearable
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: bleak>=0.22.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: build>=1.2.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: twine>=5.0.0; extra == 'dev'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# Senswar Python SDK
|
|
26
|
+
|
|
27
|
+
Python SDK for connecting to Senswar/SensWear hardware over BLE.
|
|
28
|
+
|
|
29
|
+
This first SDK slice supports:
|
|
30
|
+
|
|
31
|
+
- Discovering and connecting to a Senswar device advertising as `Sens Wear ...`.
|
|
32
|
+
- Reading the custom power service battery gauge characteristic.
|
|
33
|
+
- Subscribing to battery gauge notifications.
|
|
34
|
+
- Reading the custom power service charger state characteristic.
|
|
35
|
+
- Subscribing to charger state notifications.
|
|
36
|
+
- Reading and writing the custom LED service color characteristic.
|
|
37
|
+
- Subscribing to custom IMU quaternion and acceleration notifications.
|
|
38
|
+
- Reading, configuring, and subscribing to custom temperature notifications.
|
|
39
|
+
- Writing custom haptic vibration patterns.
|
|
40
|
+
|
|
41
|
+
## Install for local development
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
44
|
+
cd C:\Users\salamid1\Desktop\Projects\SenseWear\SDKs\Python
|
|
45
|
+
python -m pip install -e .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Read the battery gauge
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from senswar import SenswarClient
|
|
53
|
+
|
|
54
|
+
async def main() -> None:
|
|
55
|
+
async with SenswarClient() as device:
|
|
56
|
+
state = await device.battery.read()
|
|
57
|
+
print(f"Battery: {state.state_of_charge_percent:.1f}%")
|
|
58
|
+
print(f"Voltage: {state.voltage_mv} mV")
|
|
59
|
+
print(f"Temperature: {state.temperature_c:.1f} C")
|
|
60
|
+
|
|
61
|
+
asyncio.run(main())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
To connect to a known BLE address or exact advertised name, pass it to the client:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
async with SenswarClient("Sens Wear (Regulator)") as device:
|
|
68
|
+
state = await device.battery.read()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Read the charger state
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import asyncio
|
|
75
|
+
from senswar import SenswarClient
|
|
76
|
+
|
|
77
|
+
async def main() -> None:
|
|
78
|
+
async with SenswarClient() as device:
|
|
79
|
+
state = await device.charger.read()
|
|
80
|
+
print(f"Power good: {state.power_good}")
|
|
81
|
+
print(f"Charging: {state.charging}")
|
|
82
|
+
print(f"Charged: {state.charged}")
|
|
83
|
+
print(f"Fault present: {state.has_fault}")
|
|
84
|
+
|
|
85
|
+
asyncio.run(main())
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Set the LED color
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import asyncio
|
|
92
|
+
from senswar import LedColor, SenswarClient
|
|
93
|
+
|
|
94
|
+
async def main() -> None:
|
|
95
|
+
async with SenswarClient() as device:
|
|
96
|
+
await device.led.set(LedColor(red=255, green=0, blue=0))
|
|
97
|
+
current = await device.led.read()
|
|
98
|
+
print(current.to_hex(include_white=True))
|
|
99
|
+
|
|
100
|
+
asyncio.run(main())
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
You can also use hex strings, tuples, or the raw firmware integer:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
await device.led.set("#00ff00")
|
|
107
|
+
await device.led.set((0, 0, 255, 0))
|
|
108
|
+
await device.led.set(0x000000ff)
|
|
109
|
+
await device.led.off()
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Stream the IMU
|
|
113
|
+
|
|
114
|
+
The IMU service is notify-only. Subscribing to either IMU characteristic enables firmware-side IMU streaming.
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
import asyncio
|
|
118
|
+
from senswar import SenswarClient
|
|
119
|
+
|
|
120
|
+
async def main() -> None:
|
|
121
|
+
async with SenswarClient() as device:
|
|
122
|
+
def on_quaternion(sample):
|
|
123
|
+
print(sample.to_tuple())
|
|
124
|
+
|
|
125
|
+
def on_acceleration(sample):
|
|
126
|
+
print(sample.to_tuple())
|
|
127
|
+
|
|
128
|
+
await device.imu.subscribe_quaternion(on_quaternion)
|
|
129
|
+
await device.imu.subscribe_linear_acceleration(on_acceleration)
|
|
130
|
+
await asyncio.sleep(10)
|
|
131
|
+
await device.imu.unsubscribe_all()
|
|
132
|
+
|
|
133
|
+
asyncio.run(main())
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Read and stream temperature
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import asyncio
|
|
140
|
+
from senswar import SenswarClient
|
|
141
|
+
|
|
142
|
+
async def main() -> None:
|
|
143
|
+
async with SenswarClient() as device:
|
|
144
|
+
await device.temperature.set_sampling_rate_hz(2)
|
|
145
|
+
|
|
146
|
+
latest = await device.temperature.read()
|
|
147
|
+
print(f"Latest: {latest.temperature_c:.3f} C")
|
|
148
|
+
|
|
149
|
+
def on_temperature(sample):
|
|
150
|
+
print(f"Stream: {sample.temperature_c:.3f} C")
|
|
151
|
+
|
|
152
|
+
await device.temperature.subscribe(on_temperature)
|
|
153
|
+
await asyncio.sleep(10)
|
|
154
|
+
await device.temperature.unsubscribe()
|
|
155
|
+
|
|
156
|
+
asyncio.run(main())
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Play haptic vibration
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
import asyncio
|
|
163
|
+
from senswar import HapticFrame, HapticPattern, SenswarClient
|
|
164
|
+
|
|
165
|
+
async def main() -> None:
|
|
166
|
+
async with SenswarClient() as device:
|
|
167
|
+
await device.haptic.vibrate(duration_ms=150, intensity=255)
|
|
168
|
+
|
|
169
|
+
pattern = HapticPattern.from_frames([
|
|
170
|
+
HapticFrame(duration_ms=80, intensity=220),
|
|
171
|
+
HapticFrame(duration_ms=60, intensity=0),
|
|
172
|
+
HapticFrame(duration_ms=120, intensity=180),
|
|
173
|
+
])
|
|
174
|
+
await device.haptic.play(pattern)
|
|
175
|
+
|
|
176
|
+
asyncio.run(main())
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Battery gauge fields
|
|
180
|
+
|
|
181
|
+
The SDK maps the firmware's `power_lbs_gauge_state` payload. The current firmware bridge forwards BQ27427 temperature and state-of-charge values in 0.1-unit resolution.
|
|
182
|
+
|
|
183
|
+
| SDK field | Unit |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| `temperature_deci_c` | 0.1 degrees Celsius |
|
|
186
|
+
| `voltage_mv` | millivolts |
|
|
187
|
+
| `average_current_ma` | milliamps |
|
|
188
|
+
| `average_power_mw` | milliwatts |
|
|
189
|
+
| `state_of_charge_deci_percent` | 0.1 percent |
|
|
190
|
+
| `nominal_available_capacity_mah` | mAh |
|
|
191
|
+
| `full_battery_capacity_mah` | mAh |
|
|
192
|
+
| `remaining_capacity_mah` | mAh |
|
|
193
|
+
|
|
194
|
+
Convenience properties expose `temperature_c` and `state_of_charge_percent`.
|
|
195
|
+
|
|
196
|
+
## Charger fields
|
|
197
|
+
|
|
198
|
+
The SDK maps the firmware's `power_lbs_charger_state` payload. It is a 32-bit flags value copied from the BQ25180 charger state bitfield.
|
|
199
|
+
|
|
200
|
+
| SDK property | Firmware bit |
|
|
201
|
+
| --- | --- |
|
|
202
|
+
| `button_pressed` | 0 |
|
|
203
|
+
| `wake1` | 1 |
|
|
204
|
+
| `wake2` | 2 |
|
|
205
|
+
| `shipment_mode` | 3 |
|
|
206
|
+
| `shutdown_mode` | 4 |
|
|
207
|
+
| `power_good` | 5 |
|
|
208
|
+
| `charging` | 6 |
|
|
209
|
+
| `charged` | 7 |
|
|
210
|
+
| `thermal_regulation` | 8 |
|
|
211
|
+
| `battery_uvlo` | 9 |
|
|
212
|
+
| `thermal_normal` | 10 |
|
|
213
|
+
| `thermal_warm_or_hot` | 11 |
|
|
214
|
+
| `thermal_warm` | 12 |
|
|
215
|
+
| `thermal_cool` | 13 |
|
|
216
|
+
| `safety_timer_fault` | 14 |
|
|
217
|
+
| `thermal_system_fault` | 15 |
|
|
218
|
+
| `battery_uvlo_fault` | 16 |
|
|
219
|
+
| `battery_ocp_fault` | 17 |
|
|
220
|
+
|
|
221
|
+
The `has_fault` convenience property is true when any charger fault bit is set.
|
|
222
|
+
|
|
223
|
+
## LED color fields
|
|
224
|
+
|
|
225
|
+
The SDK maps the firmware's `led_color_t` payload. The characteristic is a 4-byte little-endian RGBW value.
|
|
226
|
+
|
|
227
|
+
| SDK field | Firmware byte |
|
|
228
|
+
| --- | --- |
|
|
229
|
+
| `red` | 0 |
|
|
230
|
+
| `green` | 1 |
|
|
231
|
+
| `blue` | 2 |
|
|
232
|
+
| `white` | 3 |
|
|
233
|
+
|
|
234
|
+
The LED color characteristic UUID is `3c688943-4143-470d-a798-4629803a1983`. Writing `0x00000000` turns the LEDs off in the current firmware bridge.
|
|
235
|
+
|
|
236
|
+
## IMU fields
|
|
237
|
+
|
|
238
|
+
The SDK maps the firmware's `imu_lbs_quat` and `imu_lbs_lacc` notification payloads.
|
|
239
|
+
|
|
240
|
+
Quaternion notifications use UUID `7d2b6c11-9d78-4f3c-a122-6d2c4e6d2a11`.
|
|
241
|
+
|
|
242
|
+
| SDK field | Unit |
|
|
243
|
+
| --- | --- |
|
|
244
|
+
| `x` | raw signed Q14 |
|
|
245
|
+
| `y` | raw signed Q14 |
|
|
246
|
+
| `z` | raw signed Q14 |
|
|
247
|
+
| `w` | raw signed Q14 |
|
|
248
|
+
| `accuracy` | raw unsigned Q14 radians |
|
|
249
|
+
|
|
250
|
+
Convenience properties expose `x_float`, `y_float`, `z_float`, `w_float`, `accuracy_radians`, and `accuracy_degrees`.
|
|
251
|
+
|
|
252
|
+
Linear acceleration notifications use UUID `7d2b6c12-9d78-4f3c-a122-6d2c4e6d2a11`.
|
|
253
|
+
|
|
254
|
+
| SDK field | Unit |
|
|
255
|
+
| --- | --- |
|
|
256
|
+
| `x` | raw signed value |
|
|
257
|
+
| `y` | raw signed value |
|
|
258
|
+
| `z` | raw signed value |
|
|
259
|
+
|
|
260
|
+
Convenience properties expose `x_g`, `y_g`, and `z_g` using the BHI360 example scaling factor `value / 4096.0`. The current firmware labels this BLE stream `lacc`, but configures the BHI360 `BHY2_SENSOR_ID_ACC` virtual sensor.
|
|
261
|
+
|
|
262
|
+
## Temperature fields
|
|
263
|
+
|
|
264
|
+
The SDK maps the firmware's `temperature_lbs_sample` payload. The temperature characteristic supports read and notify.
|
|
265
|
+
|
|
266
|
+
| SDK field | Unit |
|
|
267
|
+
| --- | --- |
|
|
268
|
+
| `temperature_mdeg_c` | millidegrees Celsius |
|
|
269
|
+
|
|
270
|
+
Convenience properties expose `temperature_c` and `temperature_f`.
|
|
271
|
+
|
|
272
|
+
Temperature UUIDs:
|
|
273
|
+
|
|
274
|
+
| Purpose | UUID |
|
|
275
|
+
| --- | --- |
|
|
276
|
+
| Service | `8e83a64b-319d-47c2-ba53-6185fae0007f` |
|
|
277
|
+
| Sampling rate write | `8e83a64c-319d-47c2-ba53-6185fae0007f` |
|
|
278
|
+
| Transfer interval write | `8e83a64d-319d-47c2-ba53-6185fae0007f` |
|
|
279
|
+
| Sample read/notify | `8e83a64e-319d-47c2-ba53-6185fae0007f` |
|
|
280
|
+
|
|
281
|
+
`set_sampling_rate_hz()` writes a nonzero little-endian `uint16`. `set_transfer_interval()` also writes a nonzero little-endian `uint16`; the current MAX30208 firmware accepts it over BLE but does not use it when scheduling samples.
|
|
282
|
+
|
|
283
|
+
## Haptic pattern fields
|
|
284
|
+
|
|
285
|
+
The SDK maps the firmware's write-only haptic pattern characteristic.
|
|
286
|
+
|
|
287
|
+
Haptic UUIDs:
|
|
288
|
+
|
|
289
|
+
| Purpose | UUID |
|
|
290
|
+
| --- | --- |
|
|
291
|
+
| Service | `daa05e91-f514-4a4e-8fc5-d1b80f25f24d` |
|
|
292
|
+
| Pattern write | `daa05e92-f514-4a4e-8fc5-d1b80f25f24d` |
|
|
293
|
+
|
|
294
|
+
Pattern payload layout:
|
|
295
|
+
|
|
296
|
+
| Field | Format |
|
|
297
|
+
| --- | --- |
|
|
298
|
+
| `version` | byte, currently `1` |
|
|
299
|
+
| `flags` | byte, reserved, send `0` |
|
|
300
|
+
| `frame_count` | little-endian `uint16`, 1 to 64 |
|
|
301
|
+
| frame `duration_ms` | little-endian `uint16`, nonzero |
|
|
302
|
+
| frame `intensity` | byte, 0 to 255 |
|
|
303
|
+
|
|
304
|
+
The actuator uses DRV2605 real-time playback. A frame with intensity `0` acts as an off/pause frame.
|
senswar-0.1.0/README.md
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Senswar Python SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for connecting to Senswar/SensWear hardware over BLE.
|
|
4
|
+
|
|
5
|
+
This first SDK slice supports:
|
|
6
|
+
|
|
7
|
+
- Discovering and connecting to a Senswar device advertising as `Sens Wear ...`.
|
|
8
|
+
- Reading the custom power service battery gauge characteristic.
|
|
9
|
+
- Subscribing to battery gauge notifications.
|
|
10
|
+
- Reading the custom power service charger state characteristic.
|
|
11
|
+
- Subscribing to charger state notifications.
|
|
12
|
+
- Reading and writing the custom LED service color characteristic.
|
|
13
|
+
- Subscribing to custom IMU quaternion and acceleration notifications.
|
|
14
|
+
- Reading, configuring, and subscribing to custom temperature notifications.
|
|
15
|
+
- Writing custom haptic vibration patterns.
|
|
16
|
+
|
|
17
|
+
## Install for local development
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
cd C:\Users\salamid1\Desktop\Projects\SenseWear\SDKs\Python
|
|
21
|
+
python -m pip install -e .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Read the battery gauge
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import asyncio
|
|
28
|
+
from senswar import SenswarClient
|
|
29
|
+
|
|
30
|
+
async def main() -> None:
|
|
31
|
+
async with SenswarClient() as device:
|
|
32
|
+
state = await device.battery.read()
|
|
33
|
+
print(f"Battery: {state.state_of_charge_percent:.1f}%")
|
|
34
|
+
print(f"Voltage: {state.voltage_mv} mV")
|
|
35
|
+
print(f"Temperature: {state.temperature_c:.1f} C")
|
|
36
|
+
|
|
37
|
+
asyncio.run(main())
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
To connect to a known BLE address or exact advertised name, pass it to the client:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
async with SenswarClient("Sens Wear (Regulator)") as device:
|
|
44
|
+
state = await device.battery.read()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Read the charger state
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import asyncio
|
|
51
|
+
from senswar import SenswarClient
|
|
52
|
+
|
|
53
|
+
async def main() -> None:
|
|
54
|
+
async with SenswarClient() as device:
|
|
55
|
+
state = await device.charger.read()
|
|
56
|
+
print(f"Power good: {state.power_good}")
|
|
57
|
+
print(f"Charging: {state.charging}")
|
|
58
|
+
print(f"Charged: {state.charged}")
|
|
59
|
+
print(f"Fault present: {state.has_fault}")
|
|
60
|
+
|
|
61
|
+
asyncio.run(main())
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Set the LED color
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import asyncio
|
|
68
|
+
from senswar import LedColor, SenswarClient
|
|
69
|
+
|
|
70
|
+
async def main() -> None:
|
|
71
|
+
async with SenswarClient() as device:
|
|
72
|
+
await device.led.set(LedColor(red=255, green=0, blue=0))
|
|
73
|
+
current = await device.led.read()
|
|
74
|
+
print(current.to_hex(include_white=True))
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
You can also use hex strings, tuples, or the raw firmware integer:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
await device.led.set("#00ff00")
|
|
83
|
+
await device.led.set((0, 0, 255, 0))
|
|
84
|
+
await device.led.set(0x000000ff)
|
|
85
|
+
await device.led.off()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Stream the IMU
|
|
89
|
+
|
|
90
|
+
The IMU service is notify-only. Subscribing to either IMU characteristic enables firmware-side IMU streaming.
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import asyncio
|
|
94
|
+
from senswar import SenswarClient
|
|
95
|
+
|
|
96
|
+
async def main() -> None:
|
|
97
|
+
async with SenswarClient() as device:
|
|
98
|
+
def on_quaternion(sample):
|
|
99
|
+
print(sample.to_tuple())
|
|
100
|
+
|
|
101
|
+
def on_acceleration(sample):
|
|
102
|
+
print(sample.to_tuple())
|
|
103
|
+
|
|
104
|
+
await device.imu.subscribe_quaternion(on_quaternion)
|
|
105
|
+
await device.imu.subscribe_linear_acceleration(on_acceleration)
|
|
106
|
+
await asyncio.sleep(10)
|
|
107
|
+
await device.imu.unsubscribe_all()
|
|
108
|
+
|
|
109
|
+
asyncio.run(main())
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Read and stream temperature
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import asyncio
|
|
116
|
+
from senswar import SenswarClient
|
|
117
|
+
|
|
118
|
+
async def main() -> None:
|
|
119
|
+
async with SenswarClient() as device:
|
|
120
|
+
await device.temperature.set_sampling_rate_hz(2)
|
|
121
|
+
|
|
122
|
+
latest = await device.temperature.read()
|
|
123
|
+
print(f"Latest: {latest.temperature_c:.3f} C")
|
|
124
|
+
|
|
125
|
+
def on_temperature(sample):
|
|
126
|
+
print(f"Stream: {sample.temperature_c:.3f} C")
|
|
127
|
+
|
|
128
|
+
await device.temperature.subscribe(on_temperature)
|
|
129
|
+
await asyncio.sleep(10)
|
|
130
|
+
await device.temperature.unsubscribe()
|
|
131
|
+
|
|
132
|
+
asyncio.run(main())
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Play haptic vibration
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
import asyncio
|
|
139
|
+
from senswar import HapticFrame, HapticPattern, SenswarClient
|
|
140
|
+
|
|
141
|
+
async def main() -> None:
|
|
142
|
+
async with SenswarClient() as device:
|
|
143
|
+
await device.haptic.vibrate(duration_ms=150, intensity=255)
|
|
144
|
+
|
|
145
|
+
pattern = HapticPattern.from_frames([
|
|
146
|
+
HapticFrame(duration_ms=80, intensity=220),
|
|
147
|
+
HapticFrame(duration_ms=60, intensity=0),
|
|
148
|
+
HapticFrame(duration_ms=120, intensity=180),
|
|
149
|
+
])
|
|
150
|
+
await device.haptic.play(pattern)
|
|
151
|
+
|
|
152
|
+
asyncio.run(main())
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Battery gauge fields
|
|
156
|
+
|
|
157
|
+
The SDK maps the firmware's `power_lbs_gauge_state` payload. The current firmware bridge forwards BQ27427 temperature and state-of-charge values in 0.1-unit resolution.
|
|
158
|
+
|
|
159
|
+
| SDK field | Unit |
|
|
160
|
+
| --- | --- |
|
|
161
|
+
| `temperature_deci_c` | 0.1 degrees Celsius |
|
|
162
|
+
| `voltage_mv` | millivolts |
|
|
163
|
+
| `average_current_ma` | milliamps |
|
|
164
|
+
| `average_power_mw` | milliwatts |
|
|
165
|
+
| `state_of_charge_deci_percent` | 0.1 percent |
|
|
166
|
+
| `nominal_available_capacity_mah` | mAh |
|
|
167
|
+
| `full_battery_capacity_mah` | mAh |
|
|
168
|
+
| `remaining_capacity_mah` | mAh |
|
|
169
|
+
|
|
170
|
+
Convenience properties expose `temperature_c` and `state_of_charge_percent`.
|
|
171
|
+
|
|
172
|
+
## Charger fields
|
|
173
|
+
|
|
174
|
+
The SDK maps the firmware's `power_lbs_charger_state` payload. It is a 32-bit flags value copied from the BQ25180 charger state bitfield.
|
|
175
|
+
|
|
176
|
+
| SDK property | Firmware bit |
|
|
177
|
+
| --- | --- |
|
|
178
|
+
| `button_pressed` | 0 |
|
|
179
|
+
| `wake1` | 1 |
|
|
180
|
+
| `wake2` | 2 |
|
|
181
|
+
| `shipment_mode` | 3 |
|
|
182
|
+
| `shutdown_mode` | 4 |
|
|
183
|
+
| `power_good` | 5 |
|
|
184
|
+
| `charging` | 6 |
|
|
185
|
+
| `charged` | 7 |
|
|
186
|
+
| `thermal_regulation` | 8 |
|
|
187
|
+
| `battery_uvlo` | 9 |
|
|
188
|
+
| `thermal_normal` | 10 |
|
|
189
|
+
| `thermal_warm_or_hot` | 11 |
|
|
190
|
+
| `thermal_warm` | 12 |
|
|
191
|
+
| `thermal_cool` | 13 |
|
|
192
|
+
| `safety_timer_fault` | 14 |
|
|
193
|
+
| `thermal_system_fault` | 15 |
|
|
194
|
+
| `battery_uvlo_fault` | 16 |
|
|
195
|
+
| `battery_ocp_fault` | 17 |
|
|
196
|
+
|
|
197
|
+
The `has_fault` convenience property is true when any charger fault bit is set.
|
|
198
|
+
|
|
199
|
+
## LED color fields
|
|
200
|
+
|
|
201
|
+
The SDK maps the firmware's `led_color_t` payload. The characteristic is a 4-byte little-endian RGBW value.
|
|
202
|
+
|
|
203
|
+
| SDK field | Firmware byte |
|
|
204
|
+
| --- | --- |
|
|
205
|
+
| `red` | 0 |
|
|
206
|
+
| `green` | 1 |
|
|
207
|
+
| `blue` | 2 |
|
|
208
|
+
| `white` | 3 |
|
|
209
|
+
|
|
210
|
+
The LED color characteristic UUID is `3c688943-4143-470d-a798-4629803a1983`. Writing `0x00000000` turns the LEDs off in the current firmware bridge.
|
|
211
|
+
|
|
212
|
+
## IMU fields
|
|
213
|
+
|
|
214
|
+
The SDK maps the firmware's `imu_lbs_quat` and `imu_lbs_lacc` notification payloads.
|
|
215
|
+
|
|
216
|
+
Quaternion notifications use UUID `7d2b6c11-9d78-4f3c-a122-6d2c4e6d2a11`.
|
|
217
|
+
|
|
218
|
+
| SDK field | Unit |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| `x` | raw signed Q14 |
|
|
221
|
+
| `y` | raw signed Q14 |
|
|
222
|
+
| `z` | raw signed Q14 |
|
|
223
|
+
| `w` | raw signed Q14 |
|
|
224
|
+
| `accuracy` | raw unsigned Q14 radians |
|
|
225
|
+
|
|
226
|
+
Convenience properties expose `x_float`, `y_float`, `z_float`, `w_float`, `accuracy_radians`, and `accuracy_degrees`.
|
|
227
|
+
|
|
228
|
+
Linear acceleration notifications use UUID `7d2b6c12-9d78-4f3c-a122-6d2c4e6d2a11`.
|
|
229
|
+
|
|
230
|
+
| SDK field | Unit |
|
|
231
|
+
| --- | --- |
|
|
232
|
+
| `x` | raw signed value |
|
|
233
|
+
| `y` | raw signed value |
|
|
234
|
+
| `z` | raw signed value |
|
|
235
|
+
|
|
236
|
+
Convenience properties expose `x_g`, `y_g`, and `z_g` using the BHI360 example scaling factor `value / 4096.0`. The current firmware labels this BLE stream `lacc`, but configures the BHI360 `BHY2_SENSOR_ID_ACC` virtual sensor.
|
|
237
|
+
|
|
238
|
+
## Temperature fields
|
|
239
|
+
|
|
240
|
+
The SDK maps the firmware's `temperature_lbs_sample` payload. The temperature characteristic supports read and notify.
|
|
241
|
+
|
|
242
|
+
| SDK field | Unit |
|
|
243
|
+
| --- | --- |
|
|
244
|
+
| `temperature_mdeg_c` | millidegrees Celsius |
|
|
245
|
+
|
|
246
|
+
Convenience properties expose `temperature_c` and `temperature_f`.
|
|
247
|
+
|
|
248
|
+
Temperature UUIDs:
|
|
249
|
+
|
|
250
|
+
| Purpose | UUID |
|
|
251
|
+
| --- | --- |
|
|
252
|
+
| Service | `8e83a64b-319d-47c2-ba53-6185fae0007f` |
|
|
253
|
+
| Sampling rate write | `8e83a64c-319d-47c2-ba53-6185fae0007f` |
|
|
254
|
+
| Transfer interval write | `8e83a64d-319d-47c2-ba53-6185fae0007f` |
|
|
255
|
+
| Sample read/notify | `8e83a64e-319d-47c2-ba53-6185fae0007f` |
|
|
256
|
+
|
|
257
|
+
`set_sampling_rate_hz()` writes a nonzero little-endian `uint16`. `set_transfer_interval()` also writes a nonzero little-endian `uint16`; the current MAX30208 firmware accepts it over BLE but does not use it when scheduling samples.
|
|
258
|
+
|
|
259
|
+
## Haptic pattern fields
|
|
260
|
+
|
|
261
|
+
The SDK maps the firmware's write-only haptic pattern characteristic.
|
|
262
|
+
|
|
263
|
+
Haptic UUIDs:
|
|
264
|
+
|
|
265
|
+
| Purpose | UUID |
|
|
266
|
+
| --- | --- |
|
|
267
|
+
| Service | `daa05e91-f514-4a4e-8fc5-d1b80f25f24d` |
|
|
268
|
+
| Pattern write | `daa05e92-f514-4a4e-8fc5-d1b80f25f24d` |
|
|
269
|
+
|
|
270
|
+
Pattern payload layout:
|
|
271
|
+
|
|
272
|
+
| Field | Format |
|
|
273
|
+
| --- | --- |
|
|
274
|
+
| `version` | byte, currently `1` |
|
|
275
|
+
| `flags` | byte, reserved, send `0` |
|
|
276
|
+
| `frame_count` | little-endian `uint16`, 1 to 64 |
|
|
277
|
+
| frame `duration_ms` | little-endian `uint16`, nonzero |
|
|
278
|
+
| frame `intensity` | byte, 0 to 255 |
|
|
279
|
+
|
|
280
|
+
The actuator uses DRV2605 real-time playback. A frame with intensity `0` acts as an off/pause frame.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from senswar import HapticFrame, HapticPattern, SenswarClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def run(
|
|
8
|
+
device: str | None,
|
|
9
|
+
timeout: float,
|
|
10
|
+
duration_ms: int,
|
|
11
|
+
intensity: int,
|
|
12
|
+
double_pulse: bool,
|
|
13
|
+
) -> None:
|
|
14
|
+
async with SenswarClient(device, timeout=timeout) as client:
|
|
15
|
+
if double_pulse:
|
|
16
|
+
pattern = HapticPattern.from_frames(
|
|
17
|
+
[
|
|
18
|
+
HapticFrame(duration_ms=duration_ms, intensity=intensity),
|
|
19
|
+
HapticFrame(duration_ms=duration_ms, intensity=0),
|
|
20
|
+
HapticFrame(duration_ms=duration_ms, intensity=intensity),
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
await client.haptic.play(pattern)
|
|
24
|
+
else:
|
|
25
|
+
await client.haptic.vibrate(duration_ms=duration_ms, intensity=intensity)
|
|
26
|
+
|
|
27
|
+
print(f"Connected: {client.address or 'unknown'}")
|
|
28
|
+
print("Haptic pattern sent")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def main() -> None:
|
|
32
|
+
parser = argparse.ArgumentParser(description="Play a Senswar haptic vibration pattern over BLE.")
|
|
33
|
+
parser.add_argument(
|
|
34
|
+
"device",
|
|
35
|
+
nargs="?",
|
|
36
|
+
help="Optional BLE address/platform identifier or exact advertised name.",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument("--timeout", type=float, default=10.0, help="BLE scan/connect timeout in seconds.")
|
|
39
|
+
parser.add_argument("--duration", type=int, default=150, help="Frame duration in milliseconds.")
|
|
40
|
+
parser.add_argument("--intensity", type=int, default=255, help="Vibration intensity from 0 to 255.")
|
|
41
|
+
parser.add_argument("--double-pulse", action="store_true", help="Play two pulses separated by one off frame.")
|
|
42
|
+
args = parser.parse_args()
|
|
43
|
+
asyncio.run(run(args.device, args.timeout, args.duration, args.intensity, args.double_pulse))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from senswar import SenswarClient
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def run(device: str | None, timeout: float) -> None:
|
|
8
|
+
async with SenswarClient(device, timeout=timeout) as client:
|
|
9
|
+
state = await client.battery.read()
|
|
10
|
+
print(f"Connected: {client.address or 'unknown'}")
|
|
11
|
+
print(f"State of charge: {state.state_of_charge_percent:.1f}%")
|
|
12
|
+
print(f"Voltage: {state.voltage_mv} mV")
|
|
13
|
+
print(f"Average current: {state.average_current_ma} mA")
|
|
14
|
+
print(f"Average power: {state.average_power_mw} mW")
|
|
15
|
+
print(f"Temperature: {state.temperature_c:.2f} C")
|
|
16
|
+
print(f"Remaining capacity: {state.remaining_capacity_mah} mAh")
|
|
17
|
+
print(f"Full capacity: {state.full_battery_capacity_mah} mAh")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main() -> None:
|
|
21
|
+
parser = argparse.ArgumentParser(description="Read the Senswar battery gauge over BLE.")
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"device",
|
|
24
|
+
nargs="?",
|
|
25
|
+
help="Optional BLE address/platform identifier or exact advertised name.",
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument("--timeout", type=float, default=10.0, help="BLE scan/connect timeout in seconds.")
|
|
28
|
+
args = parser.parse_args()
|
|
29
|
+
asyncio.run(run(args.device, args.timeout))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if __name__ == "__main__":
|
|
33
|
+
main()
|