From 691bb06b325216f61721954751c95867a372127f Mon Sep 17 00:00:00 2001 From: mdxs Date: Tue, 26 Mar 2019 23:44:43 +0100 Subject: [PATCH] Refactored the Unpacker class --- README.md | 46 ++++++++++++++------------- unpacker.py | 90 ++++++++++++++++++++++++++--------------------------- 2 files changed, 68 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 6666aed..f2cd1c2 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ I've made some substantial changes to the code from the original repo, mainly: * Add support for the new secure bootloader from NRF SDK 12 ## What does it do? -This is a Python program that uses gatttool (provided with the Linux BlueZ driver) to achieve Device Firmware Updates (DFU) to a Nordic Semiconductor nRF51/52 device via Bluetooth Low Energy (BLE). + +This is a Python program that uses `gatttool` (provided with the Linux BlueZ driver) to achieve Over The Air (OTA) Device Firmware Updates (DFU) to a Nordic Semiconductor nRF5 (either nRF51 or nRF52) device via Bluetooth Low Energy (BLE). ### Main features: @@ -15,9 +16,9 @@ This is a Python program that uses gatttool (provided with the Linux BlueZ drive * Ability to detect if the peripheral is running in application mode or bootloader, and automatically switch if needed (buttonless). * Support for both Legacy (SDK <= 11) and Secure (SDK >= 12) bootloader. -Before using this utility the nRF peripheral device needs to be programmed with a DFU bootloader (see Nordic Semiconductor documentation/examples for instructions on that). +Before using this utility the nRF5 peripheral device needs to be programmed with a DFU bootloader (see Nordic Semiconductor documentation/examples for instructions on that). -This project assumes you are developing and deploying to Linux system. Astronomer80 has repos for similar applications for [Windows](https://github.com/astronomer80/nrf52_bledfu_win) and [Mac OS X](https://github.com/astronomer80/nrf52_bledfu_mac). +This project assumes you are developing on and deploying to a Linux system. Astronomer80 has repos for similar applications for [Windows](https://github.com/astronomer80/nrf52_bledfu_win) and [Mac OS X](https://github.com/astronomer80/nrf52_bledfu_mac). ## Prerequisites @@ -27,9 +28,10 @@ This project assumes you are developing and deploying to Linux system. Astronome * Python `intelhex` module (available via pip) ## Firmware Build Requirement -* Your nRF5 peripheral firmware build method will produce a firmware file ending with either `*.hex` or `*.bin`. + +* Your nRF5 peripheral firmware build method will produce a firmware file ending with either `*.hex` or `*.bin`. * Your nRF5 firmware build method will produce an Init file ending with `.dat`. -* The Nordic naming convention is `application.bin` and `application.dat`, but this utility will accept any names. +* The typical naming convention is `application.bin` and `application.dat`, but this utility will accept other names. ## Generating init files @@ -38,10 +40,10 @@ This project assumes you are developing and deploying to Linux system. Astronome Use the `gen_dat` application (you need to compile it with `gcc gen_dat.c -o gen_dat` on first run) to generate a `.dat` file from your `.bin` file. Example: ./gen_dat application.bin application.dat - -Note: The `gen_dat` utility expects a `.bin` file input, so you'll get CRC errors during DFU using a `.dat` file generated from a `.hex` file. -An alternative is to use `nrfutil` from Nordic Semi, but I've found this method to be easier. You may need to edit the `gen_dat` source to fit your specific application. +Note: The `gen_dat` utility expects a `.bin` file input, so you'll get Cyclic Redundancy Check (CRC) errors during DFU using a `.dat` file generated from a `.hex` file. + +An alternative is to use `nrfutil` from Nordic Semiconductor, but I've found this method to be easier. You may need to edit the `gen_dat` source to fit your specific application. ### Secure bootloader @@ -51,34 +53,34 @@ Note: I've had problems with the pip version of `nrfutil`. I recommend [installi ## Usage -There are two ways to specify firmware files for this utility. Either by specifying both the `.hex` or `.bin` file with the `.dat` file, or more easily by the `.zip` file, which contains both the hex and dat files. +There are two ways to specify firmware files for this utility. Either by specifying both the `.hex` or `.bin` file with the `.dat` file, or more easily by the `.zip` file, which contains both the hex and dat files. + The new `.zip` file form is encouraged by Nordic, but the older hex/bin + dat file methods should still work. ## Usage Examples > sudo ./dfu.py -f ~/application.hex -d ~/application.dat -a CD:E3:4A:47:1C:E4 -or +or: - > sudo ./dfu.py -z ~/application.zip -a CD:E3:4A:47:1C:E4 + > sudo ./dfu.py -z ~/application.zip -a CD:E3:4A:47:1C:E4 -To figure out the address of DfuTarg do a `hcitool lescan` - +You can use the `hcitool lescan` to figure out the address of a DFU target, for example: - $ sudo hcitool -i hci0 lescan - LE Scan ... - CD:E3:4A:47:1C:E4 - CD:E3:4A:47:1C:E4 (unknown) + $ sudo hcitool -i hci0 lescan + LE Scan ... + CD:E3:4A:47:1C:E4 + CD:E3:4A:47:1C:E4 (unknown) -Example Output ------------------------- - +## Example Output + ================================ == == == DFU Server == == == ================================ - + Sending file application.bin to CD:E3:4A:47:1C:E4 bin array size: 60788 Checking DFU State... @@ -90,14 +92,14 @@ Example Output Waiting for INIT DFU notification Begin DFU Progress: |xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| 100.0% Complete (60788 of 60788 bytes) - + Upload complete in 0 minutes and 14 seconds segments sent: 3040 Waiting for DFU complete notification Waiting for Firmware Validation notification Activate and reset DFU Server done - + ## TODO: * Implement link-loss procedure for Legacy Controller. diff --git a/unpacker.py b/unpacker.py index 6035802..880a8e4 100755 --- a/unpacker.py +++ b/unpacker.py @@ -1,54 +1,52 @@ #!/usr/bin/env python import os.path -import zipfile -import tempfile import random -import string -import shutil import re +import shutil +import string +import tempfile +import zipfile -from os.path import basename class Unpacker(object): - #-------------------------------------------------------------------------- - # - #-------------------------------------------------------------------------- - def entropy(self, length): - return ''.join(random.choice(string.lowercase) for i in range (length)) - - #-------------------------------------------------------------------------- - # - #-------------------------------------------------------------------------- - def unpack_zipfile(self, file): - - if not os.path.isfile(file): - raise Exception("Error: file, not found!") - - # Create unique working direction into which the zip file is expanded - self.unzip_dir = "{0}/{1}_{2}".format(tempfile.gettempdir(), os.path.splitext(basename(file))[0], self.entropy(6)) - - datfilename = "" - binfilename = "" - - with zipfile.ZipFile(file, 'r') as zip: - files = [item.filename for item in zip.infolist()] - datfilename = [m.group(0) for f in files for m in [re.search('.*\.dat', f)] if m].pop() - binfilename = [m.group(0) for f in files for m in [re.search('.*\.bin', f)] if m].pop() - - zip.extractall(r'{0}'.format(self.unzip_dir)) - - datfile = "{0}/{1}".format(self.unzip_dir, datfilename) - binfile = "{0}/{1}".format(self.unzip_dir, binfilename) - - # print "DAT file: " + datfile - # print "BIN file: " + binfile - - return binfile, datfile - - #-------------------------------------------------------------------------- - # - #-------------------------------------------------------------------------- - def delete(self): - # delete self.unzip_dir and its contents - shutil.rmtree(self.unzip_dir) + """ + Helper class to unpack a nRF5 Device Firmware Update (DFU) + based .zip file in a temporary folder and locate the .bin + and associated .dat files inside of it. + Removes the temporary folder on desctruction of the instance. + """ + + def __init__(self): + self.unzip_dir = '' + + def unpack_zipfile(self, zip_fname): + if not os.path.isfile(zip_fname): + raise Exception("Error: file '{}' not found!".format(zip_fname)) + # create unique working directory into which to expand the zip file + rnd_str = ''.join(random.choice(string.ascii_lowercase) for i in range(6)) + self.unzip_dir = '{0}/{1}_{2}'.format( \ + tempfile.gettempdir(), \ + os.path.splitext(os.path.basename(zip_fname))[0], \ + rnd_str) + bin_fname = '' + dat_fname = '' + with zipfile.ZipFile(zip_fname, 'r') as zf: + files = [item.filename for item in zf.infolist()] + bin_fname = [m.group(0) for f in files + for m in [re.search('.*\.bin', f)] if m].pop() + dat_fname = [m.group(0) for f in files + for m in [re.search('.*\.dat', f)] if m].pop() + zf.extractall(r'{0}'.format(self.unzip_dir)) + bin_file = '{0}/{1}'.format(self.unzip_dir, bin_fname) + dat_file = '{0}/{1}'.format(self.unzip_dir, dat_fname) + return bin_file, dat_file + + def __del__(self): + if self.unzip_dir: + # delete self.unzip_dir and its contents + shutil.rmtree(self.unzip_dir) + + def delete(self): + # delete self.unzip_dir and its contents + shutil.rmtree(self.unzip_dir)