From 8e3e6406f4e50a057d2c1a20ca2f9a552e47e22c Mon Sep 17 00:00:00 2001 From: Arkadiusz Balys Date: Wed, 25 Jan 2023 12:13:18 +0100 Subject: [PATCH] [nrfconnect] Added the Device Appearance attribute to factory data The appearance attribute of the Basic Information cluster has been added to the Factory Data set. That field allows for defining the visible finish of the product's external case finish method, and color hue. - The attribute has been added to the factory data provider and the Factory Data Parser. - The appearance field has been described in the factory data guide. --- .../nrfconnect_factory_data_configuration.md | 44 +++++++++++++++++-- .../generate_nrfconnect_chip_factory_data.py | 12 ++++- .../nrfconnect/nrfconnect_factory_data.schema | 14 ++++++ .../tests/test_generate_factory_data.py | 5 +++ src/platform/nrfconnect/FactoryDataParser.c | 35 ++++++++++++++- src/platform/nrfconnect/FactoryDataParser.h | 6 +++ .../nrfconnect/FactoryDataProvider.cpp | 22 ++++++++++ src/platform/nrfconnect/FactoryDataProvider.h | 2 + 8 files changed, 134 insertions(+), 6 deletions(-) diff --git a/docs/guides/nrfconnect_factory_data_configuration.md b/docs/guides/nrfconnect_factory_data_configuration.md index 16a9210c41fb8c..016f4100579764 100644 --- a/docs/guides/nrfconnect_factory_data_configuration.md +++ b/docs/guides/nrfconnect_factory_data_configuration.md @@ -107,7 +107,8 @@ The following table lists the parameters of a factory data set: | `spake2_verifier` | SPAKE2+ verifier | 97 B | byte string | mandatory | The SPAKE2+ verifier generated using SPAKE2+ salt, iteration counter, and passcode. | | `discriminator` | Discriminator | 2 B | uint16 | mandatory | A 12-bit value matching the field of the same name in the setup code. The discriminator is used during the discovery process. | | `passcode` | SPAKE passcode | 4 B | uint32 | optional | A pairing passcode is a 27-bit unsigned integer which serves as a proof of possession during the commissioning. Its value must be restricted to the values from `0x0000001` to `0x5F5E0FE` (`00000001` to `99999998` in decimal), excluding the following invalid passcode values: `00000000`, `11111111`, `22222222`, `33333333`, `44444444`, `55555555`, `66666666`, `77777777`, `88888888`, `99999999`, `12345678`, `87654321`. | -| `user` | User data | variable | JSON string | max 1024 B | The user data is provided in the JSON format. This parameter is optional and depends on device manufacturer's purpose. It is provided as a CBOR map type from persistent storage and should be parsed in the user application. This data is not used by the Matter stack. To learn how to work with user data, see [How to set user data](#how-to-set-user-data) section. | +|`product_appearance`| Product visible appearance | 2 B | CBOR map | optional | The appearance field is a structure that describes the visible appearance of the product. This field is provided in a CBOR map and consists of two attributes: `finish` (1 B), `primary_color` (1 B). See the [Appearance field description](#appearance-field-description) to learn how to set all attributes. | +| `user` | User data | variable, max 1024 B | CBOR map | optional | The user data is provided in the JSON format. This parameter is optional and depends on the device manufacturer's purpose. It is provided as a CBOR map type from persistent storage and should be parsed in the user application. This data is not used by the Matter stack. To learn how to work with user data, see the [How to set user data](#how-to-set-user-data) section. | ### Factory data format @@ -131,8 +132,8 @@ All parameters of the factory data set are either mandatory or optional: In the factory data set, the following formats are used: -- uint16 and uint32 -- These are the numeric formats representing, - respectively, two-bytes length unsigned integer and four-bytes length +- uint8, uint16, and uint32 -- These are the numeric formats representing, + respectively, one-byte length unsigned integer, two-bytes length unsigned integer, and four-bytes length unsigned integer. This value is stored in a HEX file in the big-endian order. - Byte string - This parameter represents the sequence of integers between `0` @@ -150,6 +151,34 @@ In the factory data set, the following formats are used: - All certificates stored in factory data are provided in the [X.509](https://www.itu.int/rec/T-REC-X.509-201910-I/en) format. +#### Appearance field description + +The `appearance` field in the factory data set describes the device's visible appearance. + +- `finish` - A string name that indicates the visible exterior finish of the product. +It refers to the `ProductFinishEnum` enum, and currently, you can choose one of the following names: + +| Name | Enum value | +| :--------: | :--------: | +| `matte` | 0 | +| `satin` | 1 | +| `polished` | 2 | +| `rugged` | 3 | +| `fabric` | 4 | +| `other` | 255 | + +- `primary_color` - A string name that represents the RGB color space of the device's case color, which is the most representative. +It refers to the `ColorEnum` enum, and currently, you can choose one of the following names: + +(Enum value) color name (`RGB value`) + +| (0) $$\color{black} \color{black}{black}$$ (`#000000`) | (1) $$\color{#000080}{navy}$$ (`#000080`) | (2) $$\color{#008000}{green}$$ (`#008000`)| (3) $$\color{#008080}{teal}$$ (`#008080`) | (4) $$\color{#800080}{maroon}$$ (`#800080`) | +| --------------------- | ----------------- | --------------- | ----------------- | --------------- | +| (5) $$\color{#800080}{purple}$$ (`#800080`) | (6) $$\color{#808000}{olive}$$ (`#800080`) | (7) $$\color{#808080}{gray}$$ (`#800080`) | (8) $$\color{#0000FF}{blue}$$ (`#0000FF`) | (9) $$\color{#00FF00}{lime}$$ (`#00FF00`) | +| (10) $$\color{#00FFFF}{aqua}$$ (`#00FFFF`) | (11) $$\color{#FF0000}{red}$$ (`#FF0000`) | (12) $$\color{#FF00FF}{fuchsia}$$ (`#FF00FF`) | (13) $$\color{#FFFF00}{yellow}$$ (`#FFFF00`) | (14) $$\color{#FFFFFF} \color{white}{white}$$ (`#800080`) | +| (15) $$\color{#727472}{nickel}$$ (`#727472`) | (16) $$\color{#a8a9ad}{chrome}$$ (`#a8a9ad`) | (17) $$\color{#E1C16E}{brass}$$ (`#E1C16E`) | (18) $$\color{white}{co} \color{yellow}{p} \color{orange}{pe} \color{red}{r}$$ | (19) $$\color{#C0C0C0}{silver}$$ (`#C0C0C0`) | + (20) $$\color{#FFD700}{gold}$$ (`#FFD700`) | +
## Enabling factory data support @@ -287,6 +316,11 @@ To use this script, complete the following steps: --overwrite ``` + i. (optional) Add the appearance of the product: + ``` + --appearance + ``` + 4. Run the script using the prepared list of arguments: ``` @@ -314,7 +348,9 @@ $ python scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py \ --discriminator 0xF00 \ --generate_rd_uid \ --passcode 20202021 \ ---out "build.json" \ +--product_finish "matte" \ +--product_color "black" \ +--out "build.json"' \ --schema "scripts/tools/nrfconnect/nrfconnect_factory_data.schema" ``` diff --git a/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py b/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py index 38e11967e744c1..e053abc7fb43fd 100644 --- a/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py +++ b/scripts/tools/nrfconnect/generate_nrfconnect_chip_factory_data.py @@ -61,11 +61,14 @@ PUB_KEY_PREFIX = b'\x04' INVALID_PASSCODES = [00000000, 11111111, 22222222, 33333333, 44444444, 55555555, 66666666, 77777777, 88888888, 99999999, 12345678, 87654321] +PRODUCT_FINISH_ENUM = {"other": 0, "matte": 1, "satin": 2, "polished": 3, "rugged": 4, "fabric": 5} +PRODUCT_COLOR_ENUM = {"black": 0, "navy": 1, "green": 2, "teal": 3, "maroon": 4, "purple": 5, "olive": 6, "gray": 7, "blue": 8, "lime": 9, + "aqua": 10, "red": 11, "fuchsia": 12, "yellow": 13, "white": 14, "nickel": 15, "chrome": 16, "brass": 18, "cooper": 19, + "silver": 19, "gold": 20} sys.path.insert(0, os.path.join(MATTER_ROOT, 'scripts', 'tools', 'spake2p')) from spake2p import generate_verifier # noqa: E402 isort:skip - def get_raw_private_key_der(der_file: str, password: str): """ Split given der file to get separated key pair consisting of public and private keys. @@ -325,6 +328,9 @@ def generate_json(self): self._add_entry("rd_uid", rd_uid) if self._args.enable_key: self._add_entry("enable_key", HEX_PREFIX + self._args.enable_key) + if self._args.product_finish and self._args.product_color: + self._add_entry("product_appearance", {"finish": PRODUCT_FINISH_ENUM[self._args.product_finish], + "primary_color": PRODUCT_COLOR_ENUM[self._args.product_color]}) if self._args.user: self._add_entry("user", self._user_data) @@ -516,6 +522,10 @@ def base64_str(s): return base64.b64decode(s) optional_arguments.add_argument("--generate_onboarding", action="store_true", help=("Generate a Manual Code and QR Code according to provided factory data set." "As a result a PNG image containing QRCode and a .txt file containing Manual Code will be available within output directory")) + optional_arguments.add_argument("--product_finish", type=str, choices=PRODUCT_FINISH_ENUM.keys(), + help="[string] Provide one of the product finishes") + optional_arguments.add_argument("--product_color", type=str, choices=PRODUCT_COLOR_ENUM.keys(), + help="[string] Provide one of the product colors.") args = parser.parse_args() if args.verbose: diff --git a/scripts/tools/nrfconnect/nrfconnect_factory_data.schema b/scripts/tools/nrfconnect/nrfconnect_factory_data.schema index 84cbef50df8a78..c34589605a476e 100644 --- a/scripts/tools/nrfconnect/nrfconnect_factory_data.schema +++ b/scripts/tools/nrfconnect/nrfconnect_factory_data.schema @@ -156,6 +156,20 @@ "minLength": 36, "maxLength": 36 }, + "product_appearance": { + "description": "Product appearance provides a description of the product's external case finish method (matte, satin, polished, rugged, fabric) and product's primary color", + "type": "object", + "properties": { + "finish": { + "type": "integer", + "enum": [0, 1, 2, 3, 4, 5] + }, + "primary_color": { + "type": "integer", + "enum": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] + } + } + }, "user": { "description": "A user-specific additional data which should be added to factory data. This should be a Json format.", "type": "object" diff --git a/scripts/tools/nrfconnect/tests/test_generate_factory_data.py b/scripts/tools/nrfconnect/tests/test_generate_factory_data.py index 0be21f8fcb8bad..0b5b3c558d4693 100755 --- a/scripts/tools/nrfconnect/tests/test_generate_factory_data.py +++ b/scripts/tools/nrfconnect/tests/test_generate_factory_data.py @@ -177,6 +177,8 @@ def test_generate_factory_data_all_specified(self): '--discriminator', '0xFED', '--rd_uid', '91a9c12a7c80700a31ddcfa7fce63e44', '--enable_key', '00112233445566778899aabbccddeeff', + '--product_color', 'red', + '--product_finish', 'satin', '--user', '{"name": "product_name", "version": 123, "revision": "0x123"}', '-o', os.path.join(outdir, 'fd.json') ]) @@ -207,6 +209,7 @@ def test_generate_factory_data_all_specified(self): self.assertEqual(factory_data.get('passcode'), 13243546) self.assertEqual(factory_data.get('rd_uid'), 'hex:91a9c12a7c80700a31ddcfa7fce63e44') self.assertEqual(factory_data.get('enable_key'), 'hex:00112233445566778899aabbccddeeff') + self.assertEqual(factory_data.get('product_appearance'), {'finish': 2, 'primary_color': 11}) self.assertEqual(factory_data.get('user'), {'name': 'product_name', 'version': 123, 'revision': '0x123'}) subprocess.check_call(['python3', os.path.join(TOOLS_DIR, 'nrfconnect_generate_partition.py'), @@ -240,6 +243,8 @@ def test_generate_spake2p_verifier_default(self): '--spake2_salt', 'U1BBS0UyUCBLZXkgU2FsdA==', '--passcode', '20202021', '--discriminator', '0xFED', + '--product_color', 'red', + '--product_finish', 'satin', '--user', '{"name": "product_name", "version": 123, "revision": "0x123"}', '-o', os.path.join(outdir, 'fd.json') ]) diff --git a/src/platform/nrfconnect/FactoryDataParser.c b/src/platform/nrfconnect/FactoryDataParser.c index 56da5343d973dc..d6954da4d93ffc 100644 --- a/src/platform/nrfconnect/FactoryDataParser.c +++ b/src/platform/nrfconnect/FactoryDataParser.c @@ -22,7 +22,7 @@ #include #include -#define MAX_FACTORY_DATA_NESTING_LEVEL 3 +#define MAX_FACTORY_DATA_NESTING_LEVEL 4 static inline bool uint16_decode(zcbor_state_t * states, uint16_t * value) { @@ -37,6 +37,19 @@ static inline bool uint16_decode(zcbor_state_t * states, uint16_t * value) return false; } +static inline bool uint8_decode(zcbor_state_t * states, uint8_t * value) +{ + uint32_t u32; + + if (zcbor_uint32_decode(states, &u32)) + { + *value = (uint8_t) u32; + return true; + } + + return false; +} + static bool DecodeEntry(zcbor_state_t * states, void * buffer, size_t bufferSize, size_t * outlen) { struct zcbor_string tempString; @@ -233,6 +246,26 @@ bool ParseFactoryData(uint8_t * buffer, uint16_t bufferSize, struct FactoryData { res = res && zcbor_bstr_decode(states, (struct zcbor_string *) &factoryData->enable_key); } + else if (strncmp("product_appearance", (const char *) currentString.value, currentString.len) == 0) + { + res = res && zcbor_map_start_decode(states); + struct zcbor_string appearanceString; + while (res) + { + res = zcbor_tstr_decode(states, &appearanceString); + if (strncmp("finish", (const char *) appearanceString.value, appearanceString.len) == 0) + { + res = res && uint8_decode(states, &factoryData->product_appearance.finish); + } + else if (strncmp("primary_color", (const char *) appearanceString.value, appearanceString.len) == 0) + { + res = res && uint8_decode(states, &factoryData->product_appearance.primary_color); + break; + } + } + res = res && zcbor_map_end_decode(states); + factoryData->appearancePresent = res; + } else if (strncmp("user", (const char *) currentString.value, currentString.len) == 0) { factoryData->user.data = (void *) states->payload; diff --git a/src/platform/nrfconnect/FactoryDataParser.h b/src/platform/nrfconnect/FactoryDataParser.h index b600371dde96ee..1ed9d94752dab7 100644 --- a/src/platform/nrfconnect/FactoryDataParser.h +++ b/src/platform/nrfconnect/FactoryDataParser.h @@ -58,11 +58,17 @@ struct FactoryData uint32_t passcode; struct FactoryDataString enable_key; struct FactoryDataString user; + struct ProductAppearance + { + uint8_t finish; + uint8_t primary_color; + } product_appearance; bool vendorIdPresent; bool productIdPresent; bool hwVerPresent; bool discriminatorPresent; + bool appearancePresent; }; /** diff --git a/src/platform/nrfconnect/FactoryDataProvider.cpp b/src/platform/nrfconnect/FactoryDataProvider.cpp index 229eb2745d6c91..f25b62fc1b568e 100644 --- a/src/platform/nrfconnect/FactoryDataProvider.cpp +++ b/src/platform/nrfconnect/FactoryDataProvider.cpp @@ -339,6 +339,28 @@ CHIP_ERROR FactoryDataProvider::GetEnableKey(MutableByteSpan & return CHIP_NO_ERROR; } +template +CHIP_ERROR FactoryDataProvider::GetProductFinish(app::Clusters::BasicInformation::ProductFinishEnum * finish) +{ + ReturnErrorCodeIf(!finish, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(!mFactoryData.appearancePresent, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + + *finish = static_cast(mFactoryData.product_appearance.finish); + + return CHIP_NO_ERROR; +} + +template +CHIP_ERROR FactoryDataProvider::GetProductPrimaryColor(app::Clusters::BasicInformation::ColorEnum * primaryColor) +{ + ReturnErrorCodeIf(!primaryColor, CHIP_ERROR_INVALID_ARGUMENT); + ReturnErrorCodeIf(!mFactoryData.appearancePresent, CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND); + + *primaryColor = static_cast(mFactoryData.product_appearance.primary_color); + + return CHIP_NO_ERROR; +} + template CHIP_ERROR FactoryDataProvider::GetUserData(MutableByteSpan & userData) { diff --git a/src/platform/nrfconnect/FactoryDataProvider.h b/src/platform/nrfconnect/FactoryDataProvider.h index c5517917cddde6..368ec957eff5bd 100644 --- a/src/platform/nrfconnect/FactoryDataProvider.h +++ b/src/platform/nrfconnect/FactoryDataProvider.h @@ -107,6 +107,8 @@ class FactoryDataProvider : public chip::Credentials::DeviceAttestationCredentia CHIP_ERROR GetHardwareVersion(uint16_t & hardwareVersion) override; CHIP_ERROR GetHardwareVersionString(char * buf, size_t bufSize) override; CHIP_ERROR GetRotatingDeviceIdUniqueId(MutableByteSpan & uniqueIdSpan) override; + CHIP_ERROR GetProductFinish(app::Clusters::BasicInformation::ProductFinishEnum * finish) override; + CHIP_ERROR GetProductPrimaryColor(app::Clusters::BasicInformation::ColorEnum * primaryColor) override; // ===== Members functions that are platform-specific CHIP_ERROR GetEnableKey(MutableByteSpan & enableKey);