sensor-sdk 0.3.2__cp313-cp313-win_amd64.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.
sensor/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ from sensor.sensor_controller import SensorController, SensorControllerInstance
2
+ from sensor.sensor_profile import SensorProfile
3
+ from sensor.sensor_device import BLEDevice, DeviceInfo, DeviceStateEx
4
+ from sensor.sensor_data import DataType, Sample, SensorData
5
+ from sensor.winrt_high_throughput import apply as _apply_winrt_high_throughput_patch
6
+
7
+ # SDK 版本号(单一数据源,setup.py 打包时从这里读取)
8
+ __version__ = "0.3.2"
9
+
10
+ # Windows 下尝试给 bleak WinRT backend 打高吞吐率连接参数补丁
11
+ _apply_winrt_high_throughput_patch()
12
+
13
+ __all__ = [
14
+ "SensorController",
15
+ "SensorControllerInstance",
16
+ "SensorProfile",
17
+ "BLEDevice",
18
+ "DeviceInfo",
19
+ "DeviceStateEx",
20
+ "DataType",
21
+ "Sample",
22
+ "SensorData",
23
+ "__version__",
24
+ ]
Binary file
Binary file
@@ -0,0 +1,144 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2024 Victor Chavez <vchavezb@protonmail.com>
3
+ """Bumble backend.
4
+
5
+ 本目录为 ekspla/bleak-bumble_dev_host_mode(v0.0.7, MIT)的 vendored 副本,
6
+ 上游:https://github.com/ekspla/bleak-bumble_dev_host_mode
7
+ 仅修改了包内 import 路径(bleak_bumble → sensor.bleak_bumble),许可证见 LICENSE。
8
+ """
9
+
10
+ import os
11
+ from enum import Enum
12
+ from typing import Dict, Final, Optional
13
+
14
+ from bumble.controller import Controller
15
+ from bumble.link import LocalLink
16
+ from bumble.transport import Transport, open_transport
17
+
18
+ transports: Dict[str, Transport] = {}
19
+ _link: Final = LocalLink()
20
+ _scheme_delimiter: Final = ":"
21
+
22
+ _env_transport_cfg: Final = os.getenv("BLEAK_BUMBLE")
23
+ _env_host_mode: Final = os.getenv("BLEAK_BUMBLE_HOST")
24
+
25
+
26
+ class TransportScheme(Enum):
27
+ """The transport schemes supported by bumble.
28
+
29
+ https://google.github.io/bumble/transports
30
+ """
31
+
32
+ SERIAL = "serial"
33
+ """: The serial transport implements sending/receiving HCI
34
+ packets over a UART (a.k.a serial port).
35
+ """
36
+ UDP = "udp"
37
+ """: The UDP transport is a UDP socket, receiving packets on a specified port number,
38
+ and sending packets to a specified host and port number.
39
+ """
40
+ TCP_CLIENT = "tcp-client"
41
+ """: The TCP Client transport uses an outgoing TCP connection.
42
+ """
43
+ TCP_SERVER = "tcp-server"
44
+ """: The TCP Server transport uses an incoming TCP connection.
45
+ """
46
+ WS_CLIENT = "ws-client"
47
+ """: The WebSocket Client transport is WebSocket connection
48
+ to a WebSocket server over which HCI packets are sent and received.
49
+ """
50
+ WS_SERVER = "ws-server"
51
+ """: The WebSocket Server transport is WebSocket server that accepts
52
+ connections from a WebSocket client. HCI packets are sent and received over the connection.
53
+ """
54
+ PTY = "pty"
55
+ """: The PTY transport uses a Unix pseudo-terminal device to communicate
56
+ with another process on the host, as if it were over a serial port.
57
+ """
58
+ FILE = "file"
59
+ """: The File transport allows opening any named entry on a filesystem
60
+ and use it for HCI transport I/O. This is typically used to open a PTY,
61
+ or unix driver, not for real files.
62
+ """
63
+ VHCI = "vhci"
64
+ """: The VHCI transport allows attaching a virtual controller
65
+ to the Bluetooth stack on operating systems that offer a
66
+ VHCI driver (Linux, if enabled, maybe others).
67
+ """
68
+ HCI_SOCKET = "hci-socket"
69
+ """: An HCI Socket can send/receive HCI packets to/from a
70
+ Bluetooth HCI controller managed by the host OS.
71
+ This is only supported on some platforms (currently only tested on Linux).
72
+ """
73
+ USB = "usb"
74
+ """: The USB transport interfaces with a local Bluetooth USB dongle.
75
+ """
76
+ ANDROID_NETSIM = "android-netsim"
77
+ """: The Android "netsim" transport either connects, as a host, to a
78
+ Netsim virtual controller ("host" mode), or acts as a virtual
79
+ controller itself ("controller" mode) accepting host connections.
80
+ """
81
+
82
+ @classmethod
83
+ def from_string(cls, value: str) -> "TransportScheme":
84
+ try:
85
+ return cls(value)
86
+ except ValueError:
87
+ raise ValueError(f"'{value}' is not a valid TransportScheme")
88
+
89
+
90
+ class BumbleTransportCfg:
91
+ """Transport configuration for bumble.
92
+
93
+ Args:
94
+ scheme (TransportScheme): The transport scheme supported by bumble.
95
+ args (Optional[str]): The arguments used to initialize the transport.
96
+ See https://google.github.io/bumble/transports/index.html
97
+ """
98
+
99
+ def __init__(self, scheme: TransportScheme, args: Optional[str] = None):
100
+ self.scheme: Final = scheme
101
+ self.args: Final = args
102
+
103
+ def __str__(self):
104
+ return f"{self.scheme.value}:{self.args}" if self.args else self.scheme.value
105
+
106
+
107
+ def get_default_transport_cfg() -> BumbleTransportCfg:
108
+ if _env_transport_cfg:
109
+ scheme_val, *args = _env_transport_cfg.split(_scheme_delimiter, 1)
110
+ return BumbleTransportCfg(
111
+ TransportScheme.from_string(scheme_val), args[0] if args else None
112
+ )
113
+
114
+ return BumbleTransportCfg(TransportScheme.TCP_SERVER, "127.0.0.1:1234")
115
+
116
+
117
+ def is_host_mode_enabled_from_env() -> bool:
118
+ """Check if host mode is enabled via BLEAK_BUMBLE_HOST environment variable.
119
+
120
+ Returns:
121
+ bool: True if BLEAK_BUMBLE_HOST environment variable is set to any non-empty value.
122
+ """
123
+ return True if _env_host_mode else False
124
+
125
+
126
+ async def start_transport(
127
+ cfg: BumbleTransportCfg, host_mode: bool = is_host_mode_enabled_from_env()
128
+ ) -> Transport:
129
+ transport_cmd = str(cfg)
130
+ if transport_cmd not in transports.keys():
131
+ transports[transport_cmd] = await open_transport(transport_cmd)
132
+ if not host_mode:
133
+ Controller(
134
+ "ext",
135
+ host_source=transports[transport_cmd].source,
136
+ host_sink=transports[transport_cmd].sink,
137
+ link=_link,
138
+ )
139
+ return transports[transport_cmd]
140
+
141
+
142
+ def get_link():
143
+ # Assume all transports are linked
144
+ return _link
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
sensor/fb/__init__.py ADDED
File without changes
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,530 @@
1
+ Metadata-Version: 2.4
2
+ Name: sensor-sdk
3
+ Version: 0.3.2
4
+ Summary: Python sdk for Synchroni
5
+ Home-page: https://github.com/oymotion/SynchroniSDKPython
6
+ Author: Martin Ye
7
+ Author-email: yecq_82@hotmail.com
8
+ Requires-Python: >=3.10.0
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE.txt
11
+ Requires-Dist: numpy
12
+ Requires-Dist: setuptools
13
+ Requires-Dist: bleak>=3.0.2
14
+ Requires-Dist: flatbuffers>=25.0.0
15
+ Requires-Dist: bumble==0.0.233
16
+ Requires-Dist: libusb1
17
+ Requires-Dist: libusb-package
18
+ Dynamic: author
19
+ Dynamic: author-email
20
+ Dynamic: description
21
+ Dynamic: description-content-type
22
+ Dynamic: home-page
23
+ Dynamic: license-file
24
+ Dynamic: requires-dist
25
+ Dynamic: requires-python
26
+ Dynamic: summary
27
+
28
+ # sensor-sdk
29
+
30
+ Synchroni sdk for Python
31
+
32
+ ## Brief
33
+
34
+ Synchroni SDK is the software development kit for developers to access Synchroni products.
35
+
36
+ ## Contributing
37
+
38
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
39
+
40
+ ## License
41
+
42
+ MIT
43
+
44
+ ---
45
+
46
+ ## Installation
47
+
48
+ ```sh
49
+ pip install sensor-sdk
50
+ ```
51
+
52
+ ### USB Bluetooth dongle (bumble backend)
53
+
54
+ On macOS, when a USB Bluetooth HCI dongle is plugged in (e.g. Actions `10d7:b012` or `33fa:0012`; several dongles can be used at once, each serving one connection), the SDK automatically drives it directly with a [bumble](https://github.com/google/bumble) host-mode backend instead of CoreBluetooth — no Bluetooth permission prompt, works even when the internal Bluetooth is off, and allows larger ATT MTU. The [bleak-bumble](https://github.com/ekspla/bleak-bumble_dev_host_mode) backend is vendored into the SDK (`sensor/bleak_bumble/`, MIT license), and the bumble stack + libusb are declared as regular dependencies — a plain `pip install sensor-sdk` is all that is needed.
55
+
56
+ Behavior and controls:
57
+
58
+ - With the dependencies installed and a dongle present, the backend is selected automatically on macOS when the SDK starts (native `bleak` is used otherwise). Check the active backend with `SensorController.getBLEBackendName()` (`"bumble"` / `"bleak"`).
59
+ - `SENSOR_SDK_BLE_BACKEND=bleak` forces the native backend; `SENSOR_SDK_BLE_BACKEND=bumble` forces the dongle backend on any platform; `SENSOR_SDK_BUMBLE_TRANSPORT` overrides the bumble transport spec (e.g. `usb:0`).
60
+ - USB access needs no sudo on macOS as long as the OS has not claimed the dongle (it normally doesn't when the internal Bluetooth works).
61
+ - With the bumble backend, each dongle serves one connection at a time: scanning uses a free dongle and is skipped while all dongles are connected; connections fail fast when no dongle is free. Connection timeout is raised to 25s automatically.
62
+
63
+ ## 1. Permission
64
+
65
+ Application will obtain bluetooth permission by itself.
66
+
67
+ ## 2. Import SDK
68
+
69
+ ```python
70
+ from sensor import *
71
+ ```
72
+
73
+ ## SensorController methods
74
+
75
+ ### 1. Initalize
76
+
77
+ ```python
78
+ SensorControllerInstance = SensorController()
79
+
80
+ # register scan listener
81
+ if not SensorControllerInstance.hasDeviceFoundCallback:
82
+ def on_device_callback(deviceList: List[BLEDevice]):
83
+ # return all devices doesn't connected
84
+ pass
85
+ SensorControllerInstance.onDeviceFoundCallback = on_device_callback
86
+ ```
87
+
88
+ ### 2. Start scan
89
+
90
+ Use `def startScan(period_in_ms: int) -> bool` to start scan
91
+
92
+ ```python
93
+ success = SensorControllerInstance.startScan(6000)
94
+ ```
95
+
96
+ returns true if start scan success, periodInMS means onDeviceCallback will be called every periodInMS
97
+
98
+ Use `def scan(period_in_ms: int) -> list[BLEDevice]` to scan once time
99
+
100
+ ```python
101
+ bleDevices = SensorControllerInstance.scan(6000)
102
+ ```
103
+
104
+ ### 3. Stop scan
105
+
106
+ Use `def stopScan() -> None` to stop scan
107
+
108
+ ```python
109
+ SensorControllerInstance.stopScan()
110
+ ```
111
+
112
+ ### 4. Check scaning
113
+
114
+ Use `property isScanning: bool` to check scanning status
115
+
116
+ ```python
117
+ isScanning = SensorControllerInstance.isScanning
118
+ ```
119
+
120
+ ### 5. Check if bluetooth is enabled
121
+
122
+ Use `property isEnable: bool` to check if bluetooth is enable
123
+
124
+ ```python
125
+ isEnable = SensorControllerInstance.isEnable
126
+ ```
127
+
128
+ ### 6. Create SensorProfile
129
+
130
+ Use `def requireSensor(device: BLEDevice) -> SensorProfile | None` to create SensorProfile.
131
+
132
+ If bleDevice is invalid, result is None.
133
+
134
+ ```python
135
+ sensorProfile = SensorControllerInstance.requireSensor(bleDevice)
136
+ ```
137
+
138
+ ### 7. Get SensorProfile
139
+
140
+ Use `def getSensor(deviceMac: str) -> SensorProfile | None` to get SensorProfile.
141
+
142
+ If SensorProfile didn't created, result is None.
143
+
144
+ ```python
145
+ sensorProfile = SensorControllerInstance.getSensor(bleDevice.Address)
146
+ ```
147
+
148
+ ### 8. Get Connected SensorProfiles
149
+
150
+ Use `def getConnectedSensors() -> list[SensorProfile]` to get connected SensorProfiles.
151
+
152
+ ```python
153
+ sensorProfiles = SensorControllerInstance.getConnectedSensors()
154
+ ```
155
+
156
+ ### 9. Get Connected BLE Devices
157
+
158
+ Use `def getConnectedDevices() -> list[BLEDevice]` to get connected BLE Devices.
159
+
160
+ ```python
161
+ bleDevices = SensorControllerInstance.getConnectedDevices()
162
+ ```
163
+
164
+ ### 10. Terminate
165
+
166
+ Use `def terminate()` to terminate sdk
167
+
168
+ ```python
169
+
170
+ def terminate():
171
+ SensorControllerInstance.terminate()
172
+ exit()
173
+
174
+ def main():
175
+ signal.signal(signal.SIGINT, lambda signal, frame: terminate())
176
+ time.sleep(30)
177
+ SensorControllerInstance.terminate()
178
+
179
+ Please MAKE SURE to call terminate when exit main() or press Ctrl+C
180
+ ```
181
+
182
+ ## SensorProfile methods
183
+
184
+ ### 11. Initalize
185
+
186
+ Please register callbacks for SensorProfile
187
+
188
+ ```python
189
+ sensorProfile = SensorControllerInstance.requireSensor(bleDevice)
190
+
191
+ # register callbacks
192
+ def on_state_changed(sensor, newState):
193
+ # please do logic when device disconnected unexpected
194
+ pass
195
+
196
+ def on_error_callback(sensor, reason):
197
+ # called when error occurs
198
+ pass
199
+
200
+ def on_power_changed(sensor, power):
201
+ # callback for get battery level of device, power from 0 - 100, -1 is invalid
202
+ pass
203
+
204
+ def on_data_callback(sensor, data):
205
+ # called after start data transfer
206
+ pass
207
+
208
+ sensorProfile.onStateChanged = on_state_changed
209
+ sensorProfile.onErrorCallback = on_error_callback
210
+ sensorProfile.onPowerChanged = on_power_changed
211
+ sensorProfile.onDataCallback = on_data_callback
212
+ ```
213
+
214
+ ### 12. Connect device
215
+
216
+ Use `def connect() -> bool` to connect.
217
+
218
+ ```python
219
+ success = sensorProfile.connect()
220
+ ```
221
+
222
+ ### 13. Disconnect
223
+
224
+ Use `def disconnect() -> bool` to disconnect.
225
+
226
+ If data notification is currently active, `disconnect()` will automatically stop it first before closing the BLE connection.
227
+
228
+ ```python
229
+ success = sensorProfile.disconnect()
230
+ ```
231
+
232
+ ### 14. Get device status
233
+
234
+ Use `property deviceState: DeviceStateEx` to get device status.
235
+
236
+ Please send command in 'Ready' state, should be after connect() return True.
237
+
238
+ ```python
239
+ deviceStateEx = sensorProfile.deviceState
240
+
241
+ # deviceStateEx has define:
242
+ # class DeviceStateEx(Enum):
243
+ # Disconnected = 0
244
+ # Connecting = 1
245
+ # Connected = 2
246
+ # Ready = 3
247
+ # Disconnecting = 4
248
+ # Invalid = 5
249
+ ```
250
+
251
+ ### 15. Get BLE device of SensorProfile
252
+
253
+ Use `property BLEDevice: BLEDevice` to get BLE device of SensorProfile.
254
+
255
+ ```python
256
+ bleDevice = sensorProfile.BLEDevice
257
+ ```
258
+
259
+ ### 16. Get device info of SensorProfile
260
+
261
+ Use `def getDeviceInfo() -> DeviceInfo | None` to get device info of SensorProfile.
262
+
263
+ Please call after device in 'Ready' state and `init()` has succeeded, returns None otherwise.
264
+
265
+ ```python
266
+ deviceInfo = sensorProfile.getDeviceInfo()
267
+
268
+ # deviceInfo is a DeviceInfo object with attributes:
269
+ # DeviceName, ModelName, HardwareVersion, FirmwareVersion, MTUSize
270
+ # plus a ChannelCount / SampleRate attribute pair for each modality:
271
+ # Ppg, Spo2, Impe, Emg, Eeg, Ecg, Acc, Gyro, Brth, MagAngle, Euler, Quat
272
+ # e.g. deviceInfo.EmgChannelCount, deviceInfo.EegSampleRate
273
+ ```
274
+
275
+ ### 17. Init data transfer
276
+
277
+ Use `def init(packageSampleCount: int, powerRefreshInterval: int) -> bool`.
278
+
279
+ Please call after device in 'Ready' state, return True if init succeed.
280
+
281
+ ```python
282
+ success = sensorProfile.init(5, 60*1000)
283
+ ```
284
+
285
+ packageSampleCount: set sample counts of SensorData.channelSamples in onDataCallback()
286
+ powerRefreshInterval: callback period for onPowerChanged()
287
+
288
+ ### 18. Check if init data transfer succeed
289
+
290
+ Use `property hasInited: bool` to check if init data transfer succeed.
291
+
292
+ ```python
293
+ hasInited = sensorProfile.hasInited
294
+ ```
295
+
296
+ ### 19. DataNotify
297
+
298
+ Use `def startDataNotification() -> bool` to start data notification.
299
+
300
+ Please call if hasInited return True
301
+
302
+ #### 19.1 Start data transfer
303
+
304
+ ```python
305
+ success = sensorProfile.startDataNotification()
306
+ ```
307
+
308
+ Data type list:
309
+
310
+ ```python
311
+ class DataType(Enum):
312
+ NTF_ACC = 0x1 # unit is g
313
+ NTF_GYRO = 0x2 # unit is degree/s
314
+ NTF_EEG = 0x10 # unit is uV
315
+ NTF_ECG = 0x11 # unit is uV
316
+ NTF_BRTH = 0x15 # unit is uV
317
+ ```
318
+
319
+ Process data in onDataCallback.
320
+
321
+ ```python
322
+ def on_data_callback(sensor, data):
323
+ if data.dataType == DataType.NTF_EEG:
324
+ pass
325
+ elif data.dataType == DataType.NTF_ECG:
326
+ pass
327
+
328
+ # process data as you wish
329
+ for oneChannelSamples in data.channelSamples:
330
+ for sample in oneChannelSamples:
331
+ if sample.isLost:
332
+ # do some logic
333
+ pass
334
+ else:
335
+ # draw with sample.data & sample.channelIndex
336
+ # print(f"{sample.channelIndex} | {sample.sampleIndex} | {sample.data} | {sample.impedance}")
337
+ pass
338
+
339
+ sensorProfile.onDataCallback = on_data_callback
340
+ ```
341
+
342
+ #### 19.2 Stop data transfer
343
+
344
+ Use `def stopDataNotification() -> bool` to stop data transfer.
345
+
346
+ ```python
347
+ success = sensorProfile.stopDataNotification()
348
+ ```
349
+
350
+ #### 19.3 Check if it's data transfering
351
+
352
+ Use `property isDataTransfering: bool` to check if it's data transfering.
353
+
354
+ ```python
355
+ isDataTransfering = sensorProfile.isDataTransfering
356
+ ```
357
+
358
+ ### 20. Get battery level
359
+
360
+ Use `def getBatteryLevel() -> int` to get battery level. Please call after device in 'Ready' state.
361
+
362
+ ```python
363
+ batteryPower = sensorProfile.getBatteryLevel()
364
+
365
+ # batteryPower is battery level returned, value ranges from 0 to 100, 0 means out of battery, while 100 means full.
366
+ ```
367
+
368
+ Please check console.py in examples directory
369
+
370
+ ### Async methods
371
+
372
+ all methods start with async is async methods, they has same params and return result as sync methods.
373
+
374
+ Please check async_console.py in examples directory
375
+
376
+ ### setParam method
377
+
378
+ Use `def setParam(self, key: str, value: str) -> str` to set parameter of sensor profile. Please call after device in 'Ready' state.
379
+
380
+ The asynchronous variant is `asyncSetParam(self, key: str, value: str) -> str`.
381
+
382
+ If the device is already streaming when you change an `NTF_*` or `FILTER_*` key, the SDK will stop and restart the data notification so the new setting takes effect immediately.
383
+
384
+ Below is available key and value:
385
+
386
+ ```python
387
+ # Data stream toggles
388
+ result = sensorProfile.setParam("NTF_GEST", "ON")
389
+ result = sensorProfile.setParam("NTF_EMG", "ON")
390
+ result = sensorProfile.setParam("NTF_EEG", "ON")
391
+ result = sensorProfile.setParam("NTF_ECG", "ON")
392
+ result = sensorProfile.setParam("NTF_IMU", "ON")
393
+ result = sensorProfile.setParam("NTF_BRTH", "ON")
394
+ result = sensorProfile.setParam("NTF_IMPEDANCE", "ON")
395
+ result = sensorProfile.setParam("NTF_MAG_ANGLE", "ON")
396
+ result = sensorProfile.setParam("NTF_PPG_RAW", "ON")
397
+ result = sensorProfile.setParam("NTF_GFORCE_EULER", "ON")
398
+ result = sensorProfile.setParam("NTF_GFORCE_QUAT", "ON")
399
+ result = sensorProfile.setParam("NTF_GFORCE_ACC", "ON")
400
+ result = sensorProfile.setParam("NTF_GFORCE_GYRO", "ON")
401
+ # set data stream to ON or OFF, result is "OK" if succeed
402
+ # Note: on legacy (non-new) EMG devices, NTF_GEST and NTF_EMG are mutually exclusive.
403
+
404
+ # Firmware filter toggles
405
+ result = sensorProfile.setParam("FILTER_50HZ", "ON")
406
+ # set 50Hz notch filter to ON or OFF, result is "OK" if succeed
407
+
408
+ result = sensorProfile.setParam("FILTER_60HZ", "ON")
409
+ # set 60Hz notch filter to ON or OFF, result is "OK" if succeed
410
+
411
+ result = sensorProfile.setParam("FILTER_HPF", "ON")
412
+ # set 0.5Hz hpf filter to ON or OFF, result is "OK" if succeed
413
+
414
+ result = sensorProfile.setParam("FILTER_LPF", "ON")
415
+ # set 80Hz lpf filter to ON or OFF, result is "OK" if succeed
416
+
417
+ result = sensorProfile.setParam("DEBUG_BLE_DATA_PATH", "d:/temp/test.bin")
418
+ # set the bin export path: the session's raw BLE capture is recorded in the system
419
+ # temp directory and copied to this location on stopDataNotification / disconnect;
420
+ # "True" exports to {DeviceName}_data_YYYYMMDD_HHMMSS.bin in the SDK log directory,
421
+ # "False" or "" disables export (the temp bin is just deleted).
422
+ # please give an absolute path and make sure it is valid and writeable by yourself
423
+
424
+ result = sensorProfile.setParam("DEBUG_LOG_PATH", "True")
425
+ # enable SDK file logging to a default file ({DeviceName}_log_YYYYMMDD_HHMMSS.txt),
426
+ # or pass an absolute custom path instead of "True"; "False" or "" disables it.
427
+ # getParam("DEBUG_LOG_PATH") returns the current log file path ("" when disabled).
428
+ ```
429
+
430
+
431
+ ### getParam method
432
+
433
+ Use `def getParam(self, key: str) -> str` to query the current parameter state of a sensor profile. Please call after the device reaches the 'Ready' state.
434
+
435
+ The asynchronous variant is `asyncGetParam(self, key: str) -> str`.
436
+
437
+ Supported aggregate query keys:
438
+
439
+ ```python
440
+ result = sensorProfile.getParam("FILTER")
441
+ # Returns a pipe-separated string of all filter states, e.g.:
442
+ # "FILTER_50HZ|ON|FILTER_60HZ|ON|FILTER_HPF|ON|FILTER_LPF|ON"
443
+
444
+ result = sensorProfile.getParam("NTF")
445
+ # Returns a pipe-separated string of all notification states, e.g.:
446
+ # "NTF_BRTH|ON|NTF_ECG|ON|NTF_EEG|ON|NTF_EMG|ON|..."
447
+ ```
448
+
449
+ If the key is not supported, the result starts with `"Error"`.
450
+
451
+ ## Bin file recording and replay
452
+
453
+ On every successful connect, the SDK records all raw BLE packets of the session into a `.bin` file in the SDK log directory (`~/Documents/sensorsdklog` by default, or the directory of the configured log file), named `{DeviceName}_{MAC}_{YYYYMMDD_HHMMSS}.bin`. Recording is always on; it is skipped or stopped with a warning when free disk space is below 100MB or a disk error occurs, without affecting live streaming. Bin files can be replayed offline for debugging and packet-loss analysis.
454
+
455
+ ### Get bin file info
456
+
457
+ Use `def getBinFileInfo(self, file_path: str) -> Optional[dict]` to read the config record of a bin file:
458
+
459
+ ```python
460
+ info = SensorControllerInstance.getBinFileInfo("path/to/session.bin")
461
+ # Returns a dict:
462
+ # device_mac, device_name, chip_type, is_universal_stream, feature_map,
463
+ # device_info, sensor_datas (per data-type parse config),
464
+ # replay_duration (recording seconds, written into the header record at close;
465
+ # older bins without a header fall back to a full-file estimate)
466
+ # Returns None if the file does not exist or has no config record.
467
+ ```
468
+
469
+ ### Replay a bin file
470
+
471
+ Use `def replayBinFile(self, file_path: str, sensor: Optional[SensorProfile] = None, realtime: bool = True, timeout: Optional[float] = None) -> Optional[SensorProfile]` to replay a bin file through the normal parsing pipeline. Parsed results arrive via `onDataCallback` on the returned SensorProfile, same as live data.
472
+
473
+ ```python
474
+ # Simplest form: controller creates the profile from the bin config record
475
+ sensor = SensorControllerInstance.replayBinFile("path/to/session.bin")
476
+
477
+ # Recommended: reuse an existing profile with callbacks registered
478
+ sensor = SensorControllerInstance.requireSensor(device)
479
+ sensor.onDataCallback = on_data
480
+ SensorControllerInstance.replayBinFile("path/to/session.bin", sensor, realtime=True)
481
+ ```
482
+
483
+ - `sensor`: an existing SensorProfile to replay through. When `None`, the controller creates (or reuses) a profile from the bin config record; in that mode the profile is returned only after replay finishes, so register callbacks on an existing profile to receive data.
484
+ - `realtime`: `True` replays at the recorded pace; `False` replays as fast as possible.
485
+ - `timeout`: seconds to wait for completion. `None` auto-estimates from the bin duration in realtime mode (duration + 30s, min 60s), or 600s otherwise. On timeout the call returns while replay may still be running in the background.
486
+ - Replay is rejected while the target sensor is streaming live data.
487
+
488
+ ### Pause / resume / stop replay
489
+
490
+ ```python
491
+ result = SensorControllerInstance.pauseBinReplay(sensor) # pause feeding
492
+ result = SensorControllerInstance.resumeBinReplay(sensor) # resume feeding
493
+ result = SensorControllerInstance.stopBinReplay(sensor) # abort; the blocking replayBinFile call returns
494
+ # Each returns "OK" on success or an error string otherwise.
495
+ ```
496
+
497
+ ### Parse a bin file to CSV
498
+
499
+ Use `def parseBinToCsv(self, bin_path: str, csv_path: Optional[str] = None) -> str` to convert a recorded bin file to CSV offline (parsing runs through the real pipeline; row timestamps come from the bin records):
500
+
501
+ ```python
502
+ csv_path = SensorControllerInstance.parseBinToCsv("d:/temp/test.bin")
503
+ # or with an explicit output path:
504
+ csv_path = SensorControllerInstance.parseBinToCsv("d:/temp/test.bin", "d:/temp/test.csv")
505
+ # Returns the CSV file path. Columns:
506
+ # timestamp, mac, type, raw_hex, data_type, sample_rate, channel_count, lost_count, samples_info, first_sample
507
+ ```
508
+
509
+ ## Logging controls
510
+
511
+ File logging is disabled by default. Records emitted before file logging is first enabled are held in a bounded memory buffer and replayed into the file on first enable, so early (scan/connect) logs are not lost. The log path is automatically shared with the BLE subprocess, so both sides write to the same file.
512
+
513
+ ```python
514
+ SensorControllerInstance.setDebugEnabled(True)
515
+ # enable/disable SDK debug logs
516
+
517
+ SensorControllerInstance.setLogPath("d:/temp/sdk.log")
518
+ # enable file logging to a custom path; pass "" to disable
519
+
520
+ SensorControllerInstance.enableFileLog(True)
521
+ # enable file logging with the default path
522
+ # (~/Documents/sensorsdklog/log_YYYYMMDD_HHMMSS.txt)
523
+
524
+ SensorControllerInstance.setControllerLogPath("d:/temp/controller.log")
525
+ # SensorController-dedicated log file; pass "" to disable
526
+
527
+ SensorControllerInstance.enableControllerLog(True)
528
+ # enable the controller-dedicated log with the default path
529
+ # (~/Documents/sensorsdklog/sensor_controller_log_YYYYMMDD_HHMMSS.txt)
530
+ ```
@@ -0,0 +1,29 @@
1
+ sensor/__init__.py,sha256=c6CvPyKDVJwBVV5ffJh9a118hdLA-AZ9dANRZPO1yP0,793
2
+ sensor/bin_recorder.cp313-win_amd64.pyd,sha256=__0dFXSmR68tK_O97f_pXjmey2lunwMpODi-Vm42KME,188416
3
+ sensor/bin_to_csv.cp313-win_amd64.pyd,sha256=On4acP-jluaLbbBcLPqRzxcEP_8mSSXjah44Y0WQ4ac,104448
4
+ sensor/bleak_host.cp313-win_amd64.pyd,sha256=AEcPJVtLZPb30mOVfl7kiA0HZRHIk6-rJhvXAFSnaBs,189952
5
+ sensor/bleak_process.cp313-win_amd64.pyd,sha256=aVDzsYyrYmps18ndeWXKNCwf84tmG-ptO23VBVQargM,528384
6
+ sensor/bumble_dongle.cp313-win_amd64.pyd,sha256=SjUReCTJEscqBs3RVEIbtHKqQ0cqpEoUtiQVXhVj_Ro,184320
7
+ sensor/gforce.cp313-win_amd64.pyd,sha256=GqnhCaxWx2txku-Lvs_jeSKID7ngAF4B2xTEedTev30,466432
8
+ sensor/sdk_log.cp313-win_amd64.pyd,sha256=DsQeQNLxbEQfaGO2JPXf1eSNqMz6Fh_cu0otrFuuhkk,140800
9
+ sensor/sensor_controller.cp313-win_amd64.pyd,sha256=3qIQrp5LEbobUaPlqHW99PRNIwayQPK1Wgoxv7954ts,213504
10
+ sensor/sensor_data.cp313-win_amd64.pyd,sha256=q8VkqAhwQFhwA1Ka_K0Q8Gyi2LrZ8BdNyDnjgtcF_PM,82432
11
+ sensor/sensor_data_context.cp313-win_amd64.pyd,sha256=DquW9BpXeK9EjigdwHxgcAA2KeqWw0TZat8AAbWtmdg,573952
12
+ sensor/sensor_data_pool.cp313-win_amd64.pyd,sha256=LDT7JsyEPQfqQFNfcJWmHgHweAkdm7hKGBKVP2nh_RY,64000
13
+ sensor/sensor_device.cp313-win_amd64.pyd,sha256=mNu0WlZuxktbjBRS-bZdZivQ1rWGN0sZkn8MwfpO6AM,45568
14
+ sensor/sensor_profile.cp313-win_amd64.pyd,sha256=ehhxQ8ci8brz1VVKbZIPkm21JZ8H4s5KHM6-6ZS_Cfc,260096
15
+ sensor/sensor_utils.cp313-win_amd64.pyd,sha256=U5u94Sb_nFIJPdfdzapCI-2nTvyZpvZSRVC9RiBTZag,130048
16
+ sensor/winrt_high_throughput.cp313-win_amd64.pyd,sha256=qIO3FxfEb8qLPwOU96AR7upLOdIE0NwVcJeZRaV1avk,78848
17
+ sensor/bleak_bumble/__init__.py,sha256=OVjUkxlVNdf2PVO5R0NaBfMKzD6ZWj4FaRIXTwNBwak,5205
18
+ sensor/bleak_bumble/client.cp313-win_amd64.pyd,sha256=T3_7RRv2VHn1DyrdJmsHh0iq0QvCByjgDlxbluQ3cow,180736
19
+ sensor/bleak_bumble/scanner.cp313-win_amd64.pyd,sha256=bmIxxPe9IlfQTlEVsrT20MSQkQM5lWtXj_QvQUBaZRg,103936
20
+ sensor/bleak_bumble/utils.cp313-win_amd64.pyd,sha256=X1w9VOhBZhjc4mnVEHgJuesUBsy346vPGBiCZXgQ2tQ,55808
21
+ sensor/fb/DataType.cp313-win_amd64.pyd,sha256=Hd__ZAFq4x6tbnXHRi60PPF57DAtbTg94d2W0ETpYQc,22528
22
+ sensor/fb/Sample.cp313-win_amd64.pyd,sha256=gy87FS4Vn0MHbFSxAVK-qw5K3MXZsAPu5PGICwlxhLg,61440
23
+ sensor/fb/SensorData.cp313-win_amd64.pyd,sha256=Pm1qMGOraiENrtufXRhz9egzEQAVg4-2u-UAHSTFfR0,143360
24
+ sensor/fb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
+ sensor_sdk-0.3.2.dist-info/licenses/LICENSE.txt,sha256=8CSivOpub3IuXODTyqBRI91AxouJZk02YrcKuOAkWu8,1111
26
+ sensor_sdk-0.3.2.dist-info/METADATA,sha256=3KUTnyQlUBBEPzclKOTHsBSD4mOw1TJU9ROaPnhdJXI,18379
27
+ sensor_sdk-0.3.2.dist-info/WHEEL,sha256=AP_wkikJcUkDPUGvTNydv7g0R7BhW1u_fVHNKW7TJyI,101
28
+ sensor_sdk-0.3.2.dist-info/top_level.txt,sha256=Ftq49B6bH0Ffdc7c8LkcyakHo6lsg_snlBbpEUoILSk,7
29
+ sensor_sdk-0.3.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (83.0.0)
3
+ Root-Is-Purelib: false
4
+ Tag: cp313-cp313-win_amd64
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OYMotion Technologies Co., Ltd..
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.
@@ -0,0 +1 @@
1
+ sensor