mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-22 16:14:37 +08:00
add "odrivetool unlock" feature
This commit is contained in:
+39
-11
@@ -254,6 +254,38 @@ def find_device_in_dfu_mode(serial_number, cancellation_token):
|
||||
time.sleep(1)
|
||||
return None
|
||||
|
||||
def get_hw_version_in_dfu_mode(dfudev):
|
||||
"""
|
||||
Reads the hardware version from one-time-programmable memory.
|
||||
This is written on all ODrives sold since Summer 2018.
|
||||
"""
|
||||
otp_sector = [s for s in dfudev.sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7800][0]
|
||||
otp_data = dfudev.read_sector(otp_sector)
|
||||
if otp_data[0] == 0:
|
||||
otp_data = otp_data[16:]
|
||||
if otp_data[0] == 0xfe:
|
||||
return (otp_data[3], otp_data[4], otp_data[5])
|
||||
else:
|
||||
return None
|
||||
|
||||
def unlock_device(serial_number, cancellation_token):
|
||||
print("Looking for ODrive in DFU mode...")
|
||||
print("If the program hangs at this point, try to set the DFU switch to \"DFU\" and power cycle the ODrive.")
|
||||
|
||||
stm_device = find_device_in_dfu_mode(serial_number, cancellation_token)
|
||||
dfudev = DfuDevice(stm_device)
|
||||
|
||||
print("Unlocking device (this may take a few seconds)...")
|
||||
dfudev.unprotect()
|
||||
print("done")
|
||||
print("")
|
||||
print("Now do the following:")
|
||||
print(" 1. Put the DFU switch on the ODrive to \"DFU\"")
|
||||
print(" 2. Power-cycle the ODrive")
|
||||
print(" 3. Run \"odrivetool dfu\" (or any third party DFU tool)")
|
||||
print(" 4. Put the DFU switch on the ODrive to \"RUN\"")
|
||||
|
||||
|
||||
def update_device(device, firmware, logger, cancellation_token):
|
||||
"""
|
||||
Updates the specified device with the specified firmware.
|
||||
@@ -271,16 +303,8 @@ def update_device(device, firmware, logger, cancellation_token):
|
||||
if (logger._verbose):
|
||||
logger.debug("OTP:")
|
||||
dump_otp(dfudev)
|
||||
hw_version = get_hw_version_in_dfu_mode(dfudev) or (0, 0, 0)
|
||||
|
||||
# Read hardware version from one-time-programmable memory
|
||||
otp_sector = [s for s in dfudev.sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7800][0]
|
||||
otp_data = dfudev.read_sector(otp_sector)
|
||||
if otp_data[0] == 0:
|
||||
otp_data = otp_data[16:]
|
||||
if otp_data[0] == 0xfe:
|
||||
hw_version = (otp_data[3], otp_data[4], otp_data[5])
|
||||
else:
|
||||
hw_version = (0, 0, 0)
|
||||
else:
|
||||
found_in_dfu = False
|
||||
serial_number = "{:08X}".format(device.serial_number)
|
||||
@@ -434,8 +458,12 @@ def update_device(device, firmware, logger, cancellation_token):
|
||||
temp_config_filename = odrive.configuration.get_temp_config_filename(device)
|
||||
odrive.configuration.restore_config(device, None, logger)
|
||||
os.remove(temp_config_filename)
|
||||
|
||||
logger.success("Device firmware update successful.")
|
||||
|
||||
logger.success("Device firmware update successful.")
|
||||
else:
|
||||
logger.success("Firmware upload successful.")
|
||||
logger.info("To complete the firmware update, set the DFU switch to \"RUN\" and power cycle the board.")
|
||||
|
||||
|
||||
def launch_dfu(args, logger, cancellation_token):
|
||||
"""
|
||||
|
||||
@@ -24,6 +24,13 @@ MAX_TRANSFER_SIZE = 2048
|
||||
def address_to_4bytes(a):
|
||||
return [ a % 256, (a >> 8)%256, (a >> 16)%256, (a >> 24)%256 ]
|
||||
|
||||
def make_exception(status):
|
||||
if status[0] == 11: # errVENDOR
|
||||
suffix = " - Try running \"odrivetool unlock\" and then try \"odrivetool dfu\" again."
|
||||
else:
|
||||
suffix = ""
|
||||
return RuntimeError("An error occured. Device Status: {!r}{}".format(status, suffix))
|
||||
|
||||
class DfuDevice:
|
||||
def __init__(self, device, timeout = None):
|
||||
self.dev = device
|
||||
@@ -87,6 +94,16 @@ class DfuDevice:
|
||||
def set_address(self, ap):
|
||||
return self.dnload(0x0, [0x21] + address_to_4bytes(ap))
|
||||
|
||||
def unprotect(self):
|
||||
alt = [a for a in self.alternates() if a[0].startswith("@Device Feature/")]
|
||||
assert(len(alt) == 1)
|
||||
self.set_alternate_safe(alt[0])
|
||||
|
||||
self.dnload(0x0, [0x92])
|
||||
status = self.get_status()
|
||||
if status[1] != DfuState.DFU_DOWNLOAD_BUSY:
|
||||
raise make_exception(status)
|
||||
|
||||
def write(self, block, data):
|
||||
return self.dnload(block + 2, data)
|
||||
|
||||
@@ -171,12 +188,12 @@ class DfuDevice:
|
||||
self.set_address(addr)
|
||||
status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY)
|
||||
if status[1] != DfuState.DFU_DOWNLOAD_IDLE:
|
||||
raise RuntimeError("An error occured. Device Status: {!r}".format(status))
|
||||
raise make_exception(status)
|
||||
# take device out of DFU_DOWNLOAD_SYNC and into DFU_IDLE
|
||||
self.abort()
|
||||
status = self.wait_while_state(DfuState.DFU_DOWNLOAD_SYNC)
|
||||
if status[1] != DfuState.DFU_IDLE:
|
||||
raise RuntimeError("An error occured. Device Status: {!r}".format(status))
|
||||
raise make_exception(status)
|
||||
|
||||
|
||||
def erase_sector(self, sector):
|
||||
@@ -184,7 +201,7 @@ class DfuDevice:
|
||||
self.erase(sector['addr'])
|
||||
status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY, timeout=sector['len']/32)
|
||||
if status[1] != DfuState.DFU_DOWNLOAD_IDLE:
|
||||
raise RuntimeError("An error occured. Device Status: {!r}".format(status))
|
||||
raise make_exception(status)
|
||||
|
||||
def write_sector(self, sector, data):
|
||||
self.set_alternate_safe(sector['alt'])
|
||||
@@ -199,7 +216,7 @@ class DfuDevice:
|
||||
self.write(blocknum, block)
|
||||
status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY)
|
||||
if status[1] != DfuState.DFU_DOWNLOAD_IDLE:
|
||||
raise RuntimeError("An error occured. Device Status: {!r}".format(status))
|
||||
raise make_exception(status)
|
||||
|
||||
def read_sector(self, sector):
|
||||
"""
|
||||
@@ -226,9 +243,9 @@ class DfuDevice:
|
||||
#self.set_address(address)
|
||||
#status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY)
|
||||
#if status[1] != DfuState.DFU_DOWNLOAD_IDLE:
|
||||
# raise RuntimeError("An error occured. Device Status: {}".format(status[1]))
|
||||
# raise make_exception(status)
|
||||
|
||||
self.leave()
|
||||
status = self.wait_while_state(DfuState.DFU_MANIFEST_SYNC)
|
||||
if status[1] != DfuState.DFU_MANIFEST:
|
||||
raise RuntimeError("An error occured. Device Status: {}".format(status[1]))
|
||||
raise make_exception(status)
|
||||
|
||||
@@ -59,6 +59,8 @@ dfu_parser.add_argument('file', metavar='HEX', nargs='?',
|
||||
'If no file is provided, the script automatically downloads '
|
||||
'the latest firmware.')
|
||||
|
||||
unlock_parser = subparsers.add_parser('unlock', help="Try to remove read-out protection."
|
||||
"If no serial number is specified, the first ODrive that is found is unlocked")
|
||||
|
||||
dfu_parser = subparsers.add_parser('backup-config', help="Saves the configuration of the ODrive to a JSON file")
|
||||
dfu_parser.add_argument('file', nargs='?',
|
||||
@@ -147,6 +149,11 @@ try:
|
||||
import odrive.dfu
|
||||
odrive.dfu.launch_dfu(args, logger, app_shutdown_token)
|
||||
|
||||
elif args.command == 'unlock':
|
||||
print_version()
|
||||
import odrive.dfu
|
||||
odrive.dfu.unlock_device(args.serial_number, app_shutdown_token)
|
||||
|
||||
elif args.command == 'liveplotter':
|
||||
from odrive.utils import start_liveplotter
|
||||
print("Waiting for ODrive...")
|
||||
|
||||
Reference in New Issue
Block a user