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/crypto.py ADDED
@@ -0,0 +1,369 @@
1
+ """
2
+ Provides cryptographic methods for client/server authentication and device
3
+ provisioning and authentication.
4
+
5
+ The following algorithms and encodings are used:
6
+
7
+ Signature algorithm: NIST P-256 (aka SECP256R1)
8
+ Hashing algorithm: SHA-256
9
+
10
+ Private key: 32 bytes private value (big endian)
11
+ Public key: 32 bytes X coordinate (big endian) + 32 bytes Y coordinate (big endian)
12
+ Signature: 32 bytes R (big endian) + 32 bytes S (big endian)
13
+
14
+ These match the algorithms/encodings in ODrive hardware and must therefore not
15
+ be changed.
16
+
17
+ The following copyright applies to all core cryptographic code in this file:
18
+ ################################################################################
19
+ # Copyright (c) 2022 Anirudha Bose #
20
+ # Adapted from https://onyb.gitbook.io/secp256k1-python under the MIT license. #
21
+ ################################################################################
22
+ """
23
+
24
+ import base64
25
+ import hashlib
26
+ from random import randint
27
+ from typing import Optional, NamedTuple
28
+
29
+ class InvalidSignature(Exception):
30
+ pass
31
+
32
+ def _gcd_extended(a, b):
33
+ """Extended Euclidean algorithm"""
34
+ if a == 0:
35
+ return b, 0, 1
36
+ gcd, x, y = _gcd_extended(b % a, a)
37
+ return (gcd, y - (b//a) * x, x)
38
+
39
+ def _mod_pow(a, b, z):
40
+ # This function is only needed for Python <3.8. For 3.8+ the built-in
41
+ # function does all we need.
42
+ if b > 0:
43
+ return pow(a, b, z)
44
+ elif b == -1:
45
+ return _gcd_extended(a, z)[1]
46
+ else:
47
+ raise Exception("not implemented")
48
+
49
+ class PrimeGaloisField(NamedTuple):
50
+ prime: int
51
+
52
+ def __contains__(self, field_value: "FieldElement") -> bool:
53
+ # called whenever you do: <FieldElement> in <PrimeGaloisField>
54
+ return 0 <= field_value.value < self.prime
55
+
56
+ class FieldElement(NamedTuple):
57
+ value: int
58
+ field: PrimeGaloisField
59
+
60
+ def __repr__(self):
61
+ return "0x" + f"{self.value:x}".zfill(64)
62
+
63
+ @property
64
+ def P(self) -> int:
65
+ return self.field.prime
66
+
67
+ def __add__(self, other: "FieldElement") -> "FieldElement":
68
+ return FieldElement(
69
+ value=(self.value + other.value) % self.P,
70
+ field=self.field
71
+ )
72
+
73
+ def __sub__(self, other: "FieldElement") -> "FieldElement":
74
+ return FieldElement(
75
+ value=(self.value - other.value) % self.P,
76
+ field=self.field
77
+ )
78
+
79
+ def __rmul__(self, scalar: int) -> "FieldElement":
80
+ return FieldElement(
81
+ value=(self.value * scalar) % self.P,
82
+ field=self.field
83
+ )
84
+
85
+ def __mul__(self, other: "FieldElement") -> "FieldElement":
86
+ return FieldElement(
87
+ value=(self.value * other.value) % self.P,
88
+ field=self.field
89
+ )
90
+
91
+ def __pow__(self, exponent: int) -> "FieldElement":
92
+ return FieldElement(
93
+ value=_mod_pow(self.value, exponent, self.P),
94
+ field=self.field
95
+ )
96
+
97
+ def __truediv__(self, other: "FieldElement") -> "FieldElement":
98
+ other_inv = other ** -1
99
+ return self * other_inv
100
+
101
+ class EllipticCurve(NamedTuple):
102
+ a: FieldElement
103
+ b: FieldElement
104
+
105
+ @property
106
+ def field(self):
107
+ assert self.a.field == self.b.field
108
+ return self.a.field
109
+
110
+ def __contains__(self, point: "Point") -> bool:
111
+ x, y = point.x, point.y
112
+ return y ** 2 == x ** 3 + self.a * x + self.b
113
+
114
+ class Point():
115
+ def __init__(self, x: int, y: int, curve: EllipticCurve):
116
+ # Encapsulate int coordinates in FieldElement
117
+ self.x = None if x is None else FieldElement(x, curve.field)
118
+ self.y = None if y is None else FieldElement(y, curve.field)
119
+ self.curve = curve
120
+
121
+ # Ignore validation for I
122
+ if self.x is None and self.y is None:
123
+ return
124
+
125
+ # Verify if the point satisfies the curve equation
126
+ if self not in self.curve:
127
+ raise ValueError
128
+
129
+ def __eq__(self, other):
130
+ return isinstance(other, Point) and (self.x, self.y, self.curve) == (other.x, other.y, other.curve)
131
+
132
+ def __hash__(self):
133
+ return hash((self.x, self.y, self.curve))
134
+
135
+ def __add__(self, other):
136
+ #################################################################
137
+ # Point Addition for P₁ or P₂ = I (identity) #
138
+ # #
139
+ # Formula: #
140
+ # P + I = P #
141
+ # I + P = P #
142
+ #################################################################
143
+ if self == secp256r1_I:
144
+ return other
145
+
146
+ if other == secp256r1_I:
147
+ return self
148
+
149
+ #################################################################
150
+ # Point Addition for X₁ = X₂ (additive inverse) #
151
+ # #
152
+ # Formula: #
153
+ # P + (-P) = I #
154
+ # (-P) + P = I #
155
+ #################################################################
156
+ if self.x == other.x and self.y == (-1 * other.y):
157
+ return secp256r1_I
158
+
159
+ #################################################################
160
+ # Point Addition for X₁ ≠ X₂ (line with slope) #
161
+ # #
162
+ # Formula: #
163
+ # S = (Y₂ - Y₁) / (X₂ - X₁) #
164
+ # X₃ = S² - X₁ - X₂ #
165
+ # Y₃ = S(X₁ - X₃) - Y₁ #
166
+ #################################################################
167
+ if self.x != other.x:
168
+ x1, x2 = self.x, other.x
169
+ y1, y2 = self.y, other.y
170
+
171
+ s = (y2 - y1) / (x2 - x1)
172
+ x3 = s ** 2 - x1 - x2
173
+ y3 = s * (x1 - x3) - y1
174
+
175
+ return self.__class__(
176
+ x=x3.value,
177
+ y=y3.value,
178
+ curve=secp256r1
179
+ )
180
+
181
+ #################################################################
182
+ # Point Addition for P₁ = P₂ (vertical tangent) #
183
+ # #
184
+ # Formula: #
185
+ # S = ∞ #
186
+ # (X₃, Y₃) = I #
187
+ #################################################################
188
+ if self == other and self.y == float("inf"):
189
+ return secp256r1_I
190
+
191
+ #################################################################
192
+ # Point Addition for P₁ = P₂ (tangent with slope) #
193
+ # #
194
+ # Formula: #
195
+ # S = (3X₁² + a) / 2Y₁ .. ∂(Y²) = ∂(X² + aX + b) #
196
+ # X₃ = S² - 2X₁ #
197
+ # Y₃ = S(X₁ - X₃) - Y₁ #
198
+ #################################################################
199
+ if self == other:
200
+ x1, y1, a = self.x, self.y, self.curve.a
201
+
202
+ s = (3 * x1 ** 2 + a) / (2 * y1)
203
+ x3 = s ** 2 - 2 * x1
204
+ y3 = s * (x1 - x3) - y1
205
+
206
+ return self.__class__(
207
+ x=x3.value,
208
+ y=y3.value,
209
+ curve=secp256r1
210
+ )
211
+
212
+ assert False
213
+
214
+ def __rmul__(self, scalar: int) -> "Point":
215
+ # Naive approach:
216
+ #
217
+ # result = I
218
+ # for _ in range(scalar): # or range(scalar % N)
219
+ # result = result + self
220
+ # return result
221
+
222
+ # Optimized approach using binary expansion
223
+ current = self
224
+ result = secp256r1_I
225
+ while scalar:
226
+ if scalar & 1: # same as scalar % 2
227
+ result = result + current
228
+ current = current + current # point doubling
229
+ scalar >>= 1 # same as scalar / 2
230
+ return result
231
+
232
+ class PrivateKey(NamedTuple):
233
+ secret: int
234
+
235
+ def public_key(self):
236
+ return self.secret * secp256r1_G
237
+
238
+ PublicKey = Point
239
+
240
+ secp256r1_field = PrimeGaloisField(prime=0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff)
241
+ secp256r1 = EllipticCurve(
242
+ a=FieldElement(0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc, secp256r1_field),
243
+ b=FieldElement(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b, secp256r1_field)
244
+ )
245
+
246
+ secp256r1_G = Point(
247
+ x=0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296,
248
+ y=0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5,
249
+ curve=secp256r1
250
+ )
251
+
252
+ secp256r1_I = Point(x=None, y=None, curve=secp256r1)
253
+
254
+ # Order of the group generated by G, such that nG = I
255
+ secp256r1_N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
256
+
257
+ def gen_key() -> PrivateKey:
258
+ """
259
+ Creates a new private key. The public key can be derived from this.
260
+ """
261
+ return PrivateKey(randint(0, secp256r1_N))
262
+
263
+ def get_private_bytes(private_key: PrivateKey) -> bytes:
264
+ """
265
+ Returns the raw byte representation of `private_key`.
266
+ See file header for encoding details.
267
+ """
268
+ return private_key.secret.to_bytes(32, 'big')
269
+
270
+ def get_public_bytes(public_key: Point) -> bytes:
271
+ """
272
+ Returns the raw byte representation of `public_key`.
273
+ See file header for encoding details.
274
+ """
275
+ return public_key.x.value.to_bytes(32, 'big') + public_key.y.value.to_bytes(32, 'big')
276
+
277
+ def load_private_key(private_bytes: bytes) -> PrivateKey:
278
+ """
279
+ Loads a private key object from the raw representation in `private_bytes`.
280
+ See file header for encoding details.
281
+ """
282
+ assert len(private_bytes) == 32
283
+ private_value = int.from_bytes(private_bytes, 'big')
284
+ return PrivateKey(private_value)
285
+
286
+ def load_public_key(public_bytes: bytes) -> PublicKey:
287
+ """
288
+ Loads a public key object from the raw representation in `public_bytes`.
289
+ See file header for encoding details.
290
+ """
291
+ assert len(public_bytes) == 64
292
+ x_int = int.from_bytes(public_bytes[:32], 'big')
293
+ y_int = int.from_bytes(public_bytes[32:], 'big')
294
+ return Point(x_int, y_int, secp256r1)
295
+
296
+ def _sha256(data: bytes):
297
+ alg = hashlib.sha256()
298
+ alg.update(data)
299
+ return int.from_bytes(alg.digest(), 'big')
300
+
301
+ def sign(private_key: PrivateKey, data: bytes) -> bytes:
302
+ z = _sha256(data)
303
+
304
+ e = private_key.secret
305
+ k = randint(0, secp256r1_N)
306
+ R = k * secp256r1_G
307
+ r = R.x.value
308
+ k_inv = _mod_pow(k, -1, secp256r1_N)
309
+ s = ((z + r*e) * k_inv) % secp256r1_N
310
+
311
+ return r.to_bytes(32, 'big') + s.to_bytes(32, 'big')
312
+
313
+ def verify(public_key: Point, signature: bytes, data: bytes) -> bool:
314
+ z = _sha256(data)
315
+ r = int.from_bytes(signature[:32], 'big')
316
+ s = int.from_bytes(signature[32:], 'big')
317
+
318
+ s_inv = _mod_pow(s, -1, secp256r1_N)
319
+ u = (z * s_inv) % secp256r1_N
320
+ v = (r * s_inv) % secp256r1_N
321
+
322
+ if (u*secp256r1_G + v*public_key).x.value != r:
323
+ raise InvalidSignature()
324
+
325
+ def verify_cert(certificate: bytes, device_public_key: Point, test_mode: bool = False):
326
+ """
327
+ Verifies the device certificate cryptographically against the ODrive master
328
+ key.
329
+ Format of `certificate` documented at ../README.md#device-certificate
330
+ """
331
+ assert len(certificate) == 272, len(certificate)
332
+
333
+ message = certificate[:80] + get_public_bytes(device_public_key)
334
+ verify(load_public_key(certificate[144:208]), certificate[80:144], message)
335
+
336
+ message = b'ODrive batch key'.ljust(32, b'\0') + certificate[144:208]
337
+ verify(test_master_key if test_mode else master_key, certificate[208:272], message)
338
+
339
+ def b64encode(buf: bytes) -> str:
340
+ return base64.b64encode(buf).decode('utf-8')
341
+
342
+ def b64decode(buf: str) -> bytes:
343
+ return base64.b64decode(buf.encode('utf-8'))
344
+
345
+ def safe_b64encode(buf: bytes) -> str:
346
+ return base64.urlsafe_b64encode(buf).decode('utf-8').rstrip('=')
347
+
348
+ def safe_b64decode(buf: str) -> bytes:
349
+ return base64.urlsafe_b64decode((buf + "=" * (-len(buf) % 4)).encode('utf-8'))
350
+
351
+ master_key = load_public_key(bytes([
352
+ 0x60, 0xea, 0x3e, 0x6d, 0xc1, 0x18, 0xa2, 0x1f,
353
+ 0x3a, 0x61, 0x99, 0x0c, 0x61, 0x6e, 0xe4, 0x4a,
354
+ 0x02, 0x68, 0x80, 0xa2, 0x5c, 0x70, 0x21, 0xac,
355
+ 0x6c, 0x63, 0x0b, 0x75, 0x39, 0x9b, 0x1b, 0xe2,
356
+ 0x7c, 0xd1, 0x34, 0xc5, 0xd4, 0xf2, 0xa9, 0x1e,
357
+ 0x0b, 0x23, 0x3a, 0x18, 0xb6, 0x43, 0xd5, 0x49,
358
+ 0x7a, 0xd9, 0xe9, 0x3b, 0x8a, 0x52, 0xfe, 0x92,
359
+ 0x95, 0x06, 0xcd, 0x46, 0x18, 0xcf, 0x4c, 0x59
360
+ ]))
361
+
362
+ test_master_key_private = load_private_key(bytes([
363
+ 0xa9, 0x6a, 0x76, 0xd7, 0x54, 0x53, 0x2e, 0x2a,
364
+ 0x38, 0x7a, 0xc5, 0x54, 0x16, 0x53, 0x70, 0xf3,
365
+ 0x48, 0x84, 0x9a, 0xf1, 0x82, 0x11, 0xbf, 0xd2,
366
+ 0x1f, 0x3c, 0x05, 0xf7, 0xf3, 0xeb, 0xef, 0x08
367
+ ]))
368
+
369
+ test_master_key = test_master_key_private.public_key()
@@ -0,0 +1,18 @@
1
+ {
2
+ "50w2rj": {
3
+ "resistance": 2,
4
+ "power_rating": 50
5
+ },
6
+ "500w2rj": {
7
+ "resistance": 2,
8
+ "power_rating": 500
9
+ },
10
+ "300w2rj": {
11
+ "resistance": 2,
12
+ "power_rating": 300
13
+ },
14
+ "625w1r": {
15
+ "resistance": 1,
16
+ "power_rating": 625
17
+ }
18
+ }
odrive/data/drvs.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "drv8301": {
3
+ "faults": [
4
+ [0, "FETLC_OC"],
5
+ [1, "FETHC_OC"],
6
+ [2, "FETLB_OC"],
7
+ [3, "FETHB_OC"],
8
+ [4, "FETLA_OC"],
9
+ [5, "FETHA_OC"],
10
+ [6, "OTW"],
11
+ [7, "OTSD"],
12
+ [8, "PVDD_UV"],
13
+ [9, "GVDD_UV"],
14
+ [10, "FAULT"],
15
+ [23, "GVDD_OV"]
16
+ ],
17
+ "config": []
18
+ },
19
+ "drv8353": {
20
+ "faults": [
21
+ [10, "FAULT"],
22
+ [9, "VDS_OCP"],
23
+ [8, "GDF"],
24
+ [7, "UVLO"],
25
+ [6, "OTSD"],
26
+ [5, "VDS_HA"],
27
+ [4, "VDS_LA"],
28
+ [3, "VDS_HB"],
29
+ [2, "VDS_LB"],
30
+ [1, "VDS_HC"],
31
+ [0, "VDS_LC"],
32
+ [26, "SA_OC"],
33
+ [25, "SB_OC"],
34
+ [24, "SC_OC"],
35
+ [23, "OTW"],
36
+ [22, "GDUV"],
37
+ [21, "VGS_HA"],
38
+ [20, "VGS_LA"],
39
+ [19, "VGS_HB"],
40
+ [18, "VGS_LB"],
41
+ [17, "VGS_HC"],
42
+ [16, "VGS_LC"]
43
+ ],
44
+ "config": [
45
+ {"name": "IDRIVEN_HS", "pos": 11, "mask": 15, "codes": ["100mA", "100mA", "200mA", "300mA", "600mA", "700mA", "800mA", "900mA", "1100mA", "1200mA", "1300mA", "1400mA", "1700mA", "1800mA", "1900mA", "2000mA"]},
46
+ {"name": "IDRIVEP_HS", "pos": 15, "mask": 15, "codes": ["50mA", "50mA", "100mA", "150mA", "300mA", "350mA", "400mA", "450mA", "550mA", "600mA", "650mA", "700mA", "850mA", "900mA", "950mA", "1000mA"]},
47
+ {"name": "IDRIVEN_LS", "pos": 22, "mask": 15, "codes": ["100mA", "100mA", "200mA", "300mA", "600mA", "700mA", "800mA", "900mA", "1100mA", "1200mA", "1300mA", "1400mA", "1700mA", "1800mA", "1900mA", "2000mA"]},
48
+ {"name": "IDRIVEP_LS", "pos": 26, "mask": 15, "codes": ["50mA", "50mA", "100mA", "150mA", "300mA", "350mA", "400mA", "450mA", "550mA", "600mA", "650mA", "700mA", "850mA", "900mA", "950mA", "1000mA"]},
49
+ {"name": "VDS_LVL", "pos": 33, "mask": 15, "codes": ["0.06V", "0.07V", "0.08V", "0.09V", "0.1V", "0.2V", "0.3V", "0.4V", "0.5V", "0.6V", "0.7V", "0.8V", "0.9V", "1.0V", "1.5V", "2.0V"]},
50
+ {"name": "DEAD_TIME", "pos": 41, "mask": 3, "codes": ["50ns", "100ns", "200ns", "400ns"]},
51
+ {"name": "SEN_LVL", "pos": 44, "mask": 3, "codes": ["0.25V", "0.5V", "0.75V", "1.0V"]}
52
+ ]
53
+ },
54
+ "drv8316": {
55
+ "faults": [
56
+ [6, "BK_FLT"],
57
+ [5, "SPI_FLT"],
58
+ [4, "OCP"],
59
+ [3, "NPOR"],
60
+ [2, "OVP"],
61
+ [1, "OT"],
62
+ [0, "FAULT"],
63
+ [15, "OTW"],
64
+ [14, "OTS"],
65
+ [13, "OCP_HC"],
66
+ [12, "OCL_LC"],
67
+ [11, "OCP_HB"],
68
+ [10, "OCP_LB"],
69
+ [9, "OCP_HA"],
70
+ [8, "OCP_LA"],
71
+ [22, "OTP_ERR"],
72
+ [21, "BUCK_OCP"],
73
+ [20, "BUCK_UV"],
74
+ [19, "VCP_UV"],
75
+ [18, "SPI_PARITY"],
76
+ [17, "SPI_SCLK_FLT"],
77
+ [16, "SPI_ADDR_FLT"]
78
+ ],
79
+ "config": [
80
+ ]
81
+ }
82
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "AMT10x": {
3
+ "type": "incremental",
4
+ "cpr": 8192,
5
+ "max_rpm": 7500,
6
+ "datasheet_url": "https://www.cuidevices.com/product/resource/amt10.pdf",
7
+ "show_in_gui": true,
8
+ "image": "AMT10.jpeg",
9
+ "shop_url": "https://odriverobotics.com/shop/cui-amt-102"
10
+ },
11
+ "AMT10Ex": {
12
+ "type": "incremental",
13
+ "cpr": 20480,
14
+ "max_rpm": 3000,
15
+ "datasheet_url": "https://www.cuidevices.com/product/resource/amt10e.pdf",
16
+ "show_in_gui": true,
17
+ "image": "AMT10.jpeg",
18
+ "shop_url": "https://odriverobotics.com/shop/20480-cpr-encoder-with-odrive-cable"
19
+ },
20
+ "AMT21xA": {
21
+ "type": "rs485",
22
+ "protocol": "AMT21_POLLING",
23
+ "datasheet_url": "https://www.cuidevices.com/product/resource/amt21.pdf",
24
+ "cpr": 4096,
25
+ "max_rpm": 8000,
26
+ "show_in_gui": false
27
+ },
28
+ "AMT21xB": {
29
+ "type": "rs485",
30
+ "protocol": "AMT21_POLLING",
31
+ "datasheet_url": "https://www.cuidevices.com/product/resource/amt21.pdf",
32
+ "cpr": 16384,
33
+ "max_rpm": 4000,
34
+ "show_in_gui": true,
35
+ "image": "AMT212x-V.jpeg"
36
+ },
37
+ "AMT21xB-V-OD": {
38
+ "type": "rs485",
39
+ "protocol": "AMT21_EVENT_DRIVEN",
40
+ "datasheet_url": "https://www.cuidevices.com/product/resource/amt21.pdf",
41
+ "cpr": 16384,
42
+ "max_rpm": 6000,
43
+ "show_in_gui": true,
44
+ "image": "AMT212x-V.jpeg",
45
+ "shop_url": "https://odriverobotics.com/shop/cui-amt212b-v-od"
46
+ },
47
+ "AMT21xC": {
48
+ "type": "rs485",
49
+ "protocol": "AMT21_POLLING",
50
+ "datasheet_url": "https://www.cuidevices.com/product/resource/amt21.pdf",
51
+ "cpr": 4096,
52
+ "max_rpm": 8000,
53
+ "show_in_gui": false
54
+ },
55
+ "AMT21xD": {
56
+ "type": "rs485",
57
+ "protocol": "AMT21_POLLING",
58
+ "datasheet_url": "https://www.cuidevices.com/product/resource/amt21.pdf",
59
+ "cpr": 16384,
60
+ "max_rpm": 4000,
61
+ "show_in_gui": false
62
+ },
63
+ "JKongMotor-encoder": {
64
+ "type": "incremental",
65
+ "datasheet_url": "https://jkongmotor.en.alibaba.com/product/60286588137-800888379/JK86BLS125_48V_3000RPM_660W_BLDC_MOTOR.html",
66
+ "cpr": 10000,
67
+ "max_rpm": 3000,
68
+ "show_in_gui": false
69
+ },
70
+ "KingKong-MBS": {
71
+ "type": "rs485",
72
+ "protocol": "MBS",
73
+ "cpr": 16384,
74
+ "max_rpm": 20000,
75
+ "show_in_gui": false
76
+ },
77
+ "ODrive-OA1": {
78
+ "type": "rs485",
79
+ "protocol": "ODRIVE_OA1",
80
+ "cpr": 4096,
81
+ "max_rpm": 60000,
82
+ "show_in_gui": true,
83
+ "image": "odrive-mag-encoder.jpeg",
84
+ "shop_url": "https://odriverobotics.com/shop/odrive-rs485-magnetic-encoder"
85
+ }
86
+ }
@@ -0,0 +1,128 @@
1
+ {
2
+ "D5065-270KV": {
3
+ "phase_resistance": 0.039,
4
+ "phase_inductance": 1.57e-05,
5
+ "pole_pairs": 7,
6
+ "max_current": 65,
7
+ "kv": 270,
8
+ "moment_of_inertia": 1e-4,
9
+ "thermistor_r25": 10000,
10
+ "thermistor_beta": 3435,
11
+ "max_temp": 130,
12
+ "shop_url": "https://odriverobotics.com/shop/odrive-custom-motor-d5065",
13
+ "show_in_gui": true
14
+ },
15
+ "D6374-150KV": {
16
+ "phase_resistance": 0.039,
17
+ "phase_inductance": 2.40e-05,
18
+ "pole_pairs": 7,
19
+ "max_current": 70,
20
+ "kv": 150,
21
+ "moment_of_inertia": 1e-4,
22
+ "thermistor_r25": 10000,
23
+ "thermistor_beta": 3435,
24
+ "max_temp": 130,
25
+ "shop_url": "https://odriverobotics.com/shop/odrive-custom-motor-d6374-150kv",
26
+ "show_in_gui": true
27
+ },
28
+ "D5312s-330KV": {
29
+ "phase_resistance": 0.037,
30
+ "phase_inductance": 2.274e-05,
31
+ "pole_pairs": 7,
32
+ "max_current": 40,
33
+ "kv": 330,
34
+ "thermistor_r25": 10000,
35
+ "thermistor_beta": 3435,
36
+ "max_temp": 130,
37
+ "shop_url": "https://odriverobotics.com/shop/dual-shaft-motor-d5212s-300kv",
38
+ "show_in_gui": true
39
+ },
40
+ "M8325s-100KV": {
41
+ "phase_resistance": 0.024,
42
+ "phase_inductance": 9.97e-06,
43
+ "pole_pairs": 20,
44
+ "max_current": 50,
45
+ "kv": 100,
46
+ "thermistor_r25": 10000,
47
+ "thermistor_beta": 3435,
48
+ "max_temp": 130,
49
+ "shop_url": "https://odriverobotics.com/shop/m8325s",
50
+ "show_in_gui": true
51
+ },
52
+ "BotWheel": {
53
+ "phase_resistance": 0.8,
54
+ "phase_inductance": 1.7e-03,
55
+ "pole_pairs": 15,
56
+ "max_current": 16,
57
+ "kv": 8.7,
58
+ "thermistor_r25": 20000,
59
+ "thermistor_beta": 3950,
60
+ "max_temp": 130,
61
+ "shop_url": "https://odriverobotics.com/shop/botwheels",
62
+ "show_in_gui": true,
63
+ "builtin_inc_cpr": 3200,
64
+ "builtin_hall": true
65
+ },
66
+ "Herlea Pancake": {
67
+ "phase_resistance": 0.0463,
68
+ "phase_inductance": 3.22e-5,
69
+ "pole_pairs": 20,
70
+ "max_current": 50,
71
+ "kv": 120,
72
+ "show_in_gui": false
73
+ },
74
+ "ME1114": {
75
+ "phase_resistance": 0.033,
76
+ "phase_inductance": 1.80e-5,
77
+ "pole_pairs": 4,
78
+ "max_current": 200,
79
+ "calibration_current": 16,
80
+ "torque_constant": 0.12,
81
+ "max_vel": 83.33333333333333,
82
+ "show_in_gui": false
83
+ },
84
+ "NEMA34-JKongMotor": {
85
+ "phase_resistance": 0.0623,
86
+ "phase_inductance": 9.068e-5,
87
+ "pole_pairs": 4,
88
+ "max_current": 20,
89
+ "torque_constant": 0.13,
90
+ "thermistor_r25": 10000,
91
+ "thermistor_beta": 3435,
92
+ "max_temp": 140,
93
+ "show_in_gui": false
94
+ },
95
+ "NEMA34-Leison": {
96
+ "phase_resistance": 0.0623,
97
+ "phase_inductance": 9.068e-5,
98
+ "pole_pairs": 4,
99
+ "kv": 87,
100
+ "max_current": 20,
101
+ "thermistor_r25": 10000,
102
+ "thermistor_beta": 3435,
103
+ "max_temp": 140,
104
+ "show_in_gui": false
105
+ },
106
+ "NEMA32-Leison": {
107
+ "phase_resistance": 0.1505899578332901,
108
+ "phase_inductance": 0.00010684521112125367,
109
+ "pole_pairs": 4,
110
+ "kv": 145,
111
+ "max_current": 8,
112
+ "thermistor_r25": 10000,
113
+ "thermistor_beta": 3435,
114
+ "max_temp": 140,
115
+ "show_in_gui": false
116
+ },
117
+ "NEMA23-Leison": {
118
+ "phase_resistance": 0.147,
119
+ "phase_inductance": 107.1e-6,
120
+ "pole_pairs": 4,
121
+ "kv": 180.5,
122
+ "max_current": 10,
123
+ "thermistor_r25": 10000,
124
+ "thermistor_beta": 3435,
125
+ "max_temp": 140,
126
+ "show_in_gui": false
127
+ }
128
+ }