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.
@@ -0,0 +1,3 @@
1
+ # Python cache
2
+ __pycache__/
3
+ *.py[cod]
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.
@@ -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()