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/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)
|