├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.md └── workflows │ ├── githubci.yml │ └── release-binary.yml ├── .gitignore ├── .travis.yml ├── README.md ├── appveyor.yml ├── license.txt ├── nordicsemi ├── __init__.py ├── __main__.py ├── bluetooth │ ├── __init__.py │ └── hci │ │ ├── __init__.py │ │ ├── codec.py │ │ ├── slip.py │ │ └── tests │ │ ├── __init__.py │ │ └── test_codec.py ├── dfu │ ├── __init__.py │ ├── crc16.py │ ├── dfu.py │ ├── dfu_transport.py │ ├── dfu_transport_ble.py │ ├── dfu_transport_serial.py │ ├── init_packet.py │ ├── intelhex │ │ ├── __init__.py │ │ ├── compat.py │ │ └── getsizeof.py │ ├── manifest.py │ ├── model.py │ ├── nrfhex.py │ ├── package.py │ ├── signing.py │ ├── tests │ │ ├── __init__.py │ │ ├── firmwares │ │ │ ├── bar.hex │ │ │ ├── bar_wanted.bin │ │ │ ├── foo.hex │ │ │ ├── foo_wanted.bin │ │ │ ├── foobar_wanted.bin │ │ │ ├── pca10028_nrf51422_xxac_blinky.bin │ │ │ ├── s130_nrf51_mini.hex │ │ │ └── s132_nrf52_mini.hex │ │ ├── key.pem │ │ ├── test_dfu_transport_serial.py │ │ ├── test_init_packet.py │ │ ├── test_manifest.py │ │ ├── test_nrfhex.py │ │ ├── test_package.py │ │ └── test_signing.py │ └── util.py ├── exceptions.py ├── utility │ ├── __init__.py │ ├── target_registry.py │ └── tests │ │ ├── __init__.py │ │ ├── test_target_registry.py │ │ └── test_targets.json └── version.py ├── requirements.txt ├── setup.py └── tests ├── bdd ├── environment.py ├── genpkg_generate_dfu_package.feature ├── genpkg_help_information.feature └── steps │ ├── common_steps.py │ ├── genpkg_generate_dfu_package_steps.py │ ├── genpkg_help_information_steps.py │ └── util.py └── resources ├── blinky.bin ├── dfu_test_app_hrm_s110.hex ├── dfu_test_app_hrm_s130.hex ├── dfu_test_bootloader_b.hex ├── dfu_test_softdevice_b.hex └── test.pem /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a problem 3 | labels: 'Bug' 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | It's okay to leave some blank if it doesn't apply to your problem. 10 | 11 | - type: dropdown 12 | attributes: 13 | label: Operating System 14 | options: 15 | - Linux 16 | - MacOS 17 | - RaspberryPi OS 18 | - Windows 7 19 | - Windows 10 20 | - Windows 11 21 | - Others 22 | validations: 23 | required: true 24 | 25 | - type: input 26 | attributes: 27 | label: IDE version 28 | placeholder: e.g Arduino 1.8.15 29 | validations: 30 | required: true 31 | 32 | - type: input 33 | attributes: 34 | label: Board 35 | placeholder: e.g Feather nRF52840 Express 36 | validations: 37 | required: true 38 | 39 | - type: input 40 | attributes: 41 | label: Version 42 | description: "Release version or github latest" 43 | validations: 44 | required: true 45 | 46 | - type: textarea 47 | attributes: 48 | label: What happened ? 49 | placeholder: A clear and concise description of what the bug is. 50 | validations: 51 | required: true 52 | 53 | - type: textarea 54 | attributes: 55 | label: How to reproduce ? 56 | placeholder: | 57 | 1. Go to '...' 58 | 2. Click on '....' 59 | 3. See error 60 | validations: 61 | required: true 62 | 63 | - type: textarea 64 | attributes: 65 | label: Debug Log 66 | placeholder: Serial output when IDE's Debug Mode Level to 1 or 2 as attached txt file. 67 | validations: 68 | required: false 69 | 70 | - type: textarea 71 | attributes: 72 | label: Screenshots 73 | description: If applicable, add screenshots to help explain your problem. 74 | validations: 75 | required: false 76 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Adafruit Support Forum 3 | url: https://forums.adafruit.com 4 | about: If you have other questions or need help, post it here. 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: Feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/githubci.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [pull_request, push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Setup Python 11 | uses: actions/setup-python@v1 12 | with: 13 | python-version: '3.x' 14 | 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Install adafruit-nrfutil 19 | run: | 20 | pip3 install -r requirements.txt 21 | python3 setup.py install 22 | 23 | - name: Run Test 24 | run: | 25 | adafruit-nrfutil version 26 | adafruit-nrfutil dfu genpkg --dev-type 0x0052 --sd-req 0x00B6 --application tests/resources/blinky.bin blinky.zip 27 | -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: Relase Binaries 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | jobs: 9 | windows: 10 | runs-on: windows-latest 11 | 12 | steps: 13 | - name: Setup Python 14 | uses: actions/setup-python@v1 15 | with: 16 | python-version: '3.8' 17 | 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | 21 | - name: Create binaries 22 | run: | 23 | pip3 install pyinstaller 24 | pip3 install -r requirements.txt 25 | cd nordicsemi 26 | pyinstaller __main__.py --onefile --clean --name adafruit-nrfutil 27 | cd dist 28 | 7z a -tzip ../../adafruit-nrfutil--${{ github.event.release.tag_name }}-win.zip adafruit-nrfutil.exe 29 | 30 | - name: Upload Release Asset 31 | uses: actions/upload-release-asset@v1 32 | env: 33 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 34 | if: ${{ github.event_name == 'release' }} 35 | with: 36 | upload_url: ${{ github.event.release.upload_url }} 37 | asset_path: adafruit-nrfutil--${{ github.event.release.tag_name }}-win.zip 38 | asset_name: adafruit-nrfutil--${{ github.event.release.tag_name }}-win.zip 39 | asset_content_type: application/zip 40 | 41 | macOS: 42 | runs-on: macos-latest 43 | 44 | steps: 45 | - name: Setup Python 46 | uses: actions/setup-python@v1 47 | with: 48 | python-version: '3.x' 49 | 50 | - name: Checkout code 51 | uses: actions/checkout@v2 52 | 53 | - name: Create binaries 54 | run: | 55 | pip3 install pyinstaller 56 | pip3 install -r requirements.txt 57 | cd nordicsemi 58 | pyinstaller __main__.py --onefile --clean --name adafruit-nrfutil 59 | cd dist 60 | 7z a -tzip ../../adafruit-nrfutil--${{ github.event.release.tag_name }}-macos.zip adafruit-nrfutil 61 | 62 | - name: Upload Release Asset 63 | uses: actions/upload-release-asset@v1 64 | env: 65 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 66 | if: ${{ github.event_name == 'release' }} 67 | with: 68 | upload_url: ${{ github.event.release.upload_url }} 69 | asset_path: adafruit-nrfutil--${{ github.event.release.tag_name }}-macos.zip 70 | asset_name: adafruit-nrfutil--${{ github.event.release.tag_name }}-macos.zip 71 | asset_content_type: application/zip 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__ 3 | _build 4 | *.orig 5 | *.pyc 6 | .env 7 | *.spec 8 | build* 9 | bundles 10 | *.DS_Store 11 | .eggs 12 | dist 13 | **/*.egg-info 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | language: python 4 | python: 5 | - '3.6' 6 | cache: 7 | pip: true 8 | 9 | deploy: 10 | - provider: pypi 11 | user: adafruit-travis 12 | on: 13 | tags: true 14 | password: 15 | secure: cH35ZC/rsv+1bCxwGjxjffdkt/PZOP3kDaMOmjhY1asiV+Cjw8Ji9JYGpXeB1AKCayWe0Z4EYnc5TQ4IMklBPt18en4cLotAx8XgKJkv4RXxk25pQ/WEMJRZnCusJpnjmQso1zFzUu03QkNmgvhZiYlEeliaU7/0N3siqKMcDeRBqpn7GGq96q90CVzLXvjhA29rDD5JjjMlwWFOU03cFt+Q2EemWcHa916I2Xaf9IKPyBRE5/xqz+o/OH4MoDpM4I1ktPON/BUzyH5VbND0o8znJSFYBxJrXcHNtIHxK67eTJRXiRVAIMAwDMtvDZkCgDADstBiCAh2Cmp+CE3kNQCmbZ7BC7t6WnbPddcer+dfk2ZrMLBl3HHsgU4t06tOwcXJfXGkEeJjrVmieH9UeoCDHjchGkwtHuquBe7lEP1m6OBsm5pjWdg03xQ+NHWqY1TGs0xI9z/68qHw7Nl6vUKG9JjEq8GYIbp3O/aJJo8owIlOGztXtXT7HuxpXpd8N/uQ3Od+aMvLNx/lBQenmHLtdkrzzP6YHWqkV1fHDLDf0GCS5FZXUkTpL4rIv+GBccRVjheQVySUYstwMnrbKPHXPoe8D9QdwPvwCHzzu/Ai++1r1KB969gHkn9vTLWVJ4NiFAu234QxvfpPl/CxVCgfm/vYZSIZHDHR1l+/DmA= 16 | 17 | install: 18 | - pip install -r requirements.txt 19 | 20 | script: 21 | - python3 setup.py install 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # adafruit-nrfutil 2 | 3 | `adafruit-nrfutil` is a Python package that includes the `adafruit-nrfutil` command line utility 4 | and the `nordicsemi` library. 5 | 6 | This package is derived from the Nordic Semiconductor ASA package 7 | [pc-nrfutil](https://github.com/NordicSemiconductor/pc-nrfutil), version 0.5.3. 8 | The code has been converted from Python 2 to Python 3. 9 | 10 | The executable `nrfutil` has been renamed to `adafruit-nrfutil` to distinguish it from the 11 | original executable. 12 | 13 | This tool can be used with the [Adafruit nRF52 Feather](https://www.adafruit.com/product/3406) 14 | to flash firmware images onto the device using the simple serial port. 15 | 16 | This library is written for Python 3.5+. It is no longer Python 2 compatible! 17 | 18 | # Installation 19 | 20 | ## Prerequisites 21 | 22 | - Python3 23 | - pip3 24 | 25 | Run the following commands to make `adafruit-nrfutil` available from the command line 26 | or to development platforms like the Arduino IDE or CircuitPython: 27 | 28 | ## Installing from PyPI 29 | 30 | This is recommended method, to install latest version 31 | 32 | ``` 33 | $ pip3 install --user adafruit-nrfutil 34 | ``` 35 | 36 | ## Installing from Source 37 | 38 | Use this method if you have issue installing with PyPi or want to modify the tool. First clone this repo and go into its folder. 39 | 40 | ``` 41 | $ git clone https://github.com/adafruit/Adafruit_nRF52_nrfutil.git 42 | $ cd Adafruit_nRF52_nrfutil 43 | ``` 44 | 45 | Note: following commands use `python3`, however if you are on Windows, you may need to change it to `python` since windows installation of python 3.x still uses the name python.exe 46 | 47 | To install in user space in your home directory: 48 | 49 | ``` 50 | $ pip3 install -r requirements.txt 51 | $ python3 setup.py install 52 | ``` 53 | 54 | If you get permission errors when running `pip3 install`, your `pip3` is older 55 | or is set to try to install in the system directories. In that case use the 56 | `--user` flag: 57 | 58 | ``` 59 | $ pip3 install -r --user requirements.txt 60 | $ python3 setup.py install 61 | ``` 62 | 63 | If you want to install in system directories (generally not recommended): 64 | ``` 65 | $ sudo pip3 install -r requirements.txt 66 | $ sudo python3 setup.py install 67 | ``` 68 | 69 | ### Create self-contained binary 70 | 71 | To generate a self-contained executable binary of the utility (Windows and MacOS), run these commands: 72 | 73 | ``` 74 | pip3 install pyinstaller 75 | cd Adafruit_nRF52_nrfutil 76 | pip3 install -r requirements.txt 77 | cd Adafruit_nRF52_nrfutil\nordicsemi 78 | pyinstaller __main__.py --onefile --clean --name adafruit-nrfutil 79 | ``` 80 | You will find the .exe in `Adafruit_nRF52_nrfutil\nordicsemi\dist\adafruit-nrfutil` ( with `.exe` if you are on windows). 81 | Copy or move it elsewhere for your convenience, such as directory in your %PATH%. 82 | 83 | # Usage 84 | 85 | To get info on the usage of adafruit-nrfutil: 86 | 87 | ``` 88 | adafruit-nrfutil --help 89 | ``` 90 | 91 | To convert an nRF52 .hex file into a DFU pkg file that the serial bootloader 92 | can make use of: 93 | 94 | ``` 95 | adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application firmware.hex dfu-package.zip 96 | ``` 97 | 98 | To flash a DFU pkg file over serial: 99 | 100 | ``` 101 | adafruit-nrfutil dfu serial --package dfu-package.zip -p /dev/tty.SLAB_USBtoUART -b 115200 102 | ``` 103 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | PYTHON: 'C:\Python36' 3 | 4 | platform: x86 5 | 6 | configuration: Release 7 | 8 | init: 9 | # Set the relevant Python and pip location to the path 10 | - cmd: set PATH=%PYTHON%;%PYTHON%\scripts;%PATH% 11 | - cmd: ECHO Path - %PATH% 12 | 13 | install: 14 | - cmd: pip install -r requirements.txt 15 | - cmd: pip install pyinstaller 16 | - cmd: cd nordicsemi 17 | - cmd: pyinstaller __main__.py --onefile --clean --name adafruit-nrfutil 18 | 19 | # Not a project with an msbuild file, build done at install. 20 | build: None 21 | 22 | artifacts: 23 | - path: 'nordicsemi\dist\adafruit-nrfutil.exe' 24 | 25 | deploy: 26 | provider: GitHub 27 | force_update: true 28 | tag: $(APPVEYOR_REPO_TAG_NAME) 29 | auth_token: 30 | secure: tcFElKgzcRzCgpqiJ78WTYNO1FTx4Z8aHV47OxlZIfVkulDr/o81jQZ2aLjirvO5 31 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Nordic Semiconductor 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /nordicsemi/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """Package marker file.""" 30 | -------------------------------------------------------------------------------- /nordicsemi/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """nrfutil command line tool.""" 30 | import logging 31 | import os 32 | import click 33 | import sys,traceback 34 | 35 | from nordicsemi.dfu.dfu import Dfu 36 | from nordicsemi.dfu.dfu_transport import DfuEvent 37 | from nordicsemi.dfu.dfu_transport_serial import DfuTransportSerial 38 | from nordicsemi.dfu.package import Package 39 | from nordicsemi import version as nrfutil_version 40 | from nordicsemi.dfu.signing import Signing 41 | from nordicsemi.dfu.util import query_func 42 | 43 | 44 | class nRFException(Exception): 45 | pass 46 | 47 | 48 | def int_as_text_to_int(value): 49 | value = str(value) 50 | try: 51 | if value[:2].lower() == '0x': 52 | return int(value[2:], 16) 53 | elif value[:1] == '0': 54 | return int(value, 8) 55 | return int(value, 10) 56 | except ValueError: 57 | raise nRFException('%s is not a valid integer' % value) 58 | 59 | 60 | class BasedIntOrNoneParamType(click.ParamType): 61 | name = 'Int or None' 62 | 63 | def convert(self, value, param, ctx): 64 | value = str(value) 65 | try: 66 | if value.lower() == 'none': 67 | return 'none' 68 | return int_as_text_to_int(value) 69 | except nRFException: 70 | self.fail('%s is not a valid integer' % value, param, ctx) 71 | 72 | BASED_INT_OR_NONE = BasedIntOrNoneParamType() 73 | 74 | 75 | class TextOrNoneParamType(click.ParamType): 76 | name = 'Text or None' 77 | 78 | def convert(self, value, param, ctx): 79 | return value 80 | 81 | TEXT_OR_NONE = TextOrNoneParamType() 82 | 83 | 84 | @click.group() 85 | @click.option('--verbose', 86 | help='Show verbose information', 87 | is_flag=True) 88 | def cli(verbose): 89 | if verbose: 90 | logging.basicConfig(format='%(message)s', level=logging.INFO) 91 | else: 92 | logging.basicConfig(format='%(message)s') 93 | 94 | 95 | @cli.command() 96 | def version(): 97 | """Displays nrf utility version.""" 98 | click.echo("adafruit-nrfutil version {}".format(nrfutil_version.NRFUTIL_VERSION)) 99 | 100 | 101 | @cli.command(short_help='Generate keys for signing or generate public keys') 102 | @click.argument('key_file', required=True) 103 | @click.option('--gen-key', 104 | help='generate signing key and store at given path (pem-file)', 105 | type=click.BOOL, 106 | is_flag=True) 107 | @click.option('--show-vk', 108 | help='Show the verification keys for DFU Signing (hex|code|pem)', 109 | type=click.STRING) 110 | def keys(key_file, 111 | gen_key, 112 | show_vk): 113 | """ 114 | This set of commands support creation of signing key (private) and showing the verification key (public) 115 | from a previously loaded signing key. Signing key is stored in PEM format 116 | """ 117 | if not gen_key and show_vk is None: 118 | raise nRFException("Use either gen-key or show-vk.") 119 | 120 | signer = Signing() 121 | 122 | if gen_key: 123 | if os.path.exists(key_file): 124 | if not query_func("File found at %s. Do you want to overwrite the file?" % key_file): 125 | click.echo('Key generation aborted') 126 | return 127 | 128 | signer.gen_key(key_file) 129 | click.echo("Generated key at: %s" % key_file) 130 | 131 | elif show_vk: 132 | if not os.path.isfile(key_file): 133 | raise nRFException("No key file to load at: %s" % key_file) 134 | 135 | signer.load_key(key_file) 136 | click.echo(signer.get_vk(show_vk)) 137 | 138 | 139 | @cli.group() 140 | def dfu(): 141 | """ 142 | This set of commands support Nordic DFU OTA package generation for distribution to 143 | applications and serial DFU. 144 | """ 145 | pass 146 | 147 | 148 | @dfu.command(short_help='Generate a package for distribution to Apps supporting Nordic DFU OTA') 149 | @click.argument('zipfile', 150 | required=True, 151 | type=click.Path()) 152 | @click.option('--application', 153 | help='The application firmware file', 154 | type=click.STRING) 155 | @click.option('--application-version', 156 | help='Application version, default: 0xFFFFFFFF', 157 | type=BASED_INT_OR_NONE, 158 | default=str(Package.DEFAULT_APP_VERSION)) 159 | @click.option('--bootloader', 160 | help='The bootloader firmware file', 161 | type=click.STRING) 162 | @click.option('--dev-revision', 163 | help='Device revision, default: 0xFFFF', 164 | type=BASED_INT_OR_NONE, 165 | default=str(Package.DEFAULT_DEV_REV)) 166 | @click.option('--dev-type', 167 | help='Device type, default: 0xFFFF', 168 | type=BASED_INT_OR_NONE, 169 | default=str(Package.DEFAULT_DEV_TYPE)) 170 | @click.option('--dfu-ver', 171 | help='DFU packet version to use, default: 0.5', 172 | type=click.FLOAT, 173 | default=Package.DEFAULT_DFU_VER) 174 | @click.option('--sd-req', 175 | help='SoftDevice requirement. A list of SoftDevice versions (1 or more)' 176 | 'of which one is required to be present on the target device.' 177 | 'Example: --sd-req 0x4F,0x5A. Default: 0xFFFE.', 178 | type=TEXT_OR_NONE, 179 | default=str(Package.DEFAULT_SD_REQ[0])) 180 | @click.option('--softdevice', 181 | help='The SoftDevice firmware file', 182 | type=click.STRING) 183 | @click.option('--key-file', 184 | help='Signing key (pem fomat)', 185 | type=click.Path(exists=True, resolve_path=True, file_okay=True, dir_okay=False)) 186 | def genpkg(zipfile, 187 | application, 188 | application_version, 189 | bootloader, 190 | dev_revision, 191 | dev_type, 192 | dfu_ver, 193 | sd_req, 194 | softdevice, 195 | key_file): 196 | """ 197 | Generate a zipfile package for distribution to Apps supporting Nordic DFU OTA. 198 | The application, bootloader and softdevice files are converted to .bin if it is a .hex file. 199 | For more information on the generated init packet see: 200 | http://developer.nordicsemi.com/nRF51_SDK/doc/7.2.0/s110/html/a00065.html 201 | """ 202 | zipfile_path = zipfile 203 | 204 | if application_version == 'none': 205 | application_version = None 206 | 207 | if dev_revision == 'none': 208 | dev_revision = None 209 | 210 | if dev_type == 'none': 211 | dev_type = None 212 | 213 | sd_req_list = None 214 | 215 | if sd_req.lower() == 'none': 216 | sd_req_list = [] 217 | elif sd_req: 218 | try: 219 | # This will parse any string starting with 0x as base 16. 220 | sd_req_list = sd_req.split(',') 221 | sd_req_list = list(map(int_as_text_to_int, sd_req_list)) 222 | except ValueError: 223 | raise nRFException("Could not parse value for --sd-req. " 224 | "Hex values should be prefixed with 0x.") 225 | 226 | if key_file and dfu_ver < 0.8: 227 | click.echo("Key file was given, setting DFU version to 0.8") 228 | 229 | package = Package(dev_type, 230 | dev_revision, 231 | application_version, 232 | sd_req_list, 233 | application, 234 | bootloader, 235 | softdevice, 236 | dfu_ver, 237 | key_file) 238 | 239 | package.generate_package(zipfile_path) 240 | 241 | log_message = "Zip created at {0}".format(zipfile_path) 242 | try: 243 | click.echo(log_message) 244 | except OSError: 245 | print(log_message) 246 | 247 | def update_progress(progress=0, done=False, log_message=""): 248 | del done, log_message # Unused parameters 249 | if progress == 0: 250 | return 251 | 252 | if progress % 40 == 0: 253 | click.echo('#', nl=True) 254 | else: 255 | click.echo('#', nl=False) 256 | 257 | 258 | @dfu.command(short_help="Program a device with bootloader that support serial DFU") 259 | @click.option('-pkg', '--package', 260 | help='DFU package filename', 261 | type=click.Path(exists=True, resolve_path=True, file_okay=True, dir_okay=False), 262 | required=True) 263 | @click.option('-p', '--port', 264 | help='Serial port COM Port to which the device is connected', 265 | type=click.STRING, 266 | required=True) 267 | @click.option('-b', '--baudrate', 268 | help='Desired baud rate 38400/96000/115200/230400/250000/460800/921600/1000000 (default: 38400). ' 269 | 'Note: Physical serial ports (e.g. COM1) typically do not support baud rates > 115200', 270 | type=click.INT, 271 | default=DfuTransportSerial.DEFAULT_BAUD_RATE) 272 | @click.option('-fc', '--flowcontrol', 273 | help='Enable flow control, default: disabled', 274 | type=click.BOOL, 275 | is_flag=True) 276 | @click.option('-sb', '--singlebank', 277 | help='Single bank bootloader to skip firmware activating delay, default: Dual bank', 278 | type=click.BOOL, 279 | default=False, 280 | is_flag=True) 281 | @click.option('-t', '--touch', 282 | help='Open port with specified baud then close it, before uploading', 283 | type=click.INT, 284 | default=0) 285 | 286 | def serial(package, port, baudrate, flowcontrol, singlebank, touch): 287 | """Program a device with bootloader that support serial DFU""" 288 | serial_backend = DfuTransportSerial(port, baudrate, flowcontrol, singlebank, touch) 289 | serial_backend.register_events_callback(DfuEvent.PROGRESS_EVENT, update_progress) 290 | dfu = Dfu(package, dfu_transport=serial_backend) 291 | 292 | click.echo("Upgrading target on {1} with DFU package {0}. Flow control is {2}, {3} bank, Touch {4}" 293 | .format(package, port, "enabled" if flowcontrol else "disabled", "Single" if singlebank else "Dual", touch if touch > 0 else "disabled")) 294 | 295 | try: 296 | dfu.dfu_send_images() 297 | 298 | except Exception as e: 299 | click.echo("") 300 | click.echo("Failed to upgrade target. Error is: {0}".format(e)) 301 | traceback.print_exc(file=sys.stdout) 302 | click.echo("") 303 | click.echo("Possible causes:") 304 | click.echo("- Selected Bootloader version does not match the one on Bluefruit device.") 305 | click.echo(" Please upgrade the Bootloader or select correct version in Tools->Bootloader.") 306 | click.echo("- Baud rate must be 115200, Flow control must be off.") 307 | click.echo("- Target is not in DFU mode. Ground DFU pin and RESET and release both to enter DFU mode.") 308 | 309 | return False 310 | 311 | click.echo("Device programmed.") 312 | 313 | return True 314 | 315 | 316 | if __name__ == '__main__': 317 | cli() 318 | -------------------------------------------------------------------------------- /nordicsemi/bluetooth/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """Package marker file.""" 30 | -------------------------------------------------------------------------------- /nordicsemi/bluetooth/hci/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """Package marker file.""" 30 | -------------------------------------------------------------------------------- /nordicsemi/bluetooth/hci/codec.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | UART_HEADER_OCTET_COUNT = 4 30 | 31 | 32 | class ThreeWireUartPacket(object): 33 | """ 34 | This class encapsulate a three wire uart packet according to Bluetooth specification 35 | version 4.0 [Vol 4] part D. 36 | """ 37 | def __init__(self): 38 | self.ack = None # Acknowledgement number 39 | self.seq = None # Sequence number 40 | self.di = None # Data integrity present 41 | self.rp = None # Reliable packet 42 | self.type = None # Packet type 43 | self.length = None # Payload Length 44 | self.checksum = None # Header checksum 45 | self.payload = None # Payload 46 | 47 | @staticmethod 48 | def decode(packet): 49 | """ 50 | Decodes a packet from a str encoded array 51 | 52 | :param packet_bytes: A str encoded array 53 | :return: TheeWireUartPacket 54 | """ 55 | 56 | decoded_packet = ThreeWireUartPacket() 57 | 58 | packet_bytes = bytearray(packet) 59 | 60 | decoded_packet.ack = (packet_bytes[0] & int('38', 16)) >> 3 61 | decoded_packet.seq = (packet_bytes[0] & int('07', 16)) 62 | decoded_packet.di = (packet_bytes[0] & int('40', 16)) >> 6 63 | decoded_packet.rp = (packet_bytes[0] & int('80', 16)) >> 7 64 | decoded_packet.type = (packet_bytes[1] & int('0F', 16)) 65 | decoded_packet.length = ((packet_bytes[1] & int('F0', 16)) >> 4) + (packet_bytes[2] * 16) 66 | 67 | checksum = packet_bytes[0] 68 | checksum = checksum + packet_bytes[1] 69 | checksum = checksum + packet_bytes[2] 70 | checksum &= int('FF', 16) 71 | decoded_packet.checksum = (~checksum + 1) & int('FF', 16) 72 | 73 | if decoded_packet.length > 0: 74 | decoded_packet.payload = packet_bytes[UART_HEADER_OCTET_COUNT:-1] 75 | 76 | return decoded_packet 77 | -------------------------------------------------------------------------------- /nordicsemi/bluetooth/hci/slip.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import logging 30 | 31 | logger = logging.getLogger(__name__) 32 | 33 | 34 | class Slip(object): 35 | def __init__(self): 36 | self.SLIP_END = b'\xc0' 37 | self.SLIP_ESC = b'\xdb' 38 | self.SLIP_ESC_END = b'\xdc' 39 | self.SLIP_ESC_ESC = b'\xdd' 40 | 41 | self.started = False 42 | self.escaped = False 43 | self.stream = b'' 44 | self.packet = b'' 45 | 46 | def append(self, data): 47 | """ 48 | Append a new 49 | :param data: Append a new block of data to do decoding on when calling decode. 50 | The developer may add more than one SLIP packet before calling decode. 51 | :return: 52 | """ 53 | self.stream += data 54 | 55 | def decode(self): 56 | """ 57 | Decodes a package according to http://en.wikipedia.org/wiki/Serial_Line_Internet_Protocol 58 | :return Slip: A list of decoded slip packets 59 | """ 60 | packet_list = list() 61 | 62 | for char in self.stream: 63 | char = bytes([char]) 64 | if char == self.SLIP_END: 65 | if self.started: 66 | if len(self.packet) > 0: 67 | self.started = False 68 | packet_list.append(self.packet) 69 | self.packet = b'' 70 | else: 71 | self.started = True 72 | self.packet = b'' 73 | elif char == self.SLIP_ESC: 74 | self.escaped = True 75 | elif char == self.SLIP_ESC_END: 76 | if self.escaped: 77 | self.packet += self.SLIP_END 78 | self.escaped = False 79 | else: 80 | self.packet += char 81 | elif char == self.SLIP_ESC_ESC: 82 | if self.escaped: 83 | self.packet += self.SLIP_ESC 84 | self.escaped = False 85 | else: 86 | self.packet += char 87 | else: 88 | if self.escaped: 89 | logging.error("Error in SLIP packet, ignoring error.") 90 | self.packet = b'' 91 | self.escaped = False 92 | else: 93 | self.packet += char 94 | 95 | self.stream = '' 96 | 97 | return packet_list 98 | 99 | def encode(self, packet): 100 | """ 101 | Encode a packet according to SLIP. 102 | :param packet: A str array that represents the package 103 | :return: str array with an encoded SLIP packet 104 | """ 105 | encoded = self.SLIP_END 106 | 107 | for char in packet: 108 | if char == self.SLIP_END: 109 | encoded += self.SLIP_ESC + self.SLIP_ESC_END 110 | elif char == self.SLIP_ESC: 111 | encoded += self.SLIP_ESC + self.SLIP_ESC_ESC 112 | else: 113 | encoded += char 114 | encoded += self.SLIP_END 115 | 116 | return encoded 117 | -------------------------------------------------------------------------------- /nordicsemi/bluetooth/hci/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """Package marker file.""" 30 | -------------------------------------------------------------------------------- /nordicsemi/bluetooth/hci/tests/test_codec.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import json 30 | import unittest 31 | from nordicsemi.bluetooth.hci.slip import Slip 32 | from nordicsemi.bluetooth.hci import codec 33 | 34 | 35 | class TestInitPacket(unittest.TestCase): 36 | def setUp(self): 37 | pass 38 | 39 | def test_decode_packet(self): 40 | # TODO: extend this test, this tests only a small portion of the slip/hci decoding 41 | # These are packets read from Device Monitoring Studio 42 | # during communication between serializer application and firmware 43 | read_packets = [ 44 | b'\xC0\x10\x00\x00\xF0\xC0\xC0\xD1\x6E\x00\xC1\x01\x86\x00\x00\x00\x00\x17\x63\xC0', 45 | b'\xC0\xD2\xDE\x02\x4E\x02\x1B\x00\xFF\xFF\x01\x17\xFE\xB4\x9A\x9D\xE1\xB0\xF8\x02' 46 | b'\x01\x06\x11\x07\x1B\xC5\xD5\xA5\x02\x00\xA9\xB7\xE2\x11\xA4\xC6\x00\xFE\xE7\x74' 47 | b'\x09\x09\x49\x44\x54\x57\x32\x31\x38\x48\x5A\xBB\xC0', 48 | b'\xC0\xD3\xEE\x00\x3F\x02\x1B\x00\xFF\xFF\x01\x17\xFE\xB4\x9A\x9D\xE1\xAF\x01\xF1\x62\xC0', 49 | b'\xC0\xD4\xDE\x02\x4C\x02\x1B\x00\xFF\xFF\x01\x17\xFE\xB4\x9A\x9D\xE1\xB1\xF8\x02\x01\x06' 50 | b'\x11\x07\x1B\xC5\xD5\xA5\x02\x00\xA9\xB7\xE2\x11\xA4\xC6\x00\xFE\xE7\x74\x09\x09\x49\x44\x54\x57\x32\x31\x38\x48\x6E\xC8\xC0' 51 | ] 52 | 53 | slip = Slip() 54 | output = list() 55 | 56 | for uart_packet in read_packets: 57 | slip.append(uart_packet) 58 | 59 | packets = slip.decode() 60 | 61 | for packet in packets: 62 | output.append(codec.ThreeWireUartPacket.decode(packet)) 63 | 64 | self.assertEqual(len(output), 5) 65 | 66 | packet_index = 0 67 | self.assertEqual(output[packet_index].seq, 0) 68 | 69 | packet_index += 1 70 | self.assertEqual(output[packet_index].seq, 1) 71 | 72 | packet_index += 1 73 | self.assertEqual(output[packet_index].seq, 2) 74 | 75 | packet_index += 1 76 | self.assertEqual(output[packet_index].seq, 3) 77 | 78 | packet_index += 1 79 | self.assertEqual(output[packet_index].seq, 4) 80 | -------------------------------------------------------------------------------- /nordicsemi/dfu/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """Package marker file.""" 30 | -------------------------------------------------------------------------------- /nordicsemi/dfu/crc16.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | def calc_crc16(binary_data, crc=0xffff): 30 | """ 31 | Calculates CRC16 on binary_data 32 | 33 | :param int crc: CRC value to start calculation with 34 | :param bytearray binary_data: Array with data to run CRC16 calculation on 35 | :return int: Calculated CRC value of binary_data 36 | """ 37 | if not isinstance(binary_data, bytes): 38 | raise RuntimeError("calc_crc16 requires bytes input") 39 | for b in binary_data: 40 | crc = (crc >> 8 & 0x00FF) | (crc << 8 & 0xFF00) 41 | crc ^= b 42 | crc ^= (crc & 0x00FF) >> 4 43 | crc ^= (crc << 8) << 4 44 | crc ^= ((crc & 0x00FF) << 4) << 1 45 | return crc & 0xFFFF 46 | -------------------------------------------------------------------------------- /nordicsemi/dfu/dfu.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | # Python standard library 30 | import os 31 | import tempfile 32 | import shutil 33 | import logging 34 | from time import time, sleep 35 | from datetime import datetime, timedelta 36 | 37 | # Nordic libraries 38 | from nordicsemi.exceptions import * 39 | from nordicsemi.dfu.package import Package 40 | from nordicsemi.dfu.dfu_transport import DfuEvent 41 | from nordicsemi.dfu.model import HexType 42 | from nordicsemi.dfu.manifest import SoftdeviceBootloaderFirmware 43 | 44 | logger = logging.getLogger(__name__) 45 | 46 | 47 | class Dfu(object): 48 | """ Class to handle upload of a new hex image to the device. """ 49 | 50 | def __init__(self, zip_file_path, dfu_transport): 51 | """ 52 | Initializes the dfu upgrade, unpacks zip and registers callbacks. 53 | 54 | @param zip_file_path: Path to the zip file with the firmware to upgrade 55 | @type zip_file_path: str 56 | @param dfu_transport: Transport backend to use to upgrade 57 | @type dfu_transport: nordicsemi.dfu.dfu_transport.DfuTransport 58 | @return 59 | """ 60 | self.zip_file_path = zip_file_path 61 | self.ready_to_send = True 62 | self.response_opcode_received = None 63 | 64 | self.temp_dir = tempfile.mkdtemp(prefix="nrf_dfu_") 65 | self.unpacked_zip_path = os.path.join(self.temp_dir, 'unpacked_zip') 66 | self.manifest = Package.unpack_package(self.zip_file_path, self.unpacked_zip_path) 67 | 68 | if dfu_transport: 69 | self.dfu_transport = dfu_transport 70 | 71 | self.dfu_transport.register_events_callback(DfuEvent.TIMEOUT_EVENT, self.timeout_event_handler) 72 | self.dfu_transport.register_events_callback(DfuEvent.ERROR_EVENT, self.error_event_handler) 73 | 74 | def __del__(self): 75 | """ 76 | Destructor removes the temporary directory for the unpacked zip 77 | :return: 78 | """ 79 | shutil.rmtree(self.temp_dir) 80 | 81 | def error_event_handler(self, log_message=""): 82 | """ 83 | Event handler for errors, closes the transport backend. 84 | :param str log_message: The log message for the error. 85 | :return: 86 | """ 87 | if self.dfu_transport.is_open(): 88 | self.dfu_transport.close() 89 | 90 | logger.error(log_message) 91 | 92 | def timeout_event_handler(self, log_message): 93 | """ 94 | Event handler for timeouts, closes the transport backend. 95 | :param log_message: The log message for the timeout. 96 | :return: 97 | """ 98 | if self.dfu_transport.is_open(): 99 | self.dfu_transport.close() 100 | 101 | logger.error(log_message) 102 | 103 | @staticmethod 104 | def _read_file(file_path): 105 | """ 106 | Reads a file and returns the content as a string. 107 | 108 | :param str file_path: The path to the file to read. 109 | :return str: Content of the file. 110 | """ 111 | buffer_size = 4096 112 | 113 | file_content = bytes() 114 | 115 | with open(file_path, 'rb') as binary_file: 116 | while True: 117 | data = binary_file.read(buffer_size) 118 | 119 | if data: 120 | file_content += data 121 | else: 122 | break 123 | 124 | return file_content 125 | 126 | def _wait_while_opening_transport(self): 127 | timeout = 10 128 | start_time = datetime.now() 129 | 130 | while not self.dfu_transport.is_open(): 131 | timed_out = datetime.now() - start_time > timedelta(0, timeout) 132 | 133 | if timed_out: 134 | log_message = "Failed to open transport backend" 135 | raise NordicSemiException(log_message) 136 | 137 | sleep(0.1) 138 | 139 | 140 | def _dfu_send_image(self, program_mode, firmware_manifest): 141 | """ 142 | Does DFU for one image. Reads the firmware image and init file. 143 | Opens the transport backend, calls setup, send and finalize and closes the backend again. 144 | @param program_mode: What type of firmware the DFU is 145 | @type program_mode: nordicsemi.dfu.model.HexType 146 | @param firmware_manifest: The manifest for the firmware image 147 | @type firmware_manifest: nordicsemi.dfu.manifest.Firmware 148 | @return: 149 | """ 150 | 151 | if firmware_manifest is None: 152 | raise MissingArgumentException("firmware_manifest must be provided.") 153 | 154 | if self.dfu_transport.is_open(): 155 | raise IllegalStateException("Transport is already open.") 156 | 157 | self.dfu_transport.open() 158 | self._wait_while_opening_transport() 159 | 160 | softdevice_size = 0 161 | bootloader_size = 0 162 | application_size = 0 163 | 164 | bin_file_path = os.path.join(self.unpacked_zip_path, firmware_manifest.bin_file) 165 | firmware = self._read_file(bin_file_path) 166 | 167 | dat_file_path = os.path.join(self.unpacked_zip_path, firmware_manifest.dat_file) 168 | init_packet = self._read_file(dat_file_path) 169 | 170 | if program_mode == HexType.SD_BL: 171 | if not isinstance(firmware_manifest, SoftdeviceBootloaderFirmware): 172 | raise NordicSemiException("Wrong type of manifest") 173 | softdevice_size = firmware_manifest.sd_size 174 | bootloader_size = firmware_manifest.bl_size 175 | firmware_size = len(firmware) 176 | if softdevice_size + bootloader_size != firmware_size: 177 | raise NordicSemiException( 178 | "Size of bootloader ({} bytes) and softdevice ({} bytes)" 179 | " is not equal to firmware provided ({} bytes)".format( 180 | bootloader_size, softdevice_size, firmware_size)) 181 | 182 | elif program_mode == HexType.SOFTDEVICE: 183 | softdevice_size = len(firmware) 184 | 185 | elif program_mode == HexType.BOOTLOADER: 186 | bootloader_size = len(firmware) 187 | 188 | elif program_mode == HexType.APPLICATION: 189 | application_size = len(firmware) 190 | 191 | start_time = time() 192 | logger.info("Starting DFU upgrade of type %s, SoftDevice size: %s, bootloader size: %s, application size: %s", 193 | program_mode, 194 | softdevice_size, 195 | bootloader_size, 196 | application_size) 197 | 198 | logger.info("Sending DFU start packet") 199 | self.dfu_transport.send_start_dfu(program_mode, softdevice_size, bootloader_size, 200 | application_size) 201 | 202 | logger.info("Sending DFU init packet") 203 | self.dfu_transport.send_init_packet(init_packet) 204 | 205 | logger.info("Sending firmware file") 206 | self.dfu_transport.send_firmware(firmware) 207 | 208 | self.dfu_transport.send_validate_firmware() 209 | 210 | self.dfu_transport.send_activate_firmware() 211 | 212 | self.dfu_transport.close() 213 | 214 | # logger.info("Wait after activating %s second", self.get_activate_wait_time()) 215 | sleep(self.dfu_transport.get_activate_wait_time()) 216 | 217 | end_time = time() 218 | logger.info("\nDFU upgrade took {0}s".format(end_time - start_time)) 219 | 220 | def dfu_send_images(self): 221 | """ 222 | Does DFU for all firmware images in the stored manifest. 223 | :return: 224 | """ 225 | if self.manifest.softdevice_bootloader: 226 | self._dfu_send_image(HexType.SD_BL, self.manifest.softdevice_bootloader) 227 | 228 | if self.manifest.softdevice: 229 | self._dfu_send_image(HexType.SOFTDEVICE, self.manifest.softdevice) 230 | 231 | if self.manifest.bootloader: 232 | self._dfu_send_image(HexType.BOOTLOADER, self.manifest.bootloader) 233 | 234 | if self.manifest.application: 235 | self._dfu_send_image(HexType.APPLICATION, self.manifest.application) 236 | -------------------------------------------------------------------------------- /nordicsemi/dfu/dfu_transport.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | # Python specific imports 30 | import abc 31 | import logging 32 | 33 | # Nordic Semiconductor imports 34 | from nordicsemi.dfu.util import int32_to_bytes 35 | 36 | logger = logging.getLogger(__name__) 37 | 38 | 39 | class DfuEvent: 40 | PROGRESS_EVENT = 1 41 | TIMEOUT_EVENT = 2 42 | ERROR_EVENT = 3 43 | 44 | 45 | class DfuTransport(object, metaclass=abc.ABCMeta): 46 | """ 47 | This class as an abstract base class inherited from when implementing transports. 48 | 49 | The class is generic in nature, the underlying implementation may have missing semantic 50 | than this class describes. But the intent is that the implementer shall follow the semantic as 51 | best as she can. 52 | """ 53 | 54 | @staticmethod 55 | def create_image_size_packet(softdevice_size=0, bootloader_size=0, app_size=0): 56 | """ 57 | Creates an image size packet necessary for sending start dfu. 58 | 59 | @param softdevice_size: Size of SoftDevice firmware 60 | @type softdevice_size: int 61 | @param bootloader_size: Size of bootloader firmware 62 | @type softdevice_size: int 63 | @param app_size: Size of application firmware 64 | :return: The image size packet 65 | :rtype: str 66 | """ 67 | softdevice_size_packet = int32_to_bytes(softdevice_size) 68 | bootloader_size_packet = int32_to_bytes(bootloader_size) 69 | app_size_packet = int32_to_bytes(app_size) 70 | image_size_packet = softdevice_size_packet + bootloader_size_packet + app_size_packet 71 | return image_size_packet 72 | 73 | @abc.abstractmethod 74 | def __init__(self): 75 | self.callbacks = {} 76 | 77 | @abc.abstractmethod 78 | def open(self): 79 | """ 80 | Open a port if appropriate for the transport. 81 | :return: 82 | """ 83 | pass 84 | 85 | @abc.abstractmethod 86 | def close(self): 87 | """ 88 | Close a port if appropriate for the transport. 89 | :return: 90 | """ 91 | pass 92 | 93 | @abc.abstractmethod 94 | def is_open(self): 95 | """ 96 | Returns if transport is open. 97 | 98 | :return bool: True if transport is open, False if not 99 | """ 100 | pass 101 | 102 | @abc.abstractmethod 103 | def send_start_dfu(self, program_mode, softdevice_size=0, bootloader_size=0, app_size=0): 104 | """ 105 | Send packet to initiate DFU communication. Returns when packet is sent or timeout occurs. 106 | 107 | This call will block until packet is sent. 108 | If timeout or errors occurs exception is thrown. 109 | 110 | :param nordicsemi.dfu.model.HexType program_mode: Type of firmware to upgrade 111 | :param int softdevice_size: Size of softdevice firmware 112 | :param int bootloader_size: Size of bootloader firmware 113 | :param int app_size: Size of application firmware 114 | :return: 115 | """ 116 | pass 117 | 118 | @abc.abstractmethod 119 | def send_init_packet(self, init_packet): 120 | """ 121 | Send init_packet to device. 122 | 123 | This call will block until init_packet is sent and transfer of packet is complete. 124 | If timeout or errors occurs exception is thrown. 125 | 126 | :param str init_packet: Init packet as a str. 127 | :return: 128 | """ 129 | pass 130 | 131 | @abc.abstractmethod 132 | def send_firmware(self, firmware): 133 | """ 134 | Start sending firmware to device. 135 | 136 | This call will block until transfer of firmware is complete. 137 | If timeout or errors occurs exception is thrown. 138 | 139 | :param str firmware: 140 | :return: 141 | """ 142 | pass 143 | 144 | @abc.abstractmethod 145 | def send_validate_firmware(self): 146 | """ 147 | Send request to device to verify that firmware has been correctly transferred. 148 | 149 | This call will block until validation is sent and validation is complete. 150 | If timeout or errors occurs exception is thrown. 151 | 152 | :return bool: True if firmware validated successfully. 153 | """ 154 | pass 155 | 156 | @abc.abstractmethod 157 | def send_activate_firmware(self): 158 | """ 159 | Send command to device to activate new firmware and restart the device. 160 | The device will start up with the new firmware. 161 | 162 | Raises an nRFException if anything fails. 163 | 164 | :return: 165 | """ 166 | pass 167 | 168 | def register_events_callback(self, event_type, callback): 169 | """ 170 | Register a callback. 171 | 172 | :param DfuEvent callback: 173 | :return: None 174 | """ 175 | if event_type not in self.callbacks: 176 | self.callbacks[event_type] = [] 177 | 178 | self.callbacks[event_type].append(callback) 179 | 180 | def unregister_events_callback(self, callback): 181 | """ 182 | Unregister a callback. 183 | 184 | :param callback: # TODO: add documentation for callback 185 | :return: None 186 | """ 187 | for event_type in list(self.callbacks.keys()): 188 | if callback in self.callbacks[event_type]: 189 | self.callbacks[event_type].remove(callback) 190 | 191 | def _send_event(self, event_type, **kwargs): 192 | """ 193 | Method for sending events to registered callbacks. 194 | 195 | If callbacks throws exceptions event propagation will stop and this method be part of the track trace. 196 | 197 | :param DfuEvent event_type: 198 | :param args: Arguments to callback function 199 | :return: 200 | """ 201 | if event_type in list(self.callbacks.keys()): 202 | for callback in self.callbacks[event_type]: 203 | callback(**kwargs) 204 | -------------------------------------------------------------------------------- /nordicsemi/dfu/dfu_transport_ble.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | # Python standard library 30 | from time import sleep 31 | from datetime import datetime, timedelta 32 | import abc 33 | import logging 34 | 35 | # Nordic libraries 36 | from nordicsemi.exceptions import NordicSemiException, IllegalStateException 37 | from nordicsemi.dfu.util import int16_to_bytes 38 | from nordicsemi.dfu.dfu_transport import DfuTransport, DfuEvent 39 | 40 | logger = logging.getLogger(__name__) 41 | 42 | 43 | # BLE DFU OpCodes : 44 | class DfuOpcodesBle(object): 45 | """ DFU opcodes used during DFU communication with bootloader 46 | 47 | See http://developer.nordicsemi.com/nRF51_SDK/doc/7.2.0/s110/html/a00949.html#gafa9a52a3e6c43ccf00cf680f944d67a3 48 | for further information 49 | """ 50 | INVALID_OPCODE = 0 51 | START_DFU = 1 52 | INITIALIZE_DFU = 2 53 | RECEIVE_FIRMWARE_IMAGE = 3 54 | VALIDATE_FIRMWARE_IMAGE = 4 55 | ACTIVATE_FIRMWARE_AND_RESET = 5 56 | SYSTEM_RESET = 6 57 | REQ_PKT_RCPT_NOTIFICATION = 8 58 | RESPONSE = 16 59 | PKT_RCPT_NOTIF = 17 60 | 61 | 62 | class DfuErrorCodeBle(object): 63 | """ DFU error code used during DFU communication with bootloader 64 | 65 | See http://developer.nordicsemi.com/nRF51_SDK/doc/7.2.0/s110/html/a00949.html#gafa9a52a3e6c43ccf00cf680f944d67a3 66 | for further information 67 | """ 68 | SUCCESS = 1 69 | INVALID_STATE = 2 70 | NOT_SUPPORTED = 3 71 | DATA_SIZE_EXCEEDS_LIMIT = 4 72 | CRC_ERROR = 5 73 | OPERATION_FAILED = 6 74 | 75 | @staticmethod 76 | def error_code_lookup(error_code): 77 | """ 78 | Returns a description lookup table for error codes received from peer. 79 | 80 | :param int error_code: Error code to parse 81 | :return str: Textual description of the error code 82 | """ 83 | code_lookup = {DfuErrorCodeBle.SUCCESS: "SUCCESS", 84 | DfuErrorCodeBle.INVALID_STATE: "Invalid State", 85 | DfuErrorCodeBle.NOT_SUPPORTED: "Not Supported", 86 | DfuErrorCodeBle.DATA_SIZE_EXCEEDS_LIMIT: "Data Size Exceeds Limit", 87 | DfuErrorCodeBle.CRC_ERROR: "CRC Error", 88 | DfuErrorCodeBle.OPERATION_FAILED: "Operation Failed"} 89 | 90 | return code_lookup.get(error_code, "UNKOWN ERROR CODE") 91 | 92 | # Service UUID. For further information, look at the nRF51 SDK documentation V7.2.0: 93 | # http://developer.nordicsemi.com/nRF51_SDK/doc/7.2.0/s110/html/a00071.html#ota_spec_number 94 | UUID_DFU_SERVICE = '000015301212EFDE1523785FEABCD123' 95 | # Characteristic UUID 96 | UUID_DFU_PACKET_CHARACTERISTIC = '000015321212EFDE1523785FEABCD123' 97 | UUID_DFU_CONTROL_STATE_CHARACTERISTIC = '000015311212EFDE1523785FEABCD123' 98 | # Descriptor UUID 99 | UUID_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR = 0x2902 100 | 101 | # NOTE: If packet receipt notification is enabled, a packet receipt 102 | # notification will be received for each 'num_of_packets_between_notif' 103 | # number of packets. 104 | # 105 | # Configuration tip: Increase this to get lesser notifications from the DFU 106 | # Target about packet receipts. Make it 0 to disable the packet receipt 107 | # notification 108 | 109 | NUM_OF_PACKETS_BETWEEN_NOTIF = 10 110 | DATA_PACKET_SIZE = 20 111 | 112 | 113 | class DfuTransportBle(DfuTransport): 114 | 115 | def __init__(self): 116 | super(DfuTransportBle, self).__init__() 117 | 118 | def open(self): 119 | super(DfuTransportBle, self).open() 120 | 121 | def is_open(self): 122 | return super(DfuTransportBle, self).is_open() 123 | 124 | def close(self): 125 | super(DfuTransportBle, self).close() 126 | 127 | def _wait_for_condition(self, condition_function, expected_condition_value=True, timeout=10, 128 | waiting_for="condition"): 129 | """ 130 | Waits for condition_function to be true 131 | Will timeout after 60 seconds 132 | 133 | :param function condition_function: The function we are waiting for to return true 134 | :param str timeout_message: Message that should be logged 135 | :return: 136 | """ 137 | 138 | start_time = datetime.now() 139 | 140 | while condition_function() != expected_condition_value: 141 | timeout_message = "Timeout while waiting for {0}.".format(waiting_for) 142 | timed_out = datetime.now() - start_time > timedelta(0, timeout) 143 | if timed_out: 144 | self._send_event(DfuEvent.TIMEOUT_EVENT, log_message=timeout_message) 145 | raise NordicSemiException(timeout_message) 146 | 147 | if not self.is_open(): 148 | log_message = "Disconnected from device while waiting for {0}.".format(waiting_for) 149 | raise IllegalStateException(log_message) 150 | 151 | sleep(0.1) 152 | 153 | if self.get_last_error() != DfuErrorCodeBle.SUCCESS: 154 | error_message = "Error occoured while waiting for {0}. Error response {1}." 155 | error_code = DfuErrorCodeBle.error_code_lookup(self.get_last_error()) 156 | error_message = error_message.format(waiting_for, error_code) 157 | self._send_event(DfuEvent.ERROR_EVENT, log_message=error_message) 158 | raise NordicSemiException(error_message) 159 | 160 | @abc.abstractmethod 161 | def send_packet_data(self, data): 162 | """ 163 | Send data to the packet characteristic 164 | 165 | :param str data: The data to be sent 166 | :return: 167 | """ 168 | pass 169 | 170 | @abc.abstractmethod 171 | def send_control_data(self, opcode, data=""): 172 | """ 173 | Send data to the control characteristic 174 | 175 | :param int opcode: The opcode for the operation command sent to the control characteristic 176 | :param str data: The data to be sent 177 | :return: 178 | """ 179 | pass 180 | 181 | @abc.abstractmethod 182 | def get_received_response(self): 183 | """ 184 | Returns True if the transport layer has received a response it expected 185 | 186 | :return: bool 187 | """ 188 | pass 189 | 190 | def clear_received_response(self): 191 | """ 192 | Clears the received response status, sets it to False. 193 | 194 | :return: 195 | """ 196 | pass 197 | 198 | @abc.abstractmethod 199 | def is_waiting_for_notification(self): 200 | """ 201 | Returns True if the transport layer is waiting for a notification 202 | 203 | :return: bool 204 | """ 205 | pass 206 | 207 | def set_waiting_for_notification(self): 208 | """ 209 | Notifies the transport layer that it should wait for notification 210 | 211 | :return: 212 | """ 213 | pass 214 | 215 | @abc.abstractmethod 216 | def get_last_error(self): 217 | """ 218 | Returns the last error code 219 | 220 | :return: DfuErrorCodeBle 221 | """ 222 | pass 223 | 224 | def _start_dfu(self, program_mode, image_size_packet): 225 | logger.debug("Sending 'START DFU' command") 226 | self.send_control_data(DfuOpcodesBle.START_DFU, chr(program_mode)) 227 | logger.debug("Sending image size") 228 | self.send_packet_data(image_size_packet) 229 | self._wait_for_condition(self.get_received_response, waiting_for="response for START DFU") 230 | self.clear_received_response() 231 | 232 | def send_start_dfu(self, program_mode, softdevice_size=0, bootloader_size=0, app_size=0): 233 | super(DfuTransportBle, self).send_start_dfu(program_mode, softdevice_size, bootloader_size, app_size) 234 | image_size_packet = DfuTransport.create_image_size_packet(softdevice_size, bootloader_size, app_size) 235 | 236 | self._send_event(DfuEvent.PROGRESS_EVENT, progress=0, log_message="Setting up transfer...") 237 | 238 | try: 239 | self._start_dfu(program_mode, image_size_packet) 240 | except IllegalStateException: 241 | # We got disconnected. Try to send Start DFU again in case of buttonless dfu. 242 | self.close() 243 | self.open() 244 | 245 | if not self.is_open(): 246 | raise IllegalStateException("Failed to reopen transport backend.") 247 | 248 | self._start_dfu(program_mode, image_size_packet) 249 | 250 | def send_init_packet(self, init_packet): 251 | super(DfuTransportBle, self).send_init_packet(init_packet) 252 | init_packet_start = chr(0x00) 253 | init_packet_end = chr(0x01) 254 | 255 | logger.debug("Sending 'INIT DFU' command") 256 | self.send_control_data(DfuOpcodesBle.INITIALIZE_DFU, init_packet_start) 257 | 258 | logger.debug("Sending init data") 259 | for i in range(0, len(init_packet), DATA_PACKET_SIZE): 260 | data_to_send = init_packet[i:i + DATA_PACKET_SIZE] 261 | self.send_packet_data(data_to_send) 262 | 263 | logger.debug("Sending 'Init Packet Complete' command") 264 | self.send_control_data(DfuOpcodesBle.INITIALIZE_DFU, init_packet_end) 265 | self._wait_for_condition(self.get_received_response, timeout=60, waiting_for="response for INITIALIZE DFU") 266 | self.clear_received_response() 267 | 268 | if NUM_OF_PACKETS_BETWEEN_NOTIF: 269 | packet = int16_to_bytes(NUM_OF_PACKETS_BETWEEN_NOTIF) 270 | logger.debug("Send number of packets before device sends notification") 271 | self.send_control_data(DfuOpcodesBle.REQ_PKT_RCPT_NOTIFICATION, packet) 272 | 273 | def send_firmware(self, firmware): 274 | def progress_percentage(part, complete): 275 | """ 276 | Calculate progress percentage 277 | :param int part: Part value 278 | :param int complete: Completed value 279 | :return: int: Percentage complete 280 | """ 281 | return min(100, (part + DATA_PACKET_SIZE) * 100 / complete) 282 | 283 | super(DfuTransportBle, self).send_firmware(firmware) 284 | packets_sent = 0 285 | last_progress_update = -1 # Last packet sequence number when an update was fired to the event system 286 | bin_size = len(firmware) 287 | logger.debug("Send 'RECEIVE FIRMWARE IMAGE' command") 288 | self.send_control_data(DfuOpcodesBle.RECEIVE_FIRMWARE_IMAGE) 289 | 290 | for i in range(0, bin_size, DATA_PACKET_SIZE): 291 | progress = progress_percentage(i, bin_size) 292 | 293 | if progress != last_progress_update: 294 | self._send_event(DfuEvent.PROGRESS_EVENT, progress=progress, log_message="Uploading firmware") 295 | last_progress_update = progress 296 | 297 | self._wait_for_condition(self.is_waiting_for_notification, expected_condition_value=False, 298 | waiting_for="notification from device") 299 | 300 | data_to_send = firmware[i:i + DATA_PACKET_SIZE] 301 | 302 | log_message = "Sending Firmware bytes [{0}, {1}]".format(i, i + len(data_to_send)) 303 | logger.debug(log_message) 304 | 305 | packets_sent += 1 306 | 307 | if NUM_OF_PACKETS_BETWEEN_NOTIF != 0: 308 | if (packets_sent % NUM_OF_PACKETS_BETWEEN_NOTIF) == 0: 309 | self.set_waiting_for_notification() 310 | 311 | self.send_packet_data(data_to_send) 312 | 313 | self._wait_for_condition(self.get_received_response, waiting_for="response for RECEIVE FIRMWARE IMAGE") 314 | self.clear_received_response() 315 | 316 | def send_validate_firmware(self): 317 | super(DfuTransportBle, self).send_validate_firmware() 318 | logger.debug("Sending 'VALIDATE FIRMWARE IMAGE' command") 319 | self.send_control_data(DfuOpcodesBle.VALIDATE_FIRMWARE_IMAGE) 320 | self._wait_for_condition(self.get_received_response, waiting_for="response for VALIDATE FIRMWARE IMAGE") 321 | self.clear_received_response() 322 | logger.info("Firmware validated OK.") 323 | 324 | def send_activate_firmware(self): 325 | super(DfuTransportBle, self).send_activate_firmware() 326 | logger.debug("Sending 'ACTIVATE FIRMWARE AND RESET' command") 327 | self.send_control_data(DfuOpcodesBle.ACTIVATE_FIRMWARE_AND_RESET) 328 | -------------------------------------------------------------------------------- /nordicsemi/dfu/init_packet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from enum import Enum 30 | import struct 31 | 32 | 33 | INIT_PACKET_USES_CRC16 = 0 34 | INIT_PACKET_USES_HASH = 1 35 | INIT_PACKET_EXT_USES_ECDS = 2 36 | 37 | 38 | class PacketField(Enum): 39 | DEVICE_TYPE = 1 40 | DEVICE_REVISION = 2 41 | APP_VERSION = 3 42 | REQUIRED_SOFTDEVICES_ARRAY = 4 43 | OPT_DATA = 5 44 | NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID = 6 45 | NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH = 7 46 | NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH = 8 47 | NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16 = 9 48 | NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS = 10 49 | 50 | 51 | class Packet(object): 52 | """ 53 | Class that implements the INIT packet format. 54 | http://developer.nordicsemi.com/nRF51_SDK/doc/7.1.0/s110/html/a00065.html 55 | """ 56 | 57 | UNSIGNED_SHORT = "H" 58 | UNSIGNED_INT = "I" 59 | UNSIGNED_CHAR = "B" 60 | CHAR_ARRAY = "s" 61 | 62 | def __init__(self, init_packet_fields): 63 | """ 64 | 65 | :param init_packet_fields: Dictionary with packet fields 66 | """ 67 | self.init_packet_fields = init_packet_fields 68 | 69 | def generate_packet(self): 70 | """ 71 | Generates a binary packet from provided init_packet_fields provided in constructor. 72 | This version includes the extended data 73 | 74 | :return str: Returns a string representing the init_packet (in binary) 75 | 76 | """ 77 | # Create struct format string based on keys that are 78 | # present in self.init_packet_fields 79 | format_string = self.__generate_struct_format_string() 80 | args = [] 81 | 82 | # If you got error message AttributeError: 'int' object has no attribute 'value'. 83 | # Uncomment line 84 and comment out line 85 and run 'python setup.py install' 84 | #for key in sorted(self.init_packet_fields.keys(), key=lambda x: x): 85 | for key in sorted(list(self.init_packet_fields.keys()), key=lambda x: x.value): 86 | # Add length to fields that required that 87 | if key in [PacketField.REQUIRED_SOFTDEVICES_ARRAY, 88 | PacketField.OPT_DATA]: 89 | args.append(len(self.init_packet_fields[key])) 90 | args.extend(self.init_packet_fields[key]) 91 | else: 92 | args.append(self.init_packet_fields[key]) 93 | 94 | return struct.pack(format_string, *args) 95 | 96 | def __generate_struct_format_string(self): 97 | format_string = "<" # Use little endian format with standard sizes for python, 98 | # see https://docs.python.org/2/library/struct.html 99 | 100 | # If you got error message AttributeError: 'int' object has no attribute 'value'. 101 | # Uncomment line 102 and comment out line 103 and run 'python setup.py install' 102 | #for key in sorted(self.init_packet_fields.keys(), key=lambda x: x): 103 | for key in sorted(list(self.init_packet_fields.keys()), key=lambda x: x.value): 104 | if key in [PacketField.DEVICE_TYPE, 105 | PacketField.DEVICE_REVISION, 106 | ]: 107 | format_string += Packet.UNSIGNED_SHORT 108 | 109 | elif key in [PacketField.APP_VERSION]: 110 | format_string += Packet.UNSIGNED_INT 111 | elif key in [PacketField.REQUIRED_SOFTDEVICES_ARRAY]: 112 | array_elements = self.init_packet_fields[key] 113 | format_string += Packet.UNSIGNED_SHORT # Add length field to format packet 114 | 115 | for _ in range(len(array_elements)): 116 | format_string += Packet.UNSIGNED_SHORT 117 | elif key in [PacketField.OPT_DATA]: 118 | format_string += Packet.UNSIGNED_SHORT # Add length field to optional data 119 | format_string += "{0}{1}".format(len(self.init_packet_fields[key]), Packet.CHAR_ARRAY) 120 | elif key in [PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID]: 121 | format_string += Packet.UNSIGNED_INT # Add the extended packet id field 122 | elif key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH: 123 | format_string += Packet.UNSIGNED_INT # Add the firmware length field 124 | elif key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH: 125 | format_string += "32{0}".format(Packet.CHAR_ARRAY) # SHA-256 requires 32 bytes 126 | elif key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16: 127 | format_string += Packet.UNSIGNED_SHORT 128 | elif key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS: 129 | format_string += "64{0}".format(Packet.CHAR_ARRAY) # ECDS based on P-256 using SHA-256 requires 64 bytes 130 | 131 | return format_string 132 | -------------------------------------------------------------------------------- /nordicsemi/dfu/intelhex/compat.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011, Bernhard Leiner 2 | # Copyright (c) 2013-2018 Alexander Belchenko 3 | # All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, 6 | # with or without modification, are permitted provided 7 | # that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain 10 | # the above copyright notice, this list of conditions 11 | # and the following disclaimer. 12 | # * Redistributions in binary form must reproduce 13 | # the above copyright notice, this list of conditions 14 | # and the following disclaimer in the documentation 15 | # and/or other materials provided with the distribution. 16 | # * Neither the name of the author nor the names 17 | # of its contributors may be used to endorse 18 | # or promote products derived from this software 19 | # without specific prior written permission. 20 | # 21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 23 | # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 24 | # AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 | # IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 26 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 27 | # OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 28 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 29 | # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 30 | # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 31 | # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 32 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 33 | # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | '''Compatibility functions for python 2 and 3. 36 | 37 | @author Bernhard Leiner (bleiner AT gmail com) 38 | @author Alexander Belchenko (alexander belchenko AT gmail com) 39 | ''' 40 | 41 | __docformat__ = "javadoc" 42 | 43 | 44 | import sys, array 45 | 46 | 47 | if sys.version_info[0] >= 3: 48 | # Python 3 49 | Python = 3 50 | 51 | def asbytes(s): 52 | if isinstance(s, bytes): 53 | return s 54 | return s.encode('latin1') 55 | def asstr(s): 56 | if isinstance(s, str): 57 | return s 58 | return s.decode('latin1') 59 | 60 | # for python >= 3.2 use 'tobytes', otherwise 'tostring' 61 | array_tobytes = array.array.tobytes if sys.version_info[1] >= 2 else array.array.tostring 62 | 63 | IntTypes = (int,) 64 | StrType = str 65 | UnicodeType = str 66 | 67 | range_g = range # range generator 68 | def range_l(*args): # range list 69 | return list(range(*args)) 70 | 71 | def dict_keys(dikt): # dict keys list 72 | return list(dikt.keys()) 73 | def dict_keys_g(dikt): # dict keys generator 74 | return dikt.keys() 75 | def dict_items_g(dikt): # dict items generator 76 | return dikt.items() 77 | 78 | from io import StringIO, BytesIO 79 | 80 | def get_binary_stdout(): 81 | return sys.stdout.buffer 82 | 83 | def get_binary_stdin(): 84 | return sys.stdin.buffer 85 | 86 | else: 87 | # Python 2 88 | Python = 2 89 | 90 | asbytes = str 91 | asstr = str 92 | 93 | array_tobytes = array.array.tostring 94 | 95 | IntTypes = (int, long) 96 | StrType = basestring 97 | UnicodeType = unicode 98 | 99 | #range_g = xrange # range generator 100 | def range_g(*args): 101 | # we want to use xrange here but on python 2 it does not work with long ints 102 | try: 103 | return xrange(*args) 104 | except OverflowError: 105 | start = 0 106 | stop = 0 107 | step = 1 108 | n = len(args) 109 | if n == 1: 110 | stop = args[0] 111 | elif n == 2: 112 | start, stop = args 113 | elif n == 3: 114 | start, stop, step = args 115 | else: 116 | raise TypeError('wrong number of arguments in range_g call!') 117 | if step == 0: 118 | raise ValueError('step cannot be zero') 119 | if step > 0: 120 | def up(start, stop, step): 121 | while start < stop: 122 | yield start 123 | start += step 124 | return up(start, stop, step) 125 | else: 126 | def down(start, stop, step): 127 | while start > stop: 128 | yield start 129 | start += step 130 | return down(start, stop, step) 131 | 132 | range_l = range # range list 133 | 134 | def dict_keys(dikt): # dict keys list 135 | return dikt.keys() 136 | def dict_keys_g(dikt): # dict keys generator 137 | return dikt.keys() 138 | def dict_items_g(dikt): # dict items generator 139 | return dikt.items() 140 | 141 | from cStringIO import StringIO 142 | BytesIO = StringIO 143 | 144 | import os 145 | def _force_stream_binary(stream): 146 | """Force binary mode for stream on Windows.""" 147 | if os.name == 'nt': 148 | f_fileno = getattr(stream, 'fileno', None) 149 | if f_fileno: 150 | fileno = f_fileno() 151 | if fileno >= 0: 152 | import msvcrt 153 | msvcrt.setmode(fileno, os.O_BINARY) 154 | return stream 155 | 156 | def get_binary_stdout(): 157 | return _force_stream_binary(sys.stdout) 158 | 159 | def get_binary_stdin(): 160 | return _force_stream_binary(sys.stdin) 161 | -------------------------------------------------------------------------------- /nordicsemi/dfu/intelhex/getsizeof.py: -------------------------------------------------------------------------------- 1 | # Recursive version sys.getsizeof(). Extendable with custom handlers. 2 | # Code from http://code.activestate.com/recipes/577504/ 3 | # Created by Raymond Hettinger on Fri, 17 Dec 2010 (MIT) 4 | 5 | import sys 6 | from itertools import chain 7 | from collections import deque 8 | try: 9 | from reprlib import repr 10 | except ImportError: 11 | pass 12 | 13 | def total_size(o, handlers={}, verbose=False): 14 | """ Returns the approximate memory footprint an object and all of its contents. 15 | 16 | Automatically finds the contents of the following builtin containers and 17 | their subclasses: tuple, list, deque, dict, set and frozenset. 18 | To search other containers, add handlers to iterate over their contents: 19 | 20 | handlers = {SomeContainerClass: iter, 21 | OtherContainerClass: OtherContainerClass.get_elements} 22 | 23 | """ 24 | dict_handler = lambda d: chain.from_iterable(d.items()) 25 | all_handlers = {tuple: iter, 26 | list: iter, 27 | deque: iter, 28 | dict: dict_handler, 29 | set: iter, 30 | frozenset: iter, 31 | } 32 | all_handlers.update(handlers) # user handlers take precedence 33 | seen = set() # track which object id's have already been seen 34 | default_size = sys.getsizeof(0) # estimate sizeof object without __sizeof__ 35 | 36 | def sizeof(o): 37 | if id(o) in seen: # do not double count the same object 38 | return 0 39 | seen.add(id(o)) 40 | s = sys.getsizeof(o, default_size) 41 | 42 | if verbose: 43 | print(s, type(o), repr(o))#, file=stderr) 44 | 45 | for typ, handler in all_handlers.items(): 46 | if isinstance(o, typ): 47 | s += sum(map(sizeof, handler(o))) 48 | break 49 | return s 50 | 51 | return sizeof(o) 52 | 53 | 54 | ##### Example call ##### 55 | 56 | if __name__ == '__main__': 57 | #d = dict(a=1, b=2, c=3, d=[4,5,6,7], e='a string of chars') 58 | print("dict 3 elements") 59 | d = {0:0xFF, 1:0xEE, 2:0xCC} 60 | print(total_size(d, verbose=True)) 61 | 62 | #print("array 3 elements") 63 | #import array 64 | #print(total_size(array.array('B', b'\x01\x02\x03'))) 65 | -------------------------------------------------------------------------------- /nordicsemi/dfu/manifest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | # Python libraries 30 | import json 31 | import binascii 32 | import os 33 | 34 | # Nordic libraries 35 | from nordicsemi.exceptions import NotImplementedException 36 | from nordicsemi.dfu.init_packet import PacketField 37 | from nordicsemi.dfu.model import HexType, FirmwareKeys 38 | 39 | 40 | class ManifestGenerator(object): 41 | def __init__(self, dfu_version, firmwares_data): 42 | """ 43 | The Manifest Generator constructor. Needs a data structure to generate a manifest from. 44 | 45 | :type float dfu_version: The dfu version number to state in manifest 46 | :type dict firmwares_data: The firmwares data structure describing the Nordic DFU package 47 | """ 48 | self.dfu_version = dfu_version 49 | self.firmwares_data = firmwares_data 50 | self.manifest = None 51 | 52 | def generate_manifest(self): 53 | self.manifest = Manifest() 54 | self.manifest.dfu_version = self.dfu_version 55 | 56 | for key in self.firmwares_data: 57 | firmware_dict = self.firmwares_data[key] 58 | 59 | if key == HexType.SD_BL: 60 | _firmware = SoftdeviceBootloaderFirmware() 61 | _firmware.bl_size = firmware_dict[FirmwareKeys.BL_SIZE] 62 | _firmware.sd_size = firmware_dict[FirmwareKeys.SD_SIZE] 63 | else: 64 | _firmware = Firmware() 65 | 66 | # Strip path, add only filename 67 | _firmware.bin_file = os.path.basename(firmware_dict[FirmwareKeys.BIN_FILENAME]) 68 | _firmware.dat_file = os.path.basename(firmware_dict[FirmwareKeys.DAT_FILENAME]) 69 | 70 | init_packet_data = InitPacketData() 71 | 72 | for init_packet_data_key in firmware_dict[FirmwareKeys.INIT_PACKET_DATA]: 73 | field = firmware_dict[FirmwareKeys.INIT_PACKET_DATA][init_packet_data_key] 74 | 75 | if init_packet_data_key == PacketField.APP_VERSION: 76 | init_packet_data.application_version = field 77 | elif init_packet_data_key == PacketField.DEVICE_TYPE: 78 | init_packet_data.device_type = field 79 | elif init_packet_data_key == PacketField.DEVICE_REVISION: 80 | init_packet_data.device_revision = field 81 | elif init_packet_data_key == PacketField.REQUIRED_SOFTDEVICES_ARRAY: 82 | init_packet_data.softdevice_req = field 83 | elif init_packet_data_key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID: 84 | init_packet_data.ext_packet_id = field 85 | elif init_packet_data_key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH: 86 | init_packet_data.firmware_length = field 87 | elif init_packet_data_key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH: 88 | init_packet_data.firmware_hash = binascii.hexlify(field) 89 | elif init_packet_data_key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16: 90 | init_packet_data.firmware_crc16 = field 91 | elif init_packet_data_key == PacketField.NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS: 92 | init_packet_data.init_packet_ecds = binascii.hexlify(field) 93 | else: 94 | raise NotImplementedException( 95 | "Support for init packet data type {0} not implemented yet.".format(init_packet_data_key)) 96 | 97 | _firmware.init_packet_data = init_packet_data 98 | 99 | if key == HexType.APPLICATION: 100 | self.manifest.application = _firmware 101 | elif key == HexType.BOOTLOADER: 102 | self.manifest.bootloader = _firmware 103 | elif key == HexType.SOFTDEVICE: 104 | self.manifest.softdevice = _firmware 105 | elif key == HexType.SD_BL: 106 | self.manifest.softdevice_bootloader = _firmware 107 | else: 108 | raise NotImplementedException("Support for firmware type {0} not implemented yet.".format(key)) 109 | 110 | return self.to_json() 111 | 112 | def to_json(self): 113 | def remove_none_entries(d): 114 | if not isinstance(d, dict): 115 | return d 116 | 117 | return dict((k, remove_none_entries(v)) for k, v in d.items() if v is not None) 118 | 119 | def _try(o): 120 | try: 121 | return o.__dict__ 122 | except: 123 | return o.decode("ascii") 124 | 125 | return json.dumps({'manifest': self.manifest}, 126 | default=lambda o: remove_none_entries(_try(o)), 127 | sort_keys=True, indent=4, 128 | separators=(',', ': ')) 129 | 130 | 131 | class InitPacketData(object): 132 | def __init__(self, 133 | device_type=None, 134 | device_revision=None, 135 | application_version=None, 136 | softdevice_req=None, 137 | ext_packet_id=None, 138 | firmware_length=None, 139 | firmware_hash=None, 140 | firmware_crc16=None, 141 | init_packet_ecds=None 142 | ): 143 | """ 144 | The InitPacketData data model. 145 | 146 | :param int device_type: device type 147 | :param int device_revision: device revision 148 | :param int application_version: application version 149 | :param list softdevice_req: softdevice requirements 150 | :param int ext_packet_id: packet extension id 151 | :param int firmware_length: firmware length 152 | :param str firmware_hash: firmware hash 153 | :param int firmware_crc16: firmware CRC-16 calculated value 154 | :param str init_packet_ecds: Init packet signature 155 | :return: InitPacketData 156 | """ 157 | self.device_type = device_type 158 | self.device_revision = device_revision 159 | self.application_version = application_version 160 | self.softdevice_req = softdevice_req 161 | self.ext_packet_id = ext_packet_id 162 | self.firmware_length = firmware_length 163 | self.firmware_hash = firmware_hash 164 | self.firmware_crc16 = firmware_crc16 165 | self.init_packet_ecds = init_packet_ecds 166 | 167 | 168 | class Firmware(object): 169 | def __init__(self, 170 | bin_file=None, 171 | dat_file=None, 172 | init_packet_data=None): 173 | """ 174 | The firmware datamodel 175 | 176 | :param str bin_file: Firmware binary file 177 | :param str dat_file: Firmware .dat file (init packet for Nordic DFU) 178 | :param dict init_packet_data: Initial packet data 179 | :return: 180 | """ 181 | self.dat_file = dat_file 182 | self.bin_file = bin_file 183 | 184 | if init_packet_data: 185 | self.init_packet_data = InitPacketData(**init_packet_data) 186 | 187 | 188 | class SoftdeviceBootloaderFirmware(Firmware): 189 | def __init__(self, 190 | bin_file=None, 191 | dat_file=None, 192 | init_packet_data=None, 193 | sd_size=None, 194 | bl_size=None): 195 | """ 196 | The SoftdeviceBootloaderFirmware data model 197 | 198 | :param str bin_file: Firmware binary file 199 | :param str dat_file: Firmware .dat file (init packet for Nordic DFU) 200 | :param int sd_size: The softdevice size 201 | :param int bl_size: The bootloader size 202 | :return: SoftdeviceBootloaderFirmware 203 | """ 204 | super(SoftdeviceBootloaderFirmware, self).__init__( 205 | bin_file, 206 | dat_file, 207 | init_packet_data) 208 | self.sd_size = sd_size 209 | self.bl_size = bl_size 210 | 211 | 212 | class Manifest: 213 | def __init__(self, 214 | application=None, 215 | bootloader=None, 216 | softdevice=None, 217 | softdevice_bootloader=None, 218 | dfu_version=None): 219 | """ 220 | The Manifest data model. 221 | 222 | :param dict application: Application firmware in package 223 | :param dict bootloader: Bootloader firmware in package 224 | :param dict softdevice: Softdevice firmware in package 225 | :param dict softdevice_bootloader: Combined softdevice and bootloader firmware in package 226 | :return: Manifest 227 | """ 228 | self.softdevice_bootloader = \ 229 | SoftdeviceBootloaderFirmware(**softdevice_bootloader) if softdevice_bootloader else None 230 | 231 | self.softdevice = Firmware(**softdevice) if softdevice else None 232 | self.bootloader = Firmware(**bootloader) if bootloader else None 233 | self.application = Firmware(**application) if application else None 234 | self.dfu_version = dfu_version 235 | 236 | @staticmethod 237 | def from_json(data): 238 | """ 239 | Parses a manifest according to Nordic DFU package specification. 240 | 241 | :param str data: The manifest in string format 242 | :return: Manifest 243 | """ 244 | kwargs = json.loads(data) 245 | return Manifest(**kwargs['manifest']) 246 | -------------------------------------------------------------------------------- /nordicsemi/dfu/model.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from enum import Enum 30 | 31 | 32 | class HexType(object): 33 | SOFTDEVICE = 1 34 | BOOTLOADER = 2 35 | SD_BL = 3 36 | APPLICATION = 4 37 | 38 | 39 | class FirmwareKeys(Enum): 40 | ENCRYPT = 1 41 | FIRMWARE_FILENAME = 2 42 | BIN_FILENAME = 3 43 | DAT_FILENAME = 4 44 | INIT_PACKET_DATA = 5 45 | SD_SIZE = 6 46 | BL_SIZE = 7 47 | -------------------------------------------------------------------------------- /nordicsemi/dfu/nrfhex.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from nordicsemi.dfu import intelhex 30 | from struct import unpack 31 | 32 | 33 | class nRFHex(intelhex.IntelHex): 34 | """ 35 | Converts and merges .hex and .bin files into one .bin file. 36 | """ 37 | 38 | info_struct_address_base = 0x00003000 39 | info_struct_address_offset = 0x1000 40 | 41 | info_struct_magic_number = 0x51B1E5DB 42 | info_struct_magic_number_offset = 0x004 43 | 44 | s1x0_mbr_end_address = 0x1000 45 | s132_mbr_end_address = 0x3000 46 | 47 | def __init__(self, source, bootloader=None): 48 | """ 49 | Constructor that requires a firmware file path. 50 | Softdevices can take an optional bootloader file path as parameter. 51 | 52 | :param str source: The file path for the firmware 53 | :param str bootloader: Optional file path to bootloader firmware 54 | :return: None 55 | """ 56 | super(nRFHex, self).__init__() 57 | 58 | self.file_format = 'hex' 59 | 60 | if source.endswith('.bin'): 61 | self.file_format = 'bin' 62 | 63 | self.loadfile(source, self.file_format) 64 | 65 | self._removeuicr() 66 | 67 | self.bootloaderhex = None 68 | 69 | if bootloader is not None: 70 | self.bootloaderhex = nRFHex(bootloader) 71 | 72 | def _removeuicr(self): 73 | uicr_start_address = 0x10000000 74 | maxaddress = self.maxaddr() 75 | if maxaddress >= uicr_start_address: 76 | for i in range(uicr_start_address, maxaddress + 1): 77 | self._buf.pop(i, 0) 78 | 79 | def address_has_magic_number(self, address): 80 | try: 81 | potential_magic_number = self.gets(address, 4) 82 | potential_magic_number = unpack('I', potential_magic_number)[0] 83 | return nRFHex.info_struct_magic_number == potential_magic_number 84 | except Exception: 85 | return False 86 | 87 | def get_softdevice_variant(self): 88 | potential_magic_number_address = nRFHex.info_struct_address_base + nRFHex.info_struct_magic_number_offset 89 | 90 | if self.address_has_magic_number(potential_magic_number_address): 91 | return "s1x0" 92 | 93 | for i in range(4): 94 | potential_magic_number_address += nRFHex.info_struct_address_offset 95 | 96 | if self.address_has_magic_number(potential_magic_number_address): 97 | return "s132" 98 | 99 | return "unknown" 100 | 101 | def get_mbr_end_address(self): 102 | softdevice_variant = self.get_softdevice_variant() 103 | 104 | if softdevice_variant == "s132": 105 | return nRFHex.s132_mbr_end_address 106 | else: 107 | return nRFHex.s1x0_mbr_end_address 108 | 109 | def minaddr(self): 110 | min_address = super(nRFHex, self).minaddr() 111 | 112 | # Lower addresses are reserved for master boot record 113 | if self.file_format != 'bin': 114 | min_address = max(self.get_mbr_end_address(), min_address) 115 | 116 | return min_address 117 | 118 | def size(self): 119 | """ 120 | Returns the size of the source. 121 | :return: int 122 | """ 123 | min_address = self.minaddr() 124 | max_address = self.maxaddr() 125 | 126 | size = max_address - min_address + 1 127 | 128 | # Round up to nearest word 129 | word_size = 4 130 | number_of_words = (size + (word_size - 1)) // word_size 131 | size = number_of_words * word_size 132 | 133 | return size 134 | 135 | def bootloadersize(self): 136 | """ 137 | Returns the size of the bootloader. 138 | :return: int 139 | """ 140 | if self.bootloaderhex is None: 141 | return 0 142 | 143 | return self.bootloaderhex.size() 144 | 145 | def tobinfile(self, fobj, start=None, end=None, pad=None, size=None): 146 | """ 147 | Writes a binary version of source and bootloader respectivly to fobj which could be a 148 | file object or a file path. 149 | 150 | :param str fobj: File path or object the function writes to 151 | :return: None 152 | """ 153 | # If there is a bootloader this will make the recursion call use the samme file object. 154 | if getattr(fobj, "write", None) is None: 155 | fobj = open(fobj, "wb") 156 | close_fd = True 157 | else: 158 | close_fd = False 159 | 160 | start_address = self.minaddr() 161 | size = self.size() 162 | super(nRFHex, self).tobinfile(fobj, start=start_address, size=size) 163 | 164 | if self.bootloaderhex is not None: 165 | self.bootloaderhex.tobinfile(fobj) 166 | 167 | if close_fd: 168 | fobj.close() 169 | -------------------------------------------------------------------------------- /nordicsemi/dfu/signing.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Nordic Semiconductor. All Rights Reserved. 2 | # 3 | # The information contained herein is property of Nordic Semiconductor ASA. 4 | # Terms and conditions of usage are described in detail in NORDIC 5 | # SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. 6 | # 7 | # Licensees are granted free, non-transferable use of the information. NO 8 | # WARRANTY of ANY KIND is provided. This heading must NOT be removed from 9 | # the file. 10 | 11 | import hashlib 12 | import binascii 13 | 14 | try: 15 | from ecdsa import SigningKey 16 | from ecdsa.curves import NIST256p 17 | from ecdsa.keys import sigencode_string 18 | except Exception: 19 | print("Failed to import ecdsa, cannot do signing") 20 | 21 | from nordicsemi.exceptions import InvalidArgumentException, IllegalStateException 22 | 23 | 24 | class Signing(object): 25 | """ 26 | Class for singing of hex-files 27 | """ 28 | def gen_key(self, filename): 29 | """ 30 | Generate a new Signing key using NIST P-256 curve 31 | """ 32 | self.sk = SigningKey.generate(curve=NIST256p) 33 | 34 | with open(filename, "w") as sk_file: 35 | sk_file.write(self.sk.to_pem().decode("ascii")) 36 | 37 | def load_key(self, filename): 38 | """ 39 | Load signing key (from pem file) 40 | """ 41 | with open(filename, "r") as sk_file: 42 | sk_pem = sk_file.read() 43 | 44 | self.sk = SigningKey.from_pem(sk_pem) 45 | sk_hex = self.sk.to_string().hex() 46 | 47 | def sign(self, init_packet_data): 48 | """ 49 | Create signature for init package using P-256 curve and SHA-256 as hashing algorithm 50 | Returns R and S keys combined in a 64 byte array 51 | """ 52 | # Add assertion of init_packet 53 | if self.sk is None: 54 | raise IllegalStateException("Can't save key. No key created/loaded") 55 | 56 | # Sign the init-packet 57 | signature = self.sk.sign(init_packet_data, hashfunc=hashlib.sha256, sigencode=sigencode_string) 58 | return signature 59 | 60 | def verify(self, init_packet, signature): 61 | """ 62 | Verify init packet 63 | """ 64 | # Add assertion of init_packet 65 | if self.sk is None: 66 | raise IllegalStateException("Can't save key. No key created/loaded") 67 | 68 | vk = self.sk.get_verifying_key() 69 | 70 | # Verify init packet 71 | try: 72 | vk.verify(signature, init_packet, hashfunc=hashlib.sha256) 73 | except: 74 | return False 75 | 76 | return True 77 | 78 | def get_vk(self, output_type): 79 | """ 80 | Get verification key (as hex, code or pem) 81 | """ 82 | if self.sk is None: 83 | raise IllegalStateException("Can't get key. No key created/loaded") 84 | 85 | if output_type is None: 86 | raise InvalidArgumentException("Invalid output type for signature.") 87 | elif output_type == 'hex': 88 | return self.get_vk_hex() 89 | elif output_type == 'code': 90 | return self.get_vk_code() 91 | elif output_type == 'pem': 92 | return self.get_vk_pem() 93 | else: 94 | raise InvalidArgumentException("Invalid argument. Can't get key") 95 | 96 | def get_vk_hex(self): 97 | """ 98 | Get the verification key as hex 99 | """ 100 | if self.sk is None: 101 | raise IllegalStateException("Can't get key. No key created/loaded") 102 | 103 | vk = self.sk.get_verifying_key() 104 | vk_hexlify = binascii.hexlify(vk.to_string()).decode("ascii") 105 | 106 | vk_hex = "Verification key Qx: {0}\n".format(vk_hexlify[0:64]) 107 | vk_hex += "Verification key Qy: {0}".format(vk_hexlify[64:128]) 108 | 109 | return vk_hex 110 | 111 | def get_vk_code(self): 112 | """ 113 | Get the verification key as code 114 | """ 115 | if self.sk is None: 116 | raise IllegalStateException("Can't get key. No key created/loaded") 117 | 118 | vk = self.sk.get_verifying_key() 119 | vk_hex = binascii.hexlify(vk.to_string()) 120 | 121 | vk_x_separated = "" 122 | vk_x_str = vk_hex[0:64] 123 | for i in range(0, len(vk_x_str), 2): 124 | vk_x_separated += "0x" + vk_x_str[i:i+2].decode("ascii") + ", " 125 | vk_x_separated = vk_x_separated[:-2] 126 | 127 | vk_y_separated = "" 128 | vk_y_str = vk_hex[64:128] 129 | for i in range(0, len(vk_y_str), 2): 130 | vk_y_separated += "0x" + vk_y_str[i:i+2].decode("ascii") + ", " 131 | vk_y_separated = vk_y_separated[:-2] 132 | 133 | vk_code = "static uint8_t Qx[] = {{ {0} }};\n".format(vk_x_separated) 134 | vk_code += "static uint8_t Qy[] = {{ {0} }};".format(vk_y_separated) 135 | 136 | return vk_code 137 | 138 | def get_vk_pem(self): 139 | """ 140 | Get the verification key as PEM 141 | """ 142 | if self.sk is None: 143 | raise IllegalStateException("Can't get key. No key created/loaded") 144 | 145 | vk = self.sk.get_verifying_key() 146 | vk_pem = vk.to_pem() 147 | 148 | return vk_pem 149 | -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """Package marker file.""" 30 | -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/firmwares/bar_wanted.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_nRF52_nrfutil/1361059009ff6a24d63b37eb3a4b28127837ead2/nordicsemi/dfu/tests/firmwares/bar_wanted.bin -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/firmwares/foo_wanted.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_nRF52_nrfutil/1361059009ff6a24d63b37eb3a4b28127837ead2/nordicsemi/dfu/tests/firmwares/foo_wanted.bin -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/firmwares/foobar_wanted.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_nRF52_nrfutil/1361059009ff6a24d63b37eb3a4b28127837ead2/nordicsemi/dfu/tests/firmwares/foobar_wanted.bin -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/firmwares/pca10028_nrf51422_xxac_blinky.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_nRF52_nrfutil/1361059009ff6a24d63b37eb3a4b28127837ead2/nordicsemi/dfu/tests/firmwares/pca10028_nrf51422_xxac_blinky.bin -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEID2WUBCe/4kLhl5ekJ+O8PtprcahUNFE3RIm5htQzDedoAoGCCqGSM49 3 | AwEHoUQDQgAEZY2i7duYH2l9rnIg1oIXq+0/uHAF7IoFubVru6oX9GCQm67NrXIm 4 | wgS2ErZi/0/MvRsMkIQQkNg6Wc2tbJgdTA== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/test_dfu_transport_serial.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import logging 30 | import os 31 | import unittest 32 | 33 | # Nordic Semiconductor imports 34 | import sys 35 | from nordicsemi.dfu.dfu_transport import DfuEvent 36 | from nordicsemi.dfu import crc16 37 | from nordicsemi.dfu.init_packet import PacketField, Packet 38 | from nordicsemi.dfu.model import HexType 39 | from nordicsemi.dfu.dfu_transport_serial import DfuTransportSerial 40 | 41 | 42 | def setup_logging(): 43 | root = logging.getLogger() 44 | root.setLevel(logging.DEBUG) 45 | 46 | ch = logging.StreamHandler(sys.stdout) 47 | ch.setLevel(logging.DEBUG) 48 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 49 | ch.setFormatter(formatter) 50 | root.addHandler(ch) 51 | 52 | 53 | @unittest.skip('Ignoring these tests since they take too much time to run.') 54 | class TestDfuTransportSerial(unittest.TestCase): 55 | DEVKEY_PORT = "NORDICSEMI_PCA10028_1_PORT" 56 | 57 | def setUp(self): 58 | setup_logging() 59 | 60 | # Assert that environment variables are setUp before starting tests. 61 | # TODO: create generic functionality for fetching environment variables that map 62 | # TODO: communication ports to PCA versions 63 | # TODO: setup target nRF5X device to a given state (bootloader+sd+application) 64 | if self.DEVKEY_PORT not in os.environ: 65 | self.fail("Environment variable {0} not found. " 66 | "Must specify serial port with development kit connected." 67 | .format(self.DEVKEY_PORT)) 68 | 69 | self.transport = DfuTransportSerial(os.environ[self.DEVKEY_PORT], 70 | baud_rate=38400, 71 | flow_control=True) 72 | 73 | def tearDown(self): 74 | if self.transport and self.transport.is_open(): 75 | self.transport.close() 76 | 77 | def test_open_close(self): 78 | self.transport.open() 79 | self.assertTrue(self.transport.is_open()) 80 | self.transport.close() 81 | self.assertFalse(self.transport.is_open()) 82 | 83 | def test_dfu_methods(self): 84 | def timeout_callback(log_message): 85 | logging.debug("timeout_callback. Message: %s", log_message) 86 | 87 | def progress_callback(progress, log_message, done): 88 | logging.debug("Log message: %s, Progress: %d, done: %s", log_message, progress, done) 89 | 90 | def error_callback(log_message=""): 91 | logging.error("Log message: %s", log_message) 92 | 93 | self.transport.register_events_callback(DfuEvent.TIMEOUT_EVENT, timeout_callback) 94 | self.transport.register_events_callback(DfuEvent.PROGRESS_EVENT, progress_callback) 95 | self.transport.register_events_callback(DfuEvent.ERROR_EVENT, error_callback()) 96 | 97 | firmware = '' 98 | test_firmware_path = os.path.join("firmwares", "pca10028_nrf51422_xxac_blinky.bin") 99 | 100 | with open(test_firmware_path, 'rb') as f: 101 | while True: 102 | data = f.read() 103 | 104 | if data: 105 | firmware += data 106 | else: 107 | break 108 | 109 | crc = crc16.calc_crc16(firmware, 0xffff) 110 | 111 | self.transport.open() 112 | 113 | # Sending start DFU command to target 114 | self.transport.send_start_dfu(HexType.APPLICATION, 115 | app_size=len(firmware), 116 | softdevice_size=0, 117 | bootloader_size=0) 118 | 119 | # Sending DFU init packet to target 120 | init_packet_vars = { 121 | PacketField.DEVICE_TYPE: 1, 122 | PacketField.DEVICE_REVISION: 2, 123 | PacketField.APP_VERSION: 0xfffa, 124 | PacketField.REQUIRED_SOFTDEVICES_ARRAY: [0x005a], 125 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16: crc 126 | } 127 | pkt = Packet(init_packet_vars) 128 | self.transport.send_init_packet(pkt.generate_packet()) 129 | 130 | # Sending firmware to target 131 | self.transport.send_firmware(firmware) 132 | 133 | # Validating firmware 134 | self.transport.send_validate_firmware() 135 | self.transport.send_activate_firmware() 136 | self.transport.close() 137 | -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/test_init_packet.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import unittest 30 | from nordicsemi.dfu.init_packet import * 31 | 32 | 33 | class TestInitPacket(unittest.TestCase): 34 | def setUp(self): 35 | pass 36 | 37 | def test_generate_packet_a(self): 38 | init_packet_vars = { 39 | PacketField.DEVICE_TYPE: 1, 40 | PacketField.DEVICE_REVISION: 2, 41 | PacketField.APP_VERSION: 3, 42 | PacketField.REQUIRED_SOFTDEVICES_ARRAY: [1111, 2222, 3333, 4444], 43 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID: 2, 44 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH: 45 | b'\xc9\xd3\xbfi\xf2\x1e\x88\xa01\x1e\r\xd2BSa\x12\xf8BW\x9b\xef&Z$\xbd\x02U\xfdD?u\x9e', 46 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS: 47 | b'1\xd7B8\x129\xaa\xc3\xe6\x8b\xe2\x01\xd11\x17\x01\x00\xae\x1e\x04\xf9~q\xcd\xbfv"\xdan\xc0f2\xd49' + 48 | b'\xdc\xc7\xf8\xae\x16VV\x17\x90\xa3\x96\xadxPa\x0bs\xfe\xbdi]\xb2\x95\x81\x99\xe4\xb0\xcf\xe9\xda' 49 | } 50 | 51 | ip = Packet(init_packet_vars) 52 | data = ip.generate_packet() 53 | self.assertEqual(data, (b'\x01\x00' # Device type 54 | b'\x02\x00' # Device revision 55 | b'\x03\x00\x00\x00' # App version 56 | b'\x04\x00' # Softdevice array length 57 | b'\x57\x04' # Softdevice entry #1 58 | b'\xae\x08' # Softdevice entry #2 59 | b'\x05\x0d' # Softdevice entry #3 60 | b'\x5c\x11' # Softdevice entry #4 61 | b'\x02\x00\x00\x00' # ext packet id 62 | b'\xc9\xd3\xbfi\xf2\x1e\x88\xa01\x1e\r\xd2BSa\x12' # Firmware hash, part one 63 | b'\xf8BW\x9b\xef&Z$\xbd\x02U\xfdD?u\x9e' # Firmware hash, part two 64 | b'1\xd7B8\x129\xaa\xc3\xe6\x8b\xe2\x01\xd11\x17\x01' # Init packet ECDS, part 1 65 | b'\x00\xae\x1e\x04\xf9~q\xcd\xbfv"\xdan\xc0f2\xd49' # Init packet ECDS, part 2 66 | b'\xdc\xc7\xf8\xae\x16VV\x17\x90\xa3\x96\xadxPa\x0b' # Init packet ECDS, part 3 67 | b's\xfe\xbdi]\xb2\x95\x81\x99\xe4\xb0\xcf\xe9\xda' # Init packet ECDS, part 4 68 | ) 69 | ) 70 | 71 | def test_generate_packet_b(self): 72 | init_packet_vars = { 73 | PacketField.DEVICE_TYPE: 1, 74 | PacketField.DEVICE_REVISION: 2, 75 | PacketField.APP_VERSION: 0xffeeffee, 76 | PacketField.REQUIRED_SOFTDEVICES_ARRAY: [1111, 2222, 3333], 77 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID: 1, 78 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH: 79 | b'\xc9\xd3\xbfi\xf2\x1e\x88\xa01\x1e\r\xd2BSa\x12\xf8BW\x9b\xef&Z$\xbd\x02U\xfdD?u\x9e' 80 | } 81 | 82 | ip = Packet(init_packet_vars) 83 | data = ip.generate_packet() 84 | self.assertEqual(data, (b'\x01\x00' # Device type 85 | b'\x02\x00' # Device revision 86 | b'\xee\xff\xee\xff' # App version 87 | b'\x03\x00' # Softdevice array length 88 | b'\x57\x04' # Softdevice entry #1 89 | b'\xae\x08' # Softdevice entry #2 90 | b'\x05\x0d' # Softdevice entry #3 91 | b'\x01\x00\x00\x00' # ext packet id 92 | b'\xc9\xd3\xbfi\xf2\x1e\x88\xa01\x1e\r\xd2BSa\x12' # Firmware hash, part one 93 | b'\xf8BW\x9b\xef&Z$\xbd\x02U\xfdD?u\x9e' # Firmware hash, part two 94 | ) 95 | ) 96 | 97 | def test_generate_packet_c(self): 98 | init_packet_vars = { 99 | PacketField.DEVICE_TYPE: 1, 100 | PacketField.DEVICE_REVISION: 2, 101 | PacketField.APP_VERSION: 0xffeeffee, 102 | PacketField.REQUIRED_SOFTDEVICES_ARRAY: [1111, 2222, 3333], 103 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID: 0, 104 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16: 0xfaae 105 | } 106 | 107 | ip = Packet(init_packet_vars) 108 | data = ip.generate_packet() 109 | self.assertEqual(data, (b'\x01\x00' # Device type 110 | b'\x02\x00' # Device revision 111 | b'\xee\xff\xee\xff' # App version 112 | b'\x03\x00' # Softdevice array length 113 | b'\x57\x04' # Softdevice entry #1 114 | b'\xae\x08' # Softdevice entry #2 115 | b'\x05\x0d' # Softdevice entry #3 116 | b'\x00\x00\x00\x00' # ext packet id 117 | b'\xae\xfa' # CRC-16 checksum for firmware 118 | ) 119 | ) 120 | 121 | 122 | if __name__ == '__main__': 123 | unittest.main() 124 | -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/test_manifest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import copy 30 | import json 31 | import unittest 32 | 33 | from nordicsemi.dfu.init_packet import PacketField 34 | from nordicsemi.dfu.manifest import ManifestGenerator, Manifest 35 | from nordicsemi.dfu.model import HexType 36 | from nordicsemi.dfu.package import FirmwareKeys 37 | 38 | 39 | class TestManifest(unittest.TestCase): 40 | def setUp(self): 41 | self.firmwares_data_a = {} 42 | 43 | init_packet_data_a = { 44 | PacketField.DEVICE_TYPE: 1, 45 | PacketField.DEVICE_REVISION: 2, 46 | PacketField.APP_VERSION: 1000, 47 | PacketField.REQUIRED_SOFTDEVICES_ARRAY: [22, 11], 48 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID: 2, 49 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH: 1234, 50 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH: 51 | b'\xc9\xd3\xbfi\xf2\x1e\x88\xa01\x1e\r\xd2BSa\x12\xf8BW\x9b\xef&Z$\xbd\x02U\xfdD?u\x9e', 52 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS: 53 | b'1\xd7B8\x129\xaa\xc3\xe6\x8b\xe2\x01\xd11\x17\x01\x00\xae\x1e\x04\xf9~q\xcd\xbfv"\xdan\xc0f2\xd49' + 54 | b'\xdc\xc7\xf8\xae\x16VV\x17\x90\xa3\x96\xadxPa\x0bs\xfe\xbdi]\xb2\x95\x81\x99\xe4\xb0\xcf\xe9\xda' 55 | } 56 | 57 | self.firmwares_data_a[HexType.APPLICATION] = { 58 | FirmwareKeys.BIN_FILENAME: "app_fw.bin", 59 | FirmwareKeys.DAT_FILENAME: "app_fw.dat", 60 | FirmwareKeys.INIT_PACKET_DATA: init_packet_data_a, 61 | FirmwareKeys.ENCRYPT: False} 62 | 63 | self.firmwares_data_a[HexType.SD_BL] = { 64 | FirmwareKeys.BIN_FILENAME: "sd_bl_fw.bin", 65 | FirmwareKeys.DAT_FILENAME: "sd_bl_fw.dat", 66 | FirmwareKeys.INIT_PACKET_DATA: copy.copy(init_packet_data_a), # Fake the hash 67 | FirmwareKeys.BL_SIZE: 50, 68 | FirmwareKeys.SD_SIZE: 90 69 | } 70 | 71 | self.firmwares_data_b = {} 72 | 73 | init_packet_data_b = { 74 | PacketField.DEVICE_TYPE: 1, 75 | PacketField.DEVICE_REVISION: 2, 76 | PacketField.APP_VERSION: 1000, 77 | PacketField.REQUIRED_SOFTDEVICES_ARRAY: [22, 11], 78 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16: 0xfaae 79 | } 80 | 81 | self.firmwares_data_b[HexType.APPLICATION] = { 82 | FirmwareKeys.BIN_FILENAME: "app_fw.bin", 83 | FirmwareKeys.DAT_FILENAME: "app_fw.dat", 84 | FirmwareKeys.INIT_PACKET_DATA: init_packet_data_b 85 | } 86 | 87 | self.firmwares_data_b[HexType.BOOTLOADER] = { 88 | FirmwareKeys.BIN_FILENAME: "bootloader_fw.bin", 89 | FirmwareKeys.DAT_FILENAME: "bootloader_fw.dat", 90 | FirmwareKeys.INIT_PACKET_DATA: copy.copy(init_packet_data_b), # Fake the hash 91 | } 92 | 93 | self.firmwares_data_c = {} 94 | 95 | init_packet_data_c = { 96 | PacketField.DEVICE_TYPE: 1, 97 | PacketField.DEVICE_REVISION: 2, 98 | PacketField.APP_VERSION: 1000, 99 | PacketField.REQUIRED_SOFTDEVICES_ARRAY: [22, 11], 100 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_CRC16: 0xfaae 101 | } 102 | 103 | self.firmwares_data_c[HexType.SOFTDEVICE] = { 104 | FirmwareKeys.BIN_FILENAME: "softdevice_fw.bin", 105 | FirmwareKeys.DAT_FILENAME: "softdevice_fw.dat", 106 | FirmwareKeys.INIT_PACKET_DATA: init_packet_data_c 107 | } 108 | 109 | def test_generate_manifest(self): 110 | r = ManifestGenerator(0.5, self.firmwares_data_a) 111 | 112 | _json = json.loads(r.generate_manifest()) 113 | 114 | # Test for presence of attributes in document 115 | self.assertIn('manifest', _json) 116 | 117 | manifest = _json['manifest'] 118 | self.assertIn('application', manifest) 119 | 120 | application = manifest['application'] 121 | self.assertIn('init_packet_data', application) 122 | self.assertIn('dat_file', application) 123 | self.assertIn('bin_file', application) 124 | 125 | init_packet_data = application['init_packet_data'] 126 | self.assertIn('firmware_hash', init_packet_data) 127 | self.assertIn('softdevice_req', init_packet_data) 128 | self.assertIn('device_revision', init_packet_data) 129 | self.assertIn('device_type', init_packet_data) 130 | self.assertIn('application_version', init_packet_data) 131 | 132 | # Test for values in document 133 | self.assertEqual("app_fw.bin", application['bin_file']) 134 | self.assertEqual("app_fw.dat", application['dat_file']) 135 | 136 | self.assertEqual(2, init_packet_data['ext_packet_id']) 137 | self.assertEqual(1234, init_packet_data['firmware_length']) 138 | self.assertEqual('c9d3bf69f21e88a0311e0dd242536112f842579bef265a24bd0255fd443f759e', 139 | init_packet_data['firmware_hash']) 140 | self.assertEqual('31d742381239aac3e68be201d131170100ae1e04f97e71cdbf7622da6ec06632d439dcc7f8ae1656561790a396ad7850610b73febd695db2958199e4b0cfe9da', 141 | init_packet_data['init_packet_ecds']) 142 | self.assertEqual(1000, init_packet_data['application_version']) 143 | self.assertEqual(1, init_packet_data['device_type']) 144 | self.assertEqual(2, init_packet_data['device_revision']) 145 | self.assertEqual([22, 11], init_packet_data['softdevice_req']) 146 | 147 | # Test softdevice_bootloader 148 | bl_sd = manifest['softdevice_bootloader'] 149 | self.assertIsNotNone(bl_sd) 150 | self.assertEqual(90, bl_sd['sd_size']) 151 | self.assertEqual(50, bl_sd['bl_size']) 152 | 153 | # Test for values in document 154 | self.assertEqual("sd_bl_fw.bin", bl_sd['bin_file']) 155 | self.assertEqual("sd_bl_fw.dat", bl_sd['dat_file']) 156 | 157 | def test_manifest_a(self): 158 | r = ManifestGenerator(0.5, self.firmwares_data_a) 159 | m = Manifest.from_json(r.generate_manifest()) 160 | self.assertIsNotNone(m) 161 | self.assertIsNotNone(m.application) 162 | self.assertEqual("app_fw.bin", m.application.bin_file) 163 | self.assertEqual("app_fw.dat", m.application.dat_file) 164 | self.assertIsNone(m.bootloader) 165 | self.assertIsNone(m.softdevice) 166 | self.assertIsNotNone(m.softdevice_bootloader) 167 | self.assertEqual(90, m.softdevice_bootloader.sd_size) 168 | self.assertEqual(50, m.softdevice_bootloader.bl_size) 169 | self.assertEqual("sd_bl_fw.bin", m.softdevice_bootloader.bin_file) 170 | self.assertEqual("sd_bl_fw.dat", m.softdevice_bootloader.dat_file) 171 | 172 | def test_manifest_b(self): 173 | r = ManifestGenerator("0.5", self.firmwares_data_b) 174 | m = Manifest.from_json(r.generate_manifest()) 175 | self.assertIsNotNone(m) 176 | self.assertIsNotNone(m.application) 177 | self.assertEqual("app_fw.bin", m.application.bin_file) 178 | self.assertEqual("app_fw.dat", m.application.dat_file) 179 | self.assertIsNotNone(m.bootloader) 180 | self.assertEqual("bootloader_fw.bin", m.bootloader.bin_file) 181 | self.assertEqual("bootloader_fw.dat", m.bootloader.dat_file) 182 | self.assertIsNone(m.softdevice) 183 | self.assertIsNone(m.softdevice_bootloader) 184 | self.assertEqual(0xfaae, m.application.init_packet_data.firmware_crc16) 185 | self.assertEqual(0xfaae, m.bootloader.init_packet_data.firmware_crc16) 186 | 187 | 188 | def test_manifest_c(self): 189 | r = ManifestGenerator("0.5", self.firmwares_data_c) 190 | m = Manifest.from_json(r.generate_manifest()) 191 | self.assertIsNotNone(m) 192 | self.assertIsNone(m.application) 193 | self.assertIsNone(m.bootloader) 194 | self.assertIsNotNone(m.softdevice) 195 | self.assertEqual('softdevice_fw.bin', m.softdevice.bin_file) 196 | self.assertEqual('softdevice_fw.dat', m.softdevice.dat_file) 197 | self.assertIsNone(m.softdevice_bootloader) 198 | self.assertEqual(0xfaae, m.softdevice.init_packet_data.firmware_crc16) 199 | 200 | if __name__ == '__main__': 201 | unittest.main() 202 | -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/test_nrfhex.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import os 30 | 31 | import unittest 32 | import nordicsemi.dfu.nrfhex as nrfhex 33 | import nordicsemi.dfu.intelhex as intelhex 34 | 35 | 36 | class TestnRFHex(unittest.TestCase): 37 | def setUp(self): 38 | script_abspath = os.path.abspath(__file__) 39 | script_dirname = os.path.dirname(script_abspath) 40 | os.chdir(script_dirname) 41 | 42 | def comparefiles(self, actual, wanted): 43 | actualfile = intelhex.IntelHex() 44 | actualfile.loadfile(actual, format="bin") 45 | 46 | wantedfile = intelhex.IntelHex() 47 | wantedfile.loadfile(wanted, format="bin") 48 | 49 | self.assertEqual(actualfile.minaddr(), wantedfile.minaddr()) 50 | self.assertEqual(actualfile.maxaddr(), wantedfile.maxaddr()) 51 | 52 | minaddress = actualfile.minaddr() 53 | maxaddress = actualfile.maxaddr() 54 | 55 | length = maxaddress - minaddress 56 | 57 | actualfile_data = actualfile.gets(minaddress, length) 58 | wantedfile_data = wantedfile.gets(minaddress, length) 59 | 60 | self.assertEqual(actualfile_data, wantedfile_data) 61 | 62 | def test_tobinfile_single_file_without_uicr_content(self): 63 | nrf = nrfhex.nRFHex("firmwares/bar.hex") 64 | nrf.tobinfile("firmwares/bar.bin") 65 | 66 | self.comparefiles("firmwares/bar.bin", "firmwares/bar_wanted.bin") 67 | 68 | def test_tobinfile_single_file_with_uicr_content(self): 69 | nrf = nrfhex.nRFHex("firmwares/foo.hex") 70 | nrf.tobinfile("firmwares/foo.bin") 71 | 72 | self.comparefiles("firmwares/foo.bin", "firmwares/foo_wanted.bin") 73 | 74 | def test_tobinfile_single_bin_file(self): 75 | nrf = nrfhex.nRFHex("firmwares/bar_wanted.bin") 76 | nrf.tobinfile("firmwares/bar.bin") 77 | 78 | self.comparefiles("firmwares/bar.bin", "firmwares/bar_wanted.bin") 79 | 80 | def test_tobinfile_two_hex_files(self): 81 | nrf = nrfhex.nRFHex("firmwares/foo.hex", "firmwares/bar.hex") 82 | nrf.tobinfile("firmwares/foobar.bin") 83 | 84 | self.comparefiles("firmwares/foobar.bin", "firmwares/foobar_wanted.bin") 85 | 86 | def test_tobinfile_one_hex_one_bin(self): 87 | nrf = nrfhex.nRFHex("firmwares/foo_wanted.bin", "firmwares/bar.hex") 88 | nrf.tobinfile("firmwares/foobar.bin") 89 | 90 | self.comparefiles("firmwares/foobar.bin", "firmwares/foobar_wanted.bin") 91 | 92 | def test_tobinfile_one_bin_one_hex(self): 93 | nrf = nrfhex.nRFHex("firmwares/foo.hex", "firmwares/bar_wanted.bin") 94 | nrf.tobinfile("firmwares/foobar.bin") 95 | 96 | self.comparefiles("firmwares/foobar.bin", "firmwares/foobar_wanted.bin") 97 | 98 | def test_tobinfile_two_bin(self): 99 | nrf = nrfhex.nRFHex("firmwares/foo_wanted.bin", "firmwares/bar_wanted.bin") 100 | nrf.tobinfile("firmwares/foobar.bin") 101 | 102 | self.comparefiles("firmwares/foobar.bin", "firmwares/foobar_wanted.bin") 103 | 104 | def test_sizes(self): 105 | nrf = nrfhex.nRFHex("firmwares/foo.hex", "firmwares/bar.hex") 106 | 107 | self.assertEqual(nrf.get_mbr_end_address(), 0x1000) 108 | self.assertEqual(nrf.minaddr(), 0x1000) 109 | self.assertEqual(nrf.size(), 73152) 110 | self.assertEqual(nrf.bootloadersize(), 13192) 111 | 112 | nrf = nrfhex.nRFHex("firmwares/s132_nrf52_mini.hex") 113 | 114 | self.assertEqual(nrf.get_mbr_end_address(), 0x3000) 115 | self.assertEqual(nrf.minaddr(), 0x3000) 116 | self.assertEqual(nrf.size(), 12288) 117 | self.assertEqual(nrf.bootloadersize(), 0) 118 | 119 | def test_get_softdevice_variant(self): 120 | nrf = nrfhex.nRFHex("firmwares/foo.hex") 121 | 122 | self.assertEqual(nrf.get_softdevice_variant(), "unknown") 123 | 124 | nrf = nrfhex.nRFHex("firmwares/s130_nrf51_mini.hex") 125 | 126 | self.assertEqual(nrf.get_softdevice_variant(), "s1x0") 127 | 128 | nrf = nrfhex.nRFHex("firmwares/s132_nrf52_mini.hex") 129 | 130 | self.assertEqual(nrf.get_softdevice_variant(), "s132") 131 | 132 | 133 | if __name__ == '__main__': 134 | unittest.main() 135 | -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/test_package.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import json 30 | import os 31 | import tempfile 32 | import unittest 33 | from zipfile import ZipFile 34 | import shutil 35 | 36 | from nordicsemi.dfu.package import Package 37 | 38 | 39 | class TestPackage(unittest.TestCase): 40 | def setUp(self): 41 | self.work_directory = tempfile.mkdtemp(prefix="nrf_dfu_tests_") 42 | 43 | def tearDown(self): 44 | shutil.rmtree(self.work_directory, ignore_errors=True) 45 | 46 | def test_generate_package_application(self): 47 | self.p = Package( 48 | dev_type=1, 49 | dev_rev=2, 50 | app_version=100, 51 | sd_req=[0x1000, 0xfffe], 52 | app_fw="firmwares/bar.hex" 53 | ) 54 | 55 | pkg_name = "mypackage.zip" 56 | 57 | self.p.generate_package(pkg_name, preserve_work_directory=False) 58 | expected_zip_content = ["manifest.json", "bar.bin", "bar.dat"] 59 | 60 | with ZipFile(pkg_name, 'r') as pkg: 61 | infolist = pkg.infolist() 62 | 63 | for file_information in infolist: 64 | self.assertTrue(file_information.filename in expected_zip_content) 65 | self.assertGreater(file_information.file_size, 0) 66 | 67 | # Extract all and load json document to see if it is correct regarding to paths 68 | pkg.extractall(self.work_directory) 69 | 70 | with open(os.path.join(self.work_directory, 'manifest.json'), 'r') as f: 71 | _json = json.load(f) 72 | self.assertEqual('bar.bin', _json['manifest']['application']['bin_file']) 73 | self.assertEqual('bar.dat', _json['manifest']['application']['dat_file']) 74 | self.assertTrue('softdevice' not in _json['manifest']) 75 | self.assertTrue('softdevice_bootloader' not in _json['manifest']) 76 | self.assertTrue('bootloader' not in _json['manifest']) 77 | 78 | def test_generate_package_sd_bl(self): 79 | self.p = Package(dev_type=1, 80 | dev_rev=2, 81 | app_version=100, 82 | sd_req=[0x1000, 0xfffe], 83 | softdevice_fw="firmwares/foo.hex", 84 | bootloader_fw="firmwares/bar.hex") 85 | 86 | pkg_name = "mypackage.zip" 87 | 88 | self.p.generate_package(pkg_name, preserve_work_directory=False) 89 | 90 | expected_zip_content = ["manifest.json", "sd_bl.bin", "sd_bl.dat"] 91 | 92 | with ZipFile(pkg_name, 'r') as pkg: 93 | infolist = pkg.infolist() 94 | 95 | for file_information in infolist: 96 | self.assertTrue(file_information.filename in expected_zip_content) 97 | self.assertGreater(file_information.file_size, 0) 98 | 99 | # Extract all and load json document to see if it is correct regarding to paths 100 | pkg.extractall(self.work_directory) 101 | 102 | with open(os.path.join(self.work_directory, 'manifest.json'), 'r') as f: 103 | _json = json.load(f) 104 | self.assertEqual('sd_bl.bin', _json['manifest']['softdevice_bootloader']['bin_file']) 105 | self.assertEqual('sd_bl.dat', _json['manifest']['softdevice_bootloader']['dat_file']) 106 | 107 | def test_unpack_package_a(self): 108 | self.p = Package(dev_type=1, 109 | dev_rev=2, 110 | app_version=100, 111 | sd_req=[0x1000, 0xffff], 112 | softdevice_fw="firmwares/bar.hex", 113 | dfu_ver=0.6) 114 | pkg_name = os.path.join(self.work_directory, "mypackage.zip") 115 | self.p.generate_package(pkg_name, preserve_work_directory=False) 116 | 117 | unpacked_dir = os.path.join(self.work_directory, "unpacked") 118 | manifest = self.p.unpack_package(os.path.join(self.work_directory, pkg_name), unpacked_dir) 119 | self.assertIsNotNone(manifest) 120 | self.assertEqual('bar.bin', manifest.softdevice.bin_file) 121 | self.assertEqual(0, manifest.softdevice.init_packet_data.ext_packet_id) 122 | self.assertIsNotNone(manifest.softdevice.init_packet_data.firmware_crc16) 123 | 124 | def test_unpack_package_b(self): 125 | self.p = Package(dev_type=1, 126 | dev_rev=2, 127 | app_version=100, 128 | sd_req=[0x1000, 0xffff], 129 | softdevice_fw="firmwares/bar.hex", 130 | dfu_ver=0.7) 131 | pkg_name = os.path.join(self.work_directory, "mypackage.zip") 132 | self.p.generate_package(pkg_name, preserve_work_directory=False) 133 | 134 | unpacked_dir = os.path.join(self.work_directory, "unpacked") 135 | manifest = self.p.unpack_package(os.path.join(self.work_directory, pkg_name), unpacked_dir) 136 | self.assertIsNotNone(manifest) 137 | self.assertEqual('bar.bin', manifest.softdevice.bin_file) 138 | self.assertEqual(1, manifest.softdevice.init_packet_data.ext_packet_id) 139 | self.assertIsNone(manifest.softdevice.init_packet_data.firmware_crc16) 140 | self.assertIsNotNone(manifest.softdevice.init_packet_data.firmware_hash) 141 | 142 | def test_unpack_package_c(self): 143 | self.p = Package(dev_type=1, 144 | dev_rev=2, 145 | app_version=100, 146 | sd_req=[0x1000, 0xffff], 147 | softdevice_fw="firmwares/bar.hex", 148 | key_file="key.pem") 149 | pkg_name = os.path.join(self.work_directory, "mypackage.zip") 150 | self.p.generate_package(pkg_name, preserve_work_directory=False) 151 | 152 | unpacked_dir = os.path.join(self.work_directory, "unpacked") 153 | manifest = self.p.unpack_package(os.path.join(self.work_directory, pkg_name), unpacked_dir) 154 | self.assertIsNotNone(manifest) 155 | self.assertEqual('bar.bin', manifest.softdevice.bin_file) 156 | self.assertEqual(2, manifest.softdevice.init_packet_data.ext_packet_id) 157 | self.assertIsNone(manifest.softdevice.init_packet_data.firmware_crc16) 158 | self.assertIsNotNone(manifest.softdevice.init_packet_data.firmware_hash) 159 | self.assertIsNotNone(manifest.softdevice.init_packet_data.init_packet_ecds) 160 | self.assertEqual(manifest.dfu_version, 0.8) 161 | 162 | 163 | if __name__ == '__main__': 164 | unittest.main() 165 | -------------------------------------------------------------------------------- /nordicsemi/dfu/tests/test_signing.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import binascii 30 | import os 31 | import shutil 32 | import tempfile 33 | import unittest 34 | 35 | from nordicsemi.dfu.signing import Signing 36 | from nordicsemi.dfu.init_packet import Packet, PacketField 37 | 38 | 39 | class TestSinging(unittest.TestCase): 40 | def setUp(self): 41 | script_abspath = os.path.abspath(__file__) 42 | script_dirname = os.path.dirname(script_abspath) 43 | os.chdir(script_dirname) 44 | 45 | def test_gen_key(self): 46 | self.work_directory = tempfile.mkdtemp(prefix="nrf_signing_tests_") 47 | 48 | key_file_name = 'key.pem' 49 | key_file_path = os.path.join(self.work_directory, key_file_name) 50 | 51 | signing = Signing() 52 | signing.gen_key(key_file_path) 53 | 54 | self.assertTrue(os.path.exists(key_file_path)) 55 | 56 | shutil.rmtree(self.work_directory, ignore_errors=True) 57 | 58 | def test_load_key(self): 59 | key_file_name = 'key.pem' 60 | 61 | signing = Signing() 62 | signing.load_key(key_file_name) 63 | 64 | self.assertEqual(64, len(binascii.hexlify(signing.sk.to_string()))) 65 | 66 | def test_sign_and_verify(self): 67 | key_file_name = 'key.pem' 68 | 69 | signing = Signing() 70 | signing.load_key(key_file_name) 71 | 72 | init_packet_fields = { 73 | PacketField.DEVICE_TYPE: 0xFFFF, 74 | PacketField.DEVICE_REVISION: 0xFFFF, 75 | PacketField.APP_VERSION: 0xFFFFFFFF, 76 | PacketField.REQUIRED_SOFTDEVICES_ARRAY: [0xFFFE], 77 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_EXT_PACKET_ID: 2, 78 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_LENGTH: 1234, 79 | PacketField.NORDIC_PROPRIETARY_OPT_DATA_FIRMWARE_HASH: 80 | b'\xc9\xd3\xbfi\xf2\x1e\x88\xa01\x1e\r\xd2BSa\x12\xf8BW\x9b\xef&Z$\xbd\x02U\xfdD?u\x9e', 81 | } 82 | init_packet = Packet(init_packet_fields) 83 | init_packet_data = init_packet.generate_packet() 84 | 85 | signature = signing.sign(init_packet_data) 86 | 87 | self.assertTrue(signing.verify(init_packet_data, signature)) 88 | 89 | init_packet_fields[PacketField.NORDIC_PROPRIETARY_OPT_DATA_INIT_PACKET_ECDS] = signature 90 | 91 | init_packet = Packet(init_packet_fields) 92 | init_packet_data = init_packet.generate_packet() 93 | 94 | self.assertFalse(signing.verify(init_packet_data, signature)) 95 | 96 | def test_get_vk(self): 97 | key_file_name = 'key.pem' 98 | 99 | signing = Signing() 100 | signing.load_key(key_file_name) 101 | 102 | vk_str = signing.get_vk('hex') 103 | vk_hex = signing.get_vk_hex() 104 | self.assertEqual(vk_hex, vk_str) 105 | 106 | vk_str = signing.get_vk('code') 107 | vk_code = signing.get_vk_code() 108 | self.assertEqual(vk_code, vk_str) 109 | 110 | vk_str = signing.get_vk('pem') 111 | vk_pem = signing.get_vk_pem() 112 | self.assertEqual(vk_pem, vk_str) 113 | 114 | def test_get_vk_hex(self): 115 | key_file_name = 'key.pem' 116 | expected_vk_hex = "Verification key Qx: 658da2eddb981f697dae7220d68217abed3fb87005ec8a05b9b56bbbaa17f460\n" \ 117 | "Verification key Qy: 909baecdad7226c204b612b662ff4fccbd1b0c90841090d83a59cdad6c981d4c" 118 | 119 | signing = Signing() 120 | signing.load_key(key_file_name) 121 | 122 | vk_hex = signing.get_vk_hex() 123 | 124 | self.assertEqual(expected_vk_hex, vk_hex) 125 | 126 | def test_get_vk_code(self): 127 | key_file_name = 'key.pem' 128 | 129 | expected_vk_code = "static uint8_t Qx[] = { 0x65, 0x8d, 0xa2, 0xed, 0xdb, 0x98, 0x1f, 0x69, 0x7d, " \ 130 | "0xae, 0x72, 0x20, 0xd6, 0x82, 0x17, 0xab, 0xed, 0x3f, 0xb8, 0x70, 0x05, 0xec, " \ 131 | "0x8a, 0x05, 0xb9, 0xb5, 0x6b, 0xbb, 0xaa, 0x17, 0xf4, 0x60 };\n" \ 132 | "static uint8_t Qy[] = { 0x90, 0x9b, 0xae, 0xcd, 0xad, 0x72, 0x26, 0xc2, 0x04, " \ 133 | "0xb6, 0x12, 0xb6, 0x62, 0xff, 0x4f, 0xcc, 0xbd, 0x1b, 0x0c, 0x90, 0x84, 0x10, " \ 134 | "0x90, 0xd8, 0x3a, 0x59, 0xcd, 0xad, 0x6c, 0x98, 0x1d, 0x4c };" 135 | 136 | signing = Signing() 137 | signing.load_key(key_file_name) 138 | 139 | vk_code = signing.get_vk_code() 140 | 141 | self.assertEqual(expected_vk_code, vk_code) 142 | 143 | def test_get_vk_pem(self): 144 | key_file_name = 'key.pem' 145 | expected_vk_pem = "-----BEGIN PUBLIC KEY-----\n" \ 146 | "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEZY2i7duYH2l9rnIg1oIXq+0/uHAF\n" \ 147 | "7IoFubVru6oX9GCQm67NrXImwgS2ErZi/0/MvRsMkIQQkNg6Wc2tbJgdTA==\n" \ 148 | "-----END PUBLIC KEY-----\n" 149 | 150 | signing = Signing() 151 | signing.load_key(key_file_name) 152 | 153 | vk_pem = signing.get_vk_pem().decode("ascii") 154 | 155 | self.assertEqual(expected_vk_pem, vk_pem) 156 | -------------------------------------------------------------------------------- /nordicsemi/dfu/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | # Nordic libraries 30 | from nordicsemi.exceptions import NordicSemiException 31 | 32 | 33 | # TODO: Create query function that maps query-result strings with functions 34 | def query_func(question, default=False): 35 | """ 36 | Ask a string question 37 | No input defaults to "no" which results in False 38 | """ 39 | valid = {"yes": True, "y": True, "no": False, "n": False} 40 | if default is True: 41 | prompt = " [Y/n]" 42 | else: 43 | prompt = " [y/N]" 44 | 45 | while True: 46 | print("%s %s" % (question, prompt)) 47 | choice = input().lower() 48 | if choice == '': 49 | return default 50 | elif choice in valid: 51 | return valid[choice] 52 | else: 53 | print("Please respond with y/n") 54 | 55 | 56 | def convert_uint16_to_array(value): 57 | """ 58 | Converts a int to an array of 2 bytes (little endian) 59 | 60 | :param int value: int value to convert to list 61 | :return list[int]: list with 2 bytes 62 | """ 63 | byte0 = value & 0xFF 64 | byte1 = (value >> 8) & 0xFF 65 | return [byte0, byte1] 66 | 67 | 68 | def convert_uint32_to_array(value): 69 | """ 70 | Converts a int to an array of 4 bytes (little endian) 71 | 72 | :param int value: int value to convert to list 73 | :return list[int]: list with 4 bytes 74 | """ 75 | byte0 = value & 0xFF 76 | byte1 = (value >> 8) & 0xFF 77 | byte2 = (value >> 16) & 0xFF 78 | byte3 = (value >> 24) & 0xFF 79 | return [byte0, byte1, byte2, byte3] 80 | 81 | 82 | def slip_parts_to_four_bytes(seq, dip, rp, pkt_type, pkt_len): 83 | """ 84 | Creates a SLIP header. 85 | 86 | For a description of the SLIP header go to: 87 | http://developer.nordicsemi.com/nRF51_SDK/doc/7.2.0/s110/html/a00093.html 88 | 89 | :param int seq: Packet sequence number 90 | :param int dip: Data integrity check 91 | :param int rp: Reliable packet 92 | :param pkt_type: Payload packet 93 | :param pkt_len: Packet length 94 | :return: str with SLIP header 95 | """ 96 | ints = [0, 0, 0, 0] 97 | ints[0] = seq | (((seq + 1) % 8) << 3) | (dip << 6) | (rp << 7) 98 | ints[1] = pkt_type | ((pkt_len & 0x000F) << 4) 99 | ints[2] = (pkt_len & 0x0FF0) >> 4 100 | ints[3] = (~(sum(ints[0:3])) + 1) & 0xFF 101 | 102 | return ''.join(chr(b) for b in ints) 103 | 104 | 105 | def int32_to_bytes(value): 106 | """ 107 | Converts a int to a str with 4 bytes 108 | 109 | :param value: int value to convert 110 | :return: str with 4 bytes 111 | """ 112 | ints = [0, 0, 0, 0] 113 | ints[0] = (value & 0x000000FF) 114 | ints[1] = (value & 0x0000FF00) >> 8 115 | ints[2] = (value & 0x00FF0000) >> 16 116 | ints[3] = (value & 0xFF000000) >> 24 117 | return ''.join(chr(b) for b in ints) 118 | 119 | 120 | def int16_to_bytes(value): 121 | """ 122 | Converts a int to a str with 4 bytes 123 | 124 | :param value: int value to convert 125 | :return: str with 4 bytes 126 | """ 127 | 128 | ints = [0, 0] 129 | ints[0] = (value & 0x00FF) 130 | ints[1] = (value & 0xFF00) >> 8 131 | return ''.join(chr(b) for b in ints) 132 | 133 | 134 | def slip_decode_esc_chars(data): 135 | """Decode esc characters in a SLIP package. 136 | 137 | Replaces 0xDBDC with 0xCO and 0xDBDD with 0xDB. 138 | 139 | :return: str decoded data 140 | :type str data: data to decode 141 | """ 142 | result = [] 143 | while len(data): 144 | char = data.pop(0) 145 | if char == 0xDB: 146 | char2 = data.pop(0) 147 | if char2 == 0xDC: 148 | result.append(0xC0) 149 | elif char2 == 0xDD: 150 | result.append(0xDB) 151 | else: 152 | raise NordicSemiException('Char 0xDB NOT followed by 0xDC or 0xDD') 153 | else: 154 | result.append(char) 155 | return result 156 | 157 | 158 | def slip_encode_esc_chars(data_in): 159 | """Encode esc characters in a SLIP package. 160 | 161 | Replace 0xCO with 0xDBDC and 0xDB with 0xDBDD. 162 | 163 | :type str data_in: str to encode 164 | :return: str with encoded packet 165 | """ 166 | result = [] 167 | data = [] 168 | for i in data_in: 169 | data.append(ord(i)) 170 | 171 | while len(data): 172 | char = data.pop(0) 173 | if char == 0xC0: 174 | result.extend([0xDB, 0xDC]) 175 | elif char == 0xDB: 176 | result.extend([0xDB, 0xDD]) 177 | else: 178 | result.append(char) 179 | return ''.join(chr(i) for i in result) 180 | -------------------------------------------------------------------------------- /nordicsemi/exceptions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | class NordicSemiException(Exception): 30 | """ 31 | Exception used as based exception for other exceptions defined in this package. 32 | """ 33 | pass 34 | 35 | 36 | class NotImplementedException(NordicSemiException): 37 | """ 38 | Exception used when functionality has not been implemented yet. 39 | """ 40 | pass 41 | 42 | 43 | class InvalidArgumentException(NordicSemiException): 44 | """" 45 | Exception used when a argument is of wrong type 46 | """ 47 | pass 48 | 49 | class MissingArgumentException(NordicSemiException): 50 | """" 51 | Exception used when a argument is missing 52 | """ 53 | pass 54 | 55 | 56 | class IllegalStateException(NordicSemiException): 57 | """" 58 | Exception used when program is in an illegal state 59 | """ 60 | pass 61 | -------------------------------------------------------------------------------- /nordicsemi/utility/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.s 28 | 29 | """Package marker file.""" 30 | -------------------------------------------------------------------------------- /nordicsemi/utility/target_registry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import re 30 | import os 31 | import json 32 | from abc import ABCMeta, abstractmethod 33 | 34 | 35 | class TargetDatabase(object, metaclass=ABCMeta): 36 | @abstractmethod 37 | def get_targets(self): 38 | pass 39 | 40 | @abstractmethod 41 | def get_target(self, target_id): 42 | pass 43 | 44 | @abstractmethod 45 | def refresh(self): 46 | pass 47 | 48 | @staticmethod 49 | def find_target(targets, target_id): 50 | for target in targets: 51 | if target["id"] == target_id: 52 | return target 53 | 54 | return None 55 | 56 | 57 | class EnvTargetDatabase(TargetDatabase): 58 | def __init__(self): 59 | self.targets = None 60 | 61 | def get_targets(self): 62 | if self.targets is None: 63 | self.targets = [] 64 | 65 | for key, value in os.environ.items(): 66 | match = re.match("NORDICSEMI_TARGET_(?P\d+)_(?P[a-zA-Z_]+)", key) 67 | 68 | if match: 69 | key_value = match.groupdict() 70 | if "key" in key_value and "target" in key_value: 71 | target_id = int(key_value["target"]) 72 | 73 | target = self.find_target(self.targets, target_id) 74 | 75 | if target is None: 76 | target = {"id": int(target_id)} 77 | self.targets.append(target) 78 | 79 | target[key_value["key"].lower()] = value 80 | 81 | return self.targets 82 | 83 | def refresh(self): 84 | self.targets = None 85 | 86 | def get_target(self, target_id): 87 | return self.find_target(self.get_targets(), target_id) 88 | 89 | 90 | class FileTargetDatabase(TargetDatabase): 91 | def __init__(self, filename): 92 | self.filename = filename 93 | self.targets = None 94 | 95 | def get_targets(self): 96 | if not self.targets: 97 | self.targets = json.load(open(self.filename, "r"))["targets"] 98 | 99 | return self.targets 100 | 101 | def get_target(self, target_id): 102 | return self.find_target(self.get_targets(), target_id) 103 | 104 | def refresh(self): 105 | self.targets = None 106 | 107 | 108 | class TargetRegistry(object): 109 | def __init__(self, target_db=EnvTargetDatabase()): 110 | self.target_db = target_db 111 | 112 | def find_one(self, target_id=None): 113 | if target_id: 114 | return self.target_db.get_target(target_id) 115 | else: 116 | return None 117 | 118 | def get_all(self): 119 | return self.target_db.get_targets() 120 | -------------------------------------------------------------------------------- /nordicsemi/utility/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | """Package marker file.""" 30 | -------------------------------------------------------------------------------- /nordicsemi/utility/tests/test_target_registry.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import os 30 | import unittest 31 | from nordicsemi.utility.target_registry import TargetRegistry, EnvTargetDatabase 32 | from nordicsemi.utility.target_registry import FileTargetDatabase 33 | 34 | 35 | class TestTargetRegistry(unittest.TestCase): 36 | def setUp(self): 37 | script_abspath = os.path.abspath(__file__) 38 | script_dirname = os.path.dirname(script_abspath) 39 | os.chdir(script_dirname) 40 | 41 | # Setup the environment variables 42 | os.environ["NORDICSEMI_TARGET_1_SERIAL_PORT"] = "COM1" 43 | os.environ["NORDICSEMI_TARGET_1_PCA"] = "PCA10028" 44 | os.environ["NORDICSEMI_TARGET_1_DRIVE"] = "D:\\" 45 | os.environ["NORDICSEMI_TARGET_1_SEGGER_SN"] = "1231233333" 46 | 47 | os.environ["NORDICSEMI_TARGET_2_SERIAL_PORT"] = "COM2" 48 | os.environ["NORDICSEMI_TARGET_2_PCA"] = "PCA10028" 49 | os.environ["NORDICSEMI_TARGET_2_DRIVE"] = "E:\\" 50 | os.environ["NORDICSEMI_TARGET_2_SEGGER_SN"] = "3332222111" 51 | 52 | def test_get_targets_from_file(self): 53 | target_database = FileTargetDatabase("test_targets.json") 54 | target_repository = TargetRegistry(target_db=target_database) 55 | 56 | target = target_repository.find_one(target_id=1) 57 | assert target is not None 58 | assert target["drive"] == "d:\\" 59 | assert target["serial_port"] == "COM7" 60 | assert target["pca"] == "PCA10028" 61 | assert target["segger_sn"] == "123123123123" 62 | 63 | target = target_repository.find_one(target_id=2) 64 | assert target is not None 65 | assert target["drive"] == "e:\\" 66 | assert target["serial_port"] == "COM8" 67 | assert target["pca"] == "PCA10028" 68 | assert target["segger_sn"] == "321321321312" 69 | 70 | def test_get_targets_from_environment(self): 71 | target_database = EnvTargetDatabase() 72 | target_repository = TargetRegistry(target_db=target_database) 73 | 74 | target = target_repository.find_one(target_id=1) 75 | assert target is not None 76 | assert target["drive"] == "D:\\" 77 | assert target["serial_port"] == "COM1" 78 | assert target["pca"] == "PCA10028" 79 | assert target["segger_sn"] == "1231233333" 80 | 81 | target = target_repository.find_one(target_id=2) 82 | assert target is not None 83 | assert target["drive"] == "E:\\" 84 | assert target["serial_port"] == "COM2" 85 | assert target["pca"] == "PCA10028" 86 | assert target["segger_sn"] == "3332222111" 87 | 88 | 89 | if __name__ == '__main__': 90 | unittest.main() 91 | -------------------------------------------------------------------------------- /nordicsemi/utility/tests/test_targets.json: -------------------------------------------------------------------------------- 1 | { 2 | "targets": 3 | [{ 4 | "id": 1, 5 | "drive": "d:\\", 6 | "serial_port": "COM7", 7 | "pca": "PCA10028", 8 | "segger_sn": "123123123123" 9 | }, 10 | { 11 | "id": 2, 12 | "drive": "e:\\", 13 | "serial_port": "COM8", 14 | "pca": "PCA10028", 15 | "segger_sn": "321321321312" 16 | }] 17 | } -------------------------------------------------------------------------------- /nordicsemi/version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2015, Nordic Semiconductor 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | """ Version definition for nrfutil. """ 32 | 33 | NRFUTIL_VERSION = "0.5.3.post16" 34 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyserial >= 2.7 2 | click >= 5.1 3 | ecdsa >= 0.13 4 | behave 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) 2015, Nordic Semiconductor 4 | # All rights reserved. 5 | # 6 | # Redistribution and use in source and binary forms, with or without 7 | # modification, are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright notice, this 10 | # list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 17 | # contributors may be used to endorse or promote products derived from 18 | # this software without specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | """ 32 | Setup script for nrfutil. 33 | 34 | USAGE: 35 | python setup.py install 36 | 37 | """ 38 | import os 39 | import os.path 40 | import platform 41 | 42 | from setuptools import setup, find_packages 43 | from setuptools.command.test import test as TestCommand 44 | #from setuptools_behave import behave_test 45 | from distutils.core import setup 46 | 47 | from nordicsemi import version 48 | 49 | excludes = ["Tkconstants", 50 | "Tkinter", 51 | "tcl", 52 | "pickle", 53 | "unittest", 54 | "pyreadline"] 55 | 56 | # DFU component cli interface 57 | includes = ["nordicsemi.dfu.dfu"] 58 | 59 | packages = [] 60 | 61 | dll_excludes = [ 62 | "w9xpopen.exe", 63 | "OLEAUT32.DLL", 64 | "OLE32.DLL", 65 | "USER32.DLL", 66 | "SHELL32.DLL", 67 | "ADVAPI32.DLL", 68 | "KERNEL32.DLL", 69 | "WS2_32.DLL", 70 | "GDI32.DLL"] 71 | 72 | build_dir = os.environ.get("NRFUTIL_BUILD_DIR", "./{}".format(version.NRFUTIL_VERSION)) 73 | description = """Python 3 version of the Nordic nrfutil utility nordicsemi library (modified by Adafruit)""" 74 | 75 | 76 | class NoseTestCommand(TestCommand): 77 | def finalize_options(self): 78 | TestCommand.finalize_options(self) 79 | self.test_args = [] 80 | self.test_suite = True 81 | 82 | def run_tests(self): 83 | import nose 84 | nose.run_exit(argv=['nosetests', '--with-xunit', '--xunit-file=test-reports/unittests.xml']) 85 | 86 | common_requirements=[] 87 | 88 | # Get the long description from the README file 89 | here = os.path.abspath(os.path.dirname(__file__)) 90 | with open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 91 | long_description = f.read() 92 | 93 | setup( 94 | name="adafruit-nrfutil", 95 | version=version.NRFUTIL_VERSION, 96 | license="Nordic Semicondictor proprietary license", 97 | author="Nordic Semiconductor ASA (modified by Adafruit Industries LLC)", 98 | author_email="circuitpython@adafruit.com", 99 | url="https://github.com/adafruit/Adafruit_nRF52_nrfutil", 100 | description="Python 3 version of Nordic Semiconductor nrfutil utility and Python library (modified by Adafruit)", 101 | long_description=long_description, 102 | long_description_content_type="text/markdown", 103 | packages=find_packages(exclude=["tests.*", "tests"]), 104 | include_package_data=False, 105 | install_requires=[ 106 | "pyserial >= 2.7", 107 | "click >= 5.1", 108 | "ecdsa >= 0.13", 109 | ], 110 | tests_require=[ 111 | "nose >= 1.3.4", 112 | "behave" 113 | ], 114 | zip_safe=False, 115 | classifiers=[ 116 | 'Intended Audience :: Developers', 117 | 118 | 'Operating System :: MacOS', 119 | 'Operating System :: Microsoft :: Windows', 120 | 'Operating System :: POSIX :: Linux', 121 | 122 | 'Topic :: System :: Networking', 123 | 'Topic :: System :: Hardware :: Hardware Drivers', 124 | 'Topic :: Software Development :: Embedded Systems', 125 | 126 | 'License :: Other/Proprietary License', 127 | 'Programming Language :: Python :: 3.5', 128 | ], 129 | keywords='nordic nrf52 ble bluetooth dfu ota softdevice serialization nrfutil pc-nrfutil adafruit circuitpython', 130 | cmdclass={ 131 | 'test': NoseTestCommand 132 | # 'bdd_test': behave_test 133 | }, 134 | entry_points={ 135 | 'console_scripts': [ 136 | 'adafruit-nrfutil = nordicsemi.__main__:cli', 137 | ], 138 | }, 139 | ) 140 | -------------------------------------------------------------------------------- /tests/bdd/environment.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import logging 30 | 31 | 32 | logging.basicConfig(format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', 33 | datefmt='%m-%d %H:%M:%S ', level=logging.DEBUG) 34 | 35 | 36 | def before_all(context): 37 | pass 38 | -------------------------------------------------------------------------------- /tests/bdd/genpkg_help_information.feature: -------------------------------------------------------------------------------- 1 | Feature: Help information 2 | Scenario: User types --help 3 | Given user types 'nrfutil dfu genpkg --help' 4 | When user press enter 5 | Then output contains 'Generate a zipfile package for distribution to Apps supporting Nordic DFU' and exit code is 0 6 | 7 | Scenario: User does not type mandatory arguments 8 | Given user types 'nrfutil dfu genpkg' 9 | When user press enter 10 | Then output contains 'Error: Missing argument "zipfile".' and exit code is 2 11 | -------------------------------------------------------------------------------- /tests/bdd/steps/common_steps.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from queue import Queue 30 | import logging 31 | import os 32 | import subprocess 33 | from threading import Thread 34 | from util import process_pipe, ON_POSIX 35 | 36 | logger = logging.getLogger(__file__) 37 | 38 | 39 | class Exec(object): 40 | def __init__(self, exec_path): 41 | self.path = exec_path 42 | self.name = os.path.basename(self.path) 43 | self.dir = os.path.dirname(self.path) 44 | self.out_queue = Queue() 45 | self.stdout_thread = None 46 | self.stderr_thread = None 47 | self.process = None 48 | 49 | def execute(self, args, working_directory): 50 | args = args 51 | shell = False 52 | 53 | args.insert(0, self.path) 54 | 55 | self.process = subprocess.Popen(args=args, 56 | bufsize=0, 57 | cwd=working_directory, 58 | executable=self.path, 59 | stdin=subprocess.PIPE, 60 | stdout=subprocess.PIPE, 61 | stderr=subprocess.PIPE, 62 | close_fds=ON_POSIX, 63 | universal_newlines=True, 64 | shell=shell) 65 | 66 | if self.process.poll() is not None: 67 | raise Exception("Error starting {} application {}, return code is {}".format( 68 | self.path, 69 | self.process.poll())) 70 | 71 | self.stdout_thread = Thread(target=process_pipe, args=(self.process.stdout, self.out_queue)) 72 | self.stdout_thread.start() 73 | 74 | self.stderr_thread = Thread(target=process_pipe, args=(self.process.stderr, self.out_queue)) 75 | self.stderr_thread.start() 76 | 77 | def kill(self): 78 | if self.process is not None: 79 | self.process.kill() 80 | self.process.stdin.close() 81 | 82 | 83 | def get_resources_path(): 84 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "resources") 85 | -------------------------------------------------------------------------------- /tests/bdd/steps/genpkg_generate_dfu_package_steps.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | import json 30 | import logging 31 | import os 32 | from zipfile import ZipFile 33 | from behave import given, then, when 34 | from click.testing import CliRunner 35 | from nordicsemi.__main__ import cli, int_as_text_to_int 36 | from common_steps import get_resources_path 37 | 38 | 39 | logger = logging.getLogger(__file__) 40 | 41 | 42 | @given('the user wants to generate a DFU package with application {application}, bootloader {bootloader} and SoftDevice {softdevice} with name {package}') 43 | def step_impl(context, application, bootloader, softdevice, package): 44 | runner = CliRunner() 45 | context.runner = runner 46 | args = ['dfu', 'genpkg'] 47 | 48 | if application != 'not_set': 49 | args.extend(['--application', os.path.join(get_resources_path(), application)]) 50 | context.application = application 51 | else: 52 | context.application = None 53 | 54 | if bootloader != 'not_set': 55 | args.extend(['--bootloader', os.path.join(get_resources_path(), bootloader)]) 56 | context.bootloader = bootloader 57 | else: 58 | context.bootloader = None 59 | 60 | if softdevice != 'not_set': 61 | args.extend(['--softdevice', os.path.join(get_resources_path(), softdevice)]) 62 | context.softdevice = softdevice 63 | else: 64 | context.softdevice = None 65 | 66 | args.append(package) 67 | 68 | context.args = args 69 | 70 | 71 | @given('with option --application-version {app_ver}') 72 | def step_impl(context, app_ver): 73 | context.application_version = None 74 | 75 | if app_ver == 'not_set': 76 | context.application_version = 0xFFFFFFFF 77 | elif app_ver == 'none': 78 | context.args.extend(['--application-version', 'None']) 79 | else: 80 | context.args.extend(['--application-version', app_ver]) 81 | context.application_version = int_as_text_to_int(app_ver) 82 | 83 | 84 | @given('with option --dev-revision {dev_rev}') 85 | def step_impl(context, dev_rev): 86 | context.dev_revision = None 87 | 88 | if dev_rev == 'not_set': 89 | context.dev_revision = 0xFFFF 90 | elif dev_rev == 'none': 91 | context.args.extend(['--dev-revision', 'None']) 92 | else: 93 | context.args.extend(['--dev-revision', dev_rev]) 94 | context.dev_revision = int_as_text_to_int(dev_rev) 95 | 96 | 97 | @given('with option --dev-type {dev_type}') 98 | def step_impl(context, dev_type): 99 | context.dev_type = None 100 | 101 | if dev_type == 'not_set': 102 | context.dev_type = 0xFFFF 103 | elif dev_type == 'none': 104 | context.args.extend(['--dev-type', 'None']) 105 | else: 106 | context.args.extend(['--dev-type', dev_type]) 107 | context.dev_type = int_as_text_to_int(dev_type) 108 | 109 | 110 | @given('with option --dfu-ver {dfu_ver}') 111 | def step_impl(context, dfu_ver): 112 | context.firmware_hash = None 113 | context.ext_packet_id = None 114 | context.init_packet_ecds = None 115 | 116 | if dfu_ver == 'not_set': 117 | context.dfu_ver = 0.5 118 | context.ext_packet_id = 0 119 | else: 120 | if dfu_ver == 0.5: 121 | pass 122 | elif dfu_ver == 0.6: 123 | context.ext_packet_id = 0 124 | elif dfu_ver == 0.7: 125 | context.ext_packet_id = 1 126 | context.firmware_hash = 'exists' 127 | elif dfu_ver == 0.8: 128 | context.ext_packet_id = 2 129 | context.firmware_hash = 'exists' 130 | context.init_packet_ecds = 'exists' 131 | 132 | context.args.extend(['--dfu-ver', dfu_ver]) 133 | context.dfu_ver = float(dfu_ver) 134 | 135 | 136 | @given('with option --sd-req {sd_reqs}') 137 | def step_impl(context, sd_reqs): 138 | context.sd_req = None 139 | 140 | if sd_reqs == 'not_set': 141 | context.sd_req = [0xFFFE] 142 | elif sd_reqs == 'none': 143 | context.args.extend(['--sd-req', 'None']) 144 | else: 145 | context.args.extend(['--sd-req', sd_reqs]) 146 | 147 | sd_reqs = sd_reqs.split(",") 148 | sd_reqs_value = [] 149 | 150 | for sd_req in sd_reqs: 151 | sd_reqs_value.append(int_as_text_to_int(sd_req)) 152 | 153 | context.sd_req = sd_reqs_value 154 | 155 | 156 | @given('with option --key-file {pem_file}') 157 | def step_impl(context, pem_file): 158 | if pem_file != 'not_set': 159 | context.args.extend(['--key-file', os.path.join(get_resources_path(), pem_file)]) 160 | context.dfu_ver = 0.8 161 | 162 | 163 | @when('user press enter') 164 | def step_impl(context): 165 | pass 166 | 167 | 168 | @then('the generated DFU package {package} contains correct data') 169 | def step_impl(context, package): 170 | with context.runner.isolated_filesystem(): 171 | pkg_full_name = os.path.join(os.getcwd(), package) 172 | logger.debug("Package full name %s", pkg_full_name) 173 | 174 | result = context.runner.invoke(cli, context.args) 175 | logger.debug("exit_code: %s, output: \'%s\'", result.exit_code, result.output) 176 | assert result.exit_code == 0 177 | 178 | with ZipFile(pkg_full_name, 'r') as pkg: 179 | infolist = pkg.infolist() 180 | 181 | expected_zip_content = ["manifest.json"] 182 | 183 | if context.bootloader and context.softdevice: 184 | expected_zip_content.append("sd_bl.bin") 185 | expected_zip_content.append("sd_bl.dat") 186 | elif context.bootloader: 187 | expected_zip_content.append(context.bootloader.split(".")[0] + ".bin") 188 | expected_zip_content.append(context.bootloader.split(".")[0] + ".dat") 189 | elif context.softdevice: 190 | expected_zip_content.append(context.softdevice.split(".")[0] + ".bin") 191 | expected_zip_content.append(context.softdevice.split(".")[0] + ".dat") 192 | 193 | if context.application: 194 | expected_zip_content.append(context.application.split(".")[0] + '.bin') 195 | expected_zip_content.append(context.application.split(".")[0] + '.dat') 196 | 197 | for file_information in infolist: 198 | assert file_information.filename in expected_zip_content 199 | assert file_information.file_size > 0 200 | 201 | # Extract all and load json document to see if it is correct regarding to paths 202 | pkg.extractall() 203 | 204 | with open('manifest.json', 'r') as f: 205 | _json = json.load(f) 206 | 207 | if context.dfu_ver: 208 | assert 'dfu_version' in _json['manifest'] 209 | assert _json['manifest']['dfu_version'] == context.dfu_ver 210 | 211 | if context.bootloader and context.softdevice: 212 | data = _json['manifest']['softdevice_bootloader']['init_packet_data'] 213 | assert_init_packet_data(context, data) 214 | elif context.bootloader: 215 | data = _json['manifest']['bootloader']['init_packet_data'] 216 | assert_init_packet_data(context, data) 217 | elif context.softdevice: 218 | data = _json['manifest']['softdevice']['init_packet_data'] 219 | assert_init_packet_data(context, data) 220 | if context.application: 221 | data = _json['manifest']['application']['init_packet_data'] 222 | assert_init_packet_data(context, data) 223 | 224 | 225 | def assert_init_packet_data(context, data): 226 | if context.application_version: 227 | assert 'application_version' in data 228 | assert data['application_version'] == context.application_version 229 | 230 | if context.dev_revision: 231 | assert 'device_revision' in data 232 | assert data['device_revision'] == context.dev_revision 233 | 234 | if context.dev_type: 235 | assert 'device_type' in data 236 | assert data['device_type'] == context.dev_type 237 | 238 | if context.sd_req: 239 | assert 'softdevice_req' in data 240 | assert data['softdevice_req'] == context.sd_req 241 | 242 | if context.ext_packet_id: 243 | assert 'ext_packet_id' in data 244 | assert data['ext_packet_id'] == context.ext_packet_id 245 | 246 | if context.firmware_hash: 247 | assert 'firmware_hash' in data 248 | 249 | if context.init_packet_ecds: 250 | assert 'init_packet_ecds' in data 251 | -------------------------------------------------------------------------------- /tests/bdd/steps/genpkg_help_information_steps.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from queue import Empty 30 | import logging 31 | import os 32 | import time 33 | import sys 34 | 35 | from click.testing import CliRunner 36 | from behave import then, given, when 37 | 38 | from nordicsemi.__main__ import cli, int_as_text_to_int 39 | 40 | 41 | logger = logging.getLogger(__file__) 42 | 43 | STDOUT_TEXT_WAIT_TIME = 50 # Number of seconds to wait for expected output from stdout 44 | 45 | 46 | @given('user types \'{command}\'') 47 | def step_impl(context, command): 48 | args = command.split(' ') 49 | assert args[0] == 'nrfutil' 50 | 51 | exec_args = args[1:] 52 | 53 | runner = CliRunner() 54 | context.runner = runner 55 | context.args = exec_args 56 | 57 | 58 | @then('output contains \'{stdout_text}\' and exit code is {exit_code}') 59 | def step_impl(context, stdout_text, exit_code): 60 | result = context.runner.invoke(cli, context.args) 61 | logger.debug("exit_code: %s, output: \'%s\'", result.exit_code, result.output) 62 | assert result.exit_code == int_as_text_to_int(exit_code) 63 | assert result.output != None 64 | assert result.output.find(stdout_text) >= 0 65 | -------------------------------------------------------------------------------- /tests/bdd/steps/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Nordic Semiconductor 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # * Neither the name of Nordic Semiconductor ASA nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | from random import randint 30 | import time 31 | import sys 32 | import math 33 | 34 | ON_POSIX = 'posix' in sys.builtin_module_names 35 | 36 | 37 | def process_pipe(pipe, queue): 38 | for line in iter(pipe.readline, b''): 39 | queue.put({'type': 'output', 'data': line}) 40 | 41 | pipe.close() 42 | queue.put({'type': 'output_terminated'}) 43 | 44 | 45 | def kill_process(target): 46 | if 'proc' in target: 47 | target['proc'].kill() 48 | 49 | # Close file descriptors 50 | target['proc'].stdin.close() 51 | time.sleep(1) # Let the application terminate before proceeding 52 | 53 | 54 | def kill_processes(context): 55 | targets = context.target_registry.get_all() 56 | 57 | for target in targets: 58 | kill_process(target) 59 | 60 | 61 | def generate_options_table_for_cucumber(): 62 | retval = "" 63 | 64 | number_of_2_option_options = 1 65 | number_of_3_option_options = 4 66 | number_of_4_option_options = 1 67 | 68 | number_of_optional_option_permutations = 1 69 | number_of_optional_option_permutations *= int(math.pow(2, number_of_2_option_options)) 70 | number_of_optional_option_permutations *= int(math.pow(3, number_of_3_option_options)) 71 | number_of_optional_option_permutations *= int(math.pow(4, number_of_4_option_options)) 72 | 73 | for x in range(0, number_of_optional_option_permutations): 74 | retval += "{0:<8}".format(" ") 75 | retval += "| {0:<12}| {1:<29}| {2:<29}|".format("blinky.bin", "not_set", "not_set") 76 | 77 | permutation_name = "" 78 | options_factor = 1 79 | 80 | option = int(x / options_factor % 3) 81 | options_factor *= 3 82 | permutation_name = str(option) + permutation_name 83 | 84 | if option == 0: 85 | retval += " {0:<8}|".format("none") 86 | if option == 1: 87 | retval += " {0:<8}|".format("not_set") 88 | if option == 2: 89 | retval += " {0:<8}|".format("0x{0:02x}".format(randint(0, 255))) 90 | 91 | option = int(x / options_factor % 3) 92 | options_factor *= 3 93 | permutation_name = str(option) + permutation_name 94 | 95 | if option == 0: 96 | retval += " {0:<8}|".format("none") 97 | if option == 1: 98 | retval += " {0:<8}|".format("not_set") 99 | if option == 2: 100 | retval += " {0:<8}|".format("0x{0:02x}".format(randint(0, 255))) 101 | 102 | option = int(x / options_factor % 3) 103 | options_factor *= 3 104 | permutation_name = str(option) + permutation_name 105 | 106 | if option == 0: 107 | retval += " {0:<9}|".format("none") 108 | if option == 1: 109 | retval += " {0:<9}|".format("not_set") 110 | if option == 2: 111 | retval += " {0:<9}|".format("0x{0:02x}".format(randint(0, 255))) 112 | 113 | 114 | option = int(x / options_factor % 4) 115 | options_factor *= 4 116 | permutation_name = str(option) + permutation_name 117 | 118 | if option == 0: 119 | retval += " {0:<8}|".format("not_set") 120 | if option == 1: 121 | retval += " {0:<8}|".format("0.5") 122 | if option == 2: 123 | retval += " {0:<8}|".format("0.6") 124 | if option == 3: 125 | retval += " {0:<8}|".format("0.7") 126 | 127 | option = int(x / options_factor % 3) 128 | options_factor *= 3 129 | permutation_name = str(option) + permutation_name 130 | 131 | if option == 0: 132 | retval += " {0:<28}|".format("none") 133 | if option == 1: 134 | retval += " {0:<28}|".format("not_set") 135 | if option == 2: 136 | sd_reqs = [] 137 | 138 | for i in range(0, randint(1, 4)): 139 | sd_reqs.append("0x{0:04x}".format(randint(0, 65535))) 140 | 141 | retval += " {0:<28}|".format(",".join(sd_reqs)) 142 | 143 | option = int(x / options_factor % 2) 144 | permutation_name = str(option) + permutation_name 145 | 146 | if option == 0: 147 | retval += " {0:<9}|".format("not_set") 148 | if option == 1: 149 | retval += " {0:<9}|".format("test.pem") 150 | 151 | retval += " {0:<15}|".format("100_{0:0>6}.zip".format(permutation_name)) 152 | retval += "\n" 153 | 154 | return retval 155 | -------------------------------------------------------------------------------- /tests/resources/blinky.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_nRF52_nrfutil/1361059009ff6a24d63b37eb3a4b28127837ead2/tests/resources/blinky.bin -------------------------------------------------------------------------------- /tests/resources/dfu_test_bootloader_b.hex: -------------------------------------------------------------------------------- 1 | :020000040003F7 2 | :10C00000103C0020C9EE0300E3EE0300E5EE030060 3 | :10C010000000000000000000000000000000000020 4 | :10C02000000000000000000000000000D5C0030078 5 | :10C030000000000000000000E9EE0300EBEE03004A 6 | :10C04000EDEE0300EDEE0300EDEE0300EDEE030078 7 | :10C05000EDEE030000000000EDEE0300EDEE030046 8 | :00000001FF 9 | -------------------------------------------------------------------------------- /tests/resources/dfu_test_softdevice_b.hex: -------------------------------------------------------------------------------- 1 | :020000040000FA 2 | :0869100000000000000000007F 3 | :00000001FF 4 | -------------------------------------------------------------------------------- /tests/resources/test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEID2WUBCe/4kLhl5ekJ+O8PtprcahUNFE3RIm5htQzDedoAoGCCqGSM49 3 | AwEHoUQDQgAEZY2i7duYH2l9rnIg1oIXq+0/uHAF7IoFubVru6oX9GCQm67NrXIm 4 | wgS2ErZi/0/MvRsMkIQQkNg6Wc2tbJgdTA== 5 | -----END EC PRIVATE KEY----- 6 | --------------------------------------------------------------------------------