Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Commit

Permalink
Merge pull request #161 from tomchy/feature/zigbee_dfu_image
Browse files Browse the repository at this point in the history
Add Zigbee DFU image support
  • Loading branch information
bihanssen authored Sep 21, 2018
2 parents 46535c0 + 1348f91 commit f8cb3c3
Show file tree
Hide file tree
Showing 12 changed files with 20,579 additions and 23 deletions.
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This application and its library offer the following features:
* Bootloader DFU settings generation and display
* Device Firmware Update procedure over Bluetooth Low Energy
* Device Firmware Update procedure over Thread
* Device Firmware Update procedure over Zigbee

## License

Expand All @@ -31,6 +32,9 @@ that you are using you will need to select a release of this tool compatible wit

* Version 0.5.2 generates legacy firmware packages compatible with **nRF SDK 11.0 and older**
* Versions 1.5.0 and later generate modern firmware packages compatible with **nRF SDK 12.0 and newer**
* Versions 4.0.0 and later generate modern firmware packages compatible with **nRF SDK 15.1 and newer**

**Note**: In order to generate firmware images, compatible with **nRF SDK 12.0 to nRF SDK 15.0**, use `--no-backup` switch during generation of DFU settings.

## Installing from PyPI

Expand All @@ -44,7 +48,7 @@ This will also retrieve and install all additional required packages.

**Note**: When installing on macOS, you may need to add ` --ignore-installed six` when running pip. See [issue #79](https://github.com/NordicSemiconductor/pc-nrfutil/issues/79).

**Note**: To use the `dfu ble` or `dfu thread` option you will need to set up your boards to be able to communicate with your computer. You can find additional information here: [Hardware setup](https://github.com/NordicSemiconductor/pc-ble-driver/blob/master/Installation.md#hardware-setup).
**Note**: To use the `dfu ble`, `dfu thread` or `dfu zigbee` option you will need to set up your boards to be able to communicate with your computer. You can find additional information here: [Hardware setup](https://github.com/NordicSemiconductor/pc-ble-driver/blob/master/Installation.md#hardware-setup).

## Downloading precompiled Windows executable

Expand Down Expand Up @@ -102,7 +106,7 @@ pyinstaller /full/path/to/nrfutil.spec

**Note**: Please refer to the [pc-ble-driver-py PyPI installation note on Windows](https://github.com/NordicSemiconductor/pc-ble-driver-py#installing-from-pypi) if you are running nrfutil on this operating system.

**Note**: To use the `dfu ble` or `dfu thread` option you will need to set up your boards to be able to communicate with your computer. You can find additional information here: [Hardware setup](https://github.com/NordicSemiconductor/pc-ble-driver/blob/master/Installation.md#hardware-setup).
**Note**: To use the `dfu ble`, `dfu thread` or `dfu zigbee` option you will need to set up your boards to be able to communicate with your computer. You can find additional information here: [Hardware setup](https://github.com/NordicSemiconductor/pc-ble-driver/blob/master/Installation.md#hardware-setup).

## Usage

Expand Down Expand Up @@ -157,7 +161,7 @@ SoftDevice | FWID (sd-req)
`s140_nrf52_6.0.0` | 0xA9
`s140_nrf52_6.1.0` | 0xAE

**Note**: The Thread stack doesn't use a SoftDevice but --sd-req option is required for compatibility reasons. You can provide any value for the option as it is ignored during DFU.
**Note**: The Thread and Zigbee stacks don't use a SoftDevice but --sd-req option is required for compatibility reasons. You can provide any value for the option as it is ignored during DFU.

Not all combinations of Bootloader, SoftDevice and Application are possible when generating a package. The table below summarizes the support for different combinations.

Expand All @@ -183,14 +187,20 @@ SD + APP | Yes | **See notes 1 and 2 below**
was added in nrfutil 3.1.0 and is required since 3.2.0 in case the package should contain SD (+ BL) + APP. Also, since version 3.2.0 the new ID is copied to `--sd-req` list so that
in case of a link loss during APP update the DFU process can be restarted. In that case the new SD would overwrite itself, so `--sd-req` must contain also the ID of the new SD.

The boolean option '--zigbee' enables the generation of Zigbee update file in addition to the zip package. The following example demonstrates the generation of such update file:
```
nrfutil pkg generate --hw-version 52 --sd-req 0 --application-version 0x01020101 --application nrf52840_xxaa.hex --key-file ../priv.pem app_dfu_package.zip --zigbee True --manufacturer-id 0xCAFE --image-type 0x1234 --comment good_image
```
**Note 3:** The generated Zigbee update file is named according to the recommendation of the Zigbee Specification ([Zigbee Cluster Library Specification 11.5 - Zigbee Document 07-5123-06](http://www.zigbee.org/~zigbeeor/wp-content/uploads/2014/10/07-5123-06-zigbee-cluster-library-specification.pdf)), so the user doesn't provide the name of the Update file.

##### display
Use this option to display the contents of a DFU package in a .zip file.
```
nrfutil pkg display package.zip
```

#### dfu
This set of commands allow you to perform an actual firmware update over a serial, BLE, or Thread connection.
This set of commands allow you to perform an actual firmware update over a serial, BLE, Thread or Zigbee connection.

**Note**: When using Homebrew Python on macOS, you may encounter an error: `Fatal Python error: PyThreadState_Get: no current thread Abort trap: 6`. See [issue #46](https://github.com/NordicSemiconductor/pc-nrfutil/issues/46#issuecomment-383930818).

Expand Down Expand Up @@ -218,7 +228,19 @@ nrfutil dfu ble -pkg app_dfu_package.zip -p COM3 -f
```
The `-f` option instructs nrfutil to actually program the board connected to COM3 with the connectivity software required to operate as a network co-processor (NCP). Use with caution as this will overwrite the contents of the IC's flash memory.

##### serial
##### Zigbee
**Note**: DFU over Zigbee OTA is experimental

Perform a full DFU procedure over Zigbee network using ZCL OTA Upgrade Cluster. This command takes several options that you can list using:
```
nrfutil dfu zigbee --help
```
Below is an example of the execution of a DFU procedure using a file generated above using a OTA Upgrade server deployed on a DK with a OTA Upgrade Server operating on the 802.15.4 channel.
```
nrfutil dfu zigbee -f CAFE-1234-good_image.zigbee -snr 683604699 -chan 20
```

##### Serial

Perform a full DFU procedure over a UART serial line. The DFU target shall be configured to use some of its digital I/O pins as UART.

Expand Down Expand Up @@ -279,6 +301,10 @@ nrfutil keys display --key pk --format code private.pem
#### settings
This set of commands allow you to generate and display Bootloader DFU settings, which must be present on the last page of available flash memory for the bootloader to function correctly.

After SDK 15.1 release, there is an additional page used in order to backup Bootloader DFU settings during value updates. The backup is stored in a flash page before the DFU settings. As a result, the generated hex file will contain a copy of DFU setting in order to keep settings and their backup consistent.

In order to generate DFU setting page without backup (compatibility mode), please use `--no-backup` switch.

##### generate
Generate a flash page of Bootloader DFU settings and store it in a file in .hex format. This command takes several options that you can list using:
```
Expand Down
151 changes: 147 additions & 4 deletions nordicsemi/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,25 @@ def display_debug_warning():
"""
click.echo("{}".format(debug_warning))

def display_settings_backup_warning():
debug_warning = """
|===============================================================|
|## ## ### ######## ## ## #### ## ## ###### |
|## ## ## ## ## ## ## ### ## ## ### ## ## ## |
|## ## ## ## ## ## ## #### ## ## #### ## ## |
|## ## ## ## ## ######## ## ## ## ## ## ## ## ## ####|
|## ## ## ######### ## ## ## #### ## ## #### ## ## |
|## ## ## ## ## ## ## ## ### ## ## ### ## ## |
| ### ### ## ## ## ## ## ## #### ## ## ###### |
|===============================================================|
|You are generating a DFU settings page with backup page |
|included. This is only required for bootloaders from nRF SDK |
|15.1 and newer. If you want to skip backup page genetation, |
|use --no-backup option. |
|===============================================================|
"""
click.echo("{}".format(debug_warning))

def int_as_text_to_int(value):
try:
if value[:2].lower() == '0x':
Expand Down Expand Up @@ -236,6 +255,19 @@ def settings():
help='Custom start address for the settings page. If not specified, '
'then the last page of the flash is used.',
type=BASED_INT_OR_NONE)
@click.option('--no-backup',
help='Do not overwrite DFU settings backup page. If not specified, '
'than the resulting .hex file will contain a copy of DFU settings, '
'that will overwrite contents of DFU settings backup page.',
type=click.BOOL,
is_flag=True,
required=False)
@click.option('--backup-address',
help='Address of the DFU settings backup page inside flash. '
'By default, the backup page address is placed one page below DFU settings. '
'The value is precalculated based on configured settings address '
'(<DFU_settings_addrsss> - 0x1000).',
type=BASED_INT_OR_NONE)

def generate(hex_file,
family,
Expand All @@ -244,7 +276,9 @@ def generate(hex_file,
application_version_string,
bootloader_version,
bl_settings_version,
start_address):
start_address,
no_backup,
backup_address):

# Initial consistency checks
if family is None:
Expand Down Expand Up @@ -275,8 +309,21 @@ def generate(hex_file,
click.echo("Error: Bootloader DFU settings version required.")
return

if (no_backup is not None) and (backup_address is not None):
click.echo("Error: Bootloader DFU settings backup page cannot be specified if backup is disabled.")
return

if no_backup is None:
no_backup = False

if no_backup == False:
display_settings_backup_warning()

if (start_address is not None) and (backup_address is None):
click.echo("WARNING: Using default offset in order to calculate bootloader settings backup page")

sett = BLDFUSettings()
sett.generate(arch=family, app_file=application, app_ver=application_version_internal, bl_ver=bootloader_version, bl_sett_ver=bl_settings_version, custom_bl_sett_addr=start_address)
sett.generate(arch=family, app_file=application, app_ver=application_version_internal, bl_ver=bootloader_version, bl_sett_ver=bl_settings_version, custom_bl_sett_addr=start_address, no_backup=no_backup, backup_address=backup_address)
sett.tohexfile(hex_file)

click.echo("\nGenerated Bootloader DFU settings .hex file and stored it in: {}".format(hex_file))
Expand Down Expand Up @@ -457,6 +504,22 @@ def pkg():
help='The private (signing) key in PEM fomat.',
required=False,
type=click.Path(exists=True, resolve_path=True, file_okay=True, dir_okay=False))
@click.option('--zigbee',
help='Create an image and distribution package for Zigbee DFU server.',
required=False,
type=click.BOOL)
@click.option('--zigbee-manufacturer-id',
help='Manufacturer ID to be used in Zigbee OTA header.',
required=False,
type=BASED_INT)
@click.option('--zigbee-image-type',
help='Image type to be used in Zigbee OTA header.',
required=False,
type=BASED_INT)
@click.option('--zigbee-comment',
help='Firmware comment to be used in Zigbee OTA header.',
required=False,
type=click.STRING)
def generate(zipfile,
debug_mode,
application,
Expand All @@ -468,7 +531,11 @@ def generate(zipfile,
sd_req,
sd_id,
softdevice,
key_file):
key_file,
zigbee,
zigbee_manufacturer_id,
zigbee_image_type,
zigbee_comment):
"""
Generate a zip package for distribution to apps that support Nordic DFU OTA.
The application, bootloader, and SoftDevice files are converted to .bin if supplied as .hex files.
Expand Down Expand Up @@ -621,6 +688,21 @@ def generate(zipfile,
if default_key:
display_sec_warning()

if zigbee_comment is None:
zigbee_comment = ''
elif any(ord(char) > 127 for char in zigbee_comment): # Check if all the characters belong to the ASCII range
click.echo('Warning: Non-ASCII characters in the comment are not allowed. Discarding comment.')
zigbee_comment = ''
elif len(zigbee_comment) > 30:
click.echo('Warning: truncating the comment to 30 bytes.')
zigbee_comment = zigbee_comment[:30]

if zigbee_manufacturer_id is None:
zigbee_manufacturer_id = 0xFFFF

if zigbee_image_type is None:
zigbee_image_type = 0xFFFF

package = Package(debug_mode,
hw_version,
application_version_internal,
Expand All @@ -630,10 +712,38 @@ def generate(zipfile,
application,
bootloader,
softdevice,
key_file)
key_file,
zigbee,
zigbee_manufacturer_id,
zigbee_image_type,
zigbee_comment)

package.generate_package(zipfile_path)

# Regenerate BLE DFU package for Zigbee DFU purposes.
if zigbee:
from shutil import copyfile
from os import remove

log_message = "Zigbee update created at {0}".format(package.zigbee_ota_file.filename)
click.echo(log_message)

binfile = package.zigbee_ota_file.filename.replace(".zigbee", ".bin")
copyfile(package.zigbee_ota_file.filename, binfile)
package = Package(debug_mode,
hw_version,
application_version_internal,
bootloader_version,
sd_req_list,
sd_id_list,
binfile,
bootloader,
softdevice,
key_file)

package.generate_package(zipfile_path)
remove(binfile)

log_message = "Zip created at {0}".format(zipfile_path)
click.echo(log_message)

Expand Down Expand Up @@ -986,5 +1096,38 @@ def thread(package, port, address, server_port, panid, channel, jlink_snr, flash
finally:
transport.close()

@dfu.command(short_help="Update the firmware on a device over a Zigbee connection.")
@click.option('-f', '--file',
help='Filename of the Zigbee OTA Upgrade file.',
type=click.Path(exists=True, resolve_path=True, file_okay=True, dir_okay=False),
required=True)
@click.option('-snr', '--jlink_snr',
help='JLink serial number of the devboard which shall serve as a OTA Server cluster',
type=click.STRING)
@click.option('-chan', '--channel',
help='802.15.4 Channel that the OTA server will use',
type=click.INT)

def zigbee(file, jlink_snr, channel):
"""
Perform a Device Firmware Update on a device that implements a Zigbee OTA Client cluster.
This requires a second nRF device, connected to this computer, which shall serve as a
OTA Server cluster.
"""
ble_driver_init('NRF52')
from nordicsemi.zigbee.ota_flasher import OTAFlasher
of = OTAFlasher(fw = file, channel = channel, snr = jlink_snr)

if of.fw_check():
click.echo("Board already flashed with connectivity firmware.")
else:
click.echo("Flashing connectivity firmware...")
of.fw_flash()
click.echo("Connectivity firmware flashed.")

of.reset()
time.sleep(3.0) # A delay to init the OTA Server flashed on the devboard and the CLI inside of it
of.setup_channel()

if __name__ == '__main__':
cli()
28 changes: 20 additions & 8 deletions nordicsemi/dfu/bl_dfu_sett.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ def __init__(self):
class BLDFUSettings(object):
""" Class to abstract a bootloader and its settings """

flash_page_51_sz = 0x400
flash_page_52_sz = 0x1000
bl_sett_51_addr = 0x0003FC00
bl_sett_52_addr = 0x0007F000
bl_sett_52_qfab_addr = 0x0003F000
bl_sett_52810_addr = 0x0002F000
bl_sett_52840_addr = 0x000FF000
flash_page_51_sz = 0x400
flash_page_52_sz = 0x1000
bl_sett_51_addr = 0x0003FC00
bl_sett_52_addr = 0x0007F000
bl_sett_52_qfab_addr = 0x0003F000
bl_sett_52810_addr = 0x0002F000
bl_sett_52840_addr = 0x000FF000
bl_sett_backup_offset = 0x1000


def __init__(self, ):
Expand Down Expand Up @@ -127,7 +128,7 @@ def set_arch(self, arch):
else:
raise RuntimeError("Unknown architecture")

def generate(self, arch, app_file, app_ver, bl_ver, bl_sett_ver, custom_bl_sett_addr):
def generate(self, arch, app_file, app_ver, bl_ver, bl_sett_ver, custom_bl_sett_addr, no_backup, backup_address):
"""
Populates the settings object based on the given parameters.
Expand All @@ -137,6 +138,8 @@ def generate(self, arch, app_file, app_ver, bl_ver, bl_sett_ver, custom_bl_sett_
:param bl_ver: Bootloader version number
:param bl_sett_ver: Bootloader settings version number
:param custom_bl_sett_addr: Custom start address for the settings page
:param no_backup: Do not generate DFU setting backup page
:param backup_address: Custom bootloader settings backup page address
:return:
"""

Expand Down Expand Up @@ -207,6 +210,15 @@ def generate(self, arch, app_file, app_ver, bl_ver, bl_sett_ver, custom_bl_sett_
# insert the data at the correct address
self.ihex.puts(self.bl_sett_addr, data)

if backup_address is None:
self.backup_address = self.bl_sett_addr - self.bl_sett_backup_offset
else:
self.backup_address = backup_address

if no_backup == False:
# Update DFU settings backup page.
self.ihex.puts(self.backup_address, data)

def probe_settings(self, base):

# Unpack CRC and version
Expand Down
Loading

0 comments on commit f8cb3c3

Please sign in to comment.