synchroni-sensor-sdk 1.0.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,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,405 @@
1
+ Metadata-Version: 2.4
2
+ Name: synchroni-sensor-sdk
3
+ Version: 1.0.0
4
+ Summary: Python sdk for Synchroni
5
+ Home-page: https://github.com/SynchroniSI/synchroni-sensor-sdk
6
+ Author: Martin Ye
7
+ Author-email: yecq_82@hotmail.com
8
+ Requires-Python: >=3.9.0
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE.txt
11
+ Requires-Dist: numpy
12
+ Requires-Dist: setuptools
13
+ Requires-Dist: bleak
14
+ Dynamic: author
15
+ Dynamic: author-email
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: home-page
19
+ Dynamic: license-file
20
+ Dynamic: requires-dist
21
+ Dynamic: requires-python
22
+ Dynamic: summary
23
+
24
+ # synchroni-sensor-sdk
25
+
26
+ Synchroni sdk for Python
27
+
28
+ ## Brief
29
+
30
+ Synchroni SDK is the software development kit for developers to access Synchroni products.
31
+
32
+ ## Contributing
33
+
34
+ See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
35
+
36
+ ## License
37
+
38
+ MIT
39
+
40
+ ---
41
+
42
+ ## Installation
43
+
44
+ ```sh
45
+ pip install synchroni-sensor-sdk
46
+ ```
47
+
48
+ ## 1. Permission
49
+
50
+ Application will obtain bluetooth permission by itself.
51
+
52
+ ## 2. Import SDK
53
+
54
+ ```python
55
+ from sensor import *
56
+ ```
57
+
58
+ ## SensorController methods
59
+
60
+ ### 1. Initalize
61
+
62
+ ```python
63
+ SensorControllerInstance = SensorController()
64
+
65
+ # register scan listener
66
+ if not SensorControllerInstance.hasDeviceFoundCallback:
67
+ def on_device_callback(deviceList: List[BLEDevice]):
68
+ # return all devices doesn't connected
69
+ pass
70
+ SensorControllerInstance.onDeviceFoundCallback = on_device_callback
71
+ ```
72
+
73
+ ### 2. Start scan
74
+
75
+ Use `def startScan(period_in_ms: int) -> bool` to start scan
76
+
77
+ ```python
78
+ success = SensorControllerInstance.startScan(6000)
79
+ ```
80
+
81
+ returns true if start scan success, periodInMS means onDeviceCallback will be called every periodInMS
82
+
83
+ Use `def scan(period_in_ms: int) -> list[BLEDevice]` to scan once time
84
+
85
+ ```python
86
+ bleDevices = SensorControllerInstance.scan(6000)
87
+ ```
88
+
89
+ ### 3. Stop scan
90
+
91
+ Use `def stopScan() -> None` to stop scan
92
+
93
+ ```python
94
+ SensorControllerInstance.stopScan()
95
+ ```
96
+
97
+ ### 4. Check scaning
98
+
99
+ Use `property isScanning: bool` to check scanning status
100
+
101
+ ```python
102
+ isScanning = SensorControllerInstance.isScanning
103
+ ```
104
+
105
+ ### 5. Check if bluetooth is enabled
106
+
107
+ Use `property isEnable: bool` to check if bluetooth is enable
108
+
109
+ ```python
110
+ isEnable = SensorControllerInstance.isEnable
111
+ ```
112
+
113
+ ### 6. Create SensorProfile
114
+
115
+ Use `def requireSensor(device: BLEDevice) -> SensorProfile | None` to create SensorProfile.
116
+
117
+ If bleDevice is invalid, result is None.
118
+
119
+ ```python
120
+ sensorProfile = SensorControllerInstance.requireSensor(bleDevice)
121
+ ```
122
+
123
+ ### 7. Get SensorProfile
124
+
125
+ Use `def getSensor(device: BLEDevice) -> SensorProfile | None` to get SensorProfile.
126
+
127
+ If SensorProfile didn't created, result is None.
128
+
129
+ ```python
130
+ sensorProfile = SensorControllerInstance.getSensor(bleDevice)
131
+ ```
132
+
133
+ ### 8. Get Connected SensorProfiles
134
+
135
+ Use `def getConnectedSensors() -> list[SensorProfile]` to get connected SensorProfiles.
136
+
137
+ ```python
138
+ sensorProfiles = SensorControllerInstance.getConnectedSensors()
139
+ ```
140
+
141
+ ### 9. Get Connected BLE Devices
142
+
143
+ Use `def getConnectedDevices() -> list[BLEDevice]` to get connected BLE Devices.
144
+
145
+ ```python
146
+ bleDevices = SensorControllerInstance.getConnectedDevices()
147
+ ```
148
+
149
+ ### 10. Terminate
150
+
151
+ Use `def terminate()` to terminate sdk
152
+
153
+ ```python
154
+
155
+ def terminate():
156
+ SensorControllerInstance.terminate()
157
+ exit()
158
+
159
+ def main():
160
+ signal.signal(signal.SIGINT, lambda signal, frame: terminate())
161
+ time.sleep(30)
162
+ SensorControllerInstance.terminate()
163
+
164
+ Please MAKE SURE to call terminate when exit main() or press Ctrl+C
165
+ ```
166
+
167
+ ## SensorProfile methods
168
+
169
+ ### 11. Initalize
170
+
171
+ Please register callbacks for SensorProfile
172
+
173
+ ```python
174
+ sensorProfile = SensorControllerInstance.requireSensor(bleDevice)
175
+
176
+ # register callbacks
177
+ def on_state_changed(sensor, newState):
178
+ # please do logic when device disconnected unexpected
179
+ pass
180
+
181
+ def on_error_callback(sensor, reason):
182
+ # called when error occurs
183
+ pass
184
+
185
+ def on_power_changed(sensor, power):
186
+ # callback for get battery level of device, power from 0 - 100, -1 is invalid
187
+ pass
188
+
189
+ def on_data_callback(sensor, data):
190
+ # called after start data transfer
191
+ pass
192
+
193
+ sensorProfile.onStateChanged = on_state_changed
194
+ sensorProfile.onErrorCallback = on_error_callback
195
+ sensorProfile.onPowerChanged = on_power_changed
196
+ sensorProfile.onDataCallback = on_data_callback
197
+ ```
198
+
199
+ ### 12. Connect device
200
+
201
+ Use `def connect() -> bool` to connect.
202
+
203
+ ```python
204
+ success = sensorProfile.connect()
205
+ ```
206
+
207
+ ### 13. Disconnect
208
+
209
+ Use `def disconnect() -> bool` to disconnect.
210
+
211
+ ```python
212
+ success = sensorProfile.disconnect()
213
+ ```
214
+
215
+ ### 14. Get device status
216
+
217
+ Use `property deviceState: DeviceStateEx` to get device status.
218
+
219
+ Please send command in 'Ready' state, should be after connect() return True.
220
+
221
+ ```python
222
+ deviceStateEx = sensorProfile.deviceState
223
+
224
+ # deviceStateEx has define:
225
+ # class DeviceStateEx(Enum):
226
+ # Disconnected = 0
227
+ # Connecting = 1
228
+ # Connected = 2
229
+ # Ready = 3
230
+ # Disconnecting = 4
231
+ # Invalid = 5
232
+ ```
233
+
234
+ ### 15. Get BLE device of SensorProfile
235
+
236
+ Use `property BLEDevice: BLEDevice` to get BLE device of SensorProfile.
237
+
238
+ ```python
239
+ bleDevice = sensorProfile.BLEDevice
240
+ ```
241
+
242
+ ### 16. Get device info of SensorProfile
243
+
244
+ Use `def getDeviceInfo() -> dict | None` to get device info of SensorProfile.
245
+
246
+ Please call after device in 'Ready' state, return None if it's not connected.
247
+
248
+ ```python
249
+ deviceInfo = sensorProfile.getDeviceInfo()
250
+
251
+ # deviceInfo has defines:
252
+ # deviceInfo = {
253
+ # "deviceName": str,
254
+ # "modelName": str,
255
+ # "hardwareVersion": str,
256
+ # "firmwareVersion": str,
257
+ # "emgChannelCount": int,
258
+ # "eegChannelCount": int,
259
+ # "ecgChannelCount": int,
260
+ # "accChannelCount": int,
261
+ # "gyroChannelCount": int,
262
+ # "brthChannelCount": int,
263
+ # "mtuSize": int
264
+ # }
265
+ ```
266
+
267
+ ### 17. Init data transfer
268
+
269
+ Use `def init(packageSampleCount: int, powerRefreshInterval: int) -> bool`.
270
+
271
+ Please call after device in 'Ready' state, return True if init succeed.
272
+
273
+ ```python
274
+ success = sensorProfile.init(5, 60*1000)
275
+ ```
276
+
277
+ packageSampleCount: set sample counts of SensorData.channelSamples in onDataCallback()
278
+ powerRefreshInterval: callback period for onPowerChanged()
279
+
280
+ ### 18. Check if init data transfer succeed
281
+
282
+ Use `property hasInited: bool` to check if init data transfer succeed.
283
+
284
+ ```python
285
+ hasInited = sensorProfile.hasInited
286
+ ```
287
+
288
+ ### 19. DataNotify
289
+
290
+ Use `def startDataNotification() -> bool` to start data notification.
291
+
292
+ Please call if hasInited return True
293
+
294
+ #### 19.1 Start data transfer
295
+
296
+ ```python
297
+ success = sensorProfile.startDataNotification()
298
+ ```
299
+
300
+ Data type list:
301
+
302
+ ```python
303
+ class DataType(Enum):
304
+ NTF_ACC = 0x1 # unit is g
305
+ NTF_GYRO = 0x2 # unit is degree/s
306
+ NTF_EEG = 0x10 # unit is uV
307
+ NTF_ECG = 0x11 # unit is uV
308
+ NTF_BRTH = 0x15 # unit is uV
309
+ ```
310
+
311
+ Process data in onDataCallback.
312
+
313
+ ```python
314
+ def on_data_callback(sensor, data):
315
+ if data.dataType == DataType.NTF_EEG:
316
+ pass
317
+ elif data.dataType == DataType.NTF_ECG:
318
+ pass
319
+
320
+ # process data as you wish
321
+ for oneChannelSamples in data.channelSamples:
322
+ for sample in oneChannelSamples:
323
+ if sample.isLost:
324
+ # do some logic
325
+ pass
326
+ else:
327
+ # draw with sample.data & sample.channelIndex
328
+ # print(f"{sample.channelIndex} | {sample.sampleIndex} | {sample.data} | {sample.impedance}")
329
+ pass
330
+
331
+ sensorProfile.onDataCallback = on_data_callback
332
+ ```
333
+
334
+ #### 19.2 Stop data transfer
335
+
336
+ Use `def stopDataNotification() -> bool` to stop data transfer.
337
+
338
+ ```python
339
+ success = sensorProfile.stopDataNotification()
340
+ ```
341
+
342
+ #### 19.3 Check if it's data transfering
343
+
344
+ Use `property isDataTransfering: bool` to check if it's data transfering.
345
+
346
+ ```python
347
+ isDataTransfering = sensorProfile.isDataTransfering
348
+ ```
349
+
350
+ ### 20. Get battery level
351
+
352
+ Use `def getBatteryLevel() -> int` to get battery level. Please call after device in 'Ready' state.
353
+
354
+ ```python
355
+ batteryPower = sensorProfile.getBatteryLevel()
356
+
357
+ # batteryPower is battery level returned, value ranges from 0 to 100, 0 means out of battery, while 100 means full.
358
+ ```
359
+
360
+ Please check console.py in examples directory
361
+
362
+ ### Async methods
363
+
364
+ all methods start with async is async methods, they has same params and return result as sync methods.
365
+
366
+ Please check async_console.py in examples directory
367
+
368
+ ### setParam method
369
+
370
+ Use `def setParam(self, key: str, value: str) -> str` to set parameter of sensor profile. Please call after device in 'Ready' state.
371
+
372
+ Below is available key and value:
373
+
374
+ ```python
375
+ result = sensorProfile.setParam("NTF_EMG", "ON")
376
+ # set EMG data to ON or OFF, result is "OK" if succeed
377
+
378
+ result = sensorProfile.setParam("NTF_EEG", "ON")
379
+ # set EEG data to ON or OFF, result is "OK" if succeed
380
+
381
+ result = sensorProfile.setParam("NTF_ECG", "ON")
382
+ # set ECG data to ON or OFF, result is "OK" if succeed
383
+
384
+ result = sensorProfile.setParam("NTF_IMU", "ON")
385
+ # set IMU data to ON or OFF, result is "OK" if succeed
386
+
387
+ result = sensorProfile.setParam("NTF_BRTH", "ON")
388
+ # set BRTH data to ON or OFF, result is "OK" if succeed
389
+
390
+ result = sensorProfile.setParam("FILTER_50HZ", "ON")
391
+ # set 50Hz notch filter to ON or OFF, result is "OK" if succeed
392
+
393
+ result = sensorProfile.setParam("FILTER_60HZ", "ON")
394
+ # set 60Hz notch filter to ON or OFF, result is "OK" if succeed
395
+
396
+ result = sensorProfile.setParam("FILTER_HPF", "ON")
397
+ # set 0.5Hz hpf filter to ON or OFF, result is "OK" if succeed
398
+
399
+ result = sensorProfile.setParam("FILTER_LPF", "ON")
400
+ # set 80Hz lpf filter to ON or OFF, result is "OK" if succeed
401
+
402
+ result = sensorProfile.setParam("DEBUG_BLE_DATA_PATH", "d:/temp/test.csv")
403
+ # set debug ble data path, result is "OK" if succeed
404
+ # please give an absolute path and make sure it is valid and writeable by yourself
405
+ ```