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.
Files changed (49) hide show
  1. odrive/__init__.py +142 -0
  2. odrive/api_client.py +54 -0
  3. odrive/config.py +1296 -0
  4. odrive/crypto.py +369 -0
  5. odrive/data/brakeRs.json +18 -0
  6. odrive/data/drvs.json +82 -0
  7. odrive/data/encoders.json +86 -0
  8. odrive/data/motors.json +128 -0
  9. odrive/data/odrive-micro-x1.json +48 -0
  10. odrive/data/odrive-micro-x3.json +40 -0
  11. odrive/data/odrive-micro-x4.json +40 -0
  12. odrive/data/odrive-s1-x4.json +52 -0
  13. odrive/data/odrive-v4.4.json +58 -0
  14. odrive/data/schema-brakeR.json +6 -0
  15. odrive/data/schema-drv.json +6 -0
  16. odrive/data/schema-encoders.json +6 -0
  17. odrive/data/schema-motors.json +6 -0
  18. odrive/data/schema-odrive.json +5 -0
  19. odrive/data/schema.json +244 -0
  20. odrive/database.py +184 -0
  21. odrive/dfu.py +195 -0
  22. odrive/dfuse/DfuDevice.py +297 -0
  23. odrive/dfuse/__init__.py +1 -0
  24. odrive/enums.py +415 -0
  25. odrive/firmware.py +102 -0
  26. odrive/hw_version.py +67 -0
  27. odrive/legacy.py +73 -0
  28. odrive/legacy_config.py +123 -0
  29. odrive/legacy_dfu.py +442 -0
  30. odrive/lib/libodrive-windows-x64.dll +0 -0
  31. odrive/libodrive.py +325 -0
  32. odrive/pyfibre/fibre/__init__.py +4 -0
  33. odrive/pyfibre/fibre/libfibre-windows-amd64.dll +0 -0
  34. odrive/pyfibre/fibre/libfibre.py +1078 -0
  35. odrive/pyfibre/fibre/libwinpthread-1.dll +0 -0
  36. odrive/pyfibre/fibre/shell.py +162 -0
  37. odrive/pyfibre/fibre/utils.py +133 -0
  38. odrive/release_api.py +184 -0
  39. odrive/rich_text.py +134 -0
  40. odrive/shell.py +144 -0
  41. odrive/utils.py +881 -0
  42. odrive/version.py +1 -0
  43. odrive-0.6.9.dev0.data/scripts/odrive_demo.py +50 -0
  44. odrive-0.6.9.dev0.data/scripts/odrivetool +263 -0
  45. odrive-0.6.9.dev0.data/scripts/odrivetool.bat +2 -0
  46. odrive-0.6.9.dev0.dist-info/METADATA +22 -0
  47. odrive-0.6.9.dev0.dist-info/RECORD +49 -0
  48. odrive-0.6.9.dev0.dist-info/WHEEL +5 -0
  49. odrive-0.6.9.dev0.dist-info/top_level.txt +1 -0
odrive/__init__.py ADDED
@@ -0,0 +1,142 @@
1
+
2
+ import os
3
+ import sys
4
+
5
+ # We want to use the fibre package that is included with the odrive package
6
+ # in order to avoid any version mismatch issues,
7
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)), "pyfibre"))
8
+
9
+ import asyncio
10
+ import concurrent
11
+ import threading
12
+ import time
13
+ from typing import Optional
14
+
15
+ from .version import __version__
16
+ from .utils import get_serial_number_str, get_serial_number_str_sync, attach_metadata
17
+
18
+ # Backwards compatibility with Python 3.6 (default on Ubuntu 18.04)
19
+ if sys.version_info < (3, 7):
20
+ asyncio.get_running_loop = asyncio.get_event_loop
21
+
22
+ def asyncio_run(coro):
23
+ loop = asyncio.get_event_loop()
24
+ return loop.run_until_complete(coro)
25
+ asyncio.run = asyncio_run
26
+
27
+ default_usb_search_path = 'usb:idVendor=0x1209,idProduct=0x0D32,bInterfaceClass=0,bInterfaceSubClass=1,bInterfaceProtocol=0'
28
+ default_search_path = default_usb_search_path
29
+
30
+
31
+ _discovery_lock = threading.Lock()
32
+ _discovery_started = [False]
33
+ _discovery_path = [None]
34
+
35
+ connected_devices = []
36
+ connected_devices_changed = concurrent.futures.Future()
37
+
38
+ def start_discovery(path):
39
+ """
40
+ Starts device discovery in a background thread. This function returns
41
+ immediately.
42
+ If discovery was already started, this function does nothing.
43
+ """
44
+
45
+ # Start backend if it's not already started
46
+ with _discovery_lock:
47
+ if _discovery_started[0]:
48
+ if path != _discovery_path[0]:
49
+ raise Exception("Cannot change discovery path between multiple find_any() "
50
+ "calls: {} != {}. Use fibre.Domain() directly for finer "
51
+ "grained discovery control.".format(path, _discovery_path))
52
+ return # discovery already started
53
+ _discovery_started[0] = True
54
+ _discovery_path[0] = path
55
+
56
+ async def discovered_object(obj):
57
+ def lost_object(_):
58
+ connected_devices.remove(obj)
59
+
60
+ # indicate that connected_devices changed
61
+ global connected_devices_changed
62
+ signal = connected_devices_changed
63
+ connected_devices_changed = concurrent.futures.Future()
64
+ signal.set_result(None)
65
+
66
+ await attach_metadata(obj)
67
+
68
+ with _discovery_lock:
69
+ connected_devices.append(obj)
70
+ obj._on_lost.add_done_callback(lost_object)
71
+
72
+ # indicate that connected_devices changed
73
+ global connected_devices_changed
74
+ signal = connected_devices_changed
75
+ connected_devices_changed = concurrent.futures.Future()
76
+ signal.set_result(None)
77
+
78
+ def domain_thread():
79
+ _domain_termination_token = concurrent.futures.Future() # unused
80
+
81
+ import fibre
82
+ with fibre.Domain(path) as domain:
83
+ discovery = domain.run_discovery(discovered_object)
84
+ _domain_termination_token.result()
85
+ discovery.stop()
86
+
87
+ threading.Thread(target=domain_thread, daemon=True).start()
88
+
89
+
90
+ def find_any(path: str = default_search_path,
91
+ serial_number: str = None,
92
+ cancellation_token: concurrent.futures.Future = None,
93
+ timeout: float = None):
94
+ """
95
+ Blocks until the first matching ODrive object is connected and then returns
96
+ that object.
97
+
98
+ If find_any() is called multiple times, the same object may be returned
99
+ (depending on the serial_number argument).
100
+
101
+ The first call to find_any() will start a background thread that handles
102
+ the backend. This background thread will keep running until the program is
103
+ terminated.
104
+
105
+ If you want finer grained control over object discovery
106
+ consider using fibre.Domain directly.
107
+ """
108
+ assert cancellation_token is None or isinstance(cancellation_token, concurrent.futures.Future)
109
+ if cancellation_token is None:
110
+ cancellation_tokens = []
111
+ else:
112
+ cancellation_tokens = [cancellation_token]
113
+
114
+ start_discovery(path)
115
+
116
+ wait_start = time.monotonic()
117
+ while True:
118
+ signal = connected_devices_changed
119
+ with _discovery_lock:
120
+ for obj in connected_devices:
121
+ if serial_number is None or obj._serial_number == serial_number:
122
+ return obj
123
+
124
+ current_timeout = None if timeout is None else max(0, timeout - (time.monotonic() - wait_start))
125
+ wait_result = concurrent.futures.wait([signal] + cancellation_tokens, timeout=current_timeout, return_when=concurrent.futures.FIRST_COMPLETED)
126
+
127
+ if (not cancellation_token is None) and (cancellation_token in wait_result.done):
128
+ raise concurrent.futures.CancelledError()
129
+ elif (not signal in wait_result.done):
130
+ raise TimeoutError()
131
+
132
+
133
+ async def find_any_async(path: str = default_search_path, serial_number: Optional[str] = None):
134
+ start_discovery(path)
135
+
136
+ while True:
137
+ signal = connected_devices_changed
138
+ with _discovery_lock:
139
+ for obj in connected_devices:
140
+ if serial_number is None or obj._serial_number == serial_number:
141
+ return obj
142
+ await asyncio.shield(asyncio.wrap_future(signal))
odrive/api_client.py ADDED
@@ -0,0 +1,54 @@
1
+ import datetime
2
+ import json
3
+ import os
4
+ from typing import Union
5
+
6
+ import odrive.crypto as crypto
7
+ from odrive.crypto import b64encode, b64decode
8
+
9
+ API_BASE_ADDR = 'https://api.odriverobotics.com'
10
+
11
+ class ApiClient():
12
+ def __init__(self, session, api_base_addr: str = API_BASE_ADDR, key: Union[str, bytes, None] = None):
13
+ self._session = session
14
+ self._api_base_addr = api_base_addr
15
+ if key is None and 'ODRIVE_API_KEY' in os.environ:
16
+ key = os.environ['ODRIVE_API_KEY']
17
+ self._key = None if key is None else crypto.load_private_key(key if isinstance(key, bytes) else b64decode(key))
18
+
19
+ async def call(self, method: str, endpoint: str, inputs=None):
20
+ url = self._api_base_addr + endpoint
21
+ content = json.dumps(inputs or {}).encode('utf-8')
22
+ headers = {
23
+ 'content-type': 'application/json; charset=utf-8'
24
+ }
25
+
26
+ if not self._key is None:
27
+ url_bytes = endpoint.encode('utf-8')
28
+ timestamp_bytes = int(datetime.datetime.now().timestamp()).to_bytes(8, 'little', signed=False)
29
+ message = url_bytes + b':' + timestamp_bytes + b':' + content
30
+ signature = crypto.sign(self._key, message)
31
+ headers['Authorization'] = 'hmacauth ' + ':'.join([
32
+ b64encode(crypto.get_public_bytes(self._key.public_key())),
33
+ b64encode(timestamp_bytes),
34
+ b64encode(signature)
35
+ ])
36
+
37
+ async with self._session.request(method, url, headers=headers, data=content, ) as response:
38
+ if response.status != 200:
39
+ try:
40
+ ex_data = await response.json()
41
+ except:
42
+ ex_raw = await response.read()
43
+ raise Exception(f"Server failed with {response.status} ({response.reason}): {ex_raw}")
44
+ else:
45
+ tb = ''.join(ex_data['traceback'])
46
+ raise Exception(f"Server failed with {response.status} ({response.reason}): {ex_data['message']} in \n{tb}")
47
+
48
+ return await response.json()
49
+
50
+ async def download(self, url: str):
51
+ async with self._session.get(url) as response:
52
+ if response.status != 200:
53
+ raise Exception(f"Server failed with {response.status} ({response.reason})")
54
+ return await response.read()