add "odrivetool unlock" feature

This commit is contained in:
Samuel Sadok
2021-06-28 12:38:18 +02:00
parent 1f45cdaded
commit b1c10de958
3 changed files with 69 additions and 17 deletions
+39 -11
View File
@@ -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):
"""
+23 -6
View File
@@ -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)
+7
View File
@@ -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...")