odrive 0.6.9.dev0__py37-none-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.
- odrive/__init__.py +142 -0
- odrive/api_client.py +54 -0
- odrive/config.py +1296 -0
- odrive/crypto.py +369 -0
- odrive/data/brakeRs.json +18 -0
- odrive/data/drvs.json +82 -0
- odrive/data/encoders.json +86 -0
- odrive/data/motors.json +128 -0
- odrive/data/odrive-micro-x1.json +48 -0
- odrive/data/odrive-micro-x3.json +40 -0
- odrive/data/odrive-micro-x4.json +40 -0
- odrive/data/odrive-s1-x4.json +52 -0
- odrive/data/odrive-v4.4.json +58 -0
- odrive/data/schema-brakeR.json +6 -0
- odrive/data/schema-drv.json +6 -0
- odrive/data/schema-encoders.json +6 -0
- odrive/data/schema-motors.json +6 -0
- odrive/data/schema-odrive.json +5 -0
- odrive/data/schema.json +244 -0
- odrive/database.py +184 -0
- odrive/dfu.py +195 -0
- odrive/dfuse/DfuDevice.py +297 -0
- odrive/dfuse/__init__.py +1 -0
- odrive/enums.py +415 -0
- odrive/firmware.py +102 -0
- odrive/hw_version.py +67 -0
- odrive/legacy.py +73 -0
- odrive/legacy_config.py +123 -0
- odrive/legacy_dfu.py +442 -0
- odrive/lib/libodrive-windows-x64.dll +0 -0
- odrive/libodrive.py +325 -0
- odrive/pyfibre/fibre/__init__.py +4 -0
- odrive/pyfibre/fibre/libfibre-windows-amd64.dll +0 -0
- odrive/pyfibre/fibre/libfibre.py +1078 -0
- odrive/pyfibre/fibre/libwinpthread-1.dll +0 -0
- odrive/pyfibre/fibre/shell.py +162 -0
- odrive/pyfibre/fibre/utils.py +133 -0
- odrive/release_api.py +184 -0
- odrive/rich_text.py +134 -0
- odrive/shell.py +144 -0
- odrive/utils.py +881 -0
- odrive/version.py +1 -0
- odrive-0.6.9.dev0.data/scripts/odrive_demo.py +50 -0
- odrive-0.6.9.dev0.data/scripts/odrivetool +263 -0
- odrive-0.6.9.dev0.data/scripts/odrivetool.bat +2 -0
- odrive-0.6.9.dev0.dist-info/METADATA +22 -0
- odrive-0.6.9.dev0.dist-info/RECORD +49 -0
- odrive-0.6.9.dev0.dist-info/WHEEL +5 -0
- odrive-0.6.9.dev0.dist-info/top_level.txt +1 -0
odrive/libodrive.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import ctypes
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import sys
|
|
7
|
+
import threading
|
|
8
|
+
from typing import Callable, Dict, List, Optional, Tuple
|
|
9
|
+
from .hw_version import HwVersion
|
|
10
|
+
|
|
11
|
+
_lib_names = {
|
|
12
|
+
('Linux', 'x86_64'): 'libodrive-linux-x86_64.so',
|
|
13
|
+
('Linux', 'aarch64'): 'libodrive-linux-aarch64.so',
|
|
14
|
+
('Windows', 'AMD64'): 'libodrive-windows-x64.dll',
|
|
15
|
+
('Darwin', 'x86_64'): 'libodrive-macos-x86_64.dylib',
|
|
16
|
+
('Darwin', 'arm64'): 'libodrive-macos-arm64.dylib',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class _HwVersion(ctypes.Structure):
|
|
20
|
+
_fields_ = [("product_line", ctypes.c_uint8),
|
|
21
|
+
("version", ctypes.c_uint8),
|
|
22
|
+
("variant", ctypes.c_uint8),
|
|
23
|
+
("reserved", ctypes.c_uint8)]
|
|
24
|
+
|
|
25
|
+
class _FwManifest(ctypes.Structure):
|
|
26
|
+
_fields_ = [("magic_number", ctypes.c_uint32),
|
|
27
|
+
("fw_version_major", ctypes.c_uint8),
|
|
28
|
+
("fw_version_minor", ctypes.c_uint8),
|
|
29
|
+
("fw_version_revision", ctypes.c_uint8),
|
|
30
|
+
("fw_version_unreleased", ctypes.c_uint8),
|
|
31
|
+
("hw_version", _HwVersion),
|
|
32
|
+
("reserved", ctypes.c_uint8 * 32),
|
|
33
|
+
("build", ctypes.c_uint8 * 20)]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LibODrive():
|
|
37
|
+
"""
|
|
38
|
+
This class is not thread-safe.
|
|
39
|
+
|
|
40
|
+
All public member functions should be called from the same thread as `loop`
|
|
41
|
+
and all callbacks are called on `loop`.
|
|
42
|
+
|
|
43
|
+
Internally, this class launches a separate I/O thread on which most of
|
|
44
|
+
libodrive's backend actually runs. Libodrive handles event passing between
|
|
45
|
+
the two threads.
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def get_default_lib_path():
|
|
50
|
+
_system_desc = (platform.system(), platform.machine())
|
|
51
|
+
|
|
52
|
+
if _system_desc == ('Linux', 'aarch64') and sys.maxsize <= 2**32:
|
|
53
|
+
_system_desc = ('Linux', 'armv7l') # Python running in 32-bit mode on 64-bit OS
|
|
54
|
+
|
|
55
|
+
if not _system_desc in _lib_names:
|
|
56
|
+
raise ModuleNotFoundError("libodrive is not supported on your platform ({} {}).")
|
|
57
|
+
|
|
58
|
+
_script_dir = os.path.dirname(os.path.realpath(__file__))
|
|
59
|
+
return os.path.join(_script_dir, "lib", _lib_names[_system_desc])
|
|
60
|
+
|
|
61
|
+
@property
|
|
62
|
+
def version(self):
|
|
63
|
+
return (
|
|
64
|
+
(self._version.value >> 24) & 0xff,
|
|
65
|
+
(self._version.value >> 16) & 0xff,
|
|
66
|
+
(self._version.value >> 8) & 0xff,
|
|
67
|
+
(self._version.value >> 0) & 0xff,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def __init__(self, loop: asyncio.AbstractEventLoop, lib_path: Optional[str] = None):
|
|
71
|
+
self._loop: asyncio.AbstractEventLoop = asyncio.get_running_loop() if loop is None else loop
|
|
72
|
+
|
|
73
|
+
if lib_path is None:
|
|
74
|
+
lib_path = LibODrive.get_default_lib_path()
|
|
75
|
+
if not os.path.isfile(lib_path):
|
|
76
|
+
raise ImportError(f"{lib_path} not found. Try to reinstall the Python package. If you're a developer, run tools/setup.sh first.")
|
|
77
|
+
|
|
78
|
+
if os.name == 'nt':
|
|
79
|
+
dll_dir = os.path.dirname(lib_path)
|
|
80
|
+
if sys.version_info >= (3, 8):
|
|
81
|
+
os.add_dll_directory(dll_dir)
|
|
82
|
+
else:
|
|
83
|
+
os.environ['PATH'] = dll_dir + os.pathsep + os.environ['PATH']
|
|
84
|
+
self._lib = ctypes.windll.LoadLibrary(lib_path)
|
|
85
|
+
else:
|
|
86
|
+
self._lib = ctypes.cdll.LoadLibrary(lib_path)
|
|
87
|
+
|
|
88
|
+
self._version = ctypes.c_uint32.in_dll(self._lib, 'libodrive_version')
|
|
89
|
+
if self._version.value & 0xffff0000 != 0x00070000:
|
|
90
|
+
raise ImportError(f"Incompatible libodrive version ({self._version.value:08X}). Try to reinstall the Python package. If you're a developer, run tools/setup.sh first.")
|
|
91
|
+
|
|
92
|
+
# Load functions
|
|
93
|
+
self._lib.libodrive_init.argtypes = []
|
|
94
|
+
self._lib.libodrive_init.restype = ctypes.c_void_p
|
|
95
|
+
|
|
96
|
+
self._lib.libodrive_deinit.argtypes = [ctypes.c_void_p]
|
|
97
|
+
self._lib.libodrive_deinit.restype = None
|
|
98
|
+
|
|
99
|
+
self._lib.libodrive_iteration.argtypes = [ctypes.c_void_p, ctypes.c_int]
|
|
100
|
+
self._lib.libodrive_iteration.restype = ctypes.c_int
|
|
101
|
+
|
|
102
|
+
self._lib.libodrive_interrupt_iteration.argtypes = [ctypes.c_void_p]
|
|
103
|
+
self._lib.libodrive_interrupt_iteration.restype = ctypes.c_int
|
|
104
|
+
|
|
105
|
+
self._lib.libodrive_handle_callbacks.argtypes = [ctypes.c_void_p]
|
|
106
|
+
self._lib.libodrive_handle_callbacks.restype = ctypes.c_int
|
|
107
|
+
|
|
108
|
+
self._lib.libodrive_start_usb_discovery.argtypes = [ctypes.c_void_p, _TOnFoundDevice, _TOnLostDevice, ctypes.c_void_p]
|
|
109
|
+
self._lib.libodrive_start_usb_discovery.restype = ctypes.c_void_p
|
|
110
|
+
|
|
111
|
+
self._lib.libodrive_stop_discovery.argtypes = [ctypes.c_void_p]
|
|
112
|
+
self._lib.libodrive_stop_discovery.restype = ctypes.c_int
|
|
113
|
+
|
|
114
|
+
self._lib.libodrive_usb_device_from_handle.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
|
|
115
|
+
self._lib.libodrive_usb_device_from_handle.restype = None
|
|
116
|
+
|
|
117
|
+
self._lib.libodrive_connect_bootloader.argtypes = [ctypes.c_void_p, _TOnConnected, _TOnConnectionFailed]
|
|
118
|
+
self._lib.libodrive_connect_bootloader.restype = ctypes.c_int
|
|
119
|
+
|
|
120
|
+
self._lib.libodrive_disconnect.argtypes = [ctypes.c_void_p]
|
|
121
|
+
self._lib.libodrive_disconnect.restype = ctypes.c_int
|
|
122
|
+
|
|
123
|
+
self._lib.libodrive_start_installation.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int, _TOnInstallationProgress, _TOnInstallationDone]
|
|
124
|
+
self._lib.libodrive_start_installation.restype = ctypes.c_int
|
|
125
|
+
|
|
126
|
+
self._lib.libodrive_open_firmware.argtypes = [ctypes.c_char_p, ctypes.c_size_t, ctypes.POINTER(ctypes.c_void_p), ctypes.POINTER(ctypes.POINTER(_FwManifest))]
|
|
127
|
+
self._lib.libodrive_open_firmware.restype = ctypes.c_int
|
|
128
|
+
|
|
129
|
+
self._lib.libodrive_close_firmware.argtypes = [ctypes.c_void_p]
|
|
130
|
+
self._lib.libodrive_close_firmware.restype = None
|
|
131
|
+
|
|
132
|
+
# Init
|
|
133
|
+
self._ctx = self._lib.libodrive_init()
|
|
134
|
+
assert self._ctx
|
|
135
|
+
|
|
136
|
+
self._notify_handle: Optional[asyncio.Handle] = None
|
|
137
|
+
self._worker_thread_shutdown = False
|
|
138
|
+
self._worker_thread = threading.Thread(target=self._worker_thread_func)
|
|
139
|
+
self._notify_pending = False
|
|
140
|
+
#self._worker_thread.start()
|
|
141
|
+
|
|
142
|
+
def stop_thread(self):
|
|
143
|
+
self._worker_thread_shutdown = True
|
|
144
|
+
self._lib.libodrive_interrupt_iteration(self._ctx)
|
|
145
|
+
self._worker_thread.join()
|
|
146
|
+
if not self._notify_handle is None:
|
|
147
|
+
self._notify_handle.cancel()
|
|
148
|
+
|
|
149
|
+
def deinit(self):
|
|
150
|
+
self._lib.libodrive_deinit(self._ctx)
|
|
151
|
+
|
|
152
|
+
def _notify(self):
|
|
153
|
+
self._notify_pending = False
|
|
154
|
+
self._lib.libodrive_handle_callbacks(self._ctx)
|
|
155
|
+
|
|
156
|
+
def _worker_thread_func(self):
|
|
157
|
+
while not self._worker_thread_shutdown:
|
|
158
|
+
result = self._lib.libodrive_iteration(self._ctx, -1) # no timeout
|
|
159
|
+
assert result == 0 or result == 1
|
|
160
|
+
if result == 1 and not self._notify_pending:
|
|
161
|
+
self._notify_pending = True
|
|
162
|
+
self._notify_handle = self._loop.call_soon_threadsafe(self._notify)
|
|
163
|
+
|
|
164
|
+
def start_usb_discovery(self):
|
|
165
|
+
def _enter(intf: Interface):
|
|
166
|
+
handle = self._lib.libodrive_start_usb_discovery(self._ctx, _on_found_device, _on_lost_device, id(intf))
|
|
167
|
+
assert handle
|
|
168
|
+
intf._handle = handle
|
|
169
|
+
_intf_map[id(intf)] = intf # callbacks only called on same thread, so it's safe to assign this after starting
|
|
170
|
+
return intf
|
|
171
|
+
intf = Interface(self._lib, _enter)
|
|
172
|
+
return intf
|
|
173
|
+
|
|
174
|
+
def open_firmware(self, data: bytes):
|
|
175
|
+
handle = ctypes.c_void_p()
|
|
176
|
+
manifest = ctypes.POINTER(_FwManifest)()
|
|
177
|
+
self._lib.libodrive_open_firmware(data, len(data), ctypes.byref(handle), ctypes.byref(manifest))
|
|
178
|
+
assert handle
|
|
179
|
+
assert manifest
|
|
180
|
+
return Firmware(self._lib, handle, manifest.contents)
|
|
181
|
+
|
|
182
|
+
class Interface():
|
|
183
|
+
@staticmethod
|
|
184
|
+
def _on_found_device(udata: int, device: int, serial_number: bytes, product_string: bytes, fibre2_capable: bool, handle: int, msg: bytes):
|
|
185
|
+
intf = _intf_map[udata]
|
|
186
|
+
py_dev = Device(intf._lib, device, serial_number.decode(), product_string.decode(), fibre2_capable)
|
|
187
|
+
_dev_map[device] = py_dev
|
|
188
|
+
intf.devices.append(py_dev)
|
|
189
|
+
|
|
190
|
+
# signal all waiting tasks and recycle _event for next round
|
|
191
|
+
intf._event.set()
|
|
192
|
+
intf._event = asyncio.Event()
|
|
193
|
+
|
|
194
|
+
@staticmethod
|
|
195
|
+
def _on_lost_device(udata: int, device: int):
|
|
196
|
+
intf = _intf_map[udata]
|
|
197
|
+
py_dev = _dev_map.pop(device)
|
|
198
|
+
intf.devices.remove(py_dev)
|
|
199
|
+
|
|
200
|
+
def __init__(self, lib, enter_impl):
|
|
201
|
+
self._lib = lib
|
|
202
|
+
self._enter_impl = enter_impl
|
|
203
|
+
self._handle = 0 # set in start_usb_discovery()
|
|
204
|
+
self.devices: List[Device] = []
|
|
205
|
+
self._event = asyncio.Event()
|
|
206
|
+
|
|
207
|
+
def __enter__(self):
|
|
208
|
+
return self._enter_impl(self)
|
|
209
|
+
|
|
210
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
211
|
+
_intf_map.pop(id(self))
|
|
212
|
+
self._lib.libodrive_stop_discovery(self._handle)
|
|
213
|
+
self._handle = 0
|
|
214
|
+
|
|
215
|
+
def get_device(self, serial_number: Optional[str], is_bootloader: Optional[bool]):
|
|
216
|
+
for dev in self.devices:
|
|
217
|
+
if not serial_number is None and dev.serial_number != serial_number:
|
|
218
|
+
continue
|
|
219
|
+
if not is_bootloader is None and dev.is_bootloader != is_bootloader:
|
|
220
|
+
continue
|
|
221
|
+
return dev
|
|
222
|
+
return None
|
|
223
|
+
|
|
224
|
+
async def wait_for(self, serial_number: Optional[str], is_bootloader: Optional[bool]):
|
|
225
|
+
while True:
|
|
226
|
+
dev = self.get_device(serial_number, is_bootloader)
|
|
227
|
+
if not dev is None:
|
|
228
|
+
return dev
|
|
229
|
+
await self._event.wait() # wait for another change in the set of known devices
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class Device():
|
|
233
|
+
def __init__(self, lib, handle: int, serial_number: str, product_string: str, is_bootloader: bool):
|
|
234
|
+
self._lib = lib
|
|
235
|
+
self._handle = handle
|
|
236
|
+
self.serial_number = serial_number
|
|
237
|
+
self.product_string = product_string
|
|
238
|
+
self.is_bootloader = is_bootloader
|
|
239
|
+
self.hw_version = None
|
|
240
|
+
self._connection_future: asyncio.Future = asyncio.Future()
|
|
241
|
+
self._on_installation_progress_cb: Optional[Callable[[bool, str, int, int], None]] = None
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def _on_connected(device: int, hw_version, manufacturer: bytes):
|
|
245
|
+
_dev_map[device].hw_version = HwVersion.from_tuple((hw_version.contents.product_line, hw_version.contents.version, hw_version.contents.variant))
|
|
246
|
+
_dev_map[device]._connection_future.set_result(None)
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def _on_connection_failed(device: int, msg: bytes):
|
|
250
|
+
# This can be called when the connection has already be established.
|
|
251
|
+
if not _dev_map[device]._connection_future.done():
|
|
252
|
+
_dev_map[device]._connection_future.set_exception(Exception(msg.decode()))
|
|
253
|
+
|
|
254
|
+
@staticmethod
|
|
255
|
+
def _on_installation_progress(device: int, new_action_group: bool, action_string: bytes, action_index: int, n_actions: int):
|
|
256
|
+
_dev_map[device]._on_installation_progress_cb(new_action_group, action_string.decode(), action_index, n_actions)
|
|
257
|
+
|
|
258
|
+
@staticmethod
|
|
259
|
+
def _on_installation_done(device: int, msg: bytes):
|
|
260
|
+
if len(msg) == 0:
|
|
261
|
+
_dev_map[device]._installation_done.set_result(None)
|
|
262
|
+
else:
|
|
263
|
+
_dev_map[device]._installation_done.set_exception(Exception(msg.decode()))
|
|
264
|
+
|
|
265
|
+
async def connect_bootloader(self) -> None:
|
|
266
|
+
assert self._lib.libodrive_connect_bootloader(self._handle, _on_connected, _on_connection_failed) == 0
|
|
267
|
+
await self._connection_future
|
|
268
|
+
|
|
269
|
+
async def run_installation(self, fw: 'Firmware', erase_all: bool, on_installation_progress: Callable[[bool, str, int, int], None]) -> None:
|
|
270
|
+
self._installation_done: asyncio.Future[None] = asyncio.Future()
|
|
271
|
+
self._on_installation_progress_cb = on_installation_progress
|
|
272
|
+
assert self._lib.libodrive_start_installation(self._handle, fw._handle, erase_all, _on_installation_progress, _on_installation_done) == 0
|
|
273
|
+
await self._installation_done
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class Firmware():
|
|
277
|
+
def __init__(self, lib, handle: ctypes.c_void_p, manifest: _FwManifest):
|
|
278
|
+
self._lib = lib
|
|
279
|
+
self._handle = handle
|
|
280
|
+
self._manifest = manifest
|
|
281
|
+
|
|
282
|
+
def __enter__(self):
|
|
283
|
+
return self
|
|
284
|
+
|
|
285
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
286
|
+
self._lib.libodrive_close_firmware(self._handle)
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def fw_version(self) -> Tuple[int, int, int]:
|
|
290
|
+
return (
|
|
291
|
+
self._manifest.fw_version_major,
|
|
292
|
+
self._manifest.fw_version_minor,
|
|
293
|
+
self._manifest.fw_version_revision,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
@property
|
|
297
|
+
def hw_version(self) -> HwVersion:
|
|
298
|
+
return HwVersion.from_tuple((
|
|
299
|
+
self._manifest.hw_version.product_line,
|
|
300
|
+
self._manifest.hw_version.version,
|
|
301
|
+
self._manifest.hw_version.variant,
|
|
302
|
+
))
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def build(self) -> bytes:
|
|
306
|
+
return bytes(self._manifest.build)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
_TOnFoundDevice = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_bool, ctypes.c_void_p, ctypes.c_char_p)
|
|
310
|
+
_TOnLostDevice = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p)
|
|
311
|
+
_TOnConnected = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.POINTER(_HwVersion), ctypes.c_void_p)
|
|
312
|
+
_TOnConnectionFailed = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_char_p)
|
|
313
|
+
_TOnInstallationProgress = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_bool, ctypes.c_char_p, ctypes.c_int, ctypes.c_int)
|
|
314
|
+
_TOnInstallationDone = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_char_p)
|
|
315
|
+
|
|
316
|
+
# Must keep references to FFI-wrapped callbacks to keep them alive
|
|
317
|
+
_on_found_device = _TOnFoundDevice(Interface._on_found_device)
|
|
318
|
+
_on_lost_device = _TOnLostDevice(Interface._on_lost_device)
|
|
319
|
+
_on_connected = _TOnConnected(Device._on_connected)
|
|
320
|
+
_on_connection_failed = _TOnConnectionFailed(Device._on_connection_failed)
|
|
321
|
+
_on_installation_progress = _TOnInstallationProgress(Device._on_installation_progress)
|
|
322
|
+
_on_installation_done = _TOnInstallationDone(Device._on_installation_done)
|
|
323
|
+
|
|
324
|
+
_dev_map: Dict[int, Device] = {}
|
|
325
|
+
_intf_map: Dict[int, Interface] = {}
|
|
Binary file
|