| #!/usr/bin/env python3 |
| # |
| # Copyright (c) 2022 Project CHIP Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| import argparse |
| import codecs |
| import json |
| import logging as log |
| import sys |
| |
| import cbor2 as cbor |
| from intelhex import IntelHex |
| |
| HEX_PREFIX = "hex:" |
| |
| |
| class PartitionCreator: |
| """ |
| Class to create nrfconnect partition containing FactoryData |
| |
| :param offset: This is a partition offset where data will be stored in device's flash memory |
| :param length: This is a maximum partition size |
| :param input: This is a path to input JSON file |
| :param output: This is a path to output directory |
| |
| """ |
| |
| def __init__(self, offset: int, length: int, input: str, output: str) -> None: |
| self._ih = IntelHex() |
| self._length = length |
| self._offset = offset |
| self._data_ready = False |
| self._output = output |
| self._input = input |
| try: |
| self.__data_to_save = self._convert_to_dict(self._load_json()) |
| except IOError: |
| sys.exit(-1) |
| |
| def generate_cbor(self): |
| """ |
| Generates .cbor file using cbor2 library. |
| It generate a CBORTag 55799 which is user-specific tag |
| |
| """ |
| if self.__data_to_save: |
| # prepare raw data from Json |
| cbor_data = cbor.dumps(self.__data_to_save) |
| return cbor_data |
| |
| def create_hex(self, data: bytes): |
| """ |
| Creates .hex file from CBOR. |
| This file can be write directly to device. |
| |
| """ |
| if len(data) > self._length: |
| raise ValueError("generated CBOR file exceeds declared maximum partition size! {} > {}".format(len(data), self._length)) |
| self._ih.putsz(self._offset, data) |
| self._ih.write_hex_file(self._output + ".hex", True) |
| self._data_ready = True |
| return True |
| |
| def create_bin(self): |
| """ |
| Creates raw binary data of created previously .hex file |
| |
| """ |
| if not self._data_ready: |
| log.error("Please create hex file first!") |
| return False |
| self._ih.tobinfile(self._output + ".bin") |
| return True |
| |
| @staticmethod |
| def _convert_to_dict(data): |
| """ |
| Converts a list containing tuples ("key_name", "key_value") to a dictionary |
| |
| If "key_value" of data entry is a string-type variable and contains a HEX_PREFIX algorithm decodes it |
| to hex format to be sure that a cbor file will contain proper bytes. |
| |
| If "key_value" of data entry is a dictionary, algorithm appends it to the created dictionary. |
| """ |
| output_dict = dict() |
| for entry in data: |
| if not isinstance(entry, dict): |
| log.debug("Processing entry {}".format(entry)) |
| if isinstance(data[entry], str) and data[entry].startswith(HEX_PREFIX): |
| output_dict[entry] = codecs.decode(data[entry][len(HEX_PREFIX):], "hex") |
| elif isinstance(data[entry], str): |
| output_dict[entry] = data[entry].encode("utf-8") |
| else: |
| output_dict[entry] = data[entry] |
| else: |
| output_dict[entry] = entry |
| return output_dict |
| |
| def _load_json(self): |
| """ |
| Loads file containing a JSON data and converts it to JSON format |
| |
| :raises IOError: if provided JSON file can not be read out. |
| """ |
| try: |
| with open(self._input, "rb") as json_file: |
| return json.loads(json_file.read()) |
| except IOError as e: |
| log.error("Can not read Json file {}".format(self._input)) |
| raise e |
| |
| |
| def print_flashing_help(): |
| print("\nTo flash the generated hex containing factory data, run the following command:") |
| print("For nrf52:") |
| print("-------------------------------------------------------------------------------") |
| print("nrfjprog -f nrf52 --program HEXFILE_PATH --sectorerase") |
| print("-------------------------------------------------------------------------------") |
| print("For nrf53:") |
| print("-------------------------------------------------------------------------------") |
| print("nrfjprog -f nrf53 --program HEXFILE_PATH --sectorerase") |
| print("-------------------------------------------------------------------------------") |
| |
| |
| def main(): |
| |
| def allow_any_int(i): return int(i, 0) |
| |
| parser = argparse.ArgumentParser(description="NrfConnect Factory Data NVS partition generator tool") |
| parser.add_argument("-i", "--input", type=str, required=True, |
| help="Path to input .json file") |
| parser.add_argument("-o", "--output", type=str, required=True, |
| help=("Prefix for output file paths, e.g. setting dir/output causes creation of the following files: " |
| "dir/output.hex, and dir/output.bin")) |
| parser.add_argument("--offset", type=allow_any_int, required=True, |
| help="Partition offset - an address in device's NVM memory, where factory data will be stored") |
| parser.add_argument("--size", type=allow_any_int, required=True, |
| help="The maximum partition size") |
| parser.add_argument("-v", "--verbose", action="store_true", |
| help="Run this script with DEBUG logging level") |
| parser.add_argument("-r", "--raw", action="store_true", |
| help=("Do not print flashing help and other logs, only generate a .hex file. " |
| "It can be useful when the script is used by other script.")) |
| args = parser.parse_args() |
| |
| if args.verbose: |
| log.basicConfig(format='[%(asctime)s][%(levelname)s] %(message)s', level=log.DEBUG) |
| elif args.raw: |
| log.basicConfig(format='%(message)s', level=log.ERROR) |
| else: |
| log.basicConfig(format='[%(asctime)s] %(message)s', level=log.INFO) |
| |
| partition_creator = PartitionCreator(args.offset, args.size, args.input, args.output) |
| cbor_data = partition_creator.generate_cbor() |
| try: |
| if not args.raw: |
| print("Generating .hex file: {}.hex with offset: {} and size: {}".format(args.output, hex(args.offset), hex(args.size))) |
| if partition_creator.create_hex(cbor_data) and partition_creator.create_bin(): |
| if not args.raw: |
| print_flashing_help() |
| except ValueError as e: |
| log.error(e) |
| sys.exit(-1) |
| |
| |
| if __name__ == "__main__": |
| main() |