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/enums.py ADDED
@@ -0,0 +1,415 @@
1
+
2
+ # TODO: This file is dangerous because the enums could potentially change between API versions. Should transmit as part of the JSON.
3
+ # To regenerate this file, nagivate to the top level of the ODrive repository and run:
4
+ # python Firmware/interface_generator_stub.py --definitions Firmware/odrive-interface.yaml --template tools/enums_template.j2 --output tools/odrive/enums.py
5
+
6
+ import enum
7
+
8
+ class GpioMode(enum.IntEnum):
9
+ DIGITAL = 0
10
+ DIGITAL_PULL_UP = 1
11
+ DIGITAL_PULL_DOWN = 2
12
+ ANALOG_IN = 3
13
+ UART_A = 4
14
+ UART_B = 5
15
+ UART_C = 6
16
+ CAN_A = 7
17
+ I2C_A = 8
18
+ SPI_A = 9
19
+ PWM = 10
20
+ ENC0 = 11
21
+ ENC1 = 12
22
+ ENC2 = 13
23
+ MECH_BRAKE = 14
24
+ STATUS = 15
25
+ BRAKE_RES = 16
26
+ AUTO = 17
27
+
28
+ class StreamProtocolType(enum.IntEnum):
29
+ FIBRE = 0
30
+ ASCII = 1
31
+ STDOUT = 2
32
+ ASCII_AND_STDOUT = 3
33
+ OTHER = 4
34
+
35
+ class Protocol(enum.IntFlag):
36
+ NONE = 0x00000000
37
+ SIMPLE = 0x00000001
38
+
39
+ class AxisState(enum.IntEnum):
40
+ UNDEFINED = 0
41
+ IDLE = 1
42
+ STARTUP_SEQUENCE = 2
43
+ FULL_CALIBRATION_SEQUENCE = 3
44
+ MOTOR_CALIBRATION = 4
45
+ ENCODER_INDEX_SEARCH = 6
46
+ ENCODER_OFFSET_CALIBRATION = 7
47
+ CLOSED_LOOP_CONTROL = 8
48
+ LOCKIN_SPIN = 9
49
+ ENCODER_DIR_FIND = 10
50
+ HOMING = 11
51
+ ENCODER_HALL_POLARITY_CALIBRATION = 12
52
+ ENCODER_HALL_PHASE_CALIBRATION = 13
53
+ ANTICOGGING_CALIBRATION = 14
54
+
55
+ class ControlMode(enum.IntEnum):
56
+ VOLTAGE_CONTROL = 0
57
+ TORQUE_CONTROL = 1
58
+ VELOCITY_CONTROL = 2
59
+ POSITION_CONTROL = 3
60
+
61
+ class ComponentStatus(enum.IntEnum):
62
+ NOMINAL = 0
63
+ NO_RESPONSE = 1
64
+ INVALID_RESPONSE_LENGTH = 2
65
+ PARITY_MISMATCH = 3
66
+ ILLEGAL_HALL_STATE = 4
67
+ POLARITY_NOT_CALIBRATED = 5
68
+ PHASES_NOT_CALIBRATED = 6
69
+ NUMERICAL_ERROR = 7
70
+ MISSING_INPUT = 8
71
+ RELATIVE_MODE = 9
72
+ UNCONFIGURED = 10
73
+ OVERSPEED = 11
74
+ INDEX_NOT_FOUND = 12
75
+ BAD_CONFIG = 13
76
+ NOT_ENABLED = 14
77
+ SPINOUT_DETECTED = 15
78
+
79
+ class ODriveError(enum.IntFlag):
80
+ NONE = 0x00000000
81
+ INITIALIZING = 0x00000001
82
+ SYSTEM_LEVEL = 0x00000002
83
+ TIMING_ERROR = 0x00000004
84
+ MISSING_ESTIMATE = 0x00000008
85
+ BAD_CONFIG = 0x00000010
86
+ DRV_FAULT = 0x00000020
87
+ MISSING_INPUT = 0x00000040
88
+ DC_BUS_OVER_VOLTAGE = 0x00000100
89
+ DC_BUS_UNDER_VOLTAGE = 0x00000200
90
+ DC_BUS_OVER_CURRENT = 0x00000400
91
+ DC_BUS_OVER_REGEN_CURRENT = 0x00000800
92
+ CURRENT_LIMIT_VIOLATION = 0x00001000
93
+ MOTOR_OVER_TEMP = 0x00002000
94
+ INVERTER_OVER_TEMP = 0x00004000
95
+ VELOCITY_LIMIT_VIOLATION = 0x00008000
96
+ POSITION_LIMIT_VIOLATION = 0x00010000
97
+ WATCHDOG_TIMER_EXPIRED = 0x01000000
98
+ ESTOP_REQUESTED = 0x02000000
99
+ SPINOUT_DETECTED = 0x04000000
100
+ BRAKE_RESISTOR_DISARMED = 0x08000000
101
+ THERMISTOR_DISCONNECTED = 0x10000000
102
+ CALIBRATION_ERROR = 0x40000000
103
+
104
+ class ProcedureResult(enum.IntEnum):
105
+ SUCCESS = 0
106
+ BUSY = 1
107
+ CANCELLED = 2
108
+ DISARMED = 3
109
+ NO_RESPONSE = 4
110
+ POLE_PAIR_CPR_MISMATCH = 5
111
+ PHASE_RESISTANCE_OUT_OF_RANGE = 6
112
+ PHASE_INDUCTANCE_OUT_OF_RANGE = 7
113
+ UNBALANCED_PHASES = 8
114
+ INVALID_MOTOR_TYPE = 9
115
+ ILLEGAL_HALL_STATE = 10
116
+ TIMEOUT = 11
117
+ HOMING_WITHOUT_ENDSTOP = 12
118
+ INVALID_STATE = 13
119
+ NOT_CALIBRATED = 14
120
+ NOT_CONVERGING = 15
121
+
122
+ class EncoderId(enum.IntEnum):
123
+ NONE = 0
124
+ INC_ENCODER0 = 1
125
+ INC_ENCODER1 = 2
126
+ INC_ENCODER2 = 3
127
+ SENSORLESS_ESTIMATOR = 4
128
+ SPI_ENCODER0 = 5
129
+ SPI_ENCODER1 = 6
130
+ SPI_ENCODER2 = 7
131
+ HALL_ENCODER0 = 8
132
+ HALL_ENCODER1 = 9
133
+ RS485_ENCODER0 = 10
134
+ RS485_ENCODER1 = 11
135
+ RS485_ENCODER2 = 12
136
+ ONBOARD_ENCODER0 = 13
137
+ ONBOARD_ENCODER1 = 14
138
+
139
+ class SpiEncoderMode(enum.IntEnum):
140
+ DISABLED = 0
141
+ RLS = 1
142
+ AMS = 2
143
+ CUI = 3
144
+ AEAT = 4
145
+ MA732 = 5
146
+ TLE = 6
147
+ BISSC = 7
148
+
149
+ class Rs485EncoderMode(enum.IntEnum):
150
+ DISABLED = 0
151
+ AMT21_POLLING = 1
152
+ AMT21_EVENT_DRIVEN = 2
153
+ MBS = 3
154
+ ODRIVE_OA1 = 4
155
+
156
+ class InputMode(enum.IntEnum):
157
+ INACTIVE = 0
158
+ PASSTHROUGH = 1
159
+ VEL_RAMP = 2
160
+ POS_FILTER = 3
161
+ MIX_CHANNELS = 4
162
+ TRAP_TRAJ = 5
163
+ TORQUE_RAMP = 6
164
+ MIRROR = 7
165
+ TUNING = 8
166
+
167
+ class MotorType(enum.IntEnum):
168
+ HIGH_CURRENT = 0
169
+ GIMBAL = 2
170
+ ACIM = 3
171
+
172
+ class CanError(enum.IntFlag):
173
+ NONE = 0x00000000
174
+ DUPLICATE_CAN_IDS = 0x00000001
175
+ BUS_OFF = 0x00000002
176
+ LOW_LEVEL = 0x00000004
177
+ PROTOCOL_INIT = 0x00000008
178
+
179
+
180
+ def _extend_enum(t, new_vals):
181
+ all = {
182
+ e.name: e.value for e in t
183
+ }
184
+ all.update(new_vals)
185
+ if issubclass(t, enum.IntFlag):
186
+ return enum.IntFlag(t.__name__, all)
187
+ else:
188
+ return enum.IntEnum(t.__name__, all)
189
+
190
+ ### Legacy enums (not autogenerated) ###
191
+
192
+ # Renamed during 0.6.7 => 0.6.8
193
+
194
+ EncoderId = _extend_enum(EncoderId, {
195
+ 'AMT21_ENCODER0': 10,
196
+ 'AMT21_ENCODER1': 11,
197
+ 'AMT21_ENCODER2': 12,
198
+ })
199
+
200
+ # Removed during 0.6.0 => 0.6.1
201
+
202
+ class LegacyODriveError(enum.IntFlag):
203
+ NONE = 0x00000000
204
+ CONTROL_ITERATION_MISSED = 0x00000001
205
+ DC_BUS_UNDER_VOLTAGE = 0x00000002
206
+ DC_BUS_OVER_VOLTAGE = 0x00000004
207
+ DC_BUS_OVER_REGEN_CURRENT = 0x00000008
208
+ DC_BUS_OVER_CURRENT = 0x00000010
209
+ BRAKE_DEADTIME_VIOLATION = 0x00000020
210
+ BRAKE_DUTY_CYCLE_NAN = 0x00000040
211
+ INVALID_BRAKE_RESISTANCE = 0x00000080
212
+
213
+ class AxisError(enum.IntFlag):
214
+ NONE = 0x00000000
215
+ INVALID_STATE = 0x00000001
216
+ WATCHDOG_TIMER_EXPIRED = 0x00000800
217
+ MIN_ENDSTOP_PRESSED = 0x00001000
218
+ MAX_ENDSTOP_PRESSED = 0x00002000
219
+ ESTOP_REQUESTED = 0x00004000
220
+ HOMING_WITHOUT_ENDSTOP = 0x00020000
221
+ OVER_TEMP = 0x00040000
222
+ UNKNOWN_POSITION = 0x00080000
223
+
224
+ class MotorError(enum.IntFlag):
225
+ NONE = 0x00000000
226
+ DRV_FAULT = 0x00000008
227
+ CONTROL_DEADLINE_MISSED = 0x00000010
228
+ MODULATION_MAGNITUDE = 0x00000080
229
+ CURRENT_SENSE_SATURATION = 0x00000400
230
+ CURRENT_LIMIT_VIOLATION = 0x00001000
231
+ MODULATION_IS_NAN = 0x00010000
232
+ MOTOR_THERMISTOR_OVER_TEMP = 0x00020000
233
+ FET_THERMISTOR_OVER_TEMP = 0x00040000
234
+ TIMER_UPDATE_MISSED = 0x00080000
235
+ CURRENT_MEASUREMENT_UNAVAILABLE = 0x00100000
236
+ CONTROLLER_FAILED = 0x00200000
237
+ I_BUS_OUT_OF_RANGE = 0x00400000
238
+ BRAKE_RESISTOR_DISARMED = 0x00800000
239
+ SYSTEM_LEVEL = 0x01000000
240
+ BAD_TIMING = 0x02000000
241
+ UNKNOWN_PHASE_ESTIMATE = 0x04000000
242
+ UNKNOWN_PHASE_VEL = 0x08000000
243
+ UNKNOWN_TORQUE = 0x10000000
244
+ UNKNOWN_CURRENT_COMMAND = 0x20000000
245
+ UNKNOWN_CURRENT_MEASUREMENT = 0x40000000
246
+ UNKNOWN_VBUS_VOLTAGE = 0x80000000
247
+ UNKNOWN_VOLTAGE_COMMAND = 0x100000000
248
+ UNKNOWN_GAINS = 0x200000000
249
+ CONTROLLER_INITIALIZING = 0x400000000
250
+
251
+ class SensorlessEstimatorError(enum.IntFlag):
252
+ NONE = 0x00000000
253
+ UNSTABLE_GAIN = 0x00000001
254
+ UNKNOWN_CURRENT_MEASUREMENT = 0x00000002
255
+
256
+ # Removed during 0.5.4 => 0.6.0:
257
+
258
+ class EncoderMode(enum.IntEnum):
259
+ INCREMENTAL = 0
260
+ HALL = 1
261
+ SINCOS = 2
262
+ SPI_ABS_CUI = 256
263
+ SPI_ABS_AMS = 257
264
+ SPI_ABS_AEAT = 258
265
+ SPI_ABS_RLS = 259
266
+ SPI_ABS_MA732 = 260
267
+
268
+ MotorError = _extend_enum(MotorError, {
269
+ 'PHASE_RESISTANCE_OUT_OF_RANGE': 0x00000001,
270
+ 'PHASE_INDUCTANCE_OUT_OF_RANGE': 0x00000002,
271
+ 'UNBALANCED_PHASES': 0x800000000
272
+ })
273
+
274
+ class ControllerError(enum.IntFlag):
275
+ NONE = 0x00000000
276
+ OVERSPEED = 0x00000001
277
+ INVALID_INPUT_MODE = 0x00000002
278
+ UNSTABLE_GAIN = 0x00000004
279
+ INVALID_MIRROR_AXIS = 0x00000008
280
+ INVALID_LOAD_ENCODER = 0x00000010
281
+ INVALID_ESTIMATE = 0x00000020
282
+ INVALID_CIRCULAR_RANGE = 0x00000040
283
+ SPINOUT_DETECTED = 0x00000080
284
+
285
+ class EncoderError(enum.IntFlag):
286
+ NONE = 0x00000000
287
+ UNSTABLE_GAIN = 0x00000001
288
+ CPR_POLEPAIRS_MISMATCH = 0x00000002
289
+ NO_RESPONSE = 0x00000004
290
+ UNSUPPORTED_ENCODER_MODE = 0x00000008
291
+ ILLEGAL_HALL_STATE = 0x00000010
292
+ INDEX_NOT_FOUND_YET = 0x00000020
293
+ ABS_SPI_TIMEOUT = 0x00000040
294
+ ABS_SPI_COM_FAIL = 0x00000080
295
+ ABS_SPI_NOT_READY = 0x00000100
296
+ HALL_NOT_CALIBRATED_YET = 0x00000200
297
+
298
+ # Enums in 0.5.2, 0.5.3 and 0.5.4 are identical
299
+
300
+ # Removed during 0.5.1 => 0.5.2:
301
+
302
+ AxisState = _extend_enum(AxisState, {
303
+ 'SENSORLESS_CONTROL': 5
304
+ })
305
+
306
+ class ThermistorCurrentLimiterError(enum.IntFlag):
307
+ NONE = 0x00000000
308
+ OVER_TEMP = 0x00000001
309
+
310
+ AxisError = _extend_enum(AxisError, {
311
+ 'DC_BUS_UNDER_VOLTAGE': 0x00000002,
312
+ 'DC_BUS_OVER_VOLTAGE': 0x00000004,
313
+ 'CURRENT_MEASUREMENT_TIMEOUT': 0x00000008,
314
+ 'BRAKE_RESISTOR_DISARMED': 0x00000010,
315
+ 'MOTOR_DISARMED': 0x00000020,
316
+ 'MOTOR_FAILED': 0x00000040,
317
+ 'SENSORLESS_ESTIMATOR_FAILED': 0x00000080,
318
+ 'ENCODER_FAILED': 0x00000100,
319
+ 'CONTROLLER_FAILED': 0x00000200,
320
+ 'POS_CTRL_DURING_SENSORLESS': 0x00000400
321
+ })
322
+
323
+ class LockinState(enum.IntEnum):
324
+ INACTIVE = 0
325
+ RAMP = 1
326
+ ACCELERATE = 2
327
+ CONST_VEL = 3
328
+
329
+ MotorError = _extend_enum(MotorError, {
330
+ 'ADC_FAILED': 0x00000004,
331
+ 'NOT_IMPLEMENTED_MOTOR_TYPE': 0x00000020,
332
+ 'BRAKE_CURRENT_OUT_OF_RANGE': 0x00000040,
333
+ 'BRAKE_DEADTIME_VIOLATION': 0x00000100,
334
+ 'UNEXPECTED_TIMER_CALLBACK': 0x00000200,
335
+ 'BRAKE_DUTY_CYCLE_NAN': 0x00002000,
336
+ 'DC_BUS_OVER_REGEN_CURRENT': 0x00004000,
337
+ 'DC_BUS_OVER_CURRENT': 0x00008000
338
+ })
339
+
340
+ class ArmedState(enum.IntEnum):
341
+ DISARMED = 0
342
+ WAITING_FOR_TIMINGS = 1
343
+ WAITING_FOR_UPDATE = 2
344
+ ARMED = 3
345
+
346
+ class DrvFault(enum.IntFlag):
347
+ NO_FAULT = 0x00000000
348
+ FET_LOW_C_OVERCURRENT = 0x00000001
349
+ FET_HIGH_C_OVERCURRENT = 0x00000002
350
+ FET_LOW_B_OVERCURRENT = 0x00000004
351
+ FET_HIGH_B_OVERCURRENT = 0x00000008
352
+ FET_LOW_A_OVERCURRENT = 0x00000010
353
+ FET_HIGH_A_OVERCURRENT = 0x00000020
354
+ OVERTEMPERATURE_WARNING = 0x00000040
355
+ OVERTEMPERATURE_SHUTDOWN = 0x00000080
356
+ P_VDD_UNDERVOLTAGE = 0x00000100
357
+ G_VDD_UNDERVOLTAGE = 0x00000200
358
+ G_VDD_OVERVOLTAGE = 0x00000400
359
+
360
+ # Changed during 0.5.1 => 0.5.2:
361
+ # PROTOCOL_SIMPLE = 0
362
+ # changed to
363
+ # PROTOCOL_SIMPLE = 0x00000001
364
+
365
+ # MotorError.ERROR_CURRENT_UNSTABLE renamed to MotorError.CURRENT_LIMIT_VIOLATION
366
+
367
+
368
+ def _load_legacy_names():
369
+ """
370
+ Loads legacy names for all enumerators.
371
+ e.g. AxisState.IDLE => AXIS_STATE_IDLE
372
+
373
+ This was introduced in odrive package version 0.6.0.
374
+
375
+ This will be removed in future versions of the odrive package and is only
376
+ included for a transition period until we have updated the legacy
377
+ documentation.
378
+ """
379
+ import sys
380
+ module = sys.modules[__name__]
381
+
382
+ def to_macro_case(s):
383
+ return ''.join([('_' + c) if c.isupper() else (c.upper()) for c in s]).lstrip('_')
384
+
385
+ legacy_enum_names = [
386
+ 'StreamProtocolType',
387
+ 'Protocol',
388
+ 'AxisState',
389
+ 'ControlMode',
390
+ 'ComponentStatus',
391
+ 'ProcedureResult',
392
+ 'EncoderId',
393
+ 'SpiEncoderMode',
394
+ 'InputMode',
395
+ 'MotorType',
396
+ 'LegacyODriveError',
397
+ 'CanError',
398
+ 'AxisError',
399
+ 'MotorError',
400
+ 'SensorlessEstimatorError',
401
+ 'GpioMode',
402
+ 'EncoderError',
403
+ 'ControllerError',
404
+ 'ThermistorCurrentLimiterError',
405
+ 'LockinState',
406
+ 'EncoderMode',
407
+ 'ArmedState',
408
+ 'DrvFault',
409
+ ]
410
+ for var in legacy_enum_names:
411
+ t = getattr(module, var)
412
+ for item in t:
413
+ setattr(module, ('LEGACY_ODRIVE_ERROR' if var == 'LegacyODriveError' else to_macro_case(var)) + '_' + item.name, item.value)
414
+
415
+ _load_legacy_names()
odrive/firmware.py ADDED
@@ -0,0 +1,102 @@
1
+
2
+ import abc
3
+ import io
4
+ import json
5
+ import os
6
+ import tempfile
7
+ from typing import Iterator, Tuple
8
+
9
+ class FirmwareFile(abc.ABC):
10
+ @staticmethod
11
+ def from_file(file):
12
+ return _FsFirmwareFile(file)
13
+
14
+ @abc.abstractmethod
15
+ def as_buffer(self) -> bytes:
16
+ """
17
+ Returns the contents of the file as byte array.
18
+ """
19
+ pass
20
+
21
+ @abc.abstractmethod
22
+ def as_stream(self):
23
+ """
24
+ Returns a context manager that provides a stream of the file content
25
+ """
26
+ pass
27
+
28
+ @abc.abstractmethod
29
+ def as_file(self):
30
+ """
31
+ Returns a context manager that provides an object with a `name`
32
+ attribute that points to an actual file in the filesystem.
33
+ """
34
+ pass
35
+
36
+ def get_flash_sections(self) -> Iterator[Tuple[str, int, bytes]]:
37
+ """
38
+ Scans the ELF file for sections to be loaded and returns them as an
39
+ iterator of tuples (name: str, addr: int, content: bytes).
40
+ """
41
+ import elftools.elf.elffile
42
+
43
+ with self.as_stream() as stream:
44
+ elffile = elftools.elf.elffile.ELFFile(stream)
45
+ for segment in elffile.iter_segments():
46
+ if segment.header['p_type'] != 'PT_LOAD':
47
+ continue # skip no-load sections
48
+ #print(segment.header)
49
+
50
+ segment_vaddr = segment.header['p_vaddr']
51
+ segment_addr = segment.header['p_paddr']
52
+ segment_size = segment.header['p_filesz']
53
+
54
+ for section in elffile.iter_sections():
55
+ if segment.section_in_segment(section):
56
+ section_addr = section.header['sh_addr'] - segment_vaddr + segment_addr # convert virt addr to phys addr
57
+ section_size = section.header['sh_size']
58
+
59
+ # Prune the section based on the containing segment's range.
60
+ # For instance the .bss section has a non-zero size even
61
+ # though it's in a segment with p_filesz==0.
62
+ if section_addr < segment_addr:
63
+ section_size -= segment_addr - section_addr # can get <0
64
+ section_addr = segment_addr
65
+ if section_addr + section_size > segment_addr + segment_size:
66
+ section_size = segment_addr + segment_size - section_addr
67
+
68
+ if section_size <= 0:
69
+ continue # skip sections with zero bytes to load
70
+
71
+ yield section.name, section_addr, section.data()
72
+
73
+ def get_reset_address(self):
74
+ """Returns the reset address for this firmware."""
75
+ import elftools.elf.elffile
76
+
77
+ with self.as_stream() as stream:
78
+ elffile = elftools.elf.elffile.ELFFile(stream)
79
+ isr_vector_section = [
80
+ s for s in elffile.iter_sections()
81
+ if s.name == '.isr_vector'][0]
82
+ return isr_vector_section.header['sh_addr']
83
+
84
+ class _FsFirmwareFile(FirmwareFile):
85
+ def __init__(self, path: str):
86
+ self.name = path
87
+
88
+ def as_buffer(self):
89
+ with self.as_stream() as stream:
90
+ return stream.read()
91
+
92
+ def as_stream(self):
93
+ return open(self.name, 'rb')
94
+
95
+ def as_file(self):
96
+ return self
97
+
98
+ def __enter__(self):
99
+ return self
100
+
101
+ def __exit__(self, exc_type, exc_val, exc_tb):
102
+ pass
odrive/hw_version.py ADDED
@@ -0,0 +1,67 @@
1
+
2
+ import re
3
+ from typing import List, NamedTuple, Tuple
4
+
5
+ class HwVersion(NamedTuple):
6
+ """
7
+ Represents a hardware version triplet.
8
+ See also hw_version.hpp and hw_version.dart.
9
+ """
10
+ product_line: int
11
+ version: int
12
+ variant: int
13
+
14
+ @staticmethod
15
+ def from_string(arg: str):
16
+ """
17
+ Constructs a HwVersion from a string of the form "4.4.58".
18
+ """
19
+ return HwVersion(*(int(i) for i in re.match(r'^([0-9]+)\.([0-9]+).([0-9]+)$', arg).groups()))
20
+
21
+ @staticmethod
22
+ def from_tuple(arg: Tuple[int, int, int]):
23
+ """
24
+ Constructs a HwVersion from a tuple or list of the form (4, 4, 58).
25
+ """
26
+ assert len(arg) == 3
27
+ assert all([type(a) == int for a in arg])
28
+ return HwVersion(*arg)
29
+
30
+ @staticmethod
31
+ def from_json(json):
32
+ return HwVersion.from_tuple(json)
33
+
34
+ @property
35
+ def display_name(self):
36
+ """
37
+ Returns a display name such as "ODrive Pro" or "unknown device"
38
+ corresponding to this board version.
39
+ See also hw_version.hpp.
40
+ """
41
+ if self.product_line == 3:
42
+ return f"ODrive v3.{self.version}"
43
+ elif self.product_line == 4:
44
+ return {
45
+ 0: "ODrive Pro v4.0",
46
+ 1: "ODrive Pro v4.1",
47
+ 2: "ODrive Pro v4.2",
48
+ 3: "ODrive Pro v4.3",
49
+ 4: "ODrive Pro",
50
+ }.get(self.version, "unknown ODrive Pro")
51
+ elif self.product_line == 5:
52
+ return {
53
+ 0: "ODrive S1 X1",
54
+ 1: "ODrive S1 X3",
55
+ 2: "ODrive S1",
56
+ }.get(self.version, "unknown ODrive S")
57
+ elif self.product_line == 6:
58
+ return {
59
+ 0: "ODrive Micro X1",
60
+ 1: "ODrive Micro X3",
61
+ 2: "ODrive Micro X4",
62
+ }.get(self.version, "unknown ODrive Micro")
63
+ else:
64
+ return "unknown device"
65
+
66
+ def to_json(self):
67
+ return [self.product_line, self.version, self.variant]
odrive/legacy.py ADDED
@@ -0,0 +1,73 @@
1
+ """
2
+ Functions to support legacy firmware
3
+ """
4
+
5
+ from odrive.rich_text import RichText, Color, Style
6
+ import odrive.utils
7
+
8
+ def format_errors(odrv, clear=False):
9
+ """
10
+ Returns a summary of the error status of the device as RichText.
11
+ """
12
+ lines = []
13
+
14
+ STYLE_GOOD = (Color.GREEN, Color.DEFAULT, Style.BOLD)
15
+ STYLE_WARN = (Color.YELLOW, Color.DEFAULT, Style.BOLD)
16
+ STYLE_BAD = (Color.RED, Color.DEFAULT, Style.BOLD)
17
+
18
+ axes = [(name, getattr(odrv, name)) for name in dir(odrv) if name.startswith('axis')]
19
+ axes.sort()
20
+
21
+ def decode_flags(val, enum_type):
22
+ errorcodes = {v.value: f"{enum_type.__name__}.{v.name}" for v in enum_type}
23
+ if val == 0:
24
+ return [RichText("no error", *STYLE_GOOD)]
25
+ else:
26
+ return [RichText("Error(s):", *STYLE_BAD)] + [
27
+ RichText(errorcodes.get((1 << bit), 'UNKNOWN ERROR: 0x{:08X}'.format(1 << bit)), *STYLE_BAD)
28
+ for bit in range(64) if val & (1 << bit) != 0]
29
+
30
+ def decode_enum(val, enum_type):
31
+ errorcodes = {v.value: f"{enum_type.__name__}.{v.name}" for v in enum_type}
32
+ return [RichText(errorcodes.get(val, 'Unknown value: ' + str(val)), *(STYLE_GOOD if (val == 0) else STYLE_BAD))]
33
+
34
+ def decode_drv_fault(axis, val):
35
+ if val == 0:
36
+ return [RichText("none", *STYLE_GOOD)]
37
+ elif not hasattr(axis, '_metadata'):
38
+ return [RichText("metadata not loaded", *STYLE_WARN)]
39
+ else:
40
+ return [RichText(odrive.utils.decode_drv_faults(axis._metadata['drv'], val), *STYLE_BAD)]
41
+
42
+ def dump_item(indent, name, obj, path, decoder):
43
+ prefix = indent + name.strip('0123456789') + ": "
44
+ for elem in path.split('.'):
45
+ if not hasattr(obj, elem):
46
+ return [prefix + RichText("not found", *STYLE_WARN)]
47
+ obj = getattr(obj, elem)
48
+
49
+ lines = decoder(obj)
50
+ lines = [indent + name + ": " + lines[0]] + [
51
+ indent + " " + line
52
+ for line in lines[1:]
53
+ ]
54
+ return lines
55
+
56
+ lines += dump_item("", "system", odrv, 'error', lambda x: decode_flags(x, odrive.enums.LegacyODriveError))
57
+
58
+ for name, axis in axes:
59
+ lines.append(name)
60
+ lines += dump_item(" ", 'axis', axis, 'error', lambda x: decode_flags(x, odrive.enums.AxisError))
61
+ lines += dump_item(" ", 'motor', axis, 'motor.error', lambda x: decode_flags(x, odrive.enums.MotorError))
62
+ lines += dump_item(" ", 'DRV fault', axis, 'last_drv_fault', lambda x: decode_drv_fault(axis, x))
63
+ lines += dump_item(" ", 'sensorless_estimator', axis, 'sensorless_estimator.error', lambda x: decode_flags(x, odrive.enums.SensorlessEstimatorError))
64
+ lines += dump_item(" ", 'encoder', axis, 'encoder.error', lambda x: decode_flags(x, odrive.enums.EncoderError))
65
+ lines += dump_item(" ", 'controller', axis, 'controller.error', lambda x: decode_flags(x, odrive.enums.ControllerError))
66
+
67
+ if clear:
68
+ odrv.clear_errors()
69
+
70
+ if clear:
71
+ odrv.clear_errors()
72
+
73
+ return RichText('\n').join(lines)