Add support for Nordic's new nRF Util tool, aka nrfutil. Via its "device" command, nrfutil now supports most of the functionality offered by nrfjprog. The tool itself can be found here: https://www.nordicsemi.com/Products/Development-tools/nrf-util Signed-off-by: Carles Cufi <carles.cufi@nordicsemi.no>
118 lines
3.7 KiB
Python
118 lines
3.7 KiB
Python
# Copyright (c) 2023 Nordic Semiconductor ASA.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
'''Runner for flashing with nrfutil.'''
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
import subprocess
|
|
|
|
from runners.nrf_common import NrfBinaryRunner
|
|
|
|
class NrfUtilBinaryRunner(NrfBinaryRunner):
|
|
'''Runner front-end for nrfutil.'''
|
|
|
|
def __init__(self, cfg, family, softreset, dev_id, erase=False,
|
|
tool_opt=[], force=False, recover=False):
|
|
|
|
super().__init__(cfg, family, softreset, dev_id, erase,
|
|
tool_opt, force, recover)
|
|
self._ops = []
|
|
self._op_id = 1
|
|
|
|
@classmethod
|
|
def name(cls):
|
|
return 'nrfutil'
|
|
|
|
@classmethod
|
|
def tool_opt_help(cls) -> str:
|
|
return 'Additional options for nrfutil, e.g. "--log-level"'
|
|
|
|
@classmethod
|
|
def do_create(cls, cfg, args):
|
|
return NrfUtilBinaryRunner(cfg, args.nrf_family, args.softreset,
|
|
args.dev_id, erase=args.erase,
|
|
tool_opt=args.tool_opt, force=args.force,
|
|
recover=args.recover)
|
|
|
|
def _exec(self, args):
|
|
try:
|
|
out = self.check_output(['nrfutil', '--json', 'device'] + args)
|
|
except subprocess.CalledProcessError as e:
|
|
# https://docs.python.org/3/reference/compound_stmts.html#except-clause
|
|
cpe = e
|
|
out = cpe.stdout
|
|
else:
|
|
cpe = None
|
|
finally:
|
|
# https://github.com/ndjson/ndjson-spec
|
|
out = [json.loads(s) for s in
|
|
out.decode(sys.getdefaultencoding()).split('\n') if len(s)]
|
|
self.logger.debug(f'output: {out}')
|
|
if cpe:
|
|
if 'execute-batch' in args:
|
|
for o in out:
|
|
if o['type'] == 'batch_end' and o['data']['error']:
|
|
cpe.returncode = o['data']['error']['code']
|
|
raise cpe
|
|
|
|
return out
|
|
|
|
def do_get_boards(self):
|
|
out = self._exec(['list'])
|
|
devs = []
|
|
for o in out:
|
|
if o['type'] == 'task_end':
|
|
devs = o['data']['data']['devices']
|
|
snrs = [dev['serialNumber'] for dev in devs]
|
|
|
|
self.logger.debug(f'Found boards: {snrs}')
|
|
return snrs
|
|
|
|
def do_require(self):
|
|
self.require('nrfutil')
|
|
|
|
def _insert_op(self, op):
|
|
op['operationId'] = f'{self._op_id}'
|
|
self._op_id += 1
|
|
self._ops.append(op)
|
|
|
|
def _exec_batch(self):
|
|
# prepare the dictionary and convert to JSON
|
|
batch = json.dumps({'family': f'{self.family}',
|
|
'operations': [op for op in self._ops]},
|
|
indent=4) + '\n'
|
|
|
|
hex_dir = Path(self.hex_).parent
|
|
json_file = os.fspath(hex_dir / f'generated_nrfutil_batch.json')
|
|
|
|
with open(json_file, "w") as f:
|
|
f.write(batch)
|
|
|
|
# reset first in case an exception is thrown
|
|
self._ops = []
|
|
self._op_id = 1
|
|
self.logger.debug(f'Executing batch in: {json_file}')
|
|
self._exec(['execute-batch', '--batch-path', f'{json_file}',
|
|
'--serial-number', f'{self.dev_id}'])
|
|
|
|
def do_exec_op(self, op, force=False):
|
|
self.logger.debug(f'Executing op: {op}')
|
|
if force:
|
|
if len(self._ops) != 0:
|
|
raise RuntimeError(f'Forced exec with {len(self._ops)} ops')
|
|
self._insert_op(op)
|
|
self._exec_batch()
|
|
return True
|
|
# Defer by default
|
|
return False
|
|
|
|
def flush_ops(self, force=True):
|
|
if not force:
|
|
return
|
|
while self.ops:
|
|
self._insert_op(self.ops.popleft())
|
|
self._exec_batch()
|