Skip to content

Commit 1285389

Browse files
ArekBalysNordicpull[bot]
authored andcommitted
[nrfconnect] Added onboarding codes generation to the factory data script (#25879)
Generate onboarding parameters: - manual pairing code - QRCode using Nrfconnect factory data script. To generate onboarding parameters add --generate_onboarding to the generate_nrfconnect_chip_factory_data.py script. Added generation of onboarding parameters to the CMAKE script
1 parent 4bce0c2 commit 1285389

File tree

7 files changed

+164
-2
lines changed

7 files changed

+164
-2
lines changed

.github/workflows/examples-nrfconnect.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ jobs:
9999
run: scripts/run_in_build_env.sh "python3 scripts/setup/nrfconnect/update_ncs.py --check"
100100
- name: Run unit tests of factory data generation script
101101
timeout-minutes: 15
102-
run: scripts/run_in_build_env.sh "./scripts/tools/nrfconnect/tests/test_generate_factory_data.py"
102+
run: |
103+
scripts/run_in_build_env.sh 'pip3 install -r scripts/setup/requirements.nrfconnect.txt'
104+
scripts/run_in_build_env.sh "./scripts/tools/nrfconnect/tests/test_generate_factory_data.py"
103105
- name: Build example nRF Connect SDK Lock App on nRF52840 DK
104106
if: github.event_name == 'push' || steps.changed_paths.outputs.nrfconnect == 'true'
105107
timeout-minutes: 15

config/nrfconnect/chip-module/Kconfig

+8
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,14 @@ config CHIP_FACTORY_DATA_MERGE_WITH_FIRMWARE
139139
when flashing the firmware using the west tool, includes the factory data
140140
as well.
141141

142+
config CHIP_FACTORY_DATA_GENERATE_ONBOARDING_CODES
143+
bool "Generate onboarding codes during the generation of a factory data set"
144+
help
145+
Enables generation of onboarding codes (manual pairing code and QR code)
146+
during the generation of a factory data set. You can provide the
147+
onboarding codes a Matter controller to commission a device to a Matter
148+
network.
149+
142150
# Select source of the certificates
143151
choice CHIP_FACTORY_DATA_CERT_SOURCE
144152
prompt "Attestation certificate file source"

config/nrfconnect/chip-module/generate_factory_data.cmake

+4
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,10 @@ string(APPEND script_args "--passcode ${CONFIG_CHIP_DEVICE_SPAKE2_PASSCODE}\n")
9191
string(APPEND script_args "--include_passcode\n")
9292
string(APPEND script_args "--overwrite\n")
9393

94+
if(CONFIG_CHIP_FACTORY_DATA_GENERATE_ONBOARDING_CODES)
95+
string(APPEND script_args "--generate_onboarding\n")
96+
endif()
97+
9498
# check if spake2 verifier should be generated using script
9599
if(NOT CONFIG_CHIP_FACTORY_DATA_GENERATE_SPAKE2_VERIFIER)
96100
# Spake2 verifier should be provided using kConfig

docs/guides/nrfconnect_factory_data_configuration.md

+55
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ data secure by applying hardware write protection.
4242
- [Option 1: Using the php-json-schema tool](#option-1-using-the-php-json-schema-tool)
4343
- [Option 2: Using a website validator](#option-2-using-a-website-validator)
4444
- [Option 3: Using the nRF Connect Python script](#option-3-using-the-nrf-connect-python-script)
45+
- [Generating onboarding codes](#generating-onboarding-codes)
46+
- [Enabling onboarding codes generation within the build system](#enabling-onboarding-codes-generation-within-the-build-system)
4547
- [Preparing factory data partition on a device](#preparing-factory-data-partition-on-a-device)
4648
- [Creating a factory data partition with the second script](#creating-a-factory-data-partition-with-the-second-script)
4749
- [Building an example with factory data](#building-an-example-with-factory-data)
@@ -493,6 +495,59 @@ as an additional argument. To do this, complete the following steps:
493495
> **Note:** To learn more about the JSON schema, visit
494496
> [this unofficial JSON Schema tool usage website](https://json-schema.org/understanding-json-schema/).
495497
498+
### Generating onboarding codes
499+
500+
The
501+
[generate_nrfconnect_chip_factory_data.py](../../scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py)
502+
script lets you generating a manual code and a QR code from the given factory
503+
data parameters. You can use these codes to perform commissioning to the Matter
504+
network over Bluetooth LE since they include all the pairing data required by
505+
the Matter controller. You can place these codes on the device packaging or on
506+
the device itself during production.
507+
508+
To generate a manual pairing code and a QR code, complete the following steps:
509+
510+
1. Install all required Python dependencies for Matter:
511+
512+
```
513+
$ python -m pip install -r ./scripts/setup/requirements.nrfconnect.txt
514+
```
515+
516+
2. Complete steps 1, 2, and 3 from the
517+
[Creating the factory data JSON file with the first script](#creating-the-factory-data-json-file-with-the-first-script)
518+
section to prepare the final invocation of the Python script.
519+
520+
3. Add the `--generate_onboarding` argument to the Python script final
521+
invocation.
522+
523+
4. Run the script.
524+
525+
5. Navigate to the output directory provided as the `-o` argument.
526+
527+
The output directory contains the following files you need:
528+
529+
- JSON file containing the latest factory data set.
530+
- Test file containing the generated manual code and the text version of the
531+
QR Code.
532+
- PNG file containing the generated QR Code as an image.
533+
534+
#### Enabling onboarding codes generation within the build system
535+
536+
You can generate onboarding codes using the nRF Connect platform build system
537+
described in
538+
[Building an example with factory data](#building-an-example-with-factory-data),
539+
and build an example with the following additional option:
540+
`-DCONFIG_CHIP_FACTORY_DATA_GENERATE_ONBOARDING_CODES=y`.
541+
542+
For example, the build command for the nRF52840 DK could look like this:
543+
544+
```
545+
$ west build -b nrf52840dk_nrf52840 -- \
546+
-DCONFIG_CHIP_FACTORY_DATA=y \
547+
-DCONFIG_CHIP_FACTORY_DATA_BUILD=y \
548+
-DCONFIG_CHIP_FACTORY_DATA_GENERATE_ONBOARDING_CODES=y
549+
```
550+
496551
### Preparing factory data partition on a device
497552
498553
The factory data partition is an area in the device's persistent storage where a
+4-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
jsonschema>=4.4.0
22
cbor2>=5.4.3
3-
ecdsa>=0.18.0
3+
ecdsa>=0.18.0
4+
qrcode==7.4.2
5+
bitarray==2.6.0
6+
python_stdnum==1.18

scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py

+41
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@
2929
from cryptography.hazmat.backends import default_backend
3030
from cryptography.hazmat.primitives.serialization import load_der_private_key
3131

32+
try:
33+
import qrcode
34+
from generate_setup_payload import CommissioningFlow, SetupPayload
35+
except ImportError:
36+
SDK_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))))
37+
sys.path.append(os.path.join(SDK_ROOT, "src/setup_payload/python"))
38+
try:
39+
import qrcode
40+
from generate_setup_payload import CommissioningFlow, SetupPayload
41+
except ModuleNotFoundError or ImportError:
42+
no_onboarding_modules = True
43+
else:
44+
no_onboarding_modules = False
45+
else:
46+
no_onboarding_modules = False
47+
3248
try:
3349
import jsonschema
3450
except ImportError:
@@ -327,6 +343,9 @@ def generate_json(self):
327343
except IOError:
328344
log.error("Cannot save output file into directory: {}".format(self._args.output))
329345

346+
if self._args.generate_onboarding:
347+
self._generate_onboarding_data()
348+
330349
def _add_entry(self, name: str, value: any):
331350
""" Add single entry to list of tuples ("key", "value") """
332351
if (isinstance(value, bytes) or isinstance(value, bytearray)):
@@ -375,6 +394,19 @@ def _process_der(self, path: str):
375394
log.error(e)
376395
raise e
377396

397+
def _generate_onboarding_data(self):
398+
setup_payload = SetupPayload(discriminator=self._args.discriminator,
399+
pincode=self._args.passcode,
400+
rendezvous=2, # fixed pairing BLE
401+
flow=CommissioningFlow.Standard,
402+
vid=self._args.vendor_id,
403+
pid=self._args.product_id)
404+
with open(self._args.output[:-len(".json")] + ".txt", "w") as manual_code_file:
405+
manual_code_file.write("Manualcode : " + setup_payload.generate_manualcode() + "\n")
406+
manual_code_file.write("QRCode : " + setup_payload.generate_qrcode())
407+
qr = qrcode.make(setup_payload.generate_qrcode())
408+
qr.save(self._args.output[:-len(".json")] + ".png")
409+
378410

379411
def main():
380412
parser = argparse.ArgumentParser(description="NrfConnect Factory Data NVS generator tool")
@@ -481,6 +513,9 @@ def base64_str(s): return base64.b64decode(s)
481513
help=("Provide a path to the Product Attestation Authority (PAA) key to generate "
482514
"the PAI certificate. Without providing it, a testing PAA key stored in the Matter "
483515
"repository will be used."))
516+
optional_arguments.add_argument("--generate_onboarding", action="store_true",
517+
help=("Generate a Manual Code and QR Code according to provided factory data set."
518+
"As a result a PNG image containing QRCode and a .txt file containing Manual Code will be available within output directory"))
484519
args = parser.parse_args()
485520

486521
if args.verbose:
@@ -501,6 +536,12 @@ def base64_str(s): return base64.b64decode(s)
501536
"-r ./scripts/requirements.nrfconnect.txt from the Matter root directory."))
502537
return
503538

539+
if args.generate_onboarding and no_onboarding_modules:
540+
log.error(("Requested generation of onboarding codes, but the some modules are not installed. \n"
541+
"Install all dependencies for Matter by invoking: pip3 install "
542+
"-r ./scripts/requirements.nrfconnect.txt from the Matter root directory."))
543+
return
544+
504545
generator = FactoryDataGenerator(args)
505546
generator.generate_json()
506547

scripts/tools/nrfconnect/tests/test_generate_factory_data.py

+49
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import subprocess
2222
import tempfile
2323
import unittest
24+
from os.path import exists
2425

2526
TOOLS_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
2627

@@ -120,6 +121,10 @@
120121
0xde, 0x3d, 0xc0, 0x14, 0x3a, 0x97, 0xe1, 0x35, 0x38, 0xf7, 0xff, 0x76,
121122
0x05, 0x5e, 0xbf, 0x27, 0x90, 0x6f, 0x50, 0x0f])
122123

124+
TEST_MANUAL_CODE = "Manualcode : 35442608082"
125+
126+
TEST_QR_CODE = "QRCode : MT:KAYA3EYF15ND8B1OA00"
127+
123128

124129
def write_file(path: str, content: bytes) -> None:
125130
with open(path, 'wb') as f:
@@ -258,6 +263,50 @@ def test_generate_spake2p_verifier_default(self):
258263
'--raw'
259264
])
260265

266+
def test_generate_onboarding_codes(self):
267+
with tempfile.TemporaryDirectory() as outdir:
268+
write_file(os.path.join(outdir, 'DAC_key.der'), DAC_DER_KEY)
269+
write_file(os.path.join(outdir, 'DAC_cert.der'), DAC_DER_CERT)
270+
write_file(os.path.join(outdir, 'PAI_cert.der'), PAI_DER_CERT)
271+
272+
subprocess.check_call(['python3', os.path.join(TOOLS_DIR, 'generate_nrfconnect_chip_factory_data.py'),
273+
'-s', os.path.join(TOOLS_DIR, 'nrfconnect_factory_data.schema'),
274+
'--include_passcode',
275+
'--sn', 'SN:12345678',
276+
'--vendor_id', '0x127F',
277+
'--product_id', '0xABCD',
278+
'--vendor_name', 'Nordic Semiconductor ASA',
279+
'--product_name', 'Lock Gen2',
280+
'--part_number', 'PCA10056',
281+
'--product_url', 'https://example.com/lock',
282+
'--product_label', 'Lock',
283+
'--date', '2022-07-20',
284+
'--hw_ver', '101',
285+
'--hw_ver_str', 'v1.1',
286+
'--dac_key', os.path.join(outdir, 'DAC_key.der'),
287+
'--dac_cert', os.path.join(outdir, 'DAC_cert.der'),
288+
'--pai_cert', os.path.join(outdir, 'PAI_cert.der'),
289+
'--spake2_it', '2000',
290+
'--spake2_salt', 'U1BBS0UyUCBLZXkgU2FsdA==',
291+
'--passcode', '13243546',
292+
'--spake2_verifier', ('WN0SgEXLfUN19BbJqp6qn4pS69EtdNLReIMZwv/CIM0ECMP7ytiAJ7txIYJ0Ovlha/'
293+
'rQ3E+88mj3qaqqnviMaZzG+OyXEdSocDIT9ZhmkTCgWwERaHz4Vdh3G37RT6kqbw=='),
294+
'--discriminator', '0xFED',
295+
'--rd_uid', '91a9c12a7c80700a31ddcfa7fce63e44',
296+
'--enable_key', '00112233445566778899aabbccddeeff',
297+
'--user', '{"name": "product_name", "version": 123, "revision": "0x123"}',
298+
'-o', os.path.join(outdir, 'fd.json'),
299+
'--generate_onboarding'
300+
])
301+
302+
self.assertTrue(exists(os.path.join(outdir, 'fd.txt')))
303+
self.assertTrue(exists(os.path.join(outdir, 'fd.png')))
304+
305+
with open(os.path.join(outdir, 'fd.txt'), 'r') as onboarding_code_file:
306+
onboarding = onboarding_code_file.readlines()
307+
self.assertEqual(onboarding[0][:-1], TEST_MANUAL_CODE)
308+
self.assertEqual(onboarding[1], TEST_QR_CODE)
309+
261310

262311
if __name__ == '__main__':
263312
unittest.main()

0 commit comments

Comments
 (0)