mudra-sdk 0.2.2__tar.gz → 0.2.4__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.
Files changed (38) hide show
  1. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/PKG-INFO +1 -1
  2. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/__init__.py +3 -2
  3. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/cloud/api_config.py +1 -1
  4. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/cloud/data.py +3 -3
  5. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/cloud/mudra_server_client.py +3 -5
  6. mudra_sdk-0.2.4/mudra_sdk/libs/darwin/arm64/MudraSDK.dylib +0 -0
  7. mudra_sdk-0.2.4/mudra_sdk/libs/darwin/x86_64/MudraSDK.dylib +0 -0
  8. mudra_sdk-0.2.4/mudra_sdk/libs/windows/x64/MudraSDK.dll +0 -0
  9. mudra_sdk-0.2.4/mudra_sdk/libs/windows/x86/MudraSDK.dll +0 -0
  10. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/models/__init__.py +1 -0
  11. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/models/callbacks.py +1 -1
  12. mudra_sdk-0.2.4/mudra_sdk/models/firmware_details.py +12 -0
  13. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/models/mudra.py +4 -3
  14. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/models/mudra_device.py +55 -19
  15. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/service/ble_service.py +5 -14
  16. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk.egg-info/PKG-INFO +1 -1
  17. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk.egg-info/SOURCES.txt +1 -0
  18. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/setup.py +1 -1
  19. mudra_sdk-0.2.2/mudra_sdk/libs/darwin/arm64/MudraSDK.dylib +0 -0
  20. mudra_sdk-0.2.2/mudra_sdk/libs/darwin/x86_64/MudraSDK.dylib +0 -0
  21. mudra_sdk-0.2.2/mudra_sdk/libs/windows/x64/MudraSDK.dll +0 -0
  22. mudra_sdk-0.2.2/mudra_sdk/libs/windows/x86/MudraSDK.dll +0 -0
  23. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/MANIFEST.in +0 -0
  24. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/README.md +0 -0
  25. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/cloud/__init__.py +0 -0
  26. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/cloud/auth_storage.py +0 -0
  27. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/cloud/request_interceptor.py +0 -0
  28. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/libs/__init__.py +0 -0
  29. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/libs/library_loader.py +0 -0
  30. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/models/computation_wrapper.py +0 -0
  31. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/models/data_recorder.py +0 -0
  32. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/models/enums.py +0 -0
  33. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/models/firmware_status.py +0 -0
  34. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk/service/__init__.py +0 -0
  35. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk.egg-info/dependency_links.txt +0 -0
  36. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk.egg-info/requires.txt +0 -0
  37. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/mudra_sdk.egg-info/top_level.txt +0 -0
  38. {mudra_sdk-0.2.2 → mudra_sdk-0.2.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mudra_sdk
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python SDK for Mudra with native library support
5
5
  Author: Foad Khoury
6
6
  Project-URL: Documentation, https://wearable-devices.github.io/#welcome
@@ -2,7 +2,7 @@
2
2
  Mudra SDK - Python client for Mudra API
3
3
  """
4
4
 
5
- __version__ = "0.2.2"
5
+ __version__ = "0.2.4"
6
6
  __author__ = "Foad Khoury"
7
7
 
8
8
  # Import main classes/functions to make them available at package level
@@ -13,6 +13,7 @@ from .service import *
13
13
  # Define what's available when someone does: from mudra_sdk import *
14
14
  __all__ = [
15
15
  "Mudra",
16
- "MudraDevice",
16
+ "MudraDevice",
17
17
  "FirmwareCallbacks",
18
+ "FirmwareDetails",
18
19
  ]
@@ -122,7 +122,7 @@ class Endpoint(Enum):
122
122
  ABORT_VIDEO_RECORDING = ("data/upload/abortVideoRecording", HttpMethod.POST)
123
123
  DOWNLOAD_RECORDING = ("data/download/recordingDb", HttpMethod.POST)
124
124
  DOWNLOAD_VIDEO_RECORDING_DIRECT = ("data/download/directVideoRecording", HttpMethod.POST)
125
- LICENSE_SECURE_TOKEN = ("license/secure-token", HttpMethod.POST)
125
+ LICENSE_SECURE_TOKEN = ("license/getLicense", HttpMethod.POST)
126
126
 
127
127
  def __init__(self, path: str, method: HttpMethod):
128
128
  self.path = path
@@ -458,12 +458,12 @@ class SetSerialNumberRequest:
458
458
  @dataclass
459
459
  class LicenseSecureTokenRequest:
460
460
  """Request for license/secure-token API"""
461
- random_number: bytes
461
+ securityNumber: int
462
462
 
463
463
  def to_json(self) -> Dict[str, Any]:
464
464
  return {
465
- 'encryptionMethod': 'BITWISE',
466
- 'random_number': list(self.random_number),
465
+ 'encryptionMethod': 'ADVANCED',
466
+ 'securityNumber': self.securityNumber,
467
467
  }
468
468
 
469
469
 
@@ -622,12 +622,10 @@ class MudraServerClient(RequestInterceptorDelegate):
622
622
  }
623
623
  )
624
624
 
625
- def send_security_number_api_call(self, random_number: bytes) -> Optional[bytes]:
626
- """Call license secure token API with random number. Returns token as bytes or None.
627
- uses first 4 bytes of random_number."""
625
+ def send_security_number_api_call(self, securityNumber: int) -> Optional[bytes]:
626
+ """Call license secure token API with value. Returns token as bytes or None."""
628
627
  try:
629
- first_four = random_number[:4] if len(random_number) >= 4 else random_number
630
- request = LicenseSecureTokenRequest(random_number=bytes(first_four))
628
+ request = LicenseSecureTokenRequest(securityNumber=securityNumber)
631
629
  response = self._make_api_call(
632
630
  endpoint=Endpoint.LICENSE_SECURE_TOKEN,
633
631
  data=request.to_json(),
@@ -1,3 +1,4 @@
1
1
  from .mudra import Mudra
2
2
  from .mudra_device import MudraDevice
3
3
  from .enums import FirmwareCallbacks
4
+ from .firmware_details import FirmwareDetails
@@ -130,7 +130,7 @@ class BleServiceDelegate(ABC):
130
130
  pass
131
131
 
132
132
  @abstractmethod
133
- def on_security_number_received(self, device_address: str, data: bytes):
133
+ def on_security_number_received(self, device_address: str, value: int):
134
134
  pass
135
135
 
136
136
  @abstractmethod
@@ -0,0 +1,12 @@
1
+ class FirmwareDetails:
2
+
3
+ __slots__ = ()
4
+
5
+ def __new__(cls):
6
+ raise TypeError("%s cannot be instantiated" % cls.__name__)
7
+
8
+ #: Stable firmware version for Mudra Band (DFU, device info, cloud details).
9
+ STABLE_VERSION_MUDRA = "6.0.8.5"
10
+
11
+ #: Release / MDK target firmware version for update checks.
12
+ RELEASE_VERSION_MUDRA = "6.0.8.5"
@@ -91,7 +91,7 @@ class Mudra(BleServiceDelegate):
91
91
 
92
92
  async def set_band_mode(self, device: mudraDeviceModule.MudraDevice, band_mode: BandMode):
93
93
  await self._ble_service.set_band_mode(device, band_mode)
94
-
94
+
95
95
  async def get_security_number(self, device: mudraDeviceModule.MudraDevice):
96
96
  await self._ble_service.get_security_number(device)
97
97
 
@@ -157,9 +157,9 @@ class Mudra(BleServiceDelegate):
157
157
  if device_address in self._mudra_devices:
158
158
  self._mudra_devices[device_address].on_firmware_version_received(version)
159
159
 
160
- def on_security_number_received(self, device_address: str, data: bytes):
160
+ def on_security_number_received(self, device_address: str, value: int):
161
161
  if device_address in self._mudra_devices:
162
- self._mudra_devices[device_address].on_security_number_received(data)
162
+ self._mudra_devices[device_address].on_security_number_received(value)
163
163
 
164
164
  def on_battery_level_changed(self, device_address: str, level: int):
165
165
  if device_address in self._mudra_devices:
@@ -185,6 +185,7 @@ class Mudra(BleServiceDelegate):
185
185
  mudra_device = self._mudra_devices[device.address]
186
186
  if mudra_device and self._delegate:
187
187
  self._delegate.on_mudra_device_disconnected(device)
188
+ mudra_device.handle_disconnection()
188
189
 
189
190
  def on_mudra_device_disconnecting(self, device: BLEDevice):
190
191
  mudra_device = self._mudra_devices[device.address]
@@ -10,6 +10,40 @@ import mudra_sdk.models.mudra as mudraModule
10
10
  import mudra_sdk.models.computation_wrapper as computationWrapperModule
11
11
  from mudra_sdk.service.ble_service import MudraBLEServicesUUID, MudraCharacteristicUUID
12
12
 
13
+
14
+ class SecurityNumberPoller:
15
+ __slots__ = ("_device", "_task")
16
+
17
+ def __init__(self, device: "MudraDevice"):
18
+ self._device = device
19
+ self._task: Optional[asyncio.Task] = None
20
+
21
+ def start(self) -> None:
22
+ if self._task is not None and not self._task.done():
23
+ return
24
+ loop = asyncio.get_running_loop()
25
+ self._task = loop.create_task(self._poll_until_received())
26
+
27
+ def stop(self) -> None:
28
+ if self._task is None:
29
+ return
30
+ task = self._task
31
+ self._task = None
32
+ if not task.done():
33
+ task.cancel()
34
+
35
+ async def _poll_until_received(self) -> None:
36
+ try:
37
+ while True:
38
+ await self._device.get_security_number()
39
+ await asyncio.sleep(0.1)
40
+ except asyncio.CancelledError:
41
+ raise
42
+ finally:
43
+ if self._task is asyncio.current_task():
44
+ self._task = None
45
+
46
+
13
47
  class MudraDevice(BLEDevice):
14
48
  def __init__(self, ble_device: BLEDevice):
15
49
  super().__init__(ble_device.address, ble_device.name, ble_device.details)
@@ -49,8 +83,8 @@ class MudraDevice(BLEDevice):
49
83
  FirmwareDataType.embeddedAirTouch: False,
50
84
  }
51
85
  ### ----------------------- Parameters ----------------------- ###
52
- self._security_number: Optional[bytes] = None
53
86
  self._license_sent: bool = False
87
+ self._security_number_poller = SecurityNumberPoller(self)
54
88
 
55
89
  ### ----------------------- Data Recorder ----------------------- ###
56
90
  self._data_recorder = DataRecorder()
@@ -61,7 +95,6 @@ class MudraDevice(BLEDevice):
61
95
  self._on_navigation_axis_ready: Optional[computationWrapperModule.OnNavigationAxisReceivedCallback] = None
62
96
  self._on_navigation_direction_ready: Optional[computationWrapperModule.OnNavigationDirectionReceivedCallback] = None
63
97
  self._on_button_changed_ready: Optional[computationWrapperModule.OnAirMouseButtonChangedReceivedCallback] = None
64
- self._on_security_number_received: Optional[computationWrapperModule.OnSecurityNumberReceivedCallback] = None
65
98
 
66
99
  ### ----------------------- Parameters Callbacks ----------------------- ###
67
100
  self._on_charging_state_changed: Optional[computationWrapperModule.OnChargingStateChangedCallback] = None
@@ -85,10 +118,11 @@ class MudraDevice(BLEDevice):
85
118
  if not status:
86
119
  return False
87
120
  print(f"Device {self.name} is finished initializing")
88
- await self.get_security_number()
89
- await self.get_security_number()
90
121
  return await mudraModule.Mudra().is_connected(self)
91
122
 
123
+ def handle_disconnection(self):
124
+ self._security_number_poller.stop()
125
+
92
126
  ### ----------------------- Ready Methods ----------------------- ###
93
127
 
94
128
  def on_pressure_data_received(self, pressure_data: float):
@@ -113,26 +147,31 @@ class MudraDevice(BLEDevice):
113
147
  self._on_navigation_direction_ready(direction)
114
148
 
115
149
  def on_charging_state_changed(self, is_charging: bool):
150
+ print(f"Charging state changed: {is_charging}")
116
151
  self._is_charging = is_charging
117
152
  if self._on_charging_state_changed:
118
153
  self._on_charging_state_changed(is_charging)
119
154
 
120
155
  def on_serial_number_right_part_received(self, serial_number: int):
156
+ print(f"Serial number right part received: {serial_number}")
121
157
  self._serial_number_right_part = serial_number
122
158
 
123
159
  def on_serial_number_left_part_received(self, serial_number: int):
160
+ print(f"Serial number left part received: {serial_number}")
124
161
  self._serial_number_left_part = serial_number
125
162
 
126
163
  def on_firmware_version_received(self, version: str):
164
+ print(f"Firmware version received: {version}")
127
165
  self._firmware_version = version
128
166
 
129
- def on_security_number_received(self, data: bytes):
130
- self._security_number = data
131
- if self._on_security_number_received:
132
- self._on_security_number_received(data)
167
+ def on_security_number_received(self, value: int):
168
+ print(f"Security number received: {value}")
133
169
 
170
+ self._security_number_poller.stop()
134
171
  if not self._license_sent:
135
- self._send_security_number(data)
172
+ self._license_sent = True
173
+ loop = asyncio.get_running_loop()
174
+ loop.create_task(self._send_security_number(value))
136
175
 
137
176
  def on_battery_level_changed(self, level: int):
138
177
  self._battery_level = level
@@ -145,7 +184,6 @@ class MudraDevice(BLEDevice):
145
184
  return self._is_charging
146
185
 
147
186
  def get_serial_number(self) -> Optional[int]:
148
- self._license_sent = False
149
187
  if self._serial_number_right_part is not None and self._serial_number_left_part is not None:
150
188
  return self._serial_number_right_part + self._serial_number_left_part
151
189
  return None
@@ -167,20 +205,19 @@ class MudraDevice(BLEDevice):
167
205
  if self._characteristic_status[MudraCharacteristicUUID.COMMAND_CHARACTERISTIC]:
168
206
  await mudraModule.Mudra().set_band_mode(self, band_mode)
169
207
 
170
- async def get_security_number(self,):
208
+ async def get_security_number(self):
171
209
  if self._characteristic_status[MudraCharacteristicUUID.COMMAND_CHARACTERISTIC]:
172
210
  await mudraModule.Mudra().get_security_number(self)
173
211
 
174
- def _send_security_number(self, random_number: bytes):
175
- """Call send_security_number API and set license on device"""
176
- self._license_sent = True
212
+ async def _send_security_number(self, value: int):
213
+ """Sync fallback when no asyncio loop is running."""
177
214
  try:
178
- token = MudraServerClient().send_security_number_api_call(random_number)
215
+ token = MudraServerClient().send_security_number_api_call(value)
179
216
  if token is not None:
180
- # Schedule async set_license - BLE callback may be sync but typically runs in async context
181
217
  try:
182
218
  loop = asyncio.get_running_loop()
183
219
  loop.create_task(self.set_license(token))
220
+ self._license_sent = False
184
221
  except RuntimeError:
185
222
  print("Cannot set license: no running event loop (ensure app uses asyncio)")
186
223
  else:
@@ -203,6 +240,8 @@ class MudraDevice(BLEDevice):
203
240
  def on_characteristic_discovered(self, characteristic_uuid: MudraCharacteristicUUID):
204
241
  print(f"Characteristic discovered: {characteristic_uuid}")
205
242
  self._characteristic_status[characteristic_uuid] = True
243
+ if(characteristic_uuid == MudraCharacteristicUUID.COMMAND_CHARACTERISTIC):
244
+ self._security_number_poller.start()
206
245
 
207
246
  def on_firmware_status_updated(self, data: bytes):
208
247
  self.firmware_status.update(data)
@@ -264,9 +303,6 @@ class MudraDevice(BLEDevice):
264
303
  self._computation_wrapper.set_air_touch_active(active)
265
304
  await self._update_state_enabled()
266
305
 
267
- async def set_on_security_number_received(self, callback: computationWrapperModule.OnSecurityNumberReceivedCallback) -> None:
268
- self._on_security_number_received = callback
269
-
270
306
  ### ----------------------- Is Callbacks set Methods ----------------------- ###
271
307
 
272
308
  def is_on_imu_gyro_callback_set(self) -> bool:
@@ -2,7 +2,7 @@ import asyncio
2
2
  from calendar import c
3
3
  import cmd
4
4
  from enum import Enum
5
- from typing import Any, Callable, Optional, Dict
5
+ from typing import Any, Callable, Optional, Dict, List, Tuple
6
6
  from bleak import BleakScanner, BleakClient
7
7
  from bleak.backends.device import BLEDevice
8
8
  from bleak.backends.service import BleakGATTServiceCollection
@@ -10,8 +10,6 @@ from mudra_sdk.models.enums import AirMouseButton, FirmwareCallbacks, FirmwareCo
10
10
 
11
11
  from ..models.callbacks import BleServiceDelegate
12
12
 
13
- from typing import Optional
14
-
15
13
  class BleService:
16
14
  _instance = None
17
15
  _delegate: Optional[BleServiceDelegate] = None
@@ -56,8 +54,6 @@ class BleService:
56
54
  # Attempt connection
57
55
  print(f"Calling BleakClient.connect() for {address}")
58
56
  await client.connect()
59
- import asyncio
60
- await asyncio.sleep(10)
61
57
 
62
58
  # Store the connected client
63
59
  self._connected_clients[address] = client
@@ -266,13 +262,13 @@ class BleService:
266
262
  case FirmwareCallbacks.BAND_CONNECTION_STATUS:
267
263
  pass
268
264
  case FirmwareCallbacks.SecurityNumber:
269
- if self._delegate:
270
- self._delegate.on_security_number_received(device.address, data[1:])
265
+ if self._delegate and len(data) >= 5:
266
+ value = data[4] << 24 | data[3] << 16 | data[2] << 8 | data[1]
267
+ self._delegate.on_security_number_received(device.address, value)
271
268
  case FirmwareCallbacks.NONE:
272
269
  print("NONE")
273
270
  case _:
274
271
  print("Unknown firmware callback")
275
-
276
272
  def _logging_notification_handler(self, device: BLEDevice, sender: int, data: bytearray):
277
273
  """Handle LOGGING characteristic notifications."""
278
274
  print(f"LOGGING notification from {sender} (device {device.address}): {data.hex()}")
@@ -449,15 +445,10 @@ class BleService:
449
445
  await self.send_general_command(device, bytes(cmd))
450
446
 
451
447
  async def set_license(self, device: BLEDevice, data: bytes):
452
- cmd = bytearray(FirmwareCommand.setLicense.id)
453
- cmd[2] = data[0]
454
- cmd[3] = data[1]
455
- cmd[4] = data[2]
456
- cmd[5] = data[3]
448
+ cmd = bytearray(list(FirmwareCommand.setLicense.id) + list(data))
457
449
  await self.send_general_command(device, bytes(cmd))
458
450
 
459
451
  ### ----------------------- Scan Methods ----------------------- ###
460
-
461
452
  @property
462
453
  def _is_scanning(self) -> bool:
463
454
  """Check if currently scanning for devices."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mudra_sdk
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python SDK for Mudra with native library support
5
5
  Author: Foad Khoury
6
6
  Project-URL: Documentation, https://wearable-devices.github.io/#welcome
@@ -24,6 +24,7 @@ mudra_sdk/models/callbacks.py
24
24
  mudra_sdk/models/computation_wrapper.py
25
25
  mudra_sdk/models/data_recorder.py
26
26
  mudra_sdk/models/enums.py
27
+ mudra_sdk/models/firmware_details.py
27
28
  mudra_sdk/models/firmware_status.py
28
29
  mudra_sdk/models/mudra.py
29
30
  mudra_sdk/models/mudra_device.py
@@ -29,7 +29,7 @@ if libs_dir.exists():
29
29
 
30
30
  setup(
31
31
  name="mudra_sdk",
32
- version="0.2.2",
32
+ version="0.2.4",
33
33
  packages=find_packages(exclude=["test*", "tests*"]),
34
34
  install_requires=[
35
35
  "bleak>=0.21.0",
File without changes
File without changes
File without changes