def launch_dfu(args, logger, cancellation_token): """ Waits for a device that matches args.path and args.serial_number and then upgrades the device's firmware. """ serial_number = args.serial_number find_odrive_cancellation_token = Event(cancellation_token) logger.info("Waiting for ODrive...") devices = [None, None] # Start background thread to scan for ODrives in DFU mode def find_device_in_dfu_mode_thread(): devices[0] = find_device_in_dfu_mode(serial_number, find_odrive_cancellation_token) find_odrive_cancellation_token.set() t = threading.Thread(target=find_device_in_dfu_mode_thread) t.daemon = True t.start() # Scan for ODrives not in DFU mode # We only scan on USB because DFU is only implemented over USB devices[1] = odrive.find_any("usb", serial_number, find_odrive_cancellation_token, cancellation_token) find_odrive_cancellation_token.set() device = devices[0] or devices[1] firmware = FirmwareFromFile(args.file) if args.file else None update_device(device, firmware, logger, cancellation_token)
def update_device(device, firmware, logger, cancellation_token): """ Updates the specified device with the specified firmware. The device passed to this function can either be in normal mode or in DFU mode. The firmware should be an instance of Firmware or None. If firmware is None, the newest firmware for the device is downloaded from GitHub releases. """ if isinstance(device, usb.core.Device): serial_number = device.serial_number dfudev = DfuDevice(device) if (logger._verbose): logger.debug("OTP:") dump_otp(dfudev) # 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: serial_number = device.__channel__.usb_device.serial_number dfudev = None # Read hardware version as reported from firmware hw_version_major = device.hw_version_major if hasattr(device, 'hw_version_major') else 0 hw_version_minor = device.hw_version_minor if hasattr(device, 'hw_version_minor') else 0 hw_version_variant = device.hw_version_variant if hasattr(device, 'hw_version_variant') else 0 hw_version = (hw_version_major, hw_version_minor, hw_version_variant) if hw_version < (3, 5, 0): print(" DFU mode is not supported on board version 3.4 or earlier.") print(" This is because entering DFU mode on such a device would") print(" break the brake resistor FETs under some circumstances.") print("Warning: DFU mode is not supported on ODrives earlier than v3.5 unless you perform a hardware mod.") if not odrive.utils.yes_no_prompt("Do you still want to continue?", False): raise OperationAbortedException() fw_version_major = device.fw_version_major if hasattr(device, 'fw_version_major') else 0 fw_version_minor = device.fw_version_minor if hasattr(device, 'fw_version_minor') else 0 fw_version_revision = device.fw_version_revision if hasattr(device, 'fw_version_revision') else 0 fw_version_prerelease = device.fw_version_prerelease if hasattr(device, 'fw_version_prerelease') else True fw_version = (fw_version_major, fw_version_minor, fw_version_revision, fw_version_prerelease) print("Found ODrive {} ({}) with firmware {}{}".format( serial_number, get_hw_version_string(hw_version), get_fw_version_string(fw_version), " in DFU mode" if dfudev is not None else "")) if firmware is None: if hw_version == (0, 0, 0): if dfudev is None: suggestion = 'You have to manually flash an up-to-date firmware to make automatic checks work. Run `odrivetool dfu --help` for more info.' else: suggestion = 'Run "make write_otp" to program the board version.' raise Exception('Cannot check online for new firmware because the board version is unknown. ' + suggestion) print("Checking online for newest firmware...", end='') firmware = get_newest_firmware(hw_version) if firmware is None: raise Exception("could not find any firmware release for this board version") print(" found {}".format(get_fw_version_string(firmware.fw_version))) if firmware.fw_version <= fw_version: print() if firmware.fw_version < fw_version: print("Warning: you are about to flash firmware {} which is older than the firmware on the device ({}).".format( get_fw_version_string(firmware.fw_version), get_fw_version_string(fw_version))) else: print("You are about to flash firmware {} which is the same version as the firmware on the device ({}).".format( get_fw_version_string(firmware.fw_version), get_fw_version_string(fw_version))) if not odrive.utils.yes_no_prompt("Do you want to flash this firmware anyway?", False): raise OperationAbortedException() # load hex file # TODO: Either use the elf format or pack a custom format with a manifest. # This way we can for instance verify the target board version and only # have to publish one file for every board (instead of elf AND hex files). hexfile = IntelHex(firmware.get_as_hex()) logger.debug("Contiguous segments in hex file:") for start, end in hexfile.segments(): logger.debug(" {:08X} to {:08X}".format(start, end - 1)) # Back up configuration if dfudev is None: do_backup_config = device.user_config_loaded if hasattr(device, 'user_config_loaded') else False if do_backup_config: odrive.configuration.backup_config(device, None, logger) elif not odrive.utils.yes_no_prompt("The configuration cannot be backed up because the device is already in DFU mode. The configuration may be lost after updating. Do you want to continue anyway?", True): raise OperationAbortedException() # Put the device into DFU mode if it's not already in DFU mode if dfudev is None: find_odrive_cancellation_token = Event(cancellation_token) put_into_dfu_mode(device, find_odrive_cancellation_token) stm_device = find_device_in_dfu_mode(serial_number, cancellation_token) find_odrive_cancellation_token.set() dfudev = DfuDevice(stm_device) logger.debug("Sectors on device: ") for sector in dfudev.sectors: logger.debug(" {:08X} to {:08X} ({})".format( sector['addr'], sector['addr'] + sector['len'] - 1, sector['name'])) # fill sectors with data touched_sectors = list(populate_sectors(dfudev.sectors, hexfile)) logger.debug("The following sectors will be flashed: ") for sector,_ in touched_sectors: logger.debug(" {:08X} to {:08X}".format(sector['addr'], sector['addr'] + sector['len'] - 1)) # Erase try: for i, (sector, data) in enumerate(touched_sectors): print("Erasing... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) dfudev.erase_sector(sector) print('Erasing... done \r', end='', flush=True) finally: print('', flush=True) # Flash try: for i, (sector, data) in enumerate(touched_sectors): print("Flashing... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) dfudev.write_sector(sector, data) print('Flashing... done \r', end='', flush=True) finally: print('', flush=True) # Verify try: for i, (sector, expected_data) in enumerate(touched_sectors): print("Verifying... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) observed_data = dfudev.read_sector(sector) mismatch_pos = get_first_mismatch_index(observed_data, expected_data) if not mismatch_pos is None: mismatch_pos -= mismatch_pos % 16 observed_snippet = ' '.join('{:02X}'.format(x) for x in observed_data[mismatch_pos:mismatch_pos+16]) expected_snippet = ' '.join('{:02X}'.format(x) for x in expected_data[mismatch_pos:mismatch_pos+16]) raise RuntimeError("Verification failed around address 0x{:08X}:\n".format(sector['addr'] + mismatch_pos) + " expected: " + expected_snippet + "\n" " observed: " + observed_snippet) print('Verifying... done \r', end='', flush=True) finally: print('', flush=True) # If the flash operation failed for some reason, your device is bricked now. # You can unbrick it as long as the device remains powered on. # (or always with an STLink) # So for debugging you should comment this last part out. # Jump to application dfudev.jump_to_application(0x08000000) logger.info("Waiting for the device to reappear...") device = odrive.find_any("usb", serial_number, cancellation_token, cancellation_token, timeout=30) if do_backup_config: odrive.configuration.restore_config(device, None, logger) os.remove(odrive.configuration.get_temp_config_filename(device)) logger.success("Device firmware update successful.")
axes_by_name[axis_ctx.name] = axis_ctx # Ensure mechanical couplings are valid couplings = [] if test_rig_yaml['couplings'] is None: test_rig_yaml['couplings'] = {} else: for coupling in test_rig_yaml['couplings']: c = [ axes_by_name[axis_name] for axis_name in coupling if (axis_name in axes_by_name) ] if len(c) > 1: couplings.append(c) app_shutdown_token = Event() try: for test in all_tests: if isinstance(test, ODriveTest): def odrv_test_thread(odrv_name): odrv_ctx = odrives_by_name[odrv_name] logger.notify('* running {} on {}...'.format( type(test).__name__, odrv_name)) try: test.check_preconditions( odrv_ctx, logger.indent(' {}: '.format(odrv_name))) except: raise PreconditionsNotMet() test.run_test(odrv_ctx,