diff --git a/.properties b/.properties
index 5d3c4765cf7..3400f621a17 100644
--- a/.properties
+++ b/.properties
@@ -1,6 +1,6 @@
id=com.silabs.sdk.stack.super
-version=4.2.1
+version=4.2.2
label=Gecko SDK Suite
description=Gecko SDK Suite
diff --git a/app/amazon/amazon_bluetooth_experimental_templates.xml b/app/amazon/amazon_bluetooth_experimental_templates.xml
index ec14e6cd6d1..00c1d7ab0d6 100644
--- a/app/amazon/amazon_bluetooth_experimental_templates.xml
+++ b/app/amazon/amazon_bluetooth_experimental_templates.xml
@@ -6,7 +6,7 @@
-
+
@@ -21,7 +21,7 @@
-
+
@@ -36,9 +36,9 @@
-
+
-
+
diff --git a/app/amazon/amazon_platform_experimental_templates.xml b/app/amazon/amazon_platform_experimental_templates.xml
index 3bc3fb5554b..f7c504e8d75 100644
--- a/app/amazon/amazon_platform_experimental_templates.xml
+++ b/app/amazon/amazon_platform_experimental_templates.xml
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/app/bluetooth/bluetooth_experimental_templates.xml b/app/bluetooth/bluetooth_experimental_templates.xml
index ee9f5b61db0..d6662c6795f 100644
--- a/app/bluetooth/bluetooth_experimental_templates.xml
+++ b/app/bluetooth/bluetooth_experimental_templates.xml
@@ -1,14 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
@@ -21,9 +53,9 @@
-
-
-
+
+
+
@@ -36,9 +68,9 @@
-
+
-
+
@@ -51,9 +83,24 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/bluetooth/bluetooth_production_demos.xml b/app/bluetooth/bluetooth_production_demos.xml
index 0cf5a54c53e..476535c5be2 100644
--- a/app/bluetooth/bluetooth_production_demos.xml
+++ b/app/bluetooth/bluetooth_production_demos.xml
@@ -6,11 +6,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -18,11 +18,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -30,11 +30,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -42,11 +42,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -54,11 +54,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -66,11 +66,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -78,11 +78,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -90,11 +90,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -102,11 +102,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -114,11 +114,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -126,11 +126,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -138,11 +138,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -150,11 +150,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -162,11 +162,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -174,11 +174,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -186,11 +186,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -198,11 +198,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -210,11 +210,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -222,11 +222,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -234,11 +234,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -246,11 +246,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -258,11 +258,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -270,11 +270,11 @@
-
+
-
+
Network Co-Processor (NCP) target application. Runs the Bluetooth stack dynamically and provides access to it via Bluetooth API (BGAPI) using UART connection. NCP mode makes it possible to run your application on a host controller or PC.
@@ -282,11 +282,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -294,11 +294,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -306,11 +306,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -318,11 +318,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -330,11 +330,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -342,11 +342,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -354,11 +354,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -366,11 +366,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -378,11 +378,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -390,11 +390,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -402,11 +402,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -414,11 +414,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -426,11 +426,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -438,11 +438,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -450,11 +450,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -462,11 +462,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -474,11 +474,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -486,11 +486,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -498,11 +498,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -510,11 +510,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -522,11 +522,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -534,11 +534,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -546,11 +546,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -558,11 +558,11 @@
-
+
-
+
The classic blinky example using Bluetooth communication. Demonstrates a simple two-way data exchange over GATT. This can be tested with the EFR Connect mobile app.
@@ -570,11 +570,11 @@
-
+
-
+
Demonstrates the features of the EFR32xG24 Dev Kit Board. This can be tested with the EFR Connect mobile app.
@@ -582,11 +582,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -594,11 +594,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -606,11 +606,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -618,11 +618,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -630,11 +630,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -642,11 +642,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -654,11 +654,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -666,11 +666,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -678,11 +678,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -690,11 +690,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -702,11 +702,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the mock relative humidity and temperature sensor.
@@ -714,11 +714,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -726,11 +726,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -738,11 +738,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -750,11 +750,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -762,11 +762,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -774,11 +774,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -786,11 +786,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -798,11 +798,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -810,11 +810,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -822,11 +822,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -834,11 +834,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -846,11 +846,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -858,11 +858,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -870,11 +870,11 @@
-
+
-
+
Implements a GATT Server with the Health Thermometer Profile, which enables a Client device to connect and get temperature data. Temperature is read from the Si7021 digital relative humidity and temperature sensor of the WSTK or of the Thunderboard.
@@ -882,11 +882,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -894,11 +894,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -906,11 +906,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -918,11 +918,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -930,11 +930,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -942,11 +942,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -954,11 +954,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -966,11 +966,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -978,11 +978,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -990,11 +990,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1002,11 +1002,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1014,11 +1014,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1026,11 +1026,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1038,11 +1038,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1050,11 +1050,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1062,11 +1062,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1074,11 +1074,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1086,11 +1086,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1098,11 +1098,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1110,11 +1110,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1122,11 +1122,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1134,11 +1134,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1146,11 +1146,11 @@
-
+
-
+
This example tests the throughput capabilities of the device and can be used to measure throughput between 2 *EFR32* devices, as well as between a device and a smartphone using EFR Connect mobile app, through the Throughput demo tile.
@@ -1158,11 +1158,11 @@
-
+
-
+
Demonstrates the features of the Thunderboard EFR32BG22 Kit. This can be tested with the EFR Connect mobile app.
@@ -1170,11 +1170,11 @@
-
+
-
+
Demonstrates the features of the Thunderboard EFR32BG22 Kit. This can be tested with the EFR Connect mobile app.
@@ -1182,11 +1182,11 @@
-
+
-
+
Demonstrates the features of the Thunderboard Sense 2 Kit. This can be tested with the EFR Connect mobile app.
@@ -1194,11 +1194,11 @@
-
+
-
+
Voice over Bluetooth Low Energy sample application. It is supported by Thunderboard Sense 2 and Thunderboard EFR32BG22 boards and demonstrates how to send voice data over GATT, which is acquired from the on-board microphones.
@@ -1206,11 +1206,11 @@
-
+
-
+
Voice over Bluetooth Low Energy sample application. It is supported by Thunderboard Sense 2 and Thunderboard EFR32BG22 boards and demonstrates how to send voice data over GATT, which is acquired from the on-board microphones.
@@ -1218,11 +1218,11 @@
-
+
-
+
Voice over Bluetooth Low Energy sample application. It is supported by Thunderboard Sense 2 and Thunderboard EFR32BG22 boards and demonstrates how to send voice data over GATT, which is acquired from the on-board microphones.
@@ -1230,11 +1230,11 @@
-
+
-
+
Sends non-connectable advertisements in iBeacon format. The iBeacon Service gives Bluetooth accessories a simple and convenient way to send iBeacons to smartphones. This example can be tested together with the EFR Connect mobile app.
@@ -1242,7 +1242,7 @@
-
+
@@ -1253,7 +1253,7 @@
-
+
@@ -1264,7 +1264,7 @@
-
+
@@ -1275,7 +1275,7 @@
-
+
@@ -1286,7 +1286,7 @@
-
+
@@ -1297,7 +1297,7 @@
-
+
@@ -1308,7 +1308,7 @@
-
+
@@ -1319,7 +1319,7 @@
-
+
@@ -1330,7 +1330,7 @@
-
+
@@ -1341,7 +1341,7 @@
-
+
@@ -1352,7 +1352,7 @@
-
+
@@ -1363,7 +1363,7 @@
-
+
@@ -1374,7 +1374,7 @@
-
+
@@ -1385,7 +1385,7 @@
-
+
@@ -1396,7 +1396,7 @@
-
+
@@ -1407,7 +1407,7 @@
-
+
@@ -1418,7 +1418,7 @@
-
+
@@ -1429,7 +1429,7 @@
-
+
@@ -1440,7 +1440,7 @@
-
+
@@ -1451,7 +1451,7 @@
-
+
@@ -1462,7 +1462,7 @@
-
+
@@ -1473,7 +1473,7 @@
-
+
@@ -1484,7 +1484,7 @@
-
+
@@ -1495,7 +1495,7 @@
-
+
@@ -1506,7 +1506,7 @@
-
+
@@ -1517,7 +1517,7 @@
-
+
@@ -1528,7 +1528,7 @@
-
+
@@ -1539,11 +1539,11 @@
-
+
-
+
Network Co-Processor (NCP) target application extended with CTE Receiver support. It enables Angle of Arrival (AoA) calculation. Use this application with Direction Finding host examples.
@@ -1551,11 +1551,11 @@
-
+
-
+
This sample app demonstrates a CTE (Constant Tone Extension) transmitter that can be used as an asset tag in a Direction Finding setup estimating Angle of Arrival (AoA).
@@ -1563,11 +1563,11 @@
-
+
-
+
This sample app demonstrates a CTE (Constant Tone Extension) transmitter that can be used as an asset tag in a Direction Finding setup estimating Angle of Arrival (AoA).
@@ -1575,11 +1575,11 @@
-
+
-
+
This sample app demonstrates a CTE (Constant Tone Extension) transmitter that can be used as an asset tag in a Direction Finding setup estimating Angle of Arrival (AoA).
@@ -1587,11 +1587,11 @@
-
+
-
+
Network Co-Processor (NCP) target application extended with CTE Receiver support. It enables Angle of Departure (AoD) calculation. Use this application with Direction Finding Studio tools.
@@ -1599,11 +1599,11 @@
-
+
-
+
Network Co-Processor (NCP) target application extended with CTE Receiver support. It enables Angle of Departure (AoD) calculation. Use this application with Direction Finding Studio tools.
@@ -1611,11 +1611,11 @@
-
+
-
+
Network Co-Processor (NCP) target application extended with CTE Receiver support. It enables Angle of Departure (AoD) calculation. Use this application with Direction Finding Studio tools.
@@ -1623,11 +1623,11 @@
-
+
-
+
This sample app demonstrates a CTE (Constant Tone Extension) transmitter that can be used as a locator beacon in a Direction Finding setup estimating Angle of Departure (AoD).
@@ -1635,11 +1635,11 @@
-
+
-
+
This sample app demonstrates a CTE (Constant Tone Extension) transmitter that can be used as a locator beacon in a Direction Finding setup estimating Angle of Departure (AoD).
@@ -1647,11 +1647,11 @@
-
+
-
+
This is a Dynamic Multiprotocol reference application demonstrating a light bulb that can be switched both via Bluetooth and via a Proprietary protocol. Can be tested with the EFR Connect mobile app and Flex (RAIL) Switch sample app.
@@ -1659,10 +1659,10 @@
-
+
-
+
diff --git a/app/bluetooth/bluetooth_production_templates.xml b/app/bluetooth/bluetooth_production_templates.xml
index be5fe8fc099..adf66ddb123 100644
--- a/app/bluetooth/bluetooth_production_templates.xml
+++ b/app/bluetooth/bluetooth_production_templates.xml
@@ -7,8 +7,8 @@
-
-
+
+
@@ -21,9 +21,9 @@
-
-
-
+
+
+
@@ -36,9 +36,9 @@
-
-
-
+
+
+
@@ -52,8 +52,8 @@
-
-
+
+
@@ -66,9 +66,9 @@
-
+
-
+
@@ -83,7 +83,7 @@
-
+
@@ -96,9 +96,9 @@
-
+
-
+
@@ -111,9 +111,9 @@
-
+
-
+
@@ -126,9 +126,9 @@
-
+
-
+
@@ -141,9 +141,9 @@
-
+
-
+
@@ -156,9 +156,9 @@
-
+
-
+
@@ -173,7 +173,7 @@
-
+
@@ -188,7 +188,7 @@
-
+
@@ -203,7 +203,7 @@
-
+
@@ -218,7 +218,7 @@
-
+
@@ -231,9 +231,9 @@
-
+
-
+
@@ -246,9 +246,9 @@
-
+
-
+
@@ -263,7 +263,7 @@
-
+
@@ -278,7 +278,7 @@
-
+
@@ -293,7 +293,7 @@
-
+
@@ -308,7 +308,7 @@
-
+
@@ -321,9 +321,9 @@
-
+
-
+
@@ -336,9 +336,9 @@
-
+
-
+
@@ -351,9 +351,9 @@
-
+
-
+
@@ -366,9 +366,9 @@
-
+
-
+
@@ -383,7 +383,7 @@
-
+
@@ -396,9 +396,9 @@
-
+
-
+
@@ -413,7 +413,7 @@
-
+
@@ -428,7 +428,7 @@
-
+
@@ -441,9 +441,9 @@
-
+
-
+
@@ -458,7 +458,7 @@
-
+
@@ -471,9 +471,9 @@
-
+
-
+
@@ -486,9 +486,9 @@
-
+
-
+
@@ -501,9 +501,9 @@
-
+
-
+
@@ -518,7 +518,7 @@
-
+
@@ -531,9 +531,9 @@
-
+
-
+
@@ -548,7 +548,7 @@
-
+
@@ -563,7 +563,7 @@
-
+
@@ -578,7 +578,7 @@
-
+
@@ -593,7 +593,7 @@
-
+
@@ -608,7 +608,7 @@
-
+
@@ -623,7 +623,7 @@
-
+
diff --git a/app/bluetooth/common/cbap_lib/lib/cbap_CM33_gcc.a b/app/bluetooth/common/cbap_lib/lib/cbap_CM33_gcc.a
index 4fd4b1df103..9e8ac306b47 100644
--- a/app/bluetooth/common/cbap_lib/lib/cbap_CM33_gcc.a
+++ b/app/bluetooth/common/cbap_lib/lib/cbap_CM33_gcc.a
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:eb6f92796a7b3f5722372086df7d5b43717c318d8f60b1432e370281ceb8c4fe
+oid sha256:10fdeca23d37aa5a1b0e3395a4876d68a335d6b0062346fd3fed0799600441b7
size 5322
diff --git a/app/bluetooth/common/cbap_lib/lib/cbap_CM33_iar.a b/app/bluetooth/common/cbap_lib/lib/cbap_CM33_iar.a
index 42860830f74..89122569035 100644
--- a/app/bluetooth/common/cbap_lib/lib/cbap_CM33_iar.a
+++ b/app/bluetooth/common/cbap_lib/lib/cbap_CM33_iar.a
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:03a2be66479f457d460e73ef2907dfb6e4430c92ae1baadd1cbc2ab0e5ed8c62
+oid sha256:4c03f166fa7e54e175a14e76bc6f8249a0dc39c2cfd7ed1358995f3685abd7ea
size 10542
diff --git a/app/bluetooth/common_host/cpc/cpc.c b/app/bluetooth/common_host/cpc/cpc.c
index fa91e91fdc9..38f32eef72b 100644
--- a/app/bluetooth/common_host/cpc/cpc.c
+++ b/app/bluetooth/common_host/cpc/cpc.c
@@ -49,6 +49,9 @@
static cpc_handle_t lib_handle;
static cpc_endpoint_t endpoint;
+// cpc endpoint fd
+static int ep_sock_fd;
+
// temporary rx buffer
typedef struct {
int32_t len;
@@ -62,7 +65,7 @@ static uint8_t handshake_msg[4] = { 0x20, 0x00, 0x01, 0x00 };
// end the receiving loop if signal is received.
static volatile bool run = true;
// signal if the controller was reset
-static volatile bool has_reset = false;
+static volatile sig_atomic_t has_reset = false;
// two worker threads
static pthread_t sv;
@@ -80,52 +83,71 @@ static void reset_callback(void);
int32_t cpc_open(void *handle, char *cpc_instance, bool reset_on_start)
{
+ (void)handle;
int ret;
- uint8_t retry = 0;
+ ssize_t size;
+ uint8_t retry;
// Initialize CPC communication
+ retry = 0;
do {
ret = cpc_init(&lib_handle, cpc_instance, false, reset_callback);
if (ret == 0) {
- // speed up boot process if everything seems ok
break;
}
nanosleep((const struct timespec[]){ { 0, CPC_RETRY_SLEEP_NS } }, NULL);
retry++;
- } while ((ret != 0) && (retry < RETRY_COUNT));
+ } while (retry < RETRY_COUNT);
if (ret < 0) {
- perror("cpc_init: ");
+ perror("Failed to cpc_init ");
return ret;
}
- // Start Bluetooth endpoint
- ret = cpc_open_endpoint(lib_handle,
- &endpoint,
- SL_CPC_ENDPOINT_BLUETOOTH,
- CPC_TRANSMIT_WINDOW);
+ // Open Bluetooth endpoint
+ retry = 0;
+ do {
+ ret = cpc_open_endpoint(lib_handle,
+ &endpoint,
+ SL_CPC_ENDPOINT_BLUETOOTH,
+ CPC_TRANSMIT_WINDOW);
+ if (ret > 0) {
+ ep_sock_fd = ret;
+ break;
+ }
+ nanosleep((const struct timespec[]){ { 0, CPC_RETRY_SLEEP_NS } }, NULL);
+ retry++;
+ } while (retry < RETRY_COUNT);
+
if (ret < 0) {
- perror("cpc_open_endpoint ");
+ perror("Failed to cpc_open_endpoint ");
return ret;
}
// Create supervisory thread
ret = pthread_create(&sv, NULL, supervisor, NULL);
if (ret) {
- perror("Couldn't create thread ");
+ perror("Failed to create thread ");
return ret;
}
- handle = endpoint.ptr;
-
// Send handshake msg
- (void)cpc_write_endpoint(endpoint, &handshake_msg[0], 4, CPC_ENDPOINT_WRITE_FLAG_NONE);
+ size = cpc_write_endpoint(endpoint, &handshake_msg[0], 4, CPC_ENDPOINT_WRITE_FLAG_NONE);
+ if (size < 0) {
+ perror("Failed to send handshake msg ");
+ return (int)size;
+ }
if (reset_on_start) {
// Discard response
- (void)cpc_read_endpoint(endpoint, &buf_rx.buf, FROM_CPC_BUF_SIZE, CPC_ENDPOINT_READ_FLAG_NONE);
- buf_rx.len = 0;
- memset(buf_rx.buf, 0, FROM_CPC_BUF_SIZE);
+ size = cpc_read_endpoint(endpoint, &buf_rx.buf, FROM_CPC_BUF_SIZE, CPC_ENDPOINT_READ_FLAG_NONE);
+ if (size < 0) {
+ perror("Failed to discard response msg ");
+ return (int)size;
+ } else {
+ buf_rx.len = 0;
+ memset(buf_rx.buf, 0, FROM_CPC_BUF_SIZE);
+ }
}
return ret;
@@ -138,6 +160,9 @@ int32_t cpc_tx(void *handle, uint32_t data_length, uint8_t *data)
if (!has_reset) {
size = cpc_write_endpoint(endpoint, &data[0], data_length, CPC_ENDPOINT_WRITE_FLAG_NONE);
+ if (size < 0) {
+ perror("Failed to cpc_write_endpoint ");
+ }
} else {
// In case of reset we don't care if send was succesfull or not
size = data_length;
@@ -149,6 +174,7 @@ int32_t cpc_tx(void *handle, uint32_t data_length, uint8_t *data)
int32_t cpc_rx(void *handle, uint32_t data_length, uint8_t *data)
{
(void)handle;
+ (void)data_length;
if (buf_rx.len > 0) {
memcpy(data, buf_rx.buf, buf_rx.len);
@@ -160,24 +186,48 @@ int32_t cpc_rx(void *handle, uint32_t data_length, uint8_t *data)
int32_t cpc_rx_peek(void *handle)
{
(void)handle;
+ ssize_t size;
if (!has_reset) {
+ // Return if the endpoint has not been reopened after the reset
+ if (ep_sock_fd <= 0) {
+ return 0;
+ }
+
// Make read blocking - possible because threaded structure in host_comm
- buf_rx.len = (int32_t)cpc_read_endpoint(endpoint, &buf_rx.buf,
- FROM_CPC_BUF_SIZE, CPC_ENDPOINT_READ_FLAG_NONE);
+ size = cpc_read_endpoint(endpoint, &buf_rx.buf,
+ FROM_CPC_BUF_SIZE, CPC_ENDPOINT_READ_FLAG_NONE);
+
+ if (size < 0) {
+ // Swallow EBADF and ECONNRESET because the fd will be invalid while the device is starting up after the reset
+ if (size != -EBADF && size != -ECONNRESET) {
+ perror("Failed to cpc_read_endpoint ");
+ }
+ buf_rx.len = 0;
+ } else {
+ buf_rx.len = (int32_t)size;
+ }
} else {
// If in reset, don't try to read
buf_rx.len = 0;
}
- if (buf_rx.len < 0) {
- buf_rx.len = 0;
- }
+
return buf_rx.len;
}
int32_t cpc_close(void *handle)
{
- return cpc_close_endpoint(&endpoint);
+ (void)handle;
+ int ret;
+
+ ret = cpc_close_endpoint(&endpoint);
+ if (ret == 0) {
+ ep_sock_fd = -1;
+ } else {
+ perror("Failed to cpc_close_endpoint ");
+ }
+
+ return ret;
}
// -----------------------------------------------------------------------------
@@ -197,39 +247,65 @@ static void reset_callback(void)
int reset_cpc(void)
{
int ret;
- uint8_t retry = 0;
+ uint8_t retry;
+ ssize_t size;
app_log_debug("RESET" APP_LOG_NL);
- // Restart cpp communication
+ // Close previously opened Bluetooth endpoint
+ if (ep_sock_fd > 0) {
+ ret = cpc_close_endpoint(&endpoint);
+ if (ret == 0) {
+ ep_sock_fd = -1;
+ } else {
+ perror("Failed to cpc_close_endpoint ");
+ return ret;
+ }
+ }
+
+ // Restart connection to CPCd
+ retry = 0;
do {
ret = cpc_restart(&lib_handle);
if (ret == 0) {
- // speed up boot process if everything seems ok
break;
}
nanosleep((const struct timespec[]){ { 0, CPC_RETRY_SLEEP_NS } }, NULL);
retry++;
- } while ((ret != 0) && (retry < RETRY_COUNT));
+ } while (retry < RETRY_COUNT);
if (ret < 0) {
- perror("cpc restart ");
+ perror("Failed to cpc_restart ");
return ret;
}
// Open Bluetooth endpoint
- ret = cpc_open_endpoint(lib_handle,
- &endpoint,
- SL_CPC_ENDPOINT_BLUETOOTH,
- CPC_TRANSMIT_WINDOW);
+ retry = 0;
+ do {
+ ret = cpc_open_endpoint(lib_handle,
+ &endpoint,
+ SL_CPC_ENDPOINT_BLUETOOTH,
+ CPC_TRANSMIT_WINDOW);
+ if (ret > 0) {
+ ep_sock_fd = ret;
+ break;
+ }
+ nanosleep((const struct timespec[]){ { 0, CPC_RETRY_SLEEP_NS } }, NULL);
+ retry++;
+ } while (retry < RETRY_COUNT);
+
if (ret < 0) {
- perror(" open endpoint ");
+ perror("Failed to cpc_open_endpoint ");
return ret;
}
// Send handshake msg, but don't discard the answer, as upper layers need it.
- cpc_write_endpoint(endpoint, &handshake_msg[0], 4, CPC_ENDPOINT_WRITE_FLAG_NONE);
- has_reset = false;
+ size = cpc_write_endpoint(endpoint, &handshake_msg[0], 4, CPC_ENDPOINT_WRITE_FLAG_NONE);
+ if (size < 0) {
+ perror("Failed to send handshake msg ");
+ return (int)size;
+ }
+
return ret;
}
@@ -238,19 +314,20 @@ int reset_cpc(void)
*****************************************************************************/
void *supervisor(void *ptr)
{
- // unused variable
(void)ptr;
int ret;
while (run) {
if (has_reset) {
+ has_reset = false;
ret = reset_cpc();
if (ret < 0) {
- perror("reset ");
- // better to die here than continue to work falsely
+ perror("Failed to reset_cpc ");
+ // Better to die here than continue to work falsely
exit(EXIT_FAILURE);
}
}
+
nanosleep((const struct timespec[]){ { 0, CPC_RESET_SLEEP_NS } }, NULL);
}
return NULL;
diff --git a/app/bluetooth/common_host/cpc/cpc.h b/app/bluetooth/common_host/cpc/cpc.h
index fd7821a9eeb..adef13770a0 100644
--- a/app/bluetooth/common_host/cpc/cpc.h
+++ b/app/bluetooth/common_host/cpc/cpc.h
@@ -38,7 +38,7 @@
* @param[out] handle eventually it's a socket handle
* @param[in] cpc_instance Instance name of CPCd
* @param[in] reset_on_start Indicates if a reset should be performed on start
- * @return 0 on success, -1 on failure.
+ * @return 0 on success, negative value on failure.
*****************************************************************************/
int32_t cpc_open(void *handle, char *cpc_instance, bool reset_on_start);
@@ -48,7 +48,7 @@ int32_t cpc_open(void *handle, char *cpc_instance, bool reset_on_start);
* @param[in] handle Socket handle
* @param[in] data_length The amount of bytes to write.
* @param[in] data Buffer used for storing the data.
- * @return The amount of bytes written or -1 on failure.
+ * @return The amount of bytes written or negative value on failure.
*****************************************************************************/
int32_t cpc_tx(void *handle, uint32_t data_length, uint8_t *data);
@@ -58,7 +58,7 @@ int32_t cpc_tx(void *handle, uint32_t data_length, uint8_t *data);
* @param[in] handle Socket handle
* @param[in] data_length The amount of bytes to read.
* @param[out] data Buffer used for storing the data.
- * @return The amount of bytes read or -1 on failure.
+ * @return The amount of bytes read or negative value on failure.
*****************************************************************************/
int32_t cpc_rx(void *handle, uint32_t data_length, uint8_t *data);
@@ -66,14 +66,14 @@ int32_t cpc_rx(void *handle, uint32_t data_length, uint8_t *data);
* Return the number of bytes in the input buffer. This call will block until
* there's data in the buffer.
* @param[in] handle Socket handle
- * @return The number of bytes in the input buffer or -1 on failure.
+ * @return The number of bytes in the input buffer or negative value on failure.
*****************************************************************************/
int32_t cpc_rx_peek(void *handle);
/**************************************************************************//**
* Close the CPC connection.
* @param[in] handle Socket handle
- * @return 0 on success, -1 on failure.
+ * @return 0 on success, negative value on failure.
*****************************************************************************/
int32_t cpc_close(void *handle);
diff --git a/app/bluetooth/documentation/slBluetooth_docContent.xml b/app/bluetooth/documentation/slBluetooth_docContent.xml
index e34584ee7c7..096971684d7 100644
--- a/app/bluetooth/documentation/slBluetooth_docContent.xml
+++ b/app/bluetooth/documentation/slBluetooth_docContent.xml
@@ -1,6 +1,6 @@
-
+
Includes detailed information on using the Gecko Bootloader with Silicon Labs Bluetooth applications. It supplements the general Gecko Bootloader implementation information provided in UG489: Silicon Labs Gecko Bootloader User's Guide.
@@ -8,7 +8,7 @@
-
+
Describes the Wi-Fi impact on Bluetooth and methods to improve Bluetooth coexistence with Wi-Fi. Explains design considerations to improve coexistence without direct interaction between Bluetooth and Wi-Fi radios. These techniques are applicable to the EFR32MGx and EFR32BGx series. Discusses the Silicon Labs Packet Traffic Arbitration (PTA) support to coordinate 2.4GHz RF traffic for co-located Bluetooth and Wi-Fi radios.
@@ -16,7 +16,7 @@
-
+
Explains how NVM3 can be used as non-volatile data storage in various protocol implementations.
@@ -24,7 +24,7 @@
-
+
Describes how to lock and unlock the debug access of EFR32 Gecko Series 2 devices. Many aspects of the debug access, including the secure debug unlock are described. The Debug Challenge Interface (DCI) and Secure Engine (SE) Mailbox Interface for locking and unlocking debug access are also included.
@@ -32,7 +32,7 @@
-
+
Contains detailed information on configuring and using the Secure Boot with hardware Root of Trust and Secure Loader on Series 2 devices, including how to provision the signing key. This is a companion document to UG489: Silicon Labs Gecko Bootloader User's Guide.
@@ -40,7 +40,7 @@
-
+
Details on programming, provisioning, and configuring Series 2 devices in production environments. Covers Secure Engine Subsystem of Series 2 devices, which runs easily upgradeable Secure Engine (SE) or Virtual Secure Engine (VSE) firmware.
@@ -48,14 +48,14 @@
-
+
Describes how to measure the power consumption of EFR32BG devices running the Bluetooth i-Beacon example. For general instructions, see AN969: Measuring Power Consumption in Wireless Gecko Devices, available on silabs.com.
-
+
How to program, provision, and configure the anti-tamper module on EFR32 Series 2 devices with Secure Vault.
@@ -63,7 +63,7 @@
-
+
Describes how to configure the NCP target and how to program the NCP host when using the Bluetooth Stack in Network Co-Processor mode
@@ -71,14 +71,14 @@
-
+
Describes how to integrate a v3.x Silicon Labs Bluetooth application with an RTOS, and demonstrate how a time- and event-driven application can be run in parallel with the Bluetooth stack.
-
+
Reviews performing radio frequency physical layer evaluation with EFR32BG SoCs and BGM modules using the Direct Test Mode protocol in Bluetooth SDK v3.x.
@@ -86,7 +86,7 @@
-
+
How to authenticate an EFR32 Series 2 device with Secure Vault, using secure device certificates and signatures.
@@ -94,14 +94,14 @@
-
+
Provides details on how to develop a dynamic multiprotocol application running Bluetooth and a proprietary protocol on RAIL in GSDK v3.x.
-
+
How to securely "wrap" keys in EFR32 Series 2 devices with Secure Vault, so they can be stored in non-volatile storage.
@@ -109,28 +109,28 @@
-
+
Describes the sample applications provided to demonstrate the directing finding capabilities of Bluetooth 5.1. Angle of Arrival (AoA) estimation is demonstrated with the use of Silicon Labs' Real Time Locating (RTL) library. These techniques are applicable to the EFR32MGx and EFR32BGx series.
-
+
Bluetooth 5.1 makes it possible to send Constant Tone Extensions (CTEs) in Bluetooth packets on which phase measurements can be done. This guide is for those implementing custom applications that take advantage of phase measurement and antenna switching capabilites.
-
+
Provides details on designing Bluetooth Low Energy applications with security and privacy in mind.
-
+
Describes how to provision and configure Series 2 devices through the DCI and SWD.
@@ -138,14 +138,14 @@
-
+
Includes the results of the interoperability testing of Silicon Labs' ICs and Bluetooth Low Energy stack with Android and iOS smart phones.
-
+
Describes how to integrate crypto functionality into applications using PSA Crypto compared to Mbed TLS.
@@ -153,7 +153,7 @@
-
+
Describes using Simplicity Studio 5's Network Analyzer to debug Bluetooth Mesh and Low Energy applications. It can be read jointly with AN958: Debugging and Programming Interfaces for Customer Designs for more information on using Packet Trace Interface with custom hardware.
@@ -161,7 +161,7 @@
-
+
Gecko Bootloader v2.x, introduced in GSDK 4.0, contains a number of changes compared to Gecko Bootloader v1.x. This document describes the differences between the versions, including how to configure the new Gecko Bootloader in Simplicity Studio 5.
@@ -169,14 +169,14 @@
-
+
Gives a short overview of the standard Host Controller Interface (HCI) and how to use it with a Silicon Labs Bluetooth LE controller.
-
+
Describes how to run any combination of Zigbee EmberZNet, OpenThread, and Bluetooth networking stacks on a Linux host processor, interfacing with a single EFR32 Radio Co-processor (RCP) with multiprotocol and multi-PAN support, as well as how to run the Zigbee stack on the EFR32 as a network co-processor (NCP) alongside the OpenThread RCP.
@@ -184,84 +184,92 @@
-
+
Summarizes Amazon FreeRTOS components and sample applications, and explains how to use the examples to communicate with the Amazon Web Services (AWS) cloud with a smart phone app.
-
+
Describes how to exploit the different features of Bluetooth technology to achieve the minimum possible energy consumption for a given use case.
-
+
+ Covers the basics of ARMv8-M TrustZone, describes how TrustZone is implemented on Series 2 devices, and provides application examples.
+
+
+
+
+
+
+
Describes the theoretical background of certificate-based authentication and pairing, and demonstrates the usage of the related sample applications that can be found in the Silicon Labs Bluetooth SDK.
-
+
Provides an overview and hyperlinks to all packaged documentation.
-
+
Describes the differences between using Bluetooth SDK v2.x in Simplicity Studio 4 and using Bluetooth SDK v3.x in Simplicity Studio 5. Outlines the steps needed to migrate a v2.x project to v3.x.
-
+
Describes using the Simplicity Studio 5 IDE and tools for application development with Bluetooth SDK v3.x.
-
+
Describes the software components provided by Silicon Labs to support Direction Finding (DF) and provides instructions on how to start developing your own application.
-
+
Contains a comprehensive list of APIs used to interface to the Silicon Labs Bluetooth Real-Time Locating Library.
-
+
Contains a comprehensive list of APIs used to interface to the Silicon Labs Bluetooth stack.
-
+
Lists compatibility requirements and sources for all software components in the development environment. Discusses the latest changes to the Silicon Labs Bluetooth SDK and associated utilities, including added/deleted/deprecated features/API, and lists fixed and known issues.
-
+
Discusses the latest changes to the The Real-Time Locating (RTL) library, including added/deleted/deprecated APIs, and lists fixed and known issues.
-
+
A detailed overview of the changes, additions, and fixes in the Gecko Platform components. The Gecko Platform includes EMLIB, EMDRV, RAIL Library, NVM3, and the component-based infrastructure.
@@ -269,7 +277,7 @@
-
+
Introduces the security concepts that must be considered when implementing an Internet of Things (IoT) system. Using the ioXt Alliance's eight security principles as a structure, it clearly delineates the solutions Silicon Labs provides to support endpoint security and what you must do outside of the Silicon Labs framework.
@@ -277,7 +285,7 @@
-
+
Introduces bootloading for Silicon Labs networking devices. Discusses the Gecko Bootloader as well as legacy Ember and Bluetooth bootloaders, and describes the file formats used by each.
@@ -285,7 +293,7 @@
-
+
Introduces non-volatile data storage using flash and the three different storage implementations offered for Silicon Labs microcontrollers and SoCs: Simulated EEPROM, PS Store, and NVM3.
@@ -293,14 +301,14 @@
-
+
Offers an overview for those new to the Bluetooth low energy technology.
-
+
Describes the four multiprotocol modes, discusses considerations when selecting protocols for multiprotocol implementations, and reviews the Radio Scheduler, a required component of a dynamic multiprotocol solution.
@@ -308,7 +316,7 @@
-
+
Describes methods to improve the coexistence of 2.4 GHz IEEE 802.11b/g/n Wi-Fi and other 2.4 GHz radios such as Bluetooth, Bluetooth Mesh, Bluetooth Low Energy, and IEEE 802.15.4-based radios such as Zigbee and OpenThread.
@@ -316,14 +324,14 @@
-
+
Explains the basics of Bluetooth Angle of Arrival (AoA) and Angle of Departure (AoD) direction finding technologies and provides the theory behind estimating angle of arrival.
-
+
Reviews using this XML-based mark-up language to describe the Bluetooth GATT database, configure access and security properties, and include the GATT database as part of the firmware.
@@ -331,7 +339,7 @@
-
+
Describes how and when to use Simplicity Commander's Command-Line Interface.
@@ -339,7 +347,7 @@
-
+
Describes how to implement a dynamic multiprotocol solution.
@@ -347,14 +355,14 @@
-
+
Covers the Bluetooth stack v3.x architecture, application development flow, using the MCU core and peripherals, stack configuration options, and stack resource usage.
-
+
Describes how to use the Simplicity Studio 5 GATT Configurator, an intuitive interface providing access to all the Profiles, Services, Characteristics, and Descriptors as defined in the Bluetooth specification.
@@ -362,7 +370,7 @@
-
+
Describes the high-level implementation of the Silicon Labs Gecko Bootloader for EFR32 SoCs and NCPs, and provides information on how to get started using the Gecko Bootloader with Silicon Labs wireless protocol stacks in GSDK 4.0 and higher.
@@ -370,7 +378,7 @@
-
+
The Bluetooth Direction Finding Tool Suite is meant to ease development with the Silicon Labs' RTL library. It provides multiple tools to configure the system, and also helps the development with analyzer tools that calculate many output parameters from the observed IQ samples.
diff --git a/app/bluetooth/esf.properties b/app/bluetooth/esf.properties
index eca0412b185..8e8014831f3 100644
--- a/app/bluetooth/esf.properties
+++ b/app/bluetooth/esf.properties
@@ -3,8 +3,8 @@ id=com.silabs.stack.ble
label=Bluetooth SDK
description=Bluetooth Software Development Kit
-version=5.1.0.0
-prop.subLabel=Bluetooth\\ 5.1.0
+version=5.1.1.0
+prop.subLabel=Bluetooth\\ 5.1.1
# Default compatibility of the BLE SDK
prop.boardCompatibility=.*
diff --git a/app/bluetooth/example/bt_soc_blinky/app.c b/app/bluetooth/example/bt_soc_blinky/app.c
index ed57e31edb9..92d22a6fa5f 100644
--- a/app/bluetooth/example/bt_soc_blinky/app.c
+++ b/app/bluetooth/example/bt_soc_blinky/app.c
@@ -166,7 +166,7 @@ void sl_bt_on_event(sl_bt_msg_t *evt)
// database was changed by a remote GATT client.
case sl_bt_evt_gatt_server_attribute_value_id:
// The value of the gattdb_led_control characteristic was changed.
- if (gattdb_led_control == evt->data.evt_gatt_server_characteristic_status.characteristic) {
+ if (gattdb_led_control == evt->data.evt_gatt_server_attribute_value.attribute) {
uint8_t data_recv;
size_t data_recv_len;
diff --git a/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap.slcp b/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap.slcp
index 8556222f376..105f547eb1b 100644
--- a/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap.slcp
+++ b/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap.slcp
@@ -88,7 +88,7 @@ requires:
tag:
- hardware:rf:band:2400
- hardware:device:flash:512
- - hardware:device:ram:32
+ - hardware:device:ram:64
- hardware:component:led:1+
ui_hints:
diff --git a/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap_tz.slcw b/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap_tz.slcw
new file mode 100644
index 00000000000..08677fab8a7
--- /dev/null
+++ b/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap_tz.slcw
@@ -0,0 +1,20 @@
+label: bt_soc_cbap_tz_workspace
+description: >
+ Demonstrates Certificate Based Authentication and Pairing over BLE.
+ This example utilizes TrustZone for storing keys.
+quality: experimental
+project:
+ - path: ../bt_soc_tz_secure_application/bt_soc_tz_secure_application.slcp
+ id: bt_soc_tz_secure_application
+ output: bt_soc_tz_secure_application
+ - path: bt_soc_cbap_tz_ns.slcp
+ id: bt_soc_tz_nonsecure_application
+ output: bt_soc_cbap_tz_ns
+ - path: ../../../../platform/bootloader/sample-apps/workspaces/bootloader-apploader/bootloader-apploader-secure.slcp
+ id: bootloader-apploader-secure
+ output: bootloader-apploader-secure
+ - path: ../../../../platform/bootloader/sample-apps/workspaces/bootloader-apploader/bootloader-apploader-nonsecure.slcp
+ id: bootloader-apploader-nonsecure
+ output: bootloader-apploader-nonsecure
+post_build:
+ profile: bt_tz_workspace
diff --git a/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap_tz_ns.slcp b/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap_tz_ns.slcp
index 4fde740eb45..f1542df802b 100644
--- a/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap_tz_ns.slcp
+++ b/app/bluetooth/example/bt_soc_cbap/bt_soc_cbap_tz_ns.slcp
@@ -39,7 +39,8 @@ component:
- led0
- id: simple_timer
- id: cbap
- - id: bt_post_build
+ - id: trustzone_nonsecure
+ - id: tz_secure_key_library
source:
- path: main.c
@@ -66,6 +67,10 @@ other_file:
- path: image/readme_img1.png
- path: image/readme_img2.png
+import:
+ # Import the config files from the TZ Secure application
+ - id: bt_soc_tz_secure_application
+
configuration:
- name: SL_STACK_SIZE
value: "6200"
@@ -82,17 +87,41 @@ configuration:
- name: APP_LOG_LEVEL_FILTER_THRESHOLD
value: APP_LOG_LEVEL_INFO
+template_contribution:
+ # Offset secure application if bootloader is used
+ - name: memory_flash_start
+ value: 0x2C000
+ condition: [device_sdid_200]
+ - name: memory_flash_start
+ value: 0x2C000
+ condition: [device_sdid_205]
+ - name: memory_flash_start
+ value: 0x802C000
+ unless: [device_sdid_200, device_sdid_205]
+
+ - name: memory_flash_size
+ value: 0x64000 # 400k
+
+ - name: memory_ram_start
+ value: 0x20003000
+ - name: memory_ram_size
+ value: 0x8000 # 32k
+
requires:
- name: armv8m.main # Armv8-M Supports TrustZone
+post_build:
+ profile: tz_nonsecure_application
+
conflicts:
- name: device_security_vault # For secure devices use the other slcp variant
tag:
- hardware:rf:band:2400
- - hardware:device:flash:512
- - hardware:device:ram:32
+ - hardware:device:flash:576
+ - hardware:device:ram:64
- hardware:component:led:1+
+ - companion:secure_app # Needs a companion app and cannot be built alone
ui_hints:
highlight:
diff --git a/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator.slcp b/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator.slcp
index 5a0eb86d30e..7e265b6eacb 100644
--- a/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator.slcp
+++ b/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator.slcp
@@ -104,7 +104,7 @@ requires:
tag:
- hardware:device:flash:512
- - hardware:device:ram:32
+ - hardware:device:ram:64
ui_hints:
highlight:
diff --git a/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator_tz.slcw b/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator_tz.slcw
new file mode 100644
index 00000000000..f9fbbbc7a93
--- /dev/null
+++ b/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator_tz.slcw
@@ -0,0 +1,22 @@
+label: bt_soc_csr_generator_tz_workspace
+description: >
+ Certificate generating firmware workspace. Software is generating the device
+ EC key pair, the signing request for the device certificate, and other related
+ data. The generated data can be read out by the Central Authority.
+ This example utilizes TrustZone for storing keys.
+quality: experimental
+project:
+ - path: ../bt_soc_tz_secure_application/bt_soc_tz_secure_application.slcp
+ id: bt_soc_tz_secure_application
+ output: bt_soc_tz_secure_application
+ - path: bt_soc_csr_generator_tz_ns.slcp
+ id: bt_soc_tz_nonsecure_application
+ output: bt_soc_csr_generator_tz_ns
+ - path: ../../../../platform/bootloader/sample-apps/workspaces/bootloader-apploader/bootloader-apploader-secure.slcp
+ id: bootloader-apploader-secure
+ output: bootloader-apploader-secure
+ - path: ../../../../platform/bootloader/sample-apps/workspaces/bootloader-apploader/bootloader-apploader-nonsecure.slcp
+ id: bootloader-apploader-nonsecure
+ output: bootloader-apploader-nonsecure
+post_build:
+ profile: bt_tz_workspace
diff --git a/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator_tz_ns.slcp b/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator_tz_ns.slcp
index 83201954931..d77ed8f6701 100644
--- a/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator_tz_ns.slcp
+++ b/app/bluetooth/example/bt_soc_csr_generator/bt_soc_csr_generator_tz_ns.slcp
@@ -33,6 +33,8 @@ component:
- id: mpu
- id: nvm3_lib
- id: nvm3_default
+ - id: trustzone_nonsecure
+ - id: tz_secure_key_library
- id: psa_crypto
- id: psa_crypto_cmac
- id: psa_crypto_ccm
@@ -46,7 +48,6 @@ component:
- id: psa_crypto_sha1
- id: sl_system
- id: sleeptimer
- - id: bt_post_build
source:
- path: app.c
@@ -72,6 +73,10 @@ other_file:
- path: image/readme_img1.png
- path: image/readme_img2.png
+import:
+ # Import the config files from the TZ Secure application
+ - id: bt_soc_tz_secure_application
+
configuration:
- name: SL_STACK_SIZE
value: "6200"
@@ -98,16 +103,40 @@ configuration:
condition:
- psa_crypto
+template_contribution:
+ # Offset flash after bootloader and secure app
+ - name: memory_flash_start
+ value: 0x2C000
+ condition: [device_sdid_200]
+ - name: memory_flash_start
+ value: 0x2C000
+ condition: [device_sdid_205]
+ - name: memory_flash_start
+ value: 0x802C000
+ unless: [device_sdid_200, device_sdid_205]
+
+ - name: memory_flash_size
+ value: 0x64000 # 400k
+
+ - name: memory_ram_start
+ value: 0x20003000
+ - name: memory_ram_size
+ value: 0x8000 # 32k
+
requires:
- name: device_supports_bluetooth
- name: armv8m.main # Armv8-M Supports TrustZone
+post_build:
+ profile: tz_nonsecure_application
+
conflicts:
- name: device_security_vault # For secure devices use the other slcp variant
tag:
- - hardware:device:flash:512
- - hardware:device:ram:32
+ - hardware:device:flash:576
+ - hardware:device:ram:64
+ - companion:secure_app # Needs a companion app and cannot be built alone
ui_hints:
highlight:
diff --git a/app/bluetooth/example/bt_soc_csr_generator/config/csr_generator_config.h b/app/bluetooth/example/bt_soc_csr_generator/config/csr_generator_config.h
index 5a42d959ece..7543a592e68 100644
--- a/app/bluetooth/example/bt_soc_csr_generator/config/csr_generator_config.h
+++ b/app/bluetooth/example/bt_soc_csr_generator/config/csr_generator_config.h
@@ -53,9 +53,9 @@
#define CSR_GENERATOR_CONFIG_CERTIFICATE_ON_DEVICE 1
// Certificate Request RAM address
-// Default: 0x20010000
+// Default: 0x2000FC00
// The RAM address where the Certificate Request is stored.
-#define CSR_GENERATOR_CSR_RAM_ADDRESS 0x20010000
+#define CSR_GENERATOR_CSR_RAM_ADDRESS 0x2000FC00
// NVM3 key region to use
// Default: 0x60000
diff --git a/app/bluetooth/example/bt_soc_tz_secure_application/bt_soc_tz_secure_application.slcp b/app/bluetooth/example/bt_soc_tz_secure_application/bt_soc_tz_secure_application.slcp
new file mode 100644
index 00000000000..a0860c44946
--- /dev/null
+++ b/app/bluetooth/example/bt_soc_tz_secure_application/bt_soc_tz_secure_application.slcp
@@ -0,0 +1,81 @@
+project_name: bt_soc_tz_secure_application
+package: Bluetooth
+label: Sample project for TrustZone Secure Key Library
+description: >-
+ This project can be used as a reference implementation
+ for creating secure applications with TrustZone for bluetooth products.
+
+ This project makes a TrustZone secure library for running PSA libraries
+ in the secure world.
+category: Bluetooth Examples
+quality: experimental
+
+filter:
+ - name: "Wireless Technology"
+ value: ["Bluetooth"]
+ - name: "Device Type"
+ value: ["SoC"]
+ - name: "Project Difficulty"
+ value: ["Advanced"]
+
+provides:
+ - name: bt_soc_tz_secure_application
+
+requires:
+ - name: tz_secure_key_library_s
+ - name: armv8m.main # Armv8-M Supports TrustZone
+
+conflicts:
+ - name: device_security_vault # Don't use TrustZone with secure devices
+
+define:
+ # PSA Crypto configuration:
+ - name: TFM_CRYPTO_CONC_OPER_NUM
+ value: 2
+
+template_contribution:
+ - name: application_type
+ value: "APPLICATION_TYPE_MCU"
+ condition:
+ - bootloader_app_properties
+
+ # Offset flash after bootloader
+ - name: memory_flash_start
+ value: 0x14000
+ condition: [device_sdid_200]
+ - name: memory_flash_start
+ value: 0x14000
+ condition: [device_sdid_205]
+ - name: memory_flash_start
+ value: 0x8014000
+ unless: [device_sdid_200, device_sdid_205]
+
+ - name: memory_flash_size
+ value: 0x18000 # 96k
+
+ - name: memory_ram_start
+ value: 0x20000000
+ - name: memory_ram_size
+ value: 0x3000 # 12k
+
+configuration:
+ - name: SL_STACK_SIZE
+ value: '3072'
+ - name: SL_HEAP_SIZE
+ value: '3072'
+ - name: BOOTLOADER_DISABLE_OLD_BOOTLOADER_MITIGATION
+ value: '1'
+
+tag:
+ - companion:nonsecure_app # Needs a companion app and cannot be built alone
+
+import:
+ # Import the config files from the TZ Non-Secure application
+ - id: bt_soc_tz_nonsecure_application
+
+export:
+ library:
+ - path: artifact/trustzone_secure_library.o
+
+post_build:
+ profile: tz_secure_application
diff --git a/app/bluetooth/example_host/bt_host_cpc_hci_bridge/bridge.c b/app/bluetooth/example_host/bt_host_cpc_hci_bridge/bridge.c
index 44a3dc5e1cb..cd6c9f9e05b 100644
--- a/app/bluetooth/example_host/bt_host_cpc_hci_bridge/bridge.c
+++ b/app/bluetooth/example_host/bt_host_cpc_hci_bridge/bridge.c
@@ -81,7 +81,7 @@ static int ep_sock_fd;
// end the receiving loop if signal is received.
static volatile bool run = true;
// signal if the controller was reset
-static volatile bool has_reset = false;
+static volatile sig_atomic_t has_reset = false;
static void reset_callback(void);
static int cpc_poll_fds(void);
@@ -101,34 +101,43 @@ static void signal_handler(int sig)
uint32_t startup(void)
{
int ret;
- uint8_t retry = 0;
+ uint8_t retry;
// Initialize CPC communication
+ retry = 0;
do {
ret = cpc_init(&lib_handle, cpc_instance, false, reset_callback);
if (ret == 0) {
- // speed up boot process if everything seems ok
break;
}
nanosleep((const struct timespec[]){ { 0, CPC_RETRY_SLEEP_NS } }, NULL);
retry++;
- } while ((ret != 0) && (retry < RETRY_COUNT));
+ } while (retry < RETRY_COUNT);
if (ret < 0) {
- perror("cpc_init: ");
- return ret;
+ perror("Failed to cpc_init ");
+ return (uint32_t)ret;
}
- // Start Bluetooth endpoint
- ret = cpc_open_endpoint(lib_handle,
- &endpoint,
- SL_CPC_ENDPOINT_BLUETOOTH_RCP,
- CPC_TRANSMIT_WINDOW);
+ // Open Bluetooth endpoint
+ retry = 0;
+ do {
+ ret = cpc_open_endpoint(lib_handle,
+ &endpoint,
+ SL_CPC_ENDPOINT_BLUETOOTH_RCP,
+ CPC_TRANSMIT_WINDOW);
+ if (ret > 0) {
+ ep_sock_fd = ret;
+ break;
+ }
+ nanosleep((const struct timespec[]){ { 0, CPC_RETRY_SLEEP_NS } }, NULL);
+ retry++;
+ } while (retry < RETRY_COUNT);
+
if (ret < 0) {
- perror("cpc_open_endpoint ");
- return ret;
+ perror("Failed to cpc_open_endpoint ");
+ return (uint32_t)ret;
}
- ep_sock_fd = ret;
// Open virtual UART device
ret = openpty(&pty_m, &pty_s, NULL, NULL, NULL);
@@ -146,7 +155,8 @@ uint32_t startup(void)
strerror(errno));
}
}
- return ret;
+
+ return (uint32_t)ret;
}
/**************************************************************************//**
@@ -163,40 +173,58 @@ static void reset_callback(void)
int reset_cpc(void)
{
int ret;
- uint8_t retry = 0;
+ uint8_t retry;
if (DEBUG) {
printf("\n\nRESET\n\n");
}
- // Restart cpp communication
+ // Close previously opened Bluetooth endpoint
+ if (ep_sock_fd > 0) {
+ ret = cpc_close_endpoint(&endpoint);
+ if (ret == 0) {
+ ep_sock_fd = -1;
+ } else {
+ perror("Failed to cpc_close_endpoint ");
+ return ret;
+ }
+ }
+
+ // Restart connection to CPCd
+ retry = 0;
do {
ret = cpc_restart(&lib_handle);
if (ret == 0) {
- // speed up boot process if everything seems ok
break;
}
nanosleep((const struct timespec[]){ { 0, CPC_RETRY_SLEEP_NS } }, NULL);
retry++;
- } while ((ret != 0) && (retry < RETRY_COUNT));
- has_reset = false;
+ } while (retry < RETRY_COUNT);
if (ret < 0) {
- perror("cpc restart ");
+ perror("Failed to cpc_restart ");
return ret;
}
// Open Bluetooth endpoint
- ret = cpc_open_endpoint(lib_handle,
- &endpoint,
- SL_CPC_ENDPOINT_BLUETOOTH_RCP,
- CPC_TRANSMIT_WINDOW);
+ retry = 0;
+ do {
+ ret = cpc_open_endpoint(lib_handle,
+ &endpoint,
+ SL_CPC_ENDPOINT_BLUETOOTH_RCP,
+ CPC_TRANSMIT_WINDOW);
+ if (ret > 0) {
+ ep_sock_fd = ret;
+ break;
+ }
+ nanosleep((const struct timespec[]){ { 0, CPC_RETRY_SLEEP_NS } }, NULL);
+ retry++;
+ } while (retry < RETRY_COUNT);
if (ret < 0) {
- perror(" open endpoint ");
+ perror("Failed to cpc_open_endpoint ");
}
- ep_sock_fd = ret;
return ret;
}
@@ -226,9 +254,10 @@ int main(int argc, char *argv[])
// Reset cpc communication if daemon signals
while (run) {
if (has_reset) {
+ has_reset = false;
ret = reset_cpc();
if (ret < 0) {
- perror("reset ");
+ perror("Failed to reset_cpc ");
exit(EXIT_FAILURE);
}
} else {
@@ -301,6 +330,7 @@ int cpc_poll_fds(void)
int ret;
int max_fd;
ssize_t size = 0;
+ ssize_t size2 = 0;
uint8_t *hci_packet_buf;
ssize_t hci_packet_len;
struct timeval tv;
@@ -332,8 +362,11 @@ int cpc_poll_fds(void)
printf("data_to_cpc=");
print_buffer(data_to_cpc, hci_packet_len);
}
- cpc_write_endpoint(endpoint, hci_packet_buf, hci_packet_len, 0);
-
+ size2 = cpc_write_endpoint(endpoint, hci_packet_buf, hci_packet_len, 0);
+ if (size2 < 0) {
+ perror("Failed to cpc_write_endpoint");
+ return FAILURE;
+ }
size -= hci_packet_len;
hci_packet_buf += hci_packet_len;
} while (size >= hci_packet_len);
diff --git a/platform/security/postbuild_profile/tz_secure_application.slpb b/app/bluetooth/postbuild_profile/bt_tz_non_secure_application.slpb
similarity index 80%
rename from platform/security/postbuild_profile/tz_secure_application.slpb
rename to app/bluetooth/postbuild_profile/bt_tz_non_secure_application.slpb
index 4fe0dd47584..9da83359cd2 100644
--- a/platform/security/postbuild_profile/tz_secure_application.slpb
+++ b/app/bluetooth/postbuild_profile/bt_tz_non_secure_application.slpb
@@ -4,4 +4,4 @@ steps:
- task: copy
input: "{{build_dir}}/{{project_name}}.s37"
output: "artifact/{{project_name}}.s37"
- export: trustzone_secure_binary
+ export: tz_nonsecure_binary
diff --git a/app/bluetooth/postbuild_profile/bt_tz_secure_application.slpb b/app/bluetooth/postbuild_profile/bt_tz_secure_application.slpb
new file mode 100644
index 00000000000..f40e6cdaad4
--- /dev/null
+++ b/app/bluetooth/postbuild_profile/bt_tz_secure_application.slpb
@@ -0,0 +1,7 @@
+parameters:
+ - name: build_dir
+steps:
+ - task: copy
+ input: "{{build_dir}}/{{project_name}}.s37"
+ output: "artifact/{{project_name}}.s37"
+ export: tz_secure_binary
diff --git a/app/bluetooth/postbuild_profile/bt_tz_workspace.slpb b/app/bluetooth/postbuild_profile/bt_tz_workspace.slpb
new file mode 100644
index 00000000000..2f6994508c2
--- /dev/null
+++ b/app/bluetooth/postbuild_profile/bt_tz_workspace.slpb
@@ -0,0 +1,20 @@
+parameters: []
+constants:
+ - name: workspace_name
+ value: bt_soc_trustzone_workspace
+steps:
+ - task: convert
+ input:
+ - "{{trustzone_secure_binary}}"
+ - "{{trustzone_nonsecure_binary}}"
+ output: artifact/{{workspace_name}}-app-only.s37
+ - task: convert
+ input:
+ - "{{bootloader_secure_app}}"
+ - "{{bootloader_nonsecure_app}}"
+ - "{{trustzone_secure_binary}}"
+ - "{{trustzone_nonsecure_binary}}"
+ output: artifact/{{workspace_name}}-full.s37
+ - task: create_gbl
+ app: "artifact/{{workspace_name}}-app-only.s37"
+ output: artifact/{{workspace_name}}-app-only.gbl
diff --git a/app/bluetooth/script/certificate_authorities/production_line_tool.py b/app/bluetooth/script/certificate_authorities/production_line_tool.py
index cd83db2557c..b3f287d4a17 100644
--- a/app/bluetooth/script/certificate_authorities/production_line_tool.py
+++ b/app/bluetooth/script/certificate_authorities/production_line_tool.py
@@ -89,7 +89,7 @@ def main(level, validity, serial, ip, protocol):
if 'xg22' in family.lower():
ram_start = 0x20000000 # The CSR allocation is different for xG22 devices.
else:
- ram_start = 0x20010000
+ ram_start = 0x2000FC00
# Paths
if level == 0:
diff --git a/app/btmesh/btmesh.properties b/app/btmesh/btmesh.properties
new file mode 100644
index 00000000000..5ef5309966c
--- /dev/null
+++ b/app/btmesh/btmesh.properties
@@ -0,0 +1,23 @@
+# This file provides metadata for the stack directory, as part of ESF.
+id=com.silabs.stack.btMesh
+label=Bluetooth Mesh SDK
+description=Bluetooth Mesh Software Development Kit
+version=4.2.0.0
+prop.subLabel=Bluetooth\\ Mesh\\ 4.2.0
+
+# Default compatibility of the BT Mesh SDK (This is needed for the documentation only)
+prop.boardCompatibility=.*
+prop.partCompatibility=.*efr32bg2(1|2.*f512|4).* .*efr32mg2[1247].* .*efr32[mb]g1[23]p.* .*mgm(1[23]|2[124]).* .*bgm(13|21|22.*hna|24).*
+
+# General properties are prepended with "prop."
+prop.file.templatesFile=btmesh_production_templates.xml btmesh_alpha_templates.xml btmesh_beta_templates.xml btmesh_test_templates.xml btmesh_internal_templates.xml btmesh_srtest_internal_templates.xml btmesh_long_packets_test_internal_templates.xml
+prop.file.docsFile=documentation/slBtMesh_docContent.xml
+prop.file.demosFile= btmesh_production_demos.xml btmesh_alpha_demos.xml btmesh_beta_demos.xml btmesh_test_demos.xml btmesh_internal_demos.xml
+#prop.file.modulesFile=modules.xml
+#prop.file.docsFile=docs.xml
+
+prop.file.bleSDK.bgbuildLocation=../../protocol/bluetooth/bin
+prop.bgbuildToolchain=true
+prop.file.adapterPacks=../../protocol/bluetooth/bin/gatt script/generator
+prop.file.btconfGattItemSourcePath=gatt_xml
+prop.btConfGattItemDatabases=mesh
diff --git a/app/btmesh/btmesh_internal_demos.xml b/app/btmesh/btmesh_internal_demos.xml
new file mode 100644
index 00000000000..5da246a06b5
--- /dev/null
+++ b/app/btmesh/btmesh_internal_demos.xml
@@ -0,0 +1,159 @@
+
+
+
+ Friend example for IOP test. This node acts as a friend for the low power node and caches messages sent to it when the low power node is sleeping.
+
+
+
+
+
+
+
+
+
+
+
+ Friend example for IOP test. This node acts as a friend for the low power node and caches messages sent to it when the low power node is sleeping.
+
+
+
+
+
+
+
+
+
+
+
+ Friend example for IOP test. This node acts as a friend for the low power node and caches messages sent to it when the low power node is sleeping.
+
+
+
+
+
+
+
+
+
+
+
+ Low power node example for IOP test. This node acts as a typical low power device and sleeps most of the time. It needs a friend node to cache messages and forward them when polled.
+
+
+
+
+
+
+
+
+
+
+
+ Low power node example for IOP test. This node acts as a typical low power device and sleeps most of the time. It needs a friend node to cache messages and forward them when polled.
+
+
+
+
+
+
+
+
+
+
+
+ Low power node example for IOP test. This node acts as a typical low power device and sleeps most of the time. It needs a friend node to cache messages and forward them when polled.
+
+
+
+
+
+
+
+
+
+
+
+ Low power node example for IOP test. This node acts as a typical low power device and sleeps most of the time. It needs a friend node to cache messages and forward them when polled.
+
+
+
+
+
+
+
+
+
+
+
+ Proxy example for IOP test. This node forwards/relays messages between GATT and advertising bearers in the network.
+
+
+
+
+
+
+
+
+
+
+
+ Proxy example for IOP test. This node forwards/relays messages between GATT and advertising bearers in the network.
+
+
+
+
+
+
+
+
+
+
+
+ Proxy example for IOP test. This node forwards/relays messages between GATT and advertising bearers in the network.
+
+
+
+
+
+
+
+
+
+
+
+ Relay example for IOP test. This node acts as a relay, i.e. if a node is out of range for another node, it relays messages between the two, provided the relay node is in range for both.
+
+
+
+
+
+
+
+
+
+
+
+ Relay example for IOP test. This node acts as a relay, i.e. if a node is out of range for another node, it relays messages between the two, provided the relay node is in range for both.
+
+
+
+
+
+
+
+
+
+
+
+ Relay example for IOP test. This node acts as a relay, i.e. if a node is out of range for another node, it relays messages between the two, provided the relay node is in range for both.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/btmesh/btmesh_production_demos.xml b/app/btmesh/btmesh_production_demos.xml
new file mode 100644
index 00000000000..3eb87cca982
--- /dev/null
+++ b/app/btmesh/btmesh_production_demos.xml
@@ -0,0 +1,2355 @@
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI. Contains models from BT mesh specification 1.1.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI.
+
+
+
+
+
+
+
+
+
+
+
+ An NCP Target C application that makes it possible for the NCP Host Controller to access the Bluetooth Mesh stack via UART. It provides access to the host layer via BGAPI and not to the link layer via HCI.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the bare minimum needed for an NCP Target C application. This example is recommended for EFR32xG22, which has limited RAM and flash, and therefore some of the stack classes are disabled by default.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Certificate generating firmware example. Software is generating the device EC key pair, the signing request for the device certificate, and other related data. The generated data can be read out by the Central Authority.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ Demonstrates the Firmware Distributor role based on the BT Mesh Model specification. Distributor is responsible for delivering new firmware images to the Updating nodes and monitoring the progress of the firmware update.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the Thunderboard Sense 2 can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, hue, and saturation can be set. The example also tries to establish friendship as a Friend node.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the mainboard can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the Thunderboard Sense 2 board can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box software demo where the LEDs of the WSTK can be switched on and off, and their lighting intensity, color temperature, and delta UV can be set.
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Client Model. It collects and displays sensor measurement data from remote device(s) (e.g., btmesh_soc_sensor_server).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature, people count, and illuminance, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature, people count, and illuminance, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature, people count, and illuminance, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature, people count, and illuminance, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ This example demonstrates the Bluetooth Mesh Sensor Server Model and Sensor Setup Server Model. It measures temperature and people count, and sends the measurement data to a remote device (e.g., btmesh_soc_sensor_client).
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ A Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD. Button presses (only PB0 is functional) can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ A Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD. Button presses (only PB0 is functional) can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ A Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD. Button presses (only PB0 is functional) can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ A Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD. Button presses (only PB0 is functional) can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ A Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD. Button presses (only PB0 is functional) can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD.Push Button presses can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ A Software Demo where the device acts as a switch. It is optimized for low current consumption with disabled CLI, logging, and LCD. Button presses (only PB0 is functional) can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses (only PB0 is functional) or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses (only PB0 is functional) or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses (only PB0 is functional) or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses (only PB0 is functional) or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses (only PB0 is functional) or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
+ An out-of-the-box Software Demo where the device acts as a switch. Push Button presses (only PB0 is functional) or CLI commands can control the state, lightness, and color temperature of the LEDs and scenes on a remote device.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/btmesh/btmesh_production_templates.xml b/app/btmesh/btmesh_production_templates.xml
new file mode 100644
index 00000000000..bfeef64cd80
--- /dev/null
+++ b/app/btmesh/btmesh_production_templates.xml
@@ -0,0 +1,708 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/btmesh/btmesh_upgrade_fail.lua b/app/btmesh/btmesh_upgrade_fail.lua
deleted file mode 100644
index d16c3f413ae..00000000000
--- a/app/btmesh/btmesh_upgrade_fail.lua
+++ /dev/null
@@ -1,3 +0,0 @@
-if slc.is_selected("btmesh_stack") == true then
- error("Because the BT Mesh SDK is not in this GSDK release, upgrading your project would remove the BT Mesh stack from it. Please cancel.")
-end
\ No newline at end of file
diff --git a/app/btmesh/common/app_btmesh_util/app_btmesh_util.c b/app/btmesh/common/app_btmesh_util/app_btmesh_util.c
new file mode 100644
index 00000000000..4edf44f5c12
--- /dev/null
+++ b/app/btmesh/common/app_btmesh_util/app_btmesh_util.c
@@ -0,0 +1,205 @@
+/***************************************************************************//**
+ * @file
+ * @brief App BT Mesh Utility
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "nvm3.h"
+#include "sl_status.h"
+#include "app_btmesh_util.h"
+
+/// Handler for NVM3, use default
+#define SL_BTMESH_NVM3 nvm3_defaultHandle
+/// Start of the BT mesh NVM3 region keys
+#define SL_BTMESH_NVM3_REGION 0
+/// The maximum LSBs of the BT mesh NVM3 region keys
+#define SL_BTMESH_NVM3_END 0xFFFF
+
+/**
+ * Converts @ref Ecode_t to @ref sl_status_t
+ *
+ * @param ec Error code coming from NVM3 API calls
+ * @return The appropriate value the input is converted to. Some cases that
+ * would be hard to make sense of in higher layers are merged into
+ * @ref SL_STATUS_FAIL.
+ */
+static sl_status_t ecode2sc(Ecode_t ec);
+
+sl_status_t app_btmesh_nvm_init(void)
+{
+ return ecode2sc(nvm3_initDefault());
+}
+
+sl_status_t app_btmesh_nvm_read(uint16_t key, void *buf, size_t *len)
+{
+ uint32_t nvm3_objecttype;
+ // Store input buffer length temporarily
+ size_t tmp = *len;
+ // Read length of the data
+ Ecode_t nvm3_ec = nvm3_getObjectInfo(SL_BTMESH_NVM3,
+ SL_BTMESH_NVM3_REGION | (uint32_t)key,
+ &nvm3_objecttype,
+ len);
+
+ // If would be too long, fail
+ if (*len > tmp) {
+ return SL_STATUS_WOULD_OVERFLOW;
+ }
+
+ // If failure has occurred, return proper error code
+ if (ECODE_NVM3_OK != nvm3_ec) {
+ return ecode2sc(nvm3_ec);
+ }
+
+ // Read the data into the buffer based on the read length
+ nvm3_ec = nvm3_readData(SL_BTMESH_NVM3,
+ SL_BTMESH_NVM3_REGION | (uint32_t)key,
+ buf,
+ *len);
+
+ return ecode2sc(nvm3_ec);
+}
+
+sl_status_t app_btmesh_nvm_write(uint16_t key,
+ const void *buf,
+ size_t len)
+{
+ Ecode_t nvm3_ec = nvm3_writeData(SL_BTMESH_NVM3,
+ SL_BTMESH_NVM3_REGION | (uint32_t)key,
+ buf, len);
+
+ return ecode2sc(nvm3_ec);
+}
+
+sl_status_t app_btmesh_nvm_erase(uint16_t key)
+{
+ Ecode_t nvm3_ec = nvm3_deleteObject(SL_BTMESH_NVM3,
+ SL_BTMESH_NVM3_REGION | key);
+
+ return ecode2sc(nvm3_ec);
+}
+
+sl_status_t app_btmesh_nvm_erase_all(void)
+{
+ Ecode_t nvm3_ec = nvm3_eraseAll(SL_BTMESH_NVM3);
+
+ return ecode2sc(nvm3_ec);
+}
+
+static sl_status_t ecode2sc(Ecode_t ec)
+{
+ switch (ec) {
+ case ECODE_NVM3_OK:
+ return SL_STATUS_OK;
+ case ECODE_NVM3_ERR_SIZE_TOO_SMALL:
+ case ECODE_NVM3_ERR_NO_VALID_PAGES:
+ case ECODE_NVM3_ERR_RESIZE_NOT_ENOUGH_SPACE:
+ return SL_STATUS_NO_MORE_RESOURCE;
+ case ECODE_NVM3_ERR_PAGE_SIZE_NOT_SUPPORTED:
+ case ECODE_NVM3_ERR_PARAMETER:
+ case ECODE_NVM3_ERR_WRITE_DATA_SIZE:
+ case ECODE_NVM3_ERR_OBJECT_SIZE_NOT_SUPPORTED:
+ case ECODE_NVM3_ERR_RESIZE_PARAMETER:
+ case ECODE_NVM3_ERR_ADDRESS_RANGE:
+ return SL_STATUS_INVALID_PARAMETER;
+ case ECODE_NVM3_ERR_STORAGE_FULL:
+ return SL_STATUS_FULL;
+ case ECODE_NVM3_ERR_NOT_OPENED:
+ return SL_STATUS_INVALID_STATE;
+ case ECODE_NVM3_ERR_OPENED_WITH_OTHER_PARAMETERS:
+ return SL_STATUS_ALREADY_INITIALIZED;
+ case ECODE_NVM3_ERR_KEY_INVALID:
+ case ECODE_NVM3_ERR_INT_KEY_MISMATCH:
+ return SL_STATUS_INVALID_KEY;
+ case ECODE_NVM3_ERR_KEY_NOT_FOUND:
+ return SL_STATUS_NOT_FOUND;
+ case ECODE_NVM3_ERR_ERASE_FAILED:
+ return SL_STATUS_FLASH_ERASE_FAILED;
+ case ECODE_NVM3_ERR_WRITE_FAILED:
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ default:
+ return SL_STATUS_FAIL;
+ }
+}
+
+const char *app_btmesh_uuid_64_to_string(char *buffer,
+ uint32_t buffer_size,
+ const sl_bt_uuid_64_t *uuid_64,
+ char separator,
+ bool uppercase)
+{
+ return app_btmesh_bytes_to_hex(buffer,
+ buffer_size,
+ uuid_64->data,
+ sizeof(uuid_64->data),
+ separator,
+ uppercase);
+}
+
+const char *app_btmesh_bytes_to_hex(char *buffer,
+ uint32_t buffer_size,
+ const uint8_t *bytes,
+ uint32_t bytes_size,
+ char separator,
+ bool uppercase)
+{
+ const char hex_char_uppercase[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ const char hex_char_lowercase[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+ const char *hex_char = uppercase ? hex_char_uppercase : hex_char_lowercase;
+ uint32_t total_size, total_separator_size;
+ const char *hex_str = "INVALID";
+
+ // If no input array is provided or the length of the input array is zero then
+ // an empty string is returned unless the output buffer is invalid (e.g. NULL)
+ bytes_size = (bytes != NULL) ? bytes_size : 0;
+
+ // If the separator is set the null character then no separator is used so
+ // the multiplication by zero results in zero total separator size.
+ // There is a separator after each two hex characters except for the last two.
+ total_separator_size = ((0 < bytes_size) ? (bytes_size - 1) : 0)
+ * (separator != '\0');
+
+ // One character shall be reserved for the terminating null character
+ total_size = 2 * bytes_size + total_separator_size + 1;
+
+ if ((buffer != NULL) && (total_size <= buffer_size)) {
+ uint32_t out_idx = 0;
+ for (uint32_t in_idx = 0; in_idx < bytes_size; in_idx++) {
+ buffer[out_idx++] = hex_char[(bytes[in_idx] >> 4) & 0xF];
+ buffer[out_idx++] = hex_char[(bytes[in_idx] >> 0) & 0xF];
+ if (separator != '\0' && (in_idx + 1) < bytes_size) {
+ buffer[out_idx++] = separator;
+ }
+ }
+ buffer[out_idx] = '\0';
+ hex_str = buffer;
+ }
+ return hex_str;
+}
diff --git a/app/btmesh/common/app_btmesh_util/app_btmesh_util.h b/app/btmesh/common/app_btmesh_util/app_btmesh_util.h
new file mode 100644
index 00000000000..907829fc33c
--- /dev/null
+++ b/app/btmesh/common/app_btmesh_util/app_btmesh_util.h
@@ -0,0 +1,412 @@
+/***************************************************************************//**
+ * @file
+ * @brief App BT Mesh Utility
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef APP_BTMESH_UTIL_H
+#define APP_BTMESH_UTIL_H
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#include "app_btmesh_util_config.h"
+#include "sl_status.h"
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * @addtogroup App BT Mesh Utility
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @def app_btmesh_util_log_status_f
+ * @brief Logs the btmesh API failures
+ *
+ * There are two special errors which are related to missing model configuration.
+ * If the BT Mesh configurator (e.g. mobile app) does not bind application key
+ * to the model or does not configure the model publication data then publish
+ * or other API calls which leads to message sending fails.
+ * From the BT Mesh node perspective it can't be determined if the configurator
+ * missed these configuration steps on purpose or not so these errors are logged
+ * at a less severe log level.
+ ******************************************************************************/
+#if defined(SL_CATALOG_APP_LOG_PRESENT)
+#define app_btmesh_util_log_status_f(sc, ...) \
+ do { \
+ switch (sc) { \
+ case SL_STATUS_OK: \
+ break; \
+ case SL_STATUS_BT_MESH_APP_KEY_NOT_BOUND: \
+ app_log_status_level_f(APP_BTMESH_UTIL_LOG_LEVEL_APP_KEY_NOT_BOUND, \
+ sc, \
+ __VA_ARGS__); \
+ break; \
+ case SL_STATUS_BT_MESH_PUBLISH_NOT_CONFIGURED: \
+ app_log_status_level_f(APP_BTMESH_UTIL_LOG_LEVEL_PUBLISH_NOT_CONF, \
+ sc, \
+ __VA_ARGS__); \
+ break; \
+ default: \
+ app_log_status_level_f(APP_LOG_LEVEL_ERROR, sc, __VA_ARGS__); \
+ break; \
+ } \
+ } while (0)
+#else
+#define app_btmesh_util_log_status_f(sc, ...) (void)(sc)
+#endif
+
+// This macro calculates the number of precompile logging enable request in the
+// specific c file where the this header file is included from
+#define APP_BTMESH_UTIL_COMPONENT_LOGGING \
+ (SL_BTMESH_BLOB_STORAGE_LOGGING_CFG_VAL \
+ + SL_BTMESH_BLOB_TRANSFER_CLIENT_LOGGING_CFG_VAL \
+ + SL_BTMESH_BLOB_TRANSFER_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_CTL_CLIENT_LOGGING_CFG_VAL \
+ + SL_BTMESH_CTL_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_FRIEND_LOGGING_CFG_VAL \
+ + SL_BTMESH_FW_DIST_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_FW_UPDATE_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_GENERIC_ONOFF_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_HSL_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_IV_UPDATE_LOGGING_CFG_VAL \
+ + SL_BTMESH_LC_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_LIGHTING_CLIENT_LOGGING_CFG_VAL \
+ + SL_BTMESH_LIGHTING_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_LPN_LOGGING_CFG_VAL \
+ + SL_BTMESH_PROVISIONEE_LOGGING_CFG_VAL \
+ + SL_BTMESH_PROVISIONING_DECORATOR_LOGGING_CFG_VAL \
+ + SL_BTMESH_REMOTE_PROVISIONING_CLIENT_LOGGING_CFG_VAL \
+ + SL_BTMESH_REMOTE_PROVISIONING_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_SCENE_CLIENT_LOGGING_CFG_VAL \
+ + SL_BTMESH_SCHEDULER_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_SELF_PROVISIONING_CLI_LOGGING_CFG_VAL \
+ + SL_BTMESH_SENSOR_CLIENT_LOGGING_CFG_VAL \
+ + SL_BTMESH_SENSOR_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_TIME_SERVER_LOGGING_CFG_VAL \
+ + SL_BTMESH_VENDOR_LOOPBACK_LOGGING_CFG_VAL)
+// Component A shall not include the config file of another component B because
+// _LOGGING macro of component B could turn on the logging in the
+// component A unnecessarily. This is important in case of components with log.
+// Warning is emitted to notify the developer to resolve the include problem.
+#if (1 < APP_BTMESH_UTIL_COMPONENT_LOGGING)
+#warning "Multiple BT Mesh component logging macros are defined in one file."
+#endif
+
+// Defines the local log macros for components based on the component configuration.
+// The app_btmesh_util.h file shall be included after the component configuration
+// header file include which defines the _LOGGING macro.
+// Warning! The app_btmesh_util.h header file is context sensitive so it behaves
+// differently in different c files based on the file specific value of
+// the _LOGGING macros so this header shall not be included
+// from other header files.
+#if defined(SL_CATALOG_APP_LOG_PRESENT) && (APP_BTMESH_UTIL_COMPONENT_LOGGING != 0)
+#define log_nl() app_log_nl()
+#define log(...) app_log(__VA_ARGS__)
+#define log_level(level, ...) app_log_level(level, __VA_ARGS__)
+#define log_debug(...) app_log_debug(__VA_ARGS__)
+#define log_info(...) app_log_info(__VA_ARGS__)
+#define log_warning(...) app_log_warning(__VA_ARGS__)
+#define log_error(...) app_log_error(__VA_ARGS__)
+#define log_critical(...) app_log_critical(__VA_ARGS__)
+#define log_append(...) app_log_append(__VA_ARGS__)
+#define log_append_level(level, ...) app_log_append_level(level, __VA_ARGS__)
+#define log_append_debug(...) app_log_append_debug(__VA_ARGS__)
+#define log_append_info(...) app_log_append_info(__VA_ARGS__)
+#define log_append_warning(...) app_log_append_warning(__VA_ARGS__)
+#define log_append_error(...) app_log_append_error(__VA_ARGS__)
+#define log_append_critical(...) app_log_append_critical(__VA_ARGS__)
+#define log_status_debug(sc) app_log_status_debug(sc)
+#define log_status_info(sc) app_log_status_info(sc)
+#define log_status_warning(sc) app_log_status_warning(sc)
+#define log_status_error(sc) app_log_status_error(sc)
+#define log_status_critical(sc) app_log_status_critical(sc)
+#define log_status_debug_f(sc, ...) app_log_status_debug_f(sc, __VA_ARGS__)
+#define log_status_info_f(sc, ...) app_log_status_info_f(sc, __VA_ARGS__)
+#define log_status_warning_f(sc, ...) app_log_status_warning_f(sc, __VA_ARGS__)
+#define log_status_error_f(sc, ...) app_log_status_error_f(sc, __VA_ARGS__)
+#define log_status_critical_f(sc, ...) app_log_status_critical_f(sc, __VA_ARGS__)
+#define log_status(sc) app_log_status(sc)
+#define log_status_level(level, sc) app_log_status_level(level, sc)
+#define log_status_f(sc, ...) app_log_status_f(sc, __VA_ARGS__)
+#define log_status_level_f(level, sc, ...) app_log_status_level_f(level, sc, __VA_ARGS__)
+#define log_btmesh_status_f(sc, ...) app_btmesh_util_log_status_f(sc, __VA_ARGS__)
+#define log_hexdump_debug(p_data, len) app_log_hexdump_debug(p_data, len)
+#define log_hexdump_info(p_data, len) app_log_hexdump_info(p_data, len)
+#define log_hexdump_warning(p_data, len) app_log_hexdump_warning(p_data, len)
+#define log_hexdump_error(p_data, len) app_log_hexdump_error(p_data, len)
+#define log_hexdump_critical(p_data, len) app_log_hexdump_critical(p_data, len)
+#define log_hexdump_level(level, p_data, len) app_log_hexdump_level(level, p_data, len)
+#define log_hexdump_debug_s(separator, p_data, len) app_log_hexdump_debug_s(separator, p_data, len)
+#define log_hexdump_info_s(separator, p_data, len) app_log_hexdump_info_s(separator, p_data, len)
+#define log_hexdump_warning_s(separator, p_data, len) app_log_hexdump_warning_s(separator, p_data, len)
+#define log_hexdump_error_s(separator, p_data, len) app_log_hexdump_error_s(separator, p_data, len)
+#define log_hexdump_critical_s(separator, p_data, len) app_log_hexdump_critical_s(separator, p_data, len)
+#define log_hexdump_level_s(level, separator, p_data, len) app_log_hexdump_level_s(level, separator, p_data, len)
+#else
+#define log_nl()
+#define log(...)
+#define log_level(level, ...)
+#define log_debug(...)
+#define log_info(...)
+#define log_warning(...)
+#define log_error(...)
+#define log_critical(...)
+#define log_append(...)
+#define log_append_level(level, ...)
+#define log_append_debug(...)
+#define log_append_info(...)
+#define log_append_warning(...)
+#define log_append_error(...)
+#define log_append_critical(...)
+#define log_status_debug(sc) (void)(sc)
+#define log_status_info(sc) (void)(sc)
+#define log_status_warning(sc) (void)(sc)
+#define log_status_error(sc) (void)(sc)
+#define log_status_critical(sc) (void)(sc)
+#define log_status_debug_f(sc, ...) (void)(sc)
+#define log_status_info_f(sc, ...) (void)(sc)
+#define log_status_warning_f(sc, ...) (void)(sc)
+#define log_status_error_f(sc, ...) (void)(sc)
+#define log_status_critical_f(sc, ...) (void)(sc)
+#define log_status(sc) (void)(sc)
+#define log_status_level(level, sc) (void)(sc)
+#define log_status_f(sc, ...) (void)(sc)
+#define log_status_level_f(level, sc, ...) (void)(sc)
+#define log_btmesh_status_f(sc, ...) (void)(sc)
+#define log_hexdump_debug(p_data, len)
+#define log_hexdump_info(p_data, len)
+#define log_hexdump_warning(p_data, len)
+#define log_hexdump_error(p_data, len)
+#define log_hexdump_critical(p_data, len)
+#define log_hexdump_level(level, p_data, len)
+#define log_hexdump_debug_s(separator, p_data, len)
+#define log_hexdump_info_s(separator, p_data, len)
+#define log_hexdump_warning_s(separator, p_data, len)
+#define log_hexdump_error_s(separator, p_data, len)
+#define log_hexdump_critical_s(separator, p_data, len)
+#define log_hexdump_level_s(level, separator, p_data, len)
+#endif // defined(SL_CATALOG_APP_LOG_PRESENT) && (APP_BTMESH_UTIL_COMPONENT_LOGGING != 0)
+
+/// Maximum size of UUID64 string representation with separators (including null char)
+#define APP_BTMESH_UUID_64_STR_MAX_SIZE 24
+
+// Log new line shortcut definition
+#define NL APP_LOG_NL
+
+/***************************************************************************//**
+ * Provides string representation of UUID64 in a compound literal
+ *
+ * It is guaranteed that this macro returns a valid string even in case of
+ * invalid parameters.
+ *
+ * @warning This macro calls @ref app_btmesh_uuid_64_to_string function with a
+ * compound literal which means the lifetime of returned string representation
+ * is the same as the enclosing block so the return value shall not be referenced
+ * outside of the most enclosing block where this macro is called from.
+ * For example the macro value shall not be returned from the caller function.
+ *
+ * This macro can be used in printf-like calls (e.g. log calls) directly because
+ * it returns a string under every circumstances and the lifetime of compound
+ * literal is not problematic if the value is passed as a parameter directly
+ * to another function.
+ *
+ * @param[in] uuid_64 Input UUID64 which shall be converted to string
+ * @param[in] separator Separator character to be used between two hex characters.
+ * If the separator is the null character then the no separator is used.
+ * @param[in] uppercase If true hex characters are upper case otherwise lower case
+ *
+ * @return String representation of UUID64 which has enclosing block lifetime
+ * @retval "INVALID" if an error occurs due to invalid parameters
+ ******************************************************************************/
+#define APP_BTMESH_UUID_64_TO_STRING(uuid_64, separator, uppercase) \
+ app_btmesh_uuid_64_to_string((char[APP_BTMESH_UUID_64_STR_MAX_SIZE]){ 0 }, \
+ sizeof((char[APP_BTMESH_UUID_64_STR_MAX_SIZE]){ 0 }), \
+ uuid_64, \
+ separator, \
+ uppercase)
+
+/***************************************************************************//**
+ * Convert progress into percentage, based on target and current values
+ *
+ * @param _target Target value
+ * @param _progress Current value
+ * @retval Float percentage of the progress
+ ******************************************************************************/
+#define SL_PROG_TO_PCT(_target, _progress) \
+ (((float)(_progress)) / ((float)(_target)) * 100.0f)
+
+/***************************************************************************//**
+ * Convert progress into percentage, based on target and current values
+ *
+ * @param _target Target value
+ * @param _progress Current value
+ * @retval Integer percentage of the progress
+ ******************************************************************************/
+#define SL_PROG_TO_PCT_INT(_target, _progress) \
+ (((_progress) * 100) / (_target) )
+
+/***************************************************************************//**
+ * Silicon Laboratories Company ID as integer
+ ******************************************************************************/
+#define SLI_SILABS_CID_INT 0x02FF
+
+/***************************************************************************//**
+ * Silicon Laboratories Company ID as little-endian string
+ ******************************************************************************/
+#define SLI_SILABS_CID_STR_LE "\xFF\x02"
+
+/***************************************************************************//**
+ * Silicon Laboratories Company ID as big-endian string
+ ******************************************************************************/
+#define SLI_SILABS_CID_STR_BE "\x02\xFF"
+
+/***************************************************************************//**
+ * Initializes the NVM interface.
+ *
+ * Calls the default initializer of NVM3.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_ALREADY_INITIALIZED In case it was initialized earlier
+ * with parameters different from default
+ *
+ ******************************************************************************/
+sl_status_t app_btmesh_nvm_init(void);
+
+/***************************************************************************//**
+ * Reads data from NVM.
+ *
+ * @param[in] key Identifier of the data.
+ * @param[out] buf Buffer to the data to read.
+ * @param[inout] len Pointer to the length of the read data. As in input it
+ * takes the length of buffer. As an output it contains the
+ * length of the read data.
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_STATE In case NVM has not been initialized
+ * @retval SL_STATUS_INVALID_KEY In case key is invalid
+ * @retval SL_STATUS_NOT_FOUND In case key has not been found
+ * @retval SL_STATUS_INVALID_PARAMETER In case any of the input parameters is
+ * invalid
+ ******************************************************************************/
+sl_status_t app_btmesh_nvm_read(uint16_t key, void *buf, size_t *len);
+
+/***************************************************************************//**
+ * Writes data into the NVM.
+ *
+ * @param[in] key Identifier of the data.
+ * @param[in] buf Buffer to the data to write.
+ * @param[in] len Length of the data in the buffer.
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_STATE In case NVM has not been initialized
+ * @retval SL_STATUS_FULL In case NVM is full
+ * @retval SL_STATUS_NO_MORE_RESOURCE In case NVM doesn't have the resources
+ * to store the data
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case writing to NVM has failed
+ * @retval SL_STATUS_INVALID_PARAMETER In case any of the input parameters is
+ * invalid
+ ******************************************************************************/
+sl_status_t app_btmesh_nvm_write(uint16_t key, const void *buf, size_t len);
+
+/***************************************************************************//**
+ * Erases data from NVM.
+ *
+ * @param[in] key Identifier of the data.
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_STATE In case NVM has not been initialized
+ * @retval SL_STATUS_INVALID_KEY In case key is invalid
+ * @retval SL_STATUS_NOT_FOUND In case key has not been found
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case writing to NVM has failed
+ * @retval SL_STATUS_INVALID_PARAMETER In case any of the input parameters is
+ * invalid
+ ******************************************************************************/
+sl_status_t app_btmesh_nvm_erase(uint16_t key);
+
+/***************************************************************************//**
+ * Erases all NVM data.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_STATE In case NVM has not been initialized
+ * @retval SL_STATUS_INVALID_PARAMETER In case any of the input parameters is
+ * invalid
+ ******************************************************************************/
+sl_status_t app_btmesh_nvm_erase_all(void);
+
+/***************************************************************************//**
+ * Provides string representation of UUID64
+ *
+ * It is guaranteed that this function returns a valid string even in case of
+ * invalid parameters.
+ *
+ * @param[out] buffer Output buffer where the string representation is written
+ * @param[in] buffer_size Size of output buffer
+ * @param[in] uuid_64 Input UUID64 which shall be converted to string
+ * @param[in] separator Separator character to be used between two hex characters.
+ * If the separator is the null character then no separator is used.
+ * @param[in] uppercase If true hex characters are upper case otherwise lower case
+ *
+ * @return Output buffer which contains the string representation of UUID64
+ * @retval "INVALID" if an error occurs due to invalid parameters
+ ******************************************************************************/
+const char *app_btmesh_uuid_64_to_string(char *buffer,
+ uint32_t buffer_size,
+ const sl_bt_uuid_64_t *uuid_64,
+ char separator,
+ bool uppercase);
+
+/***************************************************************************//**
+ * Provides hex representation of byte array as a string
+ *
+ * It is guaranteed that this function returns a valid string even in case of
+ * invalid parameters.
+ *
+ * @param[out] buffer Output buffer where the hex representation is written
+ * @param[in] buffer_size Size of output buffer
+ * @param[in] bytes Input byte array which shall be converted to hex
+ * @param[in] bytes_size Size of input byte array
+ * @param[in] separator Separator character to be used between two hex characters.
+ * If the separator is the null character then no separator is used.
+ * @param[in] uppercase If true hex characters are upper case otherwise lower case
+ *
+ * @return Output buffer which contains the hex representation of byte array
+ * @retval "INVALID" if an error occurs due to invalid parameters
+ ******************************************************************************/
+const char *app_btmesh_bytes_to_hex(char *buffer,
+ uint32_t buffer_size,
+ const uint8_t *bytes,
+ uint32_t bytes_size,
+ char separator,
+ bool uppercase);
+
+/** @} (end addtogroup App BT Mesh Utility) */
+
+#endif /* APP_BTMESH_UTIL_H */
diff --git a/app/btmesh/common/app_btmesh_util/config/app_btmesh_util_config.h b/app/btmesh/common/app_btmesh_util/config/app_btmesh_util_config.h
new file mode 100644
index 00000000000..610452c292b
--- /dev/null
+++ b/app/btmesh/common/app_btmesh_util/config/app_btmesh_util_config.h
@@ -0,0 +1,59 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef APP_BTMESH_UTIL_CONFIG_H
+#define APP_BTMESH_UTIL_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// App BT Mesh Utility configuration
+
+// Log Level of not bound appkey BT Mesh error
+// DEBUG
+// INFO
+// WARNING
+// ERROR
+// CRITICAL
+// Default: DEBUG
+#define APP_BTMESH_UTIL_LOG_LEVEL_APP_KEY_NOT_BOUND APP_LOG_LEVEL_DEBUG
+
+// Log Level of not configured publish BT Mesh error
+// DEBUG
+// INFO
+// WARNING
+// ERROR
+// CRITICAL
+// Default: DEBUG
+#define APP_BTMESH_UTIL_LOG_LEVEL_PUBLISH_NOT_CONF APP_LOG_LEVEL_DEBUG
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // APP_BTMESH_UTIL_CONFIG_H
diff --git a/app/btmesh/common/btmesh_ae_client/btmesh_ae_client.dcd b/app/btmesh/common/btmesh_ae_client/btmesh_ae_client.dcd
new file mode 100644
index 00000000000..21cd29d9d4f
--- /dev/null
+++ b/app/btmesh/common/btmesh_ae_client/btmesh_ae_client.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "vendor_models" : [
+ {"cid": "0x02ff", "mid": "0xfffc", "name": "Silabs Configuration Client"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_ae_client/sl_btmesh_ae_client.c b/app/btmesh/common/btmesh_ae_client/sl_btmesh_ae_client.c
new file mode 100644
index 00000000000..b73d6a044a8
--- /dev/null
+++ b/app/btmesh/common/btmesh_ae_client/sl_btmesh_ae_client.c
@@ -0,0 +1,62 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Advertisement Extension Client
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#include "app_assert.h"
+
+#include "sl_btmesh_ae_client.h"
+
+/***************************************************************************//**
+ * @addtogroup ae_client BT Mesh Advertisement Extension Client
+ * @{
+ ******************************************************************************/
+void sl_btmesh_ae_client_on_event(const sl_btmesh_msg_t *const evt)
+{
+ sl_status_t sc;
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id: {
+ sc = sl_btmesh_silabs_config_client_init();
+ app_assert_status_f(sc, "Failed to init AE client");
+ break;
+ }
+ case sl_btmesh_evt_node_initialized_id: {
+ if (0 != evt->data.evt_node_initialized.provisioned) {
+ sc = sl_btmesh_silabs_config_client_init();
+ app_assert_status_f(sc, "Failed to init AE client");
+ }
+ break;
+ }
+ }
+}
+
+/** @} end ae_client */
diff --git a/app/btmesh/common/btmesh_ae_client/sl_btmesh_ae_client.h b/app/btmesh/common/btmesh_ae_client/sl_btmesh_ae_client.h
new file mode 100644
index 00000000000..3d2292a7116
--- /dev/null
+++ b/app/btmesh/common/btmesh_ae_client/sl_btmesh_ae_client.h
@@ -0,0 +1,60 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Advertisement Extension Client
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_AE_CLIENT_H
+#define SL_BTMESH_AE_CLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup ae_client BT Mesh Advertisement Extension Client
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Handle Advertisement Extension Client events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ *
+ ******************************************************************************/
+void sl_btmesh_ae_client_on_event(const sl_btmesh_msg_t *const evt);
+
+/** @} end ae_client */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // SL_BTMESH_AE_CLIENT_H
diff --git a/app/btmesh/common/btmesh_ae_server/btmesh_ae_server.dcd b/app/btmesh/common/btmesh_ae_server/btmesh_ae_server.dcd
new file mode 100644
index 00000000000..8b00d7a7c02
--- /dev/null
+++ b/app/btmesh/common/btmesh_ae_server/btmesh_ae_server.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "vendor_models" : [
+ {"cid": "0x02ff", "mid": "0xfffd", "name": "Silabs Configuration Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_ae_server/sl_btmesh_ae_server.c b/app/btmesh/common/btmesh_ae_server/sl_btmesh_ae_server.c
new file mode 100644
index 00000000000..67636362b8d
--- /dev/null
+++ b/app/btmesh/common/btmesh_ae_server/sl_btmesh_ae_server.c
@@ -0,0 +1,62 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Advertisement Extension Server
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#include "app_assert.h"
+
+#include "sl_btmesh_ae_server.h"
+
+/***************************************************************************//**
+ * @addtogroup ae_server BT Mesh Advertisement Extension Server
+ * @{
+ ******************************************************************************/
+void sl_btmesh_ae_server_on_event(const sl_btmesh_msg_t *const evt)
+{
+ sl_status_t sc;
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id: {
+ sc = sl_btmesh_silabs_config_server_init();
+ app_assert_status_f(sc, "Failed to init AE server");
+ break;
+ }
+ case sl_btmesh_evt_node_initialized_id: {
+ if (0 != evt->data.evt_node_initialized.provisioned) {
+ sc = sl_btmesh_silabs_config_server_init();
+ app_assert_status_f(sc, "Failed to init AE server");
+ }
+ break;
+ }
+ }
+}
+
+/** @} end ae_server */
diff --git a/app/btmesh/common/btmesh_ae_server/sl_btmesh_ae_server.h b/app/btmesh/common/btmesh_ae_server/sl_btmesh_ae_server.h
new file mode 100644
index 00000000000..ce0327da446
--- /dev/null
+++ b/app/btmesh/common/btmesh_ae_server/sl_btmesh_ae_server.h
@@ -0,0 +1,60 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Advertisement Extension Server
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_AE_SERVER_H
+#define SL_BTMESH_AE_SERVER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup ae_server BT Mesh Advertisement Extension Server
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Handle Advertisement Extension Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ *
+ ******************************************************************************/
+void sl_btmesh_ae_server_on_event(const sl_btmesh_msg_t *const evt);
+
+/** @} end ae_server */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // SL_BTMESH_AE_SERVER_H
diff --git a/app/btmesh/common/btmesh_blob_storage/config/sl_btmesh_blob_storage_config.h b/app/btmesh/common/btmesh_blob_storage/config/sl_btmesh_blob_storage_config.h
new file mode 100644
index 00000000000..af8cdaeb941
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_storage/config/sl_btmesh_blob_storage_config.h
@@ -0,0 +1,29 @@
+#ifndef SL_BTMESH_BLOB_STORAGE_CONFIG_H
+#define SL_BTMESH_BLOB_STORAGE_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Logging
+#define SL_BTMESH_BLOB_STORAGE_LOGGING_CFG_VAL 0
+
+// Alignment
+// <1=> Byte (1)
+// <2=> Word (2)
+// <4=> Double Word (4)
+// <8=> Quad Word (8)
+#define SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL 8
+
+// Data check interval <1-255>
+// When checking whether a slot is empty, occupied, or corrupted, use this interval to jump ahead in memory.
+// Higher value results in faster operation, but there's a probability of it yielding false results.
+#define SL_BTMESH_BLOB_STORAGE_DATA_CHECK_JUMP_CFG_VAL 255
+
+// Async delete separation time (ms) <0-1000>
+// Separation time between two asynchronous delete steps in milliseconds.
+// The BLOB storage async delete is performed in multiple steps and each delete step blocks the code execution.
+// If delete steps are executed too often then it could starve other timing sensitive SW components.
+#define SL_BTMESH_BLOB_STORAGE_ASYNC_DELETE_SEPARATION_TIME_MS_CFG_VAL 10
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_BLOB_STORAGE_CONFIG_H
diff --git a/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage.c b/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage.c
new file mode 100644
index 00000000000..339258c111d
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage.c
@@ -0,0 +1,1203 @@
+/***************************************************************************//**
+ * @file
+ * @brief Implementation of BLOB storage component
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include
+#include
+
+#include "sl_common.h"
+
+#include "sl_malloc.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+#include "app_assert.h"
+
+#include "btl_interface.h"
+#include "btl_interface_storage.h"
+#include "sl_bgapi.h"
+#include "sl_status.h"
+
+#include "sl_btmesh_blob_storage.h"
+#include "sl_btmesh_blob_storage_app_id.h"
+#include "sl_btmesh_blob_storage_config.h"
+#include "sli_btmesh_blob_storage.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup blob_storage BLOB Storage
+ * @{
+ ******************************************************************************/
+
+/// Align the input value to the following word barrier
+#define sli_blob_storage_align_to_next_word(_val) \
+ (((_val) + SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL - 1) \
+ & (~(SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL - 1)))
+/// Align the input value to the previous word barrier
+#define sli_blob_storage_align_to_prev_word(_val) \
+ ((_val) & (~(SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL - 1)))
+/// Calculates the number of bytes over alignment
+#define sli_blob_storage_word_align_remainder(_val) \
+ ((_val) % SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL)
+/// Calculates the number of bytes remaining until the next aligned value
+#define sli_blob_storage_word_align_pad_length(_val) \
+ (SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL \
+ - sli_blob_storage_word_align_remainder(_val))
+
+#define sli_calc_max_blob_size(slot_size) \
+ (sizeof(blob_storage_footer_t) < (slot_size)) \
+ ? ((slot_size) - sizeof(blob_storage_footer_t)) : 0
+
+/// Version of the storage implementation, stored in footer as validity flag
+#define STORAGE_VERSION 2
+/// Value to be used as padding for application specific footers
+#define APP_FOOTER_PADDING_PATTERN 0xAA
+/// Buffer size for @ref blob_storage_compare
+#define COMPARE_BUFFER_SIZE 64
+/// The biggest alignment supported
+#define MAX_ALIGNMENT 8
+
+/// Structure of the application specific footer metadata
+PACKSTRUCT(typedef struct {
+ /// Application identifier to identify footer user
+ uint16_t app_id;
+ /// Length of the footer and this metadata
+ uint16_t length;
+}) blob_storage_app_footer_metadata_t;
+
+/// Footer of a BLOB stored at the end of the slot
+PACKSTRUCT(typedef struct {
+ /// Validity flag
+ uint8_t validity_flag[MAX_ALIGNMENT];
+ /// Identifier of BLOB in storage slot
+ sl_bt_uuid_64_t blob_id;
+ /// Size of BLOB in storage slot
+ uint32_t blob_size;
+ /// Reserved
+ uint8_t rfu0;
+ /// Reserved
+ uint8_t rfu1;
+ /// Reserved
+ uint8_t rfu2;
+ /// Footer version
+ uint8_t version;
+}) blob_storage_footer_t;
+
+static struct {
+ /// Index of the currently written slot (i.e. Slot ID)
+ uint32_t current_index;
+ /// Number of bytes written during the current write process
+ uint32_t written_bytes;
+ /// Flag indicating whether padding has already been done
+ ///
+ /// Padding is used to align written data to needed length. Can occur only once
+ /// during writing into one slot.
+ bool padding;
+ /// Length of the cache
+ uint32_t cache_length;
+ /// Pointer to the slot data cache
+ sl_btmesh_blob_storage_slot_metadata_cache_t *slot_cache;
+} blob_storage = { .slot_cache = NULL }; ///< Storage API state representation
+
+/***************************************************************************//**
+ * Check the current status of a slot identified by Slot ID
+ *
+ * @param[in] slot_id The ID of the slot inquired about
+ *
+ * @return Status of the slot
+ * @retval EMPTY In case the slot doesn't have any data
+ * @retval CORRUPTED In case data is present but footer is invalid or data is
+ * not present, but footer is not empty.
+ * @retval OCCUPIED In case data is present and footer is valid.
+ * @retval INTERNAL_ERROR In case of error in underlying API
+ ******************************************************************************/
+static sl_btmesh_blob_storage_status_t blob_storage_check_slot_status(uint32_t slot_id);
+
+/***************************************************************************//**
+ * Read the footer into the buffer.
+ *
+ * @note The footer need not be valid for the function to return with a valid
+ * footer position.
+ *
+ * @param[out] footer Buffer for the footer
+ * @param[in] slot_id The ID of the slot holding the footer
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL In case of internal error
+ * @retval SL_STATUS_INVALID_SIGNATURE In case version is invalid
+ ******************************************************************************/
+static sl_status_t blob_storage_get_footer(blob_storage_footer_t *footer,
+ uint32_t slot_id);
+
+/***************************************************************************//**
+ * Retrieve a pointer to the footer metadata contained in the slot identified
+ * and having the given application identifier.
+ *
+ * @param[in] slot_id The ID of a BLOB corresponding to the footer
+ * @param[in] app_id The ID of the application corresponding to the footer
+ * @param[out] metadata Buffer for the metadata
+ * @param[out] offset Offset of the metadata
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND If app_id and slot_id doesn't identify any footer
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+static sl_status_t blob_storage_get_app_footer_metadata(uint32_t slot_id,
+ uint16_t app_id,
+ blob_storage_app_footer_metadata_t *metadata,
+ uint32_t *offset);
+
+/***************************************************************************//**
+ * Provide the supported maximum BLOB size in the slot
+ *
+ * @param[in] slot_id The ID of the slot inquired about
+ *
+ * @return Maximum supported BLOB size in the slot
+ ******************************************************************************/
+static uint32_t blob_storage_get_max_blob_size_in_slot(uint32_t slot_id);
+
+/***************************************************************************//**
+ * Compare specific part of a bootloader storage slot with a RAM buffer
+ *
+ * @param[in] slot_id Identifier of the storage slot to be compared
+ * @param[in] slot_offset Offset in the slot where the comparison shall be started
+ * @param[in] expected_data Expected data which shall be compared to storage slot data
+ * @param[in] length Number of compared bytes
+ * @param[out] comparison_result Comparison result shall be interpreted as memcmp
+ *
+ * @return Status of the comparison
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+static sl_status_t blob_storage_compare(uint32_t slot_id,
+ uint32_t slot_offset,
+ uint8_t const *expected_data,
+ uint32_t length,
+ int32_t *comparison_result);
+
+void sl_btmesh_blob_storage_init(void)
+{
+ BootloaderStorageInformation_t info;
+ // Initialize Bootloader
+ int32_t bootloader_status = bootloader_init();
+ app_assert(bootloader_status == BOOTLOADER_OK,
+ "Bootloader initialization failed, error code %ld!",
+ bootloader_status);
+ // Read storage information from Bootloader
+ bootloader_getStorageInfo(&info);
+
+ // Extract number of slots
+ blob_storage.cache_length = info.numStorageSlots;
+
+ if (NULL == blob_storage.slot_cache) {
+ // Allocate memory to store cached data about slots
+ blob_storage.slot_cache =
+ sl_calloc(blob_storage.cache_length,
+ sizeof(sl_btmesh_blob_storage_slot_metadata_cache_t));
+ } else {
+ // Free and reallocate
+ // Using free and calloc instead of realloc, because calloc clears memory
+ sl_free(blob_storage.slot_cache);
+ blob_storage.slot_cache =
+ sl_calloc(blob_storage.cache_length,
+ sizeof(sl_btmesh_blob_storage_slot_metadata_cache_t));
+ }
+
+ app_assert(blob_storage.slot_cache != NULL, "Allocation error!");
+
+ // Set write related status parameters to unknown, i.e. 0xFFFFFFFF
+ blob_storage.current_index = UINT32_MAX;
+ blob_storage.written_bytes = UINT32_MAX;
+
+ // Sync
+ sli_btmesh_blob_storage_sync();
+
+ sli_btmesh_blob_storage_erase_init();
+}
+
+void sl_btmesh_blob_storage_deinit(void)
+{
+ // Free allocated memory
+ sl_free(blob_storage.slot_cache);
+ // Clear status variables
+ memset(&blob_storage, 0, sizeof(blob_storage));
+}
+
+sl_status_t sl_btmesh_blob_storage_write_start(sl_bt_uuid_64_t const *blob_id,
+ uint32_t size)
+{
+ if (sl_btmesh_blob_storage_get_erase_error_code()
+ == SL_BTMESH_BLOB_STORAGE_DELETE_BUSY) {
+ return SL_STATUS_BUSY;
+ }
+
+ sl_status_t ret_val = SL_STATUS_NO_MORE_RESOURCE;
+
+ // Check if BLOB with same ID exists
+ if (UINT32_MAX != sli_btmesh_blob_storage_get_slot_id(blob_id)) {
+ return SL_STATUS_ALREADY_EXISTS;
+ }
+
+ BootloaderStorageSlot_t slot;
+ for (uint32_t i = 0; i < blob_storage.cache_length; ++i) {
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_EMPTY
+ == blob_storage.slot_cache[i].status) {
+ bootloader_getStorageSlotInfo(i, &slot);
+ // Check if footer and data would fit
+ if (size > (slot.length - sizeof(blob_storage_footer_t))) {
+ ret_val = SL_STATUS_WOULD_OVERFLOW;
+ continue;
+ }
+ // Copy BLOB ID into cache
+ memcpy(&blob_storage.slot_cache[i].blob_id,
+ blob_id,
+ sizeof(sl_bt_uuid_64_t));
+ // Store BLOB size
+ blob_storage.slot_cache[i].blob_size = size;
+ // Initialize write variables
+ blob_storage.written_bytes = 0;
+ blob_storage.current_index = i;
+ blob_storage.padding = false;
+ ret_val = SL_STATUS_OK;
+ log_debug("BLOB Storage write start; slot %lu, address 0x%08lX, "
+ "length 0x%08lX" NL,
+ blob_storage.current_index,
+ slot.address,
+ slot.length);
+ break;
+ }
+ }
+ if (ret_val != SL_STATUS_OK) {
+ log_warning("BLOB Storage write start, empty slot not available"NL);
+ }
+
+ return ret_val;
+}
+
+sl_status_t sl_btmesh_blob_storage_write(uint32_t offset,
+ uint32_t len,
+ void *data)
+{
+ log_debug("BLOB Storage write; offset 0x%08lX, "
+ "length %lu cache status %d" NL,
+ offset,
+ len, blob_storage.slot_cache[blob_storage.current_index].status);
+ // Write data into flash
+ if (BOOTLOADER_OK
+ != bootloader_writeStorage(blob_storage.current_index,
+ offset,
+ data,
+ // Truncate length to alignment (if needed)
+ sli_blob_storage_align_to_prev_word(len))) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+ // Check whether length had to be truncated
+ if (0 != sli_blob_storage_word_align_remainder(len)) {
+ uint8_t buffer[SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL];
+ // Padding is only done once at the end of a BLOB. If already padded,
+ // inputs are wrong.
+ app_assert_s(!blob_storage.padding);
+ // Indicate padding
+ blob_storage.padding = true;
+ // Fill buffer with padding
+ memset(buffer, UINT8_MAX, SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL);
+ // Copy remainder data into buffer
+ memcpy(buffer,
+ &((uint8_t *)data)[sli_blob_storage_align_to_prev_word(len)],
+ sli_blob_storage_word_align_remainder(len));
+ // Write (padded) data into flash
+ if (BOOTLOADER_OK
+ != bootloader_writeStorage(blob_storage.current_index,
+ offset
+ + sli_blob_storage_align_to_prev_word(len),
+ buffer,
+ SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL)) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+ }
+ blob_storage.written_bytes += len;
+ blob_storage.slot_cache[blob_storage.current_index].status = SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED;
+ return SL_STATUS_OK;
+}
+
+void sl_btmesh_blob_storage_get_cache(sl_btmesh_blob_storage_slot_metadata_cache_t const **cache,
+ uint32_t *len)
+{
+ *cache = blob_storage.slot_cache;
+ *len = blob_storage.cache_length;
+}
+
+sl_status_t sl_btmesh_blob_storage_verify(void)
+{
+ if (UINT32_MAX == blob_storage.current_index) {
+ return SL_STATUS_INVALID_STATE;
+ }
+ // Check whether written length equals the received size of BLOB
+ if (blob_storage.written_bytes
+ != blob_storage.slot_cache[blob_storage.current_index].blob_size) {
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ blob_storage_footer_t footer;
+ memset(&footer, UINT8_MAX, sizeof(blob_storage_footer_t));
+
+ // Copy BLOB ID into footer
+ memcpy(&footer.blob_id,
+ &blob_storage.slot_cache[blob_storage.current_index].blob_id,
+ sizeof(sl_bt_uuid_64_t));
+
+ footer.blob_size =
+ blob_storage.slot_cache[blob_storage.current_index].blob_size;
+ footer.version = STORAGE_VERSION;
+
+ BootloaderStorageSlot_t slot;
+ if (BOOTLOADER_OK
+ != bootloader_getStorageSlotInfo(blob_storage.current_index, &slot)) {
+ return SL_STATUS_FAIL;
+ }
+
+ if (BOOTLOADER_OK != bootloader_writeStorage(blob_storage.current_index,
+ slot.length - sizeof(blob_storage_footer_t),
+ (uint8_t*)&footer,
+ sizeof(blob_storage_footer_t)) ) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+ // Resync
+ sli_btmesh_blob_storage_sync();
+ log_debug("BLOB Storage write verified status: %d"NL, blob_storage.slot_cache[blob_storage.current_index].status);
+ // Clear status
+ blob_storage.current_index = UINT32_MAX;
+ blob_storage.written_bytes = UINT32_MAX;
+ return SL_STATUS_OK;
+}
+
+bool sl_btmesh_blob_storage_is_present(sl_bt_uuid_64_t const *blob_id)
+{
+ return (UINT32_MAX != sli_btmesh_blob_storage_get_slot_id(blob_id));
+}
+
+sl_status_t sl_btmesh_blob_storage_get_blob_size(sl_bt_uuid_64_t const *blob_id,
+ uint32_t *blob_size)
+{
+ uint32_t slot_id = sli_btmesh_blob_storage_get_slot_id(blob_id);
+ if (UINT32_MAX == slot_id) {
+ return SL_STATUS_NOT_FOUND;
+ }
+
+ *blob_size = blob_storage.slot_cache[slot_id].blob_size;
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_storage_read(sl_bt_uuid_64_t const *blob_id,
+ uint32_t offset,
+ uint32_t *len,
+ void *buffer)
+{
+ uint32_t slot_id = sli_btmesh_blob_storage_get_slot_id(blob_id);
+ int32_t r; // Bootloader result
+
+ // If read operation would read over BLOB data, truncate length
+ if (blob_storage.slot_cache[slot_id].blob_size < offset + *len) {
+ *len = blob_storage.slot_cache[slot_id].blob_size - offset;
+ }
+
+ r = bootloader_readStorage(slot_id, offset, buffer, *len);
+
+ switch (r) {
+ case BOOTLOADER_OK:
+ return SL_STATUS_OK;
+ case BOOTLOADER_ERROR_STORAGE_INVALID_SLOT:
+ return SL_STATUS_NOT_FOUND;
+ default:
+ return SL_STATUS_FAIL;
+ }
+}
+
+sl_status_t sl_btmesh_blob_storage_invalidate(sl_bt_uuid_64_t const *blob_id)
+{
+ uint32_t slot_id;
+ sl_status_t sc;
+ slot_id = sli_btmesh_blob_storage_get_slot_id(blob_id);
+
+ if (UINT32_MAX == slot_id) {
+ return SL_STATUS_NOT_FOUND;
+ }
+
+ sc = sli_blob_storage_invalidate_slot(slot_id, false);
+
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_storage_invalidate_by_owner(uint16_t owner_id)
+{
+ sl_btmesh_blob_storage_status_t slot_status;
+ sl_status_t sc_all = SL_STATUS_OK;
+
+ for (uint32_t slot_id = 0; slot_id < blob_storage.cache_length; ++slot_id) {
+ slot_status = blob_storage.slot_cache[slot_id].status;
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED == slot_status) {
+ sl_bt_uuid_64_t *blob_id = &blob_storage.slot_cache[slot_id].blob_id;
+ // Invalidate only those BLOBs which belongs to the specific owner
+ if (sl_btmesh_blob_storage_is_managed_by_owner(blob_id, owner_id)) {
+ sl_status_t sc = sli_blob_storage_invalidate_slot(slot_id, false);
+ // The first error code is returned but all storage slot invalidation
+ // is requested because other slot invalidation might succeed
+ if ((SL_STATUS_OK != sc) && (SL_STATUS_OK == sc_all)) {
+ sc_all = sc;
+ }
+ }
+ }
+ }
+ return sc_all;
+}
+
+sl_status_t sl_btmesh_blob_storage_invalidate_all(void)
+{
+ sl_btmesh_blob_storage_status_t slot_status;
+ sl_status_t sc_all = SL_STATUS_OK;
+
+ for (uint32_t slot_id = 0; slot_id < blob_storage.cache_length; ++slot_id) {
+ slot_status = blob_storage.slot_cache[slot_id].status;
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED == slot_status) {
+ sl_status_t sc = sli_blob_storage_invalidate_slot(slot_id, false);
+ // The first error code is returned but all storage slot invalidation
+ // is requested because other slot invalidation might succeed
+ if ((SL_STATUS_OK != sc) && (SL_STATUS_OK == sc_all)) {
+ sc_all = sc;
+ }
+ }
+ }
+ return sc_all;
+}
+
+uint32_t sl_btmesh_blob_storage_calc_app_footer_size(uint16_t footer_data_length)
+{
+ return sli_blob_storage_align_to_next_word((uint32_t) footer_data_length
+ + sizeof(blob_storage_app_footer_metadata_t));
+}
+
+sl_status_t sl_btmesh_blob_storage_write_app_footer(sl_bt_uuid_64_t const * blob_id,
+ void const * footer,
+ uint16_t app_id,
+ uint16_t length)
+{
+ uint32_t slot_id = sli_btmesh_blob_storage_get_slot_id(blob_id);
+ BootloaderStorageSlot_t slot;
+ uint32_t offset;
+ SL_ATTRIBUTE_ALIGN(SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL) blob_storage_app_footer_metadata_t metadata;
+ uint16_t write_length;
+ const uint16_t remainder_length =
+ sli_blob_storage_word_align_remainder(length);
+ const uint16_t aligned_length = sli_blob_storage_align_to_prev_word(length);
+ const uint16_t padded_length = sli_blob_storage_align_to_next_word(length);
+
+ // Buffer to be used for padding the data, so no allocation is required
+ SL_ATTRIBUTE_ALIGN(SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL) uint8_t buffer[SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL];
+
+ if (length == UINT16_MAX) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+
+ if (BOOTLOADER_OK != bootloader_getStorageSlotInfo(slot_id, &slot)) {
+ return SL_STATUS_FAIL;
+ }
+
+ // Get offset of first possible footer metadata
+ offset = slot.length
+ - sizeof(blob_storage_footer_t)
+ - sizeof(blob_storage_app_footer_metadata_t);
+
+ bootloader_readStorage(slot_id,
+ offset,
+ (uint8_t*)&metadata,
+ sizeof(blob_storage_app_footer_metadata_t));
+
+ // Length is at the end of the footer. If flash is clear it'll be all ones.
+ while (metadata.length != UINT16_MAX) {
+ // Offset must be word aligned
+ offset -= sl_btmesh_blob_storage_calc_app_footer_size(metadata.length);
+ // Read out footer metadata
+ bootloader_readStorage(slot_id,
+ offset,
+ (uint8_t*)&metadata,
+ sizeof(blob_storage_app_footer_metadata_t));
+ // Check if we happen to venture into the BLOB itself
+ if (offset < blob_storage.slot_cache[slot_id].blob_size) {
+ return SL_STATUS_WOULD_OVERFLOW;
+ }
+ }
+
+ // Calculate full length of data to be written
+ write_length = sl_btmesh_blob_storage_calc_app_footer_size(length);
+
+ // Calculate offset relative to the start of the empty footer metadata slot
+ offset -= write_length - sizeof(blob_storage_app_footer_metadata_t);
+
+ // If write start offset is inside BLOB
+ if (offset < blob_storage.slot_cache[slot_id].blob_size) {
+ return SL_STATUS_WOULD_OVERFLOW;
+ }
+
+ // Assemble footer metadata
+ metadata.app_id = app_id;
+ metadata.length = length;
+
+ // If pointer is aligned, write it right away
+ if (((uintptr_t)footer) % SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL == 0) {
+ if (BOOTLOADER_OK
+ != bootloader_writeStorage(slot_id, offset, (uint8_t*)footer, aligned_length)) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+ } else {
+ // If pointer is unaligned, use intermediary buffer, which is aligned
+ for (uint32_t written = 0; written < aligned_length; written +=
+ SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL) {
+ memcpy(buffer,
+ &((uint8_t*)footer)[written],
+ SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL);
+
+ if (BOOTLOADER_OK
+ != bootloader_writeStorage(slot_id, offset + written, buffer,
+ SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL)) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+ }
+ }
+
+ // Initialize padded buffer
+ memset(buffer,
+ APP_FOOTER_PADDING_PATTERN,
+ SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL);
+
+ // Copy data into padded buffer
+ memcpy(buffer,
+ ((uint8_t *)footer) + aligned_length,
+ remainder_length);
+
+ // If alignment is shorter than the metadata, just write the padded data
+ if ((SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL
+ < sizeof(blob_storage_app_footer_metadata_t))
+ // If metadata wouldn't fit into the padded buffer, write padded data
+ || (remainder_length > SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL
+ - sizeof(blob_storage_app_footer_metadata_t))) {
+ if (BOOTLOADER_OK != bootloader_writeStorage(slot_id,
+ offset + aligned_length,
+ buffer,
+ SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL)) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+
+ // Reinitialize padded buffer
+ memset(buffer,
+ APP_FOOTER_PADDING_PATTERN,
+ SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL);
+ }
+
+ // If metadata wouldn't fit into the buffer, it's sure to be aligned, just write it
+ if (SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL
+ <= sizeof(blob_storage_app_footer_metadata_t)) {
+ if (BOOTLOADER_OK != bootloader_writeStorage(slot_id,
+ offset + padded_length,
+ (uint8_t *)&metadata,
+ sizeof(blob_storage_app_footer_metadata_t))) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+ } else {
+ memcpy(&buffer[SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL
+ - sizeof(blob_storage_app_footer_metadata_t)],
+ &metadata,
+ sizeof(blob_storage_app_footer_metadata_t));
+
+ if (BOOTLOADER_OK != bootloader_writeStorage(slot_id,
+ offset + (padded_length == write_length ? aligned_length : padded_length),
+ buffer,
+ SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL)) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+ }
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_storage_set_managed(sl_bt_uuid_64_t const *blob_id, uint16_t owner_id)
+{
+ return sl_btmesh_blob_storage_write_app_footer(blob_id,
+ &owner_id,
+ BLOB_STORAGE_APP_ID_MANAGED_FLAG,
+ sizeof(owner_id));
+}
+
+bool sl_btmesh_blob_storage_is_managed(sl_bt_uuid_64_t const *blob_id)
+{
+ uint16_t owner_id;
+ uint16_t length = sizeof(owner_id);
+
+ sl_status_t sc = sl_btmesh_blob_storage_get_app_footer(blob_id,
+ BLOB_STORAGE_APP_ID_MANAGED_FLAG,
+ &owner_id,
+ &length);
+ (void) length;
+ if (SL_STATUS_OK == sc) {
+ return true;
+ }
+ return false;
+}
+
+bool sli_btmesh_blob_storage_is_managed(uint32_t slot_id)
+{
+ uint16_t owner_id;
+ uint16_t length = sizeof(owner_id);
+
+ sl_status_t sc = sli_btmesh_blob_storage_get_app_footer(slot_id,
+ BLOB_STORAGE_APP_ID_MANAGED_FLAG,
+ &owner_id,
+ &length);
+ (void) length;
+ if (SL_STATUS_OK == sc) {
+ return true;
+ }
+ return false;
+}
+
+bool sl_btmesh_blob_storage_is_managed_by_owner(sl_bt_uuid_64_t const *blob_id,
+ uint16_t owner_id)
+{
+ uint16_t blob_owner_id;
+ uint16_t length = sizeof(blob_owner_id);
+ sl_status_t sc = sl_btmesh_blob_storage_get_app_footer(blob_id,
+ BLOB_STORAGE_APP_ID_MANAGED_FLAG,
+ &blob_owner_id,
+ &length);
+
+ if ((SL_STATUS_OK == sc) && (length == sizeof(blob_owner_id))) {
+ return (blob_owner_id == owner_id);
+ }
+ return false;
+}
+
+bool sli_btmesh_blob_storage_is_managed_by_owner(uint32_t slot_id,
+ uint16_t owner_id)
+{
+ uint16_t blob_owner_id;
+ uint16_t length = sizeof(blob_owner_id);
+ sl_status_t sc = sli_btmesh_blob_storage_get_app_footer(slot_id,
+ BLOB_STORAGE_APP_ID_MANAGED_FLAG,
+ &blob_owner_id,
+ &length);
+
+ if ((SL_STATUS_OK == sc) && (length == sizeof(blob_owner_id))) {
+ return (blob_owner_id == owner_id);
+ }
+ return false;
+}
+
+uint32_t sl_btmesh_blob_storage_get_managed_flag_size(void)
+{
+ return sl_btmesh_blob_storage_calc_app_footer_size(sizeof(uint16_t));
+}
+
+sl_status_t sl_btmesh_blob_storage_get_blob_id_by_owner(uint16_t owner_id,
+ uint32_t occurrence_idx,
+ sl_bt_uuid_64_t *blob_id)
+{
+ sl_status_t sc = SL_STATUS_NOT_FOUND;
+ uint32_t current_occurrence_idx = 0;
+ uint32_t cache_length = blob_storage.cache_length;
+
+ for (uint32_t slot_id = 0; slot_id < cache_length; ++slot_id) {
+ sl_btmesh_blob_storage_status_t slot_status =
+ blob_storage.slot_cache[slot_id].status;
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED == slot_status
+ && sli_btmesh_blob_storage_is_managed_by_owner(slot_id, owner_id)) {
+ if (occurrence_idx <= current_occurrence_idx) {
+ sc = SL_STATUS_OK;
+ memcpy(blob_id,
+ &blob_storage.slot_cache[slot_id].blob_id,
+ sizeof(sl_bt_uuid_64_t));
+ break;
+ } else {
+ current_occurrence_idx++;
+ }
+ }
+ }
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_storage_get_app_footer_length(sl_bt_uuid_64_t const *blob_id,
+ uint16_t app_id,
+ uint16_t *length)
+{
+ blob_storage_app_footer_metadata_t metadata;
+ uint32_t slot_id, offset;
+ sl_status_t sc;
+
+ // Retrieve slot ID belonging to BLOB
+ slot_id = sli_btmesh_blob_storage_get_slot_id(blob_id);
+ sc = blob_storage_get_app_footer_metadata(slot_id,
+ app_id,
+ &metadata,
+ &offset);
+ if (sc == SL_STATUS_OK) {
+ *length = metadata.length;
+ }
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_storage_get_app_footer(sl_bt_uuid_64_t const *blob_id,
+ uint16_t app_id,
+ void *buffer,
+ uint16_t *length)
+{
+ uint32_t slot_id;
+ // Retrieve slot ID belonging to BLOB
+ slot_id = sli_btmesh_blob_storage_get_slot_id(blob_id);
+ return sli_btmesh_blob_storage_get_app_footer(slot_id, app_id, buffer, length);
+}
+
+sl_status_t sli_btmesh_blob_storage_get_app_footer(uint32_t slot_id,
+ uint16_t app_id,
+ void *buffer,
+ uint16_t *length)
+{
+ blob_storage_app_footer_metadata_t metadata;
+ uint32_t offset;
+ sl_status_t sc;
+ sc = blob_storage_get_app_footer_metadata(slot_id,
+ app_id,
+ &metadata,
+ &offset);
+ if (SL_STATUS_OK != sc) {
+ return sc;
+ }
+ // Buffer is too short for this footer
+ if (metadata.length > *length) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+ *length = metadata.length;
+ if (BOOTLOADER_OK
+ != bootloader_readStorage(slot_id, offset, buffer, *length)) {
+ return SL_STATUS_FAIL;
+ }
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_storage_get_blob_id_by_footer(uint16_t app_id,
+ const void *expected_footer,
+ uint16_t length,
+ sl_bt_uuid_64_t *blob_id)
+{
+ sl_status_t sc;
+ blob_storage_app_footer_metadata_t metadata = { UINT16_MAX, UINT16_MAX };
+ uint32_t offset;
+
+ for (uint32_t slot_id = 0; slot_id < blob_storage.cache_length; ++slot_id) {
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED
+ == blob_storage.slot_cache[slot_id].status) {
+ sc = blob_storage_get_app_footer_metadata(slot_id,
+ app_id,
+ &metadata,
+ &offset);
+
+ // Nothing is found or the length of the footer is different then expected
+ // continue with the next slot
+ if ((SL_STATUS_NOT_FOUND == sc) || (metadata.length != length)) {
+ continue;
+ } else if (SL_STATUS_OK != sc) {
+ // Return with error code in case other error occurs
+ return sc;
+ }
+
+ int32_t comparison_result;
+ sc = blob_storage_compare(slot_id,
+ offset,
+ expected_footer,
+ length,
+ &comparison_result);
+ if (SL_STATUS_OK != sc) {
+ // If comparison operation failed, return error code
+ return sc;
+ }
+
+ if (0 == comparison_result) {
+ memcpy(blob_id,
+ &blob_storage.slot_cache[slot_id].blob_id,
+ sizeof(sl_bt_uuid_64_t));
+ return SL_STATUS_OK;
+ }
+ }
+ }
+ return SL_STATUS_NOT_FOUND;
+}
+
+uint32_t sl_btmesh_blob_storage_get_total_space(void)
+{
+ uint32_t total_space = 0;
+
+ for (uint32_t i = 0; i < blob_storage.cache_length; ++i) {
+ total_space += blob_storage_get_max_blob_size_in_slot(i);
+ }
+ return total_space;
+}
+
+uint32_t sl_btmesh_blob_storage_get_remaining_space(bool include_unmanaged_blobs)
+{
+ sl_btmesh_blob_storage_status_t slot_status;
+ uint32_t max_blob_size_in_slot;
+ uint32_t remaining_space = 0;
+
+ for (uint32_t i = 0; i < blob_storage.cache_length; ++i) {
+ slot_status = blob_storage.slot_cache[i].status;
+ bool is_slot_available = false;
+
+ if ((SL_BTMESH_BLOB_STORAGE_STATUS_EMPTY == slot_status)
+ || (SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED == slot_status)) {
+ // If the slot is empty or corrupted then it can be used to store BLOBs
+ // without any restrictions
+ is_slot_available = true;
+ } else if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED == slot_status) {
+ // The unmanaged BLOBs can be deleted on demand when space is required
+ // for new BLOBs which makes it necessary to include it in the remaining
+ // space calculation in some cases (application dependent)
+ is_slot_available = include_unmanaged_blobs
+ && !sli_btmesh_blob_storage_is_managed(i);
+ }
+
+ if (is_slot_available) {
+ max_blob_size_in_slot = blob_storage_get_max_blob_size_in_slot(i);
+ remaining_space += max_blob_size_in_slot;
+ }
+ }
+ return remaining_space;
+}
+
+uint32_t sl_btmesh_blob_storage_get_max_blob_size(void)
+{
+ uint32_t max_blob_size = 0;
+ uint32_t max_blob_size_in_slot;
+
+ for (uint32_t i = 0; i < blob_storage.cache_length; ++i) {
+ max_blob_size_in_slot = blob_storage_get_max_blob_size_in_slot(i);
+ if (max_blob_size < max_blob_size_in_slot) {
+ max_blob_size = max_blob_size_in_slot;
+ }
+ }
+ return max_blob_size;
+}
+
+uint32_t sl_btmesh_blob_storage_get_max_blob_size_free(bool include_unmanaged_blobs)
+{
+ sl_btmesh_blob_storage_status_t slot_status;
+ uint32_t max_blob_size_in_slot;
+ uint32_t max_blob_size_free = 0;
+
+ for (uint32_t i = 0; i < blob_storage.cache_length; ++i) {
+ slot_status = blob_storage.slot_cache[i].status;
+ bool is_slot_available = false;
+
+ if ((SL_BTMESH_BLOB_STORAGE_STATUS_EMPTY == slot_status)
+ || (SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED == slot_status)) {
+ // If the slot is empty or corrupted then it can be used to store BLOBs
+ // without any restrictions
+ is_slot_available = true;
+ } else if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED == slot_status) {
+ // The unmanaged BLOBs can be deleted on demand when space is required
+ // for new BLOBs which makes it necessary to include it in the maximum
+ // free BLOB size calculation in some cases (application dependent)
+ is_slot_available = include_unmanaged_blobs
+ && !sli_btmesh_blob_storage_is_managed(i);
+ }
+
+ if (is_slot_available) {
+ max_blob_size_in_slot = blob_storage_get_max_blob_size_in_slot(i);
+ if (max_blob_size_free < max_blob_size_in_slot) {
+ max_blob_size_free = max_blob_size_in_slot;
+ }
+ }
+ }
+ return max_blob_size_free;
+}
+
+uint32_t sl_btmesh_blob_storage_get_max_blob_count(void)
+{
+ return blob_storage.cache_length;
+}
+
+sl_btmesh_blob_storage_status_t sli_btmesh_blob_storage_get_slot_status(uint32_t slot_id)
+{
+ return blob_storage.slot_cache[slot_id].status;
+}
+
+void sli_btmesh_blob_storage_sync(void)
+{
+ for (uint32_t i = 0; i < blob_storage.cache_length; ++i) {
+ blob_storage.slot_cache[i].status = blob_storage_check_slot_status(i);
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED
+ == blob_storage.slot_cache[i].status) {
+ blob_storage_footer_t footer;
+ sl_status_t sc = blob_storage_get_footer(&footer, i);
+ if (SL_STATUS_OK == sc) {
+ memcpy(&blob_storage.slot_cache[i].blob_id,
+ &footer.blob_id,
+ sizeof(sl_bt_uuid_64_t));
+ blob_storage.slot_cache[i].blob_size = footer.blob_size;
+ } else {
+ // This should not happen because this code is executed only if the same
+ // blob_storage_get_footer call was successful in blob_storage_check_slot_status
+ // function but to be on the safe side it is handled here.
+ blob_storage.slot_cache[i].status =
+ SL_BTMESH_BLOB_STORAGE_STATUS_INTERNAL_ERROR;
+ }
+ }
+ }
+}
+
+static sl_status_t blob_storage_get_footer(blob_storage_footer_t *footer,
+ uint32_t slot_id)
+{
+ BootloaderStorageSlot_t slot;
+ if (BOOTLOADER_OK != bootloader_getStorageSlotInfo(slot_id, &slot)) {
+ return SL_STATUS_FAIL;
+ }
+
+ // Read footer from the end of slot
+ if (BOOTLOADER_OK
+ != bootloader_readStorage(slot_id,
+ slot.length - sizeof(blob_storage_footer_t),
+ (uint8_t*)footer,
+ sizeof(blob_storage_footer_t))) {
+ return SL_STATUS_FAIL;
+ }
+
+ if (footer->version != STORAGE_VERSION) {
+ return SL_STATUS_INVALID_SIGNATURE;
+ }
+
+ return SL_STATUS_OK;
+}
+
+static sl_btmesh_blob_storage_status_t blob_storage_check_slot_status(uint32_t slot_id)
+{
+ BootloaderStorageSlot_t slot;
+ sl_btmesh_blob_storage_status_t ret_val = SL_BTMESH_BLOB_STORAGE_STATUS_EMPTY;
+ blob_storage_footer_t footer;
+
+ if (BOOTLOADER_OK != bootloader_getStorageSlotInfo(slot_id, &slot)) {
+ return SL_BTMESH_BLOB_STORAGE_STATUS_INTERNAL_ERROR;
+ }
+
+ // Read footer associated with slot
+ if (SL_STATUS_FAIL == blob_storage_get_footer(&footer, slot_id)) {
+ return SL_BTMESH_BLOB_STORAGE_STATUS_INTERNAL_ERROR;
+ }
+
+ // Check validity flag. It's enough to check the first byte, since it's
+ // written as a whole.
+ if (footer.validity_flag[0] != UINT8_MAX) {
+ return SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED;
+ }
+
+ // Check if non-footer data is empty.
+ // Since checking every byte would take long, use a heuristic approach of
+ // checking only certain bytes.
+ for (uint32_t i = 0; i < (slot.length - sizeof(blob_storage_footer_t));
+ i += SL_BTMESH_BLOB_STORAGE_DATA_CHECK_JUMP_CFG_VAL) {
+ uint8_t check;
+ if (BOOTLOADER_OK != bootloader_readStorage(slot_id, i, &check, 1)) {
+ return SL_BTMESH_BLOB_STORAGE_STATUS_INTERNAL_ERROR;
+ }
+ if (UINT8_MAX != check) {
+ ret_val = SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED;
+ break;
+ }
+ }
+
+ // If data is found, check validity by reading the version
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED == ret_val) {
+ if (STORAGE_VERSION != footer.version) {
+ ret_val = SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED;
+ }
+ } else {
+ // Check if footer is empty
+ for (uint32_t i = 0; i < sizeof(blob_storage_footer_t); ++i) {
+ uint8_t check = *(((uint8_t*)&footer) + i);
+ if (UINT8_MAX != check) {
+ ret_val = SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED;
+ break;
+ }
+ }
+ }
+
+ return ret_val;
+}
+
+uint32_t sli_btmesh_blob_storage_get_slot_id(sl_bt_uuid_64_t const *blob_id)
+{
+ for (uint32_t i = 0; i < blob_storage.cache_length; ++i) {
+ // Don't bother with invalid or empty slots
+ if (blob_storage.slot_cache[i].status
+ != SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED) {
+ continue;
+ }
+ // Compare BLOB ID
+ if (0 == memcmp(blob_id,
+ &blob_storage.slot_cache[i].blob_id,
+ sizeof(sl_bt_uuid_64_t))) {
+ // Return slot ID
+ return i;
+ }
+ }
+ return UINT32_MAX;
+}
+
+sl_status_t sli_blob_storage_invalidate_slot(uint32_t slot_id, bool force)
+{
+ if ((force == false)
+ && (sl_btmesh_blob_storage_get_erase_error_code()
+ == SL_BTMESH_BLOB_STORAGE_DELETE_BUSY)) {
+ return SL_STATUS_BUSY;
+ }
+
+ BootloaderStorageSlot_t slot;
+ uint8_t invalid_flag[MAX_ALIGNMENT];
+
+ // Clear all fields which results in invalid footer
+ memset(invalid_flag, 0, MAX_ALIGNMENT);
+ if (BOOTLOADER_OK != bootloader_getStorageSlotInfo(slot_id, &slot)) {
+ return SL_STATUS_FAIL;
+ }
+ // Write invalidity flag at the start of the footer
+ if (BOOTLOADER_OK
+ != bootloader_writeStorage(slot_id,
+ slot.length - sizeof(blob_storage_footer_t),
+ invalid_flag,
+ MAX_ALIGNMENT)) {
+ return SL_STATUS_FLASH_PROGRAM_FAILED;
+ }
+ blob_storage.slot_cache[slot_id].status = SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED;
+ return SL_STATUS_OK;
+}
+
+static sl_status_t blob_storage_get_app_footer_metadata(uint32_t slot_id,
+ uint16_t app_id,
+ blob_storage_app_footer_metadata_t *metadata,
+ uint32_t *offset)
+{
+ BootloaderStorageSlot_t slot;
+ if (BOOTLOADER_OK != bootloader_getStorageSlotInfo(slot_id, &slot)) {
+ return SL_STATUS_FAIL;
+ }
+ // Set offset to the first possible position for footer metadata
+ *offset = slot.length
+ - sizeof(blob_storage_footer_t)
+ - sizeof(blob_storage_app_footer_metadata_t);
+
+ do {
+ if (BOOTLOADER_OK != bootloader_readStorage(slot_id,
+ *offset,
+ (uint8_t*)metadata,
+ sizeof(blob_storage_app_footer_metadata_t))) {
+ return SL_STATUS_FAIL;
+ }
+ if (app_id == metadata->app_id) {
+ // Point to footer start
+ *offset -= sl_btmesh_blob_storage_calc_app_footer_size(metadata->length)
+ - sizeof(blob_storage_app_footer_metadata_t);
+
+ return SL_STATUS_OK;
+ }
+ // Try to find next metadata
+ *offset -= sl_btmesh_blob_storage_calc_app_footer_size(metadata->length);
+ } while (metadata->length != UINT16_MAX); // Length of 0xFFFF signifies invalid field
+ return SL_STATUS_NOT_FOUND;
+}
+
+static uint32_t blob_storage_get_max_blob_size_in_slot(uint32_t slot_id)
+{
+ BootloaderStorageSlot_t slot;
+
+ // If it is not possible to query the storage slot then no blob can be stored
+ // in it therefore 0 is returned as the supported max blob size in the slot.
+ if (BOOTLOADER_OK != bootloader_getStorageSlotInfo(slot_id, &slot)) {
+ return 0;
+ }
+
+ return sli_calc_max_blob_size(slot.length);
+}
+
+static sl_status_t blob_storage_compare(uint32_t slot_id,
+ uint32_t slot_offset,
+ uint8_t const * expected_data,
+ uint32_t length,
+ int32_t *comparison_result)
+{
+ uint8_t compare_chunk_buf[COMPARE_BUFFER_SIZE];
+ uint32_t remaining_length = length;
+ uint32_t chunk_offset = 0;
+ int chunk_compare_result;
+
+ while (0 < remaining_length) {
+ uint32_t chunk_size = (remaining_length < COMPARE_BUFFER_SIZE)
+ ? remaining_length : COMPARE_BUFFER_SIZE;
+
+ if (BOOTLOADER_OK != bootloader_readStorage(slot_id,
+ slot_offset + chunk_offset,
+ &compare_chunk_buf[0],
+ chunk_size)) {
+ return SL_STATUS_FAIL;
+ }
+
+ chunk_compare_result = memcmp(&compare_chunk_buf[0],
+ &expected_data[chunk_offset],
+ chunk_size);
+
+ if (0 != chunk_compare_result) {
+ *comparison_result = chunk_compare_result;
+ return SL_STATUS_OK;
+ }
+
+ remaining_length -= chunk_size;
+ chunk_offset += chunk_size;
+ }
+
+ *comparison_result = 0;
+ return SL_STATUS_OK;
+}
+
+/** @} end blob_storage */
diff --git a/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage.h b/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage.h
new file mode 100644
index 00000000000..e9cb6f69741
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage.h
@@ -0,0 +1,629 @@
+/***************************************************************************//**
+ * @file
+ * @brief Interface header for BLOB storage component intended for general use
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_BLOB_STORAGE_H
+#define SL_BTMESH_BLOB_STORAGE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup blob_storage BLOB Storage
+ * @{
+ ******************************************************************************/
+
+typedef enum sl_btmesh_blob_storage_status_e {
+ /// Slot is empty
+ SL_BTMESH_BLOB_STORAGE_STATUS_EMPTY,
+ /// Slot data is corrupted
+ ///
+ /// \li Data present, but no footer
+ /// \li Footer present, but no data
+ /// \li Footer invalid
+ SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED,
+ /// Slot has data and is valid
+ SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED,
+ /// Internal API error
+ SL_BTMESH_BLOB_STORAGE_STATUS_INTERNAL_ERROR
+} sl_btmesh_blob_storage_status_t;
+
+typedef enum sl_btmesh_blob_storage_delete_state_e {
+ /// Asynchronous delete inactive
+ SL_BTMESH_BLOB_STORAGE_DELETE_INACTIVE,
+ /// Asynchronous delete active and busy
+ SL_BTMESH_BLOB_STORAGE_DELETE_BUSY,
+ /// Asynchronous delete has failed
+ SL_BTMESH_BLOB_STORAGE_DELETE_FAILED,
+ /// Asynchronous delete has succeeded
+ SL_BTMESH_BLOB_STORAGE_DELETE_SUCCESS
+} sl_btmesh_blob_storage_delete_state_t;
+
+/// Cached data about a certain slot
+typedef struct {
+ /// BLOB ID contained in the slot
+ sl_bt_uuid_64_t blob_id;
+ /// Size of the BLOB residing in the slot
+ uint32_t blob_size;
+ /// Status of the slot (empty or otherwise)
+ sl_btmesh_blob_storage_status_t status;
+} sl_btmesh_blob_storage_slot_metadata_cache_t;
+
+/***************************************************************************//**
+ * Initializes the storage wrapper
+ ******************************************************************************/
+void sl_btmesh_blob_storage_init(void);
+
+/***************************************************************************//**
+ * Deinitializes the storage wrapper
+ ******************************************************************************/
+void sl_btmesh_blob_storage_deinit(void);
+
+/***************************************************************************//**
+ * Reads data from the identified BLOB into a buffer
+ *
+ * @param[in] blob_id Identifier of the BLOB
+ * @param[in] offset Offset relative to BLOB start, where read should start
+ * @param[inout] len Length of the buffer; modified if there's less data then
+ * buffer could fit
+ * @param[out] buffer Buffer in which data is read
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND In case BLOB has not been find
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_read(sl_bt_uuid_64_t const *blob_id,
+ uint32_t offset,
+ uint32_t *len,
+ void *buffer);
+
+/***************************************************************************//**
+ * Starts writing procedure
+ *
+ * Sets internal state variables to correspond to writing a BLOB.
+ * @param[in] blob_id Identifier of the BLOB about to be written into storage
+ * @param[in] size Size of the BLOB about to be written into storage
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_ALREADY_EXISTS In case BLOB ID already exists in storage
+ * @retval SL_STATUS_WOULD_OVERFLOW In case BLOB is too big to fit
+ * @retval SL_STATUS_NO_MORE_RESOURCE In case no slot is available
+ * @retval SL_STATUS_BUSY In case BLOB Storage is processing an async. request
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_write_start(sl_bt_uuid_64_t const *blob_id,
+ uint32_t size);
+
+/***************************************************************************//**
+ * Writes data into storage.
+ *
+ * @pre @ref sl_btmesh_blob_storage_write_start must be called first.
+ * @param[in] offset Offset of the data inside the BLOB. Must be
+ * @ref SL_BTMESH_BLOB_STORAGE_ALIGNMENT_CFG_VAL aligned.
+ * @param[in] len Length of the data
+ * @param[in] data Pointer to the data
+ *
+ * @return Result of write operation
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FLASH_WRITE_INHIBITED In case of non-word aligned offset
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case storage programming failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_write(uint32_t offset,
+ uint32_t len,
+ void *data);
+
+/***************************************************************************//**
+ * Retrieves a pointer to the cache
+ *
+ * @param cache[out] Pointer to the buffer of a pointer for cache start
+ * @param len[out] Pointer to the length cache
+ ******************************************************************************/
+void sl_btmesh_blob_storage_get_cache(sl_btmesh_blob_storage_slot_metadata_cache_t const **cache,
+ uint32_t *len);
+
+/***************************************************************************//**
+ * Verifies the BLOB to be considered consistent
+ *
+ * @return Verification result
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_STATE In case less data has been written than
+ * indicated with
+ * @ref sl_btmesh_blob_storage_write_start or
+ * writing has not been started properly
+ * @retval SL_STATUS_FAIL In case of internal API error
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case writing validity info into
+ * storage has failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_verify(void);
+
+/***************************************************************************//**
+ * Invalidates BLOB footer
+ *
+ * @param[in] blob_id Identifier of the BLOB to invalidate
+ *
+ * @return Result code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND In case BLOB identifier is not found
+ * @retval SL_STATUS_FAIL In case of internal API error
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case storage writing has failed
+ * @retval SL_STATUS_BUSY In case BLOB Storage is processing an async. request
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_invalidate(sl_bt_uuid_64_t const *blob_id);
+
+/***************************************************************************//**
+ * Invalidates all valid BLOB footer which belongs to a specific owner
+ *
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @param[in] owner_id Identifier of owner
+ *
+ * @return Result code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL In case of internal API error
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case storage writing has failed
+ * @retval SL_STATUS_BUSY In case BLOB Storage is processing an async. request
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_invalidate_by_owner(uint16_t owner_id);
+
+/***************************************************************************//**
+ * Invalidates all valid BLOB footer
+ *
+ * @return Result code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL In case of internal API error
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case storage writing has failed
+ * @retval SL_STATUS_BUSY In case BLOB Storage is processing an async. request
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_invalidate_all(void);
+
+/***************************************************************************//**
+ * Deletes a BLOB from storage
+ *
+ * @param[in] blob_id Identifier of the BLOB to be deleted
+ *
+ * @return Result of erase
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND In case no BLOB has been identified in storage
+ * @retval SL_STATUS_FLASH_ERASE_FAILED In case erase has failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete(sl_bt_uuid_64_t const *blob_id);
+
+/***************************************************************************//**
+ * Start asynchronous delete of a BLOB
+ *
+ * @param[in] blob_id Identifier of the BLOB to be deleted
+ *
+ * @see sl_btmesh_blob_storage_get_erase_error_code
+ *
+ * @return Request status
+ * @retval SL_BTMESH_OK Request successful
+ * @retval SL_BTMESH_BUSY Erase handler busy
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_start(sl_bt_uuid_64_t const *blob_id);
+
+/***************************************************************************//**
+ * Deletes all BLOB from storage which belongs to a specific owner
+ *
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @param[in] owner_id Identifier of owner
+ *
+ * @return Result of erase
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND No managed slot was found which belongs to the owner
+ * @retval SL_STATUS_FLASH_ERASE_FAILED In case erase has failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_managed_by_owner(uint16_t owner_id);
+
+/***************************************************************************//**
+ * Start asynchronous delete of BLOBs that are unmanaged
+ *
+ * @see sl_btmesh_blob_storage_get_erase_error_code
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @param[in] owner_id Identifier of owner
+ *
+ * @return Request status
+ * @retval SL_BTMESH_OK Request successful
+ * @retval SL_BTMESH_BUSY Erase handler busy
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_managed_by_owner_start(uint16_t owner_id);
+
+/***************************************************************************//**
+ * Deletes all BLOBs from storage
+ *
+ * @return Result of erase
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FLASH_ERASE_FAILED In case erase has failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_all(void);
+
+/***************************************************************************//**
+ * Start asynchronous delete of all BLOBs
+ *
+ * @see sl_btmesh_blob_storage_get_erase_error_code
+ *
+ * @return Request status
+ * @retval SL_BTMESH_OK Request successful
+ * @retval SL_BTMESH_BUSY Erase handler busy
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_all_start(void);
+
+/***************************************************************************//**
+ * Clear slots with invalid data
+ *
+ * @return Result of erase
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND No invalid slot was found
+ * @retval SL_STATUS_FLASH_ERASE_FAILED In case erase has failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_invalid_slots(void);
+
+/***************************************************************************//**
+ * Start asynchronous delete of invalid BLOBs
+ *
+ * @see sl_btmesh_blob_storage_get_erase_error_code
+ *
+ * @return Request status
+ * @retval SL_BTMESH_OK Request successful
+ * @retval SL_BTMESH_BUSY Erase handler busy
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_invalid_slots_start(void);
+
+/***************************************************************************//**
+ * Clear slots that are unmanaged
+ *
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @return Result of erase
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND No unmanaged slot was found
+ * @retval SL_STATUS_FLASH_ERASE_FAILED In case erase has failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_unmanaged_slots(void);
+
+/***************************************************************************//**
+ * Start asynchronous delete of BLOBs that are unmanaged
+ *
+ * @see sl_btmesh_blob_storage_get_erase_error_code
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @return Request status
+ * @retval SL_BTMESH_OK Request successful
+ * @retval SL_BTMESH_BUSY Erase handler busy
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_delete_unmanaged_slots_start(void);
+
+/***************************************************************************//**
+ * Set asynchronous delete separation time
+ *
+ * Asynchronous delete separation time is measured in milliseconds between two
+ * asynchronous delete steps. The BLOB storage async delete is performed in
+ * multiple steps and each delete step blocks the code execution.
+ * If delete steps are executed too often then it could starve other timing
+ * sensitive SW components so delete separation time feature can be used to
+ * prevent this scenario.
+ *
+ * @param[in] separation_time_ms Separation time between two async erase steps
+ *
+ * @return Request status
+ * @retval SL_STATUS_OK In case of success
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_set_delete_separation_time(uint16_t separation_time_ms);
+
+/***************************************************************************//**
+ * Checks whether BLOB identified is present in storage
+ *
+ * @param[in] blob_id
+ *
+ * @return Boolean value
+ * @retval true BLOB is present
+ * @retval false BLOB is not present
+ ******************************************************************************/
+bool sl_btmesh_blob_storage_is_present(sl_bt_uuid_64_t const *blob_id);
+
+/***************************************************************************//**
+ * Queries the BLOB size
+ *
+ * @param[in] blob_id Identifier of the BLOB inquired about
+ * @param[out] blob_size Length of the BLOB
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND In case no BLOB has been identified
+ * @retval SL_STATUS_INVALID_SIGNATURE In case invalid internal BLOB signature
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_get_blob_size(sl_bt_uuid_64_t const *blob_id,
+ uint32_t *blob_size);
+
+/***************************************************************************//**
+ * Calculates the size of application specific footer on BLOB storage medium.
+ *
+ * @note BLOB storage stores some additional metadata (id, length) with the
+ * footer data and it takes into consideration the alignment requirements of
+ * the underlying medium.
+ *
+ * @param[in] footer_data_length Length of the application specific footer data
+ *
+ * @return Size of application specific footer on BLOB storage medium.
+ ******************************************************************************/
+uint32_t sl_btmesh_blob_storage_calc_app_footer_size(uint16_t footer_data_length);
+
+/***************************************************************************//**
+ * Writes application specific footer into storage belonging to identified the
+ * BLOB and application
+ *
+ * @param[in] blob_id Identifier of the BLOB
+ * @param[in] footer Footer data
+ * @param[in] app_id Identifier of the application data. Must be unique to the
+ * application layer user in the compiled software. Used to
+ * identify footer.
+ * @param[in] length Length of the data
+ *
+ * @return Result of footer write
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case write failed
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_write_app_footer(sl_bt_uuid_64_t const *blob_id,
+ void const *footer,
+ uint16_t app_id,
+ uint16_t length);
+
+/***************************************************************************//**
+ * Queries footer information length assigned to a given BLOB, belonging to an
+ * application identifier.
+ *
+ * @param[in] blob_id Identifier of the BLOB
+ * @param[in] app_id Identifier of the application data
+ * @param[out] length Length of application data in case of success
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of successful query
+ * @retval SL_STATUS_NOT_FOUND If app_id and blob_id don't identify a footer
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_get_app_footer_length(sl_bt_uuid_64_t const *blob_id,
+ uint16_t app_id,
+ uint16_t *length);
+
+/***************************************************************************//**
+ * Extracts footer information assigned to a given BLOB, belonging to an
+ * application identifier.
+ *
+ * @param[in] blob_id Identifier of the BLOB
+ * @param[in] app_id Identifier of the application data
+ * @param[out] buffer Pointer to footer buffer
+ * @param[inout] length Length of the buffer; contains the length of the data
+ * read in case of success
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of successful reading
+ * @retval SL_STATUS_NOT_FOUND If app_id and blob_id don't identify a footer
+ * @retval SL_STATUS_INVALID_PARAMETER If buffer is too short
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_get_app_footer(sl_bt_uuid_64_t const *blob_id,
+ uint16_t app_id,
+ void *buffer,
+ uint16_t *length);
+
+/***************************************************************************//**
+ * Set BLOB to be managed, meaning it won't be deleted.
+ *
+ * Each managed BLOB is managed by a specific owner which is stored with BLOB.
+ * Unmanaged BLOBs will be deleted when out of available space.
+ *
+ * @param[in] blob_id Identifier of the BLOB
+ * @param[in] owner_id Identifier of owner which the BLOB belongs to
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case write failed
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_set_managed(sl_bt_uuid_64_t const *blob_id,
+ uint16_t owner_id);
+
+/***************************************************************************//**
+ * Check if BLOB is managed.
+ *
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @param[in] blob_id Identifier of the BLOB
+ *
+ * @return Boolean value
+ * @retval true BLOB is managed
+ * @retval false BLOB isn't managed or BLOB does not exist
+ ******************************************************************************/
+bool sl_btmesh_blob_storage_is_managed(sl_bt_uuid_64_t const *blob_id);
+
+/***************************************************************************//**
+ * Check if BLOB is managed by a specific owner.
+ *
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @param[in] blob_id Identifier of the BLOB
+ * @param[in] owner_id Identifier of expected owner
+ *
+ * @return Boolean value
+ * @retval true BLOB belongs to the specified owner
+ * @retval false BLOB belongs to another owner or BLOB is not managed or
+ * BLOB does not exist
+ ******************************************************************************/
+bool sl_btmesh_blob_storage_is_managed_by_owner(sl_bt_uuid_64_t const *blob_id,
+ uint16_t owner_id);
+
+/***************************************************************************//**
+ * Return the size of managed flag on BLOB storage medium.
+ *
+ * BLOB storage needs to distinguish managed and unmanaged BLOBs so it marks
+ * managed BLOBs as such which requires some space on the medium.
+ *
+ * @return Size of managed flag on BLOB storage medium
+ ******************************************************************************/
+uint32_t sl_btmesh_blob_storage_get_managed_flag_size(void);
+
+/***************************************************************************//**
+ * Searches BLOBs managed by a specific owner and provides the BLOB ID of the
+ * requested occurrence.
+ *
+ * The search order for managed BLOBs of an owner is not specified but it is
+ * guaranteed that the order does not change unless new BLOB is added or an
+ * existing BLOB of the same owner is removed (delete, invalidate) from the
+ * BLOB storage.
+ *
+ * When a managed BLOB belonging to an owner is removed from BLOB storage then
+ * occurrence_idx of BLOBs with same owner is decremented if the occurrence_idx
+ * of the BLOB was greater than the occurrence_idx of the removed BLOB otherwise
+ * the occurrence_idx does not change. In other words the BLOBs are shifted to
+ * lower indexes.
+ *
+ * If a new managed BLOB is added by an owner to the BLOB storage then the
+ * search order of BLOBs with the same owner may change arbitrarily because
+ * it is not guaranteed which occurrence_idx the added BLOB is assigned to.
+ *
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @param[in] owner_id Identifier of owner which the BLOB belongs to
+ * @param[in] occurrence_idx Selects a BLOB from BLOBs managed by the same owner.
+ * Zero means the first BLOB with matching owner found during the search.
+ * @param[out] blob_id Identifier of the BLOB
+ *
+ * @return Result of managed BLOB ID get operation
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND If no BLOBs are found with owner_id or the
+ * occurrence_idx is greater or equal to the number of BLOBs managed by owner
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_get_blob_id_by_owner(uint16_t owner_id,
+ uint32_t occurrence_idx,
+ sl_bt_uuid_64_t *blob_id);
+
+/***************************************************************************//**
+ * Searches for a specific app_id and data in the footer of each valid BLOB and
+ * returns the BLOB ID of the first match
+ *
+ * @param[in] app_id Identifier of the application data
+ * @param[in] expected_footer Expected footer data for comparison
+ * @param[in] length Length of the data
+ * @param[out] blob_id Identifier of the BLOB of the first match
+ *
+ * @return Result of footer based BLOB ID query (propagates other internal
+ * errors)
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND If app_id is not found or the data is different
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_storage_get_blob_id_by_footer(uint16_t app_id,
+ const void *expected_footer,
+ uint16_t length,
+ sl_bt_uuid_64_t *blob_id);
+
+/***************************************************************************//**
+ * Queries total space for BLOB data storage in bytes (used + empty)
+ *
+ * @return Total space in bytes
+ ******************************************************************************/
+uint32_t sl_btmesh_blob_storage_get_total_space(void);
+
+/***************************************************************************//**
+ * Queries remaining empty bytes in the BLOB storage
+ *
+ * @param[in] include_unmanaged_blobs If true then unmanaged blobs are calculated
+ * into the remaining empty space.
+ *
+ * @return Remaining empty bytes
+ ******************************************************************************/
+uint32_t sl_btmesh_blob_storage_get_remaining_space(bool include_unmanaged_blobs);
+
+/***************************************************************************//**
+ * Queries maximum BLOB size which can be stored if the whole BLOB storage is
+ * empty
+ *
+ * @note This might not be available even if one BLOB is stored
+ *
+ * @return Maximum BLOB size
+ ******************************************************************************/
+uint32_t sl_btmesh_blob_storage_get_max_blob_size(void);
+
+/***************************************************************************//**
+ * Queries maximum BLOB size which can be stored in the free space of BLOB
+ * storage
+ *
+ * @param[in] include_unmanaged_blobs If true then unmanaged blobs are calculated
+ * into the maximum free BLOB size.
+ *
+ * @return Maximum supported BLOB size in free part of the BLOB storage
+ ******************************************************************************/
+uint32_t sl_btmesh_blob_storage_get_max_blob_size_free(bool include_unmanaged_blobs);
+
+/***************************************************************************//**
+ * Queries maximum number of BLOBs which can be stored
+ *
+ * @return Maximum number of BLOBs
+ ******************************************************************************/
+uint32_t sl_btmesh_blob_storage_get_max_blob_count(void);
+
+/***************************************************************************//**
+ * Queries the error code/state of the asynchronous erase
+ *
+ * @return State of asynchronous erase
+ * @retval SL_BTMESH_BLOB_STORAGE_DELETE_INACTIVE If erase is inactive
+ * @retval SL_BTMESH_BLOB_STORAGE_DELETE_BUSY If erase is ongoing
+ * @retval SL_BTMESH_BLOB_STORAGE_DELETE_FAILED If erase has failed
+ * @retval SL_BTMESH_BLOB_STORAGE_DELETE_SUCCESS If erase is successful
+ ******************************************************************************/
+sl_btmesh_blob_storage_delete_state_t sl_btmesh_blob_storage_get_erase_error_code(void);
+
+/***************************************************************************//**
+ * Handles the asynchronous erase state machine
+ ******************************************************************************/
+void sl_btmesh_blob_storage_delete_step_handle(void);
+
+/***************************************************************************//**
+ * Check if BLOB Storage allows the system to sleep
+ *
+ * @return If it is ok to sleep
+ * @retval true The system is allowed to sleep
+ * @retval false The system shall be kept awake
+ *
+ * Asynchronous erase requires the device to stay awake to finish.
+ ******************************************************************************/
+bool sl_btmesh_blob_storage_is_ok_to_sleep();
+
+/** @} end blob_storage */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif //SL_BTMESH_BLOB_STORAGE_H
diff --git a/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage_app_id.h b/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage_app_id.h
new file mode 100644
index 00000000000..f2d17040cb6
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage_app_id.h
@@ -0,0 +1,59 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh BLOB Storage Application Identifiers
+ *
+ * This file contains the list of reserved application identifiers (app_id) of
+ * Silicon Laboratories software components. If the application needs to store
+ * additional data in app footer then the usage of these application identifiers
+ * shall be avoided.
+ *
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_BLOB_STORAGE_APP_ID_H
+#define SL_BTMESH_BLOB_STORAGE_APP_ID_H
+
+/***************************************************************************//**
+ * @addtogroup blob_storage
+ * @{
+ ******************************************************************************/
+
+/// Reserved BLOB Storage owner ID of FW Distribution Server
+#define BLOB_STORAGE_OWNER_ID_FW_DIST_SERVER 0x1000
+
+/// Reserved BLOB Storage app ID of Firmware ID (BT Mesh DFU)
+#define BLOB_STORAGE_APP_ID_DFU_FWID 0x1001
+
+/// Reserved BLOB Storage app ID of Metadata (BT Mesh DFU)
+#define BLOB_STORAGE_APP_ID_DFU_METADATA 0x1002
+
+/// Reserved BLOB Storage app ID of managed flag
+#define BLOB_STORAGE_APP_ID_MANAGED_FLAG 0x8000
+
+/** @} end blob_storage */
+
+#endif // SL_BTMESH_BLOB_STORAGE_APP_ID_H
diff --git a/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage_erase.c b/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage_erase.c
new file mode 100644
index 00000000000..f86bcc9abed
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_storage/sl_btmesh_blob_storage_erase.c
@@ -0,0 +1,478 @@
+/***************************************************************************//**
+ * @file
+ * @brief Implementation of BLOB storage component erase functionality
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "btl_interface.h"
+#include "sl_status.h"
+#include "sl_bgapi.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#include "sl_btmesh_blob_storage.h"
+#include "sl_btmesh_blob_storage_config.h"
+#include "sli_btmesh_blob_storage.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+struct {
+ struct sl_simple_timer timer; ///< Timer to separate two erase steps (chunk)
+ BootloaderEraseStatus_t status; ///< Erase status for chunked erase
+ uint32_t slot_id; ///< Currently erasing slot's identifier
+ int32_t error_code; ///< Bootloader error code
+ uint16_t owner; ///< Erase by Owner flag and owner identifier
+ uint16_t separation_time_ms; ///< Separation time between two erase steps (chunk)
+ bool separation_time_elapsed : 1; ///< Separation time elapsed flag
+ bool erasing : 1; ///< Erasing state flag
+ bool erase_started : 1; ///< Erase started state flag
+ bool invalid : 1; ///< Erase Invalid Slots state flag
+ bool unmanaged : 1; ///< Erase Unmanaged Slots state flag
+ bool all : 1; ///< Erase All Slots state flag
+} blob_storage_erase;
+
+/***************************************************************************//**
+ * Starts erase separation timer
+ ******************************************************************************/
+static void blob_storage_start_delete_separation_timer(void);
+
+/***************************************************************************//**
+ * Erase separation time elapsed callback
+ ******************************************************************************/
+static void blob_storage_on_delete_separation_time_elapsed(sl_simple_timer_t *timer,
+ void *data);
+
+/***************************************************************************//**
+ * Forcibly delete a erase data from slot identified
+ *
+ * @param[in] slot_id Identifier of the slot to be erased
+ *
+ * @return Result of erase
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NOT_FOUND Slot identifier invalid
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+static sl_status_t blob_storage_force_delete_slot(uint32_t slot_id);
+
+/***************************************************************************//**
+ * Starts deleting slot identified
+ *
+ * @param[in] slot_id Identifier of the slot to be erased
+ ******************************************************************************/
+static void blob_storage_force_delete_slot_start(uint32_t slot_id);
+
+/***************************************************************************//**
+ * Helper function for delete functions to reduce code duplication
+ *
+ * @param[in] slot_id Identifier of the slot to be erased
+ * @param[inout] status Status of the earlier erases. Should be initialized to
+ * SL_STATUS_NOT_FOUND
+ ******************************************************************************/
+static inline void blob_storage_delete_helper(uint32_t slot_id,
+ sl_status_t *status);
+
+void sli_btmesh_blob_storage_erase_init(void)
+{
+ blob_storage_erase.erasing = false;
+ blob_storage_erase.erase_started = false;
+ blob_storage_erase.invalid = false;
+ blob_storage_erase.unmanaged = false;
+ blob_storage_erase.all = false;
+ blob_storage_erase.error_code = INT32_MAX;
+ blob_storage_erase.owner = UINT16_MAX;
+ blob_storage_erase.separation_time_elapsed = true;
+ blob_storage_erase.separation_time_ms =
+ SL_BTMESH_BLOB_STORAGE_ASYNC_DELETE_SEPARATION_TIME_MS_CFG_VAL;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete(sl_bt_uuid_64_t const *blob_id)
+{
+ sl_status_t sc;
+ uint32_t slot_id = sli_btmesh_blob_storage_get_slot_id(blob_id);
+
+ sc = blob_storage_force_delete_slot(slot_id);
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_invalid_slots(void)
+{
+ sl_status_t sc = SL_STATUS_NOT_FOUND;
+ for (uint32_t slot_id = 0;
+ slot_id < sl_btmesh_blob_storage_get_max_blob_count();
+ ++slot_id) {
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED
+ == sli_btmesh_blob_storage_get_slot_status(slot_id)) {
+ blob_storage_delete_helper(slot_id, &sc);
+ }
+ }
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_unmanaged_slots(void)
+{
+ sl_status_t sc = SL_STATUS_NOT_FOUND;
+ sl_btmesh_blob_storage_slot_metadata_cache_t const *cache;
+ uint32_t cache_length;
+ sl_btmesh_blob_storage_get_cache(&cache, &cache_length);
+ for (uint32_t slot_id = 0; slot_id < cache_length; ++slot_id) {
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED == cache[slot_id].status
+ && !sl_btmesh_blob_storage_is_managed(&cache[slot_id].blob_id)) {
+ blob_storage_delete_helper(slot_id, &sc);
+ }
+ }
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_managed_by_owner(uint16_t owner_id)
+{
+ sl_status_t sc = SL_STATUS_NOT_FOUND;
+ sl_btmesh_blob_storage_slot_metadata_cache_t const *cache;
+ uint32_t cache_length;
+ sl_btmesh_blob_storage_get_cache(&cache, &cache_length);
+
+ for (uint32_t slot_id = 0; slot_id < cache_length; ++slot_id) {
+ // Only attempt erase when slot is not empty
+ if (SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED == cache[slot_id].status
+ && sl_btmesh_blob_storage_is_managed_by_owner(&cache[slot_id].blob_id,
+ owner_id)) {
+ blob_storage_delete_helper(slot_id, &sc);
+ }
+ }
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_all(void)
+{
+ sl_status_t sc = SL_STATUS_NOT_FOUND;
+ uint32_t slot_cnt = sl_btmesh_blob_storage_get_max_blob_count();
+ for (uint32_t slot_id = 0; slot_id < slot_cnt; ++slot_id) {
+ blob_storage_delete_helper(slot_id, &sc);
+ }
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_start(sl_bt_uuid_64_t const *blob_id)
+{
+ if (blob_storage_erase.erasing) {
+ return SL_STATUS_BUSY;
+ }
+
+ blob_storage_erase.erasing = true;
+ blob_storage_erase.slot_id = sli_btmesh_blob_storage_get_slot_id(blob_id);
+ blob_storage_force_delete_slot_start(blob_storage_erase.slot_id);
+ log_debug("Starting BLOB erase in slot %lu!" NL, blob_storage_erase.slot_id);
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_invalid_slots_start(void)
+{
+ if (blob_storage_erase.erasing) {
+ return SL_STATUS_BUSY;
+ }
+
+ blob_storage_erase.invalid = true;
+ blob_storage_erase.erasing = true;
+ blob_storage_erase.slot_id = 0;
+ blob_storage_erase.error_code = INT32_MAX;
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_unmanaged_slots_start(void)
+{
+ if (blob_storage_erase.erasing) {
+ return SL_STATUS_BUSY;
+ }
+
+ blob_storage_erase.unmanaged = true;
+ blob_storage_erase.erasing = true;
+ blob_storage_erase.slot_id = 0;
+ blob_storage_erase.error_code = INT32_MAX;
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_managed_by_owner_start(uint16_t owner_id)
+{
+ if (blob_storage_erase.erasing) {
+ return SL_STATUS_BUSY;
+ }
+
+ blob_storage_erase.owner = owner_id;
+ blob_storage_erase.erasing = true;
+ blob_storage_erase.slot_id = 0;
+ blob_storage_erase.error_code = INT32_MAX;
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_storage_delete_all_start(void)
+{
+ if (blob_storage_erase.erasing) {
+ return SL_STATUS_BUSY;
+ }
+
+ blob_storage_erase.all = true;
+ blob_storage_erase.erasing = true;
+ blob_storage_erase.slot_id = 0;
+ blob_storage_erase.error_code = INT32_MAX;
+
+ return SL_STATUS_OK;
+}
+
+sl_btmesh_blob_storage_delete_state_t sl_btmesh_blob_storage_get_erase_error_code(void)
+{
+ if (!blob_storage_erase.erasing) {
+ if (INT32_MAX == blob_storage_erase.error_code) {
+ return SL_BTMESH_BLOB_STORAGE_DELETE_INACTIVE;
+ }
+ if (BOOTLOADER_OK == blob_storage_erase.error_code) {
+ return SL_BTMESH_BLOB_STORAGE_DELETE_SUCCESS;
+ } else {
+ return SL_BTMESH_BLOB_STORAGE_DELETE_FAILED;
+ }
+ }
+ return SL_BTMESH_BLOB_STORAGE_DELETE_BUSY;
+}
+
+void sl_btmesh_blob_storage_delete_step_handle(void)
+{
+ if (!blob_storage_erase.erasing) {
+ return;
+ }
+ if (blob_storage_erase.erase_started) {
+ if (BOOTLOADER_ERROR_STORAGE_CONTINUE == blob_storage_erase.error_code) {
+ if (blob_storage_erase.separation_time_elapsed) {
+ blob_storage_erase.error_code =
+ bootloader_chunkedEraseStorageSlot(&blob_storage_erase.status);
+ // The async delete separation timer shall be started to introduce a
+ // delay between two consecutive async delete steps. It is important to
+ // start the timer even if the last chunk of the storage slot is erased
+ // because other slots may be deleted as well after this one.
+ if ((BOOTLOADER_ERROR_STORAGE_CONTINUE == blob_storage_erase.error_code)
+ || (BOOTLOADER_OK == blob_storage_erase.error_code)) {
+ blob_storage_start_delete_separation_timer();
+ }
+ }
+ }
+ if (BOOTLOADER_ERROR_STORAGE_CONTINUE != blob_storage_erase.error_code) {
+ blob_storage_erase.erase_started = false;
+ // Resync
+ sli_btmesh_blob_storage_sync();
+ }
+ } else {
+ if (blob_storage_erase.invalid) {
+ if (sl_btmesh_blob_storage_get_max_blob_count()
+ == blob_storage_erase.slot_id) {
+ // clear flags
+ blob_storage_erase.erasing = false;
+ blob_storage_erase.invalid = false;
+ } else if (SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED
+ == sli_btmesh_blob_storage_get_slot_status(blob_storage_erase.slot_id)) {
+ blob_storage_force_delete_slot_start(blob_storage_erase.slot_id++);
+ } else {
+ blob_storage_erase.slot_id++;
+ }
+ } else if (blob_storage_erase.unmanaged) {
+ if (sl_btmesh_blob_storage_get_max_blob_count()
+ == blob_storage_erase.slot_id) {
+ // clear flags
+ blob_storage_erase.erasing = false;
+ blob_storage_erase.unmanaged = false;
+ } else if (!sli_btmesh_blob_storage_is_managed(blob_storage_erase.slot_id)) {
+ blob_storage_force_delete_slot_start(blob_storage_erase.slot_id++);
+ } else {
+ blob_storage_erase.slot_id++;
+ }
+ } else if (blob_storage_erase.owner != UINT16_MAX) {
+ if (sl_btmesh_blob_storage_get_max_blob_count()
+ == blob_storage_erase.slot_id) {
+ // clear flags
+ blob_storage_erase.erasing = false;
+ blob_storage_erase.owner = UINT16_MAX;
+ } else if (sli_btmesh_blob_storage_is_managed_by_owner(blob_storage_erase.slot_id,
+ blob_storage_erase.owner)) {
+ blob_storage_force_delete_slot_start(blob_storage_erase.slot_id++);
+ } else {
+ blob_storage_erase.slot_id++;
+ }
+ } else if (blob_storage_erase.all) {
+ if (sl_btmesh_blob_storage_get_max_blob_count()
+ == blob_storage_erase.slot_id) {
+ // clear flags
+ blob_storage_erase.erasing = false;
+ blob_storage_erase.all = false;
+ } else {
+ blob_storage_force_delete_slot_start(blob_storage_erase.slot_id++);
+ }
+ } else {
+ blob_storage_erase.erasing = false;
+ }
+ }
+}
+
+bool sl_btmesh_blob_storage_is_ok_to_sleep()
+{
+ return !blob_storage_erase.erasing;
+}
+
+sl_status_t sl_btmesh_blob_storage_set_delete_separation_time(uint16_t separation_time_ms)
+{
+ blob_storage_erase.separation_time_ms = separation_time_ms;
+ return SL_STATUS_OK;
+}
+
+static void blob_storage_start_delete_separation_timer(void)
+{
+ blob_storage_erase.separation_time_elapsed = false;
+ if (blob_storage_erase.separation_time_ms == 0) {
+ // If the delete separation time is zero then the timer elapses immediately
+ // so the timer callback shall be called directly
+ blob_storage_on_delete_separation_time_elapsed(&blob_storage_erase.timer, NULL);
+ } else {
+ sl_status_t sc;
+ sc = sl_simple_timer_start(&blob_storage_erase.timer,
+ blob_storage_erase.separation_time_ms,
+ blob_storage_on_delete_separation_time_elapsed,
+ NULL,
+ false);
+ app_assert_status(sc);
+ }
+}
+
+static void blob_storage_on_delete_separation_time_elapsed(sl_simple_timer_t *timer,
+ void *data)
+{
+ (void) timer;
+ (void) data;
+ blob_storage_erase.separation_time_elapsed = true;
+}
+
+static sl_status_t blob_storage_force_delete_slot(uint32_t slot_id)
+{
+ sl_btmesh_blob_storage_status_t slot_status;
+
+ // If the slot is occupied then it shall be invalidated first because the
+ // bootloader_eraseStorageSlot erases the pages of the storage slot from
+ // lower address to higher address which means the data is erased sooner than
+ // the footer. This is essential because the BLOB in the slot would appear
+ // valid due to the intact footer while the BLOB data was deleted partially
+ // without invalidation.
+ slot_status = sli_btmesh_blob_storage_get_slot_status(slot_id);
+
+ if (slot_status == SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED) {
+ // The invalidation shall be forced because the invalidated slot will be
+ // deleted immediately so an ongoing erase should not cause any issues.
+ sl_status_t sc_invalidate = sli_blob_storage_invalidate_slot(slot_id, true);
+
+ // If the invalidation fails then it does not mean that an error will occur
+ // during the storage slot erase. An issue may occur with failed invalidation
+ // if there is a reset during erase.
+ // If the invalidation fails then a warning is logged and the storage slot
+ // is erased anyway because it might have more negative consequences if a
+ // storage slot is not erased. (e.g. no new BLOB can be written)
+ log_status_warning_f(sc_invalidate,
+ "Failed to invalidate occupied slot before delete (slot=%lu)" NL,
+ (unsigned long) slot_id);
+ }
+
+ switch (bootloader_eraseStorageSlot(slot_id)) {
+ case BOOTLOADER_ERROR_STORAGE_INVALID_SLOT:
+ return SL_STATUS_NOT_FOUND;
+ case BOOTLOADER_OK:
+ break;
+ default:
+ return SL_STATUS_FAIL;
+ }
+ // Resync
+ sli_btmesh_blob_storage_sync();
+ return SL_STATUS_OK;
+}
+
+static void blob_storage_force_delete_slot_start(uint32_t slot_id)
+{
+ sl_btmesh_blob_storage_status_t slot_status;
+
+ // If the slot is occupied then it shall be invalidated first because the
+ // bootloader_initChunkedEraseStorageSlot and bootloader_chunkedEraseStorageSlot
+ // erase the pages of the storage slot from lower address to higher address
+ // which means the data is erased sooner than the footer.
+ // This is essential because the BLOB in the slot would appear valid due to the
+ // intact footer while the BLOB data was deleted partially without invalidation.
+ slot_status = sli_btmesh_blob_storage_get_slot_status(slot_id);
+
+ if (slot_status == SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED) {
+ // The invalidation shall be forced because this function is also called when
+ // multiple storage slots are deleted one by one.
+ // For example: sl_btmesh_blob_storage_delete_all_start
+ sl_status_t sc_invalidate = sli_blob_storage_invalidate_slot(slot_id, true);
+
+ // If the invalidation fails then it does not mean that an error will occur
+ // during the storage slot erase. An issue may occur with failed invalidation
+ // if there is a reset during erase or the BLOB data in the storage slot is
+ // read while it is being erased.
+ // If the invalidation fails then a warning is logged and the storage slot
+ // is erased anyway because it might have more negative consequences if a
+ // storage slot is not erased. (e.g. no new BLOB can be written)
+ log_status_warning_f(sc_invalidate,
+ "Failed to invalidate occupied slot before delete (slot=%lu)" NL,
+ (unsigned long) slot_id);
+ }
+
+ blob_storage_erase.error_code =
+ bootloader_initChunkedEraseStorageSlot(slot_id,
+ &blob_storage_erase.status);
+ if (BOOTLOADER_OK == blob_storage_erase.error_code) {
+ // Signal ongoing erase
+ blob_storage_erase.error_code = BOOTLOADER_ERROR_STORAGE_CONTINUE;
+ blob_storage_erase.erase_started = true;
+ }
+}
+
+static inline void blob_storage_delete_helper(uint32_t slot_id,
+ sl_status_t *status)
+{
+ sl_status_t temp_sc = blob_storage_force_delete_slot(slot_id);
+
+ app_assert(temp_sc != SL_STATUS_NOT_FOUND, "Invalid slot ID!");
+
+ // status should point to a value of SL_STATUS_NOT_FOUND initially
+ if (*status == SL_STATUS_NOT_FOUND && SL_STATUS_OK == temp_sc) {
+ *status = SL_STATUS_OK;
+ }
+
+ if (SL_STATUS_OK != temp_sc) {
+ // Erase is requested for all storage slots even if the erase of previous
+ // storage slots have failed. However, if at least one erase fails then
+ // the return value is set to SL_STATUS_FLASH_ERASE_FAILED.
+ *status = SL_STATUS_FLASH_ERASE_FAILED;
+ }
+}
diff --git a/app/btmesh/common/btmesh_blob_storage/sli_btmesh_blob_storage.h b/app/btmesh/common/btmesh_blob_storage/sli_btmesh_blob_storage.h
new file mode 100644
index 00000000000..506f261cb54
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_storage/sli_btmesh_blob_storage.h
@@ -0,0 +1,140 @@
+/***************************************************************************//**
+ * @file
+ * @brief Interface header for BLOB storage component internal interfaces
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SLI_BTMESH_BLOB_STORAGE_H
+#define SLI_BTMESH_BLOB_STORAGE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * Initializes the storage erase functionality
+ ******************************************************************************/
+void sli_btmesh_blob_storage_erase_init(void);
+
+/***************************************************************************//**
+ * Read data from bootloader and stores it in the cache
+ ******************************************************************************/
+void sli_btmesh_blob_storage_sync(void);
+
+/***************************************************************************//**
+ * Query status of the slot
+ *
+ * @param[in] slot_id Index of the slot
+ *
+ * @return Status of the slot
+ * @retval SL_BTMESH_BLOB_STORAGE_STATUS_EMPTY If slot is empty
+ * @retval SL_BTMESH_BLOB_STORAGE_STATUS_CORRUPTED If data is present and invalid
+ * @retval SL_BTMESH_BLOB_STORAGE_STATUS_OCCUPIED If data is present and valid
+ * @retval SL_BTMESH_BLOB_STORAGE_STATUS_INTERNAL_ERROR Some internal error occurred
+ */
+sl_btmesh_blob_storage_status_t sli_btmesh_blob_storage_get_slot_status(uint32_t slot_id);
+
+/***************************************************************************//**
+ * Check if BLOB ID is stored in any of the slots and identifies it.
+ *
+ * @param[in] blob_id BLOB ID inquired about
+ *
+ * @return Identifier of the slot containing the identified BLOB
+ * @retval UINT32_MAX In case no slot is found
+ ******************************************************************************/
+uint32_t sli_btmesh_blob_storage_get_slot_id(sl_bt_uuid_64_t const *blob_id);
+
+/***************************************************************************//**
+ * Extracts footer information assigned to a given slot, belonging to an
+ * application identifier.
+ *
+ * @param slot_id Index of the slot
+ * @param[in] app_id Identifier of the application layer user
+ * @param[out] buffer Pointer to footer buffer
+ * @param[inout] length Length of the buffer; contains the length of the data
+ * read in case of success
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of successful reading
+ * @retval SL_STATUS_NOT_FOUND If app_id and blob_id don't identify a footer
+ * @retval SL_STATUS_INVALID_PARAMETER If buffer is too short
+ * @retval SL_STATUS_FAIL In case of internal API error
+ ******************************************************************************/
+sl_status_t sli_btmesh_blob_storage_get_app_footer(uint32_t slot_id,
+ uint16_t app_id,
+ void *buffer,
+ uint16_t *length);
+/***************************************************************************//**
+ * Check if BLOB is managed.
+ *
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @param[in] slot_id Index of the slot
+ *
+ * @return Boolean value
+ * @retval true BLOB is managed
+ * @retval false Slot isn't managed
+ ******************************************************************************/
+bool sli_btmesh_blob_storage_is_managed(uint32_t slot_id);
+
+/***************************************************************************//**
+ * Check if slot is managed by a specific owner.
+ *
+ * @see sl_btmesh_blob_storage_set_managed
+ *
+ * @param[in] slot_id Index of the slot
+ * @param[in] owner_id Identifier of expected owner
+ *
+ * @return Boolean value
+ * @retval true BLOB belongs to the specified owner
+ * @retval false Slot belongs to another owner or Slot is not managed or
+ * BLOB does not exist
+ ******************************************************************************/
+bool sli_btmesh_blob_storage_is_managed_by_owner(uint32_t slot_id,
+ uint16_t owner_id);
+
+/***************************************************************************//**
+ * Invalidate BLOB footer in the requested slot
+ *
+ * @param[in] slot_id Identifier of slot where BLOB footer shall be invalidated
+ * @param[in] force If false then invalidation fails in case of any active
+ * asynchronous delete operation otherwise it is executed unconditionally.
+ *
+ * @return Result code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_BUSY In case of active delete operation (if force is false)
+ * @retval SL_STATUS_FAIL In case of internal API error
+ * @retval SL_STATUS_FLASH_PROGRAM_FAILED In case storage writing has failed
+ ******************************************************************************/
+sl_status_t sli_blob_storage_invalidate_slot(uint32_t slot_id,
+ bool force);
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif /* SLI_BTMESH_BLOB_STORAGE_H */
diff --git a/app/btmesh/common/btmesh_blob_transfer_client/btmesh_blob_transfer_client.dcd b/app/btmesh/common/btmesh_blob_transfer_client/btmesh_blob_transfer_client.dcd
new file mode 100644
index 00000000000..3e3728598a9
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_client/btmesh_blob_transfer_client.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1401", "name":"BLOB Transfer Client"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_blob_transfer_client/config/sl_btmesh_blob_transfer_client_config.h b/app/btmesh/common/btmesh_blob_transfer_client/config/sl_btmesh_blob_transfer_client_config.h
new file mode 100644
index 00000000000..8fabbb19839
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_client/config/sl_btmesh_blob_transfer_client_config.h
@@ -0,0 +1,136 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh BLOB Transfer Client Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_BLOB_TRANSFER_CLIENT_CONFIG_H
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// BT Mesh BLOB Transfer Client Configuration
+
+// Enable Logging
+// Default: 1
+// Enable / disable logging of BLOB Transfer Client model specific messages
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_LOGGING_CFG_VAL (1)
+
+// Text prepended to every log message
+// Default: "BLOB Transfer"
+// Every log message in the component is started with this text.
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_PREFIX_CFG_VAL "BlobTfClient"
+
+// Log BLOB Status messages
+// Default: 1
+// Log the content of BT Mesh BLOB status messages.
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL 1
+
+//
+
+// BLOB Transfer Limits
+
+// Max number of servers
+// <1-1008:1>
+// Default: 8
+// Maximum number of BLOB transfer servers which can be serviced in a transfer (affects BT Mesh stack memory usage)
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_SERVERS_CFG_VAL 8
+
+// Max number of blocks
+// <1-1888:1>
+// Default: 1850
+// Maximum number of blocks supported in a BLOB Transfer (affects BT Mesh stack memory usage)
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_BLOCKS_CFG_VAL 1850
+
+// Max number of chunks per block
+// <1-2000:1>
+// Default: 128
+// Maximum number of chunks per block supported in a BLOB Transfer (affects BT Mesh stack memory usage)
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNKS_PER_BLOCK_CFG_VAL 128
+
+// Max chunk size
+// <1-241:1>
+// Default: 241
+// Maximum chunk size which can be selected during BLOB Transfer
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNK_SIZE_CFG_VAL 241
+
+// Preferred chunk size
+// <1-241:1>
+// Default: 53
+// If the preferred chunk size is supported by all BLOB Transfer Servers then the default chunk size calculation
+// algorithm tries to select it as chunk size of the block otherwise the chunk size is set to the closest value
+// which fills all segments of the chunk. There is a tradeoff between small and large chunks.
+// The normal (non-AE) segmented chunks are able to transfer 12 bytes per advertisement minus the 1 byte
+// opcode and 2 byte chunk number and 4 bytes of MIC. This means N regular advertisements are able to
+// transfer 12 x N - 7 bytes of chunk data. If N is a big number then the payload per message converges
+// to 12 but if N is low then the fixed 7 byte protocol overhead penalty becomes significant.
+// If there is noise and at least one segment is lost then the whole chunk needs to be retransmitted.
+// Probability of transfer failure is higher for long segmented chunks and it takes more time to retransmit
+// a long chunk. The chunk size can't be arbitrarily low because the max number of chunks per block multiplied
+// by the chunk size shall be greater than or equal to block size. A low chunk size leads to more chunks per
+// block which has negative effects when the number of chunks per block exceeds 40, because the BLOB Block Status
+// message becomes segmented, which means all servers starts to respond with segmented messages. This might have
+// significant impact on the transfer speed.
+// Chunk size with 5 full segments is used as default preferred chunk size because in this case the
+// (12 x N - 7) / (12 x N) = 88% of the ideal (non-noisy) transfer speed is preserved and
+// 40 x (12 x N - 7) = 2120 -> 2048 (2^N) byte blocks can be used without BLOB Block Status
+// message segmentation.
+// If BT Mesh over Advertisement Extension Silabs proprietary feature is turned on then the default chunk
+// calculation algorithm selects the chunk size to fill the AE packet completely with chunk data based on
+// the network PDU size if the chunk size is supported by the BLOB Transfer Servers, otherwise it falls back
+// to the preferred chunk size calculation. (see Advertisement Extension Server component for details)
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_PREF_CHUNK_SIZE_CFG_VAL 53
+
+//
+
+// Retry and Separation parameters
+
+// Default separation time between chunks
+// <0-65535:1>
+// Default: 0
+// Default minimum separation time between two chunks in the same block
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_SEPARATION_TIME_MS_DEFAULT_CFG_VAL 0
+
+// Default max retry of message transmissions
+// <0-1000:1>
+// Default: 50
+// Default max retries of message transmissions (query info, transfer start, block start, block query)
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_RETRY_MAX_DEFAULT_CFG_VAL 50
+
+// Default retry time of message transmissions
+// <0-65535:1>
+// Default: 2000
+// Default retry time of message transmissions (query info, transfer start, block start, block query)
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_RETRY_TIME_MS_DEFAULT_CFG_VAL 2000
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+ #endif // SL_BTMESH_BLOB_TRANSFER_CLIENT_CONFIG_H
diff --git a/app/btmesh/common/btmesh_blob_transfer_client/sl_btmesh_blob_transfer_client.c b/app/btmesh/common/btmesh_blob_transfer_client/sl_btmesh_blob_transfer_client.c
new file mode 100644
index 00000000000..5c53c8e2921
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_client/sl_btmesh_blob_transfer_client.c
@@ -0,0 +1,1954 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh BLOB Transfer Client
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+// -----------------------------------------------------------------------------
+// Includes
+// -----------------------------------------------------------------------------
+#include
+#include "sl_status.h"
+#include "em_device.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+#include "sl_btmesh_model_specification_v1_1_defs.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+#include "sl_btmesh_blob_storage.h"
+
+#include "sl_btmesh_blob_transfer_client.h"
+#include "sl_btmesh_blob_transfer_client_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+// -----------------------------------------------------------------------------
+// Macros
+// -----------------------------------------------------------------------------
+
+// Shortcut to add log prefix to log messages
+#define LOG_PREFIX SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_PREFIX_CFG_VAL " "
+
+// Shortcut for Invalid Block Size Log constant
+#define INVALID_BLOCK_SIZE_LOG SL_BTMESH_BLOB_TRANSFER_CLIENT_INVALID_BLOCK_SIZE_LOG
+
+// Logs non-temporary procedure errors
+#define log_procedure_status_error(sc, procedure, elem_index) \
+ do { \
+ if ((SL_STATUS_BUSY != (sc)) && (SL_STATUS_NO_MORE_RESOURCE != (sc))) { \
+ log_status_error_f(sc, \
+ LOG_PREFIX procedure " failed (elem=%d)" NL, \
+ elem_index); \
+ } \
+ } while (0)
+
+// Suppress compiler warning of unused static function
+#define SL_UNUSED __attribute__((unused))
+
+// Returns the string representation of BLOB ID in a compound literal.
+// WARNING! This macro shall be used as a parameter of log calls only due to the
+// lifetime of underlying compound literal in APP_BTMESH_UUID_64_TO_STRING.
+#define BLOB_ID_TO_STRING(blob_id) APP_BTMESH_UUID_64_TO_STRING(blob_id, ' ', true)
+
+// Vendor ID of Bluetooth SIG model
+#define SIG_VENDOR_ID 0xFFFF
+
+// The BT Mesh standard network PDU size belonging to non-connectable
+// non-scannable advertisements
+#define STANDARD_NETWORK_PDU_SIZE 29
+
+// Provides string representation of MBT phase
+#define mbt_phase_to_string(mbt_phase) \
+ sl_btmesh_blob_transfer_client_mbt_phase_to_string((sl_btmesh_mbt_server_phase_t) mbt_phase)
+
+// Provides string representation of MBT status
+#define mbt_status_to_string(mbt_status) \
+ sl_btmesh_blob_transfer_client_mbt_status_to_string((sl_btmesh_mbt_server_status_t) mbt_status)
+
+/***************************************************************************//**
+ * @addtogroup blob_transfer_client BT Mesh BLOB Transfer Client
+ * @{
+ ******************************************************************************/
+
+// -----------------------------------------------------------------------------
+// Type definitions
+// -----------------------------------------------------------------------------
+
+/// BLOB Transfer Client state constants
+typedef enum {
+ /// Uninitialized state
+ STATE_UNINIT,
+ /// Inactive state
+ STATE_INACTIVE,
+ /// All BLOB transfer failed (error or timeout on every server)
+ STATE_TRANSFER_FAILED,
+ /// At least one BLOB Transfer Server received the BLOB successfully
+ STATE_TRANSFER_COMPLETED,
+ /// Last BLOB transfer was canceled by upper layer
+ STATE_TRANSFER_CANCELED,
+ /// Send BLOB information get message to servers until they respond or timeout
+ STATE_SEND_QUERY_INFO,
+ /// Send BLOB transfer start message to servers until they respond or timeout
+ STATE_SEND_TRANSFER_START,
+ /// Send BLOB block start message to servers until they respond or timeout
+ STATE_SEND_BLOCK_START,
+ /// Send BLOB chunk transfer messages to servers
+ STATE_SEND_CHUNKS,
+ /// Send BLOB block query message to servers until they respond or timeout
+ STATE_SEND_BLOCK_QUERY,
+ /// Send cancel message to servers until they respond or timeout
+ STATE_CANCELING,
+ /// Current BLOB transfer is suspended
+ STATE_SUSPENDED,
+ /// Total number of BLOB Transfer Client states
+ STATE_COUNT
+} blob_transfer_client_state_t;
+
+/// BLOB Data Provider type determines how the data is provided for the chunk
+/// transfer. Based on this information the BLOB Transfer Client is able to
+/// provide the data for the chunk transfers automatically.
+typedef enum {
+ /// No BLOB data provider is configured
+ BLOB_DATA_PROVIDER_NONE,
+ /// The BLOB data is provided in a continuous memory location (array)
+ BLOB_DATA_PROVIDER_ARRAY,
+ /// The BLOB data is provided in the BLOB storage component
+ BLOB_DATA_PROVIDER_BLOB_STORAGE
+} blob_data_provider_type_t;
+
+/// Each @ref blob_transfer_client_state_t has its own state flags which
+/// makes it possible to categorize certain states from different aspects
+typedef struct {
+ /// If idle state flag is set, then no BLOB Transfer is active and the BLOB
+ /// Transfer Client is able to start a new transfer
+ uint8_t idle : 1;
+ /// If retry state flag is set, then it means the current state supports
+ /// retry.
+ ///
+ /// In retry states the BLOB Transfer Client sends messages to the BLOB
+ /// Transfer Servers and waits for their responses. It might happen that
+ /// the request or the response message is lost which means it could be
+ /// necessary to sends these messages multiple times until every active
+ /// BLOB Transfer Server responds or times out.
+ ///
+ /// The @ref blob_transfer_client_t::retry_time_ms is measured from the
+ /// reception of the BT Mesh stack event
+ /// @ref sl_btmesh_evt_mbt_client_tx_complete_id. If the retry timer elapses
+ /// and maximum number of retries (@ref blob_transfer_client_t::retry_max) is
+ /// not yet reached, then the current state is entered again
+ /// (self-transition), which executes the proper state entry and exit actions.
+ ///
+ /// The Mesh Model 1.1 Specification refers to these retries as:
+ /// "The number of request repetitions is implementation specific"
+ uint8_t retry : 1;
+ /// Separation flag is set for chunk transfer and it keeps
+ /// @ref blob_transfer_client_t::separation_time_ms between two chunks to
+ /// spare the bandwidth in the Mesh network.
+ ///
+ /// By default 0ms is the separation time to achieve the highest possible
+ /// throughput.
+ uint8_t separation : 1;
+} blob_transfer_client_state_flags_t;
+
+typedef struct {
+ /// BLOB Data Provider type determines how data is provided for chunk transfer
+ blob_data_provider_type_t type;
+ /// Union of Data Provider descriptors
+ union {
+ struct {
+ sl_bt_uuid_64_t blob_id;
+ } blob_storage;
+ struct {
+ const uint8_t *data;
+ uint32_t length;
+ } array;
+ } descriptor;
+} blob_data_provider_t;
+
+typedef struct {
+ struct sl_simple_timer retry_timer;
+ struct sl_simple_timer separation_timer;
+ sl_btmesh_blob_transfer_client_notify_cb_t notify;
+ blob_data_provider_t data_provider;
+ uint32_t blob_size;
+ uint32_t current_chunk_offset;
+ uint16_t current_chunk_length;
+ uint16_t current_block_chunk_size;
+ uint16_t current_block_number;
+ uint16_t elem_index;
+ uint16_t separation_time_ms;
+ uint16_t retry_time_ms;
+ uint16_t retry_max;
+ uint16_t retry_counter;
+ uint16_t max_chunk_size_min;
+ uint16_t max_chunks_min;
+ uint8_t block_size_log;
+ blob_transfer_client_state_t state;
+ sl_btmesh_mbt_client_transfer_mode_t transfer_mode;
+ bool retry_time_elapsed : 1;
+ bool separation_time_elapsed : 1;
+ bool chunk_requested : 1;
+ uint8_t chunk_data[SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNK_SIZE_CFG_VAL];
+ const uint8_t *chunk_data_ptr;
+} blob_transfer_client_t;
+
+// -----------------------------------------------------------------------------
+// Static Function Declarations
+// -----------------------------------------------------------------------------
+
+/***************************************************************************//**
+ * Translates BLOB Transfer Client result and notifies the user
+ *
+ * @param self BLOB Transfer Client descriptor structure
+ * @param result BLOB Transfer Client result parameter
+ ******************************************************************************/
+static void notify_transfer_result(blob_transfer_client_t *const self,
+ sl_btmesh_blob_transfer_client_result_t result);
+
+/***************************************************************************//**
+ * Notifies user about BLOB Transfer progress
+ *
+ * @param self BLOB Transfer Client descriptor structure
+ * @param confirmed_tx_bytes Number of transmitted bytes confirmed
+ ******************************************************************************/
+static void notify_progress(blob_transfer_client_t *const self,
+ uint32_t confirmed_tx_bytes);
+
+/***************************************************************************//**
+ * Notifies user that server has failed
+ *
+ * @param self BLOB Transfer Client descriptor structure
+ * @param server_address Address of the server that failed
+ * @param timeout Timout flag
+ * @param error Error code of failure
+ ******************************************************************************/
+static void notify_server_failed(blob_transfer_client_t *const self,
+ uint16_t server_address,
+ uint8_t timeout,
+ uint8_t error);
+
+/***************************************************************************//**
+ * Notifies user that transfer has failed
+ *
+ * @param self BLOB Transfer Client descriptor structure
+ ******************************************************************************/
+static void process_transfer_failed(blob_transfer_client_t *const self);
+
+/***************************************************************************//**
+ * Transitions the BLOB Transfer Client state machine into the selected state
+ *
+ * @param self Pointer to the BLOB Transfer Client descriptor structure
+ * @param target_state State to transfer into
+ ******************************************************************************/
+static void state_transition(blob_transfer_client_t *const self,
+ blob_transfer_client_state_t target_state);
+
+/***************************************************************************//**
+ * Handles Transfer Complete event
+ *
+ * The event is received when the transfer has completed, either successfully or
+ * unsuccessfully. Success depends on the state of servers.
+ *
+ * @param self Pointer to the BLOB Transfer Client descriptor structure
+ ******************************************************************************/
+static void handle_transfer_complete(blob_transfer_client_t *const self);
+
+/***************************************************************************//**
+ * Callback for retry timer
+ *
+ * @param timer Timer handler
+ * @param data Callback data
+ ******************************************************************************/
+static void retry_timer_cb(sl_simple_timer_t *timer, void *data);
+
+/***************************************************************************//**
+ * Callback for separation timer
+ *
+ * @param timer Timer handler
+ * @param data Callback data
+ ******************************************************************************/
+static void separation_timer_cb(sl_simple_timer_t *timer, void *data);
+
+/***************************************************************************//**
+ * Callback for chunk request response retry timer
+ *
+ * @param timer Timer handler
+ * @param data Callback data
+ ******************************************************************************/
+static void send_chunk_request_response(sl_simple_timer_t *timer, void *data);
+
+// -----------------------------------------------------------------------------
+// Static Inline Function Declarations
+// -----------------------------------------------------------------------------
+
+/***************************************************************************//**
+ * Calculates the number of blocks for the BLOB being transferred
+ *
+ * @param self Pointer to the BLOB Transfer Client descriptor structure
+ * @return Number of blocks in BLOB
+ ******************************************************************************/
+__STATIC_INLINE uint16_t calc_total_block_count(blob_transfer_client_t *const self)
+{
+ uint32_t block_count = (self->blob_size >> self->block_size_log);
+
+ // Check if there's a partial block at the end
+ if (0 != (self->blob_size & ((1UL << self->block_size_log) - 1))) {
+ // If so increment block count to include the partial last block
+ block_count++;
+ }
+ return block_count;
+}
+
+// -----------------------------------------------------------------------------
+// Static Variables
+// -----------------------------------------------------------------------------
+
+static const blob_transfer_client_state_flags_t state_flags[STATE_COUNT] = {
+ [STATE_UNINIT] = { .idle = 0, .retry = 0, .separation = 0 },
+ [STATE_INACTIVE] = { .idle = 1, .retry = 0, .separation = 0 },
+ [STATE_TRANSFER_FAILED] = { .idle = 1, .retry = 0, .separation = 0 },
+ [STATE_TRANSFER_COMPLETED] = { .idle = 1, .retry = 0, .separation = 0 },
+ [STATE_TRANSFER_CANCELED] = { .idle = 1, .retry = 0, .separation = 0 },
+ [STATE_SEND_QUERY_INFO] = { .idle = 0, .retry = 1, .separation = 0 },
+ [STATE_SEND_TRANSFER_START] = { .idle = 0, .retry = 1, .separation = 0 },
+ [STATE_SEND_BLOCK_START] = { .idle = 0, .retry = 1, .separation = 0 },
+ [STATE_SEND_CHUNKS] = { .idle = 0, .retry = 0, .separation = 1 },
+ [STATE_SEND_BLOCK_QUERY] = { .idle = 0, .retry = 1, .separation = 0 },
+ [STATE_CANCELING] = { .idle = 0, .retry = 1, .separation = 0 },
+ [STATE_SUSPENDED] = { .idle = 0, .retry = 0, .separation = 0 }
+};
+
+static blob_transfer_client_t blob_tf_client;
+
+// -----------------------------------------------------------------------------
+// Public Function Definitions
+// -----------------------------------------------------------------------------
+
+// Set up a new BLOB transfer
+sl_status_t sl_btmesh_blob_transfer_client_setup(uint16_t elem_index,
+ sl_bt_uuid_64_t blob_id,
+ uint32_t blob_size,
+ uint16_t appkey_index,
+ uint8_t ttl,
+ uint32_t timeout_base,
+ uint16_t group_address,
+ uuid_128 virtual_address,
+ uint16_t multicast_threshold,
+ size_t servers_len,
+ const uint8_t* servers)
+{
+ sl_status_t sc;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ if (0 == state_flags[self->state].idle) {
+ log_error(LOG_PREFIX
+ "Setup call in non-idle state (elem=%d)" NL,
+ elem_index);
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ self->blob_size = blob_size;
+
+ // The BLOB Transfer Server component supports both transfer mode so the mbt
+ // client stack model setup function is called accordingly and the transfer
+ // mode is selected when the BLOB transfer is started
+ sc = sl_btmesh_mbt_client_setup(elem_index,
+ blob_id,
+ blob_size,
+ appkey_index,
+ ttl,
+ timeout_base,
+ (uint8_t) sl_btmesh_mbt_client_mbt_transfer_mode_both,
+ group_address,
+ virtual_address,
+ multicast_threshold,
+ servers_len,
+ servers);
+ log_status_error_f(sc,
+ LOG_PREFIX "Setup has failed (elem=%d)" NL,
+ elem_index);
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_transfer_client_set_params(uint16_t elem_index,
+ uint16_t separation_time_ms,
+ uint16_t retry_time_ms,
+ uint16_t retry_max)
+{
+ (void) elem_index;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ if (0 == state_flags[self->state].idle) {
+ log_error(LOG_PREFIX
+ "set Params call in non-idle state (elem=%d)" NL,
+ elem_index);
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ self->separation_time_ms = separation_time_ms;
+ self->retry_time_ms = retry_time_ms;
+ self->retry_max = retry_max;
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_transfer_client_setup_data_provider_array(uint16_t elem_index,
+ const uint8_t *array,
+ uint32_t length)
+{
+ (void)elem_index;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ app_assert(NULL != array, "BLOB Transfer array is null.");
+ app_assert(0 != length, "BLOB Transfer array length is zero.");
+
+ if (0 == state_flags[self->state].idle) {
+ log_error(LOG_PREFIX
+ "Data Provider call in non-idle state (elem=%d)" NL,
+ elem_index);
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ // If the upper layer is NOT the distribution server, then the BLOB size
+ // parameter of sl_btmesh_blob_transfer_client_setup shall be consistent with
+ // data size parameter of data provider
+ // Note: if the upper layer is the btmesh_fw_distribution_server then the
+ // sl_btmesh_blob_transfer_client_setup is not called as the setup
+ // process is executed automatically in the BT Mesh stack
+ if (0 == self->blob_size) {
+ self->blob_size = length;
+ } else if (self->blob_size != length) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+
+ self->data_provider.type = BLOB_DATA_PROVIDER_ARRAY;
+ self->data_provider.descriptor.array.data = array;
+ self->data_provider.descriptor.array.length = length;
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_transfer_client_setup_data_provider_blob_storage(uint16_t elem_index,
+ const sl_bt_uuid_64_t *blob_id)
+{
+ (void)elem_index;
+ blob_transfer_client_t *const self = &blob_tf_client;
+ uint32_t blob_size;
+
+ app_assert(NULL != blob_id, "BLOB id is null.");
+
+ if (0 == state_flags[self->state].idle) {
+ log_error(LOG_PREFIX
+ "Data Provider call in non-idle state (elem=%d)" NL,
+ elem_index);
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ // Check if the BLOB id exists at all in the BLOB storage
+ sl_status_t sc = sl_btmesh_blob_storage_get_blob_size(blob_id,
+ &blob_size);
+ if (SL_STATUS_OK != sc) {
+ log_status_error_f(sc,
+ LOG_PREFIX "Data Provider setup failed (elem=%d)" NL,
+ elem_index);
+ return sc;
+ }
+ // If the upper layer is NOT the distribution server then BLOB size parameter
+ // of sl_btmesh_blob_transfer_client_setup shall be consistent with data size
+ // parameter of data provider
+ // Note: if the upper layer is the btmesh_fw_distribution_server then the
+ // sl_btmesh_blob_transfer_client_setup is not called as the setup
+ // process is executed automatically in the BT Mesh stack
+ if (0 == self->blob_size) {
+ self->blob_size = blob_size;
+ } else if (self->blob_size != blob_size) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+
+ self->data_provider.type = BLOB_DATA_PROVIDER_BLOB_STORAGE;
+ memcpy(&self->data_provider.descriptor.blob_storage.blob_id,
+ blob_id,
+ sizeof(self->data_provider.descriptor.blob_storage.blob_id));
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_blob_transfer_client_start(uint16_t elem_index,
+ sl_btmesh_mbt_client_transfer_mode_t transfer_mode,
+ sl_btmesh_blob_transfer_client_notify_cb_t notify)
+{
+ (void)elem_index;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ if (transfer_mode == sl_btmesh_mbt_client_mbt_transfer_mode_none) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+
+ if (0 == state_flags[self->state].idle) {
+ log_error(LOG_PREFIX
+ "Start call in non-idle state (elem=%d)" NL,
+ elem_index);
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ if (BLOB_DATA_PROVIDER_NONE == self->data_provider.type) {
+ return SL_STATUS_INVALID_CONFIGURATION;
+ }
+
+ self->elem_index = elem_index;
+ self->transfer_mode = transfer_mode;
+ self->notify = notify;
+
+ state_transition(self, STATE_SEND_QUERY_INFO);
+
+ if (STATE_SEND_QUERY_INFO != self->state) {
+ // The state transition might not be successful, if the query information
+ // BT Mesh stack API call fails (sl_btmesh_mbt_client_query_information)
+ // however this should not happen.
+ log_error(LOG_PREFIX
+ "Start call state transition failed (elem=%d,state=%d)" NL,
+ elem_index,
+ self->state);
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ return SL_STATUS_OK;
+}
+
+void sl_btmesh_blob_transfer_client_cancel(uint16_t elem_index)
+{
+ (void) elem_index;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ state_transition(self, STATE_CANCELING);
+}
+
+sl_status_t sl_btmesh_blob_transfer_client_abort(uint16_t elem_index)
+{
+ (void) elem_index;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ state_transition(self, STATE_INACTIVE);
+
+ return sl_btmesh_mbt_client_abort(self->elem_index);
+}
+
+sl_status_t sl_btmesh_blob_transfer_client_on_aborted(uint16_t elem_index)
+{
+ (void) elem_index;
+ sl_status_t sc;
+
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ if (STATE_INACTIVE != self->state) {
+ state_transition(self, STATE_INACTIVE);
+ sc = SL_STATUS_OK;
+ } else {
+ sc = SL_STATUS_INVALID_STATE;
+ }
+
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_transfer_client_on_suspended(uint16_t elem_index)
+{
+ (void) elem_index;
+ sl_status_t sc = SL_STATUS_FAIL;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ if (0 == state_flags[self->state].idle) {
+ state_transition(self, STATE_SUSPENDED);
+ sc = SL_STATUS_OK;
+ } else {
+ sc = SL_STATUS_INVALID_STATE;
+ }
+
+ return sc;
+}
+
+sl_status_t sl_btmesh_blob_transfer_client_on_resumed(uint16_t elem_index)
+{
+ (void) elem_index;
+ sl_status_t sc = SL_STATUS_FAIL;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ if (STATE_SUSPENDED == self->state) {
+ state_transition(self, STATE_SEND_TRANSFER_START);
+ sc = SL_STATUS_OK;
+ } else {
+ sc = SL_STATUS_INVALID_STATE;
+ }
+
+ return sc;
+}
+
+SL_WEAK uint8_t
+sl_btmesh_blob_transfer_client_calculate_block_size_log(uint32_t blob_size,
+ uint8_t block_size_log_min,
+ uint8_t block_size_log_max,
+ uint16_t block_count_max,
+ uint16_t max_chunk_size_min,
+ uint16_t max_chunks_min)
+{
+ uint32_t block_size_min_limit, block_size_max_limit;
+ uint8_t block_size_log = block_size_log_max;
+ uint32_t block_size = 1UL << block_size_log_max;
+
+ // Block size can't be greater than the product of the maximum chunk size and
+ // the maximum number of chunks per block
+ block_size_max_limit =
+ (uint32_t) max_chunk_size_min * max_chunks_min;
+ // Minimum block size is limited by the maximum number of blocks, because
+ // high BLOB size with small blocks leads to too many blocks.
+ block_size_min_limit = (blob_size + block_count_max - 1) / block_count_max;
+
+ // The default block size calculation prefers the maximum possible block size,
+ // which leads to the highest transfer speed due to fewer BLOB Block Start
+ // Block Get and Block Status messages. BLOB Transfer Client has to wait for
+ // the status message of every BLOB Transfer Server which can add up quite
+ // fast in a multicast scenario, where there are tens or hundreds of nodes.
+ // Note: It is unlikely, but possible, that the block_size_log_max is smaller
+ // than block_size_log_min because the block size log ranges of the
+ // different BLOB Transfer Servers have no intersection. In this case it
+ // is not possible to transfer the BLOB to all servers at the same time
+ // so the implementation chooses the block_size_log_max to send the BLOB
+ // at least to a subset of servers.
+ // In general this should not happen in case of firmware image transfer
+ // because the same kind of updating nodes are updated with the same
+ // firmware so their capabilities should match.
+ for (block_size_log = block_size_log_max;
+ block_size_log_min <= block_size_log;
+ block_size_log--) {
+ block_size = 1UL << block_size_log;
+ if (block_size <= block_size_max_limit) {
+ break;
+ }
+ }
+
+ if ((block_size < block_size_min_limit)
+ && (block_size_max_limit < block_size)) {
+ return SL_BTMESH_BLOB_TRANSFER_CLIENT_INVALID_BLOCK_SIZE_LOG;
+ }
+
+ return block_size_log;
+}
+
+SL_WEAK uint16_t
+sl_btmesh_blob_transfer_client_calculate_chunk_size(uint8_t block_size_log,
+ uint16_t max_chunk_size_min,
+ uint16_t max_chunks_min,
+ uint16_t nw_pdu_size)
+{
+ const uint16_t LOTP_PAYLOAD_PER_SEG_MSG = 12;
+ const uint16_t LOTP_PAYLOAD_PER_NON_SEG_MSG = 15;
+ // Protocol overhead of Network Layer
+ // (IVI+NID) + (CTL+TTL) + SEQ + SRC + DST + NetMIC = 13 bytes
+ // 1 + 1 + 3 + 2 + 2 + 4 = 13 bytes
+ const uint16_t NW_MSG_PROT_OVERHEAD = 13;
+ // Protocol overhead of Lower Transport Layer
+ // (SEG+AID+AKF) = 1 byte
+ const uint16_t LOTP_UNSEG_MSG_PROT_OVERHEAD = 1;
+ // Protocol overhead of Upper Transport Layer: TransMIC (4 bytes)
+ const uint16_t UPTP_PROT_OVERHEAD = 4;
+ // BLOB Chunk Transfer access message: Opcode (1byte) + Chunk number (2 byte)
+ const uint16_t ACCESS_CHUNK_PROT_OVERHEAD = 3;
+ // Total protocol overhead of upper transport layer and access layer
+ const uint16_t UPTP_ACCESS_PROT_OVERHEAD =
+ UPTP_PROT_OVERHEAD + ACCESS_CHUNK_PROT_OVERHEAD;
+ // BLOB Chunk Transfer unsegmented access message data size
+ const uint16_t ACCESS_CHUNK_DATA_PER_NON_SEG_MSG =
+ LOTP_PAYLOAD_PER_NON_SEG_MSG - UPTP_ACCESS_PROT_OVERHEAD;
+ // Total protocol overhead in NW PDU (unsegmented)
+ const uint16_t TOTAL_UNSEG_PROT_OVERHEAD = NW_MSG_PROT_OVERHEAD
+ + LOTP_UNSEG_MSG_PROT_OVERHEAD
+ + UPTP_PROT_OVERHEAD
+ + ACCESS_CHUNK_PROT_OVERHEAD;
+
+ uint32_t block_size = 1 << block_size_log;
+ // The block size and maximum number of chunks limits the chunk size (minimum)
+ uint16_t min_chunk_size = (block_size + max_chunks_min - 1) / max_chunks_min;
+ uint16_t chunk_size;
+
+ if (SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNK_SIZE_CFG_VAL < max_chunk_size_min) {
+ max_chunk_size_min = SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNK_SIZE_CFG_VAL;
+ }
+
+ if (max_chunk_size_min < min_chunk_size) {
+ // This should not happen if the BLOB transfer servers are configured properly.
+ // The max_chunk_size_min is returned because it might work for some servers
+ // when the configurations of BLOB transfer servers are different.
+ return max_chunk_size_min;
+ }
+
+ if (STANDARD_NETWORK_PDU_SIZE < nw_pdu_size) {
+ // The Silicon Labs BT Mesh over Advertisement Extensions proprietary feature
+ // is enabled so the chunk size is calculated to fit into a single AE packet
+ // unless the calculated chunk size is not in the allowed chunk size range.
+ chunk_size = nw_pdu_size - TOTAL_UNSEG_PROT_OVERHEAD;
+ if (min_chunk_size <= chunk_size && chunk_size <= max_chunk_size_min) {
+ return chunk_size;
+ } else if (max_chunk_size_min < chunk_size) {
+ return max_chunk_size_min;
+ } else {
+ // If AE packet can't be used then the chunk size calculation falls back
+ // to standard BT Mesh 1.0 advertisements.
+ }
+ }
+
+ if (max_chunk_size_min <= (2 * LOTP_PAYLOAD_PER_SEG_MSG - UPTP_ACCESS_PROT_OVERHEAD)) {
+ if (max_chunk_size_min <= ACCESS_CHUNK_DATA_PER_NON_SEG_MSG) {
+ // If max_chunk_size_min is less than chunk data size which fits into a
+ // single segment then the max_chunk_size_min is used as the chunk size.
+ chunk_size = max_chunk_size_min;
+ } else {
+ // If the max chunk size does not make it possible to fill two segments
+ // completely with data then it is not efficient to send segmented messages
+ // so unsegmented chunks are used if min_chunk_size makes it possible.
+ if (min_chunk_size <= ACCESS_CHUNK_DATA_PER_NON_SEG_MSG) {
+ chunk_size = ACCESS_CHUNK_DATA_PER_NON_SEG_MSG;
+ } else {
+ chunk_size = max_chunk_size_min;
+ }
+ }
+ } else if (SL_BTMESH_BLOB_TRANSFER_CLIENT_PREF_CHUNK_SIZE_CFG_VAL < min_chunk_size) {
+ // If the preferred chunk size is less than the min_chunk_size then chunk
+ // size is set to the closest value which results in full chunk segments.
+ chunk_size = ((min_chunk_size + UPTP_ACCESS_PROT_OVERHEAD + LOTP_PAYLOAD_PER_SEG_MSG - 1)
+ / LOTP_PAYLOAD_PER_SEG_MSG) * LOTP_PAYLOAD_PER_SEG_MSG
+ - UPTP_ACCESS_PROT_OVERHEAD;
+ if (chunk_size < min_chunk_size || max_chunk_size_min < chunk_size) {
+ // If selected chunk size does not fit into the allowed chunk size range
+ // then min_chunk_size is selected.
+ chunk_size = min_chunk_size;
+ }
+ } else if (max_chunk_size_min < SL_BTMESH_BLOB_TRANSFER_CLIENT_PREF_CHUNK_SIZE_CFG_VAL) {
+ // If the preferred chunk size is greater than the max_chunk_size_min then
+ // chunk size is set to the closest value which results in full chunk segments.
+ chunk_size = ((max_chunk_size_min + UPTP_ACCESS_PROT_OVERHEAD)
+ / LOTP_PAYLOAD_PER_SEG_MSG) * LOTP_PAYLOAD_PER_SEG_MSG
+ - UPTP_ACCESS_PROT_OVERHEAD;
+ if (chunk_size < min_chunk_size || max_chunk_size_min < chunk_size) {
+ // If selected chunk size does not fit into the allowed chunk size range
+ // then max_chunk_size_min is selected.
+ chunk_size = max_chunk_size_min;
+ }
+ } else {
+ // The preferred chunk size is selected if it fits into the allowed
+ // chunk size range.
+ chunk_size = SL_BTMESH_BLOB_TRANSFER_CLIENT_PREF_CHUNK_SIZE_CFG_VAL;
+ }
+
+ return chunk_size;
+}
+
+// -----------------------------------------------------------------------------
+// Static Function Definitions
+// -----------------------------------------------------------------------------
+
+static void notify_transfer_result(blob_transfer_client_t *const self,
+ sl_btmesh_blob_transfer_client_result_t result)
+{
+ if (NULL != self->notify) {
+ sl_btmesh_blob_transfer_client_notification_t notification;
+ notification.kind = SL_BTMESH_BLOB_TRANSFER_CLIENT_KIND_TRANSFER_RESULT;
+ notification.elem_index = self->elem_index;
+ notification.params.transfer_result = result;
+ self->notify(¬ification);
+ }
+}
+
+static void notify_progress(blob_transfer_client_t *const self,
+ uint32_t confirmed_tx_bytes)
+{
+ if (NULL != self->notify) {
+ sl_btmesh_blob_transfer_client_notification_t notification;
+ notification.kind = SL_BTMESH_BLOB_TRANSFER_CLIENT_KIND_PROGRESS;
+ notification.elem_index = self->elem_index;
+ notification.params.progress.confirmed_tx_bytes = confirmed_tx_bytes;
+ notification.params.progress.blob_size = self->blob_size;
+ self->notify(¬ification);
+ }
+}
+
+static void notify_server_failed(blob_transfer_client_t *const self,
+ uint16_t server_address,
+ uint8_t timeout,
+ uint8_t error)
+{
+ if (NULL != self->notify) {
+ sl_btmesh_blob_transfer_client_notification_t notification;
+ notification.kind = SL_BTMESH_BLOB_TRANSFER_CLIENT_KIND_SERVER_FAILED;
+ notification.elem_index = self->elem_index;
+ notification.params.server_failed.server_address = server_address;
+ notification.params.server_failed.timeout = timeout;
+ notification.params.server_failed.error = error;
+ self->notify(¬ification);
+ }
+}
+
+static void process_transfer_failed(blob_transfer_client_t *const self)
+{
+ notify_transfer_result(self,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_RESULT_FAILED);
+ state_transition(self, STATE_TRANSFER_FAILED);
+}
+
+static void start_retry_timer(blob_transfer_client_t *const self)
+{
+ sl_status_t sc;
+
+ if (0 == self->retry_time_ms) {
+ // If the retry time is zero then it means that the retry shall be performed
+ // immediately after the current messages are sent to the server.
+ // (tx complete)
+ retry_timer_cb(&self->retry_timer, self);
+ } else {
+ // Wait for the retry time to provide some time for the servers to respond
+ // This also spares bandwidth, because the BLOB transfer client does not
+ // flood the mesh network with messages
+ sc = sl_simple_timer_start(&self->retry_timer,
+ self->retry_time_ms,
+ retry_timer_cb,
+ self,
+ false);
+ app_assert_status_f(sc,
+ "Failed to start retry timer (elem=%d)",
+ self->elem_index);
+ }
+}
+
+static void restart_retry_timer(blob_transfer_client_t *const self)
+{
+ // In case in the meantime the state-machine transitioned into a state without
+ // retry, to avoid unintended behavior.
+ if (!state_flags[self->state].retry) {
+ return;
+ }
+ start_retry_timer(self);
+}
+
+static void stop_retry_timer(blob_transfer_client_t *const self)
+{
+ // It is not considered an error, if stop is requested for a timer which is
+ // not running therefore stop is always called here to be safe
+ sl_status_t sc = sl_simple_timer_stop(&self->retry_timer);
+
+ app_assert_status_f(sc,
+ "Failed to stop retry timer (elem=%d)",
+ self->elem_index);
+}
+
+static void start_separation_timer(blob_transfer_client_t *const self)
+{
+ sl_status_t sc;
+
+ if (0 == self->separation_time_ms) {
+ /*
+ * If the separation time is zero then the callback function shall be called
+ * immediately and it is not necessary to start the timer at all
+ */
+ separation_timer_cb(&self->separation_timer, self);
+ } else {
+ /*
+ * Wait the separation time to have some idle time between mesh messages
+ * to avoid flooding the mesh network with messages
+ */
+ sc = sl_simple_timer_start(&self->separation_timer,
+ self->separation_time_ms,
+ separation_timer_cb,
+ self,
+ false);
+ app_assert_status_f(sc,
+ "Failed to start separation timer (elem=%d)",
+ self->elem_index);
+ }
+}
+
+static void stop_separation_timer(blob_transfer_client_t *const self)
+{
+ // It is not considered an error, if stop is requested for a timer which is
+ // not running therefore stop is always called here to be safe
+ sl_status_t sc = sl_simple_timer_stop(&self->separation_timer);
+
+ app_assert_status_f(sc,
+ "Failed to stop separation timer (elem=%d)",
+ self->elem_index);
+}
+
+static void sl_btmesh_blob_transfer_client_element_init(uint16_t elem_index)
+{
+ sl_status_t sc;
+ blob_transfer_client_t *const self = &blob_tf_client;
+
+ self->data_provider.type = BLOB_DATA_PROVIDER_NONE;
+ self->elem_index = elem_index;
+ self->state = STATE_UNINIT;
+
+ sc = sl_btmesh_mbt_client_init(elem_index,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_SERVERS_CFG_VAL,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_BLOCKS_CFG_VAL,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNKS_PER_BLOCK_CFG_VAL);
+
+ app_assert_status_f(sc, "Failed to init BLOB Transfer Client");
+
+ state_transition(self, STATE_INACTIVE);
+}
+
+static void sl_btmesh_blob_transfer_client_init(void)
+{
+ sl_btmesh_blob_transfer_client_element_init(BTMESH_BLOB_TRANSFER_CLIENT_MAIN);
+}
+
+// Process the return value of MBT procedure BT Mesh stack API calls in retry state
+static void process_mbt_procedure_status(blob_transfer_client_t *const self,
+ sl_status_t sc)
+{
+ if (SL_STATUS_OK != sc) {
+ if ((SL_STATUS_BUSY == sc) || (SL_STATUS_NO_MORE_RESOURCE == sc)) {
+ // The retry counter is not incremented here, because the message was not
+ // sent at all due to busy transport layer or not enough memory but the
+ // current operation shall be retried later
+ start_retry_timer(self);
+ } else {
+ // The retry counter is incremented to reduce number of retries in case
+ // of unrecoverable errors but the retry timer is started for additional
+ // robustness in case of unexpected temporary errors
+ if ((self->retry_counter + 1) < self->retry_max) {
+ // The retry counter is incremented by 2 overall (other in state_transition)
+ self->retry_counter++;
+ start_retry_timer(self);
+ }
+ }
+ }
+}
+
+static void super_state_idle_entry(blob_transfer_client_t *const self)
+{
+ self->data_provider.type = BLOB_DATA_PROVIDER_NONE;
+ self->separation_time_ms = SL_BTMESH_BLOB_TRANSFER_CLIENT_SEPARATION_TIME_MS_DEFAULT_CFG_VAL;
+ self->retry_max = SL_BTMESH_BLOB_TRANSFER_CLIENT_RETRY_MAX_DEFAULT_CFG_VAL;
+ self->retry_time_ms = SL_BTMESH_BLOB_TRANSFER_CLIENT_RETRY_TIME_MS_DEFAULT_CFG_VAL;
+ self->blob_size = 0;
+}
+
+static void state_send_query_info_entry(blob_transfer_client_t *const self)
+{
+ sl_status_t sc;
+
+ // Send the BLOB Information Get messages to the BLOB Transfer servers.
+ // Based on the multicast threshold multiple messages could be sent.
+ // Multiple BLOB transfer server can receive the messages
+ sc = sl_btmesh_mbt_client_query_information(self->elem_index);
+
+ log_info(LOG_PREFIX "Query Info procedure %s%s(elem=%d)" NL,
+ (0 < self->retry_counter) ? "retry " : "",
+ (SL_STATUS_BUSY == sc) ? "busy "
+ : ((SL_STATUS_NO_MORE_RESOURCE == sc) ? "no memory " : ""),
+ self->elem_index);
+
+ process_mbt_procedure_status(self, sc);
+ log_procedure_status_error(sc, "Query Info", self->elem_index);
+}
+
+static void state_send_transfer_start_entry(blob_transfer_client_t *const self)
+{
+ sl_status_t sc;
+
+ // Send the BLOB Transfer Start messages to the BLOB Transfer servers.
+ // Based on the multicast threshold multiple messages could be sent.
+ // Multiple BLOB transfer server can receive the messages
+ sc = sl_btmesh_mbt_client_start_transfer(self->elem_index,
+ self->block_size_log,
+ self->transfer_mode);
+
+ log_info(LOG_PREFIX "Start procedure %s%s(elem=%d,blk_log=%d,tf_mode=%s)" NL,
+ (0 < self->retry_counter) ? "retry " : "",
+ (SL_STATUS_BUSY == sc) ? "busy "
+ : ((SL_STATUS_NO_MORE_RESOURCE == sc) ? "no memory " : ""),
+ self->elem_index,
+ self->block_size_log,
+ sl_btmesh_blob_transfer_client_transfer_mode_to_string(self->transfer_mode));
+
+ process_mbt_procedure_status(self, sc);
+ log_procedure_status_error(sc, "Start", self->elem_index);
+}
+
+static void state_send_start_block_entry(blob_transfer_client_t *const self)
+{
+ sl_status_t sc;
+ uint16_t block_number = 0;
+ uint32_t block_size = 0;
+
+ // Send the BLOB Transfer Block Start messages to the BLOB Transfer servers.
+ // Based on the multicast threshold multiple messages could be sent.
+ // Multiple BLOB transfer server can receive the messages
+ // Note: the block number and block size are not used here, because those are
+ // set in the block start complete event.
+ sc = sl_btmesh_mbt_client_start_block(self->elem_index,
+ self->current_block_chunk_size,
+ &block_number,
+ &block_size);
+
+ log_info(LOG_PREFIX "Block Start procedure %s%s(elem=%d,chunk_size=%d)" NL,
+ (0 < self->retry_counter) ? "retry " : "",
+ (SL_STATUS_BUSY == sc) ? "busy "
+ : ((SL_STATUS_NO_MORE_RESOURCE == sc) ? "no memory " : ""),
+ self->elem_index,
+ self->current_block_chunk_size);
+
+ process_mbt_procedure_status(self, sc);
+ log_procedure_status_error(sc, "Block Start", self->elem_index);
+}
+
+static void state_send_block_query_entry(blob_transfer_client_t *const self)
+{
+ sl_status_t sc;
+
+ sc = sl_btmesh_mbt_client_query_block_status(self->elem_index);
+
+ log_info(LOG_PREFIX "Block Query procedure %s%s(elem=%d)" NL,
+ (0 < self->retry_counter) ? "retry " : "",
+ (SL_STATUS_BUSY == sc) ? "busy "
+ : ((SL_STATUS_NO_MORE_RESOURCE == sc) ? "no memory " : ""),
+ self->elem_index);
+
+ process_mbt_procedure_status(self, sc);
+ log_procedure_status_error(sc, "Block Query", self->elem_index);
+}
+
+static void state_send_cancel(blob_transfer_client_t *const self)
+{
+ sl_status_t sc;
+
+ sc = sl_btmesh_mbt_client_cancel_transfer(self->elem_index);
+
+ log_info(LOG_PREFIX "Transfer Cancel procedure %s%s(elem=%d)" NL,
+ (0 < self->retry_counter) ? "retry " : "",
+ (SL_STATUS_BUSY == sc) ? "busy "
+ : ((SL_STATUS_NO_MORE_RESOURCE == sc) ? "no memory " : ""),
+ self->elem_index);
+
+ process_mbt_procedure_status(self, sc);
+ log_procedure_status_error(sc, "Cancel", self->elem_index);
+}
+
+static void send_chunk(blob_transfer_client_t *const self)
+{
+ // This is a sanity check to avoid buffer overflow, but this should not happen
+ // due to the max chunk size saturation in handle_query_information_complete
+ if (SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNK_SIZE_CFG_VAL < self->current_chunk_length) {
+ log_error(LOG_PREFIX "chunk length is too high (elem=%d,"
+ "chunk_len=0x%04X,chunk_len_max=0x%04X)" NL,
+ self->elem_index,
+ self->current_chunk_length,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNK_SIZE_CFG_VAL);
+ process_transfer_failed(self);
+ return;
+ }
+
+ self->chunk_requested = false;
+ self->separation_time_elapsed = false;
+
+ if (BLOB_DATA_PROVIDER_ARRAY == self->data_provider.type) {
+ self->chunk_data_ptr =
+ &self->data_provider.descriptor.array.data[self->current_chunk_offset];
+ const uint32_t array_length = self->data_provider.descriptor.array.length;
+
+ if (array_length < self->current_chunk_offset + self->current_chunk_length) {
+ log_error(LOG_PREFIX
+ "length mismatch when read from array provider "
+ "(elem=%d,offset=0x%08lX,chunk_len=0x%04X,array_len=0x%08lX)" NL,
+ self->elem_index,
+ self->current_chunk_offset,
+ self->current_chunk_length,
+ array_length);
+ process_transfer_failed(self);
+ return;
+ }
+ } else if (BLOB_DATA_PROVIDER_BLOB_STORAGE == self->data_provider.type) {
+ sl_status_t sc;
+ uint32_t chunk_length = self->current_chunk_length;
+ const sl_bt_uuid_64_t *blob_id =
+ &self->data_provider.descriptor.blob_storage.blob_id;
+ self->chunk_data_ptr = &self->chunk_data[0];
+
+ sc = sl_btmesh_blob_storage_read(blob_id,
+ self->current_chunk_offset,
+ &chunk_length,
+ &self->chunk_data[0]);
+ if (SL_STATUS_OK != sc) {
+ log_status_error_f(sc,
+ LOG_PREFIX
+ "failed to read from BLOB storage "
+ "(elem=%d,blobid=%s,offset=0x%08lX,chunk_len=0x%04X)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id),
+ self->current_chunk_offset,
+ self->current_chunk_length);
+ process_transfer_failed(self);
+ return;
+ } else if (chunk_length != self->current_chunk_length) {
+ // The length of the read data is less than the requested
+ log_error(LOG_PREFIX
+ "BLOB storage read length mismatch (elem=%d,blobid=%s,"
+ "offset=0x%08lX,req_chunk_len=0x%04X,is_chunk_len=0x%04lX)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id),
+ self->current_chunk_offset,
+ self->current_chunk_length,
+ chunk_length);
+ process_transfer_failed(self);
+ return;
+ }
+ } else {
+ // This is an unexpected error, as previous state machine state makes sure
+ // this should not happen so the transfer is stopped and a log message
+ // is emitted
+ log_error(LOG_PREFIX "invalid data provider" NL);
+ process_transfer_failed(self);
+ return;
+ }
+
+ // Chunk request has its own retry mechanism
+ send_chunk_request_response(&self->retry_timer, self);
+}
+
+static void handle_server_information_status(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_server_information_status_t *const evt)
+{
+ (void)evt; // To suppress the warning if the logging is disabled
+
+ // Restart the timer since we know in case this event is handled, that the
+ // message was sent out properly. However, we don't stop the retry timer,
+ // since if any node doesn't respond, we still need to retry.
+ restart_retry_timer(self);
+
+#if (SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL != 0)
+ // This event is triggered, if one BLOB Information Status message is received
+ // from a specific server. The BT Mesh stack waits for the status message of
+ // every server and triggers the query information complete event with the
+ // block and chunk parameters supported by every server.
+ // This means that it is not necessary to store the information about every
+ // server as the stack aggregates them and provides it in another event.
+ // The individual information could be useful for analysis and diagnostics
+ // so it is logged.
+ log_info(LOG_PREFIX
+ "Info received (elem=%d,addr=0x%04X,blk_log=%d-%d,"
+ "max_chunk_size=0x%04X,max_chunks=%d)" NL,
+ evt->elem_index,
+ evt->server_address,
+ evt->min_block_size_log,
+ evt->max_block_size_log,
+ evt->max_chunk_size,
+ evt->max_chunks);
+#endif // SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL
+}
+
+static void handle_server_transfer_status(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_server_transfer_status_t *const evt)
+{
+ (void)evt; // To suppress the warning if the logging is disabled
+
+ // Restart the timer since we know in case this event is handled, that the
+ // message was sent out properly. However, we don't stop the retry timer,
+ // since if any node doesn't respond, we still need to retry.
+ restart_retry_timer(self);
+
+#if (SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL != 0)
+ log_info(LOG_PREFIX
+ "Transfer Status received (elem=%d,addr=0x%04X,status=%s,"
+ "phase=%s,blobid=%s,blob_size=0x%08lX,blk_log=%d",
+ evt->elem_index,
+ evt->server_address,
+ mbt_status_to_string(evt->status),
+ mbt_phase_to_string(evt->phase),
+ BLOB_ID_TO_STRING(&evt->blob_id),
+ evt->blob_size,
+ evt->block_size_log);
+ if (0 < evt->blocks_not_received.len) {
+ log_append_info(",miss_blks=");
+ log_hexdump_info(evt->blocks_not_received.data, evt->blocks_not_received.len);
+ }
+ log_append_info(")" NL);
+#endif // SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL
+}
+
+static void handle_server_block_status(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_server_block_status_t *const evt)
+{
+ (void)evt; // To suppress the warning if the logging is disabled
+
+ // Restart the timer since we know in case this event is handled, that the
+ // message was sent out properly. However, we don't stop the retry timer,
+ // since if any node doesn't respond, we still need to retry.
+ restart_retry_timer(self);
+
+#if (SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL != 0)
+ log_info(LOG_PREFIX "Block Status received (elem=%d,addr=0x%04X,status=%s",
+ evt->elem_index,
+ evt->server_address,
+ mbt_status_to_string(evt->status));
+ if (0 < evt->missing_chunks.len) {
+ log_append_info(",miss_chunks=");
+ log_hexdump_info(evt->missing_chunks.data, evt->missing_chunks.len);
+ }
+ log_append_info(")" NL);
+#endif // SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL
+}
+
+static void handle_server_partial_block_report(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_server_partial_block_report_t *const evt)
+{
+ (void)self;
+ (void)evt; // To suppress the warning if the logging is disabled
+
+#if (SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL != 0)
+ log_info(LOG_PREFIX "Partial Block Report received (elem=%d,addr=0x%04X",
+ evt->elem_index,
+ evt->server_address);
+ if (0 < evt->missing_chunks.len) {
+ log_append_info(",miss_chunks=");
+ log_hexdump_info(evt->missing_chunks.data, evt->missing_chunks.len);
+ }
+ log_append_info(")" NL);
+#endif // SL_BTMESH_BLOB_TRANSFER_CLIENT_LOG_BLOB_STATUS_MSG_CFG_VAL
+}
+
+static void handle_query_information_complete(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_query_information_complete_t *const evt)
+{
+ if (STATE_SEND_QUERY_INFO == self->state) {
+ self->max_chunks_min = evt->max_chunks_min;
+
+ if (SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNK_SIZE_CFG_VAL < evt->max_chunk_size_min) {
+ self->max_chunk_size_min = SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_CHUNK_SIZE_CFG_VAL;
+ } else {
+ self->max_chunk_size_min = evt->max_chunk_size_min;
+ }
+
+ self->block_size_log =
+ sl_btmesh_blob_transfer_client_calculate_block_size_log(self->blob_size,
+ evt->block_size_log_min,
+ evt->block_size_log_max,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_BLOCKS_CFG_VAL,
+ self->max_chunk_size_min,
+ evt->max_chunks_min);
+ if (INVALID_BLOCK_SIZE_LOG == self->block_size_log) {
+ self->block_size_log = evt->block_size_log_max;
+ log_error(LOG_PREFIX
+ "Block Size Calculation failed defaults to Block Size Log Max"
+ "(elem=%d,blob_size=0x%08lX,blk_log=%d-%d,blk_cnt_max=%d,"
+ "max_chunk_size=0x%04X,max_chunks=%d)" NL,
+ evt->elem_index,
+ self->blob_size,
+ evt->block_size_log_min,
+ evt->block_size_log_max,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_BLOCKS_CFG_VAL,
+ evt->max_chunk_size_min,
+ evt->max_chunks_min);
+ }
+
+ sl_btmesh_mbt_client_transfer_mode_t supported_transfer_modes =
+ (sl_btmesh_mbt_client_transfer_mode_t) evt->supported_transfer_modes;
+ sl_btmesh_mbt_client_transfer_mode_t common_transfer_modes =
+ (sl_btmesh_mbt_client_transfer_mode_t) evt->common_transfer_modes;
+
+ // The supported_transfer_modes is used for logging only
+ (void) supported_transfer_modes;
+
+ log_info(LOG_PREFIX
+ "Query Info Complete "
+ "(elem=%d,blk_log=%d-%d,max_chunk_size=0x%04X,max_chunks=%d,"
+ "sup_tf_mode=%s,com_tf_mode=%s)" NL,
+ evt->elem_index,
+ evt->block_size_log_min,
+ evt->block_size_log_max,
+ evt->max_chunk_size_min,
+ evt->max_chunks_min,
+ sl_btmesh_blob_transfer_client_transfer_mode_to_string(supported_transfer_modes),
+ sl_btmesh_blob_transfer_client_transfer_mode_to_string(common_transfer_modes));
+
+ if (sl_btmesh_mbt_client_mbt_transfer_mode_both == self->transfer_mode) {
+ // The transfer mode shall be set by the query info completely
+ switch (common_transfer_modes) {
+ case sl_btmesh_mbt_client_mbt_transfer_mode_push:
+ case sl_btmesh_mbt_client_mbt_transfer_mode_pull:
+ self->transfer_mode = common_transfer_modes;
+ break;
+ case sl_btmesh_mbt_client_mbt_transfer_mode_both:
+ // Push mode shall be the default if both transfer modes are supported
+ case sl_btmesh_mbt_client_mbt_transfer_mode_none:
+ // Subset of updating nodes can participate in the BLOB transfer because
+ // neither transfer mode is supported by all nodes (defaults to push)
+ // Note: it is not necessary to check the supported_transfer_modes in
+ // the event because it must have "both" value otherwise the
+ // common_transfer_modes would not be "none"
+ default:
+ self->transfer_mode = sl_btmesh_mbt_client_mbt_transfer_mode_push;
+ break;
+ }
+ }
+ state_transition(self, STATE_SEND_TRANSFER_START);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX
+ "unexpected Query Info Complete event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->state);
+ }
+}
+
+static void handle_start_transfer_complete(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_start_transfer_complete_t *const evt)
+{
+ (void)evt; // To suppress the warning if the logging is disabled
+ uint16_t nw_pdu_size = STANDARD_NETWORK_PDU_SIZE;
+
+ if (STATE_SEND_TRANSFER_START == self->state) {
+ log_info(LOG_PREFIX "Start Transfer Complete (elem=%d)" NL,
+ evt->elem_index);
+
+#ifdef SL_CATALOG_BTMESH_AE_SERVER_PRESENT
+ sl_status_t sc_nw, sc_mdl;
+ uint16_t configured_nw_pdu_size = STANDARD_NETWORK_PDU_SIZE;
+ uint16_t blob_transfer_client_ae_enabled = false;
+ sc_nw = sl_btmesh_silabs_config_server_get_network_pdu(&configured_nw_pdu_size);
+ sc_mdl = sl_btmesh_silabs_config_server_get_model_enable(self->elem_index,
+ SIG_VENDOR_ID,
+ MESH_MBT_SERVER_MODEL_ID,
+ &blob_transfer_client_ae_enabled);
+ if (sc_nw == SL_STATUS_OK && sc_mdl == SL_STATUS_OK) {
+ if (blob_transfer_client_ae_enabled) {
+ nw_pdu_size = configured_nw_pdu_size;
+ } else {
+ nw_pdu_size = STANDARD_NETWORK_PDU_SIZE;
+ }
+ } else {
+ log_status_error_f(sc_nw,
+ LOG_PREFIX
+ "failed to query network PDU size (elem=%d)" NL,
+ self->elem_index);
+ log_status_error_f(sc_nw,
+ LOG_PREFIX
+ "failed to query BLOB Transfer Client model AE status "
+ "(elem=%d)" NL,
+ self->elem_index);
+ }
+#endif // SL_CATALOG_BTMESH_AE_SERVER_PRESENT
+
+ self->current_block_chunk_size =
+ sl_btmesh_blob_transfer_client_calculate_chunk_size(self->block_size_log,
+ self->max_chunk_size_min,
+ self->max_chunks_min,
+ nw_pdu_size);
+
+ // This is last event handler before the transfer of the blocks is started
+ // therefore the progress notification with zero confirmed tx bytes shall
+ // be emitted here
+ notify_progress(self, 0);
+
+ state_transition(self, STATE_SEND_BLOCK_START);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX
+ "unexpected Start Transfer Complete event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->state);
+ }
+}
+
+static void handle_start_block_complete(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_start_block_complete_t *const evt)
+{
+ (void)evt; // To suppress the warning if the logging is disabled
+
+ if (STATE_SEND_BLOCK_START == self->state) {
+ self->current_block_number = evt->block_number;
+ log_info(LOG_PREFIX
+ "Start Block Complete (elem=%d,blk_idx=%d,blk_num=%d,blk_size=%lu)" NL,
+ evt->elem_index,
+ evt->block_number,
+ calc_total_block_count(self),
+ evt->block_size);
+
+ state_transition(self, STATE_SEND_CHUNKS);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Start Block Complete event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->state);
+ }
+}
+
+static void handle_send_chunk_request(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_send_chunk_request_t *const evt)
+{
+ if (STATE_SEND_CHUNKS == self->state) {
+ self->chunk_requested = true;
+ self->current_chunk_offset = evt->offset;
+ self->current_chunk_length = evt->length;
+
+ if (false != self->separation_time_elapsed) {
+ // The chunk data is transmitted at the first chunk of the block, because
+ // separation time is measured between chunks of the same block therefore
+ // the first chunk shall be sent in this event handler.
+ //
+ // If the separation time is zero then the handle_tx_complete sets the
+ // separation_time_elapsed flag therefore this event handler sends the
+ // chunk immediately.
+ send_chunk(self);
+ }
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX
+ "unexpected Send Chunk Request event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->state);
+ }
+}
+
+static void handle_send_chunks_complete(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_send_chunks_complete_t *const evt)
+{
+ (void)evt;
+ if (STATE_SEND_CHUNKS == self->state) {
+ state_transition(self, STATE_SEND_BLOCK_QUERY);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX
+ "unexpected Send Chunks Complete event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->state);
+ }
+}
+
+static void handle_query_block_status_complete(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_query_block_status_complete_t *const evt)
+{
+ if (STATE_SEND_BLOCK_QUERY == self->state) {
+ uint32_t confirmed_tx_bytes;
+ uint16_t completed_block_count = self->current_block_number
+ + (0 != evt->block_complete);
+
+ if (completed_block_count < calc_total_block_count(self)) {
+ confirmed_tx_bytes = completed_block_count
+ * (1UL << self->block_size_log);
+ } else {
+ confirmed_tx_bytes = self->blob_size;
+ }
+
+ log_info(LOG_PREFIX
+ "Query Block Status Complete "
+ "(elem=%d,blk_done=%d,transfer_done=%d,progress=%u%%)" NL,
+ evt->elem_index,
+ evt->block_complete,
+ evt->transfer_complete,
+ (unsigned) (100 * confirmed_tx_bytes / self->blob_size));
+
+ if (0 != evt->transfer_complete) {
+ notify_progress(self, self->blob_size);
+ } else if (0 != evt->block_complete) {
+ notify_progress(self, confirmed_tx_bytes);
+ state_transition(self, STATE_SEND_BLOCK_START);
+ } else {
+ state_transition(self, STATE_SEND_CHUNKS);
+ }
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX
+ "unexpected Query Block Status Complete event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->state);
+ }
+}
+
+static void handle_tx_complete(blob_transfer_client_t *const self)
+{
+ // If the state supports retry, then it shall start the retry timer if the max
+ // number of retries is not reached.
+ // If the maximum number of retries is reached, then the state machine waits
+ // in the current state until the BT Mesh stack drops the nonresponding BLOB
+ // servers and continues with the active servers. (BT Mesh stack event)
+ if (0 != state_flags[self->state].retry) {
+ if (self->retry_counter < self->retry_max) {
+ start_retry_timer(self);
+ }
+ }
+
+ // If multiple consecutive mesh messages need to be sent (send chunks), then
+ // the separation time feature makes it possible to introduce a separation
+ // time between these messages in order to spare the bandwidth for another
+ // nodes
+ if (0 != state_flags[self->state].separation) {
+ // The separation time is started after the last message as well, because
+ // this component is not able to determine which message is the last.
+ // For example in case of push mode BLOB transfer the chunks are not sent
+ // in order especially when the missing chunks are sent.
+ // The timer is started after the last chunk, however it will be stopped by
+ // the state exit code when the state transition occurs due to the send
+ // chunks complete event. Even if the timer elapses only a flag is set and
+ // the send chunk function won't be called because the chunk_requested flag
+ // is not set.
+ start_separation_timer(self);
+ }
+}
+
+static void handle_server_failed(blob_transfer_client_t *const self,
+ const sl_btmesh_evt_mbt_client_server_failed_t *const evt)
+{
+ if (0 == state_flags[self->state].idle) {
+ log_info(LOG_PREFIX
+ "Server Failed received (elem=%d,addr=0x%04X,timeout=%d,err=%s)" NL,
+ evt->elem_index,
+ evt->server_address,
+ evt->timeout,
+ mbt_status_to_string(evt->error));
+
+ // Send notification to the upper layer about the BLOB Transfer Server failure
+ notify_server_failed(self, evt->server_address, evt->timeout, evt->error);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX
+ "unexpected Server Failed event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->state);
+ }
+}
+
+static void handle_transfer_complete(blob_transfer_client_t *const self)
+{
+ const uint16_t max_server_count = SL_BTMESH_BLOB_TRANSFER_CLIENT_MAX_SERVERS_CFG_VAL;
+ uint16_t server_count = 0, failed_server_count = 0;
+ uint16_t server_address = 0, rx_blocks = 0, rx_chunks = 0;
+ uint8_t current_procedure_status;
+ bool all_server_failed = false;
+ sl_status_t sc;
+
+ if (0 == state_flags[self->state].idle) {
+ // Iterate over the BLOB transfer servers to check their status
+ // The number of servers in the BLOB transfer is not known but the maximum
+ // server count is known because that is an initialization parameter of the
+ // BT Mesh stack MBT client model. The server count can be determined by
+ // checking the return value of sl_btmesh_mbt_client_get_server_status.
+ for (uint16_t server_idx = 0; server_idx < max_server_count; server_idx++) {
+ sc = sl_btmesh_mbt_client_get_server_status(self->elem_index,
+ server_idx,
+ &server_address,
+ ¤t_procedure_status,
+ &rx_blocks,
+ &rx_chunks);
+ if (sc == SL_STATUS_OK) {
+ sl_btmesh_mbt_client_server_status_t server_status;
+ server_status = (sl_btmesh_mbt_client_server_status_t) current_procedure_status;
+ server_count++;
+ if (server_status == sl_btmesh_mbt_client_server_status_error) {
+ failed_server_count++;
+ }
+ } else if ((sc == SL_STATUS_BT_MESH_DOES_NOT_EXIST) && (server_idx != 0)) {
+ // The BT Mesh stack returns SL_STATUS_BT_MESH_DOES_NOT_EXIST when the
+ // server index does not exists so it is possible to determine the number
+ // of servers by checking this return value.
+ // The server with zero index shall exist because a BLOB transfer shall
+ // have at least one BLOB transfer server, and consequently the first
+ // sl_btmesh_mbt_client_get_server_status BT Mesh stack call can return
+ // the SL_STATUS_BT_MESH_DOES_NOT_EXIST return value because of an error.
+ break;
+ } else {
+ // Unexpected error so the BLOB transfer is considered to be failed
+ all_server_failed = true;
+ log_status_error_f(sc,
+ LOG_PREFIX "failed to get server status "
+ "(elem=%d,server_idx=%u)" NL,
+ self->elem_index,
+ server_idx);
+ break;
+ }
+ }
+
+ all_server_failed = (server_count == failed_server_count);
+
+ if (all_server_failed) {
+ process_transfer_failed(self);
+ } else if (self->state == STATE_CANCELING) {
+ notify_transfer_result(self,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_RESULT_CANCELED);
+ state_transition(self, STATE_TRANSFER_CANCELED);
+ } else {
+ notify_transfer_result(self,
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_RESULT_COMPLETED);
+ state_transition(self, STATE_TRANSFER_COMPLETED);
+ }
+ }
+}
+
+static void retry_timer_cb(sl_simple_timer_t *timer, void *data)
+{
+ (void) timer;
+ blob_transfer_client_t *const self = data;
+
+ app_assert(NULL != data, "The data is NULL in retry timer callback");
+
+ // Register that the retry timer elapsed which is important in states which
+ // support retries because it clears the retry counter only if the state is
+ // entered the first time and not as the result of a retry (self transition).
+ self->retry_time_elapsed = true;
+
+ // If the retry time elapsed then a self transition is executed in order to
+ // run the state actions again. The current state is left and entered again
+ // which means the entry and exit functions are called again therefore this
+ // implements the retry.
+ state_transition(self, self->state);
+}
+
+static void separation_timer_cb(sl_simple_timer_t *timer, void *data)
+{
+ (void) timer;
+ blob_transfer_client_t *const self = data;
+
+ app_assert(NULL != data, "The data is NULL in retry timer callback");
+
+ self->separation_time_elapsed = true;
+
+ if (false != self->chunk_requested) {
+ send_chunk(self);
+ }
+}
+
+static void send_chunk_request_response(sl_simple_timer_t *timer, void *data)
+{
+ sl_status_t sc;
+
+ blob_transfer_client_t *const self = data;
+
+ sc = sl_btmesh_mbt_client_send_chunk_request_rsp(self->elem_index,
+ self->current_chunk_length,
+ self->chunk_data_ptr);
+
+ switch (sc) {
+ default:
+ log_status_error_f(sc,
+ LOG_PREFIX "Send Chunk failed (elem=%d,chunk_len=0x%04X)" NL,
+ self->elem_index,
+ self->current_chunk_length);
+ process_transfer_failed(self);
+ break;
+ case SL_STATUS_BUSY:
+ case SL_STATUS_NO_MORE_RESOURCE:
+ // In these two cases, start the retry timer with this callback function
+ sc = sl_simple_timer_start(timer,
+ self->retry_time_ms,
+ send_chunk_request_response,
+ &data,
+ false);
+ app_assert_status_f(sc,
+ "Failed to start chunk request response retry timer (elem=%d)",
+ self->elem_index);
+ break;
+ case SL_STATUS_OK:
+ // The chunk request is frequent so it is logged only in debug mode
+ log_debug(LOG_PREFIX "Send Chunk (elem=%d,offset=0x%08lX,chunk_len=0x%04X)" NL,
+ self->elem_index,
+ self->current_chunk_offset,
+ self->current_chunk_length);
+ break;
+ }
+}
+
+void sl_btmesh_blob_transfer_client_on_event(const sl_btmesh_msg_t * const evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id: {
+ sl_btmesh_blob_transfer_client_init();
+ break;
+ }
+ case sl_btmesh_evt_node_initialized_id: {
+ if (0 != evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_blob_transfer_client_init();
+ }
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_server_information_status_id: {
+ handle_server_information_status(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_server_information_status);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_server_transfer_status_id: {
+ handle_server_transfer_status(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_server_transfer_status);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_server_block_status_id: {
+ handle_server_block_status(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_server_block_status);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_server_partial_block_report_id: {
+ handle_server_partial_block_report(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_server_partial_block_report);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_query_information_complete_id: {
+ handle_query_information_complete(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_query_information_complete);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_start_transfer_complete_id: {
+ handle_start_transfer_complete(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_start_transfer_complete);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_start_block_complete_id: {
+ handle_start_block_complete(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_start_block_complete);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_send_chunk_request_id: {
+ handle_send_chunk_request(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_send_chunk_request);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_send_chunks_complete_id: {
+ handle_send_chunks_complete(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_send_chunks_complete);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_query_block_status_complete_id: {
+ handle_query_block_status_complete(
+ &blob_tf_client,
+ &evt->data.evt_mbt_client_query_block_status_complete);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_tx_complete_id: {
+ handle_tx_complete(&blob_tf_client);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_server_failed_id: {
+ handle_server_failed(&blob_tf_client,
+ &evt->data.evt_mbt_client_server_failed);
+ break;
+ }
+ case sl_btmesh_evt_mbt_client_transfer_complete_id: {
+ handle_transfer_complete(&blob_tf_client);
+ break;
+ }
+ }
+}
+
+static void state_transition(blob_transfer_client_t *const self,
+ blob_transfer_client_state_t target_state)
+{
+ blob_transfer_client_state_flags_t source_state_flags, target_state_flags;
+
+ app_assert_s(target_state < STATE_COUNT);
+
+ source_state_flags = state_flags[self->state];
+ target_state_flags = state_flags[target_state];
+
+ // No state can support the separation and retry at the same time because
+ // only one simple timer data structure is allocated, therefore only one timer
+ // can run at any point of time in the BLOB Transfer Client
+ app_assert_s((0 == source_state_flags.retry)
+ || (0 == source_state_flags.separation));
+
+ // If a state with the retry feature is exited, then the retry timer shall
+ // be stopped, even in case of a retry self-transition, because in this case
+ // the retry timer is started again by the tx complete event handler
+ // Note: it is not considered as an error if a non-running timer is stopped
+ // in the simple timer component
+ stop_retry_timer(self);
+
+ // If a state with separation feature is exited, then the separation timer
+ // shall be stopped
+ // Note: it is not considered an error, if a non-running timer is stopped
+ // in the simple timer component
+ stop_separation_timer(self);
+
+ self->state = target_state;
+
+ if ((0 == source_state_flags.idle)
+ && (0 != target_state_flags.idle)) {
+ // If any of the idle sub-states are entered from a non-idle state, then
+ // the idle entry function is called.
+ super_state_idle_entry(self);
+ }
+
+ if (0 != target_state_flags.retry) {
+ // If a state with retry feature is entered the first time during the
+ // transfer and not as a result of a retry self-transition then the retry
+ // counter shall be cleared.
+ if (false == self->retry_time_elapsed) {
+ self->retry_counter = 0;
+ } else {
+ self->retry_counter++;
+ self->retry_time_elapsed = false;
+ }
+ }
+
+ if (0 != target_state_flags.separation) {
+ // It is not necessary to respect the separation time when the first chunk
+ // is sent, because the separation time is measured between chunks
+ self->separation_time_elapsed = true;
+ }
+
+ switch (target_state) {
+ case STATE_SEND_QUERY_INFO: {
+ state_send_query_info_entry(self);
+ break;
+ }
+ case STATE_SEND_TRANSFER_START: {
+ state_send_transfer_start_entry(self);
+ break;
+ }
+ case STATE_SEND_BLOCK_START: {
+ state_send_start_block_entry(self);
+ break;
+ }
+ case STATE_SEND_BLOCK_QUERY: {
+ state_send_block_query_entry(self);
+ break;
+ }
+ case STATE_CANCELING: {
+ state_send_cancel(self);
+ break;
+ }
+ default:
+ // Suppress the switch warnings
+ break;
+ }
+}
+
+const char *sl_btmesh_blob_transfer_client_transfer_mode_to_string(sl_btmesh_mbt_client_transfer_mode_t transfer_mode)
+{
+ switch (transfer_mode) {
+ case sl_btmesh_mbt_client_mbt_transfer_mode_none:
+ return "None";
+ case sl_btmesh_mbt_client_mbt_transfer_mode_push:
+ return "Push";
+ case sl_btmesh_mbt_client_mbt_transfer_mode_pull:
+ return "Pull";
+ case sl_btmesh_mbt_client_mbt_transfer_mode_both:
+ return "Both";
+ default:
+ return "INVALID";
+ }
+}
+
+const char *sl_btmesh_blob_transfer_client_mbt_phase_to_string(sl_btmesh_mbt_server_phase_t mbt_phase)
+{
+ // The string representation is set based on the latest specification
+ switch (mbt_phase) {
+ case sl_btmesh_mbt_server_phase_inactive:
+ return "Inactive";
+ case sl_btmesh_mbt_server_phase_idle:
+ return "Wait Start";
+ case sl_btmesh_mbt_server_phase_waiting_for_block:
+ return "Wait Block";
+ case sl_btmesh_mbt_server_phase_waiting_for_chunks:
+ return "Wait Chunk";
+ case sl_btmesh_mbt_server_phase_complete:
+ return "Complete";
+ case sl_btmesh_mbt_server_phase_suspended:
+ return "Suspended";
+ default:
+ return "INVALID";
+ }
+}
+
+const char *sl_btmesh_blob_transfer_client_mbt_status_to_string(sl_btmesh_mbt_server_status_t mbt_status)
+{
+ switch (mbt_status) {
+ case sl_btmesh_mbt_server_status_success:
+ return "Success";
+ case sl_btmesh_mbt_server_status_invalid_block_number:
+ return "Invalid Block Number";
+ case sl_btmesh_mbt_server_status_wrong_block_size:
+ return "Invalid Block Size";
+ case sl_btmesh_mbt_server_status_wrong_chunk_size:
+ return "Invalid Chunk Size";
+ case sl_btmesh_mbt_server_status_invalid_state:
+ return "Wrong Phase";
+ case sl_btmesh_mbt_server_status_invalid_parameter:
+ return "Invalid Parameter";
+ case sl_btmesh_mbt_server_status_wrong_object_id:
+ return "Wrong BLOB ID";
+ case sl_btmesh_mbt_server_status_storage_limit:
+ return "BLOB Too Large";
+ case sl_btmesh_mbt_server_status_unsupported_transfer_mode:
+ return "Unsupported Transfer Mode";
+ case sl_btmesh_mbt_server_status_internal_error:
+ return "Internal Error";
+ case sl_btmesh_mbt_server_status_information_unavailable:
+ return "Information Unavailable";
+ case sl_btmesh_mbt_server_status_malformed_message:
+ return "Malformed Message";
+ case sl_btmesh_mbt_server_status_timeout:
+ return "Timeout";
+ default:
+ return "INVALID";
+ }
+}
+
+/** @} end blob_transfer_client */
diff --git a/app/btmesh/common/btmesh_blob_transfer_client/sl_btmesh_blob_transfer_client.h b/app/btmesh/common/btmesh_blob_transfer_client/sl_btmesh_blob_transfer_client.h
new file mode 100644
index 00000000000..68edbe8bf02
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_client/sl_btmesh_blob_transfer_client.h
@@ -0,0 +1,503 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh BLOB Transfer Client
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_BLOB_TRANSFER_CLIENT_H
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_H
+
+#include "sl_enum.h"
+#include "sl_status.h"
+#include "em_common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup blob_transfer_client BT Mesh BLOB Transfer Client
+ * @{
+ ******************************************************************************/
+
+/// Invalid value for the binary logarithm of the block size
+#define SL_BTMESH_BLOB_TRANSFER_CLIENT_INVALID_BLOCK_SIZE_LOG 0xFF
+
+/// BLOB Transfer Client Kind
+SL_ENUM(sl_btmesh_blob_transfer_client_notification_kind_t) {
+ /// BLOB Transfer Result Notification
+ ///
+ /// This notification is triggered at the end of BLOB Transfer
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_KIND_TRANSFER_RESULT,
+
+ /// BLOB Transfer Progress Notification
+ ///
+ /// The progress is reported at the end of each block because at the end of
+ /// the block it is sure that all active BLOB Transfer Servers have received
+ /// the whole block (every chunk).
+ ///
+ /// @note If some BLOB Transfer Servers didn't receive certain chunks then
+ /// those chunk would be retransmitted in case of Push and Pull transfers
+ /// as well, therefore the transmitted chunks are not a good indicator of
+ /// the overall transfer progress.
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_KIND_PROGRESS,
+
+ /// BLOB Transfer Server Failed Notification
+ ///
+ /// @note If one BLOB Transfer Server fails but at least one other BLOB
+ /// Transfer Server remains active then the BLOB Transfer is continued
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_KIND_SERVER_FAILED
+};
+
+/// BLOB Transfer Client Result
+SL_ENUM(sl_btmesh_blob_transfer_client_result_t) {
+ /// At least one BLOB Transfer Server received the BLOB successfully.
+ /// The other servers might have failed due to error or timeout.
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_RESULT_COMPLETED,
+ /// All BLOB Transfer Servers have failed due to timeout or various kinds of
+ /// errors (e.g. internal error, out of resources see Mesh Model specification
+ /// 1.1)
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_RESULT_FAILED,
+ /// Last BLOB transfer was canceled by upper layer
+ SL_BTMESH_BLOB_TRANSFER_CLIENT_RESULT_CANCELED
+};
+
+/// BLOB Transfer progress
+///
+/// This structure represent the progress of the active BLOB Transfer Servers
+///
+/// @note Some servers could fail with error or timeout, but if there is at least
+/// one active BLOB Transfer Server then the transfer continues
+typedef struct {
+ /// Number of BLOB bytes which have been received by every active BLOB Transfer
+ /// Server
+ ///
+ /// @note The progress is calculated at the end of the blocks because in the
+ /// block, chunks could be retransmitted multiple times
+ uint32_t confirmed_tx_bytes;
+ /// BLOB size in bytes
+ uint32_t blob_size;
+} sl_btmesh_blob_transfer_client_progress_t;
+
+/// BLOB Transfer Server Failed Notification
+typedef struct {
+ /// Address of the BLOB Transfer Server
+ uint16_t server_address;
+ /// If a BLOB Transfer Server doesn't respond until a specified time then
+ /// the timeout is set to 1
+ uint8_t timeout;
+ /// The error is set only if the timeout is zero and a BLOB Transfer Server has
+ /// responded with non-zero status code (error) in BLOB Transfer Status or BLOB
+ /// Block Status message
+ uint8_t error;
+} sl_btmesh_blob_transfer_client_server_failed_t;
+
+/// Notification data for upper layers
+typedef struct {
+ /// Identifies the notification type
+ sl_btmesh_blob_transfer_client_notification_kind_t kind;
+ /// Index of the element which triggered the notification
+ uint16_t elem_index;
+ /// Notification specific parameters
+ union {
+ /// Status of the active BLOB transfer on the element
+ sl_btmesh_blob_transfer_client_result_t transfer_result;
+ /// Progress of the active BLOB Transfer on the element
+ sl_btmesh_blob_transfer_client_progress_t progress;
+ /// Reason of BLOB Transfer Server failure during the active BLOB Transfer
+ sl_btmesh_blob_transfer_client_server_failed_t server_failed;
+ } params;
+} sl_btmesh_blob_transfer_client_notification_t;
+
+typedef void (*sl_btmesh_blob_transfer_client_notify_cb_t)(
+ const sl_btmesh_blob_transfer_client_notification_t *const notification);
+
+/***************************************************************************//**
+ *
+ * Set up a new BLOB transfer.
+ *
+ * @note This shall not be called when the Distribution Server initiates the
+ * BLOB transfer because BT Mesh stack sets up the BLOB transfer inside the BT
+ * Mesh stack automatically.
+ *
+ * @param[in] elem_index The client model element index.
+ * @param[in] blob_id The ID of the BLOB.
+ * @param[in] blob_size The size of the BLOB.
+ * @param[in] appkey_index Index of the application key to be used for
+ * communicating with the servers
+ * @param[in] ttl The TTL to use when communicating with the servers.
+ * @param[in] timeout_base If a Server does not respond within this time frame,
+ * it will be marked as inactive.
+ * @param[in] group_address The group address to used if the
+ * @ref sl_btmesh_blob_transfer_client_setup::multicast_threshold is exceeded.
+ * Zero to only use unicast.
+ * @param[in] virtual_address Virtual address in case virtual addresses are used
+ * @param[in] multicast_threshold If the number of servers for any step exceeds
+ * this number, the group address will be used. Otherwise, servers will be
+ * looped through one by one.
+ * @param[in] servers_len Length of data in servers
+ * @param[in] servers List of MBT server addresses, represented as little endian
+ * two byte sequences.
+ *
+ * @return Result of the BLOB Transfer setup
+ * @retval SL_STATUS_OK if successful. Error code otherwise.
+ * @retval SL_STATUS_INVALID_STATE if not Idle
+ *
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_transfer_client_setup(uint16_t elem_index,
+ sl_bt_uuid_64_t blob_id,
+ uint32_t blob_size,
+ uint16_t appkey_index,
+ uint8_t ttl,
+ uint32_t timeout_base,
+ uint16_t group_address,
+ uuid_128 virtual_address,
+ uint16_t multicast_threshold,
+ size_t servers_len,
+ const uint8_t* servers);
+
+/***************************************************************************//**
+ *
+ * Set up an array data provider for a new BLOB transfer.
+ *
+ * The BLOB transfer client reads the BLOB data from this array during chunk
+ * transfer.
+ *
+ * @param[in] elem_index Client model element index
+ * @param[in] array Pointer to an array which stores the BLOB data
+ * @param[in] length Length of the array
+ *
+ * @return Result of the Data Provider setup
+ * @retval SL_STATUS_OK if successful
+ * @retval SL_STATUS_INVALID_STATE Data Provider setup called in non-idle state
+ * @retval SL_STATUS_INVALID_PARAMETER BLOB size inconsistency between the
+ * @ref sl_btmesh_blob_transfer_client_setup and this Data Provider setup call
+ *
+ ******************************************************************************/
+sl_status_t
+sl_btmesh_blob_transfer_client_setup_data_provider_array(uint16_t elem_index,
+ const uint8_t *array,
+ uint32_t length);
+
+/***************************************************************************//**
+ *
+ * Set up a BLOB storage data provider for a new BLOB transfer.
+ *
+ * The BLOB Transfer Client reads the BLOB data from the BLOB storage based on
+ * the passed BLOB id.
+ *
+ * @note The BLOB id used to identify the BLOB data in the BLOB storage (on the
+ * client side) is likely not the same as the BLOB id used during the BLOB
+ * transfer to the servers.
+ *
+ * @param[in] elem_index Client model element index
+ * @param[in] blob_id Pointer to the BLOB id
+ *
+ * @return Result of the Data Provider setup
+ * @retval SL_STATUS_OK if successful
+ * @retval SL_STATUS_INVALID_STATE Data Provider setup called in non-idle state
+ * @retval SL_STATUS_NOT_FOUND BLOB is not found in the BLOB storage
+ * @retval SL_STATUS_INVALID_PARAMETER BLOB size inconsistency between the
+ * @ref sl_btmesh_blob_transfer_client_setup and this Data Provider setup call
+ *
+ ******************************************************************************/
+sl_status_t
+sl_btmesh_blob_transfer_client_setup_data_provider_blob_storage(uint16_t elem_index,
+ const sl_bt_uuid_64_t *blob_id);
+
+/***************************************************************************//**
+ *
+ * Set up additional parameters for a new BLOB transfer
+ *
+ * @note This function is separated from @ref sl_btmesh_blob_transfer_client_setup
+ * because the @ref sl_btmesh_blob_transfer_client_setup shall not be called
+ * when @ref sl_btmesh_fw_distribution_server initiates the BLOB transfer
+ * because BT Mesh stack sets up the BLOB transfer inside the BT Mesh stack
+ * automatically.
+ *
+ * @param[in] elem_index Client model element index
+ * @param[in] separation_time_ms Minimum separation time between two chunks
+ * in the same block, in milliseconds
+ * @param[in] retry_time_ms Retry time of message transmissions in milliseconds
+ * (query info, transfer start, block start, block query)
+ * @param[in] retry_max Maximum number of retries of message transmissions
+ * (query info, transfer start, block start, block query)
+ *
+ * @return Result of the parameter setup
+ * @retval SL_STATUS_OK if successful
+ * @retval SL_STATUS_INVALID_STATE Parameter setup called in non-idle state
+ *
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_transfer_client_set_params(uint16_t elem_index,
+ uint16_t separation_time_ms,
+ uint16_t retry_time_ms,
+ uint16_t retry_max);
+
+/***************************************************************************//**
+ *
+ * Starts a previously set up BLOB Transfer.
+ *
+ * @param[in] elem_index Client model element index
+ * @param[in] transfer_mode Allowed transfer mode
+ * @param[in] notify Function pointer to a notification callback function which
+ * is called in the following cases:
+ * \li BLOB Transfer completed
+ * \li BLOB Transfer failed
+ * \li BLOB Transfer canceled
+ * \li BLOB Transfer progress changes
+ * \li BLOB Transfer Server failed
+ *
+ * It is valid to pass NULL as notify parameter. If the callback function is
+ * null then no notification is initiated.
+ * If @p transfer_mode is set to @p sl_btmesh_mbt_client_mbt_transfer_mode_both
+ * then the BLOB transfer client selects the transfer mode based on the result
+ * of query information. If one transfer mode is supported by all receiving nodes
+ * only then that transfer mode is selected otherwise push mode is selected.
+ * If @p transfer_mode is set to @p sl_btmesh_mbt_client_mbt_transfer_mode_push
+ * or @p sl_btmesh_mbt_client_mbt_transfer_mode_pull then that is used as
+ * transfer mode.
+ *
+ * @return SL_STATUS_OK if successful. Error code otherwise
+ * @retval SL_STATUS_INVALID_PARAMETER Invalid transfer mode
+ * @retval SL_STATUS_INVALID_STATE BLOB Transfer Client busy with another transfer
+ * @retval SL_STATUS_INVALID_CONFIGURATION No data provider was specified
+ *
+ ******************************************************************************/
+sl_status_t
+sl_btmesh_blob_transfer_client_start(uint16_t elem_index,
+ sl_btmesh_mbt_client_transfer_mode_t transfer_mode,
+ sl_btmesh_blob_transfer_client_notify_cb_t notify);
+
+/***************************************************************************//**
+ *
+ * Cancels an ongoing BLOB Transfer.
+ *
+ * @param[in] elem_index Client model element index
+ *
+ ******************************************************************************/
+void sl_btmesh_blob_transfer_client_cancel(uint16_t elem_index);
+
+/***************************************************************************//**
+ *
+ * Aborts an ongoing BLOB Transfer.
+ *
+ * @param[in] elem_index Client model element index
+ *
+ * @note This function is not to be used when the MBT client is interfaced
+ * by a higher layer component (e.g. distribution server).
+ *
+ * @return SL_STATUS_OK if successful. Error code otherwise.
+ *
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_transfer_client_abort(uint16_t elem_index);
+
+/***************************************************************************//**
+ *
+ * Informs BLOB Transfer Client SDK component that the transfer in MBT Client
+ * model of BT Mesh stack was aborted.
+ *
+ * @param[in] elem_index Client model element index
+ *
+ * @note This function shall be used only when the MBT client is interfaced
+ * by a higher layer component (e.g. distribution server).
+ * The MBT Client model can be aborted by higher layer models in the BT
+ * Mesh stack which is not reported in MBT Client model events so it shall
+ * be reported by calling this API.
+ *
+ * @return SL_STATUS_OK if successful. Error code otherwise.
+ *
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_transfer_client_on_aborted(uint16_t elem_index);
+
+/***************************************************************************//**
+ *
+ * Informs BLOB Transfer Client SDK component that the transfer in MBT Client
+ * model of BT Mesh stack was suspended.
+ *
+ * @param[in] elem_index Client model element index
+ *
+ * @note This function shall be used only when the MBT client is interfaced
+ * by a higher layer component (e.g. distribution server).
+ * The MBT Client model can be suspended by higher layer models in the BT
+ * Mesh stack which is not reported in MBT Client model events so it shall
+ * be reported by calling this API.
+ *
+ * @return SL_STATUS_OK if successful. Error code otherwise.
+ *
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_transfer_client_on_suspended(uint16_t elem_index);
+
+/***************************************************************************//**
+ *
+ * Informs BLOB Transfer Client SDK component that the transfer in MBT Client
+ * model of BT Mesh stack was resumed.
+ *
+ * @param[in] elem_index Client model element index
+ *
+ * @note This function shall be used only when the MBT client is interfaced
+ * by a higher layer component (e.g. distribution server).
+ * The MBT Client model can be resumed by higher layer models in the BT
+ * Mesh stack which is not reported in MBT Client model events so it shall
+ * be reported by calling this API.
+ *
+ * @return SL_STATUS_OK if successful. Error code otherwise.
+ *
+ ******************************************************************************/
+sl_status_t sl_btmesh_blob_transfer_client_on_resumed(uint16_t elem_index);
+
+/***************************************************************************//**
+ * Handle BLOB Transfer Client events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ *
+ ******************************************************************************/
+void sl_btmesh_blob_transfer_client_on_event(const sl_btmesh_msg_t * const evt);
+
+// -----------------------------------------------------------------------------
+// Overridable Callback Functions
+// -----------------------------------------------------------------------------
+/***************************************************************************//**
+ * Calculates the binary logarithm of the block size for the current BLOB
+ * transfer from the provided parameters which are the result of the Retrieve
+ * Capabilities procedure of the BLOB Transfer.
+ *
+ * The parameters passed represent the aggregated capabilities of the BLOB
+ * transfer client and every BLOB transfer server which participates in the
+ * current transfer.
+ *
+ * The default implementation calculates the greatest possible block size from
+ * the parameters. If another implementation is required then the strong symbol
+ * definition shall be provided for this function with the implementation in the
+ * application code.
+ *
+ * @param[in] blob_size Size of the BLOB
+ * @param[in] block_size_log_min Min allowed binary logarithm of the block size
+ * @param[in] block_size_log_max Max allowed binary logarithm of the block size
+ * @param[in] block_count_max Max allowed number of blocks
+ * @param[in] max_chunk_size_min Max allowed chunk size in the current transfer
+ * @param[in] max_chunks_min Max allowed number of chunks per block in the
+ * current transfer
+ *
+ * @return Binary logarithm of the selected block size for the current transfer
+ * @retval SL_BTMESH_BLOB_TRANSFER_CLIENT_INVALID_BLOCK_SIZE_LOG Invalid parameters
+ *
+ ******************************************************************************/
+uint8_t
+sl_btmesh_blob_transfer_client_calculate_block_size_log(uint32_t blob_size,
+ uint8_t block_size_log_min,
+ uint8_t block_size_log_max,
+ uint16_t block_count_max,
+ uint16_t max_chunk_size_min,
+ uint16_t max_chunks_min);
+
+/***************************************************************************//**
+ * Calculates the chunk size for the next block in the current BLOB transfer
+ * from the previously selected binary logarithm of the block size and from
+ * the result of the Retrieve Capabilities procedure of the BLOB Transfer.
+ *
+ * If the configurable preferred chunk size is supported by all BLOB Transfer
+ * Servers then the default chunk size calculation algorithm selects it as chunk
+ * size of the block otherwise the chunk size is set to the closest value which
+ * fills all segments of the chunk.
+ * If another implementation is required then the strong symbol definition shall
+ * be provided for this function with the implementation in the application code.
+ *
+ * @param[in] block_size_log The selected binary logarithm of the block size
+ * during previous phase of the BLOB transfer
+ * @param[in] max_chunk_size_min Max allowed chunk size in the current transfer
+ * @param[in] max_chunks_min Max allowed number of chunks per block in the
+ * current transfer
+ * @param[in] nw_pdu_size Size of Network PDU. If the Silicon Labs BT Mesh
+ * over advertisement extension proprietary feature
+ * is turned on then it can be greater than 29 bytes.
+ *
+ * @return Chunk size for next block in the current BLOB transfer
+ *
+ ******************************************************************************/
+uint16_t
+sl_btmesh_blob_transfer_client_calculate_chunk_size(uint8_t block_size_log,
+ uint16_t max_chunk_size_min,
+ uint16_t max_chunks_min,
+ uint16_t nw_pdu_size);
+
+/***************************************************************************//**
+ * Provides string representation of transfer mode
+ *
+ * It is guaranteed that this function returns a valid string even in case of
+ * invalid transfer mode.
+ *
+ * @param[in] transfer_mode transfer mode
+ *
+ * @note The to_string functions are used for log calls only in this component.
+ * If the logging is disabled then the linker can eliminate these functions.
+ *
+ * @return String representation of transfer mode
+ * @retval "INVALID" if the transfer mode is invalid
+ ******************************************************************************/
+const char *sl_btmesh_blob_transfer_client_transfer_mode_to_string(sl_btmesh_mbt_client_transfer_mode_t transfer_mode);
+
+/***************************************************************************//**
+ * Provides string representation of MBT phase
+ *
+ * It is guaranteed that this function returns a valid string even in case of
+ * invalid MBT phase.
+ *
+ * @param[in] mbt_phase MBT phase
+ *
+ * @note The to_string functions are used for log calls only in this component.
+ * If the logging is disabled then the linker can eliminate these functions.
+ *
+ * @return String representation of MBT phase
+ * @retval "INVALID" if the MBT phase is invalid
+ ******************************************************************************/
+const char *sl_btmesh_blob_transfer_client_mbt_phase_to_string(sl_btmesh_mbt_server_phase_t mbt_phase);
+
+/***************************************************************************//**
+ * Provides string representation of MBT status
+ *
+ * It is guaranteed that this function returns a valid string even in case of
+ * invalid MBT status.
+ *
+ * @param[in] mbt_status MBT status
+ *
+ * @note The to_string functions are used for log calls only in this component.
+ * If the logging is disabled then the linker can eliminate these functions.
+ *
+ * @return String representation of MBT status
+ * @retval "INVALID" if the MBT status is invalid
+ ******************************************************************************/
+const char *sl_btmesh_blob_transfer_client_mbt_status_to_string(sl_btmesh_mbt_server_status_t mbt_status);
+
+/** @} end blob_transfer_client */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+ #endif // SL_BTMESH_BLOB_TRANSFER_CLIENT_H
diff --git a/app/btmesh/common/btmesh_blob_transfer_server/btmesh_blob_transfer_server.dcd b/app/btmesh/common/btmesh_blob_transfer_server/btmesh_blob_transfer_server.dcd
new file mode 100644
index 00000000000..aeb9e6d32f9
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_server/btmesh_blob_transfer_server.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1400", "name":"BLOB Transfer Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_blob_transfer_server/btmesh_blob_transfer_server_validation.lua b/app/btmesh/common/btmesh_blob_transfer_server/btmesh_blob_transfer_server_validation.lua
new file mode 100644
index 00000000000..f63d9c18149
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_server/btmesh_blob_transfer_server_validation.lua
@@ -0,0 +1,261 @@
+-- Log function to add prefix to log messages
+local LOG_LVL_INFO = 3
+local LOG_LVL_WARNING = 2
+local LOG_LVL_ERROR = 1
+local LOG_PREFIX = "btmesh_blob_transfer_server_validation: "
+local VALIDATION_PREFIX = "BLOB Transfer Server: "
+local log_level = LOG_LVL_INFO
+
+-- Status code definitions
+local STATUS = {
+ OK = 0,
+ CONFIG_DOES_NOT_EXIST = -1,
+ CONFIG_TYPE_MISMATCH = -2
+}
+
+local function log_info(message)
+ if LOG_LVL_INFO <= log_level then
+ logit("[I] " .. LOG_PREFIX .. message)
+ end
+end
+
+local function log_warning(message)
+ if LOG_LVL_WARNING <= log_level then
+ logit("[W] " .. LOG_PREFIX .. message)
+ end
+end
+
+local function log_error(message)
+ if LOG_LVL_ERROR <= log_level then
+ logit("[E] " .. LOG_PREFIX .. message)
+ end
+end
+
+local function report_error(problem, target, description, quickfix)
+ log_error(problem .. " - " .. description)
+ validation.error(VALIDATION_PREFIX .. problem,
+ target,
+ description,
+ quickfix)
+end
+
+local function report_warning(problem, target, description, quickfix)
+ log_warning(problem .. " - " .. description)
+ validation.warning(VALIDATION_PREFIX .. problem,
+ target,
+ description,
+ quickfix)
+end
+
+-- automatic conversion of input parameters
+local function autonumber(input)
+ local base = 10
+ local orig_input = input
+ if (type(input) == "string") then
+ input = input:gsub("[\(\)\"uUlL]", "")
+ if string.find(input,"[bxhBXH]") ~= nil then
+ if string.find(string.lower(input), "0b") == 1 then
+ input = input:gsub("[bB]","")
+ base = 2
+ elseif string.find(string.lower(input), "0x") == 1 then
+ input = input:gsub("[xXhH]","")
+ base = 16
+ end
+ elseif string.find(input, "0") == 1 then
+ base = 8
+ end
+ elseif (type(input) == "number") then
+ return input
+ else
+ log_error("autonumber() expects either a string or a number!")
+ return nil
+ end
+ local result = tonumber(input, base)
+ if result == nil then
+ log_error("Configured value is not valid: \"" .. tostring(orig_input) .. "\" - modify it to a numeric value!")
+ end
+ return result
+end
+
+-- Iterate over the configuration name and descriptor pairs from config_schema
+-- and checks if the configuration exists (for required config names) and
+-- converts it to the descriptor value type.
+-- If the configuration name does not exist or type conversion is not successful
+-- then error is reported in the log and on the user interface of Simplicity
+-- Studio. The preprocessed config is written to preprocessed_config parameter.
+local function preprocess_config(preprocessed_config, config_schema)
+ local status = STATUS.OK
+ for name, descriptor in pairs(config_schema) do
+ if slc.config(name) == nil then
+ if descriptor.required then
+ local problem = "Configuration missing"
+ local description = "Configuration constant does not exist: " .. name
+ report_error(problem, validation.target_for_project(), description, nil)
+ if status == STATUS.OK then
+ -- Store the status code of the first error
+ status = STATUS.CONFIG_DOES_NOT_EXIST
+ end
+ end
+ else
+ if (descriptor.value_type == "number") or (descriptor.value_type == "boolean") then
+ local num = autonumber(slc.config(name).value)
+ if num ~= nil then
+ if (descriptor.value_type == "boolean") then
+ preprocessed_config[name] = (num ~= 0)
+ else
+ preprocessed_config[name] = num
+ end
+ end
+ else
+ preprocessed_config[name] = slc.config(name).value
+ end
+
+ -- If the number or boolean conversion fails then the name doesn't
+ -- exist in the config table so the preprocessed_config[name] is nil
+ -- which has nil type
+ if type(preprocessed_config[name]) ~= descriptor.value_type then
+ local problem = "Configuration type mismatch"
+ local description =
+ string.format("The %s constant is not a %s (value: %s)",
+ name,
+ descriptor.value_type,
+ tostring(slc.config(name).value))
+ report_error(problem, validation.target_for_defines({name}), description, nil)
+ if status == STATUS.OK then
+ -- Store the status code of the first error
+ status = STATUS.CONFIG_TYPE_MISMATCH
+ end
+ end
+ end
+ end
+ return status
+end
+
+local function chunk_size_to_message_count(chunk_size)
+ local MAX_UNSEGMENTED_PAYLOAD = 11
+ local MAX_PAYLOAD_PER_SEGMENT = 12
+ local CHUNK_SIZE_HEADER_MIN_SIZE = 3 -- Opcode (1) + Chunk Number (2)
+ local TRANSMIC_MIN_SIZE = 4
+ local MAX_UNSEGMENTED_CHUNK_SIZE = MAX_UNSEGMENTED_PAYLOAD
+ - CHUNK_SIZE_HEADER_MIN_SIZE
+ if MAX_UNSEGMENTED_CHUNK_SIZE < chunk_size then
+ return math.ceil((CHUNK_SIZE_HEADER_MIN_SIZE + chunk_size + TRANSMIC_MIN_SIZE)
+ / MAX_PAYLOAD_PER_SEGMENT)
+ else
+ return 1
+ end
+end
+
+local min_block_size_log = "SL_BTMESH_BLOB_TRANSFER_SERVER_MIN_BLOCK_SIZE_LOG_CFG_VAL"
+local max_block_size_log = "SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_BLOCK_SIZE_LOG_CFG_VAL"
+local max_chunks_per_block = "SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_CHUNKS_PER_BLOCK_CFG_VAL"
+local max_chunk_size = "SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_CHUNK_SIZE_CFG_VAL"
+local push_mode = "SL_BTMESH_BLOB_TRANSFER_SERVER_PUSH_MODE_CFG_VAL"
+local pull_mode = "SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_MODE_CFG_VAL"
+local pull_chunk_request_cnt = "SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_CHUNK_REQUEST_CNT_CFG_VAL"
+local lpn_mode = "SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_MODE_CFG_VAL"
+local lpn_min_queue_length = "SL_BTMESH_LPN_MIN_QUEUE_LENGTH_CFG_VAL"
+
+local config_schema = {
+ [min_block_size_log] = {value_type = "number", required = true},
+ [max_block_size_log] = {value_type = "number", required = true},
+ [max_chunks_per_block] = {value_type = "number", required = true},
+ [max_chunk_size] = {value_type = "number", required = true},
+ [push_mode] = {value_type = "boolean", required = true},
+ [pull_mode] = {value_type = "boolean", required = true},
+ [pull_chunk_request_cnt] = {value_type = "number", required = true},
+ [lpn_mode] = {value_type = "boolean", required = true},
+ [lpn_min_queue_length] = {value_type = "number", required = false}
+}
+
+local config = {}
+local lpn_component_selected = slc.is_selected("btmesh_lpn")
+
+if lpn_component_selected then
+ log_info("Node with low power feature")
+ config_schema[lpn_min_queue_length].required = true
+end
+
+local status = preprocess_config(config, config_schema)
+
+if status == STATUS.OK then
+ -- It is guaranteed that all required config parameters exist and these are
+ -- converted to the proper type
+
+ -- Commented due to max 1000 instruction per lua script limit
+ -- for name, value in pairs(config) do
+ -- -- Log the converted configuration parameters
+ -- log_info(name .. ": " .. tostring(value) .. " (type: " .. type(value) .. ")")
+ -- end
+
+ if config[max_block_size_log] < config[min_block_size_log] then
+ local problem = "Block size configuration invalid"
+ local description =
+ string.format("Min Block Size Log (%i) shall not be greater than " ..
+ "Max Block Size Log (%i)",
+ config[min_block_size_log],
+ config[max_block_size_log])
+ report_error(problem,
+ validation.target_for_defines({min_block_size_log,
+ max_block_size_log}),
+ description,
+ nil)
+ end
+
+ if (config[max_chunks_per_block] * config[max_chunk_size]) < (2 ^ config[max_block_size_log]) then
+ local problem = "Block and chunk configuration inconsistent"
+ local description =
+ string.format("Max Block Size (%i) shall not be greater than product of " ..
+ "max chunk size (%i) and max number of chunks per block (%i)",
+ 2 ^ config[max_block_size_log],
+ config[max_chunk_size],
+ config[max_chunks_per_block])
+ report_error(problem,
+ validation.target_for_defines({max_block_size_log,
+ max_chunk_size,
+ max_chunks_per_block}),
+ description,
+ nil)
+ end
+
+ if config[lpn_mode] ~= lpn_component_selected then
+ local problem = "LPN mode and LPN component consistency issue"
+ local description = "The LPN mode configuration option shall be turned on " ..
+ "if and only if the Low Power Node component is present"
+ report_error(problem,
+ validation.target_for_defines({lpn_mode}),
+ description,
+ nil)
+ elseif config[lpn_mode] then
+ -- LPN mode is turned on and the LPN component exists in the project
+
+ if config[push_mode] or not config[pull_mode] then
+ local problem = "Transfer mode inconsistency"
+ local description = "Pull transfer mode is recommended for low power nodes"
+ report_warning(problem,
+ validation.target_for_defines({push_mode, pull_mode}),
+ description,
+ nil)
+ end
+
+ local chunk_message_count = chunk_size_to_message_count(config[max_chunk_size])
+ if (config[lpn_min_queue_length] < config[pull_chunk_request_cnt] * chunk_message_count)
+ and config[pull_mode] then
+ local problem = "Requested chunks greater than friend queue size"
+ local description =
+ string.format("The %i max chunk size fits into %i message and " ..
+ "%i chunks requested at the same time which does " ..
+ "not fit into min lpn queue size (%i message)",
+ config[max_chunk_size],
+ chunk_message_count,
+ config[pull_chunk_request_cnt],
+ config[lpn_min_queue_length])
+ report_error(problem,
+ validation.target_for_defines({max_chunk_size,
+ pull_chunk_request_cnt,
+ lpn_min_queue_length}),
+ description,
+ nil)
+ end
+ end
+end
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_blob_transfer_server/config/sl_btmesh_blob_transfer_server_config.h b/app/btmesh/common/btmesh_blob_transfer_server/config/sl_btmesh_blob_transfer_server_config.h
new file mode 100644
index 00000000000..d93dae1a243
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_server/config/sl_btmesh_blob_transfer_server_config.h
@@ -0,0 +1,125 @@
+/***************************************************************************//**
+ * @file
+ * @brief BLOB Transfer Server Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_BLOB_TRANSFER_SERVER_CONFIG_H
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Min Block Size Log <0x6-0x20>
+// Please note, that decreasing the minimum block size will result in increased heap usage.
+// Block states need to be monitored. The smaller the blocks, the bigger the state storage.
+// Change this value with care.
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_MIN_BLOCK_SIZE_LOG_CFG_VAL 0x9
+
+// Max Block Size Log <0x6-0x20>
+// Please note, that increasing the maximum block size will result in increased heap usage.
+// Blocks are cached on heap before being written into NVM.
+// Change this value with care.
+// The maximum block size shall be less than or equal to the product of max chunks per block and chunk size.
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_BLOCK_SIZE_LOG_CFG_VAL 0x9
+
+// Maximum of number of chunks per block <8-64:8>
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_CHUNKS_PER_BLOCK_CFG_VAL 16
+
+// Maximum chunk size <8-241:1>
+// If the max chunk size is 8 then the chunk data fits into a single BT Mesh advertisement message.
+// If the chunk data is segmented then N segments is able to transfer (N*12)-7 byte data.
+// The advantage of higher chunk size is the higher throughput in low noise environment.
+// The advantage of lower chunk size is that fewer messages are retransmitted in high noise environment due to lost chunk messages.
+// LPN only: the number of chunk messages (segments) multiplied by requested chunk count in partial block report shall fit into the friend queue.
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_CHUNK_SIZE_CFG_VAL 241
+
+// Logging
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_LOGGING_CFG_VAL 1
+
+// Transfer Start user callback
+// Enable/disable callback function when BLOB transfer starts.
+#define SL_BTMESH_BLOB_TRANSFER_START_CALLBACK_CFG_VAL 1
+
+// Transfer Progress user callback
+// Enable/disable callback function when block transfer is finished.
+#define SL_BTMESH_BLOB_TRANSFER_PROGRESS_CALLBACK_CFG_VAL 1
+
+// Transfer Done user callback
+// Enable/disable callback function when BLOB transfer is finished.
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_TRANSFER_DONE_CALLBACK_CFG_VAL 1
+
+// Supported Transfer Modes
+
+// Push Mode
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_PUSH_MODE_CFG_VAL 1
+
+//
+
+// Pull Mode
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_MODE_CFG_VAL 1
+
+// Number of chunks requested in Block Status or Partial Block Report <1-32>
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_CHUNK_REQUEST_CNT_CFG_VAL 4
+
+// Interval, in milliseconds, between Partial Block Reports, if nothing is received <1000-30000:100>
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_RETRY_INTERVAL_MS_CFG_VAL 1000
+
+// Number of retries sending the same Partial Block Report, before giving up <1-10>
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_RETRY_CNT_CFG_VAL 8
+
+// LPN Mode
+// Only pull transfer mode can be used on LPN nodes.
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_MODE_CFG_VAL 0
+
+// LPN high throughput mode
+// In high throughput mode the LPN node polls the friend node more frequently to increase the throughput at the expense of power consumption.
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_HIGH_THROUGHPUT_MODE_CFG_VAL 1
+
+// LPN poll delay in milliseconds <100-30000:100>
+// The delay of first LPN poll when the BLOB Transfer Server expects messages from the client after an event.
+// The major part of BLOB transfer to LPN is the waiting for the poll timeout to elapse in order to poll the friend node for BLOB Transfer messages.
+// The maximum number of messages can be transferred per polling equals to friend queue size during BLOB transfer to LPN.
+// This poll delay configuration parameter value makes the polling more frequent when BLOB Transfer messages are expected to increase the throughput.
+// The LPN poll delay shall be less than SL_BTMESH_LPN_POLL_TIMEOUT_CFG_VAL in sl_btmesh_lpn_config.h file.
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_POLL_DELAY_MS_CFG_VAL 500
+
+// LPN poll logging
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_POLL_LOGGING_CFG_VAL 0
+
+//
+//
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#if SL_BTMESH_BLOB_TRANSFER_SERVER_MIN_BLOCK_SIZE_LOG_CFG_VAL > SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_BLOCK_SIZE_LOG_CFG_VAL
+#error The Max Block Size Log value shall be equal to or greater than the Min Block Size Log value.
+#endif
+
+#endif // SL_BTMESH_BLOB_TRANSFER_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server.c b/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server.c
new file mode 100644
index 00000000000..235d008986b
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server.c
@@ -0,0 +1,537 @@
+/***************************************************************************//**
+ * @file
+ * @brief BLOB Transfer Server application level functionality
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#include
+#include
+
+#include "sl_bt_api.h"
+#include "sl_btmesh.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+#include "sl_malloc.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#include "app_assert.h"
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_simple_timer.h"
+
+#include "sl_btmesh_blob_storage.h"
+
+#ifdef SL_CATALOG_BTMESH_STACK_LPN_PRESENT
+#include "sl_btmesh_lpn.h"
+#endif // SL_CATALOG_BTMESH_STACK_LPN_PRESENT
+
+#include "sl_btmesh_blob_transfer_server.h"
+#include "sl_btmesh_blob_transfer_server_api.h"
+#include "sl_btmesh_blob_transfer_server_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup mesh_blob_transfer_server
+ * @{
+ ******************************************************************************/
+
+/// In high throughput mode the LPN node polls the friend node more frequently
+/// to increase the throughput at the expense of power consumption
+/// @note LPN high throughput mode can be active only in LPN mode
+#define LPN_HIGH_THROUGHPUT_MODE_ACTIVE \
+ SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_MODE_CFG_VAL \
+ && SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_HIGH_THROUGHPUT_MODE_CFG_VAL
+
+// Returns the string representation of BLOB ID in a compound literal.
+// WARNING! This macro shall be used as a parameter of log calls only due to the
+// lifetime of underlying compound literal in APP_BTMESH_UUID_64_TO_STRING.
+#define BLOB_ID_TO_STRING(blob_id) APP_BTMESH_UUID_64_TO_STRING(blob_id, ' ', true)
+
+#if SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_POLL_LOGGING_CFG_VAL
+#define blob_lpn_poll_log(...) log_debug(__VA_ARGS__)
+#else
+#define blob_lpn_poll_log(...)
+#endif
+
+typedef enum {
+ /// The BLOB server is idle
+ IDLE,
+ /// BLOB server is active
+ ACTIVE,
+ /*************************************************************************//**
+ * @addtogroup activesubstates Active Sub-states
+ * @{
+ ****************************************************************************/
+ /// BLOBs are being erased
+ ACTIVE_ERASING,
+ /// Invalid BLOBs are being erased
+ ACTIVE_ERASING_INVALID,
+ /// Unamanged BLOBs are being erased
+ ACTIVE_ERASING_UNMANAGED,
+ /// Transfer is ongoing
+ ACTIVE_TRANSFER,
+ /** @} end activesubstates */
+ /*************************************************************************//**
+ * @addtogroup idlesubstates Idle Sub-states
+ * @{
+ ****************************************************************************/
+ /// The state machine is either initialized or the transfer is canceled
+ IDLE_INACTIVE,
+ /// The transfer has been completed
+ IDLE_DONE
+ /** @} end idlesubstates */
+} blob_transfer_server_state_t;
+
+struct {
+#if LPN_HIGH_THROUGHPUT_MODE_ACTIVE
+ /// Handler for the LPN high throughput timer
+ sl_btmesh_lpn_high_throughput_timer_t high_throughput_timer;
+#endif // LPN_HIGH_THROUGHPUT_MODE_ACTIVE
+ /// ID of the BLOB being transferred
+ sl_bt_uuid_64_t blob_id;
+ /// Size of the BLOB being transferred
+ uint32_t blob_size;
+ /// Current progress of the current transfer in bytes
+ uint32_t progress;
+ /// Block size calculated based on the log value received at transfer start
+ uint32_t blob_block_size;
+ /// Buffer for blocks
+ uint8_t * block_buffer;
+ /// State of the current transfer
+ union {
+ struct {
+ uint16_t idle : 1; ///< Idle state
+ uint16_t inactive : 1; ///< Idle/Inactive state
+ uint16_t done : 1; ///< Idle/Done state
+ uint16_t active : 1; ///< Active state
+ uint16_t suspended : 1; ///< Active/Suspended state
+ uint16_t erasing : 1; ///< Active/Erasing state
+ uint16_t erasing_invalid : 1; ///< Active/Erasing Invalid state
+ uint16_t erasing_unmanaged : 1; ///< Active/Erasing Unmanaged state
+ uint16_t transfer : 1; ///< Active/Transfer state
+ };
+ uint16_t raw; ///< raw data for quick access
+ } state;
+} blob_transfer_server;
+
+/***************************************************************************//**
+ * Handler for state change
+ *
+ * Takes care of entry actions.
+ *
+ * @param state State to transfer into
+ ******************************************************************************/
+static void blob_transfer_server_state_change(blob_transfer_server_state_t state);
+
+void sl_btmesh_blob_transfer_server_init(void)
+{
+ uint32_t max_blob_size = sl_btmesh_blob_storage_get_max_blob_size();
+ if (max_blob_size == 0) {
+ log_warning("No available flash for BLOB Transfer. "
+ "Consider removing the component." NL);
+ } else {
+ sl_status_t sc =
+ sl_btmesh_mbt_server_init(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_MIN_BLOCK_SIZE_LOG_CFG_VAL,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_BLOCK_SIZE_LOG_CFG_VAL,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_CHUNKS_PER_BLOCK_CFG_VAL,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_MAX_CHUNK_SIZE_CFG_VAL,
+ max_blob_size,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_PUSH_MODE_CFG_VAL
+ | (SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_MODE_CFG_VAL << 1),
+ SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_CHUNK_REQUEST_CNT_CFG_VAL,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_RETRY_INTERVAL_MS_CFG_VAL,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_PULL_RETRY_CNT_CFG_VAL);
+ app_assert_status(sc);
+
+ // Initial state is Idle
+ blob_transfer_server_state_change(IDLE);
+ }
+}
+
+inline sl_status_t sl_btmesh_blob_transfer_server_start(sl_bt_uuid_64_t const *const blob_id,
+ const uint16_t timeout_10s,
+ const uint8_t ttl)
+{
+ // This function essentially wraps the call below
+ return sl_btmesh_mbt_server_start(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ *blob_id,
+ timeout_10s,
+ ttl);
+}
+
+sl_status_t sl_btmesh_blob_transfer_server_set_pull_mode_parameters(uint16_t pull_mode_retry_interval_ms,
+ uint16_t pull_mode_retry_count)
+{
+ // This function essentially wraps the call below
+ return sl_btmesh_mbt_server_set_pull_mode_parameters(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ pull_mode_retry_interval_ms,
+ pull_mode_retry_count);
+}
+
+void sl_btmesh_blob_transfer_server_on_event(sl_btmesh_msg_t const *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_blob_transfer_server_init();
+ break;
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_blob_transfer_server_init();
+ }
+ break;
+ case sl_btmesh_evt_mbt_server_transfer_start_req_id: {
+ sl_btmesh_evt_mbt_server_transfer_start_req_t const *msg =
+ &evt->data.evt_mbt_server_transfer_start_req;
+ // If suspended, there are two cases we can move forward
+ if (blob_transfer_server.state.suspended == 1) {
+ if (0
+ == memcmp(&blob_transfer_server.blob_id,
+ &msg->blob_id,
+ sizeof(sl_bt_uuid_64_t))) {
+ // If it's the same blob, resume.
+ blob_transfer_server.state.suspended = 0;
+ sl_btmesh_mbt_server_transfer_start_rsp(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ sl_btmesh_mbt_server_status_success);
+ return;
+ }
+ // If it's a different BLOB, treat it as regular start message
+ }
+ // If BLOB already present ignore
+ if (sl_btmesh_blob_storage_is_present(&msg->blob_id)) {
+ log_error("BLOB (%s) already present!" NL,
+ BLOB_ID_TO_STRING(&msg->blob_id));
+ sl_btmesh_mbt_server_transfer_start_rsp(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ sl_btmesh_mbt_server_status_internal_error);
+ return;
+ }
+ memcpy(&blob_transfer_server.blob_id,
+ &msg->blob_id,
+ sizeof(sl_bt_uuid_64_t));
+ // Set progress target
+ blob_transfer_server.blob_size = msg->blob_size;
+ // Set expected block size
+ blob_transfer_server.blob_block_size = 1 << msg->block_size_log;
+ blob_transfer_server.block_buffer =
+ sl_malloc(blob_transfer_server.blob_block_size);
+ // Check allocation result
+ if (blob_transfer_server.block_buffer == NULL) {
+ log_critical("Block buffer allocation failed!" NL);
+ sl_btmesh_mbt_server_transfer_start_rsp(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ sl_btmesh_mbt_server_status_internal_error);
+ // State change
+ blob_transfer_server_state_change(IDLE_INACTIVE);
+ } else {
+ log_info("BLOB Transfer Start received, id: %s\t"
+ "size: %lu B" NL,
+ BLOB_ID_TO_STRING(&blob_transfer_server.blob_id),
+ blob_transfer_server.blob_size);
+
+ // State change
+ blob_transfer_server_state_change(ACTIVE);
+ }
+ break;
+ }
+ case sl_btmesh_evt_mbt_server_block_start_id: {
+ // If not in Active/Transfer state, ignore message
+ if (blob_transfer_server.state.transfer == 0) {
+ return;
+ }
+ sl_btmesh_evt_mbt_server_block_start_t const *msg =
+ &evt->data.evt_mbt_server_block_start;
+ // Check whether received block is related to BLOB being received
+ if (memcmp(&msg->blob_id,
+ &blob_transfer_server.blob_id,
+ sizeof(sl_bt_uuid_64_t))
+ != 0) {
+ return;
+ }
+ blob_transfer_server.progress = msg->block_number
+ * blob_transfer_server.blob_block_size;
+
+ log_debug("Block %d start" NL, msg->block_number);
+ break;
+ }
+ case sl_btmesh_evt_mbt_server_block_complete_id: {
+ sl_btmesh_evt_mbt_server_block_complete_t const *msg =
+ &evt->data.evt_mbt_server_block_complete;
+ // If not in Active/Transfer state, ignore message
+ if (blob_transfer_server.state.transfer == 0) {
+ return;
+ }
+#if SL_BTMESH_BLOB_TRANSFER_PROGRESS_CALLBACK_CFG_VAL
+ // User callback for progress at block end
+ sl_btmesh_blob_transfer_server_transfer_progress(&blob_transfer_server.blob_id,
+ SL_PROG_TO_PCT(blob_transfer_server.blob_size,
+ blob_transfer_server.progress));
+#endif // SL_BTMESH_BLOB_TRANSFER_PROGRESS_CALLBACK_CFG_VAL
+ // Calculate offset of block to be received; since the last block can be
+ // smaller than the rest, use the size received in Transfer Start to
+ // calculate offset
+ uint32_t write_offset = blob_transfer_server.blob_block_size
+ * msg->block_number;
+ // Write data using wrapper
+ sl_status_t sc =
+ sl_btmesh_blob_storage_write(write_offset,
+ msg->block_size,
+ blob_transfer_server.block_buffer);
+ app_assert_status_f(sc, "Storage writing failed!");
+ log_info("Block %d complete (%s), progress %u%%" NL,
+ msg->block_number,
+ BLOB_ID_TO_STRING(&blob_transfer_server.blob_id),
+ // Calculate progress in percent
+ (unsigned) SL_PROG_TO_PCT_INT(blob_transfer_server.blob_size,
+ blob_transfer_server.progress));
+
+ if (blob_transfer_server.progress == blob_transfer_server.blob_size) {
+ // State change
+ blob_transfer_server_state_change(IDLE_DONE);
+ }
+ break;
+ }
+#if LPN_HIGH_THROUGHPUT_MODE_ACTIVE
+ case sl_btmesh_evt_mbt_server_partial_block_report_tx_complete_id:
+ {
+ // Start polling after partial block report is sent out, because
+ // the next messages are sent out quickly by the client.
+ sl_btmesh_lpn_high_throughput_register(&blob_transfer_server.high_throughput_timer,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_POLL_DELAY_MS_CFG_VAL,
+ SL_BTMESH_LPN_HIGH_THROUGHPUT_SLOWING);
+ blob_lpn_poll_log("Start slowing poll delay %dms (partial block)" NL,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_POLL_DELAY_MS_CFG_VAL);
+ break;
+ }
+#endif // LPN_HIGH_THROUGHPUT_MODE_ACTIVE
+ case sl_btmesh_evt_mbt_server_state_changed_id: {
+ sl_btmesh_evt_mbt_server_state_changed_t const *msg =
+ &evt->data.evt_mbt_server_state_changed;
+ log_debug("MBT Server state changed to %d" NL, msg->new_state);
+ switch (msg->new_state) {
+ case sl_btmesh_mbt_server_phase_suspended:
+ blob_transfer_server.state.suspended = 1;
+ break;
+ case sl_btmesh_mbt_server_phase_inactive:
+ if (blob_transfer_server.state.idle == 0) {
+ log_error("BLOB Transfer (%s) aborted" NL,
+ BLOB_ID_TO_STRING(&blob_transfer_server.blob_id));
+ // Notify user that transfer is aborted
+ sl_btmesh_blob_transfer_server_transfer_abort(&blob_transfer_server.blob_id);
+ blob_transfer_server_state_change(IDLE_INACTIVE);
+ }
+#if LPN_HIGH_THROUGHPUT_MODE_ACTIVE
+ // intentional fall-through
+ case sl_btmesh_mbt_server_phase_waiting_for_block:
+ // Start poll delay to poll for expected BLOB transfer messages:
+ // BLOB Transfer Start, BLOB Block Start
+ // Note: sl_btmesh_mbt_server_phase_idle means that the expected BLOB ID
+ // is set but the BLOB Transfer Start message is not received yet
+ sl_btmesh_lpn_high_throughput_register(&blob_transfer_server.high_throughput_timer,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_POLL_DELAY_MS_CFG_VAL,
+ SL_BTMESH_LPN_HIGH_THROUGHPUT_SLOWING);
+ blob_lpn_poll_log("Start slowing poll delay %dms (state change)" NL,
+ SL_BTMESH_BLOB_TRANSFER_SERVER_LPN_POLL_DELAY_MS_CFG_VAL);
+#endif // LPN_HIGH_THROUGHPUT_MODE_ACTIVE
+ break;
+ default:
+#if LPN_HIGH_THROUGHPUT_MODE_ACTIVE
+ // If the BLOB transfer is no longer active then the LPN high throughput
+ // request shall be unregistered.
+ sl_btmesh_lpn_high_throughput_unregister(&blob_transfer_server.high_throughput_timer);
+#endif // LPN_HIGH_THROUGHPUT_MODE_ACTIVE
+ break;
+ }
+ // sl_btmesh_evt_mbt_server_state_changed_id
+ break;
+ }
+ case sl_btmesh_evt_mbt_server_chunk_id: {
+ // If not in Active state, ignore message
+ if (blob_transfer_server.state.active == 0) {
+ return;
+ }
+ sl_btmesh_evt_mbt_server_chunk_t const *msg =
+ &evt->data.evt_mbt_server_chunk;
+ // Increment progress
+ blob_transfer_server.progress += msg->data.len;
+ // Buffer data according to block offset
+ memcpy(&blob_transfer_server.block_buffer[msg->block_offset],
+ msg->data.data,
+ msg->data.len);
+ // Calculate progress in percent
+ float prog = SL_PROG_TO_PCT(blob_transfer_server.blob_size,
+ blob_transfer_server.progress);
+ log_debug("BLOB Transfer (%s) %3d.%02d%%" NL,
+ BLOB_ID_TO_STRING(&blob_transfer_server.blob_id),
+ (int)prog,
+ (int)(prog * 100) % 100);
+ (void)prog;
+ break;
+ }
+ case sl_btmesh_evt_mbt_server_transfer_cancel_id: {
+ if (blob_transfer_server.state.active == 0) {
+ return;
+ }
+ sl_btmesh_evt_mbt_server_transfer_cancel_t const *msg =
+ &evt->data.evt_mbt_server_transfer_cancel;
+ // Compare ID's
+ if (0 == memcmp(&msg->blob_id,
+ &blob_transfer_server.blob_id,
+ sizeof(sl_bt_uuid_64_t))) {
+ // State change
+ blob_transfer_server_state_change(IDLE_INACTIVE);
+ // Log cancellation
+ log_info("BLOB Transfer (%s) Canceled" NL,
+ BLOB_ID_TO_STRING(&blob_transfer_server.blob_id));
+ }
+ break;
+ }
+ default:
+ // empty
+ break;
+ }
+}
+
+static void blob_transfer_server_state_change(blob_transfer_server_state_t state)
+{
+ // Clear state flags
+ blob_transfer_server.state.raw = 0;
+ switch (state) {
+ case IDLE:
+ // Idle has an initial state transition to Idle/Inactive: fall through
+ case IDLE_INACTIVE:
+ blob_transfer_server.state.idle = 1;
+ blob_transfer_server.state.inactive = 1;
+ // Set the BLOB ID to Unknown, i.e. 0xFFFFFFFFFFFFFFFF
+ memset(&blob_transfer_server.blob_id, UINT8_MAX, sizeof(sl_bt_uuid_64_t));
+ // Set BLOB Size to Unknown, i.e. 0xFFFFFFFF
+ blob_transfer_server.blob_size = UINT32_MAX;
+ // Set progress to Unknown, i.e. 0xFFFFFFFF
+ blob_transfer_server.progress = UINT32_MAX;
+ sl_free(blob_transfer_server.block_buffer);
+ blob_transfer_server.block_buffer = NULL;
+ break;
+ case IDLE_DONE: {
+ blob_transfer_server.state.idle = 1;
+ blob_transfer_server.state.done = 1;
+ // Log finished transfer
+ log_info("BLOB Transfer (%s) Done" NL,
+ BLOB_ID_TO_STRING(&blob_transfer_server.blob_id));
+ sl_status_t sc = sl_btmesh_blob_storage_verify();
+ log_status_critical_f(sc, "BLOB signing failed!" NL);
+#if SL_BTMESH_BLOB_TRANSFER_SERVER_TRANSFER_DONE_CALLBACK_CFG_VAL
+ // Notify user that transfer has completed
+ sl_btmesh_blob_transfer_server_transfer_done(&blob_transfer_server.blob_id);
+#endif // SL_BTMESH_BLOB_TRANSFER_SERVER_TRANSFER_DONE_CALLBACK_CFG_VAL
+ sl_free(blob_transfer_server.block_buffer);
+ break;
+ }
+ case ACTIVE:
+ case ACTIVE_TRANSFER: {
+ sl_status_t s;
+ blob_transfer_server.state.active = 1;
+ // Reset progress
+ blob_transfer_server.progress = 0;
+
+ // Notify storage wrapper about write start
+ s = sl_btmesh_blob_storage_write_start(&blob_transfer_server.blob_id,
+ blob_transfer_server.blob_size);
+
+ if (SL_STATUS_NO_MORE_RESOURCE == s) {
+ log_info("No more slots available, attempting erase." NL);
+ blob_transfer_server_state_change(ACTIVE_ERASING);
+ break;
+ }
+
+ switch (s) {
+ case SL_STATUS_OK:
+ sl_btmesh_mbt_server_transfer_start_rsp(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ sl_btmesh_mbt_server_status_success);
+#if SL_BTMESH_BLOB_TRANSFER_START_CALLBACK_CFG_VAL
+ // Notify user about transfer start
+ sl_btmesh_blob_transfer_server_transfer_start(&blob_transfer_server.blob_id);
+#endif // SL_BTMESH_BLOB_TRANSFER_START_CALLBACK_CFG_VAL
+ blob_transfer_server.state.transfer = 1;
+ break;
+
+ default:
+ sl_btmesh_mbt_server_transfer_start_rsp(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ sl_btmesh_mbt_server_status_internal_error);
+ blob_transfer_server_state_change(IDLE_INACTIVE);
+ break;
+ }
+ break;
+ case ACTIVE_ERASING:
+ case ACTIVE_ERASING_INVALID:
+ blob_transfer_server.state.active = 1;
+ blob_transfer_server.state.erasing = 1;
+ blob_transfer_server.state.erasing_invalid = 1;
+ sl_btmesh_blob_storage_delete_invalid_slots_start();
+ break;
+ case ACTIVE_ERASING_UNMANAGED:
+ blob_transfer_server.state.active = 1;
+ blob_transfer_server.state.erasing = 1;
+ blob_transfer_server.state.erasing_unmanaged = 1;
+ sl_btmesh_blob_storage_delete_unmanaged_slots_start();
+ break;
+ }
+ }
+}
+
+void sl_btmesh_blob_transfer_server_step_handle(void)
+{
+ if (1 == blob_transfer_server.state.erasing) {
+ switch (sl_btmesh_blob_storage_get_erase_error_code()) {
+ case SL_BTMESH_BLOB_STORAGE_DELETE_SUCCESS:
+ blob_transfer_server_state_change(ACTIVE_TRANSFER);
+ break;
+ case SL_BTMESH_BLOB_STORAGE_DELETE_FAILED:
+ case SL_BTMESH_BLOB_STORAGE_DELETE_INACTIVE:
+ // If the delete failed or the last delete operation didn't find any
+ // invalid or unmanaged slot to delete
+ if (blob_transfer_server.state.erasing_invalid == 1) {
+ blob_transfer_server_state_change(ACTIVE_ERASING_UNMANAGED);
+ } else if (blob_transfer_server.state.erasing_unmanaged == 1) {
+ sl_btmesh_mbt_server_transfer_start_rsp(BTMESH_BLOB_TRANSFER_SERVER_MAIN,
+ sl_btmesh_mbt_server_status_storage_limit);
+ blob_transfer_server_state_change(IDLE_INACTIVE);
+ }
+ break;
+ default:
+ // empty
+ break;
+ }
+ }
+}
+
+/** @} end mesh_blob_transfer_server */
diff --git a/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server.h b/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server.h
new file mode 100644
index 00000000000..002ed416d26
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server.h
@@ -0,0 +1,107 @@
+/***************************************************************************//**
+ * @file
+ * @brief Definition of BLOB Transfer Server interface
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_BLOB_TRANSFER_SERVER_H
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup mesh_blob_transfer_server BT Mesh BLOB Transfer Server
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Initializes the BLOB Transfer Server application
+ ******************************************************************************/
+extern void sl_btmesh_blob_transfer_server_init(void);
+
+/***************************************************************************//**
+ * Processes BT Mesh stack events related to BLOB Trasnfer Server
+ *
+ * @param[in] evt BT Mesh stack event
+ ******************************************************************************/
+extern void sl_btmesh_blob_transfer_server_on_event(sl_btmesh_msg_t const *evt);
+
+/***************************************************************************//**
+ * Let BLOB Transfer Server accept an incoming BLOB with given ID
+ *
+ * This function needs to be called from other application components, that can
+ * determine incoming BLOB ID's, before any transfer can be done.
+ *
+ * @note Firmware Update Server automatically handles this operation when it
+ * receives a firmware update start message
+ * (@ref sl_btmesh_evt_fw_update_server_update_start_req_id).
+ *
+ * @param blob_id Identifier of BLOB to be received
+ * @param timeout_10s Timeout of reception in 10 seconds. If no data is received
+ * for this time, the transfer will be suspended. The actual
+ * timeout is calculated as (1 + @p timeout_10s) × 10 s
+ * @param ttl The TTL used for communicating with the client
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_STATE In case transmission is already ongoing
+ ******************************************************************************/
+extern sl_status_t sl_btmesh_blob_transfer_server_start(sl_bt_uuid_64_t const * const blob_id,
+ const uint16_t timeout_10s,
+ const uint8_t ttl);
+
+/***************************************************************************//**
+ * Set PULL transfer retry parameters
+ *
+ * The new values will take effect at the next transfer start. The command does
+ * not modify parameters of the ongoing transfer.
+ *
+ * @param pull_mode_retry_interval_ms Retry interval in milliseconds
+ * @param pull_mode_retry_count Number of times to retry
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_STATE In case state is invalid
+ ******************************************************************************/
+extern sl_status_t sl_btmesh_blob_transfer_server_set_pull_mode_parameters(uint16_t pull_mode_retry_interval_ms,
+ uint16_t pull_mode_retry_count);
+
+/***************************************************************************//**
+ * Handles the BLOB transfer state machine
+ *
+ * Used for asynchronously erasing BLOBs
+ ******************************************************************************/
+void sl_btmesh_blob_transfer_server_step_handle(void);
+
+/** @} end mesh_blob_transfer_server */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // SL_BTMESH_BLOB_TRANSFER_SERVER_H
diff --git a/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server_api.c b/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server_api.c
new file mode 100644
index 00000000000..f6d2e4794ee
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server_api.c
@@ -0,0 +1,105 @@
+/***************************************************************************//**
+ * @file
+ * @brief Weak implementation of user API functions
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#include "sl_btmesh.h"
+
+#include "sl_btmesh_blob_transfer_server_api.h"
+#include "sl_btmesh_blob_storage.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+#include "sl_btmesh_wstk_lcd.h"
+#include
+// First block flag
+static bool first_block;
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+
+SL_WEAK void sl_btmesh_blob_transfer_server_transfer_start(sl_bt_uuid_64_t const *const blob_id)
+{
+ (void)blob_id;
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("BLOB Transfer Start", SL_BTMESH_WSTK_LCD_ROW_BLOB_STATUS_CFG_VAL);
+ char blob_id_buff[3 * 8] = { 0 };
+ sprintf(blob_id_buff,
+ "0x%02X%02X%02X%02X%02X%02X%02X%02X",
+ blob_id->data[0],
+ blob_id->data[1],
+ blob_id->data[2],
+ blob_id->data[3],
+ blob_id->data[4],
+ blob_id->data[5],
+ blob_id->data[6],
+ blob_id->data[7]);
+ sl_btmesh_LCD_write(blob_id_buff, SL_BTMESH_WSTK_LCD_ROW_BLOB_ID_CFG_VAL);
+ first_block = true;
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+}
+
+SL_WEAK void sl_btmesh_blob_transfer_server_transfer_progress(sl_bt_uuid_64_t const *const blob_id,
+ float progress)
+{
+ (void)blob_id;
+ (void)progress;
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ if (first_block) {
+ sl_btmesh_LCD_write("BLOB Transfer Prog.",
+ SL_BTMESH_WSTK_LCD_ROW_BLOB_STATUS_CFG_VAL);
+ first_block = false;
+ }
+ char prog_buff[8] = { 0 };
+ sprintf(prog_buff, "%3u.%01u %%", (uint8_t)progress, (uint8_t)(((uint16_t)(progress * 10)) % 10));
+ sl_btmesh_LCD_write(prog_buff, SL_BTMESH_WSTK_LCD_ROW_BLOB_PROGRESS_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+}
+
+SL_WEAK void sl_btmesh_blob_transfer_server_transfer_done(sl_bt_uuid_64_t const *const blob_id)
+{
+ (void)blob_id;
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("BLOB Transfer Done", SL_BTMESH_WSTK_LCD_ROW_BLOB_STATUS_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+}
+
+SL_WEAK void sl_btmesh_blob_transfer_server_transfer_abort(sl_bt_uuid_64_t const *const blob_id)
+{
+ (void)blob_id;
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("BLOB Transfer Aborted", SL_BTMESH_WSTK_LCD_ROW_BLOB_STATUS_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+}
+
+SL_WEAK void sl_btmesh_blob_transfer_server_storage_full(void)
+{
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("BLOB Storage Full", SL_BTMESH_WSTK_LCD_ROW_BLOB_STATUS_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+}
diff --git a/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server_api.h b/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server_api.h
new file mode 100644
index 00000000000..6a990a9a112
--- /dev/null
+++ b/app/btmesh/common/btmesh_blob_transfer_server/sl_btmesh_blob_transfer_server_api.h
@@ -0,0 +1,96 @@
+/***************************************************************************//**
+ * @file
+ * @brief User API header for BLOB Transfer Server component
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_BLOB_TRANSFER_SERVER_API_H
+#define SL_BTMESH_BLOB_TRANSFER_SERVER_API_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup mesh_blob_transfer_server
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup mesh_blob_transfer_server_api
+ *
+ * BT Mesh BLOB Transfer Server user-overridable API
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Called when starting BLOB transfer
+ *
+ * @param[in] blob_id Identifier of BLOB to be transferred
+ ******************************************************************************/
+extern void sl_btmesh_blob_transfer_server_transfer_start(sl_bt_uuid_64_t const *const blob_id);
+
+/***************************************************************************//**
+ * Called when new block is started
+ *
+ * @note Progress is incremented on a chunk basis, but user callback is called
+ * on block basis to avoid too frequent calls.
+ *
+ * @param[in] blob_id Identifier of the BLOB being transferred
+ * @param[in] progress Progress in floating point percentage
+ ******************************************************************************/
+extern void sl_btmesh_blob_transfer_server_transfer_progress(sl_bt_uuid_64_t const *const blob_id,
+ float progress);
+
+/***************************************************************************//**
+ * Called when BLOB transfer is done
+ *
+ * @param[in] blob_id Identifier of BLOB transferred
+ ******************************************************************************/
+extern void sl_btmesh_blob_transfer_server_transfer_done(sl_bt_uuid_64_t const *const blob_id);
+
+/***************************************************************************//**
+ * Called when BLOB transfer is aborted
+ *
+ * @param[in] blob_id Identifier of BLOB being transferred
+ ******************************************************************************/
+extern void sl_btmesh_blob_transfer_server_transfer_abort(sl_bt_uuid_64_t const *const blob_id);
+
+/***************************************************************************//**
+ * Called when BLOB storage signals it's full
+ ******************************************************************************/
+extern void sl_btmesh_blob_transfer_server_storage_full(void);
+
+/** @} end mesh_blob_transfer_server_api */
+
+/** @} end mesh_blob_transfer_server */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // SL_BTMESH_BLOB_TRANSFER_SERVER_API_H
diff --git a/app/btmesh/common/btmesh_ctl_client/btmesh_ctl_client.dcd b/app/btmesh/common/btmesh_ctl_client/btmesh_ctl_client.dcd
new file mode 100644
index 00000000000..86ef9c7acdd
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_client/btmesh_ctl_client.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1305", "name":"Light CTL Client"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_ctl_client/config/sl_btmesh_ctl_client_config.h b/app/btmesh/common/btmesh_ctl_client/config/sl_btmesh_ctl_client_config.h
new file mode 100644
index 00000000000..f8758ac675a
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_client/config/sl_btmesh_ctl_client_config.h
@@ -0,0 +1,63 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_CTL_CLIENT_CONFIG_H
+#define SL_BTMESH_CTL_CLIENT_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// CTL Client configuration
+
+// CTL model retransmission count
+// Default: 3
+// CTL model retransmission count (How many times CTL model messages are to be sent out for reliability).
+#define SL_BTMESH_CTL_CLIENT_RETRANSMISSION_COUNT_CFG_VAL (3)
+
+// CTL model retransmission timeout [ms] <0-1275:5>
+// Default: 50
+// CTL model retransmission timeout.
+#define SL_BTMESH_CTL_CLIENT_RETRANSMISSION_TIMEOUT_CFG_VAL (50)
+
+// Enable color temperature wraparound
+// Default: 0
+// If the color temperature reaches the max or min value then it wraps around.
+#define SL_BTMESH_CTL_CLIENT_TEMPERATURE_WRAP_ENABLED_CFG_VAL (0)
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for CTL Client model specific messages for this component.
+#define SL_BTMESH_CTL_CLIENT_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_CTL_CLIENT_CONFIG_H
diff --git a/app/btmesh/common/btmesh_ctl_client/sl_btmesh_ctl_client.c b/app/btmesh/common/btmesh_ctl_client/sl_btmesh_ctl_client.c
new file mode 100644
index 00000000000..84c3fa9ccc6
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_client/sl_btmesh_ctl_client.c
@@ -0,0 +1,259 @@
+/***************************************************************************//**
+ * @file
+ * @brief Bt Mesh CTL Client module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#include
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "sl_btmesh_api.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "sl_btmesh_generic_model_capi_types.h"
+#include "sl_btmesh_lib.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+#include "sl_btmesh_lighting_client.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_ctl_client_config.h"
+#include "sl_btmesh_ctl_client.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup Lighting
+ * @{
+ ******************************************************************************/
+
+/// No flags used for message
+#define NO_FLAGS 0
+/// Immediate transition time is 0 seconds
+#define IMMEDIATE 0
+/// Callback has not parameters
+#define NO_CALLBACK_DATA (void *)NULL
+/// High Priority
+#define HIGH_PRIORITY 0
+/// Minimum color temperature 800K
+#define TEMPERATURE_MIN 0x0320
+/// Maximum color temperature 20000K
+#define TEMPERATURE_MAX 0x4e20
+/// Scale factor for temperature calculations
+#define TEMPERATURE_SCALE_FACTOR 100
+/// Delta UV is hardcoded to 0
+#define DELTA_UV 0
+/// Initial temperature percentage value
+#define TEMPERATURE_PCT_INIT 50
+/// Maximum temperature percentage value
+#define TEMPERATURE_PCT_MAX 100
+/// Temperature level initial value @ref temperature_level
+#define TEMPERATURE_LEVEL_INIT 0
+/// Delay unit value for request for ctl messages in millisecond
+#define REQ_DELAY_MS 50
+
+/// periodic timer handle
+static sl_simple_timer_t ctl_retransmission_timer;
+
+/// periodic timer callback
+static void ctl_retransmission_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+/// temperature level percentage
+static uint8_t temperature_percent = TEMPERATURE_PCT_INIT;
+/// temperature level converted from percentage to actual value, range 0..65535
+static uint16_t temperature_level = TEMPERATURE_LEVEL_INIT;
+/// number of ctl requests to be sent
+static uint8_t ctl_request_count = 0;
+/// ctl transaction identifier
+static uint8_t ctl_trid = 0;
+
+/***************************************************************************//**
+ * This function publishes one light CTL request to change the temperature level
+ * of light(s) in the group. Global variable temperature_level holds the latest
+ * desired light temperature level.
+ * The CTL request also send lightness_level which holds the latest desired light
+ * lightness level and Delta UV which is hardcoded to 0 for this application.
+ *
+ * param[in] retrans Indicates if this is the first request or a retransmission,
+ * possible values are 0 = first request, 1 = retransmission.
+ *
+ * @note This application sends multiple ctl requests for each
+ * medium button press to improve reliability.
+ * The transaction ID is not incremented in case of a retransmission.
+ ******************************************************************************/
+static void send_ctl_request(uint8_t retrans)
+{
+ struct mesh_generic_request req;
+ sl_status_t sc;
+
+ req.kind = mesh_lighting_request_ctl;
+ req.ctl.lightness = sl_btmesh_get_lightness();
+ req.ctl.temperature = temperature_level;
+ req.ctl.deltauv = DELTA_UV; //hardcoded delta uv
+
+ // Increment transaction ID for each request, unless it's a retransmission
+ if (retrans == 0) {
+ ctl_trid++;
+ }
+
+ // Delay for the request is calculated so that the last request will have
+ // a zero delay and each of the previous request have delay that increases
+ // in 50 ms steps. For example, when using three ctl requests
+ // per button press the delays are set as 100, 50, 0 ms
+ uint16_t delay = (ctl_request_count - 1) * REQ_DELAY_MS;
+
+ sc = mesh_lib_generic_client_publish(MESH_LIGHTING_CTL_CLIENT_MODEL_ID,
+ BTMESH_CTL_CLIENT_MAIN,
+ ctl_trid,
+ &req,
+ IMMEDIATE, // transition
+ delay,
+ NO_FLAGS // flags
+ );
+
+ if (sc == SL_STATUS_OK) {
+ log_info("CTL request sent, trid = %u, delay = %u" NL, ctl_trid, delay);
+ } else {
+ log_btmesh_status_f(sc, "CTL Client Publish failed" NL);
+ }
+
+ // Keep track of how many requests has been sent
+ if (ctl_request_count > 0) {
+ ctl_request_count--;
+ }
+}
+
+/*******************************************************************************
+ * This function change the color temperature and sends it to the server.
+ *
+ * @param[in] change_percentage Defines the color temperature percentage change,
+ * possible values are -100% - + 100%.
+ *
+ ******************************************************************************/
+void sl_btmesh_change_temperature(int8_t change_percentage)
+{
+ // Adjust light brightness, using Light Lightness model
+ if (change_percentage > 0) {
+ temperature_percent += change_percentage;
+ if (temperature_percent > TEMPERATURE_PCT_MAX) {
+#if (SL_BTMESH_CTL_CLIENT_TEMPERATURE_WRAP_ENABLED_CFG_VAL != 0)
+ temperature_percent = 0;
+#else
+ temperature_percent = TEMPERATURE_PCT_MAX;
+#endif
+ }
+ } else {
+ if (temperature_percent < (-change_percentage)) {
+#if (SL_BTMESH_CTL_CLIENT_TEMPERATURE_WRAP_ENABLED_CFG_VAL != 0)
+ temperature_percent = TEMPERATURE_PCT_MAX;
+#else
+ temperature_percent = 0;
+#endif
+ } else {
+ temperature_percent += change_percentage;
+ }
+ }
+
+ sl_btmesh_set_temperature(temperature_percent);
+}
+
+/*******************************************************************************
+ * This function change the temperature and send it to the server.
+ *
+ * @param[in] new_color_temperature_percentage Defines new color temperature
+ * value as percentage.
+ * Valid values 0-100 %
+ *
+ ******************************************************************************/
+void sl_btmesh_set_temperature(uint8_t new_color_temperature_percentage)
+{
+ // Adjust light temperature, using Light CTL model
+ if (new_color_temperature_percentage <= TEMPERATURE_PCT_MAX) {
+ temperature_percent = new_color_temperature_percentage;
+ } else {
+ return;
+ }
+
+ // Using square of percentage to change temperature more uniformly
+ // just for demonstration
+ temperature_level = TEMPERATURE_MIN \
+ + (temperature_percent * temperature_percent \
+ / TEMPERATURE_PCT_MAX) \
+ * (TEMPERATURE_MAX - TEMPERATURE_MIN) \
+ / TEMPERATURE_SCALE_FACTOR;
+ log("Set temperature to %u %% / level %u K" NL,
+ temperature_percent * temperature_percent / TEMPERATURE_PCT_MAX,
+ temperature_level);
+
+ // Request is sent multiple times to improve reliability
+ ctl_request_count = SL_BTMESH_CTL_CLIENT_RETRANSMISSION_COUNT_CFG_VAL;
+
+ send_ctl_request(0); //Send the first request
+
+ // If there are more requests to send, start a repeating soft timer
+ // to trigger retransmission of the request after 50 ms delay
+ if (ctl_request_count > 0) {
+ sl_status_t sc = sl_simple_timer_start(&ctl_retransmission_timer,
+ SL_BTMESH_CTL_CLIENT_RETRANSMISSION_TIMEOUT_CFG_VAL,
+ ctl_retransmission_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic timer");
+ }
+}
+
+/***************************************************************************//**
+ * Timer Callbacks
+ * @param[in] handle pointer to handle instance
+ * @param[in] data pointer to input data
+ ******************************************************************************/
+static void ctl_retransmission_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+
+ send_ctl_request(1); // Retransmit ctl message
+ // Stop retransmission timer if it was the last attempt
+ if (ctl_request_count == 0) {
+ sl_status_t sc = sl_simple_timer_stop(&ctl_retransmission_timer);
+ app_assert_status_f(sc, "Failed to stop periodic timer");
+ }
+}
+/** @} (end addtogroup Lighting) */
diff --git a/app/btmesh/common/btmesh_ctl_client/sl_btmesh_ctl_client.h b/app/btmesh/common/btmesh_ctl_client/sl_btmesh_ctl_client.h
new file mode 100644
index 00000000000..bb767361876
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_client/sl_btmesh_ctl_client.h
@@ -0,0 +1,53 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_ctl_client.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_CTL_CLIENT_H
+#define SL_BTMESH_CTL_CLIENT_H
+
+/*******************************************************************************
+ * This function change the color temperature and sends it to the server.
+ *
+ * @param[in] change_percentage Defines the color temperature percentage change,
+ * possible values are -100% - + 100%.
+ *
+ ******************************************************************************/
+void sl_btmesh_change_temperature(int8_t change_percentage);
+
+/*******************************************************************************
+ * This function change the temperature and send it to the server.
+ *
+ * @param[in] new_color_temperature_percentage Defines new color temperature
+ * value as percentage.
+ * Valid values 0-100 %
+ *
+ ******************************************************************************/
+void sl_btmesh_set_temperature(uint8_t new_color_temperature_percentage);
+
+#endif // SL_BTMESH_CTL_CLIENT_H
diff --git a/app/btmesh/common/btmesh_ctl_server/btmesh_ctl_server.dcd b/app/btmesh/common/btmesh_ctl_server/btmesh_ctl_server.dcd
new file mode 100644
index 00000000000..3a5fbb56bb0
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_server/btmesh_ctl_server.dcd
@@ -0,0 +1,18 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1303", "name":"Light CTL Server"},
+ {"mid":"0x1304", "name":"Light CTL Setup Server"}
+ ]
+ },
+ {
+ "name": "Temperature",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1002", "name":"Generic Level Server"},
+ {"mid":"0x1306", "name":"Light CTL Temperature Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_ctl_server/config/sl_btmesh_ctl_server_config.h b/app/btmesh/common/btmesh_ctl_server/config/sl_btmesh_ctl_server_config.h
new file mode 100644
index 00000000000..6b766004a42
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_server/config/sl_btmesh_ctl_server_config.h
@@ -0,0 +1,93 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_CTL_SERVER_CONFIG_H
+#define SL_BTMESH_CTL_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// CTL Server configuration
+
+// Timeout [ms] for saving States of the model to NVM.
+// Default: 5000
+// Timeout [ms] for saving States of the model to NVM.
+#define SL_BTMESH_CTL_SERVER_NVM_SAVE_TIME_CFG_VAL (5000)
+
+// PS Key for NVM Page where the States of the Lighting Model are saved.
+// Default: 0x4005
+// PS Key for NVM Page where the States of the Lighting Model are saved.
+#define SL_BTMESH_CTL_SERVER_PS_KEY_CFG_VAL (0x4005)
+
+// Periodicity [ms] for updating the PWM duty cycle during a transition.
+// Default: 1
+// Periodicity [ms] for updating the PWM duty cycle during a transition.
+#define SL_BTMESH_CTL_SERVER_PWM_UPDATE_PERIOD_CFG_VAL (1)
+
+// Periodicity [ms] for updating the UI with temperature & delta UV during a transition.
+// Default: 100
+// Periodicity [ms] for updating the temperature & delta UV values on the UI.
+#define SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL (100)
+
+// Default Color Temperature
+// Default: 6500
+// Default Color Temperature value.
+#define SL_BTMESH_CTL_SERVER_DEFAULT_TEMPERATURE_CFG_VAL (6500)
+
+// Default Delta UV
+// Default: 0
+// Default Delta UV.
+#define SL_BTMESH_CTL_SERVER_DEFAULT_DELTAUV_CFG_VAL (0)
+
+// Minimum Color Temperature
+// Default: 800
+// Minimum Color Temperature.
+#define SL_BTMESH_CTL_SERVER_MINIMUM_TEMPERATURE_CFG_VAL (800)
+
+// Maximum Color Temperature
+// Default: 800
+// Maximum Color Temperature.
+#define SL_BTMESH_CTL_SERVER_MAXIMUM_TEMPERATURE_CFG_VAL (20000)
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for Lighting Server model specific messages for this component.
+#define SL_BTMESH_CTL_SERVER_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+// The PWM update period shall not be greater than the UI update period
+#if (SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL) < (SL_BTMESH_CTL_SERVER_PWM_UPDATE_PERIOD_CFG_VAL)
+#error "The SL_BTMESH_CTL_SERVER_PWM_UPDATE_PERIOD_CFG_VAL shall be less than SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL."
+#endif
+
+#endif // SL_BTMESH_CTL_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_server.c b/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_server.c
new file mode 100644
index 00000000000..dc8fd3a9a6e
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_server.c
@@ -0,0 +1,2364 @@
+/***************************************************************************//**
+ * @file
+ * @brief Bt Mesh Lighting Client module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+// C Standard Library headers
+#include
+#include
+#include
+
+#include
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "sl_btmesh_generic_model_capi_types.h"
+#include "sl_btmesh_lib.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_ctl_server_config.h"
+#include "sl_btmesh_ctl_server.h"
+#include "sl_btmesh_lighting_server.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup CTL_Server
+ * @{
+ ******************************************************************************/
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+#define scene_server_reset_register(elem_index) \
+ scene_server_reset_register_impl(elem_index)
+#else
+#define scene_server_reset_register(elem_index)
+#endif
+
+#define NO_FLAGS 0 ///< No flags used for message
+#define IMMEDIATE 0 ///< Immediate transition time is 0 seconds
+#define NO_CALLBACK_DATA (void *)NULL ///< Callback has no parameters
+#define HIGH_PRIORITY 0 ///< High Priority
+/// Values greater than max 37200000 are treated as unknown remaining time
+#define UNKNOWN_REMAINING_TIME 40000000
+
+/// Lightbulb state
+static PACKSTRUCT(struct lightbulb_state {
+ // Temperature server
+ uint16_t temperature_current; ///< Current temperature value
+ uint16_t temperature_target; ///< Target temperature value
+ uint16_t temperature_default; ///< Default temperature value
+ uint16_t temperature_min; ///< Minimum temperature value
+ uint16_t temperature_max; ///< Maximum temperature value
+
+ // Delta UV
+ int16_t deltauv_current; ///< Current delta UV value
+ int16_t deltauv_target; ///< Target delta UV value
+ int16_t deltauv_default; ///< Default delta UV value
+
+ // Secondary Generic Level
+ int16_t sec_level_current; ///< Current secondary generic level value
+ int16_t sec_level_target; ///< Target secondary generic level value
+}) lightbulb_state;
+
+static sl_status_t ctl_temperature_update(uint16_t element_index,
+ uint32_t remaining_ms);
+
+/// copy of transition delay parameter, needed for delayed ctl request
+static uint32_t delayed_ctl_trans = 0;
+/// copy of transition delay parameter, needed for delayed temperature request
+static uint32_t delayed_ctl_temperature_trans = 0;
+/// copy of transition delay parameter, needed for
+/// delayed secondary generic level request
+static uint32_t delayed_sec_level_trans = 0;
+/// copy of generic request kind, needed for delayed secondary generic request
+static mesh_generic_request_t sec_level_request_kind = mesh_generic_request_level;
+/// copy of move transition parameter for secondary generic request
+static uint32_t move_sec_level_trans = 0;
+/// copy of move delta parameter for secondary generic request
+static int16_t move_sec_level_delta = 0;
+
+// Timer handles
+static sl_simple_timer_t ctl_sec_level_move_timer;
+static sl_simple_timer_t ctl_sec_level_transition_timer;
+static sl_simple_timer_t ctl_temp_transition_timer;
+static sl_simple_timer_t ctl_transition_complete_timer;
+static sl_simple_timer_t ctl_delayed_sec_level_timer;
+static sl_simple_timer_t ctl_delayed_ctl_temperature_timer;
+static sl_simple_timer_t ctl_delayed_ctl_request_timer;
+static sl_simple_timer_t ctl_state_store_timer;
+
+// Timer callbacks
+static void ctl_sec_level_move_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void ctl_sec_level_transition_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void ctl_temp_transition_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void ctl_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void ctl_delayed_sec_level_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void ctl_delayed_ctl_temperature_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void ctl_delayed_ctl_request_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void ctl_state_store_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+/***************************************************************************//**
+ * This function loads the saved light state from Persistent Storage and
+ * copies the data in the global variable lightbulb_state.
+ * If PS key with ID SL_BTMESH_CTL_SERVER_PS_KEY_CFG_VAL does not exist or loading failed,
+ * lightbulb_state is set to zero and some default values are written to it.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightbulb_state_load(void);
+
+/***************************************************************************//**
+ * This function validates the lighbulb_state and change it if it is against
+ * the specification.
+ ******************************************************************************/
+static void lightbulb_state_validate_and_correct(void);
+
+/***************************************************************************//**
+ * This function is called each time the lightbulb state in RAM is changed.
+ * It sets up a soft timer that will save the state in flash after small delay.
+ * The purpose is to reduce amount of unnecessary flash writes.
+ ******************************************************************************/
+static void lightbulb_state_changed(void);
+
+/***************************************************************************//**
+ * This function validates the lighbulb_state and change it if it is against
+ * the specification.
+ ******************************************************************************/
+static void lightbulb_state_validate_and_correct(void);
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_respond to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_respond(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms,
+ uint8_t response_flags);
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_update to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_update(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms);
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_publish to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_publish(uint16_t model_id,
+ uint16_t element_index,
+ mesh_generic_state_t kind);
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_register_handler with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ ******************************************************************************/
+static void generic_server_register_handler(uint16_t model_id,
+ uint16_t elem_index,
+ mesh_lib_generic_server_client_request_cb cb,
+ mesh_lib_generic_server_change_cb ch,
+ mesh_lib_generic_server_recall_cb recall);
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+/***************************************************************************//**
+ * Wrapper for sl_btmesh_scene_server_reset_register with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ * The scene server register shall be reset if the state of the model changes in
+ * order to clear the current scene.
+ * This function is available only if the btmesh_scene_server component is added
+ * to the project.
+ ******************************************************************************/
+static void scene_server_reset_register_impl(uint16_t elem_index);
+#endif
+
+/***************************************************************************//**
+ * \defgroup LightCTL
+ * \brief Light CTL Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightCTL
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to light CTL request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t ctl_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_ctl;
+ current.ctl.lightness = sl_btmesh_get_lightness_current();
+ current.ctl.temperature = lightbulb_state.temperature_current;
+ current.ctl.deltauv = lightbulb_state.deltauv_current;
+
+ target.kind = mesh_lighting_state_ctl;
+ target.ctl.lightness = sl_btmesh_get_lightness_target();
+ target.ctl.temperature = lightbulb_state.temperature_target;
+ target.ctl.deltauv = lightbulb_state.deltauv_target;
+
+ return generic_server_respond(MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update light CTL state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t ctl_update(uint16_t element_index, uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_ctl;
+ current.ctl.lightness = sl_btmesh_get_lightness_current();
+ current.ctl.temperature = lightbulb_state.temperature_current;
+ current.ctl.deltauv = lightbulb_state.deltauv_current;
+
+ target.kind = mesh_lighting_state_ctl;
+ target.ctl.lightness = sl_btmesh_get_lightness_target();
+ target.ctl.temperature = lightbulb_state.temperature_target;
+ target.ctl.deltauv = lightbulb_state.deltauv_target;
+
+ return generic_server_update(MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update light CTL state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t ctl_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+ (void)element_index;
+
+ e = ctl_update(BTMESH_LIGHTING_SERVER_MAIN, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ mesh_lighting_state_ctl);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light CTL model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void ctl_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)server_addr;
+
+ log_info("ctl_request: lightness=%u, color temperature=%u, delta_uv=%d, "
+ "transition=%lu, delay=%u" NL,
+ request->ctl.lightness,
+ request->ctl.temperature,
+ request->ctl.deltauv,
+ transition_ms,
+ delay_ms);
+
+ if ((sl_btmesh_get_lightness_current() == request->ctl.lightness)
+ && (lightbulb_state.temperature_current == request->ctl.temperature)
+ && (lightbulb_state.deltauv_current == request->ctl.deltauv)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (sl_btmesh_get_lightness_current() != request->ctl.lightness) {
+ log_info("Setting lightness to <%u>" NL, request->ctl.lightness);
+ }
+ if (lightbulb_state.temperature_current != request->ctl.temperature) {
+ log_info("Setting color temperature to <%u>" NL, request->ctl.temperature);
+ }
+ if (lightbulb_state.deltauv_current != request->ctl.deltauv) {
+ log_info("Setting delta UV to <%d>" NL, request->ctl.deltauv);
+ }
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ sl_btmesh_set_lightness_current(request->ctl.lightness);
+ sl_btmesh_set_lightness_target(request->ctl.lightness);
+ if (request->ctl.lightness != 0) {
+ sl_btmesh_set_lightness_last(request->ctl.lightness);
+ }
+
+ // update LED PWM duty cycle
+ sl_btmesh_lighting_set_level(sl_btmesh_get_lightness_current(),
+ IMMEDIATE);
+
+ lightbulb_state.temperature_current = request->ctl.temperature;
+ lightbulb_state.temperature_target = request->ctl.temperature;
+ lightbulb_state.deltauv_current = request->ctl.deltauv;
+ lightbulb_state.deltauv_target = request->ctl.deltauv;
+
+ // update LED color temperature
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_current,
+ lightbulb_state.deltauv_current,
+ IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the light change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ sl_btmesh_set_lightness_target(request->ctl.lightness);
+ lightbulb_state.temperature_target = request->ctl.temperature;
+ lightbulb_state.deltauv_target = request->ctl.deltauv;
+ sl_status_t sc = sl_simple_timer_start(&ctl_delayed_ctl_request_timer,
+ delay_ms,
+ ctl_delayed_ctl_request_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed CTL Request timer");
+ // store transition parameter for later use
+ delayed_ctl_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ sl_btmesh_set_lightness_target(request->ctl.lightness);
+ lightbulb_state.temperature_target = request->ctl.temperature;
+ lightbulb_state.deltauv_target = request->ctl.deltauv;
+
+ sl_btmesh_lighting_set_level(sl_btmesh_get_lightness_target(),
+ transition_ms);
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_transition_complete_timer,
+ transition_ms,
+ ctl_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start CTL Transition Complete timer");
+ }
+ lightbulb_state_changed();
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(BTMESH_LIGHTING_SERVER_MAIN);
+ }
+
+ uint32_t remaining_ms = delay_ms + transition_ms;
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ ctl_response(BTMESH_LIGHTING_SERVER_MAIN, client_addr, appkey_index, remaining_ms);
+ }
+ ctl_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN, remaining_ms);
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ mesh_lighting_state_lightness_actual);
+ generic_server_publish(MESH_LIGHTING_CTL_TEMPERATURE_SERVER_MODEL_ID,
+ BTMESH_CTL_SERVER_TEMPERATURE,
+ mesh_lighting_state_ctl_temperature);
+}
+
+/***************************************************************************//**
+ * This function is a handler for light CTL change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void ctl_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (current->kind != mesh_lighting_state_ctl) {
+ // if kind is not 'ctl' then just report the change here
+ log_info("ctl_change, kind %u" NL, current->kind);
+ return;
+ }
+
+ if (sl_btmesh_get_lightness_current() != current->ctl.lightness) {
+ log_info("Lightness update: from %u to %u" NL,
+ lightbulb_state.temperature_current,
+ current->ctl.lightness);
+ sl_btmesh_set_lightness_current(current->ctl.lightness);
+ lightbulb_state_changed();
+ } else {
+ log_info("Lightness update -same value (%u)" NL, current->ctl.lightness);
+ }
+
+ if (lightbulb_state.temperature_current != current->ctl.temperature) {
+ log_info("Color temperature update: from %u to %u" NL,
+ lightbulb_state.temperature_current,
+ current->ctl.temperature);
+ lightbulb_state.temperature_current = current->ctl.temperature;
+ lightbulb_state_changed();
+ } else {
+ log_info("Color temperature update -same value (%u)" NL,
+ lightbulb_state.temperature_current);
+ }
+
+ if (lightbulb_state.deltauv_current != current->ctl.deltauv) {
+ log_info("Delta UV update: from %d to %d" NL,
+ lightbulb_state.deltauv_current, current->ctl.deltauv);
+ lightbulb_state.deltauv_current = current->ctl.deltauv;
+ lightbulb_state_changed();
+ } else {
+ log_info("Delta UV update -same value (%d)" NL,
+ lightbulb_state.deltauv_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light CTL recall event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void ctl_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+ (void)element_index;
+
+ log_info("Light CTL recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ sl_btmesh_set_lightness_target(current->ctl.lightness);
+ lightbulb_state.temperature_target = current->ctl.temperature;
+ lightbulb_state.deltauv_target = current->ctl.deltauv;
+ } else {
+ sl_btmesh_set_lightness_target(target->ctl.lightness);
+ lightbulb_state.temperature_target = target->ctl.temperature;
+ lightbulb_state.deltauv_target = target->ctl.deltauv;
+ }
+
+ // Lightness server is mandatory for CTL, so lightness change is handled
+ // in lightness_recall function and here we handle temperature and deltauv
+ if ((sl_btmesh_get_lightness_current()
+ == sl_btmesh_get_lightness_target())
+ && (lightbulb_state.temperature_current
+ == lightbulb_state.temperature_target)
+ && (lightbulb_state.deltauv_current
+ == lightbulb_state.deltauv_target)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall CTL lightness to %u, color temperature to %u, delta UV to %d "
+ "with transition=%lu ms" NL,
+ sl_btmesh_get_lightness_target(),
+ lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ transition_ms);
+ if (sl_btmesh_get_lightness_current()
+ != sl_btmesh_get_lightness_target()) {
+ sl_btmesh_lighting_set_level(sl_btmesh_get_lightness_target(),
+ transition_ms);
+ }
+ if ((lightbulb_state.temperature_current
+ != lightbulb_state.temperature_target)
+ || (lightbulb_state.deltauv_current
+ != lightbulb_state.deltauv_target)) {
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ transition_ms);
+ }
+ if (transition_ms == IMMEDIATE) {
+ sl_btmesh_set_lightness_current(current->ctl.lightness);
+ lightbulb_state.temperature_current = current->ctl.temperature;
+ lightbulb_state.deltauv_current = current->ctl.deltauv;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_transition_complete_timer,
+ transition_ms,
+ ctl_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start CTL Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+
+ // Lightness substate is updated in lightness_recall, here only temperature
+ // substate is updated, it is needed also for LC recall to not set LC mode
+ // to zero by bindings
+ sl_status_t e;
+ e = ctl_temperature_update(BTMESH_CTL_SERVER_TEMPERATURE, transition_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ mesh_lighting_state_ctl);
+ }
+}
+
+/***************************************************************************//**
+ * This function is called when a light CTL request
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void ctl_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ sl_btmesh_set_lightness_current(sl_btmesh_get_lightness_target());
+ lightbulb_state.temperature_current = lightbulb_state.temperature_target;
+ lightbulb_state.deltauv_current = lightbulb_state.deltauv_target;
+
+ log_info("Transition complete. New lightness is %u, "
+ "new color temperature is %u and new deltauv is %d" NL,
+ sl_btmesh_get_lightness_current(),
+ lightbulb_state.temperature_current,
+ lightbulb_state.deltauv_current);
+
+ lightbulb_state_changed();
+ ctl_update_and_publish(BTMESH_CTL_SERVER_MAIN, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for light CTL request has completed.
+ ******************************************************************************/
+static void delayed_ctl_request(void)
+{
+ log_info("Starting delayed CTL request: lightness %u -> %u, color temperature %u -> %u, "
+ "delta UV %d -> %d, %lu ms" NL,
+ sl_btmesh_get_lightness_current(),
+ sl_btmesh_get_lightness_target(),
+ lightbulb_state.temperature_current,
+ lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_current,
+ lightbulb_state.deltauv_target,
+ delayed_ctl_trans);
+
+ sl_btmesh_lighting_set_level(sl_btmesh_get_lightness_target(),
+ delayed_ctl_trans);
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ delayed_ctl_trans);
+
+ if (delayed_ctl_trans == 0) {
+ // no transition delay, update state immediately
+ sl_btmesh_set_lightness_current(sl_btmesh_get_lightness_target());
+ lightbulb_state.temperature_current = lightbulb_state.temperature_target;
+ lightbulb_state.deltauv_current = lightbulb_state.deltauv_target;
+
+ lightbulb_state_changed();
+ ctl_update_and_publish(BTMESH_CTL_SERVER_MAIN, delayed_ctl_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_transition_complete_timer,
+ delayed_ctl_trans,
+ ctl_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start CTL Transition Complete timer");
+ }
+}
+
+/** @} (end addtogroup LightCTL) */
+
+/***************************************************************************//**
+ * \defgroup LightCTLSetup
+ * \brief Light CTL Setup Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightCTLSetup
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to light CTL setup request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] kind Type of state used in light CTL setup response.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t ctl_setup_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ mesh_generic_state_t kind)
+{
+ struct mesh_generic_state current;
+
+ current.kind = kind;
+
+ switch (kind) {
+ case mesh_lighting_state_ctl_default:
+ current.ctl.lightness = sl_btmesh_get_lightness_default();
+ current.ctl.temperature = lightbulb_state.temperature_default;
+ current.ctl.deltauv = lightbulb_state.deltauv_default;
+ break;
+ case mesh_lighting_state_ctl_temperature_range:
+ current.ctl_temperature_range.min = lightbulb_state.temperature_min;
+ current.ctl_temperature_range.max = lightbulb_state.temperature_max;
+ break;
+ default:
+ break;
+ }
+
+ return generic_server_respond(MESH_LIGHTING_CTL_SETUP_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ NULL,
+ 0,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update light CTL setup state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] kind Type of state used in light CTL setup update.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t ctl_setup_update(uint16_t element_index,
+ mesh_generic_state_t kind)
+{
+ struct mesh_generic_state current;
+
+ current.kind = kind;
+
+ switch (kind) {
+ case mesh_lighting_state_ctl_default:
+ current.ctl.lightness = sl_btmesh_get_lightness_default();
+ current.ctl.temperature = lightbulb_state.temperature_default;
+ current.ctl.deltauv = lightbulb_state.deltauv_default;
+ break;
+ case mesh_lighting_state_ctl_temperature_range:
+ current.ctl_temperature_range.min = lightbulb_state.temperature_min;
+ current.ctl_temperature_range.max = lightbulb_state.temperature_max;
+ break;
+ default:
+ break;
+ }
+
+ return generic_server_update(MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ NULL,
+ 0);
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light CTL setup model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void ctl_setup_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+ (void)transition_ms;
+ (void)delay_ms;
+
+ mesh_generic_state_t kind = mesh_generic_state_last;
+ switch (request->kind) {
+ case mesh_lighting_request_ctl_default:
+ kind = mesh_lighting_state_ctl_default;
+ log_info("ctl_setup_request: state=ctl_default, default lightness=%u, "
+ "default color temperature=%u, default delta UV=%d" NL,
+ request->ctl.lightness,
+ request->ctl.temperature,
+ request->ctl.deltauv);
+
+ if ((sl_btmesh_get_lightness_default() == request->ctl.lightness)
+ && (lightbulb_state.temperature_default == request->ctl.temperature)
+ && (lightbulb_state.deltauv_default == request->ctl.deltauv)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (sl_btmesh_get_lightness_default() != request->ctl.lightness) {
+ log_info("Setting default lightness to <%u>" NL, request->ctl.lightness);
+ sl_btmesh_set_lightness_default(request->ctl.lightness);
+ }
+ if (lightbulb_state.temperature_default != request->ctl.temperature) {
+ log_info("Setting default color temperature to <%u>" NL,
+ request->ctl.temperature);
+ lightbulb_state.temperature_default = request->ctl.temperature;
+ }
+ if (lightbulb_state.deltauv_default != request->ctl.deltauv) {
+ log_info("Setting default delta UV to <%d>" NL, request->ctl.deltauv);
+ lightbulb_state.deltauv_default = request->ctl.deltauv;
+ }
+ lightbulb_state_changed();
+ }
+ break;
+
+ case mesh_lighting_request_ctl_temperature_range:
+ kind = mesh_lighting_state_ctl_temperature_range;
+ log_info("ctl_setup_request: state=ctl_temperature_range, "
+ "min color temperature=%u, max color temperature=%u" NL,
+ request->ctl_temperature_range.min,
+ request->ctl_temperature_range.max);
+
+ if ((lightbulb_state.temperature_min
+ == request->ctl_temperature_range.min)
+ && (lightbulb_state.temperature_max
+ == request->ctl_temperature_range.max)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (lightbulb_state.temperature_min
+ != request->ctl_temperature_range.min) {
+ log_info("Setting min color temperature to <%u>" NL,
+ request->ctl_temperature_range.min);
+ lightbulb_state.temperature_min = request->ctl_temperature_range.min;
+ }
+ if (lightbulb_state.temperature_max
+ != request->ctl_temperature_range.max) {
+ log_info("Setting max color temperature to <%u>" NL,
+ request->ctl_temperature_range.max);
+ lightbulb_state.temperature_max = request->ctl_temperature_range.max;
+ }
+ lightbulb_state_changed();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ ctl_setup_response(element_index, client_addr, appkey_index, kind);
+ } else {
+ ctl_setup_update(element_index, kind);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light CTL setup change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void ctl_setup_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ switch (current->kind) {
+ case mesh_lighting_state_ctl_default:
+ if (sl_btmesh_get_lightness_default() != current->ctl.lightness) {
+ log_info("Default lightness update: from %u to %u" NL,
+ sl_btmesh_get_lightness_default(),
+ current->ctl.lightness);
+ sl_btmesh_set_lightness_default(current->ctl.lightness);
+ lightbulb_state_changed();
+ } else {
+ log_info("Default lightness update -same value (%u)" NL,
+ sl_btmesh_get_lightness_default());
+ }
+
+ if (lightbulb_state.temperature_default != current->ctl.temperature) {
+ log_info("Default color temperature change: from %u to %u" NL,
+ lightbulb_state.temperature_default,
+ current->ctl.temperature);
+ lightbulb_state.temperature_default = current->ctl.temperature;
+ lightbulb_state_changed();
+ } else {
+ log_info("Default color temperature update -same value (%u)" NL,
+ lightbulb_state.temperature_default);
+ }
+
+ if (lightbulb_state.deltauv_default != current->ctl.deltauv) {
+ log_info("Default delta UV change: from %d to %d" NL,
+ lightbulb_state.deltauv_default,
+ current->ctl.deltauv);
+ lightbulb_state.deltauv_default = current->ctl.deltauv;
+ lightbulb_state_changed();
+ } else {
+ log_info("Default delta UV update -same value (%d)" NL,
+ lightbulb_state.deltauv_default);
+ }
+
+ break;
+
+ case mesh_lighting_state_ctl_temperature_range:
+ if (lightbulb_state.temperature_min
+ != current->ctl_temperature_range.min) {
+ log_info("Min color temperature update: from %u to %u" NL,
+ lightbulb_state.temperature_min,
+ current->ctl_temperature_range.min);
+ lightbulb_state.temperature_min = current->ctl_temperature_range.min;
+ lightbulb_state_changed();
+ } else {
+ log_info("Min color temperature update -same value (%u)" NL,
+ lightbulb_state.temperature_min);
+ }
+
+ if (lightbulb_state.temperature_max
+ != current->ctl_temperature_range.max) {
+ log_info("Max color temperature update: from %u to %u" NL,
+ lightbulb_state.temperature_max,
+ current->ctl_temperature_range.max);
+ lightbulb_state.temperature_max = current->ctl_temperature_range.max;
+ lightbulb_state_changed();
+ } else {
+ log_info("Max color temperature update -same value (%u)" NL,
+ lightbulb_state.temperature_max);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup LightCTLSetup) */
+
+/***************************************************************************//**
+ * \defgroup LightCTLTemperature
+ * \brief Light CTL Temperature Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightCTLTemperature
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to light CTL temperature request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t ctl_temperature_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_ctl_temperature;
+ current.ctl_temperature.temperature = lightbulb_state.temperature_current;
+ current.ctl_temperature.deltauv = lightbulb_state.deltauv_current;
+
+ target.kind = mesh_lighting_state_ctl_temperature;
+ target.ctl_temperature.temperature = lightbulb_state.temperature_target;
+ target.ctl_temperature.deltauv = lightbulb_state.deltauv_target;
+
+ return generic_server_respond(MESH_LIGHTING_CTL_TEMPERATURE_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update light CTL temperature state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t ctl_temperature_update(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_ctl_temperature;
+ current.ctl_temperature.temperature = lightbulb_state.temperature_current;
+ current.ctl_temperature.deltauv = lightbulb_state.deltauv_current;
+
+ target.kind = mesh_lighting_state_ctl_temperature;
+ target.ctl_temperature.temperature = lightbulb_state.temperature_target;
+ target.ctl_temperature.deltauv = lightbulb_state.deltauv_target;
+
+ return generic_server_update(MESH_LIGHTING_CTL_TEMPERATURE_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update light CTL temperature state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t ctl_temperature_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+ (void)element_index;
+
+ e = ctl_temperature_update(BTMESH_CTL_SERVER_TEMPERATURE, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_LIGHTING_CTL_TEMPERATURE_SERVER_MODEL_ID,
+ BTMESH_CTL_SERVER_TEMPERATURE,
+ mesh_lighting_state_ctl_temperature);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light CTL temperature model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void ctl_temperature_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)server_addr;
+
+ log_info("ctl_temperature_request: color temperature=%u, delta UV=%d, "
+ "transition=%lu, delay=%u" NL,
+ request->ctl_temperature.temperature,
+ request->ctl_temperature.deltauv,
+ transition_ms, delay_ms);
+
+ if ((lightbulb_state.temperature_current
+ == request->ctl_temperature.temperature)
+ && (lightbulb_state.deltauv_current
+ == request->ctl_temperature.deltauv)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (lightbulb_state.temperature_current
+ != request->ctl_temperature.temperature) {
+ log_info("Setting color temperature to <%u>" NL,
+ request->ctl_temperature.temperature);
+ }
+ if (lightbulb_state.deltauv_current != request->ctl_temperature.deltauv) {
+ log_info("Setting delta UV to <%d>" NL,
+ request->ctl_temperature.deltauv);
+ }
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.temperature_current = request->ctl_temperature.temperature;
+ lightbulb_state.temperature_target = request->ctl_temperature.temperature;
+ lightbulb_state.deltauv_current = request->ctl_temperature.deltauv;
+ lightbulb_state.deltauv_target = request->ctl_temperature.deltauv;
+
+ // update LED color temperature
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_current,
+ lightbulb_state.deltauv_current,
+ IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the temperature change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.temperature_target = request->ctl_temperature.temperature;
+ lightbulb_state.deltauv_target = request->ctl_temperature.deltauv;
+ sl_status_t sc = sl_simple_timer_start(&ctl_delayed_ctl_temperature_timer,
+ delay_ms,
+ ctl_delayed_ctl_temperature_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Temperature timer");
+ // store transition parameter for later use
+ delayed_ctl_temperature_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.temperature_target = request->ctl_temperature.temperature;
+ lightbulb_state.deltauv_target = request->ctl_temperature.deltauv;
+
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_temp_transition_timer,
+ transition_ms,
+ ctl_temp_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Temp Transition timer");
+ }
+ lightbulb_state_changed();
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(BTMESH_CTL_SERVER_TEMPERATURE);
+ }
+
+ uint32_t remaining_ms = delay_ms + transition_ms;
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ ctl_temperature_response(BTMESH_CTL_SERVER_TEMPERATURE,
+ client_addr,
+ appkey_index,
+ remaining_ms);
+ }
+ ctl_temperature_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE, remaining_ms);
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ mesh_lighting_state_ctl);
+ generic_server_publish(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ BTMESH_CTL_SERVER_TEMPERATURE,
+ mesh_generic_state_level);
+}
+
+/***************************************************************************//**
+ * This function is a handler for light CTL temperature change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void ctl_temperature_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (lightbulb_state.temperature_current
+ != current->ctl_temperature.temperature) {
+ log_info("Color temperature update: from %u to %u" NL,
+ lightbulb_state.temperature_current,
+ current->ctl_temperature.temperature);
+ lightbulb_state.temperature_current = current->ctl_temperature.temperature;
+ lightbulb_state_changed();
+ } else {
+ log_info("Color temperature update -same value (%u)" NL,
+ lightbulb_state.temperature_current);
+ }
+
+ if (lightbulb_state.deltauv_current != current->ctl_temperature.deltauv) {
+ log_info("Delta UV update: from %d to %d" NL,
+ lightbulb_state.deltauv_current,
+ current->ctl_temperature.deltauv);
+ lightbulb_state.deltauv_current = current->ctl_temperature.deltauv;
+ lightbulb_state_changed();
+ } else {
+ log_info("Delta UV update -same value (%d)" NL, lightbulb_state.deltauv_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light CTL temperature recall event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void ctl_temperature_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+ (void)element_index;
+
+ log_info("CTL color temperature recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.temperature_target = current->ctl_temperature.temperature;
+ lightbulb_state.deltauv_target = current->ctl_temperature.deltauv;
+ } else {
+ lightbulb_state.temperature_target = target->ctl_temperature.temperature;
+ lightbulb_state.deltauv_target = target->ctl_temperature.deltauv;
+ }
+
+ if ((lightbulb_state.temperature_current
+ == lightbulb_state.temperature_target)
+ && (lightbulb_state.deltauv_current
+ == lightbulb_state.deltauv_target)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall CTL color temperature to %u, delta UV to %d with "
+ "transition=%lu ms" NL,
+ lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ transition_ms);
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ transition_ms);
+
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.temperature_current = current->ctl_temperature.temperature;
+ lightbulb_state.deltauv_current = current->ctl_temperature.deltauv;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_temp_transition_timer,
+ transition_ms,
+ ctl_temp_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Temp Transition timer");
+ }
+ lightbulb_state_changed();
+ }
+
+ ctl_temperature_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE, transition_ms);
+}
+
+/***************************************************************************//**
+ * This function is called when a light CTL temperature request
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void ctl_temperature_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.temperature_current = lightbulb_state.temperature_target;
+ lightbulb_state.deltauv_current = lightbulb_state.deltauv_target;
+
+ log_info("Transition complete. New color temperature is %u "
+ "and new delta UV is %d" NL,
+ lightbulb_state.temperature_current,
+ lightbulb_state.deltauv_current);
+
+ lightbulb_state_changed();
+ ctl_temperature_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for light CTL temperature request
+ * has completed.
+ ******************************************************************************/
+static void delayed_ctl_temperature_request(void)
+{
+ log_info("Starting delayed CTL color temperature request: "
+ "color temperature %u -> %u, delta UV %d -> %d, %lu ms" NL,
+ lightbulb_state.temperature_current,
+ lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_current,
+ lightbulb_state.deltauv_target,
+ delayed_ctl_temperature_trans);
+
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ delayed_ctl_temperature_trans);
+
+ if (delayed_ctl_temperature_trans == 0) {
+ // no transition delay, update state immediately
+ lightbulb_state.temperature_current = lightbulb_state.temperature_target;
+ lightbulb_state.deltauv_current = lightbulb_state.deltauv_target;
+
+ lightbulb_state_changed();
+ ctl_temperature_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE,
+ delayed_ctl_temperature_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_temp_transition_timer,
+ delayed_ctl_temperature_trans,
+ ctl_temp_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Temp Transition timer");
+ }
+}
+
+/** @} (end addtogroup LightCTLTemperature) */
+
+/***************************************************************************//**
+ * \defgroup SecGenericLevel
+ * \brief Generic Level Server model on secondary element.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup SecGenericLevel
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Convert level to temperature.
+ *
+ * @param[in] level Level to convert.
+ *
+ * @return Temperature converted from level.
+ ******************************************************************************/
+static uint16_t level_to_temperature(int16_t level)
+{
+ return lightbulb_state.temperature_min
+ + (uint32_t)(level + (int32_t)32768)
+ * (lightbulb_state.temperature_max - lightbulb_state.temperature_min)
+ / 65535;
+}
+
+/***************************************************************************//**
+ * Convert temperature to level.
+ *
+ * @param[in] temperature Temperature to convert.
+ *
+ * @return Level converted from temperature.
+ ******************************************************************************/
+static int16_t temperature_to_level(uint16_t temperature)
+{
+ return (temperature - lightbulb_state.temperature_min)
+ * (uint32_t)65535
+ / (lightbulb_state.temperature_max - lightbulb_state.temperature_min)
+ - (int32_t)32768;
+}
+
+/***************************************************************************//**
+ * Response to generic level request on secondary element.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t sec_level_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_level;
+ current.level.level = lightbulb_state.sec_level_current;
+
+ target.kind = mesh_generic_state_level;
+ target.level.level = lightbulb_state.sec_level_target;
+
+ return generic_server_respond(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update generic level state on secondary element.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t sec_level_update(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_level;
+ current.level.level = lightbulb_state.sec_level_current;
+
+ target.kind = mesh_generic_state_level;
+ target.level.level = lightbulb_state.sec_level_target;
+
+ return generic_server_update(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update generic level state on secondary element
+ * and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t sec_level_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+
+ e = sec_level_update(element_index, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_level);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * Schedule next generic level move request on secondary element.
+ *
+ * @param[in] remaining_delta The remaining level delta to the target state.
+ ******************************************************************************/
+static void sec_level_move_schedule_next_request(int32_t remaining_delta)
+{
+ uint32_t transition_ms = 0;
+ if (abs(remaining_delta) < abs(move_sec_level_delta)) {
+ transition_ms = (uint32_t)(((int64_t)move_sec_level_trans * remaining_delta)
+ / move_sec_level_delta);
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_current,
+ transition_ms);
+ } else {
+ int16_t next_level = lightbulb_state.sec_level_current
+ + move_sec_level_delta;
+ transition_ms = move_sec_level_trans;
+ sl_btmesh_ctl_set_temperature_deltauv_level(level_to_temperature(next_level),
+ lightbulb_state.deltauv_current,
+ move_sec_level_trans);
+ }
+ sl_status_t sc = sl_simple_timer_start(&ctl_sec_level_move_timer,
+ transition_ms,
+ ctl_sec_level_move_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Sec Level timer");
+}
+
+/***************************************************************************//**
+ * Handle generic level move request on secondary element.
+ ******************************************************************************/
+static void sec_level_move_request(void)
+{
+ log_info("Secondary level move: level %d -> %d, delta %d in %lu ms" NL,
+ lightbulb_state.sec_level_current,
+ lightbulb_state.sec_level_target,
+ move_sec_level_delta,
+ move_sec_level_trans);
+
+ int32_t remaining_delta = (int32_t)lightbulb_state.sec_level_target
+ - lightbulb_state.sec_level_current;
+
+ if (abs(remaining_delta) < abs(move_sec_level_delta)) {
+ // end of move level as it reached target state
+ lightbulb_state.sec_level_current = lightbulb_state.sec_level_target;
+ lightbulb_state.temperature_current = lightbulb_state.temperature_target;
+ } else {
+ lightbulb_state.sec_level_current += move_sec_level_delta;
+ uint16_t temperature = level_to_temperature(lightbulb_state.sec_level_current);
+ lightbulb_state.temperature_current = temperature;
+ }
+ lightbulb_state_changed();
+ sec_level_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE,
+ UNKNOWN_REMAINING_TIME);
+
+ remaining_delta = (int32_t)lightbulb_state.sec_level_target
+ - lightbulb_state.sec_level_current;
+ if (remaining_delta != 0) {
+ sec_level_move_schedule_next_request(remaining_delta);
+ }
+}
+
+/***************************************************************************//**
+ * Stop generic level move on secondary element.
+ ******************************************************************************/
+static void sec_level_move_stop(void)
+{
+ // Cancel timers
+ sl_status_t sc = sl_simple_timer_stop(&ctl_delayed_sec_level_timer);
+ app_assert_status_f(sc, "Failed to stop Delayed Sec Level timer");
+ sc = sl_simple_timer_stop(&ctl_sec_level_move_timer);
+ app_assert_status_f(sc, "Failed to stop Sec Level Move timer");
+ //Reset move parameters
+ move_sec_level_delta = 0;
+ move_sec_level_trans = 0;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the generic level model
+ * on secondary element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void sec_level_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+
+ uint16_t temperature;
+ uint32_t remaining_ms = UNKNOWN_REMAINING_TIME;
+
+ switch (request->kind) {
+ case mesh_generic_request_level:
+ log_info("sec_level_request (generic): level=%d, transition=%lu, delay=%u" NL,
+ request->level, transition_ms, delay_ms);
+
+ sec_level_move_stop();
+ if (lightbulb_state.sec_level_current == request->level) {
+ log_info("Request for current state received; no op" NL);
+ lightbulb_state.sec_level_target = request->level;
+ } else {
+ log_info("Setting secondary level to <%d>" NL, request->level);
+
+ temperature = level_to_temperature(request->level);
+
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.sec_level_current = request->level;
+ lightbulb_state.sec_level_target = request->level;
+ lightbulb_state.temperature_current = temperature;
+ lightbulb_state.temperature_target = temperature;
+
+ // update LED Temperature
+ sl_btmesh_ctl_set_temperature_deltauv_level(temperature,
+ lightbulb_state.deltauv_current,
+ IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.sec_level_target = request->level;
+ lightbulb_state.temperature_target = temperature;
+ sec_level_request_kind = mesh_generic_request_level;
+ sl_status_t sc = sl_simple_timer_start(&ctl_delayed_sec_level_timer,
+ delay_ms,
+ ctl_delayed_sec_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Sec Level timer");
+ // store transition parameter for later use
+ delayed_sec_level_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.sec_level_target = request->level;
+ lightbulb_state.temperature_target = temperature;
+ sl_btmesh_ctl_set_temperature_deltauv_level(temperature,
+ lightbulb_state.deltauv_current,
+ transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_sec_level_transition_timer,
+ delayed_sec_level_trans,
+ ctl_sec_level_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Sec Level Transition timer");
+ }
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+
+ remaining_ms = delay_ms + transition_ms;
+ break;
+
+ case mesh_generic_request_level_move: {
+ log_info("sec_level_request (move): delta=%d, transition=%lu, delay=%u" NL,
+ request->level, transition_ms, delay_ms);
+ // Store move parameters
+ move_sec_level_delta = request->level;
+ move_sec_level_trans = transition_ms;
+
+ int16_t requested_level = 0;
+ if (move_sec_level_delta > 0) {
+ requested_level = 0x7FFF; // Max level value
+ } else if (move_sec_level_delta < 0) {
+ requested_level = 0x8000; // Min level value
+ }
+
+ if (lightbulb_state.sec_level_current == requested_level) {
+ log_info("Request for current state received; no op" NL);
+ lightbulb_state.sec_level_target = requested_level;
+ remaining_ms = IMMEDIATE;
+ } else {
+ log_info("Setting secondary level to <%d>" NL, requested_level);
+
+ temperature = level_to_temperature(requested_level);
+
+ if (delay_ms > 0) {
+ // a delay has been specified for the move. Start a soft timer
+ // that will trigger the move after the given delay
+ // Current state remains as is for now
+ lightbulb_state.sec_level_target = requested_level;
+ lightbulb_state.temperature_target = temperature;
+ sec_level_request_kind = mesh_generic_request_level_move;
+ sl_status_t sc = sl_simple_timer_start(&ctl_delayed_sec_level_timer,
+ delay_ms,
+ ctl_delayed_sec_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Sec Level timer");
+ } else {
+ // no delay so start move
+ lightbulb_state.sec_level_target = requested_level;
+ lightbulb_state.temperature_target = temperature;
+
+ int32_t remaining_delta = (int32_t)lightbulb_state.sec_level_target
+ - lightbulb_state.sec_level_current;
+ sec_level_move_schedule_next_request(remaining_delta);
+ }
+ remaining_ms = UNKNOWN_REMAINING_TIME;
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+ break;
+ }
+
+ case mesh_generic_request_level_halt:
+ log_info("sec_level_request (halt)" NL);
+
+ // Set current state
+ lightbulb_state.temperature_current = sl_btmesh_get_temperature();
+ lightbulb_state.temperature_target = lightbulb_state.temperature_current;
+ lightbulb_state.sec_level_current = temperature_to_level(lightbulb_state.temperature_current);
+ lightbulb_state.sec_level_target = lightbulb_state.sec_level_current;
+ if (delay_ms > 0) {
+ // a delay has been specified for the move halt. Start a soft timer
+ // that will trigger the move halt after the given delay
+ // Current state remains as is for now
+ remaining_ms = delay_ms;
+ sec_level_request_kind = mesh_generic_request_level_halt;
+ sl_status_t sc = sl_simple_timer_start(&ctl_delayed_sec_level_timer,
+ delay_ms,
+ ctl_delayed_sec_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Sec Level timer");
+ } else {
+ sec_level_move_stop();
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_current,
+ lightbulb_state.deltauv_current,
+ IMMEDIATE);
+ remaining_ms = IMMEDIATE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ lightbulb_state_changed();
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ sec_level_response(element_index, client_addr, appkey_index, remaining_ms);
+ }
+ sec_level_update_and_publish(element_index, remaining_ms);
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_CTL_TEMPERATURE_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_ctl_temperature);
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic level change event
+ * on secondary element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void sec_level_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (lightbulb_state.sec_level_current != current->level.level) {
+ log_info("Secondary level update: from %d to %d" NL,
+ lightbulb_state.sec_level_current,
+ current->level.level);
+ lightbulb_state.sec_level_current = current->level.level;
+ lightbulb_state_changed();
+ sec_level_move_stop();
+ } else {
+ log_info("Secondary level update -same value (%d)" NL,
+ lightbulb_state.sec_level_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic level recall event
+ * on secondary element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void sec_level_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+ (void)element_index;
+
+ log_info("Secondary Generic Level recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.sec_level_target = current->level.level;
+ } else {
+ lightbulb_state.sec_level_target = target->level.level;
+ }
+
+ if (lightbulb_state.sec_level_current == lightbulb_state.sec_level_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall secondary level to %d with transition=%lu ms" NL,
+ lightbulb_state.sec_level_target,
+ transition_ms);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.sec_level_current = current->level.level;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_sec_level_transition_timer,
+ transition_ms,
+ ctl_sec_level_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Sec Level Transition timer");
+ }
+ lightbulb_state_changed();
+ }
+}
+
+/***************************************************************************//**
+ * This function is called when a generic level request on secondary element
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void sec_level_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.sec_level_current = lightbulb_state.sec_level_target;
+ lightbulb_state.temperature_current = lightbulb_state.temperature_target;
+
+ log_info("Transition complete. New secondary level is %d" NL,
+ lightbulb_state.sec_level_current);
+
+ lightbulb_state_changed();
+ sec_level_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for generic level request
+ * on secondary element has completed.
+ ******************************************************************************/
+static void delayed_sec_level_request(void)
+{
+ log_info("Starting delayed secondary level request: level %d -> %d, %lu ms" NL,
+ lightbulb_state.sec_level_current,
+ lightbulb_state.sec_level_target,
+ delayed_sec_level_trans);
+
+ switch (sec_level_request_kind) {
+ case mesh_generic_request_level:
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_current,
+ delayed_sec_level_trans);
+
+ if (delayed_sec_level_trans == 0) {
+ // no transition delay, update state immediately
+ lightbulb_state.sec_level_current = lightbulb_state.sec_level_target;
+ lightbulb_state.temperature_current = lightbulb_state.temperature_target;
+
+ lightbulb_state_changed();
+ sec_level_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE,
+ delayed_sec_level_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&ctl_sec_level_transition_timer,
+ delayed_sec_level_trans,
+ ctl_sec_level_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Sec Level Transition timer");
+ }
+ break;
+
+ case mesh_generic_request_level_move:
+ sec_level_move_schedule_next_request((int32_t)lightbulb_state.sec_level_target
+ - lightbulb_state.sec_level_current);
+ sec_level_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE,
+ UNKNOWN_REMAINING_TIME);
+ break;
+
+ case mesh_generic_request_level_halt:
+ // Set current state
+ lightbulb_state.temperature_current = sl_btmesh_get_temperature();
+ lightbulb_state.temperature_target = lightbulb_state.temperature_current;
+ lightbulb_state.sec_level_current = temperature_to_level(lightbulb_state.temperature_current);
+ lightbulb_state.sec_level_target = lightbulb_state.sec_level_current;
+ sec_level_move_stop();
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_current,
+ lightbulb_state.deltauv_current,
+ IMMEDIATE);
+ sec_level_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE, IMMEDIATE);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup SecGenericLevel) */
+
+/***************************************************************************//**
+ * Initialization of the models supported by this node.
+ * This function registers callbacks for each of the supported models.
+ ******************************************************************************/
+static void init_ctl_models(void)
+{
+ generic_server_register_handler(MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ BTMESH_CTL_SERVER_MAIN,
+ ctl_request,
+ ctl_change,
+ ctl_recall);
+
+ generic_server_register_handler(MESH_LIGHTING_CTL_SETUP_SERVER_MODEL_ID,
+ BTMESH_CTL_SERVER_MAIN,
+ ctl_setup_request,
+ ctl_setup_change,
+ NULL);
+
+ generic_server_register_handler(MESH_LIGHTING_CTL_TEMPERATURE_SERVER_MODEL_ID,
+ BTMESH_CTL_SERVER_TEMPERATURE,
+ ctl_temperature_request,
+ ctl_temperature_change,
+ ctl_temperature_recall);
+
+ generic_server_register_handler(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ BTMESH_CTL_SERVER_TEMPERATURE,
+ sec_level_request,
+ sec_level_change,
+ sec_level_recall);
+}
+
+/***************************************************************************//**
+ * This function loads the saved light state from Persistent Storage and
+ * copies the data in the global variable lightbulb_state.
+ * If PS key with ID SL_BTMESH_CTL_SERVER_PS_KEY_CFG_VAL does not exist or loading failed,
+ * lightbulb_state is set to zero and some default values are written to it.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightbulb_state_load(void)
+{
+ sl_status_t sc;
+ size_t ps_len = 0;
+ struct lightbulb_state ps_data;
+
+ sc = sl_bt_nvm_load(SL_BTMESH_CTL_SERVER_PS_KEY_CFG_VAL,
+ sizeof(ps_data),
+ &ps_len,
+ (uint8_t *)&ps_data);
+
+ // Set default values if ps_load fail or size of lightbulb_state has changed
+ if ((sc != SL_STATUS_OK) || (ps_len != sizeof(struct lightbulb_state))) {
+ memset(&lightbulb_state, 0, sizeof(struct lightbulb_state));
+ lightbulb_state.temperature_default = SL_BTMESH_CTL_SERVER_DEFAULT_TEMPERATURE_CFG_VAL;
+ lightbulb_state.temperature_min = SL_BTMESH_CTL_SERVER_MINIMUM_TEMPERATURE_CFG_VAL;
+ lightbulb_state.temperature_max = SL_BTMESH_CTL_SERVER_MAXIMUM_TEMPERATURE_CFG_VAL;
+ lightbulb_state.deltauv_default = SL_BTMESH_CTL_SERVER_DEFAULT_DELTAUV_CFG_VAL;
+
+ // Check if default values are valid and correct them if needed
+ lightbulb_state_validate_and_correct();
+
+ if (sc == SL_STATUS_OK) {
+ // The sl_bt_nvm_load call was successful but the size of the loaded data
+ // differs from the expected size therefore error code shall be set
+ sc = SL_STATUS_INVALID_STATE;
+ log_error("CTL server lightbulb state loaded from PS with invalid size, "
+ "use defaults. (expected=%zd,actual=%zd)" NL,
+ sizeof(struct lightbulb_state),
+ ps_len);
+ } else {
+ log_status_error_f(sc,
+ "CTL server lightbulb state load from PS failed "
+ "or nvm is empty, use defaults." NL);
+ }
+ } else {
+ memcpy(&lightbulb_state, &ps_data, ps_len);
+ }
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function saves the current light state in Persistent Storage so that
+ * the data is preserved over reboots and power cycles.
+ * The light state is hold in a global variable lightbulb_state.
+ * A PS key with ID SL_BTMESH_CTL_SERVER_PS_KEY_CFG_VAL is used to store the whole struct.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightbulb_state_store(void)
+{
+ sl_status_t sc;
+
+ sc = sl_bt_nvm_save(SL_BTMESH_CTL_SERVER_PS_KEY_CFG_VAL,
+ sizeof(struct lightbulb_state),
+ (const uint8_t *)&lightbulb_state);
+
+ log_status_error_f(sc, "CTL server lightbulb state store in PS failed." NL);
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function is called each time the lightbulb state in RAM is changed.
+ * It sets up a soft timer that will save the state in flash after small delay.
+ * The purpose is to reduce amount of unnecessary flash writes.
+ ******************************************************************************/
+static void lightbulb_state_changed(void)
+{
+ sl_status_t sc = sl_simple_timer_start(&ctl_state_store_timer,
+ SL_BTMESH_CTL_SERVER_NVM_SAVE_TIME_CFG_VAL,
+ ctl_state_store_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start State Store timer");
+}
+
+/***************************************************************************//**
+ * This function validates the lighbulb_state and change it if it is against
+ * the specification.
+ ******************************************************************************/
+static void lightbulb_state_validate_and_correct(void)
+{
+ if (lightbulb_state.temperature_min < SL_BTMESH_CTL_SERVER_MINIMUM_TEMPERATURE_CFG_VAL) {
+ lightbulb_state.temperature_min = SL_BTMESH_CTL_SERVER_MINIMUM_TEMPERATURE_CFG_VAL;
+ }
+ if (lightbulb_state.temperature_min > SL_BTMESH_CTL_SERVER_MAXIMUM_TEMPERATURE_CFG_VAL) {
+ lightbulb_state.temperature_min = SL_BTMESH_CTL_SERVER_MAXIMUM_TEMPERATURE_CFG_VAL;
+ }
+ if (lightbulb_state.temperature_min > lightbulb_state.temperature_max) {
+ lightbulb_state.temperature_min = lightbulb_state.temperature_max;
+ }
+ if (lightbulb_state.temperature_default < lightbulb_state.temperature_min) {
+ lightbulb_state.temperature_default = lightbulb_state.temperature_min;
+ }
+ if (lightbulb_state.temperature_default > lightbulb_state.temperature_max) {
+ lightbulb_state.temperature_default = lightbulb_state.temperature_max;
+ }
+ if (lightbulb_state.temperature_current < lightbulb_state.temperature_min) {
+ lightbulb_state.temperature_current = lightbulb_state.temperature_min;
+ }
+ if (lightbulb_state.temperature_current > lightbulb_state.temperature_max) {
+ lightbulb_state.temperature_current = lightbulb_state.temperature_max;
+ }
+ if (lightbulb_state.temperature_target < lightbulb_state.temperature_min) {
+ lightbulb_state.temperature_target = lightbulb_state.temperature_min;
+ }
+ if (lightbulb_state.temperature_target > lightbulb_state.temperature_max) {
+ lightbulb_state.temperature_target = lightbulb_state.temperature_max;
+ }
+}
+
+/*******************************************************************************
+ * Lightbulb state initialization.
+ * This is called at each boot if provisioning is already done.
+ * Otherwise this function is called after provisioning is completed.
+ ******************************************************************************/
+void sl_btmesh_ctl_server_init(void)
+{
+ memset(&lightbulb_state, 0, sizeof(struct lightbulb_state));
+
+ lightbulb_state_load();
+
+ // Handle on power up behavior
+ uint32_t transition_ms = sl_btmesh_get_default_transition_time();
+ switch (sl_btmesh_get_lightness_onpowerup()) {
+ case MESH_GENERIC_ON_POWER_UP_STATE_OFF:
+ lightbulb_state.temperature_current = lightbulb_state.temperature_default;
+ lightbulb_state.temperature_target = lightbulb_state.temperature_default;
+ lightbulb_state.deltauv_current = lightbulb_state.deltauv_default;
+ lightbulb_state.deltauv_target = lightbulb_state.deltauv_default;
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_default,
+ lightbulb_state.deltauv_default,
+ IMMEDIATE);
+ break;
+
+ case MESH_GENERIC_ON_POWER_UP_STATE_ON:
+ lightbulb_state.temperature_current = lightbulb_state.temperature_default;
+ lightbulb_state.temperature_target = lightbulb_state.temperature_default;
+ lightbulb_state.deltauv_current = lightbulb_state.deltauv_default;
+ lightbulb_state.deltauv_target = lightbulb_state.deltauv_default;
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_default,
+ lightbulb_state.deltauv_default,
+ IMMEDIATE);
+ break;
+
+ case MESH_GENERIC_ON_POWER_UP_STATE_RESTORE:
+ if (transition_ms > 0
+ && ((lightbulb_state.temperature_target
+ != lightbulb_state.temperature_default)
+ || (lightbulb_state.deltauv_target
+ != lightbulb_state.deltauv_default))) {
+ lightbulb_state.temperature_current = lightbulb_state.temperature_default;
+ lightbulb_state.deltauv_current = lightbulb_state.deltauv_default;
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_current,
+ lightbulb_state.deltauv_current,
+ IMMEDIATE);
+ sl_status_t sc = sl_simple_timer_start(&ctl_temp_transition_timer,
+ transition_ms,
+ ctl_temp_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Temp Transition timer");
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_target,
+ lightbulb_state.deltauv_target,
+ transition_ms);
+ } else {
+ lightbulb_state.temperature_current = lightbulb_state.temperature_target;
+ lightbulb_state.deltauv_current = lightbulb_state.deltauv_target;
+ sl_btmesh_ctl_set_temperature_deltauv_level(lightbulb_state.temperature_current,
+ lightbulb_state.deltauv_current,
+ IMMEDIATE);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ lightbulb_state_changed();
+ init_ctl_models();
+ ctl_setup_update(BTMESH_CTL_SERVER_MAIN, mesh_lighting_state_ctl_default);
+ ctl_setup_update(BTMESH_CTL_SERVER_MAIN,
+ mesh_lighting_state_ctl_temperature_range);
+ ctl_temperature_update_and_publish(BTMESH_CTL_SERVER_TEMPERATURE, IMMEDIATE);
+}
+
+/*******************************************************************************
+ * Handle CTL Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_ctl_server_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_ctl_server_init();
+ break;
+
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_ctl_server_init();
+ }
+ break;
+
+ case sl_btmesh_evt_node_reset_id:
+ sl_bt_nvm_erase(SL_BTMESH_CTL_SERVER_PS_KEY_CFG_VAL);
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * @addtogroup BtmeshWrappers
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_respond to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_respond(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms,
+ uint8_t response_flags)
+{
+ sl_status_t sc = mesh_lib_generic_server_respond(model_id,
+ element_index,
+ client_addr,
+ appkey_index,
+ current,
+ target,
+ remaining_ms,
+ response_flags);
+ log_status_error_f(sc,
+ "CTL server respond failed"
+ "(claddr=0x%04x,mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ client_addr,
+ model_id,
+ element_index,
+ current->kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_update to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_update(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ sl_status_t sc = mesh_lib_generic_server_update(model_id,
+ element_index,
+ current,
+ target,
+ remaining_ms);
+
+ log_status_error_f(sc,
+ "CTL server state update failed "
+ "(mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ model_id,
+ element_index,
+ current->kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_publish to log if the Btmesh API call
+ * results in error. The parameters and the return value of the two functions
+ * are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_publish(uint16_t model_id,
+ uint16_t element_index,
+ mesh_generic_state_t kind)
+{
+ sl_status_t sc;
+
+ sc = mesh_lib_generic_server_publish(model_id, element_index, kind);
+
+ log_btmesh_status_f(sc,
+ "CTL server state publish failed "
+ "(mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ model_id,
+ element_index,
+ kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_register_handler with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ ******************************************************************************/
+static void generic_server_register_handler(uint16_t model_id,
+ uint16_t elem_index,
+ mesh_lib_generic_server_client_request_cb cb,
+ mesh_lib_generic_server_change_cb ch,
+ mesh_lib_generic_server_recall_cb recall)
+{
+ sl_status_t sc = mesh_lib_generic_server_register_handler(model_id,
+ elem_index,
+ cb,
+ ch,
+ recall);
+
+ app_assert_status_f(sc,
+ "CTL server failed to register handlers "
+ "(mdl=0x%04x,elem=%d)",
+ model_id,
+ elem_index);
+}
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+/***************************************************************************//**
+ * Wrapper for sl_btmesh_scene_server_reset_register with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ * The scene server register shall be reset if the state of the model changes in
+ * order to clear the current scene.
+ * This function is available only if the btmesh_scene_server component is added
+ * to the project.
+ ******************************************************************************/
+static void scene_server_reset_register_impl(uint16_t elem_index)
+{
+ sl_status_t sc = sl_btmesh_scene_server_reset_register(elem_index);
+
+ // The function can fail if there is no scene server model in the element or
+ // the btmesh_stack_scene_server component is not present. Both of these
+ // are configuration issues so assert can be used.
+ app_assert_status_f(sc, "CTL server failed to reset scene register.");
+}
+#endif
+
+/** @} (end addtogroup BtmeshWrappers) */
+
+/**************************************************************************//**
+ * Timer Callbacks
+ *****************************************************************************/
+static void ctl_sec_level_move_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // handling of generic level move, update the lightbulb state
+ sec_level_move_request();
+}
+
+static void ctl_sec_level_transition_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a secondary generic level request has completed,
+ //update the lightbulb state
+ sec_level_transition_complete();
+}
+
+static void ctl_temp_transition_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a ctl temperature request has completed,
+ // update the lightbulb state
+ ctl_temperature_transition_complete();
+}
+
+static void ctl_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a ctl request has completed, update the lightbulb state
+ ctl_transition_complete();
+}
+
+static void ctl_delayed_sec_level_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a secondary generic level request has passed,
+ // now process the request
+ delayed_sec_level_request();
+}
+
+static void ctl_delayed_ctl_temperature_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a ctl temperature request has passed, now process the request
+ delayed_ctl_temperature_request();
+}
+
+static void ctl_delayed_ctl_request_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a ctl request has passed, now process the request
+ delayed_ctl_request();
+}
+
+static void ctl_state_store_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // save the lightbulb state
+ lightbulb_state_store();
+}
+
+/** @} (end addtogroup CTL_SERVER) */
diff --git a/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_server.h b/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_server.h
new file mode 100644
index 00000000000..e45be17ca3b
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_server.h
@@ -0,0 +1,61 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_ctl_server.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_CTL_SERVER_H
+#define SL_BTMESH_CTL_SERVER_H
+
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_ctl_signal_transition_handler.h"
+
+// -----------------------------------------------------------------------------
+// Functions which are automatically called when the component is selected
+
+/***************************************************************************//**
+ * Handle CTL Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_ctl_server_on_event(sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * CTL Server initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @return Status of the initialization operation.
+ * Returns bg_err_success (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+void sl_btmesh_ctl_server_init(void);
+
+#endif // SL_BTMESH_CTL_SERVER_H
diff --git a/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_signal_transition_handler.c b/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_signal_transition_handler.c
new file mode 100644
index 00000000000..2d3327555f0
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_signal_transition_handler.c
@@ -0,0 +1,314 @@
+/***************************************************************************//**
+ * @file
+ * @brief Lighting Level Transition Handler Module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+/* C Standard Library headers */
+#include
+#include
+
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#include "sl_btmesh_ctl_server.h"
+#include "sl_btmesh_ctl_signal_transition_handler.h"
+#include "sl_btmesh_ctl_server_config.h"
+
+#define SIGNED_INT_16BIT_MAX_POSITIVE (0x7FFF)
+#define FIXED_POINT_Q15_FRAC_BITS (15)
+#define DELTA_UV_SIGNIFICANT_DIGITS_MUL (100)
+
+/***************************************************************************//**
+ * @addtogroup Lighting Level Transition Handler
+ * @{
+ ******************************************************************************/
+
+#define NO_FLAGS 0 ///< No flags used for message
+#define IMMEDIATE 0 ///< Immediate transition time is 0 seconds
+#define NO_CALLBACK_DATA (void *)NULL // Callback has not parameters
+#define HIGH_PRIORITY 0 // High Priority
+
+/// current temperature level
+static uint16_t current_temperature = SL_BTMESH_CTL_SERVER_DEFAULT_TEMPERATURE_CFG_VAL;
+/// starting level of temperature transition
+static uint16_t start_temperature;
+/// target level of temperature transition
+static uint16_t target_temperature;
+
+/// current delta UV level
+static int16_t current_deltauv;
+/// starting level of delta UV transition
+static int16_t start_deltauv;
+/// target level of delta UV transition
+static int16_t target_deltauv;
+
+/// temperature transition time in timer ticks
+static uint32_t temp_transtime_ticks;
+/// time elapsed from temperature transition start
+static uint32_t temp_transtime_elapsed;
+/// non-zero if temperature transition is active
+static uint8_t temp_transitioning;
+
+static sl_simple_timer_t transition_timer;
+
+// Timer callbacks
+static void transition_timer_cb(sl_simple_timer_t *timer, void *data);
+
+////////////////////////////////////////////////////////////////////////////////
+// Lighting Callbacks //
+////////////////////////////////////////////////////////////////////////////////
+
+SL_WEAK void sl_btmesh_lighting_color_pwm_cb(uint16_t color)
+{
+ (void)color;
+}
+
+SL_WEAK void sl_btmesh_ctl_on_ui_update(uint16_t temperature,
+ uint16_t deltauv)
+{
+ (void)temperature;
+ (void)deltauv;
+}
+
+/***************************************************************************//**
+ * Handler for Transition Timer, which manages LEDs transitions.
+ ******************************************************************************/
+static void transition_timer_cb(sl_simple_timer_t *timer, void *data)
+{
+ (void)data;
+ (void)timer;
+ // Initialize the variable to UI update period in order to trigger a UI update
+ // at the beginning of the transition.
+ static uint16_t time_elapsed_since_ui_update = SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL;
+
+ if (!temp_transitioning) {
+ sl_status_t sc = sl_simple_timer_stop(&transition_timer);
+ app_assert_status_f(sc, "Failed to stop Periodic Level Transition Timer\n");
+ return;
+ } else {
+ temp_transtime_elapsed++;
+
+ if (temp_transtime_elapsed >= temp_transtime_ticks) {
+ // transition complete
+ temp_transitioning = 0;
+ current_temperature = target_temperature;
+ current_deltauv = target_deltauv;
+
+ // Set the variable to UI update period in order to trigger a UI update
+ // at the beginning of the next transition.
+ time_elapsed_since_ui_update = SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL;
+
+ // Trigger a UI update in order to provide the target values at the end
+ // of the current transition
+ sl_btmesh_ctl_on_ui_update(current_temperature, current_deltauv);
+ } else {
+ // calculate current temperature based on elapsed transition time
+ if (target_temperature >= start_temperature) {
+ current_temperature = start_temperature
+ + (target_temperature - start_temperature)
+ * (uint64_t)temp_transtime_elapsed
+ / temp_transtime_ticks;
+ } else {
+ current_temperature = start_temperature
+ - (start_temperature - target_temperature)
+ * (uint64_t)temp_transtime_elapsed
+ / temp_transtime_ticks;
+ }
+
+ if (target_deltauv >= start_deltauv) {
+ current_deltauv = start_deltauv
+ + (target_deltauv - start_deltauv)
+ * (uint64_t)temp_transtime_elapsed
+ / temp_transtime_ticks;
+ } else {
+ current_deltauv = start_deltauv
+ - (start_deltauv - target_deltauv)
+ * (uint64_t)temp_transtime_elapsed
+ / temp_transtime_ticks;
+ }
+
+ // When transition is ongoing generate an event to application once every
+ // SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL ms because the event is used to update
+ // display status and therefore the rate should not be too high
+ time_elapsed_since_ui_update += SL_BTMESH_CTL_SERVER_PWM_UPDATE_PERIOD_CFG_VAL;
+
+ if (SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL <= time_elapsed_since_ui_update) {
+ time_elapsed_since_ui_update -= SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL;
+ sl_btmesh_ctl_on_ui_update(current_temperature, current_deltauv);
+ }
+ }
+ }
+
+ sl_btmesh_lighting_color_pwm_cb(current_temperature);
+}
+
+/*******************************************************************************
+ * Set LED temperature and delta UV in given transition time.
+ *
+ * @param[in] temperature Temperature of color.
+ * @param[in] deltauv Delta UV value.
+ * @param[in] transition_ms Transition time in milliseconds.
+ ******************************************************************************/
+void sl_btmesh_ctl_set_temperature_deltauv_level(uint16_t temperature,
+ int16_t deltauv,
+ uint32_t transition_ms)
+{
+ if (temperature < SL_BTMESH_CTL_SERVER_MINIMUM_TEMPERATURE_CFG_VAL) {
+ temperature = SL_BTMESH_CTL_SERVER_MINIMUM_TEMPERATURE_CFG_VAL;
+ } else if (temperature > SL_BTMESH_CTL_SERVER_MAXIMUM_TEMPERATURE_CFG_VAL) {
+ temperature = SL_BTMESH_CTL_SERVER_MAXIMUM_TEMPERATURE_CFG_VAL;
+ }
+
+ if (transition_ms == 0) {
+ current_temperature = temperature;
+ current_deltauv = deltauv;
+
+ sl_btmesh_lighting_color_pwm_cb(current_temperature);
+
+ /* if a transition was in progress, cancel it */
+ if (temp_transitioning) {
+ temp_transitioning = 0;
+ sl_status_t sc = sl_simple_timer_stop(&transition_timer);
+ app_assert_status_f(sc, "Failed to stop Periodic Level Transition Timer\n");
+ }
+ sl_btmesh_ctl_on_ui_update(current_temperature, current_deltauv);
+ return;
+ }
+
+ temp_transtime_ticks = transition_ms;
+
+ start_temperature = current_temperature;
+ target_temperature = temperature;
+
+ start_deltauv = current_deltauv;
+ target_deltauv = deltauv;
+
+ temp_transtime_elapsed = 0;
+ temp_transitioning = 1;
+
+ // enabling timer IRQ -> the temperature is adjusted in timer interrupt
+ // gradually until target temperature is reached.
+ sl_status_t sc = sl_simple_timer_start(&transition_timer,
+ SL_BTMESH_CTL_SERVER_PWM_UPDATE_PERIOD_CFG_VAL,
+ transition_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic Transition Timer\n");
+
+ return;
+}
+
+/***************************************************************************//**
+ * Utility function to print the delta UV raw value into the passed character
+ * buffer in the X.XX format.
+ *
+ * @param[out] buffer Character buffer where the formatted delta UV
+ * is stored
+ * @param[in] buffer_size Size of the character buffer
+ * @param[in] deltauv_raw Raw value of the Delta UV CTL parameter
+ *
+ * @return same as snprintf
+ ******************************************************************************/
+int sl_btmesh_ctl_server_snprint_deltauv(char *buffer,
+ uint16_t buffer_size,
+ uint16_t deltauv_raw)
+{
+ // Additive variable for rounding to 0.01 precision (2^15 / 100 / 2)
+ const uint32_t round_value = ((1UL << FIXED_POINT_Q15_FRAC_BITS) / 2)
+ / DELTA_UV_SIGNIFICANT_DIGITS_MUL;
+ uint32_t raw = deltauv_raw;
+ uint16_t integer_part, fractional_part;
+ char sign_char;
+
+ // The deltauv_raw is a 2s complement signed fixed point value 16bit (Q15)
+ // which uses 15 bit to code the fractional part therefore:
+ // delta UV raw: 0x8000 => delta UV: -1.0000000 --> -1.00
+ // delta UV raw: 0x7fff => delta UV: +0,9999695 --> ~ +1.00
+
+ if (SIGNED_INT_16BIT_MAX_POSITIVE < raw) {
+ // Convert the negative value to positive and round it
+ raw = ( (1UL << 16) - raw) + round_value;
+
+ // Store that the value was negative
+ sign_char = '-';
+ } else {
+ // Round the raw value to 0.01 precision
+ raw += round_value;
+
+ // Store that the value was not negative
+ sign_char = ' ';
+ }
+
+ // Division by 2^15 to calculate the integer part
+ integer_part = raw >> FIXED_POINT_Q15_FRAC_BITS;
+
+ // Calculate the significant number of fractional decimal digits
+ fractional_part = DELTA_UV_SIGNIFICANT_DIGITS_MUL
+ * (raw & SIGNED_INT_16BIT_MAX_POSITIVE)
+ >> FIXED_POINT_Q15_FRAC_BITS;
+
+ // Handle the corner case when a negative value was rounded to zero
+ if ((integer_part == 0) && (fractional_part == 0)) {
+ sign_char = ' ';
+ }
+
+ // Print the formatted delta UV value to the character buffer
+ return snprintf(buffer, buffer_size, "%c%d.%02d",
+ sign_char,
+ integer_part,
+ fractional_part);
+}
+
+/*******************************************************************************
+ * Function for retrieving actual temperature.
+ *
+ * @return Actual temperature level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_temperature(void)
+{
+ return(current_temperature);
+}
+
+/*******************************************************************************
+ * Function for retrieving actual delta UV.
+ *
+ * @return Actual delta UV level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_deltauv(void)
+{
+ return(current_deltauv);
+}
+
+/** @} (end addtogroup Lighting Level Transition Handler) */
diff --git a/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_signal_transition_handler.h b/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_signal_transition_handler.h
new file mode 100644
index 00000000000..94680daf5df
--- /dev/null
+++ b/app/btmesh/common/btmesh_ctl_server/sl_btmesh_ctl_signal_transition_handler.h
@@ -0,0 +1,96 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_ctl_signal_transition_handler.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_CTL_SIGNAL_TRANSITION_H
+#define SL_BTMESH_CTL_SIGNAL_TRANSITION_H
+
+/*******************************************************************************
+ * Function for retrieving actual temperature.
+ *
+ * @return Actual temperature level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_temperature(void);
+
+/*******************************************************************************
+ * Function for retrieving actual delta UV.
+ *
+ * @return Actual delta UV level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_deltauv(void);
+
+/*******************************************************************************
+ * Set LED temperature and delta UV in given transition time.
+ *
+ * @param[in] temperature Temperature of color.
+ * @param[in] deltauv Delta UV value.
+ * @param[in] transition_ms Transition time in milliseconds.
+ ******************************************************************************/
+void sl_btmesh_ctl_set_temperature_deltauv_level(uint16_t temperature,
+ int16_t deltauv,
+ uint32_t transition_ms);
+
+/**************************************************************************//**
+ * Utility function to print the delta UV raw value into the passed character
+ * buffer in the X.XX format.
+ *
+ * @param[out] buffer Character buffer where the formatted delta UV
+ * is stored
+ * @param[in] buffer_size Size of the character buffer
+ * @param[in] deltauv_raw Raw value of the Delta UV CTL parameter
+ *
+ * @return same as snprintf
+ *****************************************************************************/
+int sl_btmesh_ctl_server_snprint_deltauv(char *buffer,
+ uint16_t buffer_size,
+ uint16_t deltauv_raw);
+
+/*******************************************************************************
+ * Callback for setting Light Color by PWM level (0x0001 - FFFE)
+ *
+ * @param[in] Desired light color PWM level.
+ ******************************************************************************/
+void sl_btmesh_lighting_color_pwm_cb(uint16_t color);
+
+/***************************************************************************//**
+ * Called when the UI shall be updated with the changed CTL Model state during
+ * a transition. The rate of this callback can be controlled by changing the
+ * SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL macro.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] temperature Temperature of color.
+ * @param[in] deltauv Delta UV value.
+ ******************************************************************************/
+void sl_btmesh_ctl_on_ui_update(uint16_t temperature,
+ uint16_t deltauv);
+
+#endif // SL_BTMESH_CTL_SIGNAL_TRANSITION_H
diff --git a/app/btmesh/common/btmesh_dcd_configuration/dcd_config.btmeshconf b/app/btmesh/common/btmesh_dcd_configuration/dcd_config.btmeshconf
new file mode 100644
index 00000000000..06539fb2391
--- /dev/null
+++ b/app/btmesh/common/btmesh_dcd_configuration/dcd_config.btmeshconf
@@ -0,0 +1,17 @@
+{
+ "composition_data": {
+ "cid": "0x02ff",
+ "pid": "0xffff",
+ "vid": "0x0420",
+ "elements": [
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x0000", "name":"Configuration Server"},
+ {"mid":"0x0002", "name":"Health Server"}
+ ]
+ }
+ ]
+ }
+}
diff --git a/app/btmesh/common/btmesh_event_log/config/sl_btmesh_event_log_config.h b/app/btmesh/common/btmesh_event_log/config/sl_btmesh_event_log_config.h
new file mode 100644
index 00000000000..4ed0d1b99d0
--- /dev/null
+++ b/app/btmesh/common/btmesh_event_log/config/sl_btmesh_event_log_config.h
@@ -0,0 +1,46 @@
+/***************************************************************************//**
+ * @file
+ * @brief Mesh events logging configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_EVENT_LOG_CONFIG_H
+#define SL_BTMESH_EVENT_LOG_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Enable linear output events logging
+// Enables logging of linear output events.
+#define SL_BTMESH_LC_LINEAR_OUTPUT_LOG_ENABLE_CFG_VAL 0
+
+// Enable unknown events logging
+// Enables logging of unknown events.
+#define SL_BTMESH_UNKNOWN_EVENTS_LOG_ENABLE_CFG_VAL 0
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_EVENT_LOG_CONFIG_H
diff --git a/app/btmesh/common/btmesh_event_log/sl_btmesh_event_log.c b/app/btmesh/common/btmesh_event_log/sl_btmesh_event_log.c
new file mode 100644
index 00000000000..d76cfd6d393
--- /dev/null
+++ b/app/btmesh/common/btmesh_event_log/sl_btmesh_event_log.c
@@ -0,0 +1,341 @@
+/***************************************************************************//**
+ * @file
+ * @brief Bt Mesh Event Logging module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_btmesh_api.h"
+#include "sl_bt_api.h"
+
+#include "app_log.h"
+
+#include "sl_btmesh_event_log_config.h"
+#include "sl_btmesh_event_log.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup BtMeshEventLog
+ * @{
+ ******************************************************************************/
+
+/*******************************************************************************
+ * Handling of sensor client stack events. Both BLuetooth LE and Bluetooth mesh
+ * events are handled here.
+ * @param[in] evt_id Incoming event ID.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_handle_btmesh_logging_events(sl_btmesh_msg_t *evt)
+{
+ if (NULL == evt) {
+ return;
+ }
+
+ uint32_t evt_id = SL_BT_MSG_ID(evt->header);
+
+ // Handle events
+ switch (evt_id) {
+ /* Node */
+ case sl_bt_evt_connection_opened_id:
+ app_log("evt:le_connection_opened_id" NL);
+ break;
+
+ /* Connection */
+ case sl_bt_evt_connection_parameters_id:
+ app_log("evt:le_connection_parameters_id: "
+ "interval %u, latency %u, timeout %u" NL,
+ ((sl_bt_msg_t *)(evt))->data.evt_connection_parameters.interval,
+ ((sl_bt_msg_t *)(evt))->data.evt_connection_parameters.latency,
+ ((sl_bt_msg_t *)(evt))->data.evt_connection_parameters.timeout);
+ break;
+ case sl_bt_evt_connection_closed_id:
+ app_log("evt:conn closed, reason 0x%x" NL,
+ ((sl_bt_msg_t *)(evt))->data.evt_connection_closed.reason);
+ break;
+ case sl_btmesh_evt_node_reset_id:
+ app_log("evt:mesh_node_reset" NL);
+ break;
+ case sl_btmesh_evt_node_initialized_id:
+ app_log("evt:mesh_node_initialized" NL);
+ break;
+
+ /* Provisioning */
+ case sl_btmesh_evt_node_provisioning_started_id:
+ app_log("evt:mesh_node_provisioning_started" NL);
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ app_log("evt:mesh_node_node_provisioned" NL);
+ break;
+
+ case sl_btmesh_evt_node_provisioning_failed_id:
+ app_log("evt:mesh_node_provisioning_failed, code %x" NL,
+ evt->data.evt_node_provisioning_failed.result);
+ break;
+
+ /* Sensor Client */
+ case sl_btmesh_evt_sensor_client_descriptor_status_id:
+ app_log("evt:mesh_sensor_client_descriptor_status" NL);
+ break;
+ case sl_btmesh_evt_sensor_client_status_id:
+ app_log("evt:mesh_sensor_client_status %s" NL,
+ (evt->data.evt_sensor_client_status.client_address & 0xC000)
+ == 0xC000 ? "(group broadcast)"
+ : (evt->data.evt_sensor_client_status.client_address & 0x1000)
+ == 0 ? "(unicast)" : "(virtual)");
+ break;
+
+ /* Sensor Server */
+ case sl_btmesh_evt_sensor_server_get_request_id:
+ app_log("evt:mesh_sensor_server_get_request (from: 0x%04x)" NL,
+ evt->data.evt_sensor_server_get_request.client_address);
+ break;
+
+ case sl_btmesh_evt_sensor_server_get_column_request_id:
+ app_log("evt:mesh_sensor_server_get_column_request" NL);
+ break;
+
+ case sl_btmesh_evt_sensor_server_get_series_request_id:
+ app_log("evt:mesh_sensor_server_get_series_request" NL);
+ break;
+
+ case sl_btmesh_evt_sensor_server_publish_id:
+ app_log("evt:mesh_sensor_server_publish" NL);
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_get_cadence_request_id:
+ app_log("evt:mesh_sensor_setup_server_get_cadence_request" NL);
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_set_cadence_request_id:
+ app_log("evt:mesh_sensor_setup_server_set_cadence_request" NL);
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_get_settings_request_id:
+ app_log("evt:mesh_sensor_setup_server_get_settings_request" NL);
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_get_setting_request_id:
+ app_log("evt:mesh_sensor_setup_server_get_setting_request" NL);
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_set_setting_request_id:
+ app_log("evt:mesh_sensor_setup_server_set_setting_request" NL);
+ break;
+
+ /* Node Configuration */
+ case sl_btmesh_evt_node_config_set_id:
+ app_log("evt:mesh_node_config_set id=0x%04x" NL,
+ evt->data.evt_node_config_set.id);
+ break;
+
+ case sl_btmesh_evt_node_model_config_changed_id: {
+ uint8_t conf_state =
+ evt->data.evt_node_model_config_changed.node_config_state;
+ app_log("evt:mesh_node_model_config_changed "
+ "vid=%x, model=%x, conf_state=%s" NL,
+ evt->data.evt_node_model_config_changed.vendor_id, evt->data
+ .evt_node_model_config_changed.model_id,
+ conf_state == 0 ? "application key bindings"
+ : conf_state == 1
+ ? "publication parameters" : "subscription list");
+ (void)conf_state;
+ }
+ break;
+
+ case sl_btmesh_evt_node_key_added_id:
+ app_log("evt:mesh_node_key_added" NL
+ "Got new %s key with index 0x%x" NL,
+ evt->data.evt_node_key_added.type == 0
+ ? "network" : "application",
+ evt->data.evt_node_key_added.index);
+ break;
+
+ /* Friendship - Friend Node */
+ case sl_btmesh_evt_friend_friendship_established_id:
+ app_log("evt:mesh_friend_friendship_established "
+ "netkey_index=%d, lpn_address=0x%04x" NL,
+ evt->data.evt_friend_friendship_established.netkey_index,
+ evt->data.evt_friend_friendship_established.lpn_address);
+ break;
+
+ case sl_btmesh_evt_friend_friendship_terminated_id:
+ app_log("evt:mesh_friend_friendship_terminated "
+ "netkey_index=%d, lpn_address=0x%04x, reason=0x%04x" NL,
+ evt->data.evt_friend_friendship_terminated.netkey_index,
+ evt->data.evt_friend_friendship_terminated.lpn_address,
+ evt->data.evt_friend_friendship_terminated.reason);
+ break;
+
+ /* Friendship - Low Power Node*/
+ case sl_btmesh_evt_lpn_friendship_failed_id:
+ app_log("evt:mesh_lpn_friendship_failed "
+ "netkey_index=%d, reason=0x%04x" NL,
+ evt->data.evt_lpn_friendship_failed.netkey_index,
+ evt->data.evt_lpn_friendship_failed.reason);
+ break;
+
+ case sl_btmesh_evt_lpn_friendship_established_id:
+ app_log("evt:mesh_lpn_friendship_established "
+ "netkey_index=%d, addr=0x%04x" NL,
+ evt->data.evt_lpn_friendship_established.netkey_index,
+ evt->data.evt_lpn_friendship_established.friend_address);
+ break;
+
+ case sl_btmesh_evt_lpn_friendship_terminated_id:
+ app_log("evt:mesh_lpn_friendship_terminated "
+ "netkey_index=%d, reason=0x%04x" NL,
+ evt->data.evt_lpn_friendship_terminated.netkey_index,
+ evt->data.evt_lpn_friendship_terminated.reason);
+ break;
+
+ /* Proxy */
+ case sl_btmesh_evt_proxy_connected_id:
+ app_log("evt:mesh_proxy_connected_id" NL);
+ break;
+
+ case sl_btmesh_evt_proxy_disconnected_id:
+ app_log("evt:mesh_proxy_disconnected_id" NL);
+ break;
+
+ /* Scene Server */
+ case sl_btmesh_evt_scene_server_get_id:
+ app_log("evt:sl_btmesh_evt_scene_server_get_id, "
+ "client_address=%u, appkey_index=%u" NL,
+ evt->data.evt_scene_server_get.client_address,
+ evt->data.evt_scene_server_get.appkey_index);
+ break;
+
+ case sl_btmesh_evt_scene_server_register_get_id:
+ app_log("evt:sl_btmesh_evt_scene_server_register_get_id, "
+ "client_address=%u, appkey_index=%u" NL,
+ evt->data.evt_scene_server_register_get.client_address,
+ evt->data.evt_scene_server_register_get.appkey_index);
+ break;
+
+ case sl_btmesh_evt_scene_server_recall_id:
+ app_log("evt:sl_btmesh_evt_scene_server_recall_id, client_address=%u, "
+ "appkey_index=%u, selected_scene=%u, transition_time=%lu" NL,
+ evt->data.evt_scene_server_recall.client_address,
+ evt->data.evt_scene_server_recall.appkey_index,
+ evt->data.evt_scene_server_recall.selected_scene,
+ evt->data.evt_scene_server_recall.transition_time_ms);
+ break;
+
+ case sl_btmesh_evt_scene_server_publish_id:
+ app_log("evt:sl_btmesh_evt_scene_server_publish_id, period_ms=%lu" NL,
+ evt->data.evt_scene_server_publish.period_ms);
+ break;
+
+ case sl_btmesh_evt_scene_setup_server_store_id:
+ app_log("evt:sl_btmesh_evt_scene_setup_server_store_id, "
+ "client_address=%u, appkey_index=%u, scene_id=%u" NL,
+ evt->data.evt_scene_setup_server_store.client_address,
+ evt->data.evt_scene_setup_server_store.appkey_index,
+ evt->data.evt_scene_setup_server_store.scene_id);
+ break;
+
+ case sl_btmesh_evt_scene_setup_server_delete_id:
+ app_log("evt:sl_btmesh_evt_scene_setup_server_delete_id, "
+ "client_address=%u, appkey_index=%u, scene_id=%u" NL,
+ evt->data.evt_scene_setup_server_delete.client_address,
+ evt->data.evt_scene_setup_server_delete.appkey_index,
+ evt->data.evt_scene_setup_server_delete.scene_id);
+ break;
+
+ case sl_btmesh_evt_scene_setup_server_publish_id:
+ app_log("evt:sl_btmesh_evt_scene_setup_server_publish_id, "
+ "period_ms=%lu" NL,
+ evt->data.evt_scene_setup_server_publish.period_ms);
+ break;
+ /* Generic Server Events */
+ case sl_btmesh_evt_generic_server_client_request_id:
+ app_log("evt:sl_btmesh_evt_generic_server_client_request_id" NL);
+ break;
+
+ case sl_btmesh_evt_generic_server_state_recall_id:
+ app_log("evt:sl_btmesh_evt_generic_server_state_recall_id" NL);
+ break;
+
+ /* LC Events */
+ case sl_btmesh_evt_lc_server_mode_updated_id:
+ app_log("evt:sl_btmesh_evt_lc_server_mode_updated_id, mode=%u" NL,
+ evt->data.evt_lc_server_mode_updated.mode_value);
+ break;
+
+ case sl_btmesh_evt_lc_server_om_updated_id:
+ app_log("evt:sl_btmesh_evt_lc_server_om_updated_id, om=%u" NL,
+ evt->data.evt_lc_server_om_updated.om_value);
+ break;
+
+ case sl_btmesh_evt_lc_server_light_onoff_updated_id:
+ app_log("evt:sl_btmesh_evt_lc_server_light_onoff_updated_id, "
+ "lc_onoff=%u, transtime=%lu" NL,
+ evt->data.evt_lc_server_light_onoff_updated.onoff_state,
+ evt->data.evt_lc_server_light_onoff_updated.onoff_trans_time_ms);
+ break;
+
+ case sl_btmesh_evt_lc_server_occupancy_updated_id:
+ app_log("evt:sl_btmesh_evt_lc_server_occupancy_updated_id, "
+ "occupancy=%u" NL,
+ evt->data.evt_lc_server_occupancy_updated.occupancy_value);
+ break;
+
+ case sl_btmesh_evt_lc_server_ambient_lux_level_updated_id:
+ app_log("evt:sl_btmesh_evt_lc_server_ambient_lux_level_updated_id, "
+ "lux_level=%lu" NL,
+ evt->data.evt_lc_server_ambient_lux_level_updated.ambient_lux_level_value);
+ break;
+
+ case sl_btmesh_evt_lc_server_linear_output_updated_id:
+#if defined(SL_BTMESH_LC_LINEAR_OUTPUT_LOG_ENABLE_CFG_VAL) && SL_BTMESH_LC_LINEAR_OUTPUT_LOG_ENABLE_CFG_VAL
+ app_log("evt:sl_btmesh_evt_lc_server_linear_output_updated_id, "
+ "linear_output=%u" NL,
+ evt->data.evt_lc_server_linear_output_updated.linear_output_value);
+#endif // SL_BTMESH_LC_LINEAR_OUTPUT_LOG_ENABLE_CFG_VAL
+ break;
+
+ case sl_btmesh_evt_lc_setup_server_set_property_id:
+ app_log("evt:sl_btmesh_evt_lc_setup_server_property_set_id, "
+ "property=0x%4.4x, value=0x" NL,
+ evt->data.evt_lc_setup_server_set_property.property_id);
+ break;
+
+ default:
+#if defined(SL_BTMESH_UNKNOWN_EVENTS_LOG_ENABLE_CFG_VAL) && SL_BTMESH_UNKNOWN_EVENTS_LOG_ENABLE_CFG_VAL
+ app_log("unknown evt: %8.8x class %2.2x method %2.2x" NL,
+ evt_id,
+ (evt_id >> 16) & 0xFF,
+ (evt_id >> 24) & 0xFF);
+#endif // SL_BTMESH_UNKNOWN_EVENTS_LOG_ENABLE_CFG_VAL
+ break;
+ }
+}
+
+/** @} (end addtogroup BtMeshEventLog) */
diff --git a/app/btmesh/common/btmesh_event_log/sl_btmesh_event_log.h b/app/btmesh/common/btmesh_event_log/sl_btmesh_event_log.h
new file mode 100644
index 00000000000..97cf99d2d9c
--- /dev/null
+++ b/app/btmesh/common/btmesh_event_log/sl_btmesh_event_log.h
@@ -0,0 +1,42 @@
+/***************************************************************************//**
+ * @file
+ * @brief btmesh_event_log.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_EVENT_LOG
+#define SL_BTMESH_EVENT_LOG
+
+/***************************************************************************//**
+ * Handling of sensor client stack events. Both BLuetooth LE and Bluetooth mesh
+ * events are handled here.
+ * @param[in] evt_id Incoming event ID.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_handle_btmesh_logging_events(sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_EVENT_LOG
diff --git a/app/btmesh/common/btmesh_factory_reset/sl_btmesh_factory_reset.c b/app/btmesh/common/btmesh_factory_reset/sl_btmesh_factory_reset.c
new file mode 100644
index 00000000000..2b723f6befc
--- /dev/null
+++ b/app/btmesh/common/btmesh_factory_reset/sl_btmesh_factory_reset.c
@@ -0,0 +1,182 @@
+/***************************************************************************//**
+ * @file
+ * @brief Factory Reset module
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "em_common.h"
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_CLI_PRESENT
+#include "sl_cli.h"
+#endif // SL_CATALOG_CLI_PRESENT
+
+#include "sl_btmesh_factory_reset.h"
+
+/***************************************************************************//**
+ * @addtogroup FactoryReset
+ * @{
+ ******************************************************************************/
+
+/// Callback has no parameters
+#define NO_CALLBACK_DATA (void *)NULL
+/// timeout for factory init
+#define FACTORY_RESET_TIMEOUT 2000
+
+/// timer callback
+static sl_simple_timer_t factory_reset_timer;
+
+///timer callback prototype
+static void factory_reset_timer_cb(sl_simple_timer_t *handle, void *data);
+
+/*******************************************************************************
+ * This function is called to initiate node reset.
+ ******************************************************************************/
+void sl_btmesh_initiate_node_reset()
+{
+ sl_status_t sc = SL_STATUS_OK;
+ // Perform a factory reset of the node. This removes all the keys
+ // and other settings that have been configured for this node
+ sc = sl_btmesh_node_reset();
+ app_assert_status_f(sc, "Failed to reset node");
+
+ // Application callback on node reset
+ sl_btmesh_factory_reset_on_node_reset();
+
+ // Reboot after a small delay
+ sc = sl_simple_timer_start(&factory_reset_timer,
+ FACTORY_RESET_TIMEOUT,
+ factory_reset_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Factory reset timer");
+}
+
+/*******************************************************************************
+ * This function is called to initiate full reset.
+ ******************************************************************************/
+void sl_btmesh_initiate_full_reset()
+{
+ sl_status_t sc = SL_STATUS_OK;
+ // Perform a factory reset of the node. This removes all the keys
+ // and other settings that have been configured for this node
+ sc = sl_btmesh_node_reset();
+ app_assert_status_f(sc, "Failed to reset node");
+ // Perform a full reset by erasing PS storage.
+ sc = sl_bt_nvm_erase_all();
+ app_assert_status_f(sc, "Failed to erase NVM");
+
+ // Application callback on full reset
+ sl_btmesh_factory_reset_on_full_reset();
+
+ // Reboot after a small delay
+ sc = sl_simple_timer_start(&factory_reset_timer,
+ FACTORY_RESET_TIMEOUT,
+ factory_reset_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Factory reset timer");
+}
+
+/*******************************************************************************
+ * Handling of node reset event.
+ *
+ * @param[in] evt Pointer to incoming time event.
+ ******************************************************************************/
+void sl_btmesh_factory_reset_on_event(sl_btmesh_msg_t *evt)
+{
+ sl_status_t sc = SL_STATUS_OK;
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_reset_id:
+ // Application callback on node reset
+ sl_btmesh_factory_reset_on_node_reset();
+
+ // Reboot after a small delay
+ sc = sl_simple_timer_start(&factory_reset_timer,
+ FACTORY_RESET_TIMEOUT,
+ factory_reset_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Factory reset timer");
+ break;
+
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * Timer Callback
+ ******************************************************************************/
+static void factory_reset_timer_cb(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+ sl_bt_system_reset(0);
+}
+
+/***************************************************************************//**
+ * CLI Callback
+ * @param[in] arguments pointer to CLI arguments
+ ******************************************************************************/
+#ifdef SL_CATALOG_CLI_PRESENT
+void factory_reset_from_cli(sl_cli_command_arg_t *arguments)
+{
+ (void)arguments;
+ sl_btmesh_initiate_full_reset();
+}
+
+void node_reset_from_cli(sl_cli_command_arg_t *arguments)
+{
+ (void)arguments;
+ sl_btmesh_initiate_node_reset();
+}
+#endif // SL_CATALOG_CLI_PRESENT
+
+/***************************************************************************//**
+ * Weak implementation of node reset callback
+ ******************************************************************************/
+SL_WEAK void sl_btmesh_factory_reset_on_node_reset(void)
+{
+}
+
+/***************************************************************************//**
+ * Weak implementation of full reset callback
+ ******************************************************************************/
+SL_WEAK void sl_btmesh_factory_reset_on_full_reset(void)
+{
+}
+
+/** @} (end addtogroup FactoryReset) */
diff --git a/app/btmesh/common/btmesh_factory_reset/sl_btmesh_factory_reset.h b/app/btmesh/common/btmesh_factory_reset/sl_btmesh_factory_reset.h
new file mode 100644
index 00000000000..c850c43d94e
--- /dev/null
+++ b/app/btmesh/common/btmesh_factory_reset/sl_btmesh_factory_reset.h
@@ -0,0 +1,67 @@
+/***************************************************************************//**
+ * @file
+ * @brief factory_reset.h
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_FACTORY_RESET_H
+#define SL_BTMESH_FACTORY_RESET_H
+
+/***************************************************************************//**
+ * This function is called to initiate node reset.
+ ******************************************************************************/
+void sl_btmesh_initiate_node_reset(void);
+
+/***************************************************************************//**
+ * This function is called to initiate full reset.
+ ******************************************************************************/
+void sl_btmesh_initiate_full_reset(void);
+
+/***************************************************************************//**
+ * Handling of node reset event.
+ *
+ * @param[in] evt Pointer to incoming time event.
+ ******************************************************************************/
+void sl_btmesh_factory_reset_on_event(sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * Called during the factory reset of the node.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation if provided which is a no-operation. (empty function)
+ *
+ ******************************************************************************/
+void sl_btmesh_factory_reset_on_node_reset(void);
+
+/***************************************************************************//**
+ * Called during the factory reset of the device.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation if provided which is a no-operation. (empty function)
+ *
+ ******************************************************************************/
+void sl_btmesh_factory_reset_on_full_reset(void);
+
+#endif // SL_BTMESH_FACTORY_RESET_H
diff --git a/app/btmesh/common/btmesh_firmware_update_client/btmesh_firmware_update_client.dcd b/app/btmesh/common/btmesh_firmware_update_client/btmesh_firmware_update_client.dcd
new file mode 100644
index 00000000000..c1108851fe7
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_client/btmesh_firmware_update_client.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1403", "name":"Device Firmware Update Client"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_firmware_update_client/sl_btmesh_firmware_update_client.c b/app/btmesh/common/btmesh_firmware_update_client/sl_btmesh_firmware_update_client.c
new file mode 100644
index 00000000000..5c2da6ac530
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_client/sl_btmesh_firmware_update_client.c
@@ -0,0 +1,92 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Firmware Update Client
+ *
+ * The firmware update client application model performs the initialization only
+ * because the distributor stack class calls the necessary firmware update client
+ * stack class functions inside the BT Mesh stack therefore no additional logic
+ * is necessary at application level.
+ *
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#include "sl_btmesh_firmware_update_client.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup fw_upd_client BT Mesh Firmware Update Client
+ * @{
+ ******************************************************************************/
+static void sl_btmesh_firmware_update_client_element_init(uint16_t elem_index)
+{
+ sl_status_t sc = sl_btmesh_fw_update_client_init(elem_index);
+
+ app_assert_status_f(sc, "Failed to init Firmware Update Client");
+}
+
+static void sl_btmesh_firmware_update_client_init(void)
+{
+ sl_btmesh_firmware_update_client_element_init(BTMESH_FW_DISTRIBUTION_SERVER_MAIN);
+}
+
+void sl_btmesh_firmware_update_client_on_event(const sl_btmesh_msg_t *const evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id: {
+ sl_btmesh_firmware_update_client_init();
+ break;
+ }
+ case sl_btmesh_evt_node_initialized_id: {
+ if (0 != evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_firmware_update_client_init();
+ }
+ break;
+ }
+ }
+}
+
+/** @} end fw_upd_client */
diff --git a/app/btmesh/common/btmesh_firmware_update_client/sl_btmesh_firmware_update_client.h b/app/btmesh/common/btmesh_firmware_update_client/sl_btmesh_firmware_update_client.h
new file mode 100644
index 00000000000..c010883543e
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_client/sl_btmesh_firmware_update_client.h
@@ -0,0 +1,60 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Firmware Update Client
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_FIRMWARE_UPDATE_CLIENT_H
+#define SL_BTMESH_FIRMWARE_UPDATE_CLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup fw_upd_client BT Mesh Firmware Update Client
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Handle Firmware Update Client events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ *
+ ******************************************************************************/
+void sl_btmesh_firmware_update_client_on_event(const sl_btmesh_msg_t *const evt);
+
+/** @} end fw_upd_client */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+ #endif // SL_BTMESH_FIRMWARE_UPDATE_CLIENT_H
diff --git a/app/btmesh/common/btmesh_firmware_update_server/btmesh_firmware_update_server.dcd b/app/btmesh/common/btmesh_firmware_update_server/btmesh_firmware_update_server.dcd
new file mode 100644
index 00000000000..3e755a0d5ac
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_server/btmesh_firmware_update_server.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1402", "name":"Firmware Update Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_firmware_update_server/btmesh_firmware_update_server_validation.lua b/app/btmesh/common/btmesh_firmware_update_server/btmesh_firmware_update_server_validation.lua
new file mode 100644
index 00000000000..82f24d2bd42
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_server/btmesh_firmware_update_server_validation.lua
@@ -0,0 +1,74 @@
+-- automatic conversion of input parameters
+local function autonumber(input)
+ local base = 10
+ local orig_input = input
+ if (type(input) == "string") then
+ input = input:gsub("[\(\)\"uUlL]", "")
+ if string.find(input,"[bxhBXH]") ~= nil then
+ if string.find(string.lower(input), "0b") == 1 then
+ input = input:gsub("[bB]","")
+ base = 2
+ elseif string.find(string.lower(input), "0x") == 1 then
+ input = input:gsub("[xXhH]","")
+ base = 16
+ end
+ elseif string.find(input, "0") == 1 then
+ base = 8
+ end
+ elseif (type(input) == "number") then
+ return input
+ else
+ logit("autonumber() expects either a string or a number!")
+ return nil
+ end
+ local result = tonumber(input, base)
+ if result == nil then
+ logit("Configured value is not valid: \"" .. tostring(orig_input) .. "\" - modify it to a numeric value!")
+ end
+ return result
+end
+
+-- Calculate maximum number of firmware update server rx message (segment) count
+-- The Firmware Update Start and Firmware Update Firmware Metadata Check
+-- messages contain metadata and these have variable length because of this.
+-- The Firmware Update Start message is always longer in case of the same
+-- metadata length because it has more fields.
+-- Firmware Update Start message: Opcode (2)
+-- + Update TTL (1)
+-- + Update Timeout Base (2)
+-- + Update BLOB ID (8)
+-- + Update Firmware Image Index (1)
+-- + Incoming Firmware Metadata (Opt:1-255)
+-- The max length of an unsegmented access layer message is 11 however the
+-- mandatory part of Firmware Update Start message is 13 bytes without any
+-- metadata so it never fits into one BT Mesh message only.
+local function calc_fw_update_server_max_message_count(metadata_length)
+ local TRANSMIC_MIN_SIZE = 4
+ local FW_UPDATE_START_MIN_LENGTH = 13
+ local fw_update_message_payload_size = FW_UPDATE_START_MIN_LENGTH + metadata_length
+ return math.ceil((fw_update_message_payload_size + TRANSMIC_MIN_SIZE) / 12)
+end
+
+local metadata_length_name = "SL_BTMESH_FW_UPDATE_SERVER_METADATA_LENGTH_CFG_VAL"
+local lpn_min_queue_length_name = "SL_BTMESH_LPN_MIN_QUEUE_LENGTH_CFG_VAL"
+local lpn_component_selected = slc.is_selected("btmesh_lpn")
+
+if lpn_component_selected then
+ metadata_length = autonumber(slc.config(metadata_length_name).value)
+ lpn_min_queue_length = autonumber(slc.config(lpn_min_queue_length_name).value)
+ if lpn_min_queue_length < calc_fw_update_server_max_message_count(metadata_length) then
+ local problem = "LPN min queue length and max metadata length config inconsistent"
+ local description =
+ string.format("The maximum firmware update server message count (%i) "
+ .. "derived from max metadata length (%i) shall fit "
+ .. "into LPN min queue length (%i)",
+ calc_fw_update_server_max_message_count(metadata_length),
+ metadata_length,
+ lpn_min_queue_length)
+ validation.error(problem,
+ validation.target_for_defines({metadata_length_name,
+ lpn_min_queue_length_name}),
+ description,
+ nil)
+ end
+end
diff --git a/app/btmesh/common/btmesh_firmware_update_server/config/sl_btmesh_firmware_update_server_config.h b/app/btmesh/common/btmesh_firmware_update_server/config/sl_btmesh_firmware_update_server_config.h
new file mode 100644
index 00000000000..cd27bbe8962
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_server/config/sl_btmesh_firmware_update_server_config.h
@@ -0,0 +1,73 @@
+/***************************************************************************//**
+ * @file
+ * @brief Firmware Update Server Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_FIRMWARE_UPDATE_SERVER_CONFIG_H
+#define SL_BTMESH_FIRMWARE_UPDATE_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Number of firmware on device <1-255>
+#define SL_BTMESH_FW_UPDATE_SERVER_NUM_OF_FW_CFG_VAL 1
+
+// Maximum length of metadata <0-255>
+#define SL_BTMESH_FW_UPDATE_SERVER_METADATA_LENGTH_CFG_VAL 255
+
+// Firmware Information
+// Company Identifier
+// CID MSB
+// Hexadecimal string literal.
+#define SL_BTMESH_FW_UPDATE_SERVER_CID_MSB_CFG_VAL "\x02"
+
+// CID LSB
+// Hexadecimal string literal.
+#define SL_BTMESH_FW_UPDATE_SERVER_CID_LSB_CFG_VAL "\xFF"
+
+//
+
+// Firmware identifier
+#define SL_BTMESH_FW_UPDATE_SERVER_FWID_CFG_VAL "fwid"
+
+// Update URI
+#define SL_BTMESH_FW_UPDATE_SERVER_UPDATE_URI_CFG_VAL "https://example.com/upd_uri"
+
+//
+
+// Logging
+#define SL_BTMESH_FW_UPDATE_SERVER_LOGGING_CFG_VAL 1
+
+// Period of verification progress logs [ms] <0-2000:100>
+// Setting it to 0 the user interface (log & display) is updated every time when progress is made.
+#define SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL 200
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_FIRMWARE_UPDATE_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server.c b/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server.c
new file mode 100644
index 00000000000..9f6b019b903
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server.c
@@ -0,0 +1,616 @@
+/***************************************************************************//**
+ * @file
+ * @brief Definitions of Firmware Update Server functionality
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#include "sl_btmesh.h"
+#include "sl_btmesh_dcd.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_log_PRESENT
+
+#ifdef SL_CATALOG_APP_ASSERT_PRESENT
+#include "app_assert.h"
+#endif // SL_CATALOG_APP_ASSERT_PRESENT
+
+#include "sl_simple_timer.h"
+
+#ifdef SL_CATALOG_BTMESH_STACK_FW_DISTRIBUTION_SERVER_PRESENT
+#include "sli_btmesh_fw_distribution_server.h"
+#endif // SL_CATALOG_BTMESH_STACK_FW_DISTRIBUTION_SERVER_PRESENT
+
+#include "sl_btmesh_blob_storage.h"
+#include "sl_btmesh_blob_storage_app_id.h"
+
+#include "sl_btmesh_firmware_update_server.h"
+#include "sl_btmesh_firmware_update_server_api.h"
+#include "sl_btmesh_firmware_update_server_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup mesh_fw_update_server
+ * @{
+ ******************************************************************************/
+
+/// Returns the string representation of BLOB ID in a compound literal.
+/// WARNING! This macro shall be used as a parameter of log calls only due to the
+/// lifetime of underlying compound literal in APP_BTMESH_UUID_64_TO_STRING.
+#define BLOB_ID_TO_STRING(blob_id) APP_BTMESH_UUID_64_TO_STRING(blob_id, ' ', true)
+
+/// Storage for internal state variables
+static struct {
+ union {
+ /// State flags
+ struct {
+ /// Idle state
+ uint16_t idle : 1;
+ /// Idle/Inactive state
+ uint16_t inactive : 1;
+ /// Idle/Failed state
+ uint16_t failed : 1;
+ /// Idle/Failed/Transfer Failed state
+ uint16_t transfer_failed : 1;
+ /// Idle/Failed/Verification Failed state
+ uint16_t verification_failed : 1;
+ /// Active state
+ uint16_t active : 1;
+ /// Active/Transfer state
+ uint16_t transfer : 1;
+ /// Active/Verification state
+ uint16_t verification : 1;
+ /// Verification Successful state
+ uint16_t verification_success : 1;
+ };
+ uint16_t raw;
+ } state;
+ /// Current status of verification (used in Active/Verification)
+ sl_btmesh_fw_update_server_verify_state_t verification_status;
+ /// Chunk storage for verification steps
+ uint8_t * verification_chunk_buffer;
+ /// Size of the chunks for verification
+ uint32_t verification_chunk_size;
+ /// Size of the BLOB being verified
+ uint32_t verification_size;
+ /// Current progress of verification
+ uint32_t verification_progress;
+ /// Verification log timer
+ sl_simple_timer_t verify_timer;
+ /// BLOB identifier being transferred
+ sl_bt_uuid_64_t blob_id;
+
+ union {
+ /// Metadata check state machine state flags
+ struct {
+ /// Idle state flag
+ uint8_t idle : 1;
+ /// Active state flag
+ uint8_t active : 1;
+ /// Indicates that Update Start triggered the metadata check
+ uint8_t start_response : 1;
+ };
+ uint8_t raw;
+ } metadata_check_state;
+ /// Used for distributor to indicate self update
+ bool self_update;
+ /// Current status of metadata check (used in Active)
+ sl_btmesh_fw_update_server_metadata_check_state_t metadata_check_status;
+ PACKSTRUCT(struct {
+ /// Firmware index of metadata being checked
+ uint8_t metadata_fw_index;
+ /// Metadata buffer
+ uint8_t metadata[SL_BTMESH_FW_UPDATE_SERVER_METADATA_LENGTH_CFG_VAL];
+ })
+ ;
+ /// Length of metadata
+ uint8_t metadata_len;
+ /// Metadata check response Additional Information
+ sl_btmesh_fw_update_server_additional_information_t additional_information;
+} firmware_update_server;
+
+typedef enum {
+ /// Idle
+ IDLE,
+ /// Idle/Inactive
+ IDLE_INACTIVE,
+ /// Idle/Failed/Transfer Failed
+ IDLE_FAILED_TRANSFER_FAILED,
+ /// Idle/Failed/Verification Failed
+ IDLE_FAILED_VERIFICATION_FAILED,
+ /// Idle/Suspended
+ IDLE_SUSPENDED,
+ /// Active/Transfer
+ ACTIVE_TRANSFER,
+ /// Active/Verification
+ ACTIVE_VERIFICATION,
+ /// Verification Success
+ VERIFICATION_SUCCESS,
+
+ /// Metadata check Idle
+ METADATA_IDLE,
+ /// Metadata check Active
+ METADATA_ACTIVE
+} btmesh_firmware_udpate_state_t;
+
+/***************************************************************************//**
+ * Transitions state machine to a state with handling entry actions
+ *
+ * @param[in] state State to transition into
+ ******************************************************************************/
+static void btmesh_firmware_udpate_server_change_state(btmesh_firmware_udpate_state_t state);
+
+/***************************************************************************//**
+ * Logs verification progress
+ *
+ * @note Inputs are not used.
+ *
+ * @param[in] timer Timer structure
+ * @param[in] data Callback data
+ ******************************************************************************/
+static void btmesh_firmware_update_server_verify_progress_ui_update(sl_simple_timer_t *timer,
+ void *data);
+
+void sl_btmesh_firmware_update_server_init(void)
+{
+ sl_btmesh_fw_update_server_init(BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ SL_BTMESH_FW_UPDATE_SERVER_NUM_OF_FW_CFG_VAL,
+ SL_BTMESH_FW_UPDATE_SERVER_METADATA_LENGTH_CFG_VAL);
+
+ // Initial state is Idle
+ btmesh_firmware_udpate_server_change_state(IDLE);
+ firmware_update_server.metadata_check_state.raw = 0;
+ firmware_update_server.metadata_check_state.idle = 1;
+}
+
+void sl_btmesh_firmware_update_server_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_firmware_update_server_init();
+ break;
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_firmware_update_server_init();
+ }
+ break;
+ case sl_btmesh_evt_fw_update_server_check_fw_metadata_req_id: {
+ sl_btmesh_evt_fw_update_server_check_fw_metadata_req_t *msg =
+ &evt->data.evt_fw_update_server_check_fw_metadata_req;
+ log_debug("Received Metadata Check request." NL);
+ // Save FW index from message
+ firmware_update_server.metadata_fw_index = msg->fw_index;
+ firmware_update_server.metadata_len = msg->metadata.len;
+ firmware_update_server.metadata_check_state.start_response = 0;
+ memcpy(firmware_update_server.metadata,
+ msg->metadata.data,
+ firmware_update_server.metadata_len);
+ btmesh_firmware_udpate_server_change_state(METADATA_ACTIVE);
+ break;
+ }
+ case sl_btmesh_evt_fw_update_server_update_start_req_id: {
+ if (0 == firmware_update_server.state.idle) {
+ return;
+ }
+ sl_btmesh_evt_fw_update_server_update_start_req_t *msg =
+ &evt->data.evt_fw_update_server_update_start_req;
+
+ // Store BLOB ID of incoming firmware
+ memcpy(&firmware_update_server.blob_id,
+ &msg->blob_id,
+ sizeof(sl_bt_uuid_64_t));
+
+ log_info("Update Start (BLOB ID: %s)" NL,
+ BLOB_ID_TO_STRING(&firmware_update_server.blob_id));
+
+ firmware_update_server.metadata_len = msg->metadata.len;
+ firmware_update_server.metadata_fw_index = msg->fw_index;
+ firmware_update_server.metadata_check_state.start_response = 1;
+
+ // Store received metadata
+ memcpy(firmware_update_server.metadata,
+ msg->metadata.data,
+ firmware_update_server.metadata_len);
+ btmesh_firmware_udpate_server_change_state(METADATA_ACTIVE);
+ break;
+ }
+ case sl_btmesh_evt_mbt_server_state_changed_id: {
+ if (0 == firmware_update_server.state.transfer) {
+ return;
+ }
+ sl_btmesh_evt_mbt_server_state_changed_t *msg =
+ &evt->data.evt_mbt_server_state_changed;
+ // timed out
+ switch (msg->new_state) {
+ case sl_btmesh_mbt_server_phase_suspended:
+ if (firmware_update_server.state.active == 1
+ && firmware_update_server.state.transfer == 1) {
+ btmesh_firmware_udpate_server_change_state(IDLE_SUSPENDED);
+ }
+ break;
+ case sl_btmesh_mbt_server_phase_inactive:
+ if (firmware_update_server.state.idle == 0) {
+ // If the MBT server goes inactive, and FW Update is not idle, it
+ // must've been an MBT abort, meaning update is aborted.
+ // Notify user about update abort
+ sl_btmesh_fw_update_server_update_aborted();
+ log_error("Firmware Update aborted due BLOB Transfer is abort" NL);
+ }
+ btmesh_firmware_udpate_server_change_state(IDLE_INACTIVE);
+ break;
+ }
+ // sl_btmesh_evt_mbt_server_state_changed_id
+ break;
+ }
+ case sl_btmesh_evt_fw_update_server_update_cancelled_id:
+ if (0 == firmware_update_server.state.active) {
+ return;
+ }
+ log_info("Firmware Update Canceled" NL);
+ // Notify user about update cancelation
+ sl_btmesh_fw_update_server_update_canceled();
+ btmesh_firmware_udpate_server_change_state(IDLE_INACTIVE);
+ break;
+ case sl_btmesh_evt_fw_update_server_verify_fw_req_id: {
+ if (0 == firmware_update_server.state.transfer && !firmware_update_server.self_update) {
+ return;
+ }
+ btmesh_firmware_udpate_server_change_state(ACTIVE_VERIFICATION);
+ break;
+ }
+ case sl_btmesh_evt_fw_update_server_apply_id:
+ if (0 == firmware_update_server.state.verification_success) {
+ return;
+ }
+ log_info("Applying Firmware." NL);
+ // User callback for FW apply
+ sl_btmesh_fw_update_server_apply();
+ break;
+ case sl_btmesh_evt_fw_update_server_distributor_self_update_req_id:
+ if (0 == firmware_update_server.state.idle) {
+ return;
+ } else {
+#ifdef SL_CATALOG_BTMESH_STACK_FW_DISTRIBUTION_SERVER_PRESENT
+ mesh_dfu_dist_server_fw_info_t info;
+ sl_btmesh_evt_fw_update_server_distributor_self_update_req_t *msg =
+ &evt->data.evt_fw_update_server_distributor_self_update_req;
+ sl_status_t sc = sli_btmesh_fw_dist_server_get_fw_by_index(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ msg->fw_index,
+ &info);
+ if (SL_STATUS_OK != sc) {
+ sl_btmesh_fw_update_server_distributor_self_update_rsp(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ sl_btmesh_fw_update_server_update_start_response_type_reject_internal_error,
+ (uint8_t)firmware_update_server.additional_information);
+ } else {
+ memcpy(&firmware_update_server.blob_id,
+ info.p_blob_id,
+ sizeof(sl_bt_uuid_64_t));
+ memcpy(firmware_update_server.metadata,
+ info.p_metadata,
+ info.metadata_len);
+ firmware_update_server.self_update = true;
+ btmesh_firmware_udpate_server_change_state(METADATA_ACTIVE);
+ }
+#else // SL_CATALOG_BTMESH_STACK_FW_DISTRIBUTION_SERVER_PRESENT
+ sl_btmesh_fw_update_server_distributor_self_update_rsp(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ sl_btmesh_fw_update_server_update_start_response_type_reject_internal_error,
+ (uint8_t)firmware_update_server.additional_information);
+#endif
+ }
+ break;
+ default:
+ // empty
+ break;
+ }
+}
+
+void sl_btmesh_firmware_update_server_verify_step_handle(void)
+{
+ if (1 == firmware_update_server.state.verification) {
+ switch (firmware_update_server.verification_status) {
+ case BTMESH_FW_UPDATE_SERVER_VERIFY_SUCCESS:
+ // In case of success, accept firmware
+ sl_btmesh_fw_update_server_verify_fw_rsp(BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ !0);
+ sl_simple_timer_stop(&firmware_update_server.verify_timer);
+#if FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD > 0
+ btmesh_firmware_update_server_verify_progress_ui_update(NULL, NULL);
+#endif // FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD > 0
+ btmesh_firmware_udpate_server_change_state(VERIFICATION_SUCCESS);
+ break;
+ case BTMESH_FW_UPDATE_SERVER_VERIFY_ERROR:
+ // In case of error, reject firmware
+ sl_btmesh_fw_update_server_verify_fw_rsp(BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ 0);
+ sl_simple_timer_stop(&firmware_update_server.verify_timer);
+#if FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD > 0
+ btmesh_firmware_update_server_verify_progress_ui_update(NULL, NULL);
+#endif // FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD > 0
+ btmesh_firmware_udpate_server_change_state(IDLE_FAILED_VERIFICATION_FAILED);
+ break;
+ case BTMESH_FW_UPDATE_SERVER_VERIFY_PENDING:
+ if (NULL != firmware_update_server.verification_chunk_buffer) {
+ // Read out data from flash to pass to user callback
+ //
+ // In the default implementation this data is not used. However, this
+ // way the user has the opportunity to implement their own verification.
+ sl_btmesh_blob_storage_read(&firmware_update_server.blob_id,
+ firmware_update_server.verification_progress,
+ &firmware_update_server.verification_chunk_size,
+ firmware_update_server.verification_chunk_buffer);
+ }
+ // If pending, call step
+ firmware_update_server.verification_status =
+ sl_btmesh_fw_update_server_verify_step(firmware_update_server.verification_chunk_buffer,
+ firmware_update_server.verification_chunk_size);
+ firmware_update_server.verification_progress +=
+ firmware_update_server.verification_chunk_size;
+#if SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL == 0
+ btmesh_firmware_update_server_verify_progress_ui_update(NULL, NULL);
+#endif // SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL == 0
+ break;
+ }
+ }
+}
+
+void sl_btmesh_firmware_update_server_metadata_check_step_handle(void)
+{
+ if (1 == firmware_update_server.metadata_check_state.active) {
+ switch (firmware_update_server.metadata_check_status) {
+ case BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_SUCCESS:
+ log_info("Metadata check successful" NL);
+
+ // In case of success, accept metadata
+ if (firmware_update_server.self_update) {
+ sl_btmesh_fw_update_server_distributor_self_update_rsp(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ sl_btmesh_fw_update_server_update_start_response_type_accept,
+ (uint8_t)firmware_update_server.additional_information);
+ } else if (1 == firmware_update_server.metadata_check_state.start_response) {
+ sl_bt_uuid_64_t blob_id;
+
+ // SL_STATUS_OK indicates, that the exact footer has been found
+ // blob_id is used as dummy storage
+ sl_status_t metadata_footer_check =
+ sl_btmesh_blob_storage_get_blob_id_by_footer(
+ BLOB_STORAGE_APP_ID_DFU_METADATA,
+ &firmware_update_server.metadata_fw_index,
+ firmware_update_server.metadata_len + 1,
+ &blob_id);
+
+ if (metadata_footer_check == SL_STATUS_OK) {
+ // Set the BLOB ID to the one stored, so it identifies the already
+ // stored image
+ memcpy(&firmware_update_server.blob_id,
+ &blob_id,
+ sizeof(sl_bt_uuid_64_t));
+ }
+
+ sl_btmesh_fw_update_server_update_start_response_type_t response =
+ (metadata_footer_check == SL_STATUS_OK)
+ ? sl_btmesh_fw_update_server_update_start_response_type_fw_already_exists
+ : sl_btmesh_fw_update_server_update_start_response_type_accept;
+
+ sl_btmesh_fw_update_server_update_start_rsp(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ (uint8_t)response,
+ (uint8_t)firmware_update_server.additional_information);
+ btmesh_firmware_udpate_server_change_state(
+ (metadata_footer_check == SL_STATUS_OK)
+ ? ACTIVE_VERIFICATION
+ : ACTIVE_TRANSFER);
+ } else {
+ sl_btmesh_fw_update_server_check_fw_metadata_rsp(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ sl_btmesh_fw_update_server_update_start_response_type_accept,
+ (uint8_t)firmware_update_server.additional_information,
+ firmware_update_server.metadata_fw_index);
+ }
+ // Notify user about update starting
+ sl_btmesh_fw_update_server_update_start(&firmware_update_server.blob_id);
+ // Switch to Idle state
+ btmesh_firmware_udpate_server_change_state(METADATA_IDLE);
+ break;
+ case BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_ERROR:
+ log_error("Metadata check error" NL);
+ // In case of error, reject metadata
+ if (firmware_update_server.self_update) {
+ sl_btmesh_fw_update_server_distributor_self_update_rsp(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ sl_btmesh_fw_update_server_update_start_response_type_reject_metadata_check_failed,
+ (uint8_t)firmware_update_server.additional_information);
+ } else if (1 == firmware_update_server.metadata_check_state.start_response) {
+ sl_btmesh_fw_update_server_update_start_rsp(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ sl_btmesh_fw_update_server_update_start_response_type_reject_metadata_check_failed,
+ (uint8_t)firmware_update_server.additional_information);
+ } else {
+ sl_btmesh_fw_update_server_check_fw_metadata_rsp(
+ BTMESH_FIRMWARE_UPDATE_SERVER_MAIN,
+ sl_btmesh_fw_update_server_update_start_response_type_reject_metadata_check_failed,
+ (uint8_t)firmware_update_server.additional_information,
+ firmware_update_server.metadata_fw_index);
+ }
+ // Switch to Idle state
+ btmesh_firmware_udpate_server_change_state(METADATA_IDLE);
+ break;
+ case BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_PENDING:
+ // If pending, call step
+ firmware_update_server.metadata_check_status =
+ sl_btmesh_firmware_update_server_metadata_check_step(
+ &firmware_update_server.additional_information);
+ break;
+ }
+ }
+}
+
+bool sl_btmesh_firmware_update_server_is_ok_to_sleep(void)
+{
+ return (0 == firmware_update_server.metadata_check_state.active)
+ && (0 == firmware_update_server.state.verification);
+}
+
+static void btmesh_firmware_udpate_server_change_state(btmesh_firmware_udpate_state_t state)
+{
+ if (state < METADATA_IDLE) {
+ firmware_update_server.state.raw = 0;
+ }
+ switch (state) {
+ case IDLE:
+ // Idle's initial substate is Idle/Inactive
+ case IDLE_INACTIVE:
+ firmware_update_server.state.idle = 1;
+ firmware_update_server.state.inactive = 1;
+ firmware_update_server.self_update = false;
+ break;
+ case IDLE_FAILED_TRANSFER_FAILED:
+ firmware_update_server.state.idle = 1;
+ firmware_update_server.state.failed = 1;
+ firmware_update_server.state.transfer_failed = 1;
+ log_error("Firmware Transfer Failed" NL);
+ break;
+ case IDLE_FAILED_VERIFICATION_FAILED:
+ firmware_update_server.state.idle = 1;
+ firmware_update_server.state.failed = 1;
+ firmware_update_server.state.verification_failed = 1;
+ log_error("Firmware Verification Failed" NL);
+ break;
+ case ACTIVE_TRANSFER:
+ firmware_update_server.state.active = 1;
+ firmware_update_server.state.transfer = 1;
+ log_info("Firmware Transfer Start" NL);
+ break;
+ case IDLE_SUSPENDED:
+ firmware_update_server.state.idle = 1;
+ firmware_update_server.state.transfer = 1;
+ log_info("Firmware Transfer Suspended" NL);
+ break;
+ case ACTIVE_VERIFICATION:
+ firmware_update_server.state.active = 1;
+ firmware_update_server.state.verification = 1;
+ log_info("Firmware Verification Start (BLOB ID: %s)" NL,
+ BLOB_ID_TO_STRING(&firmware_update_server.blob_id));
+ sl_btmesh_blob_storage_slot_metadata_cache_t const * cache;
+ uint32_t len;
+ // Get BLOB size from cached BLOB table
+ sl_btmesh_blob_storage_get_cache(&cache, &len);
+ for (uint32_t i = 0; i < len; ++i) {
+ if (0 == memcmp(&cache[i].blob_id,
+ &firmware_update_server.blob_id,
+ sizeof(sl_bt_uuid_64_t))) {
+ firmware_update_server.verification_size = cache[i].blob_size;
+ break;
+ }
+ }
+ firmware_update_server.verification_progress = 0;
+ // Start verification with user callback
+ firmware_update_server.verification_status =
+ sl_btmesh_fw_update_server_verify_start(&firmware_update_server.blob_id,
+ &firmware_update_server.verification_chunk_buffer,
+ &firmware_update_server.verification_chunk_size,
+ firmware_update_server.verification_size);
+#if SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL > 0
+ sl_simple_timer_start(&firmware_update_server.verify_timer,
+ SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL,
+ btmesh_firmware_update_server_verify_progress_ui_update,
+ NULL,
+ true);
+#else // SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL > 0
+ btmesh_firmware_update_server_verify_progress_ui_update(NULL, NULL);
+#endif // FW_UPDATE_SERVER_LOGGING_VERIFY_PROGRESS_PERIOD > 0
+ break;
+ case VERIFICATION_SUCCESS: {
+ firmware_update_server.state.verification_success = 1;
+ // Store metadata and FW index
+ sl_status_t sc =
+ sl_btmesh_blob_storage_write_app_footer(&firmware_update_server.blob_id,
+ &firmware_update_server.metadata_fw_index,
+ BLOB_STORAGE_APP_ID_DFU_METADATA,
+ firmware_update_server.metadata_len
+ + 1);
+ app_assert_status_f(sc, "BLOB Storage footer writing failed!");
+ log_info("Firmware Verification Successful" NL);
+ break;
+ }
+ case METADATA_ACTIVE: {
+ firmware_update_server.metadata_check_state.idle = 0;
+ firmware_update_server.metadata_check_state.active = 1;
+ // Notify user application about metadata check starting
+ firmware_update_server.metadata_check_status =
+ sl_btmesh_fw_update_server_metadata_check_start(firmware_update_server.metadata,
+ firmware_update_server.metadata_len,
+ &firmware_update_server.additional_information);
+ log_info("Metadata check start." NL);
+ log_debug("Metadata length: %d, Add. Info: %d" NL,
+ firmware_update_server.metadata_len,
+ firmware_update_server.additional_information);
+ break;
+ }
+ case METADATA_IDLE:
+ firmware_update_server.metadata_check_state.active = 0;
+ firmware_update_server.metadata_check_state.idle = 1;
+ break;
+ }
+}
+
+static void btmesh_firmware_update_server_verify_progress_ui_update(sl_simple_timer_t *timer,
+ void *data)
+{
+ (void)timer;
+ (void)data;
+ if (BTMESH_FW_UPDATE_SERVER_VERIFY_ERROR != firmware_update_server.verification_status) {
+ uint8_t pct = SL_PROG_TO_PCT_INT(firmware_update_server.verification_size,
+ firmware_update_server.verification_progress);
+#if SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL == 0
+ static uint8_t prev;
+ if (firmware_update_server.verification_progress == 0) {
+ prev = 0;
+ }
+ if (pct > prev) {
+#endif // SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL == 0
+ log_info("Firmware verification progress %d%%" NL,
+ pct);
+#if SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL == 0
+ prev = pct;
+ }
+#endif // SL_BTMESH_FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD_CFG_VAL == 0
+ (void)pct; // In case logging is disabled, avoid warning.
+ }
+ sl_btmesh_fw_update_server_verify_progress_ui_update(firmware_update_server.verification_status,
+ firmware_update_server.verification_progress,
+ firmware_update_server.verification_size);
+}
+
+/** @} mesh_fw_update_server */
diff --git a/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server.h b/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server.h
new file mode 100644
index 00000000000..aaa8acc83ef
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server.h
@@ -0,0 +1,89 @@
+/***************************************************************************//**
+ * @file
+ * @brief Interface of Firmware Update Server
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_FIRMWARE_UPDATE_SERVER_H
+#define SL_BTMESH_FIRMWARE_UPDATE_SERVER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup mesh_fw_update_server BT Mesh Firmware Update Server
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Initializes the Firmware Update Server application layer component
+ ******************************************************************************/
+void sl_btmesh_firmware_update_server_init(void);
+
+/***************************************************************************//**
+ * Processes BT Mesh stack events for Firmware Update Server application
+ *
+ * @param[in] evt BT Mesh event
+ ******************************************************************************/
+void sl_btmesh_firmware_update_server_on_event(sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * Executes a step of firmware verification
+ *
+ * @note Uses user API callouts.
+ ******************************************************************************/
+void sl_btmesh_firmware_update_server_verify_step_handle(void);
+
+/***************************************************************************//**
+ * Executes a step of metadata check
+ *
+ * @note Uses user API callouts.
+ ******************************************************************************/
+void sl_btmesh_firmware_update_server_metadata_check_step_handle(void);
+
+/***************************************************************************//**
+ * @} end mesh_fw_update_server
+ ******************************************************************************/
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * Check if BT Mesh Firmware Update Server allows the system to sleep
+ *
+ * @return If it is ok to sleep
+ * @retval true The system is allowed to sleep
+ * @retval false The system shall be kept awake
+ *
+ * The verification and metadata check runs in the main loop step by step and
+ * therefore the power manager shall not put the system into sleep mode while
+ * these operations are not completed.
+ ******************************************************************************/
+extern bool sl_btmesh_firmware_update_server_is_ok_to_sleep(void);
+
+#endif // SL_BTMESH_FIRMWARE_UPDATE_SERVER_H
diff --git a/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server_api.c b/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server_api.c
new file mode 100644
index 00000000000..29a27582936
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server_api.c
@@ -0,0 +1,337 @@
+/***************************************************************************//**
+ * @file
+ * @brief Weak implementations of user API functions
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#include
+#include "sl_btmesh.h"
+#include "sl_malloc.h"
+#include "app_assert.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+#include "sl_btmesh_wstk_lcd.h"
+#include
+// No verification progress is written to the LCD
+#define FW_VERIFY_PRG_KB_ON_LCD_UNKNOWN 0xFFFFFFFF
+// Last verification progress written to the LCD
+static uint32_t fw_verify_prg_kb_on_lcd = FW_VERIFY_PRG_KB_ON_LCD_UNKNOWN;
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+
+#include "sl_simple_timer.h"
+
+#include "btl_interface.h"
+#include "sl_btmesh_blob_storage.h"
+
+#include "sl_btmesh_firmware_update_server_api.h"
+#include "sl_btmesh_firmware_update_server_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/// Delay between NVM erase and node reset initiated by apply,
+/// and installation of firmware
+#define APPLY_DELAY 5
+
+/// Verification chunk size of the bootloader implementation
+#define BOOTLOADER_VERIFICATION_CHUNK_SIZE 128
+
+/// Returns the string representation of BLOB ID in a compound literal.
+/// WARNING! This macro shall be used as a parameter of log calls only due to the
+/// lifetime of underlying compound literal in APP_BTMESH_UUID_64_TO_STRING.
+#define BLOB_ID_TO_STRING(blob_id) APP_BTMESH_UUID_64_TO_STRING(blob_id, ' ', true)
+
+/// Context pointer for verification
+static void *context;
+
+/// Countdown for apply delay
+static uint8_t apply_cntdwn;
+
+/// Cache for the ID of the BLOB used for the update
+///
+/// Initialized to all ones indicating unknown.
+static sl_bt_uuid_64_t blob_id_cache = { .data = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF } };
+
+/***************************************************************************//**
+ * Used to delay bootloader initiation, as NVM erase needs time.
+ *
+ * @param handle Timer handler
+ * @param data Callback data
+ ******************************************************************************/
+static void apply_step(sl_simple_timer_t *handle, void *data);
+
+SL_WEAK sl_btmesh_fw_update_server_verify_state_t
+sl_btmesh_fw_update_server_verify_start(sl_bt_uuid_64_t const *const blob_id,
+ uint8_t ** const verify_chunk_buffer,
+ uint32_t *const verify_chunk_size,
+ const uint32_t blob_size)
+{
+ (void)blob_size;
+ uint32_t slot_id;
+ sl_btmesh_blob_storage_slot_metadata_cache_t const *cache;
+ uint32_t len;
+
+ // No verification buffer is used
+ *verify_chunk_buffer = NULL;
+ // Progress calculation is based on chunk size
+ *verify_chunk_size = BOOTLOADER_VERIFICATION_CHUNK_SIZE;
+ sl_btmesh_blob_storage_get_cache(&cache, &len);
+
+ for (slot_id = 0; slot_id < len; ++slot_id) {
+ if (0 == memcmp(blob_id, &cache->blob_id, sizeof(sl_bt_uuid_64_t))) {
+ break;
+ }
+ }
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("FW Update Verify", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_STATUS_CFG_VAL);
+ fw_verify_prg_kb_on_lcd = FW_VERIFY_PRG_KB_ON_LCD_UNKNOWN;
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+
+ if (slot_id == len) {
+ return BTMESH_FW_UPDATE_SERVER_VERIFY_ERROR;
+ }
+
+ context = sl_malloc(BOOTLOADER_STORAGE_VERIFICATION_CONTEXT_SIZE);
+
+ app_assert(context != NULL, "No verification buffer could be allocated!");
+
+ if (BOOTLOADER_OK
+ != bootloader_initVerifyImage(slot_id,
+ context,
+ BOOTLOADER_STORAGE_VERIFICATION_CONTEXT_SIZE)) {
+ sl_free(context);
+ return BTMESH_FW_UPDATE_SERVER_VERIFY_ERROR;
+ }
+ return BTMESH_FW_UPDATE_SERVER_VERIFY_PENDING;
+}
+
+SL_WEAK sl_btmesh_fw_update_server_verify_state_t
+sl_btmesh_fw_update_server_verify_step(void const *data,
+ const uint32_t len)
+{
+ (void)data;
+ (void)len;
+ int32_t sc = bootloader_continueVerifyImage(context, NULL);
+ switch (sc) {
+ case BOOTLOADER_ERROR_PARSE_SUCCESS:
+ sl_free(context);
+ return BTMESH_FW_UPDATE_SERVER_VERIFY_SUCCESS;
+ case BOOTLOADER_ERROR_PARSE_CONTINUE: {
+ return BTMESH_FW_UPDATE_SERVER_VERIFY_PENDING;
+ }
+ default:
+ sl_free(context);
+
+ bool blob_managed = sl_btmesh_blob_storage_is_managed(&blob_id_cache);
+ if (blob_managed == false) {
+ // Verification failed, unmanaged BLOB is not useful any more
+ sl_btmesh_blob_storage_invalidate(&blob_id_cache);
+ } else {
+ // If the node is a Distributor and an Updating Node as well then the
+ // BLOB shall not be invalidated because it is part of the firmware list
+ // of the Distributor and therefore it can be invalidated by the Initiator
+ // only (explicitly)
+ }
+ log_error("Verification failed (0x%lX)!" NL, sc);
+ return BTMESH_FW_UPDATE_SERVER_VERIFY_ERROR;
+ }
+}
+
+SL_WEAK void sl_btmesh_fw_update_server_verify_progress_ui_update(sl_btmesh_fw_update_server_verify_state_t status,
+ uint32_t progress,
+ uint32_t size)
+{
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ switch (status) {
+ case BTMESH_FW_UPDATE_SERVER_VERIFY_PENDING:
+ {
+ char str[LCD_ROW_LEN] = { 0 };
+ uint32_t fw_verify_prg_kb = (progress + 512) >> 10;
+ uint32_t fw_verify_size_kb = (size + 512) >> 10;
+ if ((FW_VERIFY_PRG_KB_ON_LCD_UNKNOWN == fw_verify_prg_kb_on_lcd)
+ || (fw_verify_size_kb == fw_verify_prg_kb)
+ || (fw_verify_prg_kb_on_lcd < fw_verify_prg_kb)) {
+ snprintf(str,
+ LCD_ROW_LEN,
+ "%u kB / %u kB",
+ (uint16_t)fw_verify_prg_kb,
+ (uint16_t)fw_verify_size_kb);
+ sl_btmesh_LCD_write(str, SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL);
+ fw_verify_prg_kb_on_lcd = fw_verify_prg_kb;
+ }
+ break;
+ }
+
+ case BTMESH_FW_UPDATE_SERVER_VERIFY_SUCCESS:
+ sl_btmesh_LCD_write("Done", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL);
+ break;
+
+ default:
+ sl_btmesh_LCD_write("Error", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL);
+ break;
+ }
+#else
+ (void) status;
+ (void) progress;
+ (void) size;
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+}
+
+SL_WEAK sl_btmesh_fw_update_server_metadata_check_state_t
+sl_btmesh_fw_update_server_metadata_check_start(void const *metadata,
+ const uint8_t len,
+ sl_btmesh_fw_update_server_additional_information_t *const additional_information)
+{
+ (void)metadata;
+ (void)len;
+ *additional_information = BTMESH_FW_UPDATE_SERVER_ADDITIONAL_INFORMATION_UNPROVISION;
+ return BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_PENDING;
+}
+
+SL_WEAK sl_btmesh_fw_update_server_metadata_check_state_t
+sl_btmesh_firmware_update_server_metadata_check_step(sl_btmesh_fw_update_server_additional_information_t *const additional_information)
+{
+ *additional_information = BTMESH_FW_UPDATE_SERVER_ADDITIONAL_INFORMATION_UNPROVISION;
+ return BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_SUCCESS;
+}
+
+SL_WEAK void sl_btmesh_fw_update_server_update_start(sl_bt_uuid_64_t const *const blob_id)
+{
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("Firmware Update", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_STATUS_CFG_VAL);
+ sl_btmesh_LCD_write("Started", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ memcpy(&blob_id_cache, blob_id, sizeof(blob_id_cache));
+ return;
+}
+
+SL_WEAK void sl_btmesh_fw_update_server_update_canceled(void)
+{
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("Canceled", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ return;
+}
+
+SL_WEAK void sl_btmesh_fw_update_server_update_aborted(void)
+{
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("Aborted", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ return;
+}
+
+SL_WEAK void sl_btmesh_fw_update_server_apply(void)
+{
+ sl_btmesh_blob_storage_slot_metadata_cache_t const *cache;
+ uint32_t len;
+ uint32_t idx;
+ static sl_simple_timer_t timer;
+
+ sl_btmesh_blob_storage_get_cache(&cache, &len);
+
+ // Find index corresponding to input BLOB ID
+ for (idx = 0;
+ (idx < len) && (0 != memcmp(&blob_id_cache,
+ &cache[idx].blob_id,
+ sizeof(sl_bt_uuid_64_t)));
+ ++idx) {
+ ;
+ }
+
+ if (idx != len) {
+ // Invalidate slot, as it's not useful anymore
+ sl_status_t sc = sl_btmesh_blob_storage_invalidate(&cache[idx].blob_id);
+ log_status_error_f(sc,
+ "Could not invalidate BLOB %s" NL,
+ BLOB_ID_TO_STRING(&cache[idx].blob_id));
+ }
+
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("FW Update Applying", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_STATUS_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ if (BOOTLOADER_OK == bootloader_setImageToBootload(idx)) {
+ // Reset node
+ sl_btmesh_node_reset();
+ // Erase NVM data
+ sl_bt_nvm_erase_all();
+ // Delay install
+ sl_simple_timer_start(&timer, 1000, apply_step, NULL, true);
+ apply_cntdwn = APPLY_DELAY;
+ }
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ else {
+ sl_btmesh_LCD_write("Failed", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL);
+ }
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+}
+
+static void apply_step(sl_simple_timer_t *handle, void *data)
+{
+ (void)handle;
+ (void)data;
+ if (--apply_cntdwn > 0) {
+ log_info("%d..." NL, apply_cntdwn);
+ return;
+ }
+ log_info("0" NL);
+#ifdef SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ sl_btmesh_LCD_write("Restarting...", SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL);
+#endif // SL_CATALOG_BTMESH_WSTK_LCD_PRESENT
+ // Initiate reboot with new firmware
+ bootloader_rebootAndInstall();
+}
+
+/*******************************************************************************
+ * Used to retrieve information about firmware stored on the device for DFU
+ ******************************************************************************/
+SL_WEAK sl_status_t mesh_platform_get_installed_firmware_information(uint8_t index,
+ uint8_t *fwid_len,
+ const uint8_t **fwid_ptr,
+ uint8_t *uri_len,
+ const uint8_t **uri_ptr)
+{
+ if (index > 0) {
+ return SL_STATUS_BT_MESH_DOES_NOT_EXIST;
+ }
+ *fwid_ptr = (const uint8_t*) SL_BTMESH_FW_UPDATE_SERVER_CID_LSB_CFG_VAL
+ SL_BTMESH_FW_UPDATE_SERVER_CID_MSB_CFG_VAL
+ SL_BTMESH_FW_UPDATE_SERVER_FWID_CFG_VAL;
+ *fwid_len = strlen((char*)*fwid_ptr);
+ *uri_ptr = (const uint8_t*) SL_BTMESH_FW_UPDATE_SERVER_UPDATE_URI_CFG_VAL;
+ *uri_len = strlen((char*)*uri_ptr);
+ return SL_STATUS_OK;
+}
diff --git a/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server_api.h b/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server_api.h
new file mode 100644
index 00000000000..44d6139dfc3
--- /dev/null
+++ b/app/btmesh/common/btmesh_firmware_update_server/sl_btmesh_firmware_update_server_api.h
@@ -0,0 +1,234 @@
+/***************************************************************************//**
+ * @file
+ * @brief Definitions of user API features
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_FIRMWARE_UPDATE_SERVER_API_H
+#define SL_BTMESH_FIRMWARE_UPDATE_SERVER_API_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup mesh_fw_update_server
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup mesh_fw_update_server_api
+ *
+ * BT Mesh Firmware Update Server user-overridable API
+ * @{
+ ******************************************************************************/
+
+/// Verification step results
+typedef enum sl_btmesh_fw_update_server_verify_state_e {
+ /// Verification pending
+ BTMESH_FW_UPDATE_SERVER_VERIFY_PENDING,
+ /// Verification error
+ BTMESH_FW_UPDATE_SERVER_VERIFY_ERROR,
+ /// Verification successful
+ BTMESH_FW_UPDATE_SERVER_VERIFY_SUCCESS
+} sl_btmesh_fw_update_server_verify_state_t;
+
+/// Metadata check step result
+typedef enum sl_btmesh_fw_update_server_metadata_check_state_e {
+ /// Metadata check pedning
+ BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_PENDING,
+ /// Metadata check error
+ BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_ERROR,
+ /// Metadata check successful
+ BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_SUCCESS
+} sl_btmesh_fw_update_server_metadata_check_state_t;
+
+/// Definitions taken from BT Mesh Specification v1.1
+typedef enum sl_btmesh_fw_update_server_additional_information_e {
+ /// No changes to node composition data.
+ BTMESH_FW_UPDATE_SERVER_ADDITIONAL_INFORMATION_NO_CHANGE = 0x0,
+ /// Node composition data changed. The node does not support remote
+ /// provisioning.
+ BTMESH_FW_UPDATE_SERVER_ADDITIONAL_INFORMATION_COMP_DATA_CHANGE_NO_REMOTE_PROV = 0x1,
+ /// Node composition data changed, and remote provisioning is supported. The
+ /// node supports remote provisioning and composition data page 0x80. Page
+ /// 0x80 contains different composition data than page 0x0.
+ BTMESH_FW_UPDATE_SERVER_ADDITIONAL_INFORMATION_COMP_DATA_CHANGE_REMOTE_PROV = 0x2,
+ /// Node unprovisioned. The node is unprovisioned after successful application
+ /// of a verified firmware image.
+ BTMESH_FW_UPDATE_SERVER_ADDITIONAL_INFORMATION_UNPROVISION = 0x3,
+ /// Start of reserved values
+ BTMESH_FW_UPDATE_SERVER_ADDITIONAL_INFORMATION_RESERVED_START = 0x4
+} sl_btmesh_fw_update_server_additional_information_t;
+
+/***************************************************************************//**
+ * User callback for determining the maximum chunk size of verification
+ *
+ * Used to determine how big chunks can be for
+ * @ref sl_btmesh_fw_update_server_verify_step
+ *
+ * @param[in] blob_id Identifier of the BLOB to be verified
+ * @param[out] verify_chunk_buffer Verification buffer. User needs to pass the
+ * verification buffer to the framework. User
+ * also needs to free memory if it was allocated
+ * on heap, which could be done in
+ * @ref sl_btmesh_fw_update_server_apply.
+ * If it's set to NULL, than no data is read
+ * from storage automatically during
+ * verification.
+ * @param[out] verify_chunk_size Size of verification chunk. Progress
+ * calculation is based on this value.
+ * @param[in] blob_size Size of the BLOB to be verified
+ *
+ * @return Current state of verification
+ * @retval BTMESH_FW_UPDATE_SERVER_VERIFY_PENDING If further processing required
+ * @retval BTMESH_FW_UPDATE_SERVER_VERIFY_ERROR In case of an error
+ * @retval BTMESH_FW_UPDATE_SERVER_VERIFY_SUCCESS In case verification is done and
+ * successful
+ ******************************************************************************/
+sl_btmesh_fw_update_server_verify_state_t
+sl_btmesh_fw_update_server_verify_start(sl_bt_uuid_64_t const *const blob_id,
+ uint8_t **const verify_chunk_buffer,
+ uint32_t *const verify_chunk_size,
+ const uint32_t blob_size);
+
+/***************************************************************************//**
+ * User callback to execute one step of the verification
+ *
+ * Receives a chunk of data equal to or less in size defined with
+ * @ref sl_btmesh_fw_update_server_verify_start
+ *
+ * @param[in] data Buffered data
+ * @param[in] len Length of the buffer
+ *
+ * @return Current state of verification
+ * @retval BTMESH_FW_UPDATE_SERVER_VERIFY_PENDING If further processing required
+ * @retval BTMESH_FW_UPDATE_SERVER_VERIFY_ERROR In case of an error
+ * @retval BTMESH_FW_UPDATE_SERVER_VERIFY_SUCCESS In case verification is done and
+ * successful
+ ******************************************************************************/
+sl_btmesh_fw_update_server_verify_state_t
+sl_btmesh_fw_update_server_verify_step(void const *data, const uint32_t len);
+
+/***************************************************************************//**
+ * User callback to update the user interface with verification progress
+ *
+ * @param[in] status Verification status
+ * @param[in] progress Number of verified bytes
+ * @param[in] size Total number of bytes which shall be verified
+ *
+ * If @p FW_UPDATE_SERVER_VERIFY_PROGRESS_UI_UPDATE_PERIOD is not zero then it
+ * specifies the period of this function call in order to limit the user
+ * interface update rate. If the macro is zero then this function is called
+ * whenever verification is active and progress is made.
+ ******************************************************************************/
+extern void
+sl_btmesh_fw_update_server_verify_progress_ui_update(sl_btmesh_fw_update_server_verify_state_t status,
+ uint32_t progress,
+ uint32_t size);
+
+/***************************************************************************//**
+ * User callback indicating start of metadata check
+ *
+ * @param[in] metadata Buffered metadata
+ * @param[in] len Metadata length
+ * @param[out] additional_information Used in BT Mesh response to metadata check
+ *
+ * @return Current state of metadata check
+ * @retval BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_PENDING If further processing required
+ * @retval BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_ERROR In case of an error
+ * @retval BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_SUCCESS In case metadata check is done
+ * and successful
+ ******************************************************************************/
+sl_btmesh_fw_update_server_metadata_check_state_t
+sl_btmesh_fw_update_server_metadata_check_start(void const *metadata,
+ const uint8_t len,
+ sl_btmesh_fw_update_server_additional_information_t *const additional_information);
+
+/***************************************************************************//**
+ * User callback executing one step of metadata check
+ *
+ * @param[out] additional_information Used in BT Mesh response to metadata check
+ *
+ * @return Current state of metadata check
+ * @retval BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_PENDING If further processing required
+ * @retval BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_ERROR In case of an error
+ * @retval BTMESH_FW_UPDATE_SERVER_METADATA_CHECK_SUCCESS In case metadata check is done
+ * and successful
+ ******************************************************************************/
+sl_btmesh_fw_update_server_metadata_check_state_t
+sl_btmesh_firmware_update_server_metadata_check_step(sl_btmesh_fw_update_server_additional_information_t *additional_information);
+
+/***************************************************************************//**
+ * User callback indicating update start
+ *
+ * Called after metadata check is done.
+ *
+ * Can be redefined to accommodate user application functionality.
+ *
+ * @param[in] blob_id Identifier of the BLOB containing firmware
+ ******************************************************************************/
+void sl_btmesh_fw_update_server_update_start(sl_bt_uuid_64_t const *const blob_id);
+
+/***************************************************************************//**
+ * User callback indicating update cancellation
+ *
+ * Can be redefined to accommodate user application functionality.
+ ******************************************************************************/
+void sl_btmesh_fw_update_server_update_canceled(void);
+
+/***************************************************************************//**
+ * User callback indicating update abort
+ *
+ * Can be redefined to accommodate user application functionality.
+ ******************************************************************************/
+void sl_btmesh_fw_update_server_update_aborted(void);
+
+/***************************************************************************//**
+ * User callback indicating firmware apply request
+ *
+ * Can be redefined to accommodate user application functionality.
+ ******************************************************************************/
+void sl_btmesh_fw_update_server_apply(void);
+
+/***************************************************************************//**
+ * Used to retrieve information about firmware stored on the device for DFU
+ ******************************************************************************/
+sl_status_t mesh_platform_get_installed_firmware_information(uint8_t index,
+ uint8_t *fwid_len,
+ const uint8_t **fwid_ptr,
+ uint8_t *uri_len,
+ const uint8_t **uri_ptr);
+
+/** @} end mesh_fw_update_server_api */
+
+/** @} end mesh_fw_update_server */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // SL_BTMESH_FIRMWARE_UPDATE_SERVER_API_H
diff --git a/app/btmesh/common/btmesh_friend/config/sl_btmesh_friend_config.h b/app/btmesh/common/btmesh_friend/config/sl_btmesh_friend_config.h
new file mode 100644
index 00000000000..6eaf0dd01bd
--- /dev/null
+++ b/app/btmesh/common/btmesh_friend/config/sl_btmesh_friend_config.h
@@ -0,0 +1,48 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_FRIEND_CONFIG_H
+#define SL_BTMESH_FRIEND_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Friend configuration
+
+// Enable Logging
+// Default: 1
+// Enable or disable Logging for Friend specific messages for this component.
+#define SL_BTMESH_FRIEND_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_FRIEND_CONFIG_H
diff --git a/app/btmesh/common/btmesh_friend/sl_btmesh_friend.c b/app/btmesh/common/btmesh_friend/sl_btmesh_friend.c
new file mode 100644
index 00000000000..f529da56679
--- /dev/null
+++ b/app/btmesh/common/btmesh_friend/sl_btmesh_friend.c
@@ -0,0 +1,123 @@
+/***************************************************************************//**
+ * @file
+ * @brief Friend implementation
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#include
+
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_friend.h"
+#include "sl_btmesh_friend_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup friend
+ * @{
+ ******************************************************************************/
+
+/*******************************************************************************
+ * Initialize LPN functionality with configuration and friendship establishment.
+ ******************************************************************************/
+void sl_btmesh_friend_feature_init(void)
+{
+ sl_status_t result = 0;
+
+ //Initialize Friend functionality
+ log_info("Friend mode initialization" NL);
+ result = sl_btmesh_friend_init();
+ log_status_error_f(result, "Friend initialization failed" NL);
+}
+
+/*******************************************************************************
+ * Handling of mesh friend events.
+ *
+ * @param[in] evt Pointer to incoming friend event.
+ ******************************************************************************/
+void sl_btmesh_friend_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_friend_feature_init();
+ }
+ break;
+
+ case sl_btmesh_evt_friend_friendship_established_id:
+ sl_btmesh_friend_on_friendship_established(
+ evt->data.evt_friend_friendship_established.netkey_index,
+ evt->data.evt_friend_friendship_established.lpn_address);
+ break;
+
+ case sl_btmesh_evt_friend_friendship_terminated_id:
+ sl_btmesh_friend_on_friendship_terminated(
+ evt->data.evt_friend_friendship_terminated.netkey_index,
+ evt->data.evt_friend_friendship_terminated.lpn_address,
+ evt->data.evt_friend_friendship_terminated.reason);
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_friend_feature_init();
+ break;
+
+ default:
+ break;
+ }
+}
+
+// Weak implementation of Callbacks
+SL_WEAK void sl_btmesh_friend_on_friendship_established(uint16_t netkey_index,
+ uint16_t lpn_address)
+{
+ (void) netkey_index;
+ (void) lpn_address;
+}
+
+SL_WEAK void sl_btmesh_friend_on_friendship_terminated(uint16_t netkey_index,
+ uint16_t lpn_address,
+ uint16_t reason)
+{
+ (void) netkey_index;
+ (void) lpn_address;
+ (void) reason;
+}
+
+/** @} (end addtogroup friend) */
diff --git a/app/btmesh/common/btmesh_friend/sl_btmesh_friend.h b/app/btmesh/common/btmesh_friend/sl_btmesh_friend.h
new file mode 100644
index 00000000000..4951958dc27
--- /dev/null
+++ b/app/btmesh/common/btmesh_friend/sl_btmesh_friend.h
@@ -0,0 +1,90 @@
+/***************************************************************************//**
+ * @file
+ * @brief Friend feature header
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_FRIEND_H
+#define SL_BTMESH_FRIEND_H
+
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * @defgroup friend Friend Component
+ * @brief Friend feature Implementation
+ * This component implements Friend feature.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup friend
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Initialize FRIEND functionality. This function is called automatically by the
+ * FRIEND component.
+ *
+ ******************************************************************************/
+void sl_btmesh_friend_feature_init(void);
+
+/***************************************************************************//**
+ * Handling of mesh friend events.
+ *
+ * @param[in] evt Pointer to incoming friend event.
+ ******************************************************************************/
+void sl_btmesh_friend_on_event(sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * Called when the Friend Node establishes friendship with another node.
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] netkey_index Index of the network key used in friendship
+ * @param[in] lpn_address Low Power Node address
+ ******************************************************************************/
+void sl_btmesh_friend_on_friendship_established(uint16_t netkey_index,
+ uint16_t lpn_address);
+
+/***************************************************************************//**
+ * Called when the friendship that was successfully established with a Low Power
+ * Node has been terminated.
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] netkey_index Index of the network key used in friendship
+ * @param[in] lpn_address Low Power Node address
+ * @param[in] reason Reason for friendship termination
+ ******************************************************************************/
+void sl_btmesh_friend_on_friendship_terminated(uint16_t netkey_index,
+ uint16_t lpn_address,
+ uint16_t reason);
+
+/** @} (end addtogroup friend) */
+
+#endif /* SL_BTMESH_FRIEND_H */
diff --git a/app/btmesh/common/btmesh_fw_distribution_server/btmesh_fw_distribution_server.dcd b/app/btmesh/common/btmesh_fw_distribution_server/btmesh_fw_distribution_server.dcd
new file mode 100644
index 00000000000..b69d67d9403
--- /dev/null
+++ b/app/btmesh/common/btmesh_fw_distribution_server/btmesh_fw_distribution_server.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1404", "name":"Device Firmware Update Distributor Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_fw_distribution_server/config/sl_btmesh_fw_distribution_server_config.h b/app/btmesh/common/btmesh_fw_distribution_server/config/sl_btmesh_fw_distribution_server_config.h
new file mode 100644
index 00000000000..85b23963630
--- /dev/null
+++ b/app/btmesh/common/btmesh_fw_distribution_server/config/sl_btmesh_fw_distribution_server_config.h
@@ -0,0 +1,91 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Firmware Distribution Server Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_FW_DISTRIBUTION_SERVER_CONFIG_H
+#define SL_BTMESH_FW_DISTRIBUTION_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// BT Mesh Firmware Distribution Server Configuration
+
+// Enable Logging
+// Default: 1
+// Enable / disable logging of Firmware Distribution Server model specific messages
+#define SL_BTMESH_FW_DIST_SERVER_LOGGING_CFG_VAL (1)
+
+// Enable BT Mesh Stack Platform Callback Logging
+// Default: 0
+// Enable / disable logging of BT Mesh Stack Firmware Distribution Server model platform callbacks.
+// The FW Distribution Server model in BT Mesh stack calls platform callback functions to query the remaining space, firmware count and firmware information.
+#define SL_BTMESH_FW_DIST_SERVER_PLATFORM_CALLBACK_LOGGING_CFG_VAL (0)
+
+// Fwid and metadata log format
+// Hex format
+// Text format
+// Fwid and metada logging format, hex or text format is available
+#define SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX false
+
+// Text prepended to every log message
+// Default: "FwDistributor"
+// Every log message in the component is started with this text.
+#define SL_BTMESH_FW_DIST_SERVER_LOG_PREFIX_CFG_VAL "FwDistributor"
+
+//
+
+// Max node list size
+// <1-65535:1>
+// Default: 8
+// Maximum number of firmware update server nodes which can participate in the distribution
+#define SL_BTMESH_FW_DIST_SERVER_MAX_NODE_LIST_SIZE_CFG_VAL (8)
+
+// Default Multicast Threshold
+// <1-65535:1>
+// Default: 1
+// If the number of servers for any step exceeds or is equal to this number then the group address will be used,
+// otherwise servers will be looped through one by one. Value of 0 disables the feature.
+#define SL_BTMESH_FW_DIST_SERVER_MULTICAST_THRESHOLD_DEFAULT_CFG_VAL (1)
+
+// Retry time of message transmissions
+// <0-65535:1>
+// Default: 3000
+// Retry time of firmware update message transmissions
+#define SL_BTMESH_FW_DIST_SERVER_RETRY_TIME_MS_CFG_VAL (3000)
+
+// NVM key of the firmware list
+// <0x0000-0xFFFF>
+// Default: 0x4009
+// NVM key of the firmware list
+#define SL_BTMESH_FW_DIST_SERVER_FW_LIST_NVM_KEY_CFG_VAL (0x4009)
+
+//
+
+// <<< end of configuration section >>>
+
+ #endif // SL_BTMESH_FW_DISTRIBUTION_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_fw_distribution_server/sl_btmesh_fw_distribution_server.c b/app/btmesh/common/btmesh_fw_distribution_server/sl_btmesh_fw_distribution_server.c
new file mode 100644
index 00000000000..39372558968
--- /dev/null
+++ b/app/btmesh/common/btmesh_fw_distribution_server/sl_btmesh_fw_distribution_server.c
@@ -0,0 +1,3543 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Firmware Distribution Server
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+// -----------------------------------------------------------------------------
+// Includes
+// -----------------------------------------------------------------------------
+
+#include
+
+#include "sl_status.h"
+#include "em_common.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+#include "sl_malloc.h"
+
+#include "sl_btmesh_blob_storage_app_id.h"
+#include "sl_btmesh_blob_storage.h"
+
+#include "sl_btmesh_blob_transfer_client.h"
+
+#include "sl_btmesh_capi_types.h"
+#include "sl_btmesh_dfu_platform_capi.h"
+#include "sl_btmesh_model_specification_v1_1_defs.h"
+
+#include "sl_btmesh_fw_distribution_server.h"
+#include "sl_btmesh_fw_distribution_server_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup dist_server BT Mesh Firmware Distribution Server
+ * @{
+ ******************************************************************************/
+
+// -----------------------------------------------------------------------------
+// Macros
+// -----------------------------------------------------------------------------
+
+// Shortcut for the log prefix with space character separator
+#define LOG_PREFIX SL_BTMESH_FW_DIST_SERVER_LOG_PREFIX_CFG_VAL " "
+
+#if (SL_BTMESH_FW_DIST_SERVER_PLATFORM_CALLBACK_LOGGING_CFG_VAL != 0)
+#define log_btmesh_platform_cb(...) log_debug(__VA_ARGS__)
+#else
+#define log_btmesh_platform_cb(...)
+#endif // (SL_BTMESH_FW_DIST_SERVER_PLATFORM_CALLBACK_LOGGING_CFG_VAL != 0)
+
+#if defined(SL_CATALOG_APP_LOG_PRESENT) && (APP_BTMESH_UTIL_COMPONENT_LOGGING != 0)
+#define log_fwid_level(level, fwid, fwid_len, hex_format) \
+ _log_fwid_level(level, fwid, fwid_len, hex_format)
+#define log_metadata_level(level, metadata, metadata_len, hex_format) \
+ _log_metadata_level(level, metadata, metadata_len, hex_format)
+#define log_bytes_as_text(level, bytes, len, null_replacement_char) \
+ _log_bytes_as_text(level, bytes, len, null_replacement_char)
+#define log_check_level(level) \
+ _app_log_check_level(level)
+#else
+#define log_fwid_level(level, fwid, fwid_len, hex_format)
+#define log_metadata_level(level, metadata, metadata_len, hex_format)
+#define log_bytes_as_text(level, bytes, len, null_replacement_char)
+#define log_check_level(level) false
+#endif // defined(SL_CATALOG_APP_LOG_PRESENT) && (APP_BTMESH_UTIL_COMPONENT_LOGGING != 0)
+
+// Temporary buffer to store byte array chunk
+#define LOG_BYTES_AS_TEXT_CHUNK_SIZE 32
+
+// Suppress compiler warning of unused static function
+#define SL_UNUSED __attribute__((unused))
+
+// Utility macro to calculate the length of an array (number of array elements)
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+// Maximum length of the firmware metadata based on the Mesh Model specification
+//
+// See Firmware Distribution Upload Start message in the specification for more
+// details
+#define DFU_METADATA_MAX_LEN MESH_DFU_METADATA_MAX_LEN
+
+// Maximum length of firmware identifier based on the Mesh Model specification
+//
+// See Firmware ID format in the Firmware update states chapter of the
+// specification.
+// 2 bytes company ID + 0..106 bytes Version Information => Total of 108 bytes
+#define DFU_FWID_MAX_LEN MESH_DFU_FWID_MAX_LEN
+
+// Minimum length of firmware identifier based on the Mesh Model specification
+#define DFU_FWID_MIN_LEN MESH_DFU_FWID_MIN_LEN
+
+// The node address zero is invalid according to the BT Mesh specification so
+// it can be used to initialize address variables
+#define UNASSIGNED_ADDRESS 0x0000
+
+// Size of the firmware list depends on the current number of stored firmwares
+#define FW_LIST_NVM_SIZE(fw_list_len) (sizeof(fw_dist_server_fw_list_nvm_t) \
+ + (fw_list_len) * sizeof(sl_bt_uuid_64_t))
+
+// Size of the firmware descriptor depends on the FW identifier length
+#define FW_DESCRIPTOR_SIZE(fwid_length) (sizeof(fw_dist_server_fw_descriptor_t) \
+ + fwid_length)
+
+// Checks if given server is operational, and returns retval if not
+#define SERVER_STATUS_CHECK(_srv, retval ...) \
+ if (((_srv) == NULL) || (_srv)->init_failed) \
+ return retval
+
+// Checks if given server is operational, and provides retval if not
+#define SERVER_STATUS_GET(_srv, retval) \
+ ((((_srv) == NULL) || ((_srv)->init_failed)) ? (retval) : SL_STATUS_OK)
+
+// Returns the string representation of BLOB ID in a compound literal.
+// WARNING! This macro shall be used as a parameter of log calls only due to the
+// lifetime of underlying compound literal in APP_BTMESH_UUID_64_TO_STRING.
+#define BLOB_ID_TO_STRING(blob_id) APP_BTMESH_UUID_64_TO_STRING(blob_id, ' ', true)
+
+// -----------------------------------------------------------------------------
+// Type definitions
+// -----------------------------------------------------------------------------
+
+typedef enum {
+ FW_BLOB_SELECTOR_STORAGE,
+ FW_BLOB_SELECTOR_CURRENT
+} fw_blob_selector_t;
+
+typedef enum {
+ UPLOAD_STATE_INACTIVE,
+ UPLOAD_STATE_TRANSFER_FAILED,
+ UPLOAD_STATE_TRANSFER_CANCELED,
+ UPLOAD_STATE_TRANSFER_SUCCESS,
+ UPLOAD_STATE_TRANSFER_ACTIVE,
+ UPLOAD_STATE_COUNT
+} upload_state_t;
+
+typedef struct {
+ bool idle : 1;
+ bool execute_step : 1;
+ bool retry : 1;
+} dist_state_flags_t;
+
+typedef struct {
+ bool idle : 1;
+} upload_state_flags_t;
+
+typedef struct {
+ uint32_t size;
+ sl_bt_uuid_64_t blob_id;
+ uint8_t *metadata;
+ uint8_t metadata_length;
+ uint8_t fwid_length;
+ uint8_t fwid[];
+} fw_dist_server_fw_descriptor_t;
+
+/// Upload process data representation
+typedef struct {
+ /// Status of the upload
+ upload_state_t state;
+ /// Firmware descriptor
+ fw_dist_server_fw_descriptor_t *fw_descriptor;
+ /// Metadata pointer (heap array)
+ uint8_t *temp_metadata;
+ /// Metadata length
+ uint8_t temp_metadata_length;
+ /// Metadata error flag
+ bool metadata_error : 1;
+ /// Managed flag error flag
+ bool managed_blob_error : 1;
+} fw_dist_server_upload_t;
+
+/// Distribution process data representation
+typedef struct {
+ /// Timer handler
+ struct sl_simple_timer timer;
+ /// Size of the FW being transferred
+ uint32_t fw_size;
+ /// Number of nodes currently distributing to
+ uint16_t node_count;
+ /// The number of active nodes.
+ /// Nodes the distributor currently distributes to, that haven't failed.
+ uint16_t num_active_nodes;
+ /// Index of the FW being transported
+ uint16_t fw_list_index;
+ /// Counter used for retriable operations
+ uint16_t retry_counter;
+ /// Current state of distribution
+ sl_btmesh_fw_dist_server_dist_step_t state;
+ /// BLOB ID of the distributed BLOB
+ ///
+ /// This ID is the ID which the distributor received the BLOB by.
+ /// @note Not the BLOB ID that's used during distribution.
+ sl_bt_uuid_64_t storage_blob_id;
+ /// Transfer mode for distribution specified by Initiator node
+ sl_btmesh_mbt_client_transfer_mode_t transfer_mode;
+ /// Flag indicating whether retry timer elapsed
+ bool retry_time_elapsed : 1;
+ /// Flag indicating whether BLOB transfer was started in current distribution
+ bool blob_transfer_started : 1;
+} fw_dist_server_dist_t;
+
+/// Distribution Server capabilities data representation
+typedef struct {
+ /// The size of the biggest image that could be stored on the device
+ uint32_t max_fw_image_size;
+ /// Sum of the available space in slots
+ uint32_t max_upload_space;
+ /// Maximum length of the FW list.
+ ///
+ /// The maximum number of possible FWs to store is the number of slots.
+ /// @see sl_btmesh_blob_storage_get_max_blob_count
+ uint16_t max_fw_list_length;
+} fw_dist_server_capabilities_t;
+
+/// Firmware list representation
+typedef PACKSTRUCT (struct {
+ /// Length of the list
+ uint16_t current_fw_list_length;
+ /// BLOB IDs of FW images
+ sl_bt_uuid_64_t blob_ids[];
+}) fw_dist_server_fw_list_nvm_t;
+
+/// Distributor Server data representation
+typedef struct {
+ /// Element belonging to the represented Server model
+ uint16_t elem_index;
+ /// RAM mirror of Firmware List data which is stored in NVM
+ fw_dist_server_fw_list_nvm_t *fw_list_nvm;
+ /// Firmware List data
+ fw_dist_server_fw_descriptor_t **fw_list;
+ /// Capabilities of the Server
+ fw_dist_server_capabilities_t *capabilities;
+ /// Upload related status variables
+ fw_dist_server_upload_t upload;
+ /// Distribution related status variables
+ fw_dist_server_dist_t dist;
+ /// The address of the client requesting delete stored for async. response
+ uint16_t async_req_client_address;
+ /// FW ID length of current single image deletion
+ uint8_t deleting_fwid_len;
+ /// FW ID of current single image deletion
+ uint8_t * deleting_fwid;
+ /// Flag indicating initialization failure
+ bool init_failed : 1;
+ /// Flag indicating storage corruption
+ bool storage_corrupted : 1;
+ /// Ongoing single image delete
+ bool deleting : 1;
+ /// Ongoing delete of all images
+ bool deleting_all : 1;
+} fw_dist_server_t;
+
+/// Linked list of distribution servers
+typedef struct fw_dist_server_list_s {
+ /// Distribution server descriptor
+ fw_dist_server_t server;
+ /// Next list element
+ struct fw_dist_server_list_s *next;
+} fw_dist_server_list_t;
+
+// -----------------------------------------------------------------------------
+// Static Function Declarations
+// -----------------------------------------------------------------------------
+
+/***************************************************************************//**
+ * Log byte array as text
+ *
+ * The byte arrays might not be null terminated so it is not a valid C string
+ * and therefore it can't be used directly with log macros. This function adds
+ * null character termination and replaces zero bytes with the provided
+ * replacement character.
+ *
+ * @param level Log level
+ * @param bytes Pointer to the byte array which shall be logged
+ * @param len Length of byte array
+ * @param null_replacement_char Zero bytes are replaced with this character
+ *
+ ******************************************************************************/
+SL_UNUSED static void _log_bytes_as_text(uint8_t level,
+ const uint8_t *bytes,
+ uint16_t len,
+ char null_replacement_char);
+
+/***************************************************************************//**
+ * Log firmware identifier
+ *
+ * @param level Log level
+ * @param fwid Pointer to the FW id byte array
+ * @param fwid_len Length of FW id byte array
+ * @param hex_format If it is set to true then hex format is used otherwise it
+ * is logged as a string
+ *
+ ******************************************************************************/
+SL_UNUSED static void _log_fwid_level(uint8_t level,
+ const uint8_t *fwid,
+ uint8_t fwid_len,
+ bool hex_format);
+
+/***************************************************************************//**
+ * Log metadata
+ *
+ * @param level Log level
+ * @param metadata Pointer to the metadata byte array
+ * @param metadata_len Length of metadata byte array
+ * @param hex_format If it is set to true then hex format is used otherwise it
+ * is logged as a string
+ *
+ ******************************************************************************/
+SL_UNUSED static void _log_metadata_level(uint8_t level,
+ const uint8_t *metadata,
+ uint8_t metadata_len,
+ bool hex_format);
+
+/***************************************************************************//**
+ * Transition Upload state machine into selected state
+ *
+ * @param self Pointer to Distribution Server descriptor sturcture
+ * @param target_state State to transition into
+ ******************************************************************************/
+static void upload_state_transition(fw_dist_server_t *const self,
+ const upload_state_t target_state);
+
+/***************************************************************************//**
+ * Initializes the distributor state vaiables
+ *
+ * @param[in] self Pointer to the Distributor Server data representing the
+ * current Distributor
+ ******************************************************************************/
+static void dist_init(fw_dist_server_t *const self);
+
+/***************************************************************************//**
+ * Notification handler for BLOB Transfer Client API
+ *
+ * @param notification Notification data
+ ******************************************************************************/
+static void handle_blob_transfer_client_notification(
+ const sl_btmesh_blob_transfer_client_notification_t *const notification);
+
+/***************************************************************************//**
+ * Transition Distribution state machine into selected state
+ *
+ * @param self Pointer to Distribution Server descriptor structure
+ * @param target_state State to transition into
+ ******************************************************************************/
+static void dist_state_transition(fw_dist_server_t *const self,
+ const sl_btmesh_fw_dist_server_dist_step_t target_state);
+
+/***************************************************************************//**
+ * Finds server in linked list belonging to elem_index
+ *
+ * @see fw_dist_server_list_t, fw_dist_server_list
+ *
+ * @param[in] elem_index Element index for the server
+ *
+ * @returns Pointer to the server descriptor
+ ******************************************************************************/
+static fw_dist_server_t* find_server(uint16_t elem_index);
+
+/***************************************************************************//**
+ * Invalidates the FW list in BLOB storage and clears the FW list in NVM and RAM
+ *
+ * @param[in] self Pointer to the Distributor Server data representing the
+ * current Distributor
+ ******************************************************************************/
+static sl_status_t fw_invalidate_all(fw_dist_server_t *const self);
+
+// -----------------------------------------------------------------------------
+// Static Variables
+// -----------------------------------------------------------------------------
+static const upload_state_flags_t upload_state_flags[UPLOAD_STATE_COUNT] = {
+ [UPLOAD_STATE_INACTIVE] = { .idle = 1 },
+ [UPLOAD_STATE_TRANSFER_FAILED] = { .idle = 1 },
+ [UPLOAD_STATE_TRANSFER_CANCELED] = { .idle = 1 },
+ [UPLOAD_STATE_TRANSFER_SUCCESS] = { .idle = 1 },
+ [UPLOAD_STATE_TRANSFER_ACTIVE] = { .idle = 0 },
+};
+
+static const dist_state_flags_t dist_state_flags[] = {
+ [sl_btmesh_fw_dist_server_dist_step_idle] = { .idle = 1, .execute_step = 0, .retry = 0 },
+ [sl_btmesh_fw_dist_server_dist_step_starting_update] = { .idle = 0, .execute_step = 1, .retry = 1 },
+ [sl_btmesh_fw_dist_server_dist_step_transferring_image] = { .idle = 0, .execute_step = 0, .retry = 0 },
+ [sl_btmesh_fw_dist_server_dist_step_checking_verification] = { .idle = 0, .execute_step = 1, .retry = 1 },
+ [sl_btmesh_fw_dist_server_dist_step_waiting_for_apply] = { .idle = 0, .execute_step = 0, .retry = 0 },
+ [sl_btmesh_fw_dist_server_dist_step_applying_update] = { .idle = 0, .execute_step = 1, .retry = 1 },
+ [sl_btmesh_fw_dist_server_dist_step_checking_update_result] = { .idle = 0, .execute_step = 1, .retry = 1 },
+ [sl_btmesh_fw_dist_server_dist_step_completed] = { .idle = 1, .execute_step = 0, .retry = 0 },
+ [sl_btmesh_fw_dist_server_dist_step_failed] = { .idle = 1, .execute_step = 0, .retry = 0 },
+ [sl_btmesh_fw_dist_server_dist_step_cancelling] = { .idle = 0, .execute_step = 1, .retry = 1 },
+ [sl_btmesh_fw_dist_server_dist_step_suspended] = { .idle = 0, .execute_step = 0, .retry = 0 },
+};
+
+/// Linked list of available servers
+static fw_dist_server_list_t *fw_dist_server_list = NULL;
+/// Common storage capabilities descriptor
+static fw_dist_server_capabilities_t fw_dist_server_capabilities = { 0 };
+
+static void _log_bytes_as_text(uint8_t level,
+ const uint8_t *bytes,
+ uint16_t len,
+ char null_replacement_char)
+{
+ const uint16_t temp_str_size = LOG_BYTES_AS_TEXT_CHUNK_SIZE;
+ char temp_buffer[LOG_BYTES_AS_TEXT_CHUNK_SIZE + 1];
+
+ // If the logging is disabled then the buffer is not used so the compiler
+ // warning is suppressed because this function is not used at all in case of
+ // disabled logging
+ (void) temp_buffer;
+
+ // There is no terminating zero character at the end of bytes and it might
+ // contain null characters which might terminate the logging early
+ if (log_check_level(level) && (bytes != NULL)) {
+ uint16_t remaining_len = len;
+ uint16_t offset = 0;
+ while (0 < remaining_len) {
+ uint16_t chunk_len = (remaining_len < temp_str_size) ? remaining_len : temp_str_size;
+ uint16_t idx;
+ for (idx = 0; idx < chunk_len; idx++) {
+ temp_buffer[idx] = (bytes[offset] != '\0') ? bytes[offset] : null_replacement_char;
+ offset++;
+ }
+ temp_buffer[idx] = '\0';
+ log_append(temp_buffer);
+ remaining_len -= chunk_len;
+ }
+ }
+ (void)level;
+}
+
+static void _log_fwid_level(uint8_t level,
+ const uint8_t *fwid,
+ uint8_t fwid_len,
+ bool hex_format)
+{
+ // This function is not used when the logging is turned off
+ (void) level;
+
+ if ((fwid != NULL) && (fwid_len >= DFU_FWID_MIN_LEN)) {
+ if (hex_format) {
+ log_hexdump_level_s(level, "", fwid, fwid_len);
+ } else {
+ log_hexdump_level_s(level, "", fwid, DFU_FWID_MIN_LEN);
+ log_bytes_as_text(level,
+ &fwid[DFU_FWID_MIN_LEN],
+ fwid_len - DFU_FWID_MIN_LEN,
+ ' ');
+ }
+ }
+}
+
+static void _log_metadata_level(uint8_t level,
+ const uint8_t *metadata,
+ uint8_t metadata_len,
+ bool hex_format)
+{
+ // This function is not used when the logging is turned off
+ (void) level;
+ (void) metadata_len;
+
+ if (metadata != NULL) {
+ if (hex_format) {
+ log_hexdump_level(level, metadata, metadata_len);
+ } else {
+ log_bytes_as_text(level, metadata, metadata_len, ' ');
+ }
+ }
+}
+
+static fw_dist_server_t* find_server(uint16_t elem_index)
+{
+ fw_dist_server_list_t *head = fw_dist_server_list;
+ if (head == NULL) {
+ return NULL;
+ }
+ do {
+ if (head->server.elem_index == elem_index) {
+ return &head->server;
+ }
+ } while (NULL != (head = head->next));
+ return NULL;
+}
+
+static void dist_retry_timer_cb(sl_simple_timer_t *timer, void *data)
+{
+ (void) timer;
+ fw_dist_server_t *const self = data;
+
+ self->dist.retry_time_elapsed = true;
+ // If the retry timer elapses, then the current state shall be reentered to
+ // execute the state entry behavior again (self transition)
+ // Note: the retry timer is only started in states which supports retry, so it
+ // is not necessary to check that here
+ dist_state_transition(self, self->dist.state);
+
+ app_assert(NULL != data, "The data is NULL in retry timer callback");
+}
+
+static void dist_start_retry_timer(fw_dist_server_t *const self)
+{
+ sl_status_t sc;
+
+ // There is no tx complete event in the distributor which signals whether the
+ // mesh messages are sent or they are still in the message queue. Therefore it
+ // does not really make sense to send messages without waiting some time.
+ //
+ // Wait the retry time to provide some time for the servers to respond. This
+ // also spares bandwidth, because the Fw Distributor does not flood the mesh
+ // network with messages.
+ sc = sl_simple_timer_start(&self->dist.timer,
+ SL_BTMESH_FW_DIST_SERVER_RETRY_TIME_MS_CFG_VAL,
+ dist_retry_timer_cb,
+ self,
+ false);
+ app_assert_status_f(sc,
+ "Failed to dist start retry timer (elem=%d)",
+ self->elem_index);
+}
+
+static void dist_stop_retry_timer(fw_dist_server_t *const self)
+{
+ // It is not considered an error if stop is requested for a timer which is not
+ // running, therefore stop is called here always to be safe.
+ sl_status_t sc = sl_simple_timer_stop(&self->dist.timer);
+
+ app_assert_status_f(sc,
+ "Failed to stop dist retry timer (elem=%d)",
+ self->elem_index);
+}
+
+static uint32_t calc_fw_storage_max_app_footer_size(void)
+{
+ // Max number of storage bytes used to store FWID in BLOB storage
+ uint32_t fwid_footer_size =
+ sl_btmesh_blob_storage_calc_app_footer_size(DFU_FWID_MAX_LEN);
+
+ // Max number of storage bytes used to store metadata in BLOB storage
+ uint32_t metadata_footer_size =
+ sl_btmesh_blob_storage_calc_app_footer_size(DFU_METADATA_MAX_LEN);
+
+ // Max number of storage bytes used to store managed flag in BLOB storage
+ uint32_t managed_flag_size = sl_btmesh_blob_storage_get_managed_flag_size();
+
+ // Max number of storage bytes used to store distribution server specific
+ // data in BLOB storage in case of a single BLOB
+ return fwid_footer_size + metadata_footer_size + managed_flag_size;
+}
+
+static uint32_t calc_fw_storage_max_fw_image_size(void)
+{
+ uint32_t max_fw_image_size = sl_btmesh_blob_storage_get_max_blob_size();
+ uint32_t max_app_footer_size = calc_fw_storage_max_app_footer_size();
+
+ if (max_app_footer_size < max_fw_image_size) {
+ max_fw_image_size -= max_app_footer_size;
+ } else {
+ max_fw_image_size = 0;
+ }
+ return max_fw_image_size;
+}
+
+static uint32_t calc_fw_storage_max_fw_image_size_free(void)
+{
+ uint32_t max_fw_image_size_free = sl_btmesh_blob_storage_get_max_blob_size_free(true);
+ uint32_t max_app_footer_size = calc_fw_storage_max_app_footer_size();
+
+ if (max_app_footer_size < max_fw_image_size_free) {
+ max_fw_image_size_free -= max_app_footer_size;
+ } else {
+ max_fw_image_size_free = 0;
+ }
+ return max_fw_image_size_free;
+}
+
+static uint32_t calc_fw_storage_max_upload_space(fw_dist_server_t *const self)
+{
+ uint32_t max_upload_space = sl_btmesh_blob_storage_get_total_space();
+ uint32_t max_app_footer_size = calc_fw_storage_max_app_footer_size();
+
+ uint32_t max_fw_list_app_footer_size =
+ max_app_footer_size * self->capabilities->max_fw_list_length;
+
+ if (max_fw_list_app_footer_size < max_upload_space) {
+ max_upload_space -= max_fw_list_app_footer_size;
+ } else {
+ max_upload_space = 0;
+ }
+ return max_upload_space;
+}
+
+static uint32_t calc_fw_storage_remaining_upload_space(fw_dist_server_t *const self)
+{
+ uint32_t remaining_upload_space = sl_btmesh_blob_storage_get_remaining_space(true);
+ uint32_t max_app_footer_size = calc_fw_storage_max_app_footer_size();
+
+ if (NULL == self->fw_list_nvm) {
+ remaining_upload_space = 0;
+ } else {
+ // Some bytes shall be reserved to store app footers in BLOB storage for each
+ // free entry in the FW list so the remaining bytes shall be decreased by
+ // that amount. BLOB storage does not able to provide this information in the
+ // sl_btmesh_blob_storage_get_remaining_space API call because the number and
+ // length of the footer data depends on the distributor.
+ uint32_t max_fw_list_app_footer_size = max_app_footer_size
+ * (self->capabilities->max_fw_list_length
+ - self->fw_list_nvm->current_fw_list_length);
+
+ if (max_fw_list_app_footer_size < remaining_upload_space) {
+ remaining_upload_space -= max_fw_list_app_footer_size;
+ } else {
+ remaining_upload_space = 0;
+ }
+ }
+ return remaining_upload_space;
+}
+
+static void upload_init(fw_dist_server_t *const self)
+{
+ self->upload.state = UPLOAD_STATE_INACTIVE;
+ self->upload.metadata_error = false;
+ self->upload.managed_blob_error = false;
+}
+
+static sl_status_t fw_storage_cleanup(fw_dist_server_t *const self)
+{
+ sl_status_t sc = SL_STATUS_OK, sc_cleanup = SL_STATUS_OK;
+ uint32_t occurrence_idx = 0;
+ sl_bt_uuid_64_t blob_id;
+ uint16_t fw_list_index;
+
+ while (true) {
+ // Iterate each BLOB in BLOB storage which belongs to FW Distribution Server
+ // Note: the API returns SL_STATUS_NOT_FOUND if no BLOBs are found with
+ // owner_id or the occurrence_idx is greater or equal to the number of BLOBs
+ // managed by the owner
+ sc = sl_btmesh_blob_storage_get_blob_id_by_owner(BLOB_STORAGE_OWNER_ID_FW_DIST_SERVER,
+ occurrence_idx,
+ &blob_id);
+ if (sc != SL_STATUS_OK) {
+ if (sc != SL_STATUS_NOT_FOUND) {
+ // An unexpected error occurred in BLOB storage
+ log_status_error_f(sc,
+ LOG_PREFIX
+ "Failed to get BLOB by owner (elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(&blob_id));
+ sc_cleanup = sc;
+ }
+ break;
+ }
+
+ // Leaked BLOBs in BLOB storage are those BLOBs which belongs to the FW
+ // Distribution Server but they are not in the FW list.
+ // For example: Leaked BLOBs may occur when the NVM data is deleted
+ // externally but the BLOB storage remains intact. BLOB leak can happen also
+ // when a reset occurs after the BLOB is stored in the flash but before
+ // the fwid is written as an application footer to the BLOB.
+ bool blob_leaked = true;
+
+ // Check if the BLOB is present in the FW list
+ for (fw_list_index = 0;
+ fw_list_index < self->fw_list_nvm->current_fw_list_length;
+ fw_list_index++) {
+ if (0 == memcmp(&self->fw_list[fw_list_index]->blob_id,
+ &blob_id,
+ sizeof(sl_bt_uuid_64_t))) {
+ blob_leaked = false;
+ break;
+ }
+ }
+
+ if (blob_leaked) {
+ // Invalidate the leaked BLOB in BLOB storage
+ // BLOB storage guarantees that the BLOBs of FW Distribution Server with
+ // higher occurrence_idx than the invalidated BLOB are decremented by one
+ // when invalidation is executed so occurrence_idx shall not be modified
+ sl_status_t sc_invalidate = sl_btmesh_blob_storage_invalidate(&blob_id);
+
+ log_warning(LOG_PREFIX
+ "BLOB storage cleanup (elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(&blob_id));
+
+ if (SL_STATUS_OK != sc_invalidate) {
+ // Invalidation failed so as a last resort try to delete the BLOB
+ // by erasing the medium
+ sl_status_t sc_delete = sl_btmesh_blob_storage_delete(&blob_id);
+
+ if (SL_STATUS_OK != sc_delete) {
+ log_status_error_f(sc_delete,
+ LOG_PREFIX "BLOB storage cleanup failed"
+ "(elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(&blob_id));
+ sc_cleanup = sc;
+ }
+ }
+ } else {
+ // Continue the iteration
+ occurrence_idx++;
+ }
+ }
+ return sc_cleanup;
+}
+
+static sl_status_t fw_storage_get_fwid_length(fw_dist_server_t *const self,
+ const sl_bt_uuid_64_t *blob_id,
+ uint8_t *fwid_length)
+{
+ (void) self;
+ sl_status_t sc;
+ uint16_t length;
+
+ sc = sl_btmesh_blob_storage_get_app_footer_length(blob_id,
+ BLOB_STORAGE_APP_ID_DFU_FWID,
+ &length);
+ // The mandatory firmware ID footer was not written in the BLOB storage,
+ // which means the BLOB storage shall be considered corrupted.
+ log_status_error_f(sc,
+ LOG_PREFIX
+ "Firmware ID is missing from BLOB storage "
+ "(elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id));
+
+ if (sc == SL_STATUS_OK) {
+ if ((length >= DFU_FWID_MIN_LEN) && (length <= DFU_FWID_MAX_LEN)) {
+ *fwid_length = length;
+ } else {
+ sc = SL_STATUS_INVALID_RANGE;
+ // The mandatory firmware ID footer has invalid length in the BLOB
+ // storage, which means the BLOB shall be considered corrupted.
+ log_error(LOG_PREFIX
+ "Firmware ID with invalid length in BLOB storage "
+ "(elem=%d,fwid_len=%u,blobid=%s)" NL,
+ self->elem_index,
+ length,
+ BLOB_ID_TO_STRING(blob_id));
+ }
+ }
+ return sc;
+}
+
+static sl_status_t fw_storage_get_metadata_length(fw_dist_server_t *const self,
+ const sl_bt_uuid_64_t *blob_id,
+ uint8_t *metadata_length)
+{
+ (void) self;
+ sl_status_t sc;
+ uint16_t length;
+
+ sc = sl_btmesh_blob_storage_get_app_footer_length(blob_id,
+ BLOB_STORAGE_APP_ID_DFU_METADATA,
+ &length);
+ if (SL_STATUS_NOT_FOUND != sc) {
+ // The metadata is optional so it is not an error if it is missing
+ log_status_error_f(sc,
+ LOG_PREFIX
+ "Failed to read metadata from BLOB storage"
+ "(elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id));
+ }
+
+ if (sc == SL_STATUS_OK) {
+ // If the metadata exists then it shall be at least one byte long
+ if (length > 0 && length <= DFU_METADATA_MAX_LEN) {
+ *metadata_length = length;
+ } else {
+ sc = SL_STATUS_INVALID_RANGE;
+ log_error(LOG_PREFIX
+ "Metadata with invalid length in BLOB storage "
+ "(elem=%d,metadata_len=%u,blobid=%s)" NL,
+ self->elem_index,
+ length,
+ BLOB_ID_TO_STRING(blob_id));
+ }
+ }
+ return sc;
+}
+
+static sl_status_t fw_storage_fill_fw_descriptor(fw_dist_server_t *const self,
+ uint16_t fw_list_idx,
+ const sl_bt_uuid_64_t *blob_id,
+ uint32_t blob_size,
+ uint8_t fwid_length,
+ uint8_t metadata_length,
+ bool metadata_exists)
+{
+ sl_status_t sc;
+ sl_status_t sc_fwid_get = SL_STATUS_FAIL, sc_metadata_get = SL_STATUS_NOT_FOUND;
+ fw_dist_server_fw_descriptor_t *const fw_descriptor = self->fw_list[fw_list_idx];
+
+ // The fw_list_build allocates the necessary space for fwid and metadata based
+ // on the previous BLOB storage application footer length getter calls so the
+ // fwid and metadata should fit into these allocated dynamic memory buffers
+ uint16_t storage_fwid_length = fwid_length;
+ uint16_t storage_metadata_length = metadata_length;
+
+ fw_descriptor->size = blob_size;
+ fw_descriptor->blob_id = *blob_id;
+ fw_descriptor->fwid_length = fwid_length;
+ sc_fwid_get =
+ sl_btmesh_blob_storage_get_app_footer(blob_id,
+ BLOB_STORAGE_APP_ID_DFU_FWID,
+ fw_descriptor->fwid,
+ &storage_fwid_length);
+
+ // This error is really unexpected because fw_storage_get_fwid_length and
+ // fw_storage_get_metadata_length was successful so the BLOB has valid fwid
+ // and metadata which should be readable
+ log_status_error_f(sc_fwid_get,
+ LOG_PREFIX
+ "Firmware ID is missing from BLOB storage "
+ "(elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id));
+
+ if ((sc_fwid_get == SL_STATUS_OK)
+ && (fwid_length != storage_fwid_length)) {
+ log_error(LOG_PREFIX
+ "Unexpected fwid length in BLOB storage "
+ "(elem=%d,blobid=%s,fwid_len=%u[%u])" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id),
+ storage_fwid_length,
+ fwid_length);
+ sc_fwid_get = SL_STATUS_INVALID_RANGE;
+ }
+
+ if (metadata_exists) {
+ fw_descriptor->metadata_length = metadata_length;
+ sc_metadata_get =
+ sl_btmesh_blob_storage_get_app_footer(blob_id,
+ BLOB_STORAGE_APP_ID_DFU_METADATA,
+ fw_descriptor->metadata,
+ &storage_metadata_length);
+
+ log_status_error_f(sc_metadata_get,
+ LOG_PREFIX
+ "Failed to read metadata from BLOB storage"
+ "(elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id));
+
+ if ((sc_metadata_get == SL_STATUS_OK)
+ && (metadata_length != storage_metadata_length)) {
+ log_error(LOG_PREFIX
+ "Unexpected metadata length in BLOB storage "
+ "(elem=%d,blobid=%s,metadata_len=%u[%u])" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id),
+ storage_metadata_length,
+ metadata_length);
+ sc_metadata_get = SL_STATUS_INVALID_RANGE;
+ }
+ } else {
+ fw_descriptor->metadata = NULL;
+ fw_descriptor->metadata_length = 0;
+ }
+
+ if (sc_fwid_get != SL_STATUS_OK) {
+ sc = sc_fwid_get;
+ } else if (metadata_exists && (sc_metadata_get != SL_STATUS_OK)) {
+ sc = sc_metadata_get;
+ } else {
+ sc = SL_STATUS_OK;
+ }
+ return sc;
+}
+
+static void fw_list_handle_build_error(fw_dist_server_t *const self,
+ uint16_t fw_list_idx,
+ const sl_bt_uuid_64_t blob_id,
+ bool remove_from_blob_storage)
+{
+ // If the length query of fwid or metadata fails then the fw descriptor
+ // is not allocated so a NULL check is necessary here
+ if (self->fw_list[fw_list_idx] != NULL) {
+ sl_free(self->fw_list[fw_list_idx]->metadata);
+ sl_free(self->fw_list[fw_list_idx]);
+ self->fw_list[fw_list_idx] = NULL;
+ }
+
+ // The complete NVM FW list was read from the NVM already so in order to remove
+ // the inconsistent BLOB from NVM FW list all following BLOB shall be shifted
+ // towards the position of the removed inconsistent BLOB.
+ for (uint16_t idx = fw_list_idx;
+ (idx + 1) < self->fw_list_nvm->current_fw_list_length;
+ idx++) {
+ self->fw_list_nvm->blob_ids[idx] = self->fw_list_nvm->blob_ids[idx + 1];
+ }
+ // Inconsistent BLOB is removed so the FW list length is decremented
+ self->fw_list_nvm->current_fw_list_length--;
+
+ log_warning(LOG_PREFIX
+ "Inconsistent BLOB is removed from FW list "
+ "(elem=%d,blob_id=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(&blob_id));
+
+ if (remove_from_blob_storage) {
+ // Invalidate the BLOB in BLOB storage because it is much faster than the
+ // delete operation which is important to have fast startup
+ sl_status_t sc_invalidate = sl_btmesh_blob_storage_invalidate(&blob_id);
+
+ log_warning(LOG_PREFIX
+ "Incomplete BLOB removal from BLOB storage (elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(&blob_id));
+
+ if (SL_STATUS_OK != sc_invalidate) {
+ // Invalidation failed so as a last resort try to delete the BLOB
+ // by erasing the medium
+ sl_status_t sc_delete = sl_btmesh_blob_storage_delete(&blob_id);
+
+ if (SL_STATUS_OK != sc_delete) {
+ log_status_error_f(sc_delete,
+ LOG_PREFIX
+ "Incomplete BLOB removal from BLOB storage failed "
+ "(elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(&blob_id));
+ }
+ }
+ }
+}
+
+static sl_status_t fw_list_build(fw_dist_server_t *const self)
+{
+ uint16_t fw_list_idx = 0;
+ bool fw_list_nvm_changed = false;
+
+ while (fw_list_idx < self->fw_list_nvm->current_fw_list_length) {
+ sl_status_t sc_blob_size = SL_STATUS_FAIL;
+ sl_status_t sc_fwid_len = SL_STATUS_FAIL;
+ sl_status_t sc_metadata_len = SL_STATUS_NOT_FOUND;
+ sl_status_t sc_fill_fw_desc = SL_STATUS_FAIL;
+ uint32_t blob_size = 0;
+ uint8_t fwid_length = 0, metadata_length = 0;
+ bool fw_list_nvm_entry_invalid = false;
+ sl_bt_uuid_64_t *blob_id = &self->fw_list_nvm->blob_ids[fw_list_idx];
+
+ sc_blob_size = sl_btmesh_blob_storage_get_blob_size(blob_id,
+ &blob_size);
+
+ if (SL_STATUS_OK == sc_blob_size) {
+ sc_fwid_len = fw_storage_get_fwid_length(self, blob_id, &fwid_length);
+ sc_metadata_len = fw_storage_get_metadata_length(self, blob_id, &metadata_length);
+ } else {
+ // This means that the passed BLOB ID is not stored in BLOB storage,
+ // which means that there is inconsistency between the NVM FW list
+ // and the BLOB storage.
+ log_status_error_f(sc_blob_size,
+ LOG_PREFIX
+ "BLOB from NVM FW list is not found in BLOB storage "
+ "(elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id));
+ }
+
+ // Metadata is not mandatory so it is not an error when it is missing.
+ // Note: During upload, the firmware ID is written after the metadata.
+ // If a reset occurs during writing the metadata, then it means that the
+ // firmware ID is not written as well, and the previous checks regarding
+ // the firmware ID would have failed.
+ bool fw_storage_data_valid = (sc_blob_size == SL_STATUS_OK)
+ && (sc_fwid_len == SL_STATUS_OK)
+ && ((sc_metadata_len == SL_STATUS_OK)
+ || (sc_metadata_len == SL_STATUS_NOT_FOUND));
+
+ if (fw_storage_data_valid == false) {
+ fw_list_nvm_entry_invalid = true;
+ } else {
+ bool metadata_exists = (sc_metadata_len == SL_STATUS_OK);
+
+ // The fwid and metadata length is known so the the dynamic memory can
+ // be allocated and it can be used to store the fwid and metadata so
+ // no local variable is necessary during BLOB storage read.
+ self->fw_list[fw_list_idx] = sl_malloc(FW_DESCRIPTOR_SIZE(fwid_length));
+
+ if (self->fw_list[fw_list_idx] == NULL) {
+ log_critical(LOG_PREFIX
+ "FW info allocation failed (elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id));
+ return SL_STATUS_ALLOCATION_FAILED;
+ }
+
+ if (metadata_exists) {
+ self->fw_list[fw_list_idx]->metadata = sl_malloc(metadata_length);
+ if (self->fw_list[fw_list_idx]->metadata == NULL) {
+ log_critical(LOG_PREFIX
+ "Metadata allocation failed (elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(blob_id));
+ sl_free(self->fw_list[fw_list_idx]);
+ self->fw_list[fw_list_idx] = NULL;
+ return SL_STATUS_ALLOCATION_FAILED;
+ }
+ }
+
+ // Fill the allocated FW descriptor with available data like BLOB ID and
+ // size and fill the fwid and metadata from the BLOB storage directly
+ sc_fill_fw_desc = fw_storage_fill_fw_descriptor(self,
+ fw_list_idx,
+ blob_id,
+ blob_size,
+ fwid_length,
+ metadata_length,
+ metadata_exists);
+ if (sc_fill_fw_desc != SL_STATUS_OK) {
+ fw_list_nvm_entry_invalid = true;
+ }
+ }
+
+ // If there is inconsistency between NVM FW list and BLOB storage content
+ // then the inconsistent BLOB shall removed from the FW list and from the
+ // NVM FW list and from BLOB storage.
+ if (fw_list_nvm_entry_invalid) {
+ // It is possible that the BLOB is stored in BLOB storage but there are
+ // some integrity failures like missing FWID so the BLOB shall be removed
+ // from the BLOB storage in this special case.
+ bool remove_from_blob_storage = (sc_blob_size == SL_STATUS_OK);
+
+ // The FW list is being built in this function so in order to remove the
+ // BLOB from the FW list it is enough to avoid incrementing the fw_list_idx.
+ fw_list_handle_build_error(self,
+ fw_list_idx,
+ *blob_id,
+ remove_from_blob_storage);
+
+ // The NVM FW list is changed in RAM so it shall be saved into the NVM but
+ // it might happen that multiple BLOBs are removed due to inconsistency
+ // during the iteration so it is better to save it once after the iteration
+ fw_list_nvm_changed = true;
+ } else {
+ // Valid entry is added to the FW list so the index shall be incremented
+ fw_list_idx++;
+ }
+ }
+
+ if (fw_list_nvm_changed) {
+ sl_status_t sc;
+ sc = app_btmesh_nvm_write(SL_BTMESH_FW_DIST_SERVER_FW_LIST_NVM_KEY_CFG_VAL,
+ self->fw_list_nvm,
+ FW_LIST_NVM_SIZE(self->fw_list_nvm->current_fw_list_length));
+ log_status_error_f(sc,
+ LOG_PREFIX "Failed to save NVM FW list (elem=%d)" NL,
+ self->elem_index);
+ }
+ return SL_STATUS_OK;
+}
+
+static void fw_list_free_fw_descriptors(fw_dist_server_t *const self)
+{
+ // The NULL check guarantees that this API can be called at initialization time
+ if (NULL != self->fw_list) {
+ for (uint16_t fw_list_index = 0;
+ fw_list_index < self->fw_list_nvm->current_fw_list_length;
+ fw_list_index++) {
+ fw_dist_server_fw_descriptor_t *fw_descriptor = self->fw_list[fw_list_index];
+ if (fw_descriptor != NULL) {
+ // The sl_free function handles null pointer like a no-operation
+ sl_free(fw_descriptor->metadata);
+ }
+ sl_free(fw_descriptor);
+ self->fw_list[fw_list_index] = NULL;
+ }
+ }
+}
+
+static void fw_list_init(fw_dist_server_t *const self)
+{
+ sl_status_t sc;
+ self->fw_list_nvm = NULL;
+
+ if (0 == self->capabilities->max_fw_list_length) {
+ // The BLOB storage is not able store any firmware image which makes the
+ // distributor useless which may occur when the bootloader storage slot
+ // configuration is not correct (BLOB storage stores FW in bootloader slots)
+ self->init_failed = true;
+ return;
+ }
+
+ uint16_t max_fw_list_length = self->capabilities->max_fw_list_length;
+
+ size_t max_fw_list_nvm_size = FW_LIST_NVM_SIZE(max_fw_list_length);
+
+ // The RAM mirror for NVM firmware list is allocated for the maximum firmware
+ // list length at initialization time to avoid reallocation at runtime
+ self->fw_list_nvm = sl_calloc(1, max_fw_list_nvm_size);
+
+ if (self->fw_list_nvm == NULL) {
+ self->init_failed = true;
+ log_critical(LOG_PREFIX "NVM FW list allocation failed (elem=%d)" NL,
+ self->elem_index);
+ return;
+ }
+
+ // Allocate the array of pointers to FW list elements at initialization time
+ // to avoid reallocation at runtime
+ // Note: size of pointers are not significant compared to the elements
+ self->fw_list = sl_calloc(max_fw_list_length, sizeof(self->fw_list[0]));
+
+ if (self->fw_list == NULL) {
+ self->init_failed = true;
+ log_critical(LOG_PREFIX "FW list allocation failed (elem=%d)" NL,
+ self->elem_index);
+ return;
+ }
+
+ size_t fw_list_nvm_size = max_fw_list_nvm_size;
+ sc = app_btmesh_nvm_init();
+ if (SL_STATUS_OK == sc) {
+ // The NVM provides the size of the read data which can be less than the
+ // maximum length of firmware list because only the actual firmware list
+ // elements are written into the NVM at upload
+ sc = app_btmesh_nvm_read(SL_BTMESH_FW_DIST_SERVER_FW_LIST_NVM_KEY_CFG_VAL,
+ self->fw_list_nvm,
+ &fw_list_nvm_size);
+
+ if (SL_STATUS_WOULD_OVERFLOW == sc) {
+ log_error(LOG_PREFIX
+ "NVM FW list size is too high (elem=%d,fw_list_nvm_size=0x%X,"
+ "max_fw_list_nvm_size=0x%X)" NL,
+ self->elem_index,
+ fw_list_nvm_size,
+ max_fw_list_nvm_size);
+ // The FW list could not be constructed so set the length to zero in order
+ // to execute fw_invalidate_all properly because there is no allocated entry
+ // in the FW list.
+ self->fw_list_nvm->current_fw_list_length = 0;
+
+ // The NVM content could not fit into the allocated NVM FW list RAM object
+ // if a new bootloader is flashed (OTA or debugger) with less storage slots.
+ // The best strategy is to invalidate every firmware because the FW list
+ // can't be restored and it might happen that the storage slot sizes are
+ // modified as well.
+ // The initiator can expect that after a distributor FW update the
+ // firmware list might not be valid anymore.
+ // Note: It is important to use invalidate all instead of delete all because
+ // the former is faster (flash write vs erase). The Distribution Server
+ // SDK component is initialized when the device is provisioned or when
+ // the previously provisioned node is initialized (after reset).
+ // A delete all operation in the BLOB storage could lead to
+ // configuration timeout because a device is usually configured just
+ // after the provisioning was completed.
+ (void) fw_invalidate_all(self);
+
+ // If the invalidation was successful then the FW list is empty so it does
+ // not make sense to continue the FW list initialization. If the invalidation
+ // failed then something went terribly wrong so the fw_invalidate_all sets the
+ // storage corrupted flag and the FW list initialization shall be terminated.
+ return;
+ }
+ }
+
+ if (SL_STATUS_OK != sc) {
+ // This could be the correct behavior if no firmware was uploaded to the
+ // distributor since the last mass erase.
+ self->fw_list_nvm->current_fw_list_length = 0;
+
+ log_status_info_f(sc,
+ LOG_PREFIX "No FW list found in NVM (elem=%d)" NL,
+ self->elem_index);
+
+ // If the NVM data is deleted externally then some leaked BLOB might remain
+ // in the BLOB storage which belongs to the Distribution Server.
+ // If there isn't any NVM data then no BLOB shall remain in the BLOB storage
+ // so all BLOBs can be removed from BLOB storage which are owned by the
+ // Distribution Server SW component.
+ (void) fw_invalidate_all(self);
+ return;
+ }
+
+ // Calculate the minimum size of firmware list in NVM based on the firmware
+ // list length information stored in the NVM object itself
+ size_t min_fw_list_nvm_size;
+ if (fw_list_nvm_size < sizeof(fw_dist_server_fw_list_nvm_t)) {
+ min_fw_list_nvm_size = sizeof(fw_dist_server_fw_list_nvm_t);
+ } else {
+ min_fw_list_nvm_size = sizeof(fw_dist_server_fw_list_nvm_t)
+ + self->fw_list_nvm->current_fw_list_length
+ * sizeof(sl_bt_uuid_64_t);
+ }
+
+ // Check if the size of NVM object is consistent with the length of the
+ // firmware list stored in the NVM object itself
+ if ((max_fw_list_nvm_size < fw_list_nvm_size)
+ || (fw_list_nvm_size < min_fw_list_nvm_size)) {
+ log_error(LOG_PREFIX
+ "FW list is corrupted in NVM (elem=%d,fw_list_nvm_size=0x%X,"
+ "min_fw_list_nvm_size=0x%X,max_fw_list_nvm_size=0x%X)" NL,
+ self->elem_index,
+ fw_list_nvm_size,
+ min_fw_list_nvm_size,
+ max_fw_list_nvm_size);
+
+ // The FW list could not be constructed so set the length to zero in order
+ // to execute fw_invalidate_all properly because there is no allocated entry
+ // in the FW list.
+ self->fw_list_nvm->current_fw_list_length = 0;
+
+ // This kind of NVM inconsistency should not occur. It could mean somehow
+ // the data is corrupted in NVM.
+ // Note: If the number of storage slots in the bootloader is decreased then
+ // the NVM read itself fails because the NVM data does not fit into the
+ // allocated NVM fw list buffer.
+ (void) fw_invalidate_all(self);
+
+ // If the invalidation was successful then the FW list is empty so it does
+ // not make sense to continue the FW list initialization. If the invalidation
+ // failed then something went terribly wrong so the fw_invalidate_all sets the
+ // storage corrupted flag and the FW list initialization shall be terminated.
+ return;
+ }
+
+ // FW list is built in RAM based on the NVM FW list and BLOB storage content.
+ // The FW Distribution Server checks the consistency between the NVM FW list
+ // and BLOB storage and the integrity of these persistent data.
+ // Inconsistency or integrity issues may occur when a BLOB id in the NVM list
+ // is not present in the BLOB storage or the BLOB is present in the BLOB
+ // storage but no FWID information is stored with the BLOB.
+ // This kind of inconsistency may occur if a new bootloader is flashed
+ // (OTA or debugger) with different storage slot configurations or the NVM
+ // content is deleted externally.
+ // The inconsistent data is removed from the NVM FW list and from the BLOB
+ // storage but the consistent BLOBs are kept and the FW list is built from
+ // these entries in RAM.
+ // Rationale: It takes long time to upload FW images to the distributor so
+ // it can make sense to save all valid entries. This means the the FW list is
+ // modified without the knowledge of the Initiator but this happens after a
+ // reset only and the initiator shall query the FW list before it starts the
+ // firmware distribution procedure.
+ // Note: the FW List fails only if allocation error occurs.
+ sc = fw_list_build(self);
+
+ if (SL_STATUS_OK != sc) {
+ // If the allocation of existing firmware list entries is not possible then
+ // the distribution server can't operate
+ self->init_failed = true;
+ return;
+ }
+
+ // Remove leaked BLOBs from BLOB storage to free space in BLOB storage.
+ // Note: The fw_storage_cleanup operation assumes that the FW list is valid.
+ (void) fw_storage_cleanup(self);
+
+ // The FW list has been constructed successfully so the application shall be
+ // notified about the already existing firmware images on the distributor
+ for (uint16_t fw_list_idx = 0;
+ fw_list_idx < self->fw_list_nvm->current_fw_list_length;
+ ++fw_list_idx) {
+ fw_dist_server_fw_descriptor_t *fw_descriptor = self->fw_list[fw_list_idx];
+ sl_btmesh_fw_distribution_server_on_firmware_added(self->elem_index,
+ UNASSIGNED_ADDRESS,
+ fw_descriptor->size,
+ fw_list_idx,
+ fw_list_idx + 1,
+ self->capabilities->max_fw_list_length,
+ fw_descriptor->fwid,
+ fw_descriptor->fwid_length);
+ }
+}
+
+static sl_status_t fw_list_get_storage_blob_id_by_index(fw_dist_server_t *const self,
+ uint16_t fw_list_index,
+ sl_bt_uuid_64_t *storage_blob_id)
+{
+ if ((self->init_failed != false) || (self->storage_corrupted != false)) {
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ if (self->fw_list_nvm->current_fw_list_length <= fw_list_index) {
+ return SL_STATUS_NOT_FOUND;
+ }
+
+ memcpy(storage_blob_id,
+ &self->fw_list_nvm->blob_ids[fw_list_index],
+ sizeof(sl_bt_uuid_64_t));
+ return SL_STATUS_OK;
+}
+
+static sl_status_t fw_list_get_fw_info_by_index(fw_dist_server_t *const self,
+ uint16_t fw_list_index,
+ fw_blob_selector_t blob_selector,
+ mesh_dfu_dist_server_fw_info_t *fw_info)
+{
+ fw_dist_server_fw_descriptor_t *fw_descriptor = NULL;
+
+ if ((self->init_failed != false) || (self->storage_corrupted != false)) {
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ if (self->fw_list_nvm->current_fw_list_length <= fw_list_index) {
+ return SL_STATUS_BT_MESH_DOES_NOT_EXIST;
+ }
+
+ fw_descriptor = self->fw_list[fw_list_index];
+
+ // The fw_info is not a mandatory parameter because the caller might need only
+ // the information if the firmware exists at all
+ if (fw_info != NULL) {
+ fw_info->size = fw_descriptor->size;
+ fw_info->index = fw_list_index;
+ fw_info->p_blob_id = (blob_selector == FW_BLOB_SELECTOR_STORAGE)
+ ? self->fw_list_nvm->blob_ids[fw_list_index].data
+ : fw_descriptor->blob_id.data;
+ fw_info->p_fwid = fw_descriptor->fwid;
+ fw_info->fwid_len = fw_descriptor->fwid_length;
+ fw_info->p_metadata = fw_descriptor->metadata;
+ fw_info->metadata_len = fw_descriptor->metadata_length;
+ }
+
+ return SL_STATUS_OK;
+}
+
+static sl_status_t fw_list_get_fw_info_by_fwid(fw_dist_server_t *const self,
+ const uint8_t *fwid,
+ uint8_t fwid_length,
+ fw_blob_selector_t blob_selector,
+ mesh_dfu_dist_server_fw_info_t *fw_info)
+{
+ uint16_t fw_list_index;
+
+ if ((self->init_failed != false) || (self->storage_corrupted != false)) {
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ for (fw_list_index = 0; fw_list_index < self->fw_list_nvm->current_fw_list_length;
+ fw_list_index++) {
+ if ((self->fw_list[fw_list_index]->fwid_length == fwid_length)
+ && (0 == memcmp(self->fw_list[fw_list_index]->fwid, fwid, fwid_length))) {
+ break;
+ }
+ }
+
+ if (self->fw_list_nvm->current_fw_list_length <= fw_list_index) {
+ return SL_STATUS_BT_MESH_DOES_NOT_EXIST;
+ }
+
+ return fw_list_get_fw_info_by_index(self, fw_list_index, blob_selector, fw_info);
+}
+
+static sl_status_t fw_list_append(fw_dist_server_t *const self,
+ fw_dist_server_fw_descriptor_t *fw_descriptor)
+{
+ sl_status_t sc;
+ if (self->capabilities->max_fw_list_length
+ <= self->fw_list_nvm->current_fw_list_length) {
+ sc = SL_STATUS_NO_MORE_RESOURCE;
+ } else {
+ uint16_t fw_list_index = self->fw_list_nvm->current_fw_list_length++;
+ self->fw_list[fw_list_index] = fw_descriptor;
+ memcpy(&self->fw_list_nvm->blob_ids[fw_list_index],
+ &fw_descriptor->blob_id,
+ sizeof(sl_bt_uuid_64_t));
+
+ sc = app_btmesh_nvm_write(SL_BTMESH_FW_DIST_SERVER_FW_LIST_NVM_KEY_CFG_VAL,
+ self->fw_list_nvm,
+ FW_LIST_NVM_SIZE(self->fw_list_nvm->current_fw_list_length));
+ log_status_error_f(sc,
+ LOG_PREFIX "Failed to save NVM FW list (elem=%d)" NL,
+ self->elem_index);
+ }
+ return sc;
+}
+
+static sl_status_t fw_list_delete_all(fw_dist_server_t *const self)
+{
+ sl_status_t sc;
+
+ fw_list_free_fw_descriptors(self);
+
+ self->fw_list_nvm->current_fw_list_length = 0;
+ sc = app_btmesh_nvm_write(SL_BTMESH_FW_DIST_SERVER_FW_LIST_NVM_KEY_CFG_VAL,
+ self->fw_list_nvm,
+ FW_LIST_NVM_SIZE(self->fw_list_nvm->current_fw_list_length));
+ log_status_error_f(sc,
+ LOG_PREFIX "Failed to save NVM FW list (elem=%d)" NL,
+ self->elem_index);
+ return sc;
+}
+
+static sl_status_t fw_list_delete(fw_dist_server_t *const self,
+ uint16_t target_fw_list_index)
+{
+ sl_status_t sc;
+
+ if (target_fw_list_index < self->fw_list_nvm->current_fw_list_length) {
+ // The sl_free function handles null pointer like a no-operation
+ sl_free(self->fw_list[target_fw_list_index]->metadata);
+ sl_free(self->fw_list[target_fw_list_index]);
+
+ for (uint16_t fw_list_idx = target_fw_list_index;
+ (fw_list_idx + 1) < self->fw_list_nvm->current_fw_list_length;
+ fw_list_idx++) {
+ self->fw_list_nvm->blob_ids[fw_list_idx] = self->fw_list_nvm->blob_ids[fw_list_idx + 1];
+ self->fw_list[fw_list_idx] = self->fw_list[fw_list_idx + 1];
+ }
+
+ // Every FW descriptors above the target index is shifted to the beginning
+ // of the FW list so the last position is empty so it can be set to NULL
+ self->fw_list[self->fw_list_nvm->current_fw_list_length - 1] = NULL;
+
+ self->fw_list_nvm->current_fw_list_length--;
+ sc = app_btmesh_nvm_write(SL_BTMESH_FW_DIST_SERVER_FW_LIST_NVM_KEY_CFG_VAL,
+ self->fw_list_nvm,
+ FW_LIST_NVM_SIZE(self->fw_list_nvm->current_fw_list_length));
+ log_status_error_f(sc,
+ LOG_PREFIX "Failed to save NVM FW list (elem=%d)" NL,
+ self->elem_index);
+ } else {
+ sc = SL_STATUS_INVALID_PARAMETER;
+ }
+
+ return sc;
+}
+
+static void sl_btmesh_fw_distribution_server_element_init(uint16_t elem_index)
+{
+ sl_status_t sc;
+ fw_dist_server_t *self;
+ uint32_t max_blob_count;
+ static fw_dist_server_list_t *tail;
+
+ fw_dist_server_list_t *new_fw_dist_server =
+ sl_calloc(1, sizeof(struct fw_dist_server_list_s));
+
+ if (new_fw_dist_server == NULL) {
+ app_assert(new_fw_dist_server != NULL,
+ "Allocation of distribution server list failed!");
+ // If the asserts are turned off then the initialization shall not be
+ // continued because the NULL pointer could be dereferenced
+ // Note: SERVER_STATUS_CHECK macro makes sure that the NULL pointer is not
+ // dereferenced at other entry points of the component
+ return;
+ }
+
+ // If no server has been initialized, create list head
+ if (fw_dist_server_list == NULL) {
+ fw_dist_server_list = new_fw_dist_server;
+ tail = fw_dist_server_list;
+ } else {
+ // If head is present (implies tale is valid), create new tail
+ tail->next = new_fw_dist_server;
+ tail = tail->next;
+ }
+ // Initialize tail
+ tail->next = NULL;
+ self = &tail->server;
+
+ self->elem_index = elem_index;
+ self->storage_corrupted = false;
+ self->init_failed = false;
+
+ self->async_req_client_address = UINT16_MAX;
+ self->deleting = false;
+ self->deleting_fwid_len = 0;
+ self->deleting_fwid = NULL;
+ self->deleting_all = false;
+
+ // Future proofing, if multiple instances of storage interfaces are used
+ self->capabilities = &fw_dist_server_capabilities;
+
+ // There is no use case, that the blob storage should be able to store more
+ // than 65535 blobs inside an EFR32 MCU.
+ max_blob_count = sl_btmesh_blob_storage_get_max_blob_count();
+ self->capabilities->max_fw_list_length =
+ (max_blob_count < 0xFFFF) ? max_blob_count : 0xFFFF;
+
+ // The max_fw_list_length capability shall be set because it is used to
+ // calculate the max_upload_space
+ self->capabilities->max_upload_space = calc_fw_storage_max_upload_space(self);
+ self->capabilities->max_fw_image_size = calc_fw_storage_max_fw_image_size();
+
+ // It does not make sense to use Distributor if it is not able to store at
+ // least one firmware in the BLOB storage.
+ // Note: the return value of the functions, which provide this data is set
+ // to zero if an error is encountered.
+ app_assert(0 < self->capabilities->max_fw_list_length,
+ "Max BLOB count is zero.");
+ app_assert(0 < self->capabilities->max_upload_space,
+ "Max upload space is zero.");
+ app_assert(0 < self->capabilities->max_fw_image_size,
+ "Max firmware size is zero.");
+
+ upload_init(self);
+ dist_init(self);
+ fw_list_init(self);
+
+ sc = sl_btmesh_fw_dist_server_init(BTMESH_FW_DISTRIBUTION_SERVER_MAIN,
+ SL_BTMESH_FW_DIST_SERVER_MAX_NODE_LIST_SIZE_CFG_VAL,
+ self->capabilities->max_fw_list_length,
+ self->capabilities->max_fw_image_size,
+ self->capabilities->max_upload_space,
+ SL_BTMESH_FW_DIST_SERVER_MULTICAST_THRESHOLD_DEFAULT_CFG_VAL,
+ 0,
+ NULL);
+
+ app_assert_status_f(sc, "Failed to init FW Distribution Server");
+
+ sl_btmesh_fw_distribution_server_on_distribution_state_changed(self->elem_index,
+ self->dist.state,
+ self->dist.num_active_nodes,
+ self->dist.node_count);
+}
+
+static void sl_btmesh_fw_distribution_server_init(void)
+{
+ sl_btmesh_fw_distribution_server_element_init(BTMESH_FW_DISTRIBUTION_SERVER_MAIN);
+}
+
+static void dist_reset_node_counters(fw_dist_server_t *const self)
+{
+ self->dist.node_count = 0;
+ self->dist.num_active_nodes = 0;
+}
+
+static void dist_super_state_idle_entry(fw_dist_server_t *const self)
+{
+ self->dist.blob_transfer_started = false;
+ self->dist.retry_time_elapsed = false;
+ self->dist.retry_counter = 0;
+}
+
+static void dist_init(fw_dist_server_t *const self)
+{
+ self->dist.state = sl_btmesh_fw_dist_server_dist_step_idle;
+ self->dist.fw_list_index = 0xFFFF;
+ self->dist.fw_size = 0;
+ memset(&self->dist.storage_blob_id, 0, sizeof(self->dist.storage_blob_id));
+
+ dist_reset_node_counters(self);
+ dist_super_state_idle_entry(self);
+}
+
+static void handle_nodes_added(
+ const sl_btmesh_evt_fw_dist_server_nodes_added_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+ if (0 != dist_state_flags[self->dist.state].idle) {
+ // The BT Mesh stack filters those nodes from the Firmware Distribution
+ // Receivers Add message which are already present in the distribution
+ // receivers list (remove duplicates).
+ // The BT Mesh stack also checks remaining new nodes after the filtering, if
+ // there is sufficient space for the new nodes in the receivers list.
+ if ((self->dist.node_count + evt->num_added_nodes)
+ <= SL_BTMESH_FW_DIST_SERVER_MAX_NODE_LIST_SIZE_CFG_VAL) {
+ for (uint8_t idx = 0; (idx + 2) < evt->added_nodes.len; idx += 3) {
+ // Access layer messages are little endian
+ uint16_t server_address = (evt->added_nodes.data[idx + 1] << 8)
+ | evt->added_nodes.data[idx];
+ uint8_t update_fw_image_idx = evt->added_nodes.data[idx + 2];
+
+ self->dist.node_count++;
+
+ // Inform the application about the added receiver nodes to list
+ sl_btmesh_fw_distribution_server_on_node_added(self->elem_index,
+ server_address,
+ update_fw_image_idx,
+ self->dist.node_count);
+ log_info(LOG_PREFIX
+ "node is added to distribution list "
+ "(elem=%d,claddr=0x%04X,node_addr=0x%04X,node_cnt=%d)" NL,
+ evt->elem_index,
+ evt->client_address,
+ server_address,
+ self->dist.node_count);
+ }
+ } else {
+ log_debug(LOG_PREFIX
+ "space is not sufficient in node list "
+ "(elem=%d,claddr=0x%04X,list_max=%d,list_cur=%d,list_add=%d)" NL,
+ evt->elem_index,
+ evt->client_address,
+ SL_BTMESH_FW_DIST_SERVER_MAX_NODE_LIST_SIZE_CFG_VAL,
+ self->dist.node_count,
+ evt->num_added_nodes);
+ }
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Nodes Added event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->dist.state);
+ }
+}
+
+static void handle_nodes_deleted(
+ const sl_btmesh_evt_fw_dist_server_nodes_deleted_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+ if (0 != dist_state_flags[self->dist.state].idle) {
+ dist_reset_node_counters(self);
+
+ sl_btmesh_fw_distribution_server_on_all_nodes_deleted(evt->elem_index);
+
+ log_info(LOG_PREFIX "all nodes are deleted from distribution list "
+ "(elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Nodes Deleted event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->dist.state);
+ }
+}
+
+static void handle_dist_start_request(
+ const sl_btmesh_evt_fw_dist_server_dist_start_req_t *const evt)
+{
+ sl_status_t sc_fw_info, sc_storage_blob_id, sc_dist_start_rsp;
+ mesh_dfu_dist_server_fw_info_t fw_info;
+ sl_btmesh_fw_dist_server_dist_status_t dist_status =
+ sl_btmesh_fw_dist_server_dist_status_internal_error;
+
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ if (self->storage_corrupted != false) {
+ // If the storage is corrupted then the distribution start request is
+ // rejected with internal error
+ } else if (0 != dist_state_flags[self->dist.state].idle) {
+ // Store transfer mode because it shall be provided for BLOB Transfer Client
+ // when the BLOB transfer is started
+ self->dist.transfer_mode = (sl_btmesh_mbt_client_transfer_mode_t) evt->transfer_mode;
+
+ // It is not necessary to store client and group addresses, ttl,
+ // apply_immediately and timeout_base event parameters, because these are
+ // used by and stored in the BT Mesh stack. Furthermore these are not
+ // necessary in application level logic, but from diagnostic point of view
+ // it can be useful, so these event parameters are logged.
+ log_info(LOG_PREFIX
+ "Distribution Start message is received "
+ "(elem=%d,claddr=0x%04X,graddr=0x%04X,ttl=%d,policy=%d,fw_list_idx=%d,"
+ "timeout_base=%d,tf_mode=%s)" NL,
+ evt->elem_index,
+ evt->client_address,
+ evt->group_address,
+ evt->ttl,
+ evt->apply_immediately,
+ evt->fw_list_index,
+ evt->timeout_base,
+ sl_btmesh_blob_transfer_client_transfer_mode_to_string(self->dist.transfer_mode));
+
+ // The state is changed only in the State Changed event, which is expected
+ // to be triggered after this event immediately
+ self->dist.fw_list_index = evt->fw_list_index;
+
+ self->dist.num_active_nodes = self->dist.node_count;
+
+ // The FW list is created at initialization time from NVM FW list data with
+ // storage BLOB IDs and firmware list length and from BLOB storage data with
+ // firmware ID and metadata. The FW list is managed by adding new firmware
+ // data at upload complete and removing firmware data at firmware delete so
+ // the FW list is in sync with data in NVM and BLOB storage.
+ sc_fw_info = fw_list_get_fw_info_by_index(self,
+ evt->fw_list_index,
+ FW_BLOB_SELECTOR_CURRENT,
+ &fw_info);
+
+ sc_storage_blob_id = fw_list_get_storage_blob_id_by_index(self,
+ evt->fw_list_index,
+ &self->dist.storage_blob_id);
+
+ if ((SL_STATUS_OK != sc_fw_info) || (SL_STATUS_OK != sc_storage_blob_id)) {
+ // It was not possible to determine the firmware info which belongs to the
+ // firmware list index, therefore it is meaningless to continue
+ // Internal error is passed to BT Mesh stack in distribution start response
+ log_status_error_f((SL_STATUS_OK != sc_fw_info) ? (unsigned int)sc_fw_info : (unsigned int)sc_storage_blob_id,
+ LOG_PREFIX
+ "Distribution Start failed due failed fw lookup "
+ "(elem=%u,fw_list_idx=%u)" NL,
+ evt->elem_index,
+ evt->fw_list_index);
+ } else {
+ sl_status_t sc_gen_blob_id;
+ sl_bt_uuid_64_t *dist_blob_id = &self->fw_list[evt->fw_list_index]->blob_id;
+
+ log_debug(LOG_PREFIX "Distribution Start request (fwid=");
+ log_fwid_level(APP_LOG_LEVEL_DEBUG, fw_info.p_fwid, fw_info.fwid_len, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ log_append_debug(",metadata=");
+ log_metadata_level(APP_LOG_LEVEL_DEBUG, fw_info.p_metadata, fw_info.metadata_len, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ log_append_debug(")" NL);
+
+ // The distribution procedure can be started so success status is passed
+ // to BT Mesh stack in distribution start response
+ dist_status = sl_btmesh_fw_dist_server_dist_status_success;
+ sc_gen_blob_id = sl_btmesh_fw_distribution_server_generate_blob_id(dist_blob_id);
+ if (sc_gen_blob_id != SL_STATUS_OK) {
+ *dist_blob_id = self->dist.storage_blob_id;
+ }
+
+ sl_btmesh_fw_dist_server_dfu_policy_t update_policy =
+ (sl_btmesh_fw_dist_server_dfu_policy_t) evt->apply_immediately;
+
+ // Call distribution started application callback to inform the application.
+ // The distribution started callback shall be called before the distribution
+ // start response (sl_btmesh_fw_dist_server_dist_start_rsp) BT Mesh stack
+ // API is called in order to make it possible to set the multicast threshold
+ // value for the current distribution.
+ sl_btmesh_fw_distribution_server_on_distribution_started(evt->elem_index,
+ evt->client_address,
+ evt->group_address,
+ evt->ttl,
+ update_policy,
+ evt->fw_list_index,
+ evt->timeout_base,
+ self->dist.transfer_mode,
+ self->dist.node_count);
+ }
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted
+ // Internal error is passed to BT Mesh stack in distribution start response
+ log_debug(LOG_PREFIX
+ "unexpected Distribution Start Request event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->dist.state);
+ }
+
+ // Respond to distribution start request with status information which
+ // indicates that the request is accepted or rejected
+ sc_dist_start_rsp = sl_btmesh_fw_dist_server_dist_start_rsp(self->elem_index,
+ dist_status);
+ log_status_error_f(sc_dist_start_rsp,
+ LOG_PREFIX "Distribution Start response failed (elem=%d)" NL,
+ self->elem_index);
+}
+
+static void handle_dist_cancel(
+ const sl_btmesh_evt_fw_dist_server_dist_cancel_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ // The FW distribution server SDK component state transitions are triggered
+ // by sl_btmesh_evt_fw_dist_server_dist_state_changed BT Mesh stack events.
+ // The cancel event is logged and application callback is called to provide
+ // client address information to the application.
+ log_info(LOG_PREFIX "Distribution canceled (elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+ sl_btmesh_fw_distribution_server_on_distribution_canceled(evt->elem_index,
+ evt->client_address);
+
+ if (sl_btmesh_fw_dist_server_dist_step_transferring_image == self->dist.state) {
+ sl_status_t sc = sl_btmesh_blob_transfer_client_on_aborted(evt->elem_index);
+ log_status_error_f(sc,
+ LOG_PREFIX "BLOB Transfer Client abort error (elem=%d)" NL,
+ self->elem_index);
+ }
+}
+
+static void handle_dist_suspend(
+ const sl_btmesh_evt_fw_dist_server_dist_suspend_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ if (sl_btmesh_fw_dist_server_dist_step_transferring_image == self->dist.state) {
+ log_info(LOG_PREFIX "Distribution suspended (elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+
+ sl_btmesh_fw_distribution_server_on_distribution_suspended(evt->elem_index,
+ evt->client_address);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX
+ "unexpected Distribution Suspend event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->dist.state);
+ }
+}
+
+static void handle_dist_resume_request(
+ const sl_btmesh_evt_fw_dist_server_resume_req_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ sl_btmesh_fw_dist_server_dfu_policy_t update_policy =
+ (sl_btmesh_fw_dist_server_dfu_policy_t) evt->update_policy;
+
+ sl_btmesh_fw_dist_server_dist_status_t dist_status =
+ sl_btmesh_fw_dist_server_dist_status_internal_error;
+
+ if (sl_btmesh_fw_dist_server_dist_step_suspended == self->dist.state) {
+ log_info(LOG_PREFIX
+ "Distribution resumed (elem=%d,claddr=0x%04X,graddr=0x%04X,ttl=%d,"
+ "policy=%d,fw_list_idx=%d,timeout_base=%d,tf_mode=%s)" NL,
+ evt->elem_index,
+ evt->client_address,
+ evt->group_address,
+ evt->ttl,
+ evt->update_policy,
+ evt->fw_index,
+ evt->timeout_base,
+ sl_btmesh_blob_transfer_client_transfer_mode_to_string(self->dist.transfer_mode));
+ sl_btmesh_fw_distribution_server_on_distribution_resumed(evt->elem_index,
+ evt->client_address,
+ evt->group_address,
+ evt->ttl,
+ update_policy,
+ evt->fw_index,
+ evt->timeout_base,
+ self->dist.transfer_mode,
+ self->dist.node_count);
+ dist_status = sl_btmesh_fw_dist_server_dist_status_success;
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then log message is
+ // emitted and resume response is called with internal error.
+ log_debug(LOG_PREFIX
+ "unexpected Distribution Resume event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->dist.state);
+ }
+
+ sl_status_t sc = sl_btmesh_fw_dist_server_resume_rsp(evt->elem_index,
+ dist_status);
+ log_status_error_f(sc,
+ LOG_PREFIX "Distribution resume response failed (elem=%d)" NL,
+ evt->elem_index);
+}
+
+static void handle_dist_state_changed(
+ const sl_btmesh_evt_fw_dist_server_dist_state_changed_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ if (0 != dist_state_flags[self->dist.state].idle) {
+ self->dist.num_active_nodes = evt->num_active_nodes;
+ }
+ sl_btmesh_fw_distribution_server_on_distribution_state_changed(self->elem_index,
+ (sl_btmesh_fw_dist_server_dist_step_t)evt->state,
+ evt->num_active_nodes,
+ self->dist.node_count);
+ dist_state_transition(self, (sl_btmesh_fw_dist_server_dist_step_t)evt->state);
+}
+
+static void handle_dist_server_node_failed(
+ const sl_btmesh_evt_fw_dist_server_node_failed_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+ if (0 == dist_state_flags[self->dist.state].idle) {
+ self->dist.num_active_nodes--;
+
+ log_info(LOG_PREFIX "node failed phase '%s', status '%s' "
+ "(elem=%d,addr=0x%04X)" NL,
+ sl_btmesh_fw_distribution_server_node_phase_to_string((sl_btmesh_fw_dist_server_dist_node_phase_t) evt->update_phase),
+ sl_btmesh_fw_distribution_server_firmware_update_status_to_string((sl_btmesh_fw_update_server_update_status_t) evt->update_status),
+ evt->elem_index,
+ evt->address);
+
+ sl_btmesh_fw_distribution_server_on_distribution_node_failed(evt->elem_index,
+ evt->address,
+ (sl_btmesh_fw_dist_server_dist_node_phase_t) evt->update_phase,
+ (sl_btmesh_fw_update_server_update_status_t) evt->update_status,
+ evt->mbt_status,
+ evt->progress,
+ evt->fw_index,
+ self->dist.num_active_nodes,
+ self->dist.node_count);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX
+ "unexpected Node Failed event (elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->dist.state);
+ }
+}
+
+static sl_status_t delete_firmware(fw_dist_server_t *self,
+ uint16_t target_fw_list_index)
+{
+ // This function expects valid FW list index so the caller shall validate it
+ // and therefore only an assert is added here
+ app_assert_s(target_fw_list_index < self->fw_list_nvm->current_fw_list_length);
+ sl_status_t sc_storage, sc_fw_list;
+
+ // If it is not possible to invalidate the blob in the blob storage due to
+ // an error, then the implementation tries to erase the flash pages where
+ // the BLOB is stored.
+ // Note: The erase is very slow 5s/10s for 1024kB Internal / SPI flash
+ sl_bt_uuid_64_t *storage_blob_id = &self->fw_list_nvm->blob_ids[target_fw_list_index];
+ sc_storage = sl_btmesh_blob_storage_delete_start(storage_blob_id);
+
+ // The BLOB is deleted from the firmware list as well
+ sc_fw_list = fw_list_delete(self, target_fw_list_index);
+
+ if ((SL_STATUS_OK != sc_storage) || (SL_STATUS_OK != sc_fw_list)) {
+ self->storage_corrupted = true;
+ log_status_error_f(sc_storage,
+ LOG_PREFIX
+ "BLOB storage delete failed (elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(storage_blob_id));
+
+ log_status_error_f(sc_fw_list,
+ LOG_PREFIX
+ "FW list delete failed (elem=%d,blobid=%s)" NL,
+ self->elem_index,
+ BLOB_ID_TO_STRING(storage_blob_id));
+ }
+
+ return (SL_STATUS_OK != sc_storage) ? sc_storage : sc_fw_list;
+}
+
+static bool upload_is_empty_space_sufficient(fw_dist_server_t *const self,
+ uint32_t fwsize)
+{
+ uint32_t max_fw_image_size_free, remaining_upload_space;
+
+ max_fw_image_size_free = calc_fw_storage_max_fw_image_size_free();
+ remaining_upload_space = calc_fw_storage_remaining_upload_space(self);
+
+ return (fwsize <= max_fw_image_size_free) && (fwsize <= remaining_upload_space);
+}
+
+static void handle_upload_start_metadata(
+ const sl_btmesh_evt_fw_dist_server_upload_start_metadata_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ // If upload is in progress then the event shall be ignored
+ if (0 != upload_state_flags[self->upload.state].idle) {
+ self->upload.temp_metadata = NULL;
+ self->upload.temp_metadata_length = 0;
+ self->upload.metadata_error = false;
+ self->upload.managed_blob_error = false;
+
+ // It is not necessary to check the upper limit of the metadata length as
+ // it is stored in an 8 bit variable so it can't be greater than 255
+ if (0 < evt->metadata.len) {
+ // The firmware metadata shall be stored at the beginning of the upload
+ // in order to reject the BT Mesh stack upload request with internal
+ // error if the dynamic memory allocation of metadata fails
+ self->upload.temp_metadata = sl_malloc(evt->metadata.len);
+ if (self->upload.temp_metadata != NULL) {
+ memcpy(self->upload.temp_metadata, evt->metadata.data, evt->metadata.len);
+ self->upload.temp_metadata_length = evt->metadata.len;
+ } else {
+ self->upload.metadata_error = true;
+ log_error(LOG_PREFIX "Upload metadata allocation failed (elem=%d)" NL,
+ self->elem_index);
+ }
+ }
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Upload Start Metadata event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->upload.state);
+ }
+}
+
+static sl_status_t upload_is_start_req_valid(
+ const sl_btmesh_evt_fw_dist_server_upload_start_req_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ sl_status_t sc;
+ sl_bt_uuid_64_t blob_id;
+
+ // The BLOB storage and the FW list in the NVM was detected as inconsistent
+ // at startup, previous upload or delete firmware commands failed during
+ // BLOB storage, or NVM3 write operation.
+ if (false != self->storage_corrupted) {
+ log_error(LOG_PREFIX "Upload failed due to storage corrupted (elem=%d)" NL,
+ evt->elem_index);
+ return SL_STATUS_FAIL;
+ }
+
+ // The metadata is received in the same Firmware Distribution Upload Start
+ // message, but it is passed in a different event (upload start metadata) due
+ // to BGAPI limitations. (one variable length array per message)
+ // It is guaranteed, that the upload start metadata event is received first
+ // and the upload started event afterwards.
+ if (self->upload.metadata_error != false) {
+ log_error(LOG_PREFIX "Upload with invalid metadata (elem=%d)" NL,
+ evt->elem_index);
+ return SL_STATUS_NULL_POINTER;
+ }
+
+ // The firmware id does not fit into the allocated array or the length of the
+ // firmware id (mandatory) is zero, then error is returned.
+ if ((0 == evt->fwid.len)
+ || (DFU_FWID_MAX_LEN < evt->fwid.len)
+ || (NULL == evt->fwid.data)) {
+ log_error(LOG_PREFIX "Upload with invalid FW id (elem=%d)" NL,
+ evt->elem_index);
+ if (NULL == evt->fwid.data) {
+ return SL_STATUS_NULL_POINTER;
+ } else {
+ return SL_STATUS_INVALID_RANGE;
+ }
+ }
+
+ // If there is not enough space to store the firmware binary, then an error is
+ // returned. This should not happen because the distribution client can query
+ // the available space before the upload procedure.
+ if (false == upload_is_empty_space_sufficient(self, evt->size)) {
+ log_error(LOG_PREFIX
+ "Upload failed due to lack of free space "
+ "(elem=%d,fw_size=0x%lX,rem_space=0x%lX,max_space=0x%lX,max_fw_size=0x%lX)" NL,
+ evt->elem_index,
+ evt->size,
+ calc_fw_storage_remaining_upload_space(self),
+ self->capabilities->max_upload_space,
+ calc_fw_storage_max_fw_image_size_free());
+ return SL_STATUS_NO_MORE_RESOURCE;
+ }
+
+ if (self->capabilities->max_fw_list_length
+ <= self->fw_list_nvm->current_fw_list_length) {
+ log_error(LOG_PREFIX "Upload failed due to full firmware list "
+ "(elem=%d,fw_list_len=%d)" NL,
+ evt->elem_index,
+ self->capabilities->max_fw_list_length);
+ return SL_STATUS_NO_MORE_RESOURCE;
+ }
+
+ // Check if the firmware is already stored in the BLOB storage. However, the
+ // BT Mesh stack should not start the upload and notify this component, if the
+ // firmware is already present on the device, so this would mean there is some
+ // inconsistency between the NVM firmware list and BLOB storage.
+ sc = sl_btmesh_blob_storage_get_blob_id_by_footer(BLOB_STORAGE_APP_ID_DFU_FWID,
+ evt->fwid.data,
+ evt->fwid.len,
+ &blob_id);
+
+ // The get function returns SL_STATUS_OK, if the firmware ID is found.
+ // If the firmware ID already exists, then an error code is returned and the
+ // upload is rejected and a warning is emitted.
+ // The initiator can delete all firmware images to start again.
+ if (SL_STATUS_OK == sc) {
+ log_error(LOG_PREFIX "Upload failed due to firmware id already exists (elem=%d)" NL,
+ evt->elem_index);
+ sc = SL_STATUS_ALREADY_EXISTS;
+ }
+
+ return SL_STATUS_OK;
+}
+
+static void handle_upload_start_request(
+ const sl_btmesh_evt_fw_dist_server_upload_start_req_t *const evt)
+{
+ sl_btmesh_fw_dist_server_dist_status_t upload_status =
+ sl_btmesh_fw_dist_server_dist_status_internal_error;
+
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ // From the FW Distribution Server SDK component perspective there is no
+ // difference between new and resumed upload because the BT Mesh stack emits
+ // the very same sl_btmesh_evt_fw_dist_server_upload_start_metadata and
+ // sl_btmesh_evt_fw_dist_server_upload_start_req events.
+ // If the BT Mesh stack detects that a BLOB transfer was suspended with the
+ // same BLOB ID received in FW DIST UPLOAD START message then it considers it
+ // as a resume and it does not start a new BLOB transfer which means the BLOB
+ // progress is preserved in the BT Mesh stack.
+
+ // If an upload is already in progress then ignore the new request
+ if (0 != upload_state_flags[self->upload.state].idle) {
+ log_debug(LOG_PREFIX "Upload start request (fwid=");
+ log_fwid_level(APP_LOG_LEVEL_DEBUG, evt->fwid.data, evt->fwid.len, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ log_append_debug(",metadata=");
+ log_metadata_level(APP_LOG_LEVEL_DEBUG, self->upload.temp_metadata, self->upload.temp_metadata_length, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ log_append_debug(")" NL);
+
+ sl_status_t sc_upload_valid = upload_is_start_req_valid(evt);
+
+ if (SL_STATUS_OK == sc_upload_valid) {
+ self->upload.fw_descriptor = sl_malloc(FW_DESCRIPTOR_SIZE(evt->fwid.len));
+ if (self->upload.fw_descriptor != NULL) {
+ self->upload.fw_descriptor->size = evt->size;
+ self->upload.fw_descriptor->blob_id = evt->blob_id;
+ memcpy(self->upload.fw_descriptor->fwid, evt->fwid.data, evt->fwid.len);
+ self->upload.fw_descriptor->fwid_length = evt->fwid.len;
+ self->upload.fw_descriptor->metadata = self->upload.temp_metadata;
+ self->upload.fw_descriptor->metadata_length = self->upload.temp_metadata_length;
+ upload_status = sl_btmesh_fw_dist_server_dist_status_success;
+ upload_state_transition(self, UPLOAD_STATE_TRANSFER_ACTIVE);
+ } else {
+ log_error(LOG_PREFIX "Upload FW descriptor allocation failed (elem=%d)" NL,
+ self->elem_index);
+ }
+ }
+
+ if (upload_status != sl_btmesh_fw_dist_server_dist_status_success) {
+ // If the upload status is not success because metadata error then the
+ // temporary metadata pointer is NULL so the sl_free is a no-operation.
+ // If the upload status is not success because of invalid upload request
+ // the allocation of the FW descriptor is failed then the upload is
+ // rejected and the metadata shall be deallocated.
+ sl_free(self->upload.temp_metadata);
+ }
+
+ // Temporary metadata pointer is no longer necessary because it it was added
+ // to the FW descriptor or the upload was rejected and the metadata storage
+ // was already deallocated
+ self->upload.temp_metadata = NULL;
+ self->upload.temp_metadata_length = 0;
+
+ log_info(LOG_PREFIX
+ "Upload %s (elem=%d,claddr=0x%04X,fw_size=0x%lX,blobid=%s)" NL,
+ (upload_status == sl_btmesh_fw_dist_server_dist_status_success)
+ ? "started" : "start rejected",
+ evt->elem_index,
+ evt->client_address,
+ evt->size,
+ BLOB_ID_TO_STRING(&evt->blob_id));
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Upload Start event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->upload.state);
+ }
+
+ sl_status_t sc_upload_rsp = sl_btmesh_fw_dist_server_upload_start_rsp(evt->elem_index,
+ upload_status);
+ log_status_error_f(sc_upload_rsp,
+ LOG_PREFIX "Upload start response failed (elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+
+ if ((sc_upload_rsp != SL_STATUS_OK)
+ && (upload_status == sl_btmesh_fw_dist_server_dist_status_success)) {
+ // If the upload can be started but the BT Mesh stack upload start response
+ // fails then the upload is not started so the upload state machine shall
+ // transition to idle state which deallocates the FW descriptor
+ upload_state_transition(self, UPLOAD_STATE_TRANSFER_FAILED);
+ }
+}
+
+static void handle_upload_cancel(
+ const sl_btmesh_evt_fw_dist_server_upload_cancel_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+ // If there is no active transfer then the cancellation event shall be ignored
+ if (UPLOAD_STATE_TRANSFER_CANCELED != self->upload.state) {
+ log_info(LOG_PREFIX "upload is canceled (elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+
+ // The state transition to canceled state deallocates the FW descriptor
+ upload_state_transition(self, UPLOAD_STATE_TRANSFER_CANCELED);
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Upload Cancel event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->upload.state);
+ }
+}
+
+static void handle_upload_complete_metadata(
+ const sl_btmesh_evt_fw_dist_server_upload_complete_metadata_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+ sl_status_t sc_metadata_write, sc_managed_flag_write;
+
+ if (0 != upload_state_flags[self->upload.state].idle) {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Upload Completed event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->upload.state);
+ return;
+ }
+
+ // Set the managed flag first before metadata and fwid write.
+ // If a reset occurs after the managed flag is written but before the fwid is
+ // written then the BLOB is not part of the NVM FW list but it is managed by
+ // the FW Distribution Server so the storage cleanup at startup will remove
+ // this BLOB from BLOB storage.
+ // If a reset occurs before the managed flag is written then the BLOB remains
+ // unmanaged and the BLOB storage deletes it when it needs space to store new
+ // BLOB. It is important that the FWID is not written in this case so it could
+ // not cause problems when the FW Distributor Server searches BLOBs by FWID.
+ sc_managed_flag_write = sl_btmesh_blob_storage_set_managed(&self->upload.fw_descriptor->blob_id,
+ BLOB_STORAGE_OWNER_ID_FW_DIST_SERVER);
+ log_status_error_f(sc_managed_flag_write,
+ LOG_PREFIX "Upload failed to write managed flag into BLOB storage "
+ "(elem=%d,blobid=%s)" NL,
+ evt->elem_index,
+ BLOB_ID_TO_STRING(&self->upload.fw_descriptor->blob_id));
+
+ if (SL_STATUS_OK == sc_managed_flag_write) {
+ self->upload.managed_blob_error = false;
+ } else {
+ self->upload.managed_blob_error = true;
+ }
+
+ if (0 < evt->metadata.len) {
+ sc_metadata_write = sl_btmesh_blob_storage_write_app_footer(&self->upload.fw_descriptor->blob_id,
+ &evt->metadata.data[0],
+ BLOB_STORAGE_APP_ID_DFU_METADATA,
+ evt->metadata.len);
+ if (SL_STATUS_OK == sc_metadata_write) {
+ self->upload.metadata_error = false;
+ } else {
+ self->upload.metadata_error = true;
+ log_status_error_f(sc_metadata_write,
+ LOG_PREFIX
+ "Upload failed to write metadata into BLOB storage "
+ "(elem=%d,blobid=%s)" NL,
+ evt->elem_index,
+ BLOB_ID_TO_STRING(&self->upload.fw_descriptor->blob_id));
+ }
+ }
+}
+
+static void handle_upload_complete(
+ const sl_btmesh_evt_fw_dist_server_upload_complete_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ sl_status_t sc_fwid_write;
+ sl_status_t sc_fw_list_append = SL_STATUS_FAIL;
+ uint16_t fw_list_index;
+
+ if (0 != upload_state_flags[self->upload.state].idle) {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Upload Completed event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->upload.state);
+ return;
+ }
+
+ fw_list_index = self->fw_list_nvm->current_fw_list_length;
+
+ sc_fwid_write =
+ sl_btmesh_blob_storage_write_app_footer(&evt->blob_id,
+ &evt->fwid.data[0],
+ BLOB_STORAGE_APP_ID_DFU_FWID,
+ evt->fwid.len);
+ log_status_error_f(sc_fwid_write,
+ LOG_PREFIX "Upload failed to write fwid into BLOB storage "
+ "(elem=%d,blobid=%s)" NL,
+ evt->elem_index,
+ BLOB_ID_TO_STRING(&evt->blob_id));
+
+ // If any BLOB storage error occurs then the FW list shall not be updated in
+ // the NVM in order to avoid invalid entries.
+ if ((SL_STATUS_OK == sc_fwid_write)
+ && (false == self->upload.managed_blob_error)
+ && (false == self->upload.metadata_error)) {
+ sc_fw_list_append = fw_list_append(self, self->upload.fw_descriptor);
+ log_status_error_f(sc_fw_list_append,
+ LOG_PREFIX "Upload failed to append to firmware list "
+ "(elem=%d,blobid=%s)" NL,
+ evt->elem_index,
+ BLOB_ID_TO_STRING(&evt->blob_id));
+ }
+
+ // FW list append can be successful only if the previous BLOB storage
+ // operations were successful as well because sc_fw_list_append is initialized
+ // to SL_STATUS_FAIL
+ if (SL_STATUS_OK == sc_fw_list_append) {
+ // It is essential to NULL the FW descriptor in the upload data structure
+ // because the ownership of the descriptor is transferred to the FW list
+ // at the end successful upload and the upload state machines deallocates
+ // the FW descriptor when it transitions into an idle state (unless it is NULL)
+ self->upload.fw_descriptor = NULL;
+
+ sl_btmesh_fw_distribution_server_on_firmware_added(evt->elem_index,
+ evt->client_address,
+ evt->size,
+ fw_list_index,
+ self->fw_list_nvm->current_fw_list_length,
+ self->capabilities->max_fw_list_length,
+ evt->fwid.data,
+ evt->fwid.len);
+ log_info(LOG_PREFIX
+ "Upload completed (elem=%d,claddr=0x%04X,fw_list_idx=%d,"
+ "fw_size=0x%lX,blobid=%s)" NL,
+ evt->elem_index,
+ evt->client_address,
+ fw_list_index,
+ evt->size,
+ BLOB_ID_TO_STRING(&evt->blob_id));
+
+ upload_state_transition(self, UPLOAD_STATE_TRANSFER_SUCCESS);
+ } else {
+ // If any BLOB storage operation is failed then the FW list is not appended
+ // and therefore the NVM data is not updated. If all BLOB storage operations
+ // are successful but the FW append was failed then it means the NVM data
+ // was not updated in this case as well.
+ // The initiator expects that the FW image was uploaded at this point because
+ // the BLOB transfer was successful however the necessary data could not be
+ // stored persistently in BLOB storage and NVM. The uploaded firmware is
+ // appended to the end of list so indexes of other firmwares are not affected.
+ // The best strategy is to remove all persistently stored data belonging to
+ // the just uploaded firmware in order to restore a consistent state which
+ // means that the BLOB shall be invalidated or deleted in the BLOB storage.
+ // If the initiator starts the distribution with the new highest index then
+ // it receives FW Distribution Status message with "Firmware Not Found" status
+ // which describes the problem quite well. If the initiator queries the FW
+ // list element by Firmware Get or Firmware Get By Index message then the
+ // Firmware Status message indicates that the FW does not exists on the
+ // distribution server.
+ log_info(LOG_PREFIX
+ "BLOB storage recovery (elem=%d,blobid=%s)" NL,
+ evt->elem_index,
+ BLOB_ID_TO_STRING(&evt->blob_id));
+
+ // Invalidate the BLOB just in case
+ sl_status_t sc_invalidate = sl_btmesh_blob_storage_invalidate(&evt->blob_id);
+
+ if (SL_STATUS_OK != sc_invalidate) {
+ // As a last resort try to delete the BLOB by erasing the medium
+ sl_status_t sc_delete = sl_btmesh_blob_storage_delete(&evt->blob_id);
+
+ if (SL_STATUS_OK != sc_delete) {
+ log_status_error_f(sc_delete,
+ LOG_PREFIX "BLOB storage recovery failed"
+ "(elem=%d,blobid=%s)" NL,
+ evt->elem_index,
+ BLOB_ID_TO_STRING(&evt->blob_id));
+
+ // All recovery method failed so the storage is set to corrupted because
+ // persistent data is inconsistent or missing.
+ self->storage_corrupted = true;
+ }
+ }
+ upload_state_transition(self, UPLOAD_STATE_TRANSFER_FAILED);
+ }
+}
+
+static void handle_upload_failed(
+ const sl_btmesh_evt_fw_dist_server_upload_failed_t *const evt)
+{
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+ if (0 != upload_state_flags[self->upload.state].idle) {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected Upload Failed event "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->upload.state);
+ return;
+ }
+
+ // The upload process fails if and only if the BLOB Transfer fails.
+ // The BLOB ID is written into the BLOB Storage as the last step of the BLOB
+ // Transfer by BLOB Transfer Server. The BLOB storage considers a binary valid
+ // if its BLOB ID is written into flash and it reuses the space of invalid
+ // binaries automatically therefore it is not necessary to delete anything
+ // in BLOB storage in case of failed upload.
+
+ log_warning(LOG_PREFIX
+ "Upload failed, suspended, or aborted (elem=%d,"
+ "claddr=0x%04X,fw_size=0x%lX,blobid=%s)" NL,
+ evt->elem_index,
+ evt->client_address,
+ evt->size,
+ BLOB_ID_TO_STRING(&evt->blob_id));
+
+ // The state transition to failed state deallocates the FW descriptor
+ upload_state_transition(self, UPLOAD_STATE_TRANSFER_FAILED);
+}
+
+static void handle_fw_delete_request(
+ const sl_btmesh_evt_fw_dist_server_fw_delete_req_t *const evt)
+{
+ sl_status_t sc;
+ mesh_dfu_dist_server_fw_info_t fw_info;
+
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ log_info(LOG_PREFIX "FW delete request (elem=%d,claddr=0x%04X",
+ evt->elem_index,
+ evt->client_address);
+ log_append_debug(",fwid=");
+ log_fwid_level(APP_LOG_LEVEL_DEBUG, evt->fwid.data, evt->fwid.len, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ log_append_info(")" NL);
+
+ // Search for firmware information in FW list by fwid
+ sc = fw_list_get_fw_info_by_fwid(self,
+ evt->fwid.data,
+ evt->fwid.len,
+ FW_BLOB_SELECTOR_STORAGE,
+ &fw_info);
+
+ if (SL_STATUS_OK == sc) {
+ self->deleting_fwid_len = evt->fwid.len;
+ self->deleting_fwid = sl_malloc(self->deleting_fwid_len);
+
+ if (self->deleting_fwid != NULL) {
+ memcpy(self->deleting_fwid, evt->fwid.data, self->deleting_fwid_len);
+
+ sc = delete_firmware(self, fw_info.index);
+ } else {
+ sc = SL_STATUS_ALLOCATION_FAILED;
+ log_error(LOG_PREFIX "Memory allocation failed! (elem=%d)" NL,
+ self->elem_index);
+ }
+
+ if (SL_STATUS_OK == sc) {
+ self->deleting = true;
+ self->async_req_client_address = evt->client_address;
+ } else {
+ sc = sl_btmesh_fw_dist_server_delete_rsp(self->elem_index,
+ sl_btmesh_fw_dist_server_dist_status_internal_error,
+ evt->fwid.len,
+ evt->fwid.data);
+ }
+ } else {
+ log_status_error_f(sc,
+ LOG_PREFIX "FW delete failed %s (elem=%d,claddr=0x%04X)" NL,
+ (SL_STATUS_BT_MESH_DOES_NOT_EXIST == sc)
+ ? "because fwid was not found"
+ : "during fwid search",
+ evt->elem_index,
+ evt->client_address);
+ }
+}
+
+static void handle_fw_delete_response(fw_dist_server_t *self,
+ sl_status_t erase_status)
+{
+ sl_status_t sc;
+ sl_btmesh_fw_dist_server_dist_status_t firmware_status =
+ sl_btmesh_fw_dist_server_dist_status_internal_error;
+
+ if (SL_STATUS_OK == erase_status) {
+ sl_btmesh_fw_distribution_server_on_firmware_deleted(
+ self->elem_index,
+ self->async_req_client_address,
+ self->fw_list_nvm->current_fw_list_length,
+ self->capabilities->max_fw_list_length,
+ self->deleting_fwid,
+ self->deleting_fwid_len);
+
+ log_info(LOG_PREFIX "FW deleted (elem=%d,claddr=0x%04X)" NL,
+ self->elem_index,
+ self->async_req_client_address);
+
+ firmware_status = sl_btmesh_fw_dist_server_dist_status_success;
+ }
+
+ // Respond to firmware delete request with status information
+ // The BT Mesh stack sends the Firmware Distribution Firmware Status message
+ // with the provided status information to the Distribution Client
+ sc = sl_btmesh_fw_dist_server_delete_rsp(self->elem_index,
+ firmware_status,
+ self->deleting_fwid_len,
+ self->deleting_fwid);
+ log_status_error_f(sc,
+ LOG_PREFIX "FW delete response failed (elem=%d,claddr=0x%04X)" NL,
+ self->elem_index,
+ self->async_req_client_address);
+
+ self->deleting = false;
+ self->async_req_client_address = UINT16_MAX;
+ self->deleting_fwid_len = 0;
+ sl_free(self->deleting_fwid);
+ self->deleting_fwid = NULL;
+}
+
+static sl_status_t fw_invalidate_all(fw_dist_server_t *const self)
+{
+ sl_status_t sc_fw_list, sc_storage;
+
+ // Clear the firmware list in RAM and NVM
+ sc_fw_list = fw_list_delete_all(self);
+
+ // Make every BLOB in the BLOB storage invalid which is faster than the delete
+ // all operation because the flash is only written and not erased.
+ sc_storage = sl_btmesh_blob_storage_invalidate_by_owner(BLOB_STORAGE_OWNER_ID_FW_DIST_SERVER);
+
+ if ((SL_STATUS_OK == sc_storage)
+ && (SL_STATUS_OK == sc_fw_list)) {
+ // The whole BLOB storage was invalidated and the FW list in the NVM3 was
+ // deleted successfully which restores the state of these resources and
+ // therefore it fixes storage corruption as well
+ self->storage_corrupted = false;
+
+ // The upper layer shall be notified through the following callback
+ sl_btmesh_fw_distribution_server_on_all_firmware_deleted(self->elem_index,
+ MESH_ADDR_UNASSIGNED,
+ self->capabilities->max_fw_list_length);
+
+ log_info(LOG_PREFIX "all FWs are invalidated (elem=%d)" NL,
+ self->elem_index);
+ } else {
+ self->storage_corrupted = true;
+ log_status_error_f(sc_storage,
+ LOG_PREFIX "BLOB storage invalidate all failed (elem=%d)" NL,
+ self->elem_index);
+
+ log_status_error_f(sc_fw_list,
+ LOG_PREFIX "FW list delete all failed (elem=%d)" NL,
+ self->elem_index);
+ }
+
+ return (sc_fw_list != SL_STATUS_OK) ? sc_fw_list : sc_storage;
+}
+
+static void handle_fw_delete_all_request(
+ const sl_btmesh_evt_fw_dist_server_fw_delete_all_req_t *const evt)
+{
+ sl_status_t sc_fw_list, sc_delete_req, sc_delete_rsp;
+
+ fw_dist_server_t *self = find_server(evt->elem_index);
+ SERVER_STATUS_CHECK(self);
+
+ if (self->deleting_all) {
+ sc_delete_rsp = sl_btmesh_fw_dist_server_delete_all_rsp(self->elem_index,
+ sl_btmesh_fw_dist_server_dist_status_internal_error);
+
+ log_status_error_f(sc_delete_rsp,
+ LOG_PREFIX "FW delete all response failed (elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+
+ return;
+ }
+
+ log_info(LOG_PREFIX "FW delete all request (elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+
+ sc_fw_list = fw_list_delete_all(self);
+
+ log_status_error_f(sc_fw_list,
+ LOG_PREFIX "FW list delete all failed "
+ "(elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+
+ // All storage slots are erased in BLOB storage
+ // Note: The erase is very slow 5s/10s for 1024kB Internal / SPI flash
+ sc_delete_req = sl_btmesh_blob_storage_delete_managed_by_owner_start(BLOB_STORAGE_OWNER_ID_FW_DIST_SERVER);
+
+ log_status_error_f(sc_delete_req,
+ LOG_PREFIX "BLOB storage delete all failed "
+ "(elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+
+ if (SL_STATUS_OK == sc_fw_list && SL_STATUS_OK == sc_delete_req) {
+ self->deleting_all = true;
+ self->async_req_client_address = evt->client_address;
+ } else {
+ sc_delete_rsp = sl_btmesh_fw_dist_server_delete_all_rsp(self->elem_index,
+ sl_btmesh_fw_dist_server_dist_status_internal_error);
+
+ log_status_error_f(sc_delete_rsp,
+ LOG_PREFIX "FW delete all response failed (elem=%d,claddr=0x%04X)" NL,
+ evt->elem_index,
+ evt->client_address);
+ }
+}
+
+static void handle_fw_delete_all_response(fw_dist_server_t *self,
+ sl_status_t erase_status)
+{
+ sl_status_t sc_delete_rsp;
+ sl_btmesh_fw_dist_server_dist_status_t firmware_status =
+ sl_btmesh_fw_dist_server_dist_status_internal_error;
+
+ if ((SL_STATUS_OK == erase_status)) {
+ // The whole BLOB storage and the FW list in the NVM3 was deleted successfully
+ // which restores the original state of these resources and therefore it fixes
+ // storage corruption as well
+ self->storage_corrupted = false;
+
+ // All Firmware were deleted so set the status to success
+ firmware_status = sl_btmesh_fw_dist_server_dist_status_success;
+
+ // The upper layer shall be notified through the following callback
+ sl_btmesh_fw_distribution_server_on_all_firmware_deleted(
+ self->elem_index,
+ self->async_req_client_address,
+ self->capabilities->max_fw_list_length);
+
+ log_info(LOG_PREFIX "all FWs are deleted (elem=%d,claddr=0x%04X)" NL,
+ self->elem_index,
+ self->async_req_client_address);
+ } else {
+ self->storage_corrupted = true;
+ }
+
+ // Respond to firmware delete all request with status information
+ // The BT Mesh stack sends the Firmware Distribution Firmware Status message
+ // with the provided status information to the Distribution Client
+ sc_delete_rsp = sl_btmesh_fw_dist_server_delete_all_rsp(self->elem_index,
+ firmware_status);
+ log_status_error_f(sc_delete_rsp,
+ LOG_PREFIX "FW delete all response failed (elem=%d,claddr=0x%04X)" NL,
+ self->elem_index,
+ self->async_req_client_address);
+
+ self->deleting_all = false;
+ self->async_req_client_address = UINT16_MAX;
+}
+
+static void handle_blob_transfer_client_notification(
+ const sl_btmesh_blob_transfer_client_notification_t *const notification)
+{
+ fw_dist_server_t *const self = find_server(notification->elem_index);
+ SERVER_STATUS_CHECK(self);
+ app_assert_s(NULL != notification);
+
+ // Ignore BLOB transfer client events in idle substates
+ if (0 == dist_state_flags[self->dist.state].idle) {
+ if (SL_BTMESH_BLOB_TRANSFER_CLIENT_KIND_PROGRESS == notification->kind) {
+ uint32_t confirmed_tx_bytes = notification->params.progress.confirmed_tx_bytes;
+ uint32_t blob_size = notification->params.progress.blob_size;
+
+ log_info(LOG_PREFIX "distribution progress (%lu/%lu, %u%%)" NL,
+ confirmed_tx_bytes,
+ blob_size,
+ (unsigned) SL_PROG_TO_PCT_INT(blob_size, confirmed_tx_bytes));
+
+ sl_btmesh_fw_distribution_server_on_distribution_blob_progress_changed(self->elem_index,
+ confirmed_tx_bytes,
+ blob_size,
+ self->dist.node_count);
+ }
+ } else {
+ // This should not happen based on the state machine in the BT Mesh stack.
+ // If the event is not received in the expected state then it is ignored
+ // and a log message is emitted.
+ log_debug(LOG_PREFIX "unexpected BLOB Transfer Client notification "
+ "(elem=%d,state=%d)" NL,
+ self->elem_index,
+ self->upload.state);
+ }
+}
+
+static sl_status_t dist_execute_step(fw_dist_server_t *const self)
+{
+ sl_status_t sc = sl_btmesh_fw_dist_server_execute_distribution_step(self->elem_index);
+
+ if (SL_STATUS_OK == sc) {
+ log_info(LOG_PREFIX "execute %s step %s(elem=%d,nodes_active=%d/%d)" NL,
+ sl_btmesh_fw_distribution_server_distribution_state_to_string(self->dist.state),
+ self->dist.retry_time_elapsed ? "retry " : "",
+ self->elem_index,
+ self->dist.num_active_nodes,
+ self->dist.node_count);
+ } else {
+ log_status_error_f(sc,
+ LOG_PREFIX
+ "execute %s step %sfailed (elem=%d,nodes_active=%d/%d)" NL,
+ sl_btmesh_fw_distribution_server_distribution_state_to_string(self->dist.state),
+ self->dist.retry_time_elapsed ? "retry " : "",
+ self->elem_index,
+ self->dist.num_active_nodes,
+ self->dist.node_count);
+ }
+ return sc;
+}
+
+static void dist_state_waiting_for_apply_entry(fw_dist_server_t *const self)
+{
+ (void) self;
+ log_info(LOG_PREFIX "waiting for apply (elem=%d,nodes_active=%d/%d)" NL,
+ self->elem_index,
+ self->dist.num_active_nodes,
+ self->dist.node_count);
+}
+
+static void dist_state_transferring_image_start(fw_dist_server_t *const self)
+{
+ sl_status_t sc;
+
+ // It is not necessary to call the sl_btmesh_blob_transfer_client_setup,
+ // because the distributor code in BT Mesh stack calls the setup function.
+ sc = sl_btmesh_blob_transfer_client_setup_data_provider_blob_storage(self->elem_index,
+ &self->dist.storage_blob_id);
+ if (SL_STATUS_OK != sc) {
+ log_status_error_f(sc,
+ LOG_PREFIX "BLOB Transfer start failed %s(elem=%d)" NL,
+ "because data provider setup failed ",
+ self->elem_index);
+ return;
+ }
+
+ sc = sl_btmesh_blob_transfer_client_start(self->elem_index,
+ self->dist.transfer_mode,
+ handle_blob_transfer_client_notification);
+
+ if (SL_STATUS_OK != sc) {
+ log_status_error_f(sc,
+ LOG_PREFIX "BLOB Transfer start failed %s(elem=%d)" NL,
+ "because blob transfer client start failed ",
+ self->elem_index);
+ return;
+ }
+
+ self->dist.blob_transfer_started = true;
+
+ log_info(LOG_PREFIX "BLOB Transfer is started (elem=%d,nodes_active=%d/%d)" NL,
+ self->elem_index,
+ self->dist.num_active_nodes,
+ self->dist.node_count);
+}
+
+static void dist_state_transferring_image_resume(fw_dist_server_t *const self)
+{
+ sl_status_t sc = sl_btmesh_blob_transfer_client_on_resumed(self->elem_index);
+ log_status_error_f(sc,
+ LOG_PREFIX "BLOB Transfer Client resume error (elem=%d)" NL,
+ self->elem_index);
+}
+
+static void dist_log_node_status(fw_dist_server_t *const self,
+ uint16_t node_idx)
+{
+ uint16_t address;
+ uint8_t retrieved_phase;
+ uint8_t update_server_status;
+ uint8_t mbt_server_status;
+ uint8_t transfer_progress;
+ uint8_t fw_index;
+
+ sl_status_t sc = sl_btmesh_fw_dist_server_get_node_status_by_index(self->elem_index,
+ node_idx,
+ &address,
+ &retrieved_phase,
+ &update_server_status,
+ &mbt_server_status,
+ &transfer_progress,
+ &fw_index);
+ log_status_error_f(sc,
+ LOG_PREFIX "node failed event with non-existing node index "
+ "(elem=%d,index=0x%04X)" NL,
+ self->elem_index,
+ node_idx);
+ if (sc == SL_STATUS_OK) {
+ log_info(LOG_PREFIX "node status (addr=0x%04X,upd_fw_idx=%d,transfer_progress=%d,"
+ "dist_phase=%d-%s,upd_status=%d-%s,blob_status=%d-%s)" NL,
+ address,
+ fw_index,
+ transfer_progress,
+ retrieved_phase,
+ sl_btmesh_fw_distribution_server_node_phase_to_string((sl_btmesh_fw_dist_server_dist_node_phase_t)retrieved_phase),
+ update_server_status,
+ sl_btmesh_fw_distribution_server_firmware_update_status_to_string((sl_btmesh_fw_update_server_update_status_t)update_server_status),
+ mbt_server_status,
+ sl_btmesh_blob_transfer_client_mbt_status_to_string((sl_btmesh_mbt_server_status_t)mbt_server_status));
+ }
+}
+
+static void dist_log_all_node_status(fw_dist_server_t *const self)
+{
+ for (uint16_t node_idx = 0; node_idx < self->dist.node_count; ++node_idx) {
+ dist_log_node_status(self, node_idx);
+ }
+}
+
+static void dist_state_suspended_entry(fw_dist_server_t *const self)
+{
+ sl_status_t sc = sl_btmesh_blob_transfer_client_on_suspended(self->elem_index);
+ log_status_error_f(sc,
+ LOG_PREFIX "BLOB Transfer Client suspend error (elem=%d)" NL,
+ self->elem_index);
+}
+
+static void dist_state_completed_entry(fw_dist_server_t *const self)
+{
+ log_info(LOG_PREFIX "distribution is completed (elem=%d,nodes_active=%d/%d)" NL,
+ self->elem_index,
+ self->dist.num_active_nodes,
+ self->dist.node_count);
+
+ dist_log_all_node_status(self);
+}
+
+static void dist_state_failed_entry(fw_dist_server_t *const self)
+{
+ log_info(LOG_PREFIX "distribution has failed (elem=%d,nodes_active=%d/%d)" NL,
+ self->elem_index,
+ self->dist.num_active_nodes,
+ self->dist.node_count);
+
+ dist_log_all_node_status(self);
+}
+
+static void dist_state_idle_entry(fw_dist_server_t *const self)
+{
+ (void) self;
+ log_info(LOG_PREFIX "idle state is entered (elem=%d)" NL,
+ self->elem_index);
+}
+
+static void dist_state_transition(fw_dist_server_t *const self,
+ const sl_btmesh_fw_dist_server_dist_step_t target_state)
+{
+ app_assert_s(target_state < ARRAY_LEN(dist_state_flags));
+
+ sl_btmesh_fw_dist_server_dist_step_t source_state = self->dist.state;
+ dist_state_flags_t source_state_flags = dist_state_flags[source_state];
+ dist_state_flags_t target_state_flags = dist_state_flags[target_state];
+
+ if (0 != source_state_flags.retry) {
+ // If a state with retry support is left then the retry timer is stopped
+ dist_stop_retry_timer(self);
+ }
+
+ self->dist.state = target_state;
+
+ if ((0 == source_state_flags.idle)
+ && (0 != target_state_flags.idle)) {
+ // If any of the idle sub-states are entered from a non-idle state, then
+ // the idle entry function is called.
+ dist_super_state_idle_entry(self);
+ }
+
+ if (target_state != source_state) {
+ log_debug(LOG_PREFIX "state change to %d-%s (elem=%d)" NL,
+ target_state,
+ sl_btmesh_fw_distribution_server_distribution_state_to_string(target_state),
+ self->elem_index);
+
+ if (0 != target_state_flags.retry) {
+ // If a new state with retry support is entered, then the retry counter
+ // shall be set to zero, because the retries are counted in each state
+ // separately
+ self->dist.retry_counter = 0;
+ }
+ }
+
+ if (0 != target_state_flags.execute_step) {
+ // Execute distribution step to send the state specific BT Mesh messages to
+ // the updating nodes. (Firmware Update Start/Cancel/Get/Apply/Information Get)
+ // The execute distribution check could fail if it called from the wrong state.
+ // This could happen if the retry self-transition occurs when the distribution
+ // server has just changed state but the distribution server SDK component
+ // has not processed the sl_btmesh_evt_fw_dist_server_dist_state_changed
+ // event yet.
+ // Note: This race condition exists because the retries are called by the
+ // simple timer while each sl_btmesh_step processes one event in each
+ // loop so the issue can occur if the BT Mesh stack generates multiple
+ // events and the simple timer elapses. This is a very rare issue.
+ // If the distribution step execution fails then it is a good strategy to
+ // continue and start the retry timer. If the problem is temporary then the
+ // retry call may recover the distribution process otherwise the distribution
+ // server model will timeout in the BT Mesh stack which is reported to this
+ // component by sl_btmesh_evt_fw_dist_server_dist_state_changed event.
+ // The errors are logged in the function so the return value is suppressed.
+ (void) dist_execute_step(self);
+ } else {
+ switch (target_state) {
+ case sl_btmesh_fw_dist_server_dist_step_transferring_image: {
+ if (false == self->dist.blob_transfer_started) {
+ dist_state_transferring_image_start(self);
+ } else {
+ dist_state_transferring_image_resume(self);
+ }
+ break;
+ }
+ case sl_btmesh_fw_dist_server_dist_step_waiting_for_apply: {
+ dist_state_waiting_for_apply_entry(self);
+ break;
+ }
+ case sl_btmesh_fw_dist_server_dist_step_suspended: {
+ dist_state_suspended_entry(self);
+ break;
+ }
+ case sl_btmesh_fw_dist_server_dist_step_completed: {
+ dist_state_completed_entry(self);
+ break;
+ }
+ case sl_btmesh_fw_dist_server_dist_step_failed: {
+ dist_state_failed_entry(self);
+ break;
+ }
+ case sl_btmesh_fw_dist_server_dist_step_idle: {
+ dist_state_idle_entry(self);
+ break;
+ }
+ default:
+ // No other state needs to be processed
+ break;
+ }
+ }
+
+ if (0 != target_state_flags.retry) {
+ self->dist.retry_counter++;
+ self->dist.retry_time_elapsed = false;
+ dist_start_retry_timer(self);
+ }
+}
+
+static void upload_state_transition(fw_dist_server_t *const self,
+ const upload_state_t target_state)
+{
+ app_assert_s(target_state < UPLOAD_STATE_COUNT);
+ const upload_state_t source_state = self->upload.state;
+ if ((upload_state_flags[source_state].idle == 0)
+ && (upload_state_flags[target_state].idle != 0)) {
+ // If the upload state machine transitions from a non-idle substate to an idle
+ // substate then the FW descriptor shall be deallocated
+ // Note: FW descriptor can be NULL when the upload is successful and
+ // therefore it is appended to the FW list. The metadata might be NULL
+ // because it is not mandatory but in that case the sl_free is a no-operation.
+ if (self->upload.fw_descriptor != NULL) {
+ sl_free(self->upload.fw_descriptor->metadata);
+ }
+ sl_free(self->upload.fw_descriptor);
+ self->upload.fw_descriptor = NULL;
+ }
+ self->upload.state = target_state;
+}
+
+// Called to generate BLOB ID for firmware distribution
+SL_WEAK sl_status_t
+sl_btmesh_fw_distribution_server_generate_blob_id(sl_bt_uuid_64_t *blob_id)
+{
+ sl_status_t sc;
+ if (blob_id == NULL) {
+ sc = SL_STATUS_NULL_POINTER;
+ } else {
+ size_t bytes_written;
+ sc = sl_bt_system_get_random_data(sizeof(blob_id->data),
+ sizeof(blob_id->data),
+ &bytes_written,
+ blob_id->data);
+
+ if ((sc == SL_STATUS_OK) && (bytes_written != sizeof(blob_id->data))) {
+ sc = SL_STATUS_FAIL;
+ }
+ }
+ return sc;
+}
+
+// Called when receiver updating node is added to the firmware distribution list
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_node_added(uint16_t elem_index,
+ uint16_t server_address,
+ uint8_t update_fw_image_idx,
+ uint16_t node_count)
+{
+ (void) elem_index;
+ (void) server_address;
+ (void) update_fw_image_idx;
+ (void) node_count;
+}
+
+// Called when all receiver updating nodes are deleted from the firmware
+// distribution list
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_all_nodes_deleted(uint16_t elem_index)
+{
+ (void) elem_index;
+}
+
+// Called when firmware distribution is started
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_distribution_started(uint16_t elem_index,
+ uint16_t client_address,
+ uint16_t group_address,
+ uint8_t ttl,
+ sl_btmesh_fw_dist_server_dfu_policy_t update_policy,
+ uint16_t fw_list_index,
+ uint16_t timeout_base,
+ sl_btmesh_mbt_client_transfer_mode_t transfer_mode,
+ uint16_t node_count)
+{
+ (void) elem_index;
+ (void) client_address;
+ (void) group_address;
+ (void) ttl;
+ (void) update_policy;
+ (void) fw_list_index;
+ (void) timeout_base;
+ (void) transfer_mode;
+ (void) node_count;
+}
+
+// Called when firmware distribution is canceled
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_distribution_canceled(uint16_t elem_index,
+ uint16_t client_address)
+{
+ (void) elem_index;
+ (void) client_address;
+}
+
+// Called when firmware distribution is suspended
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_distribution_suspended(uint16_t elem_index,
+ uint16_t client_address)
+{
+ (void) elem_index;
+ (void) client_address;
+}
+
+// Called when firmware distribution is resumed
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_distribution_resumed(uint16_t elem_index,
+ uint16_t client_address,
+ uint16_t group_address,
+ uint8_t ttl,
+ sl_btmesh_fw_dist_server_dfu_policy_t update_policy,
+ uint16_t fw_list_index,
+ uint16_t timeout_base,
+ sl_btmesh_mbt_client_transfer_mode_t transfer_mode,
+ uint16_t node_count)
+{
+ (void) elem_index;
+ (void) client_address;
+ (void) group_address;
+ (void) ttl;
+ (void) update_policy;
+ (void) fw_list_index;
+ (void) timeout_base;
+ (void) transfer_mode;
+ (void) node_count;
+}
+
+// Called when firmware distribution state is changed
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_distribution_state_changed(uint16_t elem_index,
+ sl_btmesh_fw_dist_server_dist_step_t state,
+ uint16_t num_active_nodes,
+ uint16_t node_count)
+{
+ (void) elem_index;
+ (void) state;
+ (void) num_active_nodes;
+ (void) node_count;
+}
+
+// Called when progress of BLOB transfer step during the firmware distribution
+// is changed
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_distribution_blob_progress_changed(uint16_t elem_index,
+ uint32_t confirmed_tx_bytes,
+ uint32_t blob_size,
+ uint16_t node_count)
+{
+ (void) elem_index;
+ (void) confirmed_tx_bytes;
+ (void) blob_size;
+ (void) node_count;
+}
+
+// Called when an Updating Node fails during the distribution
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_distribution_node_failed(uint16_t elem_index,
+ uint16_t server_address,
+ sl_btmesh_fw_dist_server_dist_node_phase_t update_phase,
+ sl_btmesh_fw_update_server_update_status_t update_status,
+ uint8_t mbt_status,
+ uint8_t progress,
+ uint8_t fw_index,
+ uint16_t num_active_nodes,
+ uint16_t node_count)
+{
+ (void) elem_index;
+ (void) server_address;
+ (void) update_phase;
+ (void) update_status;
+ (void) mbt_status;
+ (void) progress;
+ (void) fw_index;
+ (void) num_active_nodes;
+ (void) node_count;
+}
+
+// Called when firmware is added to the Distributor
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_firmware_added(uint16_t elem_index,
+ uint16_t client_address,
+ uint32_t fw_size,
+ uint16_t fw_list_idx,
+ uint16_t current_fw_list_length,
+ uint16_t max_fw_list_length,
+ const uint8_t *fwid,
+ uint8_t fwid_length)
+{
+ (void) elem_index;
+ (void) client_address;
+ (void) fw_size;
+ (void) fw_list_idx;
+ (void) current_fw_list_length;
+ (void) max_fw_list_length;
+ (void) fwid;
+ (void) fwid_length;
+}
+
+// Called when a firmware is deleted from the Distributor
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_firmware_deleted(uint16_t elem_index,
+ uint16_t client_address,
+ uint16_t current_fw_list_length,
+ uint16_t max_fw_list_length,
+ const uint8_t *fwid,
+ uint8_t fwid_length)
+{
+ (void) elem_index;
+ (void) client_address;
+ (void) current_fw_list_length;
+ (void) max_fw_list_length;
+ (void) fwid;
+ (void) fwid_length;
+}
+
+// Called when ALL firmwares are deleted from the Distributor
+SL_WEAK void
+sl_btmesh_fw_distribution_server_on_all_firmware_deleted(uint16_t elem_index,
+ uint16_t client_address,
+ uint16_t max_fw_list_length)
+{
+ (void) elem_index;
+ (void) client_address;
+ (void) max_fw_list_length;
+}
+
+sl_status_t mesh_platform_dfu_dist_server_get_fw_count(size_t element_index,
+ uint16_t *count)
+{
+ sl_status_t sc;
+ fw_dist_server_t *self = find_server(element_index);
+ sc = SERVER_STATUS_GET(self, SL_STATUS_INVALID_STATE);
+
+ if (sc == SL_STATUS_OK) {
+ if (count == NULL) {
+ sc = SL_STATUS_NULL_POINTER;
+ } else if (self->storage_corrupted != false) {
+ // The FW delete all event is triggered only when the firmware count is
+ // non-zero. If the storage is corrupted then the FW delete all event is
+ // used to clean the NVM and BLOB storage to recover from corrupted
+ // storage state.
+ *count = 1;
+ sc = SL_STATUS_FAIL;
+ } else {
+ // If count is not set then the BT Mesh stack uses zero as a default value
+ *count = self->fw_list_nvm->current_fw_list_length;
+ }
+ }
+
+ log_btmesh_platform_cb(LOG_PREFIX "%s(elem=%d)->(sc=0x%04lx,count=%u)" NL,
+ __func__,
+ element_index,
+ sc,
+ self->fw_list_nvm->current_fw_list_length);
+ return sc;
+}
+
+sl_status_t mesh_platform_dfu_dist_server_get_remaining_space(size_t element_index, uint32_t *bytes)
+{
+ sl_status_t sc;
+ uint32_t remaining_upload_space = 0;
+ fw_dist_server_t *self = find_server(element_index);
+
+ sc = SERVER_STATUS_GET(self, SL_STATUS_INVALID_STATE);
+
+ if (sc == SL_STATUS_OK) {
+ if (NULL == bytes) {
+ sc = SL_STATUS_NULL_POINTER;
+ } else if (self->storage_corrupted != false) {
+ *bytes = 0;
+ sc = SL_STATUS_FAIL;
+ } else {
+ // If bytes is not set then the BT Mesh stack uses zero as a default value
+ remaining_upload_space = calc_fw_storage_remaining_upload_space(self);
+ *bytes = remaining_upload_space;
+ }
+ }
+
+ log_btmesh_platform_cb(LOG_PREFIX "%s(elem=%d)->(sc=0x%04lx,bytes=%lu)" NL,
+ __func__,
+ element_index,
+ sc,
+ remaining_upload_space);
+ return sc;
+}
+
+sl_status_t sli_btmesh_fw_dist_server_get_fw_by_index(uint16_t element_index,
+ uint16_t fw_index,
+ mesh_dfu_dist_server_fw_info_t *info)
+{
+ sl_status_t sc;
+ fw_dist_server_t *self = find_server(element_index);
+ SERVER_STATUS_CHECK(self, SL_STATUS_INVALID_STATE);
+
+ sc = fw_list_get_fw_info_by_index(self, fw_index, FW_BLOB_SELECTOR_STORAGE, info);
+
+ return sc;
+}
+
+sl_status_t mesh_platform_dfu_dist_server_get_fw_by_index(size_t element_index,
+ uint16_t fw_index,
+ mesh_dfu_dist_server_fw_info_t *info)
+{
+ sl_status_t sc;
+ fw_dist_server_t *self = find_server(element_index);
+ SERVER_STATUS_CHECK(self, SL_STATUS_INVALID_STATE);
+
+ sc = fw_list_get_fw_info_by_index(self, fw_index, FW_BLOB_SELECTOR_CURRENT, info);
+
+#if (SL_BTMESH_FW_DIST_SERVER_PLATFORM_CALLBACK_LOGGING_CFG_VAL != 0)
+ log_debug(LOG_PREFIX "%s(elem=%d,fw_list_idx=%u)->", __func__, element_index, fw_index);
+ if ((sc != SL_STATUS_OK) || (info == NULL)) {
+ log_append_debug("(sc=0x%04lx)" NL, sc);
+ } else {
+ log_append_debug("(sc=0x%04lx,size=%lu,fwid=", sc, info->size);
+ log_fwid_level(APP_LOG_LEVEL_DEBUG, info->p_fwid, info->fwid_len, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ log_append_debug(",metadata=");
+ log_metadata_level(APP_LOG_LEVEL_DEBUG, info->p_metadata, info->metadata_len, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ log_append_debug(")" NL);
+ }
+#endif
+
+ return sc;
+}
+
+sl_status_t mesh_platform_dfu_dist_server_get_fw_by_fwid(size_t element_index,
+ uint8_t fwid_len,
+ const uint8_t *fwid,
+ mesh_dfu_dist_server_fw_info_t *info)
+{
+ sl_status_t sc;
+ fw_dist_server_t *self = find_server(element_index);
+ SERVER_STATUS_CHECK(self, SL_STATUS_INVALID_STATE);
+
+ sc = fw_list_get_fw_info_by_fwid(self, fwid, fwid_len, FW_BLOB_SELECTOR_CURRENT, info);
+
+#if (SL_BTMESH_FW_DIST_SERVER_PLATFORM_CALLBACK_LOGGING_CFG_VAL != 0)
+ log_debug(LOG_PREFIX "%s(elem=%d,fwid=", __func__, element_index);
+ log_fwid_level(APP_LOG_LEVEL_DEBUG, fwid, fwid_len, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ if ((sc != SL_STATUS_OK) || (info == NULL)) {
+ log_append_debug(")->(sc=0x%04lx)" NL, sc);
+ } else {
+ log_append_debug(")->(sc=0x%04lx,fw_list_idx=%u,size=%lu,metadata=",
+ sc,
+ info->index,
+ (unsigned long) info->size);
+ log_metadata_level(APP_LOG_LEVEL_DEBUG, info->p_metadata, info->metadata_len, SL_BTMESH_FW_DIST_SERVER_FWID_METADATA_LOG_FORMAT_HEX);
+ log_append_debug(")" NL);
+ }
+#endif
+
+ return sc;
+}
+
+// Set the multicast threshold value.
+sl_status_t sl_btmesh_fw_distribution_server_set_multicast_threshold(uint16_t elem_index,
+ uint16_t multicast_threshold)
+{
+ return sl_btmesh_fw_dist_server_set_multicast_threshold(elem_index,
+ multicast_threshold);
+}
+
+void sl_btmesh_fw_distribution_server_delete_step_handle(void)
+{
+ uint16_t element_index = 0;
+ do {
+ fw_dist_server_t *self = find_server(element_index);
+ SERVER_STATUS_CHECK(self);
+
+ if (self->deleting_all) {
+ sl_btmesh_blob_storage_delete_state_t error_code =
+ sl_btmesh_blob_storage_get_erase_error_code();
+ switch (error_code) {
+ case SL_BTMESH_BLOB_STORAGE_DELETE_FAILED:
+ handle_fw_delete_all_response(self, SL_STATUS_FLASH_ERASE_FAILED);
+ break;
+ case SL_BTMESH_BLOB_STORAGE_DELETE_SUCCESS:
+ case SL_BTMESH_BLOB_STORAGE_DELETE_INACTIVE:
+ // If all BLOBs owned by the distributor were deleted successfully or
+ // no BLOBs owned by the distributor were present in BLOB storage
+ handle_fw_delete_all_response(self, SL_STATUS_OK);
+ break;
+ default:
+ // BLOB erase is either inactive or busy
+ break;
+ }
+ }
+ if (self->deleting) {
+ sl_btmesh_blob_storage_delete_state_t error_code =
+ sl_btmesh_blob_storage_get_erase_error_code();
+ switch (error_code) {
+ case SL_BTMESH_BLOB_STORAGE_DELETE_FAILED:
+ case SL_BTMESH_BLOB_STORAGE_DELETE_INACTIVE:
+ // If the BLOB delete failed or the BLOB was not found in BLOB storage
+ // FW delete needs to delete a specific BLOB so if that is not found
+ // then it is considered as an error
+ handle_fw_delete_response(self, SL_STATUS_FLASH_ERASE_FAILED);
+ break;
+ case SL_BTMESH_BLOB_STORAGE_DELETE_SUCCESS:
+ handle_fw_delete_response(self, SL_STATUS_OK);
+ break;
+ default:
+ // BLOB erase is either inactive or busy
+ break;
+ }
+ }
+ // Should return much-much earlier, but this is a very rudimentary fail-safe
+ } while (element_index++ < UINT16_MAX);
+}
+
+// Handle Firmware Distribution Server events
+void sl_btmesh_fw_distribution_server_on_event(const sl_btmesh_msg_t *const evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id: {
+ sl_btmesh_fw_distribution_server_init();
+ break;
+ }
+ case sl_btmesh_evt_node_initialized_id: {
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_fw_distribution_server_init();
+ }
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_nodes_added_id: {
+ handle_nodes_added(&evt->data.evt_fw_dist_server_nodes_added);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_nodes_deleted_id: {
+ handle_nodes_deleted(&evt->data.evt_fw_dist_server_nodes_deleted);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_dist_start_req_id: {
+ handle_dist_start_request(&evt->data.evt_fw_dist_server_dist_start_req);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_dist_cancel_id: {
+ handle_dist_cancel(&evt->data.evt_fw_dist_server_dist_cancel);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_dist_state_changed_id: {
+ handle_dist_state_changed(
+ &evt->data.evt_fw_dist_server_dist_state_changed);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_node_failed_id: {
+ handle_dist_server_node_failed(&evt->data.evt_fw_dist_server_node_failed);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_upload_start_metadata_id: {
+ handle_upload_start_metadata(
+ &evt->data.evt_fw_dist_server_upload_start_metadata);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_upload_start_req_id: {
+ handle_upload_start_request(
+ &evt->data.evt_fw_dist_server_upload_start_req);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_upload_cancel_id: {
+ handle_upload_cancel(&evt->data.evt_fw_dist_server_upload_cancel);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_upload_complete_metadata_id: {
+ handle_upload_complete_metadata(
+ &evt->data.evt_fw_dist_server_upload_complete_metadata);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_upload_complete_id: {
+ handle_upload_complete(&evt->data.evt_fw_dist_server_upload_complete);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_upload_failed_id: {
+ handle_upload_failed(&evt->data.evt_fw_dist_server_upload_failed);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_fw_delete_req_id: {
+ handle_fw_delete_request(&evt->data.evt_fw_dist_server_fw_delete_req);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_fw_delete_all_req_id: {
+ handle_fw_delete_all_request(&evt->data.evt_fw_dist_server_fw_delete_all_req);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_dist_suspend_id: {
+ handle_dist_suspend(&evt->data.evt_fw_dist_server_dist_suspend);
+ break;
+ }
+ case sl_btmesh_evt_fw_dist_server_resume_req_id: {
+ handle_dist_resume_request(&evt->data.evt_fw_dist_server_resume_req);
+ break;
+ }
+ }
+}
+
+// Provides the string representation of distribution state
+const char *sl_btmesh_fw_distribution_server_distribution_state_to_string(sl_btmesh_fw_dist_server_dist_step_t state)
+{
+ switch (state) {
+ case sl_btmesh_fw_dist_server_dist_step_idle:
+ return "Idle";
+ case sl_btmesh_fw_dist_server_dist_step_starting_update:
+ return "Starting Update";
+ case sl_btmesh_fw_dist_server_dist_step_transferring_image:
+ return "Transfer Image";
+ case sl_btmesh_fw_dist_server_dist_step_checking_verification:
+ return "Checking Verification";
+ case sl_btmesh_fw_dist_server_dist_step_waiting_for_apply:
+ return "Waiting for Apply";
+ case sl_btmesh_fw_dist_server_dist_step_applying_update:
+ return "Apply Update";
+ case sl_btmesh_fw_dist_server_dist_step_checking_update_result:
+ return "Check Update Result";
+ case sl_btmesh_fw_dist_server_dist_step_completed:
+ return "Completed";
+ case sl_btmesh_fw_dist_server_dist_step_failed:
+ return "Failed";
+ case sl_btmesh_fw_dist_server_dist_step_cancelling:
+ return "Canceling";
+ case sl_btmesh_fw_dist_server_dist_step_suspended:
+ return "Suspended";
+ default:
+ return "INVALID";
+ }
+}
+
+// Provides the string representation of firmware update phase
+const char *sl_btmesh_fw_distribution_server_node_phase_to_string(sl_btmesh_fw_dist_server_dist_node_phase_t node_phase)
+{
+ switch (node_phase) {
+ case sl_btmesh_fw_dist_server_dist_node_phase_idle:
+ return "idle";
+ case sl_btmesh_fw_dist_server_dist_node_phase_transfer_error:
+ return "transfer error";
+ case sl_btmesh_fw_dist_server_dist_node_phase_transfer_in_progress:
+ return "transfer in progress";
+ case sl_btmesh_fw_dist_server_dist_node_phase_verifying_update:
+ return "verifying update";
+ case sl_btmesh_fw_dist_server_dist_node_phase_verification_success:
+ return "verification success";
+ case sl_btmesh_fw_dist_server_dist_node_phase_verification_failed:
+ return "verification failed";
+ case sl_btmesh_fw_dist_server_dist_node_phase_apply_in_progress:
+ return "apply in progress";
+ case sl_btmesh_fw_dist_server_dist_node_phase_transfer_cancelled:
+ return "transfer cancelled";
+ case sl_btmesh_fw_dist_server_dist_node_phase_apply_success:
+ return "apply success";
+ case sl_btmesh_fw_dist_server_dist_node_phase_apply_failed:
+ return "apply failed";
+ case sl_btmesh_fw_dist_server_dist_node_phase_unknown:
+ return "unknown";
+ default:
+ return "INVALID";
+ }
+}
+
+// Provides the string representation of node firmware update status
+const char *sl_btmesh_fw_distribution_server_firmware_update_status_to_string(sl_btmesh_fw_update_server_update_status_t update_server_status)
+{
+ switch (update_server_status) {
+ case sl_btmesh_fw_update_server_update_status_success:
+ return "success";
+ case sl_btmesh_fw_update_server_update_status_out_of_resources:
+ return "out of resources";
+ case sl_btmesh_fw_update_server_update_status_wrong_phase:
+ return "wrong phase";
+ case sl_btmesh_fw_update_server_update_status_internal_error:
+ return "internal error";
+ case sl_btmesh_fw_update_server_update_status_wrong_fw_index:
+ return "wrong fw index";
+ case sl_btmesh_fw_update_server_update_status_metadata_check_failed:
+ return "metadata check failed";
+ case sl_btmesh_fw_update_server_update_status_temporarily_unable:
+ return "temporarily unavailable";
+ case sl_btmesh_fw_update_server_update_status_blob_transfer_busy:
+ return "BLOB transfer busy";
+ default:
+ return "INVALID";
+ }
+}
+/** @} end dist_server */
diff --git a/app/btmesh/common/btmesh_fw_distribution_server/sl_btmesh_fw_distribution_server.h b/app/btmesh/common/btmesh_fw_distribution_server/sl_btmesh_fw_distribution_server.h
new file mode 100644
index 00000000000..996c4b004a3
--- /dev/null
+++ b/app/btmesh/common/btmesh_fw_distribution_server/sl_btmesh_fw_distribution_server.h
@@ -0,0 +1,464 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Firmware Distribution Server
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_FW_DISTRIBUTION_SERVER_H
+#define SL_BTMESH_FW_DISTRIBUTION_SERVER_H
+
+#include "sl_btmesh_api.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * @addtogroup dist_server BT Mesh Firmware Distribution Server
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Set the multicast threshold value
+ *
+ * The FW Distribution Server BT Mesh stack model caches the value of multicast
+ * threshold when the distribution is started and it passes the value to the MBT
+ * Client model (BLOB Transfer Client) in BT Mesh stack when the BLOB transfer
+ * is started. If the number of servers for any step exceeds or equal to this
+ * number then the group address will be used, otherwise the servers will be
+ * looped through one by one. Value of 0 disables the feature.
+ *
+ * The MBT Client BT Mesh stack model tracks the number of servers which it needs
+ * to send specific messages to.
+ * It tracks for each server if the proper status message is received from the
+ * server in case of BLOB Information Get, BLOB Transfer Start, BLOB Block Start
+ * and BLOB Block Get messages. If some servers don't receive these BLOB messages
+ * or the response status messages are lost due to interference then the retry
+ * logic in BLOB Transfer Client SDK component requests to send the necessary
+ * BLOB messages to the servers by calling the proper MBT Client API again.
+ * If number of servers with missing status messages exceeds or equal to the
+ * multicast threshold value then the mentioned messages are sent to the group
+ * address, otherwise servers will be looped through one by one.
+ *
+ * The tracking of successful reception of Chunk Transfer Messages are different
+ * because these are tracked by block status procedure in case of push transfer
+ * mode or by partial block report procedure in case of pull transfer mode.
+ *
+ * The advantage of multicast messages is that the client needs to send one
+ * message only to the servers which is much faster if the number of servers
+ * which haven't received the message is high. This is especially true for chunk
+ * transfer because Chunk Transfer messages can be quite big and there are
+ * quite many of them in the BLOB Transfer.
+ *
+ * The advantage of unicast addressing in case of non-chunk messages is that only
+ * those server respond with status messages which really need to. This can have
+ * significant effect when the node count is high.
+ * For example if there are hundreds of nodes in the BLOB transfer then each
+ * group message triggers hundreds of status messages even when only some status
+ * messages from specific servers are missing on the client side.
+ * The transport layer uses segment acknowledgment for segmented unicast mesh
+ * messages which improves the reliability of segmented message transfer
+ * especially when the number of segments are high but it tends to be slower
+ * because the segment acknowledgment messages needs to be waited.
+ *
+ * @warning The MBT client stack model allocates and sends all unicast BLOB
+ * messages to each server at the same time below the multicast threshold.
+ * This could lead to exhaustive buffer allocation and the maximum number of
+ * parallel segmented messages can be exceeded as well in case of high
+ * multicast threshold level. The multicast threshold shall not be greater
+ * than SL_BTMESH_CONFIG_MAX_SEND_SEGS from sl_btmesh_config.h.
+ *
+ * @note The multicast threshold shall be set when the distribution is started
+ * (or before) in order to have an effect on the BLOB transfer otherwise it
+ * affects the next transfer only. The multicast threshold can be set in
+ * @ref sl_btmesh_fw_distribution_server_on_distribution_started callback as
+ * well to tune it to the specific FW distribution.
+ *
+ * @param elem_index Distribution Server model element index
+ * @param multicast_threshold If the number of servers for any step exceeds or
+ * is equal to this number then the group address will be used, otherwise
+ * servers will be looped through one by one.
+ * Value of 0 disables the feature.
+ *
+ * @return SL_STATUS_OK if successful. Error code otherwise.
+ *
+ ******************************************************************************/
+sl_status_t sl_btmesh_fw_distribution_server_set_multicast_threshold(uint16_t elem_index,
+ uint16_t multicast_threshold);
+
+/***************************************************************************//**
+ * Handle Firmware Distribution Server events
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_event(const sl_btmesh_msg_t *const evt);
+
+/***************************************************************************//**
+ * Provides string representations of Distribution states
+ *
+ * @param[in] state Distribution state
+ *
+ * @return String representation of distribution state
+ * @retval "INVALID" if the state is invalid
+ ******************************************************************************/
+const char* sl_btmesh_fw_distribution_server_distribution_state_to_string(sl_btmesh_fw_dist_server_dist_step_t state);
+
+/***************************************************************************//**
+ * Provides string representations of Firmware Update phase
+ *
+ * @param[in] node_phase Phase of Firmware Update Server on Updating Node
+ *
+ * @return String representation of node update phase
+ * @retval "INVALID" if the node_phase is invalid
+ ******************************************************************************/
+const char* sl_btmesh_fw_distribution_server_node_phase_to_string(sl_btmesh_fw_dist_server_dist_node_phase_t node_phase);
+
+/***************************************************************************//**
+ * Provides string representations of Firmware Update status
+ *
+ * @param[in] node_status Status of Firmware Update Server on Updating Node
+ *
+ * @return String representation of node's firmware update status
+ * @retval "INVALID" if the node_status is invalid
+ ******************************************************************************/
+const char* sl_btmesh_fw_distribution_server_firmware_update_status_to_string(sl_btmesh_fw_update_server_update_status_t node_status);
+
+// -----------------------------------------------------------------------------
+// Overridable Callback Functions
+// -----------------------------------------------------------------------------
+
+/***************************************************************************//**
+ * Called to generate BLOB ID for firmware distribution
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which uses the sl_bt_system_get_random_data
+ * BT stack API to generate random bytes for BLOB identifier.
+ *
+ * @param[out] blob_id Pointer to blob_id where the generated bytes shall be stored
+ *
+ * @return Status of BLOB id generation. If it is not SL_STATUS_OK then the
+ * firmware distribution server component uses the storage BLOB id for the
+ * firmware distribution. The storage BLOB id was used during firmware upload.
+ *
+ ******************************************************************************/
+sl_status_t sl_btmesh_fw_distribution_server_generate_blob_id(sl_bt_uuid_64_t *blob_id);
+
+/***************************************************************************//**
+ * Called when a receiver updating node is added to the firmware distribution
+ * list
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] server_address Unicast address of the added updating node
+ * @param[in] update_fw_image_idx Firmware image index on Updating node to be
+ * updated during the distribution
+ * @param[in] node_count Total number of updating nodes which participates in
+ * the firmware distribution (including this new one)
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_node_added(uint16_t elem_index,
+ uint16_t server_address,
+ uint8_t update_fw_image_idx,
+ uint16_t node_count);
+
+/***************************************************************************//**
+ * Called when all receiver updating nodes are deleted from the firmware
+ * distribution list
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_all_nodes_deleted(uint16_t elem_index);
+
+/***************************************************************************//**
+ * Called when firmware distribution is started
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * The @ref sl_btmesh_fw_distribution_server_set_multicast_threshold function
+ * can be called from this callback to set the multicast threshold value for
+ * the current distribution.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] client_address Address of the Distribution Client
+ * @param[in] group_address Group address of the distribution
+ * @param[in] ttl Distribution TTL
+ * @param[in] update_policy 1 if the Update Policy is Verify and Apply, otherwise 0
+ * @param[in] fw_list_index Index of the firmware in the Distributor's FW List
+ * @param[in] timeout_base Timeout base
+ * @param[in] transfer_mode Transfer Mode to use in the distribution
+ * @param[in] node_count Total number of updating nodes which participates in
+ * the firmware distribution
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_distribution_started(uint16_t elem_index,
+ uint16_t client_address,
+ uint16_t group_address,
+ uint8_t ttl,
+ sl_btmesh_fw_dist_server_dfu_policy_t update_policy,
+ uint16_t fw_list_index,
+ uint16_t timeout_base,
+ sl_btmesh_mbt_client_transfer_mode_t transfer_mode,
+ uint16_t node_count);
+
+/***************************************************************************//**
+ * Called when firmware distribution is canceled
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] client_address Address of the Distribution Client
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_distribution_canceled(uint16_t elem_index,
+ uint16_t client_address);
+
+/***************************************************************************//**
+ * Called when firmware distribution is suspended
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] client_address Address of the Distribution Client
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_distribution_suspended(uint16_t elem_index,
+ uint16_t client_address);
+
+/***************************************************************************//**
+ * Called when firmware distribution is suspended
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] client_address Address of the Distribution Client
+ * @param[in] group_address Group address of the distribution
+ * @param[in] ttl Distribution TTL
+ * @param[in] update_policy 1 if the Update Policy is Verify and Apply, otherwise 0
+ * @param[in] fw_list_index Index of the firmware in the Distributor's FW List
+ * @param[in] timeout_base Timeout base
+ * @param[in] transfer_mode Transfer Mode to use in the distribution
+ * @param[in] node_count Total number of updating nodes which participates in
+ * the firmware distribution
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_distribution_resumed(uint16_t elem_index,
+ uint16_t client_address,
+ uint16_t group_address,
+ uint8_t ttl,
+ sl_btmesh_fw_dist_server_dfu_policy_t update_policy,
+ uint16_t fw_list_index,
+ uint16_t timeout_base,
+ sl_btmesh_mbt_client_transfer_mode_t transfer_mode,
+ uint16_t node_count);
+
+/***************************************************************************//**
+ * Called when firmware distribution state has changed
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] state Distribution state
+ * @param[in] num_active_nodes Number of updating nodes which are still active
+ * @param[in] node_count Total number of updating nodes
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_distribution_state_changed(uint16_t elem_index,
+ sl_btmesh_fw_dist_server_dist_step_t state,
+ uint16_t num_active_nodes,
+ uint16_t node_count);
+
+/***************************************************************************//**
+ * Called when progress of BLOB transfer step during the firmware distribution
+ * has changed
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] confirmed_tx_bytes Number of BLOB bytes which are received by
+ * every active BLOB Transfer Server. The progress is calculated at the end
+ * of the blocks because in a block some chunks could be retransmitted
+ * multiple times.
+ * @param[in] blob_size Size of the BLOB
+ * @param[in] node_count Number of updating nodes
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_distribution_blob_progress_changed(uint16_t elem_index,
+ uint32_t confirmed_tx_bytes,
+ uint32_t blob_size,
+ uint16_t node_count);
+
+/***************************************************************************//**
+ * Called when an Updating Node fails during the distribution
+ *
+ * If one node fails during the distribution, it does not mean, that the
+ * distribution fails as well. The distribution only fails, when every Updating
+ * Node fails.
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] server_address Unicast address of the failed Updating Node
+ * @param[in] update_phase Update Phase of Firmware Update Server on Updating Node
+ * @param[in] update_status Update Status of Firmware Update Server on Updating Node
+ * If the firmware update of the node fails due to Firmware Update Server related
+ * error then this field isn't zero and it has sl_btmesh_fw_update_server_update_status_t
+ * value.
+ * @param[in] mbt_status Transfer status of BLOB Transfer server on Updating Node.
+ * If the firmware update of a node fails due to BLOB Transfer error then this
+ * field won't be zero.
+ * @param[in] progress 0-100, percentage of BLOB Transfer octets
+ * @param[in] fw_index Firmware image index on Updating node which is updated
+ * during the distribution
+ * @param[in] num_active_nodes Number of updating nodes which are still active
+ * @param[in] node_count Total number of updating nodes
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_distribution_node_failed(uint16_t elem_index,
+ uint16_t server_address,
+ sl_btmesh_fw_dist_server_dist_node_phase_t update_phase,
+ sl_btmesh_fw_update_server_update_status_t update_status,
+ uint8_t mbt_status,
+ uint8_t progress,
+ uint8_t fw_index,
+ uint16_t num_active_nodes,
+ uint16_t node_count);
+
+/***************************************************************************//**
+ * Called when firmware has been added to the Distributor
+ * - Called when a new firmware has been uploaded to the Distributor
+ * - Called when the firmware has been added based on the NVM content at
+ * initialization time
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] client_address Address of the Distribution Client
+ * Set to unassigned address (0x0000) when the firmware is added at init time.
+ * @param[in] fw_size Size of the firmware binary image
+ * @param[in] fw_list_idx Index of the stored firmware in the firmware list
+ * @param[in] current_fw_list_length Current number of stored firmware including
+ * this uploaded firmware as well.
+ * @param[in] max_fw_list_length Maximum number of firmwares which can be stored
+ * on the Distributor
+ * @param[in] fwid Firmware identifier with 2-byte Company ID which is
+ * followed by 0-106 bytes of Version Information
+ * @param[in] fwid_length Length of the Firmware identifier data
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_firmware_added(uint16_t elem_index,
+ uint16_t client_address,
+ uint32_t fw_size,
+ uint16_t fw_list_idx,
+ uint16_t current_fw_list_length,
+ uint16_t max_fw_list_length,
+ const uint8_t *fwid,
+ uint8_t fwid_length);
+
+/***************************************************************************//**
+ * Called when a firmware has been deleted from the Distributor
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] client_address Address of the Distribution Client
+ * @param[in] current_fw_list_length Current number of stored firmwares after the
+ * firmware is deleted from the Distributor
+ * @param[in] max_fw_list_length Maximum number of firmwares which can be stored
+ * on the Distributor
+ * @param[in] fwid Firmware identifier with 2-byte Company ID which is
+ * followed by 0-106 bytes of Version Information
+ * @param[in] fwid_length Length of the Firmware identifier data
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_firmware_deleted(uint16_t elem_index,
+ uint16_t client_address,
+ uint16_t current_fw_list_length,
+ uint16_t max_fw_list_length,
+ const uint8_t *fwid,
+ uint8_t fwid_length);
+
+/***************************************************************************//**
+ * Called when ALL firmwares are deleted from the Distributor
+ *
+ * This is a callback which can be implemented in the application
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] elem_index Distribution server model element index
+ * @param[in] client_address Address of the Distribution Client
+ * It is set to MESH_ADDR_UNASSIGNED when it is caused by an internal event.
+ * @param[in] max_fw_list_length Maximum number of firmwares which can be stored
+ * on the Distributor
+ *
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_on_all_firmware_deleted(uint16_t elem_index,
+ uint16_t client_address,
+ uint16_t max_fw_list_length);
+
+/***************************************************************************//**
+ * Used for asynchronously deleting firmware images from BLOB storage
+ ******************************************************************************/
+void sl_btmesh_fw_distribution_server_delete_step_handle(void);
+
+/** @} end dist_server */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif // SL_BTMESH_FW_DISTRIBUTION_SERVER_H
diff --git a/app/btmesh/common/btmesh_fw_distribution_server/sli_btmesh_fw_distribution_server.h b/app/btmesh/common/btmesh_fw_distribution_server/sli_btmesh_fw_distribution_server.h
new file mode 100644
index 00000000000..ddee663a265
--- /dev/null
+++ b/app/btmesh/common/btmesh_fw_distribution_server/sli_btmesh_fw_distribution_server.h
@@ -0,0 +1,52 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Firmware Distribution Server internal interfaces
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SLI_BTMESH_FW_DISTRIBUTION_SERVER_H_
+#define SLI_BTMESH_FW_DISTRIBUTION_SERVER_H_
+
+#include "sl_btmesh_dfu_platform_capi.h"
+
+/***************************************************************************//**
+ * Look up firmware information in NVM for given index
+ *
+ * @param element_index Index of the element
+ * @param fw_index Index of the firmware
+ * @param info Information buffer structure
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_STATE If firmware list is uninitialized
+ * @retval SL_STATUS_BT_MESH_DOES_NOT_EXIST If index is not in range
+ ******************************************************************************/
+sl_status_t sli_btmesh_fw_dist_server_get_fw_by_index(uint16_t element_index,
+ uint16_t fw_index,
+ mesh_dfu_dist_server_fw_info_t *info);
+
+#endif // SLI_BTMESH_FW_DISTRIBUTION_SERVER_H_
diff --git a/app/btmesh/common/btmesh_generic_base/config/sl_btmesh_generic_base_config.h b/app/btmesh/common/btmesh_generic_base/config/sl_btmesh_generic_base_config.h
new file mode 100644
index 00000000000..5882686ca50
--- /dev/null
+++ b/app/btmesh/common/btmesh_generic_base/config/sl_btmesh_generic_base_config.h
@@ -0,0 +1,175 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_GENERIC_BASE_CONFIG_H
+#define SL_BTMESH_GENERIC_BASE_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Generic Base configuration
+
+// Register size increment <0-10>
+// Default: 3
+// The dynamically reallocated array will grow in size by this value.
+// Setting this value to 0 will disable reallocation.
+#define SL_BTMESH_GENERIC_BASE_INCREMENT_CFG_VAL 3
+
+//
+
+// Generic Models Initialization configuration
+
+// Enable Generic Server Models
+// Default: 0
+// Enable Generic Server functionality.
+#define SL_BTMESH_GENERIC_BASE_SERVER_CFG_VAL 0
+
+// Generic On/Off Server
+// Default: 0
+// Initialize Generic On/Off Server.
+#define SL_BTMESH_GENERIC_ON_OFF_SERVER_INIT_CFG_VAL 0
+
+// Generic Level Server
+// Default: 0
+// Initialize Generic Level Server.
+#define SL_BTMESH_GENERIC_LEVEL_SERVER_INIT_CFG_VAL 0
+
+// Generic Default Transition Time Server
+// Default: 0
+// Initialize Generic Default Transition Time Server.
+#define SL_BTMESH_GENERIC_DEFAULT_TRANSITION_TIME_SERVER_INIT_CFG_VAL 0
+
+// Generic Power On/Off Server
+// Default: 0
+// Initialize Generic Power On/Off Server.
+#define SL_BTMESH_GENERIC_POWER_ON_OFF_SERVER_INIT_CFG_VAL 0
+
+// Generic Power Level Server
+// Default: 0
+// Initialize Generic Power Level Server.
+#define SL_BTMESH_GENERIC_POWER_LEVEL_SERVER_INIT_CFG_VAL 0
+
+// Generic Battery Server
+// Default: 0
+// Initialize Generic Battery Server.
+#define SL_BTMESH_GENERIC_BATTERY_SERVER_INIT_CFG_VAL 0
+
+// Generic Location Server
+// Default: 0
+// Initialize Generic Location Server.
+#define SL_BTMESH_GENERIC_LOCATION_SERVER_INIT_CFG_VAL 0
+
+// Generic Property Server
+// Default: 0
+// Initialize Generic Property Server.
+#define SL_BTMESH_GENERIC_PROPERTY_SERVER_INIT_CFG_VAL 0
+
+// Light Lightness Server
+// Default: 0
+// Initialize Light Lightness Server.
+#define SL_BTMESH_GENERIC_LIGHTNESS_SERVER_INIT_CFG_VAL 0
+
+// Light CTL Server
+// Default: 0
+// Initialize Light CTL Server.
+#define SL_BTMESH_GENERIC_CTL_SERVER_INIT_CFG_VAL 0
+
+// Light HSL Server
+// Default: 0
+// Initialize Light HSL Server.
+#define SL_BTMESH_GENERIC_HSL_SERVER_INIT_CFG_VAL 0
+
+//
+
+// Enable Generic Client Models
+// Default: 0
+// Enable Generic Client functionality.
+#define SL_BTMESH_GENERIC_BASE_CLIENT_CFG_VAL 0
+
+// Generic On/Off Client
+// Default: 0
+// Initialize Generic On/Off Client.
+#define SL_BTMESH_GENERIC_ON_OFF_CLIENT_INIT_CFG_VAL 0
+
+// Generic Level Client
+// Default: 0
+// Initialize Generic Level Client.
+#define SL_BTMESH_GENERIC_LEVEL_CLIENT_INIT_CFG_VAL 0
+
+// Generic Default Transition Time Client
+// Default: 0
+// Initialize Generic Default Transition Time Client.
+#define SL_BTMESH_GENERIC_DEFAULT_TRANSITION_TIME_CLIENT_INIT_CFG_VAL 0
+
+// Generic Power On/Off Client
+// Default: 0
+// Initialize Generic Power On/Off Client.
+#define SL_BTMESH_GENERIC_POWER_ON_OFF_CLIENT_INIT_CFG_VAL 0
+
+// Generic Power Level Client
+// Default: 0
+// Initialize Generic Power Level Client.
+#define SL_BTMESH_GENERIC_POWER_LEVEL_CLIENT_INIT_CFG_VAL 0
+
+// Generic Battery Client
+// Default: 0
+// Initialize Generic Battery Client.
+#define SL_BTMESH_GENERIC_BATTERY_CLIENT_INIT_CFG_VAL 0
+
+// Generic Location Client
+// Default: 0
+// Initialize Generic Location Client.
+#define SL_BTMESH_GENERIC_LOCATION_CLIENT_INIT_CFG_VAL 0
+
+// Generic Property Client
+// Default: 0
+// Initialize Generic Property Client.
+#define SL_BTMESH_GENERIC_PROPERTY_CLIENT_INIT_CFG_VAL 0
+
+// Light Lightness Client
+// Default: 0
+// Initialize Lightness Client.
+#define SL_BTMESH_GENERIC_LIGHTNESS_CLIENT_INIT_CFG_VAL 0
+
+// Light CTL Client
+// Default: 0
+// Initialize Light CTL Client.
+#define SL_BTMESH_GENERIC_CTL_CLIENT_INIT_CFG_VAL 0
+
+// Light HSL Client
+// Default: 0
+// Initialize Light HSL Client.
+#define SL_BTMESH_GENERIC_HSL_CLIENT_INIT_CFG_VAL 0
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_GENERIC_BASE_CONFIG_H
diff --git a/app/btmesh/common/btmesh_generic_base/sl_btmesh_generic_base.c b/app/btmesh/common/btmesh_generic_base/sl_btmesh_generic_base.c
new file mode 100644
index 00000000000..bd15c70a62e
--- /dev/null
+++ b/app/btmesh/common/btmesh_generic_base/sl_btmesh_generic_base.c
@@ -0,0 +1,183 @@
+/***************************************************************************//**
+ * @file
+ * @brief Mesh Generic Base library
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "app_assert.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#include "sl_btmesh_generic_model_capi_types.h"
+#include "sl_btmesh_lib.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#include "sl_btmesh_generic_base.h"
+#include "sl_btmesh_generic_base_config.h"
+
+sl_status_t sl_btmesh_generic_base_init(void)
+{
+ return mesh_lib_init(SL_BTMESH_GENERIC_BASE_REGISTRY_INIT_SIZE,
+ SL_BTMESH_GENERIC_BASE_INCREMENT_CFG_VAL);
+}
+
+void sl_btmesh_generic_base_on_event(sl_btmesh_msg_t *evt)
+{
+ sl_status_t sc = SL_STATUS_OK;
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+#if SL_BTMESH_GENERIC_BASE_SERVER_CFG_VAL || defined(SL_CATALOG_BTMESH_LIGHTING_SERVER_PRESENT) \
+ || defined(SL_CATALOG_BTMESH_CTL_SERVER_PRESENT) \
+ || defined(SL_CATALOG_BTMESH_HSL_SERVER_PRESENT) \
+ || defined(SL_CATALOG_BTMESH_GENERIC_ONOFF_SERVER_PRESENT)
+
+#if SL_BTMESH_GENERIC_CTL_SERVER_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_CTL_SERVER_PRESENT) \
+ || SL_BTMESH_GENERIC_HSL_SERVER_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_HSL_SERVER_PRESENT) \
+ || SL_BTMESH_GENERIC_POWER_LEVEL_SERVER_INIT_CFG_VAL
+
+ #if SL_BTMESH_GENERIC_CTL_SERVER_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_CTL_SERVER_PRESENT)
+ sc = sl_btmesh_generic_server_init_ctl();
+ app_assert_status_f(sc, "Failed to init ctl server");
+ #endif // SL_BTMESH_GENERIC_CTL_SERVER_INIT_CFG_VAL
+ #if SL_BTMESH_GENERIC_HSL_SERVER_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_HSL_SERVER_PRESENT)
+ sc = sl_btmesh_generic_server_init_hsl();
+ app_assert_status_f(sc, "Failed to init hsl server");
+ #endif // SL_BTMESH_GENERIC_HSL_SERVER_INIT_CFG_VAL
+ #if SL_BTMESH_GENERIC_POWER_LEVEL_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_server_init_power_level();
+ app_assert_status_f(sc, "Failed to init power level server");
+ #endif // SL_BTMESH_GENERIC_POWER_LEVEL_SERVER_INIT_CFG_VAL
+#elif SL_BTMESH_GENERIC_LIGHTNESS_SERVER_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_LIGHTING_SERVER_PRESENT)
+ sc = sl_btmesh_generic_server_init_lightness();
+ app_assert_status_f(sc, "Failed to init lightness server");
+#else // SL_BTMESH_GENERIC_CTL_SERVER_INIT_CFG_VAL || SL_BTMESH_GENERIC_HSL_SERVER_INIT_CFG_VAL || SL_BTMESH_GENERIC_POWER_LEVEL_SERVER_INIT_CFG_VAL
+ #if SL_BTMESH_GENERIC_LEVEL_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_server_init_level();
+ app_assert_status_f(sc, "Failed to init level server");
+ #endif // SL_BTMESH_GENERIC_LEVEL_SERVER_INIT_CFG_VAL
+ #if SL_BTMESH_GENERIC_POWER_ON_OFF_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_server_init_power_on_off();
+ app_assert_status_f(sc, "Failed to init power on/off server");
+ #else //SL_BTMESH_GENERIC_POWER_ON_OFF_SERVER_INIT_CFG_VAL
+ #if SL_BTMESH_GENERIC_ON_OFF_SERVER_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_GENERIC_ONOFF_SERVER_PRESENT)
+ sc = sl_btmesh_generic_server_init_on_off();
+ app_assert_status_f(sc, "Failed to init on/off server");
+ #endif // SL_BTMESH_GENERIC_ON_OFF_SERVER_INIT_CFG_VAL
+ #if SL_BTMESH_GENERIC_DEFAULT_TRANSITION_TIME_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_server_init_default_transition_time();
+ app_assert_status_f(sc, "Failed to init default transition time server");
+ #endif // SL_BTMESH_GENERIC_DEFAULT_TRANSITION_TIME_SERVER_INIT_CFG_VAL
+ #endif //SL_BTMESH_GENERIC_POWER_ON_OFF_SERVER_INIT_CFG_VAL
+#endif // SL_BTMESH_GENERIC_CTL_SERVER_INIT_CFG_VAL || SL_BTMESH_GENERIC_HSL_SERVER_INIT_CFG_VAL || SL_BTMESH_GENERIC_POWER_LEVEL_SERVER_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_BATTERY_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_server_init_battery();
+ app_assert_status_f(sc, "Failed to init battery server");
+#endif // SL_BTMESH_GENERIC_BATTERY_SERVER_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_LOCATION_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_server_init_location();
+ app_assert_status_f(sc, "Failed to init location server");
+#endif // SL_BTMESH_GENERIC_LOCATION_SERVER_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_PROPERTY_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_server_init_property();
+ app_assert_status_f(sc, "Failed to init property server");
+#endif // SL_BTMESH_GENERIC_PROPERTY_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_server_init_common();
+ app_assert_status_f(sc, "Failed to common init Generic Server");
+#endif // SL_BTMESH_GENERIC_BASE_SERVER_CFG_VAL
+
+#if SL_BTMESH_GENERIC_BASE_CLIENT_CFG_VAL || defined(SL_CATALOG_BTMESH_LIGHTING_CLIENT_PRESENT) \
+ || defined(SL_CATALOG_BTMESH_CTL_CLIENT_PRESENT)
+
+#if SL_BTMESH_GENERIC_ON_OFF_CLIENT_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_LIGHTING_CLIENT_PRESENT)
+ sc = sl_btmesh_generic_client_init_on_off();
+ app_assert_status_f(sc, "Failed to init on/off client");
+#endif // SL_BTMESH_GENERIC_ON_OFF_CLIENT_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_LIGHTING_CLIENT_PRESENT)
+#if SL_BTMESH_GENERIC_LEVEL_SERVER_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_level();
+ app_assert_status_f(sc, "Failed to init level client");
+#endif // SL_BTMESH_GENERIC_LEVEL_SERVER_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_DEFAULT_TRANSITION_TIME_CLIENT_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_default_transition_time();
+ app_assert_status_f(sc, "Failed to init default transition time client");
+#endif // SL_BTMESH_GENERIC_DEFAULT_TRANSITION_TIME_CLIENT_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_POWER_ON_OFF_CLIENT_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_power_on_off();
+ app_assert_status_f(sc, "Failed to init power on/off client");
+#endif // SL_BTMESH_GENERIC_POWER_ON_OFF_CLIENT_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_POWER_LEVEL_CLIENT_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_power_level();
+ app_assert_status_f(sc, "Failed to init power level client");
+#endif // SL_BTMESH_GENERIC_POWER_LEVEL_CLIENT_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_BATTERY_CLIENT_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_battery();
+ app_assert_status_f(sc, "Failed to init battery client");
+#endif // SL_BTMESH_GENERIC_BATTERY_CLIENT_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_LOCATION_CLIENT_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_location();
+ app_assert_status_f(sc, "Failed to init location client");
+#endif // SL_BTMESH_GENERIC_LOCATION_CLIENT_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_PROPERTY_CLIENT_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_property();
+ app_assert_status_f(sc, "Failed to init property client");
+#endif // SL_BTMESH_GENERIC_PROPERTY_CLIENT_INIT_CFG_VAL
+#if SL_BTMESH_GENERIC_LIGHTNESS_CLIENT_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_LIGHTING_CLIENT_PRESENT)
+ sc = sl_btmesh_generic_client_init_lightness();
+ app_assert_status_f(sc, "Failed to init lightness client");
+#endif // SL_BTMESH_GENERIC_LIGHTNESS_CLIENT_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_LIGHTING_CLIENT_PRESENT)
+#if SL_BTMESH_GENERIC_CTL_CLIENT_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_CTL_CLIENT_PRESENT)
+ sc = sl_btmesh_generic_client_init_ctl();
+ app_assert_status_f(sc, "Failed to init ctl client");
+#endif // SL_BTMESH_GENERIC_CTL_CLIENT_INIT_CFG_VAL || defined(SL_CATALOG_BTMESH_CTL_CLIENT_PRESENT)
+#if SL_BTMESH_GENERIC_HSL_CLIENT_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_hsl();
+ app_assert_status_f(sc, "Failed to init hsl client");
+#endif // SL_BTMESH_GENERIC_HSL_CLIENT_INIT_CFG_VAL
+ sc = sl_btmesh_generic_client_init_common();
+ app_assert_status_f(sc, "Failed to common init Generic Client");
+
+#endif // SL_BTMESH_GENERIC_BASE_CLIENT_CFG_VAL
+ break;
+#if SL_BTMESH_GENERIC_BASE_SERVER_CFG_VAL || defined(SL_CATALOG_BTMESH_LIGHTING_SERVER_PRESENT)
+ case sl_btmesh_evt_generic_server_client_request_id:
+ // intentional fall through
+ case sl_btmesh_evt_generic_server_state_recall_id:
+ // intentional fall through
+ case sl_btmesh_evt_generic_server_state_changed_id:
+ mesh_lib_generic_server_event_handler(evt);
+ break;
+#endif // SL_BTMESH_GENERIC_BASE_SERVER_CFG_VAL
+#if SL_BTMESH_GENERIC_BASE_CLIENT_CFG_VAL
+ case sl_btmesh_evt_generic_client_server_status_id:
+ mesh_lib_generic_client_event_handler(evt);
+ break;
+#endif // SL_BTMESH_GENERIC_BASE_CLIENT_CFG_VAL
+ }
+}
diff --git a/app/btmesh/common/btmesh_generic_base/sl_btmesh_generic_base.h.jinja b/app/btmesh/common/btmesh_generic_base/sl_btmesh_generic_base.h.jinja
new file mode 100644
index 00000000000..b1cc3f82993
--- /dev/null
+++ b/app/btmesh/common/btmesh_generic_base/sl_btmesh_generic_base.h.jinja
@@ -0,0 +1,38 @@
+/***************************************************************************//**
+ * @brief Mesh Generic Base library
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * The licensor of this software is Silicon Laboratories Inc. Your use of this
+ * software is governed by the terms of Silicon Labs Master Software License
+ * Agreement (MSLA) available at
+ * www.silabs.com/about-us/legal/master-software-license-agreement. This
+ * software is distributed to you in Source Code format and is governed by the
+ * sections of the MSLA applicable to Source Code.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_GENERIC_BASE_H
+#define SL_BTMESH_GENERIC_BASE_H
+
+/**
+ * @brief Initial size for handler registry
+ *
+ * Calculated based on definitions in component configuration files.
+ */
+#define SL_BTMESH_GENERIC_BASE_REGISTRY_INIT_SIZE {{ btmesh_generic_handler | sum(attribute='count')}}
+
+/**
+ * @brief Initializes the generic base functionality
+ */
+extern sl_status_t sl_btmesh_generic_base_init(void);
+
+/**
+ * @brief Handles events for generic models
+ * @param evt Event to handle
+ */
+extern void sl_btmesh_generic_base_on_event(sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_GENERIC_BASE_H
diff --git a/app/btmesh/common/btmesh_hsl_server/btmesh_hsl_server.dcd b/app/btmesh/common/btmesh_hsl_server/btmesh_hsl_server.dcd
new file mode 100644
index 00000000000..95563832bbe
--- /dev/null
+++ b/app/btmesh/common/btmesh_hsl_server/btmesh_hsl_server.dcd
@@ -0,0 +1,26 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models": [
+ { "mid": "0x1307", "name": "Light HSL Server" },
+ { "mid": "0x1308", "name": "Light HSL Setup Server" }
+ ]
+ },
+ {
+ "name": "Hue",
+ "location": "0x0000",
+ "sig_models": [
+ { "mid": "0x1002", "name": "Generic Level Server" },
+ { "mid": "0x130a", "name": "Light HSL Hue Server" }
+ ]
+ },
+ {
+ "name": "Saturation",
+ "location": "0x0000",
+ "sig_models": [
+ { "mid": "0x1002", "name": "Generic Level Server" },
+ { "mid": "0x130b", "name": "Light HSL Saturation Server" }
+ ]
+ }
+]
diff --git a/app/btmesh/common/btmesh_hsl_server/config/sl_btmesh_hsl_server_config.h b/app/btmesh/common/btmesh_hsl_server/config/sl_btmesh_hsl_server_config.h
new file mode 100644
index 00000000000..0c48f90979d
--- /dev/null
+++ b/app/btmesh/common/btmesh_hsl_server/config/sl_btmesh_hsl_server_config.h
@@ -0,0 +1,89 @@
+#ifndef SL_BTMESH_HSL_SERVER_CONFIG_H
+#define SL_BTMESH_HSL_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// HSL Server configuration
+
+// Timeout [ms] for saving States of the model to NVM.
+// Default: 5000
+// Timeout [ms] for saving States of the model to NVM.
+#define SL_BTMESH_HSL_SERVER_NVM_SAVE_TIME_CFG_VAL (5000)
+
+// PS Key for NVM Page where the States of the HSL Models are saved.
+// Default: 0x4008
+// PS Key for NVM Page where the States of the HSL Models are saved.
+#define SL_BTMESH_HSL_SERVER_PS_KEY_CFG_VAL (0x4008)
+
+// Periodicity [ms] for updating the hue during a transition.
+// Default: 1
+// Periodicity [ms] for updating the hue during a transition.
+#define SL_BTMESH_HSL_SERVER_HUE_UPDATE_PERIOD_CFG_VAL (1)
+
+// Periodicity [ms] for updating the saturation during a transition.
+// Default: 1
+// Periodicity [ms] for updating the saturation during a transition.
+#define SL_BTMESH_HSL_SERVER_SATURATION_UPDATE_PERIOD_CFG_VAL (1)
+
+// Periodicity [ms] for updating the UI with hue during a transition.
+// Default: 100
+// Periodicity [ms] for updating the hue values on the UI.
+#define SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL (100)
+
+// Periodicity [ms] for updating the UI with saturation during a transition.
+// Default: 100
+// Periodicity [ms] for updating the saturation values on the UI.
+#define SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL (100)
+
+// Default Hue
+// Default: 0
+// Default Hue value.
+#define SL_BTMESH_HSL_SERVER_DEFAULT_HUE_CFG_VAL (0)
+
+// Default Saturation
+// Default: 0
+// Default Saturation.
+#define SL_BTMESH_HSL_SERVER_DEFAULT_SATURATION_CFG_VAL (0)
+
+// Minimum Hue
+// Default: 0
+// Minimum Hue.
+#define SL_BTMESH_HSL_SERVER_MINIMUM_HUE_CFG_VAL (0)
+
+// Maximum Hue
+// Default: 65535
+// Maximum Hue.
+#define SL_BTMESH_HSL_SERVER_MAXIMUM_HUE_CFG_VAL (65535)
+
+// Minimum Saturation
+// Default: 0
+// Minimum Saturation.
+#define SL_BTMESH_HSL_SERVER_MINIMUM_SATURATION_CFG_VAL (0)
+
+// Maximum Saturation
+// Default: 65535
+// Maximum Saturation.
+#define SL_BTMESH_HSL_SERVER_MAXIMUM_SATURATION_CFG_VAL (65535)
+
+// Enable Logging
+// Default: 1
+// Enable / disable UART Logging for HSL Server models specific messages for this component.
+#define SL_BTMESH_HSL_SERVER_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+// The hue update period shall not be greater than the hue UI update period
+#if (SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL) < (SL_BTMESH_HSL_SERVER_HUE_UPDATE_PERIOD_CFG_VAL)
+#error "The SL_BTMESH_HSL_SERVER_HUE_UPDATE_PERIOD_CFG_VAL shall be less than SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL."
+#endif
+
+// The saturation update period shall not be greater than the saturation UI update period
+#if (SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL) < (SL_BTMESH_HSL_SERVER_SATURATION_UPDATE_PERIOD_CFG_VAL)
+#error "The SL_BTMESH_HSL_SERVER_SATURATION_UPDATE_PERIOD_CFG_VAL shall be less than SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL."
+#endif
+
+#endif // SL_BTMESH_HSL_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_server.c b/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_server.c
new file mode 100644
index 00000000000..b0b9cb77b99
--- /dev/null
+++ b/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_server.c
@@ -0,0 +1,3274 @@
+/***************************************************************************//**
+ * @file sl_btmesh_hsl_server.c
+ * @brief Bt Mesh HSL Server module
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+// C Standard Library headers
+#include
+#include
+#include
+
+#include
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "sl_btmesh_generic_model_capi_types.h"
+#include "sl_btmesh_lib.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_hsl_server_config.h"
+#include "sl_btmesh_hsl_server.h"
+#include "sl_btmesh_hsl_signal_transition_handler.h"
+#include "sl_btmesh_lighting_server.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup HSL_Server
+ * @{
+ ******************************************************************************/
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+#define scene_server_reset_register(elem_index) \
+ scene_server_reset_register_impl(elem_index)
+#else
+#define scene_server_reset_register(elem_index)
+#endif
+
+#define NO_FLAGS 0 ///< No flags used for message
+#define IMMEDIATE 0 ///< Immediate transition time is 0 seconds
+#define NO_CALLBACK_DATA (void *)NULL ///< Callback has no parameters
+#define HIGH_PRIORITY 0 ///< High Priority
+/// Values greater than max 37200000 are treated as unknown remaining time
+#define UNKNOWN_REMAINING_TIME 40000000
+
+/// Lightbulb state
+static PACKSTRUCT(struct lightbulb_state {
+ // Hue state
+ uint16_t hue_current; ///< Current hue value
+ uint16_t hue_target; ///< Target hue value
+ uint16_t hue_default; ///< Default hue value
+ uint16_t hue_min; ///< Minimum hue value
+ uint16_t hue_max; ///< Maximum hue value
+
+ // Generic Level bound to hue
+ int16_t hue_level_current; ///< Current hue generic level value
+ int16_t hue_level_target; ///< Target hue generic level value
+
+ // Saturation state
+ uint16_t saturation_current; ///< Current saturation value
+ uint16_t saturation_target; ///< Target saturation value
+ uint16_t saturation_default; ///< Default saturation value
+ uint16_t saturation_min; ///< Minimum saturation value
+ uint16_t saturation_max; ///< Maximum saturation value
+
+ // Generic Level bound to saturation
+ int16_t saturation_level_current; ///< Current saturation generic level value
+ int16_t saturation_level_target; ///< Target saturation generic level value
+}) lightbulb_state;
+
+static sl_status_t hsl_hue_update(uint16_t element_index,
+ uint32_t remaining_ms);
+static sl_status_t hsl_saturation_update(uint16_t element_index,
+ uint32_t remaining_ms);
+
+/// copy of transition delay parameter, needed for delayed hsl request
+static uint32_t delayed_hsl_trans = 0;
+/// copy of transition delay parameter, needed for delayed hue request
+static uint32_t delayed_hsl_hue_trans = 0;
+/// copy of transition delay parameter, needed for
+///delayed hue generic level request
+static uint32_t delayed_hue_level_trans = 0;
+/// copy of generic request kind, needed for delayed hue generic request
+static mesh_generic_request_t hue_level_request_kind = mesh_generic_request_level;
+/// copy of move transition parameter for hue generic request
+static uint32_t move_hue_level_trans = 0;
+/// copy of move delta parameter for hue generic request
+static int16_t move_hue_level_delta = 0;
+/// copy of transition delay parameter, needed for delayed saturation request
+static uint32_t delayed_hsl_saturation_trans = 0;
+/// copy of transition delay parameter, needed for
+/// delayed saturation generic level request
+static uint32_t delayed_saturation_level_trans = 0;
+/// copy of generic request kind, needed for delayed saturation generic request
+static mesh_generic_request_t saturation_level_request_kind = mesh_generic_request_level;
+/// copy of move transition parameter for saturation generic request
+static uint32_t move_saturation_level_trans = 0;
+/// copy of move delta parameter for saturation generic request
+static int16_t move_saturation_level_delta = 0;
+
+// Timer handles
+static sl_simple_timer_t hsl_transition_complete_timer;
+static sl_simple_timer_t hsl_delayed_hsl_request_timer;
+static sl_simple_timer_t hsl_hue_transition_complete_timer;
+static sl_simple_timer_t hsl_delayed_hsl_hue_timer;
+static sl_simple_timer_t hsl_hue_level_move_timer;
+static sl_simple_timer_t hsl_hue_level_transition_complete_timer;
+static sl_simple_timer_t hsl_delayed_hue_level_timer;
+static sl_simple_timer_t hsl_saturation_transition_complete_timer;
+static sl_simple_timer_t hsl_delayed_hsl_saturation_timer;
+static sl_simple_timer_t hsl_saturation_level_move_timer;
+static sl_simple_timer_t hsl_saturation_level_transition_complete_timer;
+static sl_simple_timer_t hsl_delayed_saturation_level_timer;
+static sl_simple_timer_t hsl_state_store_timer;
+
+// Timer callbacks
+static void hsl_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_delayed_hsl_request_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_hue_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_delayed_hsl_hue_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_hue_level_move_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_hue_level_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_delayed_hue_level_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_saturation_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_delayed_hsl_saturation_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_saturation_level_move_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_saturation_level_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_delayed_saturation_level_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void hsl_state_store_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+/***************************************************************************//**
+ * This function loads the saved light state from Persistent Storage and
+ * copies the data in the global variable lightbulb_state.
+ * If PS key with ID SL_BTMESH_HSL_SERVER_PS_KEY_CFG_VAL does not exist or loading failed,
+ * lightbulb_state is set to zero and some default values are written to it.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightbulb_state_load(void);
+
+/***************************************************************************//**
+ * This function is called each time the lightbulb state in RAM is changed.
+ * It sets up a soft timer that will save the state in flash after small delay.
+ * The purpose is to reduce amount of unnecessary flash writes.
+ ******************************************************************************/
+static void lightbulb_state_changed(void);
+
+/***************************************************************************//**
+ * This function validates the lighbulb_state and change it if it is against
+ * the specification.
+ ******************************************************************************/
+static void lightbulb_state_validate_and_correct(void);
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_respond to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_respond(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms,
+ uint8_t response_flags);
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_update to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_update(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms);
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_publish to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_publish(uint16_t model_id,
+ uint16_t element_index,
+ mesh_generic_state_t kind);
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_register_handler with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ ******************************************************************************/
+static void generic_server_register_handler(uint16_t model_id,
+ uint16_t elem_index,
+ mesh_lib_generic_server_client_request_cb cb,
+ mesh_lib_generic_server_change_cb ch,
+ mesh_lib_generic_server_recall_cb recall);
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+/***************************************************************************//**
+ * Wrapper for sl_btmesh_scene_server_reset_register with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ * The scene server register shall be reset if the state of the model changes in
+ * order to clear the current scene.
+ * This function is available only if the btmesh_scene_server component is added
+ * to the project.
+ ******************************************************************************/
+static void scene_server_reset_register_impl(uint16_t elem_index);
+#endif
+
+/***************************************************************************//**
+ * \defgroup LightHSL
+ * \brief Light HSL Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightHSL
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to light HSL request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_hsl;
+ current.hsl.lightness = sl_btmesh_get_lightness_current();
+ current.hsl.hue = lightbulb_state.hue_current;
+ current.hsl.saturation = lightbulb_state.saturation_current;
+
+ target.kind = mesh_lighting_state_hsl;
+ target.hsl.lightness = sl_btmesh_get_lightness_target();
+ target.hsl.hue = lightbulb_state.hue_target;
+ target.hsl.saturation = lightbulb_state.saturation_target;
+
+ return generic_server_respond(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ NO_FLAGS);
+}
+
+/***************************************************************************//**
+ * Update light HSL state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_update(uint16_t element_index, uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_hsl;
+ current.hsl.lightness = sl_btmesh_get_lightness_current();
+ current.hsl.hue = lightbulb_state.hue_current;
+ current.hsl.saturation = lightbulb_state.saturation_current;
+
+ target.kind = mesh_lighting_state_hsl;
+ target.hsl.lightness = sl_btmesh_get_lightness_target();
+ target.hsl.hue = lightbulb_state.hue_target;
+ target.hsl.saturation = lightbulb_state.saturation_target;
+
+ return generic_server_update(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update light HSL state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+ (void)element_index;
+
+ e = hsl_update(BTMESH_HSL_SERVER_MAIN, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_MAIN,
+ mesh_lighting_state_hsl);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light HSL model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void hsl_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)server_addr;
+
+ log_info("hsl_request: lightness=%u, hue=%u, saturation=%u, "
+ "transition=%lu, delay=%u" NL,
+ request->hsl.lightness,
+ request->hsl.hue,
+ request->hsl.saturation,
+ transition_ms,
+ delay_ms);
+
+ if ((sl_btmesh_get_lightness_current() == request->hsl.lightness)
+ && (lightbulb_state.hue_current == request->hsl.hue)
+ && (lightbulb_state.saturation_current == request->hsl.saturation)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (sl_btmesh_get_lightness_current() != request->hsl.lightness) {
+ log_info("Setting lightness to <%u>" NL, request->hsl.lightness);
+ }
+ if (lightbulb_state.hue_current != request->hsl.hue) {
+ log_info("Setting hue to <%u>" NL, request->hsl.hue);
+ }
+ if (lightbulb_state.saturation_current != request->hsl.saturation) {
+ log_info("Setting saturation to <%u>" NL, request->hsl.saturation);
+ }
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ sl_btmesh_set_lightness_current(request->hsl.lightness);
+ sl_btmesh_set_lightness_target(request->hsl.lightness);
+ if (request->hsl.lightness != 0) {
+ sl_btmesh_set_lightness_last(request->hsl.lightness);
+ }
+
+ // update LED PWM duty cycle
+ sl_btmesh_lighting_set_level(sl_btmesh_get_lightness_current(),
+ IMMEDIATE);
+
+ lightbulb_state.hue_current = request->hsl.hue;
+ lightbulb_state.hue_target = request->hsl.hue;
+ lightbulb_state.saturation_current = request->hsl.saturation;
+ lightbulb_state.saturation_target = request->hsl.saturation;
+
+ // update LED hue
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_current,
+ IMMEDIATE);
+
+ // update LED saturation
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_current,
+ IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the light change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ sl_btmesh_set_lightness_target(request->hsl.lightness);
+ lightbulb_state.hue_target = request->hsl.hue;
+ lightbulb_state.saturation_target = request->hsl.saturation;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_hsl_request_timer,
+ delay_ms,
+ hsl_delayed_hsl_request_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed HSL Request timer");
+ // store transition parameter for later use
+ delayed_hsl_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ sl_btmesh_set_lightness_target(request->hsl.lightness);
+ lightbulb_state.hue_target = request->hsl.hue;
+ lightbulb_state.saturation_target = request->hsl.saturation;
+
+ sl_btmesh_lighting_set_level(sl_btmesh_get_lightness_target(),
+ transition_ms);
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target,
+ transition_ms);
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_transition_complete_timer,
+ transition_ms,
+ hsl_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start HSL Transition Complete timer");
+ }
+ lightbulb_state_changed();
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(BTMESH_HSL_SERVER_MAIN);
+ }
+
+ uint32_t remaining_ms = delay_ms + transition_ms;
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ hsl_response(BTMESH_HSL_SERVER_MAIN, client_addr, appkey_index, remaining_ms);
+ }
+ hsl_update_and_publish(BTMESH_HSL_SERVER_MAIN, remaining_ms);
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_MAIN,
+ mesh_lighting_state_lightness_actual);
+ generic_server_publish(MESH_LIGHTING_HSL_HUE_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_HUE,
+ mesh_lighting_state_hsl_hue);
+ generic_server_publish(MESH_LIGHTING_HSL_SATURATION_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_SATURATION,
+ mesh_lighting_state_hsl_saturation);
+}
+
+/***************************************************************************//**
+ * This function is a handler for light HSL change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void hsl_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (current->kind != mesh_lighting_state_hsl) {
+ // if kind is not 'hsl' then just report the change here
+ log_info("hsl_change, kind %u" NL, current->kind);
+ return;
+ }
+
+ if (sl_btmesh_get_lightness_current() != current->hsl.lightness) {
+ log_info("Lightness update: from %u to %u" NL,
+ sl_btmesh_get_lightness_current(),
+ current->hsl.lightness);
+ sl_btmesh_set_lightness_current(current->hsl.lightness);
+ lightbulb_state_changed();
+ } else {
+ log_info("Lightness update -same value (%u)" NL, current->hsl.lightness);
+ }
+
+ if (lightbulb_state.hue_current != current->hsl.hue) {
+ log_info("Hue update: from %u to %u" NL,
+ lightbulb_state.hue_current,
+ current->hsl.hue);
+ lightbulb_state.hue_current = current->hsl.hue;
+ lightbulb_state_changed();
+ } else {
+ log_info("Hue update -same value (%u)" NL,
+ lightbulb_state.hue_current);
+ }
+
+ if (lightbulb_state.saturation_current != current->hsl.saturation) {
+ log_info("Saturation update: from %u to %u" NL,
+ lightbulb_state.saturation_current,
+ current->hsl.saturation);
+ lightbulb_state.saturation_current = current->hsl.saturation;
+ lightbulb_state_changed();
+ } else {
+ log_info("Saturation update -same value (%u)" NL,
+ lightbulb_state.saturation_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light HSL recall event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void hsl_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+ (void)element_index;
+
+ log_info("Light HSL recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ sl_btmesh_set_lightness_target(current->hsl.lightness);
+ lightbulb_state.hue_target = current->hsl.hue;
+ lightbulb_state.saturation_target = current->hsl.saturation;
+ } else {
+ sl_btmesh_set_lightness_target(target->hsl.lightness);
+ lightbulb_state.hue_target = target->hsl.hue;
+ lightbulb_state.saturation_target = target->hsl.saturation;
+ }
+
+ if ((sl_btmesh_get_lightness_current()
+ == sl_btmesh_get_lightness_target())
+ && (lightbulb_state.hue_current
+ == lightbulb_state.hue_target)
+ && (lightbulb_state.saturation_current
+ == lightbulb_state.saturation_target)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall HSL lightness to %u, hue to %u, saturation to %d "
+ "with transition=%lu ms" NL,
+ sl_btmesh_get_lightness_target(),
+ lightbulb_state.hue_target,
+ lightbulb_state.saturation_target,
+ transition_ms);
+ if (sl_btmesh_get_lightness_current()
+ != sl_btmesh_get_lightness_target()) {
+ sl_btmesh_lighting_set_level(sl_btmesh_get_lightness_target(),
+ transition_ms);
+ }
+ if (lightbulb_state.hue_current
+ != lightbulb_state.hue_target) {
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target,
+ transition_ms);
+ }
+ if (lightbulb_state.saturation_current
+ != lightbulb_state.saturation_target) {
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ transition_ms);
+ }
+ if (transition_ms == IMMEDIATE) {
+ sl_btmesh_set_lightness_current(current->hsl.lightness);
+ lightbulb_state.hue_current = current->hsl.hue;
+ lightbulb_state.saturation_current = current->hsl.saturation;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_transition_complete_timer,
+ transition_ms,
+ hsl_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start HSL Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+
+ // Lightness substate is updated in lightness_recall, here only hue and
+ // saturation substate is updated, it is needed also for LC recall
+ // to not set LC mode to zero by bindings
+ sl_status_t e;
+ e = hsl_hue_update(BTMESH_HSL_SERVER_HUE, transition_ms);
+ e = hsl_saturation_update(BTMESH_HSL_SERVER_SATURATION, transition_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_MAIN,
+ mesh_lighting_state_hsl);
+ }
+}
+
+/***************************************************************************//**
+ * This function is called when a light HSL request
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void hsl_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ sl_btmesh_set_lightness_current(sl_btmesh_get_lightness_target());
+ lightbulb_state.hue_current = lightbulb_state.hue_target;
+ lightbulb_state.saturation_current = lightbulb_state.saturation_target;
+
+ log_info("Transition complete. New lightness is %u, "
+ "new hue is %u and new saturation is %u" NL,
+ sl_btmesh_get_lightness_current(),
+ lightbulb_state.hue_current,
+ lightbulb_state.saturation_current);
+
+ lightbulb_state_changed();
+ hsl_update_and_publish(BTMESH_HSL_SERVER_MAIN, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for light HSL request has completed.
+ ******************************************************************************/
+static void delayed_hsl_request(void)
+{
+ log_info("Starting delayed HSL request: lightness %u -> %u, hue %u -> %u, "
+ "saturation %u -> %u, %lu ms" NL,
+ sl_btmesh_get_lightness_current(),
+ sl_btmesh_get_lightness_target(),
+ lightbulb_state.hue_current,
+ lightbulb_state.hue_target,
+ lightbulb_state.saturation_current,
+ lightbulb_state.saturation_target,
+ delayed_hsl_trans);
+
+ sl_btmesh_lighting_set_level(sl_btmesh_get_lightness_target(),
+ delayed_hsl_trans);
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target,
+ delayed_hsl_trans);
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ delayed_hsl_trans);
+
+ if (delayed_hsl_trans == 0) {
+ // no transition delay, update state immediately
+ sl_btmesh_set_lightness_current(sl_btmesh_get_lightness_target());
+ lightbulb_state.hue_current = lightbulb_state.hue_target;
+ lightbulb_state.saturation_current = lightbulb_state.saturation_target;
+
+ lightbulb_state_changed();
+ hsl_update_and_publish(BTMESH_HSL_SERVER_MAIN, delayed_hsl_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_transition_complete_timer,
+ delayed_hsl_trans,
+ hsl_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start HSL Transition Complete timer");
+ }
+}
+
+/** @} (end addtogroup LightHSL) */
+
+/***************************************************************************//**
+ * \defgroup LightHSLSetup
+ * \brief Light HSL Setup Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightHSLSetup
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to light HSL setup request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] kind Type of state used in light HSL setup response.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_setup_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ mesh_generic_state_t kind)
+{
+ struct mesh_generic_state current;
+
+ current.kind = kind;
+
+ switch (kind) {
+ case mesh_lighting_state_hsl_default:
+ current.hsl.lightness = sl_btmesh_get_lightness_default();
+ current.hsl.hue = lightbulb_state.hue_default;
+ current.hsl.saturation = lightbulb_state.saturation_default;
+ break;
+ case mesh_lighting_state_hsl_range:
+ current.hsl_range.hue_min = lightbulb_state.hue_min;
+ current.hsl_range.hue_max = lightbulb_state.hue_max;
+ current.hsl_range.saturation_min = lightbulb_state.saturation_min;
+ current.hsl_range.saturation_max = lightbulb_state.saturation_max;
+ break;
+ default:
+ break;
+ }
+
+ return generic_server_respond(MESH_LIGHTING_HSL_SETUP_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ NULL,
+ IMMEDIATE,
+ NO_FLAGS);
+}
+
+/***************************************************************************//**
+ * Update light HSL setup state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] kind Type of state used in light HSL setup update.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_setup_update(uint16_t element_index,
+ mesh_generic_state_t kind)
+{
+ struct mesh_generic_state current;
+
+ current.kind = kind;
+
+ switch (kind) {
+ case mesh_lighting_state_hsl_default:
+ current.hsl.lightness = sl_btmesh_get_lightness_default();
+ current.hsl.hue = lightbulb_state.hue_default;
+ current.hsl.saturation = lightbulb_state.saturation_default;
+ break;
+ case mesh_lighting_state_hsl_range:
+ current.hsl_range.hue_min = lightbulb_state.hue_min;
+ current.hsl_range.hue_max = lightbulb_state.hue_max;
+ current.hsl_range.saturation_min = lightbulb_state.saturation_min;
+ current.hsl_range.saturation_max = lightbulb_state.saturation_max;
+ break;
+ default:
+ break;
+ }
+
+ return generic_server_update(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ NULL,
+ IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light HSL setup model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void hsl_setup_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+ (void)transition_ms;
+ (void)delay_ms;
+
+ mesh_generic_state_t kind = mesh_generic_state_last;
+ switch (request->kind) {
+ case mesh_lighting_request_hsl_default:
+ kind = mesh_lighting_state_hsl_default;
+ log_info("hsl_setup_request: state=hsl_default, default lightness=%u, "
+ "default hue=%u, default saturation=%u" NL,
+ request->hsl.lightness,
+ request->hsl.hue,
+ request->hsl.saturation);
+
+ if ((sl_btmesh_get_lightness_default() == request->hsl.lightness)
+ && (lightbulb_state.hue_default == request->hsl.hue)
+ && (lightbulb_state.saturation_default == request->hsl.saturation)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (sl_btmesh_get_lightness_default() != request->hsl.lightness) {
+ log_info("Setting default lightness to <%u>" NL, request->hsl.lightness);
+ sl_btmesh_set_lightness_default(request->hsl.lightness);
+ }
+ if (lightbulb_state.hue_default != request->hsl.hue) {
+ log_info("Setting default hue to <%u>" NL,
+ request->hsl.hue);
+ lightbulb_state.hue_default = request->hsl.hue;
+ }
+ if (lightbulb_state.saturation_default != request->hsl.saturation) {
+ log_info("Setting default saturation to <%u>" NL, request->hsl.saturation);
+ lightbulb_state.saturation_default = request->hsl.saturation;
+ }
+ lightbulb_state_changed();
+ }
+ break;
+
+ case mesh_lighting_request_hsl_range:
+ kind = mesh_lighting_state_hsl_range;
+ log_info("hsl_setup_request: state=hsl_range, min hue=%u, max hue=%u, "
+ "min saturation=%u, max saturation=%u" NL,
+ request->hsl_range.hue_min,
+ request->hsl_range.hue_max,
+ request->hsl_range.saturation_min,
+ request->hsl_range.saturation_max);
+
+ if ((lightbulb_state.hue_min == request->hsl_range.hue_min)
+ && (lightbulb_state.hue_max == request->hsl_range.hue_max)
+ && (lightbulb_state.saturation_min
+ == request->hsl_range.saturation_min)
+ && (lightbulb_state.saturation_max
+ == request->hsl_range.saturation_max)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (lightbulb_state.hue_min != request->hsl_range.hue_min) {
+ log_info("Setting min hue to <%u>" NL, request->hsl_range.hue_min);
+ lightbulb_state.hue_min = request->hsl_range.hue_min;
+ }
+ if (lightbulb_state.hue_max != request->hsl_range.hue_max) {
+ log_info("Setting max hue to <%u>" NL, request->hsl_range.hue_max);
+ lightbulb_state.hue_max = request->hsl_range.hue_max;
+ }
+ if (lightbulb_state.saturation_min != request->hsl_range.saturation_min) {
+ log_info("Setting min saturation to <%u>" NL,
+ request->hsl_range.saturation_min);
+ lightbulb_state.saturation_min = request->hsl_range.saturation_min;
+ }
+ if (lightbulb_state.saturation_max != request->hsl_range.saturation_max) {
+ log_info("Setting max saturation to <%u>" NL,
+ request->hsl_range.saturation_max);
+ lightbulb_state.saturation_max = request->hsl_range.saturation_max;
+ }
+ lightbulb_state_changed();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ hsl_setup_response(element_index, client_addr, appkey_index, kind);
+ } else {
+ hsl_setup_update(element_index, kind);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light HSL setup change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void hsl_setup_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ switch (current->kind) {
+ case mesh_lighting_state_hsl_default:
+ if (sl_btmesh_get_lightness_default() != current->hsl.lightness) {
+ log_info("Default lightness update: from %u to %u" NL,
+ sl_btmesh_get_lightness_default(),
+ current->hsl.lightness);
+ sl_btmesh_set_lightness_default(current->hsl.lightness);
+ lightbulb_state_changed();
+ } else {
+ log_info("Default lightness update -same value (%u)" NL,
+ sl_btmesh_get_lightness_default());
+ }
+ if (lightbulb_state.hue_default != current->hsl.hue) {
+ log_info("Default hue change: from %u to %u" NL,
+ lightbulb_state.hue_default,
+ current->hsl.hue);
+ lightbulb_state.hue_default = current->hsl.hue;
+ lightbulb_state_changed();
+ } else {
+ log_info("Default hue update -same value (%u)" NL,
+ lightbulb_state.hue_default);
+ }
+ if (lightbulb_state.saturation_default != current->hsl.saturation) {
+ log_info("Default saturation change: from %u to %u" NL,
+ lightbulb_state.saturation_default,
+ current->hsl.saturation);
+ lightbulb_state.saturation_default = current->hsl.saturation;
+ lightbulb_state_changed();
+ } else {
+ log_info("Default saturation update -same value (%u)" NL,
+ lightbulb_state.saturation_default);
+ }
+ break;
+
+ case mesh_lighting_state_hsl_range:
+ if (lightbulb_state.hue_min != current->hsl_range.hue_min) {
+ log_info("Min hue update: from %u to %u" NL,
+ lightbulb_state.hue_min,
+ current->hsl_range.hue_min);
+ lightbulb_state.hue_min = current->hsl_range.hue_min;
+ lightbulb_state_changed();
+ } else {
+ log_info("Min hue update -same value (%u)" NL,
+ lightbulb_state.hue_min);
+ }
+ if (lightbulb_state.hue_max != current->hsl_range.hue_max) {
+ log_info("Max hue update: from %u to %u" NL,
+ lightbulb_state.hue_max,
+ current->hsl_range.hue_max);
+ lightbulb_state.hue_max = current->hsl_range.hue_max;
+ lightbulb_state_changed();
+ } else {
+ log_info("Max hue update -same value (%u)" NL,
+ lightbulb_state.hue_max);
+ }
+ if (lightbulb_state.saturation_min != current->hsl_range.saturation_min) {
+ log_info("Min_saturation_change: from %u to %u" NL,
+ lightbulb_state.saturation_min,
+ current->hsl_range.saturation_min);
+ lightbulb_state.saturation_min = current->hsl_range.saturation_min;
+ lightbulb_state_changed();
+ } else {
+ log_info("Min saturation update -same value (%u)" NL,
+ lightbulb_state.saturation_min);
+ }
+ if (lightbulb_state.saturation_max != current->hsl_range.saturation_max) {
+ log_info("Max_saturation_change: from %u to %u" NL,
+ lightbulb_state.saturation_max,
+ current->hsl_range.saturation_max);
+ lightbulb_state.saturation_max = current->hsl_range.saturation_max;
+ lightbulb_state_changed();
+ } else {
+ log_info("Max saturation update -same value (%u)" NL,
+ lightbulb_state.saturation_max);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup LightHSLSetup) */
+
+/***************************************************************************//**
+ * \defgroup LightHSLHue
+ * \brief Light HSL Hue Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightHSLHue
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to light HSL hue request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_hue_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_hsl_hue;
+ current.hsl_hue.hue = lightbulb_state.hue_current;
+
+ target.kind = mesh_lighting_state_hsl_hue;
+ target.hsl_hue.hue = lightbulb_state.hue_target;
+
+ return generic_server_respond(MESH_LIGHTING_HSL_HUE_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ NO_FLAGS);
+}
+
+/***************************************************************************//**
+ * Update light HSL hue state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_hue_update(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_hsl_hue;
+ current.hsl_hue.hue = lightbulb_state.hue_current;
+
+ target.kind = mesh_lighting_state_hsl_hue;
+ target.hsl_hue.hue = lightbulb_state.hue_target;
+
+ return generic_server_update(MESH_LIGHTING_HSL_HUE_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update light HSL hue state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_hue_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+ (void)element_index;
+
+ e = hsl_hue_update(BTMESH_HSL_SERVER_HUE, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_LIGHTING_HSL_HUE_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_HUE,
+ mesh_lighting_state_hsl_hue);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light HSL hue model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void hsl_hue_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)server_addr;
+
+ log_info("hsl_hue_request: hue=%u, transition=%lu, delay=%u" NL,
+ request->hsl_hue,
+ transition_ms, delay_ms);
+
+ if (lightbulb_state.hue_current == request->hsl_hue) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (lightbulb_state.hue_current != request->hsl_hue) {
+ log_info("Setting hue to <%u>" NL, request->hsl_hue);
+ }
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.hue_current = request->hsl_hue;
+ lightbulb_state.hue_target = request->hsl_hue;
+
+ // update LED hue
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_current,
+ IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the hue change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.hue_target = request->hsl_hue;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_hsl_hue_timer,
+ delay_ms,
+ hsl_delayed_hsl_hue_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Hue timer");
+ // store transition parameter for later use
+ delayed_hsl_hue_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.hue_target = request->hsl_hue;
+
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target,
+ transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_hue_transition_complete_timer,
+ transition_ms,
+ hsl_hue_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Hue Transition Complete timer");
+ }
+ lightbulb_state_changed();
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(BTMESH_HSL_SERVER_HUE);
+ }
+
+ uint32_t remaining_ms = delay_ms + transition_ms;
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ hsl_hue_response(BTMESH_HSL_SERVER_HUE,
+ client_addr,
+ appkey_index,
+ remaining_ms);
+ }
+ hsl_hue_update_and_publish(BTMESH_HSL_SERVER_HUE, remaining_ms);
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_MAIN,
+ mesh_lighting_state_hsl);
+ generic_server_publish(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_HUE,
+ mesh_generic_state_level);
+}
+
+/***************************************************************************//**
+ * This function is a handler for light HSL hue change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void hsl_hue_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (lightbulb_state.hue_current != current->hsl_hue.hue) {
+ log_info("Hue update: from %u to %u" NL,
+ lightbulb_state.hue_current,
+ current->hsl_hue.hue);
+ lightbulb_state.hue_current = current->hsl_hue.hue;
+ lightbulb_state_changed();
+ } else {
+ log_info("Hue update -same value (%u)" NL, lightbulb_state.hue_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light HSL hue recall event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void hsl_hue_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+ (void)element_index;
+
+ log_info("HSL hue recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.hue_target = current->hsl_hue.hue;
+ } else {
+ lightbulb_state.hue_target = target->hsl_hue.hue;
+ }
+
+ if (lightbulb_state.hue_current == lightbulb_state.hue_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall HSL hue to %u with transition=%lu ms" NL,
+ lightbulb_state.hue_target,
+ transition_ms);
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target,
+ transition_ms);
+
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.hue_current = current->hsl_hue.hue;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_hue_transition_complete_timer,
+ transition_ms,
+ hsl_hue_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Hue Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+
+ hsl_hue_update_and_publish(BTMESH_HSL_SERVER_HUE, transition_ms);
+}
+
+/***************************************************************************//**
+ * This function is called when a light HSL hue request
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void hsl_hue_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.hue_current = lightbulb_state.hue_target;
+
+ log_info("Transition complete. New hue is %u" NL, lightbulb_state.hue_current);
+
+ lightbulb_state_changed();
+ hsl_hue_update_and_publish(BTMESH_HSL_SERVER_HUE, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for light HSL hue request has completed.
+ ******************************************************************************/
+static void delayed_hsl_hue_request(void)
+{
+ log_info("Starting delayed HSL hue request: hue %u -> %u, %lu ms" NL,
+ lightbulb_state.hue_current,
+ lightbulb_state.hue_target,
+ delayed_hsl_hue_trans);
+
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target,
+ delayed_hsl_hue_trans);
+
+ if (delayed_hsl_hue_trans == 0) {
+ // no transition delay, update state immediately
+ lightbulb_state.hue_current = lightbulb_state.hue_target;
+
+ lightbulb_state_changed();
+ hsl_hue_update_and_publish(BTMESH_HSL_SERVER_HUE,
+ delayed_hsl_hue_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_hue_transition_complete_timer,
+ delayed_hsl_hue_trans,
+ hsl_hue_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Hue Transition Complete timer");
+ }
+}
+
+/** @} (end addtogroup LightHSLHue) */
+
+/***************************************************************************//**
+ * \defgroup HueGenericLevel
+ * \brief Generic Level Server model on hue element.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup HueGenericLevel
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to generic level request on hue element.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hue_level_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_level;
+ current.level.level = lightbulb_state.hue_level_current;
+
+ target.kind = mesh_generic_state_level;
+ target.level.level = lightbulb_state.hue_level_target;
+
+ return generic_server_respond(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ NO_FLAGS);
+}
+
+/***************************************************************************//**
+ * Update generic level state on hue element.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hue_level_update(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_level;
+ current.level.level = lightbulb_state.hue_level_current;
+
+ target.kind = mesh_generic_state_level;
+ target.level.level = lightbulb_state.hue_level_target;
+
+ return generic_server_update(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update generic level state on hue element
+ * and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hue_level_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+
+ e = hue_level_update(element_index, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_level);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * Schedule next generic level move request on hue element.
+ *
+ * @param[in] remaining_delta The remaining level delta to the target state.
+ ******************************************************************************/
+static void hue_level_move_schedule_next_request(int32_t remaining_delta)
+{
+ uint32_t transition_ms = 0;
+ if (remaining_delta == 0) {
+ if (move_hue_level_delta > 0) {
+ lightbulb_state.hue_level_current = 0x8000; // Min level value
+ lightbulb_state.hue_level_target = 0x7FFF; // Max level value
+ } else if (move_hue_level_delta < 0) {
+ lightbulb_state.hue_level_current = 0x7FFF; // Max level value
+ lightbulb_state.hue_level_target = 0x8000; // Min level value
+ }
+ transition_ms = move_hue_level_trans;
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_current + move_hue_level_delta,
+ move_hue_level_trans);
+ } else if (abs(remaining_delta) < abs(move_hue_level_delta)) {
+ transition_ms = (uint32_t)(((int64_t)move_hue_level_trans * remaining_delta)
+ / move_hue_level_delta);
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target, transition_ms);
+ } else {
+ transition_ms = move_hue_level_trans;
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_current + move_hue_level_delta,
+ move_hue_level_trans);
+ }
+ sl_status_t sc = sl_simple_timer_start(&hsl_hue_level_move_timer,
+ transition_ms,
+ hsl_hue_level_move_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Hue Generic Level Move timer");
+}
+
+/***************************************************************************//**
+ * Handle generic level move request on hue element.
+ ******************************************************************************/
+static void hue_level_move_request(void)
+{
+ log_info("Hue generic level move: level %d -> %d, delta %d in %lu ms" NL,
+ lightbulb_state.hue_level_current,
+ lightbulb_state.hue_level_target,
+ move_hue_level_delta,
+ move_hue_level_trans);
+
+ int32_t remaining_delta = (int32_t)lightbulb_state.hue_level_target
+ - lightbulb_state.hue_level_current;
+
+ if (abs(remaining_delta) < abs(move_hue_level_delta)) {
+ // end of move level as it reached target state
+ lightbulb_state.hue_level_current = lightbulb_state.hue_level_target;
+ lightbulb_state.hue_current = lightbulb_state.hue_target;
+ } else {
+ lightbulb_state.hue_level_current += move_hue_level_delta;
+ lightbulb_state.hue_current += move_hue_level_delta;
+ }
+ lightbulb_state_changed();
+ hue_level_update_and_publish(BTMESH_HSL_SERVER_HUE, UNKNOWN_REMAINING_TIME);
+
+ remaining_delta = (int32_t)lightbulb_state.hue_level_target
+ - lightbulb_state.hue_level_current;
+
+ hue_level_move_schedule_next_request(remaining_delta);
+}
+
+/***************************************************************************//**
+ * Stop generic level move on hue element.
+ ******************************************************************************/
+static void hue_level_move_stop(void)
+{
+ // Cancel timers
+ sl_status_t sc = sl_simple_timer_stop(&hsl_delayed_hue_level_timer);
+ app_assert_status_f(sc, "Failed to stop Delayed Hue Generic Level timer");
+ sc = sl_simple_timer_stop(&hsl_hue_level_move_timer);
+ app_assert_status_f(sc, "Failed to stop Hue Generic Level Move timer");
+ //Reset move parameters
+ move_hue_level_delta = 0;
+ move_hue_level_trans = 0;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the generic level model on hue element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void hue_level_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+
+ uint16_t hue;
+ uint32_t remaining_ms = UNKNOWN_REMAINING_TIME;
+
+ switch (request->kind) {
+ case mesh_generic_request_level:
+ log_info("hue_level_request (generic): level=%d, transition=%lu, delay=%u" NL,
+ request->level, transition_ms, delay_ms);
+
+ hue_level_move_stop();
+ if (lightbulb_state.hue_level_current == request->level) {
+ log_info("Request for current state received; no op" NL);
+ lightbulb_state.hue_level_target = request->level;
+ } else {
+ log_info("Setting hue generic level to <%d>" NL, request->level);
+
+ hue = request->level + 32768;
+
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.hue_level_current = request->level;
+ lightbulb_state.hue_level_target = request->level;
+ lightbulb_state.hue_current = hue;
+ lightbulb_state.hue_target = hue;
+
+ // update LED Hue
+ sl_btmesh_hsl_set_hue_level(hue, IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.hue_level_target = request->level;
+ lightbulb_state.hue_target = hue;
+ hue_level_request_kind = mesh_generic_request_level;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_hue_level_timer,
+ delay_ms,
+ hsl_delayed_hue_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Hue Generic Level timer");
+ // store transition parameter for later use
+ delayed_hue_level_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.hue_level_target = request->level;
+ lightbulb_state.hue_target = hue;
+ sl_btmesh_hsl_set_hue_level(hue, transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_hue_level_transition_complete_timer,
+ delayed_hue_level_trans,
+ hsl_hue_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Hue Generic Level Transition Complete timer");
+ }
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+
+ remaining_ms = delay_ms + transition_ms;
+ break;
+
+ case mesh_generic_request_level_move: {
+ log_info("hue_level_request (move): delta=%d, transition=%lu, delay=%u" NL,
+ request->level, transition_ms, delay_ms);
+ // Store move parameters
+ move_hue_level_delta = request->level;
+ move_hue_level_trans = transition_ms;
+
+ int16_t requested_level = 0;
+ if (move_hue_level_delta > 0) {
+ requested_level = 0x7FFF; // Max level value
+ } else if (move_hue_level_delta < 0) {
+ requested_level = 0x8000; // Min level value
+ }
+
+ if (lightbulb_state.hue_level_current == requested_level) {
+ log_info("Request for current state received; no op" NL);
+ lightbulb_state.hue_level_target = requested_level;
+ remaining_ms = IMMEDIATE;
+ } else {
+ log_info("Setting hue level to <%d>" NL, requested_level);
+
+ hue = requested_level + 32768;
+
+ if (delay_ms > 0) {
+ // a delay has been specified for the move. Start a soft timer
+ // that will trigger the move after the given delay
+ // Current state remains as is for now
+ lightbulb_state.hue_level_target = requested_level;
+ lightbulb_state.hue_target = hue;
+ hue_level_request_kind = mesh_generic_request_level_move;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_hue_level_timer,
+ delay_ms,
+ hsl_delayed_hue_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Hue Generic Level timer");
+ } else {
+ // no delay so start move
+ lightbulb_state.hue_level_target = requested_level;
+ lightbulb_state.hue_target = hue;
+
+ int32_t remaining_delta = (int32_t)lightbulb_state.hue_level_target
+ - lightbulb_state.hue_level_current;
+ hue_level_move_schedule_next_request(remaining_delta);
+ }
+ remaining_ms = UNKNOWN_REMAINING_TIME;
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+ break;
+ }
+
+ case mesh_generic_request_level_halt:
+ log_info("hue_level_request (halt)" NL);
+
+ // Set current state
+ lightbulb_state.hue_current = sl_btmesh_get_hue();
+ lightbulb_state.hue_target = lightbulb_state.hue_current;
+ lightbulb_state.hue_level_current = lightbulb_state.hue_current - 32768;
+ lightbulb_state.hue_level_target = lightbulb_state.hue_level_current;
+ if (delay_ms > 0) {
+ // a delay has been specified for the move halt. Start a soft timer
+ // that will trigger the move halt after the given delay
+ // Current state remains as is for now
+ remaining_ms = delay_ms;
+ hue_level_request_kind = mesh_generic_request_level_halt;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_hue_level_timer,
+ delay_ms,
+ hsl_delayed_hue_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Hue Generic Level timer");
+ } else {
+ hue_level_move_stop();
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_current, IMMEDIATE);
+ remaining_ms = IMMEDIATE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ lightbulb_state_changed();
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ hue_level_response(element_index, client_addr, appkey_index, remaining_ms);
+ }
+ hue_level_update_and_publish(element_index, remaining_ms);
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_HSL_HUE_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_hsl_hue);
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic level change event
+ * on hue element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void hue_level_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (lightbulb_state.hue_level_current != current->level.level) {
+ log_info("Hue generic level update: from %d to %d" NL,
+ lightbulb_state.hue_level_current,
+ current->level.level);
+ lightbulb_state.hue_level_current = current->level.level;
+ lightbulb_state_changed();
+ hue_level_move_stop();
+ } else {
+ log_info("Hue generic level update -same value (%d)" NL,
+ lightbulb_state.hue_level_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic level recall event
+ * on hue element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void hue_level_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+ (void)element_index;
+
+ log_info("Hue Generic Level recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.hue_level_target = current->level.level;
+ } else {
+ lightbulb_state.hue_level_target = target->level.level;
+ }
+
+ if (lightbulb_state.hue_level_current == lightbulb_state.hue_level_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall hue level to %d with transition=%lu ms" NL,
+ lightbulb_state.hue_level_target,
+ transition_ms);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.hue_level_current = current->level.level;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_hue_level_transition_complete_timer,
+ transition_ms,
+ hsl_hue_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Hue Generic Level Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+}
+
+/***************************************************************************//**
+ * This function is called when a generic level request on hue element
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void hue_level_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.hue_level_current = lightbulb_state.hue_level_target;
+ lightbulb_state.hue_current = lightbulb_state.hue_target;
+
+ log_info("Transition complete. New hue generic level is %d" NL,
+ lightbulb_state.hue_level_current);
+
+ lightbulb_state_changed();
+ hue_level_update_and_publish(BTMESH_HSL_SERVER_HUE, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for generic level request
+ * on hue element has completed.
+ ******************************************************************************/
+static void delayed_hue_level_request(void)
+{
+ log_info("Starting delayed hue generic level request: level %d -> %d, %lu ms" NL,
+ lightbulb_state.hue_level_current,
+ lightbulb_state.hue_level_target,
+ delayed_hue_level_trans);
+
+ switch (hue_level_request_kind) {
+ case mesh_generic_request_level:
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target,
+ delayed_hue_level_trans);
+
+ if (delayed_hue_level_trans == 0) {
+ // no transition delay, update state immediately
+ lightbulb_state.hue_level_current = lightbulb_state.hue_level_target;
+ lightbulb_state.hue_current = lightbulb_state.hue_target;
+
+ lightbulb_state_changed();
+ hue_level_update_and_publish(BTMESH_HSL_SERVER_HUE,
+ delayed_hue_level_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_hue_level_transition_complete_timer,
+ delayed_hue_level_trans,
+ hsl_hue_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Hue Generic Level Transition Complete timer");
+ }
+ break;
+
+ case mesh_generic_request_level_move:
+ hue_level_move_schedule_next_request((int32_t)lightbulb_state.hue_level_target
+ - lightbulb_state.hue_level_current);
+ hue_level_update_and_publish(BTMESH_HSL_SERVER_HUE,
+ UNKNOWN_REMAINING_TIME);
+ break;
+
+ case mesh_generic_request_level_halt:
+ // Set current state
+ lightbulb_state.hue_current = sl_btmesh_get_hue();
+ lightbulb_state.hue_target = lightbulb_state.hue_current;
+ lightbulb_state.hue_level_current = lightbulb_state.hue_current - 32768;
+ lightbulb_state.hue_level_target = lightbulb_state.hue_level_current;
+ hue_level_move_stop();
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_current,
+ IMMEDIATE);
+ hue_level_update_and_publish(BTMESH_HSL_SERVER_HUE, IMMEDIATE);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup HueGenericLevel) */
+
+/***************************************************************************//**
+ * \defgroup LightHSLSaturation
+ * \brief Light HSL Saturation Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightHSLSaturation
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to light HSL saturation request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_saturation_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_hsl_saturation;
+ current.hsl_saturation.saturation = lightbulb_state.saturation_current;
+
+ target.kind = mesh_lighting_state_hsl_saturation;
+ target.hsl_saturation.saturation = lightbulb_state.saturation_target;
+
+ return generic_server_respond(MESH_LIGHTING_HSL_SATURATION_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ NO_FLAGS);
+}
+
+/***************************************************************************//**
+ * Update light HSL saturation state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_saturation_update(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_lighting_state_hsl_saturation;
+ current.hsl_saturation.saturation = lightbulb_state.saturation_current;
+
+ target.kind = mesh_lighting_state_hsl_saturation;
+ target.hsl_saturation.saturation = lightbulb_state.saturation_target;
+
+ return generic_server_update(MESH_LIGHTING_HSL_SATURATION_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update light HSL saturation state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t hsl_saturation_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+ (void)element_index;
+
+ e = hsl_saturation_update(BTMESH_HSL_SERVER_SATURATION, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_LIGHTING_HSL_SATURATION_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_SATURATION,
+ mesh_lighting_state_hsl_saturation);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light HSL saturation model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void hsl_saturation_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)server_addr;
+
+ log_info("hsl_saturation_request: saturation=%u, transition=%lu, delay=%u" NL,
+ request->hsl_saturation,
+ transition_ms, delay_ms);
+
+ if (lightbulb_state.saturation_current == request->hsl_saturation) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (lightbulb_state.saturation_current != request->hsl_saturation) {
+ log_info("Setting saturation to <%u>" NL, request->hsl_saturation);
+ }
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.saturation_current = request->hsl_saturation;
+ lightbulb_state.saturation_target = request->hsl_saturation;
+
+ // update LED saturation
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_current,
+ IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the saturation change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.saturation_target = request->hsl_saturation;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_hsl_saturation_timer,
+ delay_ms,
+ hsl_delayed_hsl_saturation_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Saturation timer");
+ // store transition parameter for later use
+ delayed_hsl_saturation_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.saturation_target = request->hsl_saturation;
+
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_saturation_transition_complete_timer,
+ transition_ms,
+ hsl_saturation_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Saturation Transition Complete timer");
+ }
+ lightbulb_state_changed();
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(BTMESH_HSL_SERVER_SATURATION);
+ }
+
+ uint32_t remaining_ms = delay_ms + transition_ms;
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ hsl_saturation_response(BTMESH_HSL_SERVER_SATURATION,
+ client_addr,
+ appkey_index,
+ remaining_ms);
+ }
+ hsl_saturation_update_and_publish(BTMESH_HSL_SERVER_SATURATION, remaining_ms);
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_MAIN,
+ mesh_lighting_state_hsl);
+ generic_server_publish(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_SATURATION,
+ mesh_generic_state_level);
+}
+
+/***************************************************************************//**
+ * This function is a handler for light HSL saturation change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void hsl_saturation_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (lightbulb_state.saturation_current != current->hsl_saturation.saturation) {
+ log_info("Saturation update: from %u to %u" NL,
+ lightbulb_state.saturation_current,
+ current->hsl_saturation.saturation);
+ lightbulb_state.saturation_current = current->hsl_saturation.saturation;
+ lightbulb_state_changed();
+ } else {
+ log_info("Saturation update -same value (%u)" NL, lightbulb_state.saturation_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light HSL saturation recall event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void hsl_saturation_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+ (void)element_index;
+
+ log_info("HSL saturation recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.saturation_target = current->hsl_saturation.saturation;
+ } else {
+ lightbulb_state.saturation_target = target->hsl_saturation.saturation;
+ }
+
+ if (lightbulb_state.saturation_current == lightbulb_state.saturation_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall HSL saturation to %u with transition=%lu ms" NL,
+ lightbulb_state.saturation_target,
+ transition_ms);
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ transition_ms);
+
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.saturation_current = current->hsl_saturation.saturation;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_saturation_transition_complete_timer,
+ transition_ms,
+ hsl_saturation_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Saturation Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+
+ hsl_saturation_update_and_publish(BTMESH_HSL_SERVER_SATURATION, transition_ms);
+}
+
+/***************************************************************************//**
+ * This function is called when a light HSL saturation request
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void hsl_saturation_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.saturation_current = lightbulb_state.saturation_target;
+
+ log_info("Transition complete. New saturation is %u" NL,
+ lightbulb_state.saturation_current);
+
+ lightbulb_state_changed();
+ hsl_saturation_update_and_publish(BTMESH_HSL_SERVER_SATURATION, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for light HSL saturation request has completed.
+ ******************************************************************************/
+static void delayed_hsl_saturation_request(void)
+{
+ log_info("Starting delayed HSL saturation request: saturation %u -> %u, %lu ms" NL,
+ lightbulb_state.saturation_current,
+ lightbulb_state.saturation_target,
+ delayed_hsl_saturation_trans);
+
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ delayed_hsl_saturation_trans);
+
+ if (delayed_hsl_saturation_trans == 0) {
+ // no transition delay, update state immediately
+ lightbulb_state.saturation_current = lightbulb_state.saturation_target;
+
+ lightbulb_state_changed();
+ hsl_saturation_update_and_publish(BTMESH_HSL_SERVER_SATURATION,
+ delayed_hsl_saturation_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_saturation_transition_complete_timer,
+ delayed_hsl_saturation_trans,
+ hsl_saturation_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Saturation Transition Complete timer");
+ }
+}
+
+/** @} (end addtogroup LightHSLSaturation) */
+
+/***************************************************************************//**
+ * \defgroup SaturationGenericLevel
+ * \brief Generic Level Server model on saturation element.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup SaturationGenericLevel
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to generic level request on saturation element.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t saturation_level_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_level;
+ current.level.level = lightbulb_state.saturation_level_current;
+
+ target.kind = mesh_generic_state_level;
+ target.level.level = lightbulb_state.saturation_level_target;
+
+ return generic_server_respond(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ NO_FLAGS);
+}
+
+/***************************************************************************//**
+ * Update generic level state on saturation element.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t saturation_level_update(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_level;
+ current.level.level = lightbulb_state.saturation_level_current;
+
+ target.kind = mesh_generic_state_level;
+ target.level.level = lightbulb_state.saturation_level_target;
+
+ return generic_server_update(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update generic level state on saturation element
+ * and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t saturation_level_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+
+ e = saturation_level_update(element_index, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_level);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * Schedule next generic level move request on saturation element.
+ *
+ * @param[in] remaining_delta The remaining level delta to the target state.
+ ******************************************************************************/
+static void saturation_level_move_schedule_next_request(int32_t remaining_delta)
+{
+ uint32_t transition_ms = 0;
+ if (remaining_delta == 0) {
+ if (move_saturation_level_delta > 0) {
+ lightbulb_state.saturation_level_current = 0x8000; // Min level value
+ lightbulb_state.saturation_level_target = 0x7FFF; // Max level value
+ } else if (move_saturation_level_delta < 0) {
+ lightbulb_state.saturation_level_current = 0x7FFF; // Max level value
+ lightbulb_state.saturation_level_target = 0x8000; // Min level value
+ }
+ transition_ms = move_saturation_level_trans;
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_current + move_saturation_level_delta,
+ move_saturation_level_trans);
+ } else if (abs(remaining_delta) < abs(move_saturation_level_delta)) {
+ transition_ms = (uint32_t)(((int64_t)move_saturation_level_trans * remaining_delta)
+ / move_saturation_level_delta);
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ transition_ms);
+ } else {
+ transition_ms = move_saturation_level_trans;
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_current + move_saturation_level_delta,
+ move_saturation_level_trans);
+ }
+ sl_status_t sc = sl_simple_timer_start(&hsl_saturation_level_move_timer,
+ transition_ms,
+ hsl_saturation_level_move_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Saturation Generic Level Move timer");
+}
+
+/***************************************************************************//**
+ * Handle generic level move request on saturation element.
+ ******************************************************************************/
+static void saturation_level_move_request(void)
+{
+ log_info("Saturation generic level move: level %d -> %d, delta %d in %lu ms" NL,
+ lightbulb_state.saturation_level_current,
+ lightbulb_state.saturation_level_target,
+ move_saturation_level_delta,
+ move_saturation_level_trans);
+
+ int32_t remaining_delta = (int32_t)lightbulb_state.saturation_level_target
+ - lightbulb_state.saturation_level_current;
+
+ if (abs(remaining_delta) < abs(move_saturation_level_delta)) {
+ // end of move level as it reached target state
+ lightbulb_state.saturation_level_current = lightbulb_state.saturation_level_target;
+ lightbulb_state.saturation_current = lightbulb_state.saturation_target;
+ } else {
+ lightbulb_state.saturation_level_current += move_saturation_level_delta;
+ lightbulb_state.saturation_current += move_saturation_level_delta;
+ }
+ lightbulb_state_changed();
+ saturation_level_update_and_publish(BTMESH_HSL_SERVER_SATURATION,
+ UNKNOWN_REMAINING_TIME);
+
+ remaining_delta = (int32_t)lightbulb_state.saturation_level_target
+ - lightbulb_state.saturation_level_current;
+
+ saturation_level_move_schedule_next_request(remaining_delta);
+}
+
+/***************************************************************************//**
+ * Stop generic level move on saturation element.
+ ******************************************************************************/
+static void saturation_level_move_stop(void)
+{
+ // Cancel timers
+ sl_status_t sc = sl_simple_timer_stop(&hsl_delayed_saturation_level_timer);
+ app_assert_status_f(sc, "Failed to stop Delayed Saturation Generic Level timer");
+ sc = sl_simple_timer_stop(&hsl_saturation_level_move_timer);
+ app_assert_status_f(sc, "Failed to stop Saturation Generic Level Move timer");
+ //Reset move parameters
+ move_saturation_level_delta = 0;
+ move_saturation_level_trans = 0;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the generic level model on saturation element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void saturation_level_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+
+ uint16_t saturation;
+ uint32_t remaining_ms = UNKNOWN_REMAINING_TIME;
+
+ switch (request->kind) {
+ case mesh_generic_request_level:
+ log_info("saturation_level_request (generic): level=%d, transition=%lu, delay=%u" NL,
+ request->level, transition_ms, delay_ms);
+
+ saturation_level_move_stop();
+ if (lightbulb_state.saturation_level_current == request->level) {
+ log_info("Request for current state received; no op" NL);
+ lightbulb_state.saturation_level_target = request->level;
+ } else {
+ log_info("Setting saturation generic level to <%d>" NL, request->level);
+
+ saturation = request->level + 32768;
+
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.saturation_level_current = request->level;
+ lightbulb_state.saturation_level_target = request->level;
+ lightbulb_state.saturation_current = saturation;
+ lightbulb_state.saturation_target = saturation;
+
+ // update LED Saturation
+ sl_btmesh_hsl_set_saturation_level(saturation, IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.saturation_level_target = request->level;
+ lightbulb_state.saturation_target = saturation;
+ saturation_level_request_kind = mesh_generic_request_level;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_saturation_level_timer,
+ delay_ms,
+ hsl_delayed_saturation_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Saturation Generic Level timer");
+ // store transition parameter for later use
+ delayed_saturation_level_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.saturation_level_target = request->level;
+ lightbulb_state.saturation_target = saturation;
+ sl_btmesh_hsl_set_saturation_level(saturation, transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_saturation_level_transition_complete_timer,
+ delayed_saturation_level_trans,
+ hsl_saturation_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Saturation Generic Level Transition Complete timer");
+ }
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+
+ remaining_ms = delay_ms + transition_ms;
+ break;
+
+ case mesh_generic_request_level_move: {
+ log_info("saturation_level_request (move): delta=%d, transition=%lu, delay=%u" NL,
+ request->level, transition_ms, delay_ms);
+ // Store move parameters
+ move_saturation_level_delta = request->level;
+ move_saturation_level_trans = transition_ms;
+
+ int16_t requested_level = 0;
+ if (move_saturation_level_delta > 0) {
+ requested_level = 0x7FFF; // Max level value
+ } else if (move_saturation_level_delta < 0) {
+ requested_level = 0x8000; // Min level value
+ }
+
+ if (lightbulb_state.saturation_level_current == requested_level) {
+ log_info("Request for current state received; no op" NL);
+ lightbulb_state.saturation_level_target = requested_level;
+ remaining_ms = IMMEDIATE;
+ } else {
+ log_info("Setting saturation level to <%d>" NL, requested_level);
+
+ saturation = requested_level + 32768;
+
+ if (delay_ms > 0) {
+ // a delay has been specified for the move. Start a soft timer
+ // that will trigger the move after the given delay
+ // Current state remains as is for now
+ lightbulb_state.saturation_level_target = requested_level;
+ lightbulb_state.saturation_target = saturation;
+ saturation_level_request_kind = mesh_generic_request_level_move;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_saturation_level_timer,
+ delay_ms,
+ hsl_delayed_saturation_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Saturation Generic Level timer");
+ } else {
+ // no delay so start move
+ lightbulb_state.saturation_level_target = requested_level;
+ lightbulb_state.saturation_target = saturation;
+
+ int32_t remaining_delta = (int32_t)lightbulb_state.saturation_level_target
+ - lightbulb_state.saturation_level_current;
+ saturation_level_move_schedule_next_request(remaining_delta);
+ }
+ remaining_ms = UNKNOWN_REMAINING_TIME;
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+ break;
+ }
+
+ case mesh_generic_request_level_halt:
+ log_info("saturation_level_request (halt)" NL);
+
+ // Set current state
+ lightbulb_state.saturation_current = sl_btmesh_get_saturation();
+ lightbulb_state.saturation_target = lightbulb_state.saturation_current;
+ lightbulb_state.saturation_level_current = lightbulb_state.saturation_current - 32768;
+ lightbulb_state.saturation_level_target = lightbulb_state.saturation_level_current;
+ if (delay_ms > 0) {
+ // a delay has been specified for the move halt. Start a soft timer
+ // that will trigger the move halt after the given delay
+ // Current state remains as is for now
+ remaining_ms = delay_ms;
+ saturation_level_request_kind = mesh_generic_request_level_halt;
+ sl_status_t sc = sl_simple_timer_start(&hsl_delayed_saturation_level_timer,
+ delay_ms,
+ hsl_delayed_saturation_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Saturation Generic Level timer");
+ } else {
+ saturation_level_move_stop();
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_current,
+ IMMEDIATE);
+ remaining_ms = IMMEDIATE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ lightbulb_state_changed();
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ saturation_level_response(element_index,
+ client_addr,
+ appkey_index,
+ remaining_ms);
+ }
+ saturation_level_update_and_publish(element_index, remaining_ms);
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_HSL_SATURATION_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_hsl_saturation);
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic level change event
+ * on saturation element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void saturation_level_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (lightbulb_state.saturation_level_current != current->level.level) {
+ log_info("Saturation generic level update: from %d to %d" NL,
+ lightbulb_state.saturation_level_current,
+ current->level.level);
+ lightbulb_state.saturation_level_current = current->level.level;
+ lightbulb_state_changed();
+ saturation_level_move_stop();
+ } else {
+ log_info("Saturation generic level update -same value (%d)" NL,
+ lightbulb_state.saturation_level_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic level recall event
+ * on saturation element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void saturation_level_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+ (void)element_index;
+
+ log_info("Saturation Generic Level recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.saturation_level_target = current->level.level;
+ } else {
+ lightbulb_state.saturation_level_target = target->level.level;
+ }
+
+ if (lightbulb_state.saturation_level_current == lightbulb_state.saturation_level_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall saturation level to %d with transition=%lu ms" NL,
+ lightbulb_state.saturation_level_target,
+ transition_ms);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.saturation_level_current = current->level.level;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_saturation_level_transition_complete_timer,
+ transition_ms,
+ hsl_saturation_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Saturation Generic Level Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+}
+
+/***************************************************************************//**
+ * This function is called when a generic level request on saturation element
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void saturation_level_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.saturation_level_current = lightbulb_state.saturation_level_target;
+ lightbulb_state.saturation_current = lightbulb_state.saturation_target;
+
+ log_info("Transition complete. New saturation generic level is %d" NL,
+ lightbulb_state.saturation_level_current);
+
+ lightbulb_state_changed();
+ saturation_level_update_and_publish(BTMESH_HSL_SERVER_SATURATION, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for generic level request
+ * on saturation element has completed.
+ ******************************************************************************/
+static void delayed_saturation_level_request(void)
+{
+ log_info("Starting delayed saturation generic level request: level %d -> %d, %lu ms" NL,
+ lightbulb_state.saturation_level_current,
+ lightbulb_state.saturation_level_target,
+ delayed_saturation_level_trans);
+
+ switch (saturation_level_request_kind) {
+ case mesh_generic_request_level:
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ delayed_saturation_level_trans);
+
+ if (delayed_saturation_level_trans == 0) {
+ // no transition delay, update state immediately
+ lightbulb_state.saturation_level_current = lightbulb_state.saturation_level_target;
+ lightbulb_state.saturation_current = lightbulb_state.saturation_target;
+
+ lightbulb_state_changed();
+ saturation_level_update_and_publish(BTMESH_HSL_SERVER_SATURATION,
+ delayed_saturation_level_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&hsl_saturation_level_transition_complete_timer,
+ delayed_saturation_level_trans,
+ hsl_saturation_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Saturation Generic Level Transition Complete timer");
+ }
+ break;
+
+ case mesh_generic_request_level_move:
+ saturation_level_move_schedule_next_request((int32_t)lightbulb_state.saturation_level_target
+ - lightbulb_state.saturation_level_current);
+ saturation_level_update_and_publish(BTMESH_HSL_SERVER_SATURATION,
+ UNKNOWN_REMAINING_TIME);
+ break;
+
+ case mesh_generic_request_level_halt:
+ // Set current state
+ lightbulb_state.saturation_current = sl_btmesh_get_saturation();
+ lightbulb_state.saturation_target = lightbulb_state.saturation_current;
+ lightbulb_state.saturation_level_current = lightbulb_state.saturation_current - 32768;
+ lightbulb_state.saturation_level_target = lightbulb_state.saturation_level_current;
+ saturation_level_move_stop();
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_current,
+ IMMEDIATE);
+ saturation_level_update_and_publish(BTMESH_HSL_SERVER_SATURATION,
+ IMMEDIATE);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup SaturationGenericLevel) */
+
+/***************************************************************************//**
+ * Initialization of the models supported by this node.
+ * This function registers callbacks for each of the supported models.
+ ******************************************************************************/
+static void init_hsl_models(void)
+{
+ generic_server_register_handler(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_MAIN,
+ hsl_request,
+ hsl_change,
+ hsl_recall);
+
+ generic_server_register_handler(MESH_LIGHTING_HSL_SETUP_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_MAIN,
+ hsl_setup_request,
+ hsl_setup_change,
+ NULL);
+
+ generic_server_register_handler(MESH_LIGHTING_HSL_HUE_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_HUE,
+ hsl_hue_request,
+ hsl_hue_change,
+ hsl_hue_recall);
+
+ generic_server_register_handler(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_HUE,
+ hue_level_request,
+ hue_level_change,
+ hue_level_recall);
+
+ generic_server_register_handler(MESH_LIGHTING_HSL_SATURATION_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_SATURATION,
+ hsl_saturation_request,
+ hsl_saturation_change,
+ hsl_saturation_recall);
+
+ generic_server_register_handler(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ BTMESH_HSL_SERVER_SATURATION,
+ saturation_level_request,
+ saturation_level_change,
+ saturation_level_recall);
+}
+
+/***************************************************************************//**
+ * This function loads the saved light state from Persistent Storage and
+ * copies the data in the global variable lightbulb_state.
+ * If PS key with ID SL_BTMESH_HSL_SERVER_PS_KEY_CFG_VAL does not exist or loading failed,
+ * lightbulb_state is set to zero and some default values are written to it.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightbulb_state_load(void)
+{
+ sl_status_t sc;
+ size_t ps_len = 0;
+ struct lightbulb_state ps_data;
+
+ sc = sl_bt_nvm_load(SL_BTMESH_HSL_SERVER_PS_KEY_CFG_VAL,
+ sizeof(ps_data),
+ &ps_len,
+ (uint8_t *)&ps_data);
+
+ // Set default values if ps_load fail or size of lightbulb_state has changed
+ if ((sc != SL_STATUS_OK) || (ps_len != sizeof(struct lightbulb_state))) {
+ memset(&lightbulb_state, 0, sizeof(struct lightbulb_state));
+ lightbulb_state.hue_default = SL_BTMESH_HSL_SERVER_DEFAULT_HUE_CFG_VAL;
+ lightbulb_state.hue_min = SL_BTMESH_HSL_SERVER_MINIMUM_HUE_CFG_VAL;
+ lightbulb_state.hue_max = SL_BTMESH_HSL_SERVER_MAXIMUM_HUE_CFG_VAL;
+ lightbulb_state.saturation_default = SL_BTMESH_HSL_SERVER_DEFAULT_SATURATION_CFG_VAL;
+ lightbulb_state.saturation_min = SL_BTMESH_HSL_SERVER_MINIMUM_SATURATION_CFG_VAL;
+ lightbulb_state.saturation_max = SL_BTMESH_HSL_SERVER_MAXIMUM_SATURATION_CFG_VAL;
+
+ // Check if default values are valid and correct them if needed
+ lightbulb_state_validate_and_correct();
+
+ if (sc == SL_STATUS_OK) {
+ // The sl_bt_nvm_load call was successful but the size of the loaded data
+ // differs from the expected size therefore error code shall be set
+ sc = SL_STATUS_INVALID_STATE;
+ log_error("HSL server lightbulb state loaded from PS with invalid size, "
+ "use defaults. (expected=%zd,actual=%zd)" NL,
+ sizeof(struct lightbulb_state),
+ ps_len);
+ } else {
+ log_status_error_f(sc,
+ "HSL server lightbulb state load from PS failed "
+ "or nvm is empty, use defaults." NL);
+ }
+ } else {
+ memcpy(&lightbulb_state, &ps_data, ps_len);
+ }
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function saves the current light state in Persistent Storage so that
+ * the data is preserved over reboots and power cycles.
+ * The light state is hold in a global variable lightbulb_state.
+ * A PS key with ID SL_BTMESH_HSL_SERVER_PS_KEY_CFG_VAL is used to store the whole struct.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightbulb_state_store(void)
+{
+ sl_status_t sc;
+
+ sc = sl_bt_nvm_save(SL_BTMESH_HSL_SERVER_PS_KEY_CFG_VAL,
+ sizeof(struct lightbulb_state),
+ (const uint8_t *)&lightbulb_state);
+
+ log_status_error_f(sc, "HSL server lightbulb state store in PS failed." NL);
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function is called each time the lightbulb state in RAM is changed.
+ * It sets up a soft timer that will save the state in flash after small delay.
+ * The purpose is to reduce amount of unnecessary flash writes.
+ ******************************************************************************/
+static void lightbulb_state_changed(void)
+{
+ sl_status_t sc = sl_simple_timer_start(&hsl_state_store_timer,
+ SL_BTMESH_HSL_SERVER_NVM_SAVE_TIME_CFG_VAL,
+ hsl_state_store_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start State Store timer");
+}
+
+/***************************************************************************//**
+ * This function validates the lighbulb_state and change it if it is against
+ * the specification.
+ ******************************************************************************/
+static void lightbulb_state_validate_and_correct(void)
+{
+ if (lightbulb_state.hue_min > lightbulb_state.hue_max) {
+ lightbulb_state.hue_min = lightbulb_state.hue_max;
+ }
+ if (lightbulb_state.hue_default < lightbulb_state.hue_min) {
+ lightbulb_state.hue_default = lightbulb_state.hue_min;
+ }
+ if (lightbulb_state.hue_default > lightbulb_state.hue_max) {
+ lightbulb_state.hue_default = lightbulb_state.hue_max;
+ }
+ if (lightbulb_state.hue_current < lightbulb_state.hue_min) {
+ lightbulb_state.hue_current = lightbulb_state.hue_min;
+ }
+ if (lightbulb_state.hue_current > lightbulb_state.hue_max) {
+ lightbulb_state.hue_current = lightbulb_state.hue_max;
+ }
+ if (lightbulb_state.hue_target < lightbulb_state.hue_min) {
+ lightbulb_state.hue_target = lightbulb_state.hue_min;
+ }
+ if (lightbulb_state.hue_target > lightbulb_state.hue_max) {
+ lightbulb_state.hue_target = lightbulb_state.hue_max;
+ }
+
+ if (lightbulb_state.saturation_min > lightbulb_state.saturation_max) {
+ lightbulb_state.saturation_min = lightbulb_state.saturation_max;
+ }
+ if (lightbulb_state.saturation_default < lightbulb_state.saturation_min) {
+ lightbulb_state.saturation_default = lightbulb_state.saturation_min;
+ }
+ if (lightbulb_state.saturation_default > lightbulb_state.saturation_max) {
+ lightbulb_state.saturation_default = lightbulb_state.saturation_max;
+ }
+ if (lightbulb_state.saturation_current < lightbulb_state.saturation_min) {
+ lightbulb_state.saturation_current = lightbulb_state.saturation_min;
+ }
+ if (lightbulb_state.saturation_current > lightbulb_state.saturation_max) {
+ lightbulb_state.saturation_current = lightbulb_state.saturation_max;
+ }
+ if (lightbulb_state.saturation_target < lightbulb_state.saturation_min) {
+ lightbulb_state.saturation_target = lightbulb_state.saturation_min;
+ }
+ if (lightbulb_state.saturation_target > lightbulb_state.saturation_max) {
+ lightbulb_state.saturation_target = lightbulb_state.saturation_max;
+ }
+}
+
+/*******************************************************************************
+ * Lightbulb state initialization.
+ * This is called at each boot if provisioning is already done.
+ * Otherwise this function is called after provisioning is completed.
+ ******************************************************************************/
+void sl_btmesh_hsl_server_init(void)
+{
+ memset(&lightbulb_state, 0, sizeof(struct lightbulb_state));
+
+ lightbulb_state_load();
+
+ // Handle on power up behavior
+ uint32_t transition_ms = sl_btmesh_get_default_transition_time();
+ switch (sl_btmesh_get_lightness_onpowerup()) {
+ case MESH_GENERIC_ON_POWER_UP_STATE_OFF:
+ case MESH_GENERIC_ON_POWER_UP_STATE_ON:
+ lightbulb_state.hue_current = lightbulb_state.hue_default;
+ lightbulb_state.hue_target = lightbulb_state.hue_default;
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_default, IMMEDIATE);
+ lightbulb_state.saturation_current = lightbulb_state.saturation_default;
+ lightbulb_state.saturation_target = lightbulb_state.saturation_default;
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_default,
+ IMMEDIATE);
+ break;
+
+ case MESH_GENERIC_ON_POWER_UP_STATE_RESTORE:
+ if (transition_ms > 0
+ && lightbulb_state.hue_target != lightbulb_state.hue_default) {
+ lightbulb_state.hue_current = lightbulb_state.hue_default;
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_current, IMMEDIATE);
+ sl_status_t sc = sl_simple_timer_start(&hsl_hue_transition_complete_timer,
+ transition_ms,
+ hsl_hue_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Hue Transition Complete timer");
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_target,
+ transition_ms);
+ } else {
+ lightbulb_state.hue_current = lightbulb_state.hue_target;
+ sl_btmesh_hsl_set_hue_level(lightbulb_state.hue_current, IMMEDIATE);
+ }
+
+ if (transition_ms > 0
+ && lightbulb_state.saturation_target != lightbulb_state.saturation_default) {
+ lightbulb_state.saturation_current = lightbulb_state.saturation_default;
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_current,
+ IMMEDIATE);
+ sl_status_t sc = sl_simple_timer_start(&hsl_saturation_transition_complete_timer,
+ transition_ms,
+ hsl_saturation_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Saturation Transition Complete timer");
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_target,
+ transition_ms);
+ } else {
+ lightbulb_state.saturation_current = lightbulb_state.saturation_target;
+ sl_btmesh_hsl_set_saturation_level(lightbulb_state.saturation_current,
+ IMMEDIATE);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ lightbulb_state_changed();
+ init_hsl_models();
+ hsl_setup_update(BTMESH_HSL_SERVER_MAIN, mesh_lighting_state_hsl_default);
+ hsl_setup_update(BTMESH_HSL_SERVER_MAIN, mesh_lighting_state_hsl_range);
+ hsl_update_and_publish(BTMESH_HSL_SERVER_MAIN, IMMEDIATE);
+}
+
+/*******************************************************************************
+ * Handling of mesh events in hsl component.
+ * It handles:
+ * - node_provisioned
+ * - node_initialized
+ *
+ * @param[in] evt Pointer to incoming time event.
+ ******************************************************************************/
+void sl_btmesh_hsl_server_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_hsl_server_init();
+ break;
+
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_hsl_server_init();
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * @addtogroup BtmeshWrappers
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_respond to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_respond(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms,
+ uint8_t response_flags)
+{
+ sl_status_t sc = mesh_lib_generic_server_respond(model_id,
+ element_index,
+ client_addr,
+ appkey_index,
+ current,
+ target,
+ remaining_ms,
+ response_flags);
+ log_status_error_f(sc,
+ "HSL server respond failed "
+ "(claddr=0x%04x,mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ client_addr,
+ model_id,
+ element_index,
+ current->kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_update to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_update(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ sl_status_t sc = mesh_lib_generic_server_update(model_id,
+ element_index,
+ current,
+ target,
+ remaining_ms);
+
+ log_status_error_f(sc,
+ "HSL server state update failed "
+ "(mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ model_id,
+ element_index,
+ current->kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_publish to log if the Btmesh API call
+ * results in error. The parameters and the return value of the two functions
+ * are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_publish(uint16_t model_id,
+ uint16_t element_index,
+ mesh_generic_state_t kind)
+{
+ sl_status_t sc;
+
+ sc = mesh_lib_generic_server_publish(model_id, element_index, kind);
+
+ log_btmesh_status_f(sc,
+ "HSL server state publish failed "
+ "(mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ model_id,
+ element_index,
+ kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_register_handler with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ ******************************************************************************/
+static void generic_server_register_handler(uint16_t model_id,
+ uint16_t elem_index,
+ mesh_lib_generic_server_client_request_cb cb,
+ mesh_lib_generic_server_change_cb ch,
+ mesh_lib_generic_server_recall_cb recall)
+{
+ sl_status_t sc =
+ mesh_lib_generic_server_register_handler(model_id, elem_index, cb, ch, recall);
+
+ app_assert_status_f(sc,
+ "HSL server failed to register handlers "
+ "(mdl=0x%04x,elem=%d)",
+ model_id,
+ elem_index);
+}
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+/***************************************************************************//**
+ * Wrapper for sl_btmesh_scene_server_reset_register with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ * The scene server register shall be reset if the state of the model changes in
+ * order to clear the current scene.
+ * This function is available only if the btmesh_scene_server component is added
+ * to the project.
+ ******************************************************************************/
+static void scene_server_reset_register_impl(uint16_t elem_index)
+{
+ sl_status_t sc = sl_btmesh_scene_server_reset_register(elem_index);
+
+ // The function can fail if there is no scene server model in the element or
+ // the btmesh_stack_scene_server component is not present. Both of these
+ // are configuration issues so assert can be used.
+ app_assert_status_f(sc, "HSL server failed to reset scene register.");
+}
+#endif
+
+/** @} (end addtogroup BtmeshWrappers) */
+
+/***************************************************************************//**
+ * Timer Callbacks
+ ******************************************************************************/
+static void hsl_hue_level_move_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // handling of generic level move, update the lightbulb state
+ hue_level_move_request();
+}
+
+static void hsl_hue_level_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a hue generic level request has completed,
+ //update the lightbulb state
+ hue_level_transition_complete();
+}
+
+static void hsl_hue_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a hsl hue request has completed,
+ // update the lightbulb state
+ hsl_hue_transition_complete();
+}
+static void hsl_saturation_level_move_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // handling of generic level move, update the lightbulb state
+ saturation_level_move_request();
+}
+
+static void hsl_saturation_level_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a saturation generic level request has completed,
+ //update the lightbulb state
+ saturation_level_transition_complete();
+}
+
+static void hsl_saturation_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a hsl saturation request has completed,
+ // update the lightbulb state
+ hsl_saturation_transition_complete();
+}
+
+static void hsl_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a hsl request has completed, update the lightbulb state
+ hsl_transition_complete();
+}
+
+static void hsl_delayed_hue_level_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a hue generic level request has passed,
+ // now process the request
+ delayed_hue_level_request();
+}
+
+static void hsl_delayed_hsl_hue_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a hsl hue request has passed, now process the request
+ delayed_hsl_hue_request();
+}
+
+static void hsl_delayed_saturation_level_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a saturation generic level request has passed,
+ // now process the request
+ delayed_saturation_level_request();
+}
+
+static void hsl_delayed_hsl_saturation_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a hsl saturation request has passed, now process the request
+ delayed_hsl_saturation_request();
+}
+
+static void hsl_delayed_hsl_request_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a hsl request has passed, now process the request
+ delayed_hsl_request();
+}
+
+static void hsl_state_store_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // save the lightbulb state
+ lightbulb_state_store();
+}
+
+/** @} (end addtogroup HSL_SERVER) */
diff --git a/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_server.h b/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_server.h
new file mode 100644
index 00000000000..9d8c39d93af
--- /dev/null
+++ b/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_server.h
@@ -0,0 +1,57 @@
+/***************************************************************************//**
+ * @brief sl_btmesh_hsl_server.h
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_HSL_SERVER_H
+#define SL_BTMESH_HSL_SERVER_H
+
+#include "sl_btmesh_api.h"
+
+// -----------------------------------------------------------------------------
+// Functions which are automatically called when the component is selected
+
+/***************************************************************************//**
+ * Handle HSL Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_hsl_server_on_event(sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * HSL Server initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ * It is called automatically by the Universal Configurator Framework
+ *
+ ******************************************************************************/
+void sl_btmesh_hsl_server_init(void);
+
+#endif // SL_BTMESH_HSL_SERVER_H
diff --git a/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_signal_transition_handler.c b/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_signal_transition_handler.c
new file mode 100644
index 00000000000..2b9d71e25e0
--- /dev/null
+++ b/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_signal_transition_handler.c
@@ -0,0 +1,366 @@
+/***************************************************************************//**
+ * @file sl_btmesh_hsl_signal_transition_handler.c
+ * @brief HSL Transition Handler Module
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+/* C Standard Library headers */
+#include
+#include
+
+#include "app_assert.h"
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#include "sl_btmesh_hsl_server.h"
+#include "sl_btmesh_hsl_signal_transition_handler.h"
+#include "sl_btmesh_hsl_server_config.h"
+
+/***************************************************************************//**
+ * @addtogroup HSL Transition Handler
+ * @{
+ ******************************************************************************/
+
+#define NO_FLAGS 0 ///< No flags used for message
+#define IMMEDIATE 0 ///< Immediate transition time is 0 seconds
+#define NO_CALLBACK_DATA (void *)NULL // Callback has not parameters
+#define HIGH_PRIORITY 0 // High Priority
+
+/// current hue level
+static uint16_t current_hue = SL_BTMESH_HSL_SERVER_DEFAULT_HUE_CFG_VAL;
+/// starting level of hue transition
+static uint16_t start_hue;
+/// target level of hue transition
+static uint16_t target_hue;
+
+/// current saturation level
+static uint16_t current_saturation = SL_BTMESH_HSL_SERVER_DEFAULT_SATURATION_CFG_VAL;
+/// starting level of saturation transition
+static uint16_t start_saturation;
+/// target level of saturation transition
+static uint16_t target_saturation;
+
+/// hue transition time in timer ticks
+static uint32_t hue_transtime_ticks;
+/// time elapsed from hue transition start
+static uint32_t hue_transtime_elapsed;
+/// non-zero if hue transition is active
+static uint8_t hue_transitioning;
+
+/// saturation transition time in timer ticks
+static uint32_t saturation_transtime_ticks;
+/// time elapsed from saturation transition start
+static uint32_t saturation_transtime_elapsed;
+/// non-zero if saturation transition is active
+static uint8_t saturation_transitioning;
+
+static sl_simple_timer_t hue_transition_timer;
+static sl_simple_timer_t saturation_transition_timer;
+
+// Timer callbacks
+static void hue_transition_timer_cb(sl_simple_timer_t *timer, void *data);
+static void saturation_transition_timer_cb(sl_simple_timer_t *timer, void *data);
+
+////////////////////////////////////////////////////////////////////////////////
+// HSL Callbacks //
+////////////////////////////////////////////////////////////////////////////////
+
+SL_WEAK void sl_btmesh_hsl_hue_cb(uint16_t hue)
+{
+ (void)hue;
+}
+
+SL_WEAK void sl_btmesh_hsl_saturation_cb(uint16_t saturation)
+{
+ (void)saturation;
+}
+
+SL_WEAK void sl_btmesh_hsl_hue_on_ui_update(uint16_t hue)
+{
+ (void)hue;
+}
+
+SL_WEAK void sl_btmesh_hsl_saturation_on_ui_update(uint16_t saturation)
+{
+ (void)saturation;
+}
+
+/***************************************************************************//**
+ * Handler for Hue Transition Timer, which manages LEDs transitions.
+ ******************************************************************************/
+static void hue_transition_timer_cb(sl_simple_timer_t *timer, void *data)
+{
+ (void)data;
+ (void)timer;
+ // Initialize the variable to UI update period in order to trigger a UI update
+ // at the beginning of the transition.
+ static uint16_t time_elapsed_since_ui_update = SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL;
+
+ if (!hue_transitioning) {
+ sl_status_t sc = sl_simple_timer_stop(&hue_transition_timer);
+ app_assert_status_f(sc, "Failed to stop Periodic Hue Transition Timer");
+ return;
+ } else {
+ hue_transtime_elapsed++;
+
+ if (hue_transtime_elapsed >= hue_transtime_ticks) {
+ // transition complete
+ hue_transitioning = 0;
+ current_hue = target_hue;
+
+ // Set the variable to UI update period in order to trigger a UI update
+ // at the beginning of the next transition.
+ time_elapsed_since_ui_update = SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL;
+
+ // Trigger a UI update in order to provide the target values at the end
+ // of the current transition
+ sl_btmesh_hsl_hue_on_ui_update(current_hue);
+ } else {
+ // calculate current hue based on elapsed transition time
+ if (target_hue >= start_hue) {
+ current_hue = start_hue
+ + (target_hue - start_hue)
+ * (uint64_t)hue_transtime_elapsed
+ / hue_transtime_ticks;
+ } else {
+ current_hue = start_hue
+ - (start_hue - target_hue)
+ * (uint64_t)hue_transtime_elapsed
+ / hue_transtime_ticks;
+ }
+
+ // When transition is ongoing generate an event to application once every
+ // SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL ms because the event is used to update
+ // display status and therefore the rate should not be too high
+ time_elapsed_since_ui_update += SL_BTMESH_HSL_SERVER_HUE_UPDATE_PERIOD_CFG_VAL;
+
+ if (SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL <= time_elapsed_since_ui_update) {
+ time_elapsed_since_ui_update -= SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL;
+ sl_btmesh_hsl_hue_on_ui_update(current_hue);
+ }
+ }
+ }
+
+ sl_btmesh_hsl_hue_cb(current_hue);
+}
+
+/***************************************************************************//**
+ * Handler for Saturation Transition Timer, which manages LEDs transitions.
+ ******************************************************************************/
+static void saturation_transition_timer_cb(sl_simple_timer_t *timer, void *data)
+{
+ (void)data;
+ (void)timer;
+ // Initialize the variable to UI update period in order to trigger a UI update
+ // at the beginning of the transition.
+ static uint16_t time_elapsed_since_ui_update = SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL;
+
+ if (!saturation_transitioning) {
+ sl_status_t sc = sl_simple_timer_stop(&saturation_transition_timer);
+ app_assert_status_f(sc, "Failed to stop Periodic Saturation Transition Timer");
+ return;
+ } else {
+ saturation_transtime_elapsed++;
+
+ if (saturation_transtime_elapsed >= saturation_transtime_ticks) {
+ // transition complete
+ saturation_transitioning = 0;
+ current_saturation = target_saturation;
+
+ // Set the variable to UI update period in order to trigger a UI update
+ // at the beginning of the next transition.
+ time_elapsed_since_ui_update = SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL;
+
+ // Trigger a UI update in order to provide the target values at the end
+ // of the current transition
+ sl_btmesh_hsl_saturation_on_ui_update(current_saturation);
+ } else {
+ // calculate current hue based on elapsed transition time
+ if (target_saturation >= start_saturation) {
+ current_saturation = start_saturation
+ + (target_saturation - start_saturation)
+ * (uint64_t)saturation_transtime_elapsed
+ / saturation_transtime_ticks;
+ } else {
+ current_saturation = start_saturation
+ - (target_saturation - start_saturation)
+ * (uint64_t)saturation_transtime_elapsed
+ / saturation_transtime_ticks;
+ }
+
+ // When transition is ongoing generate an event to application once every
+ // SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL ms because the event is used to update
+ // display status and therefore the rate should not be too high
+ time_elapsed_since_ui_update += SL_BTMESH_HSL_SERVER_SATURATION_UPDATE_PERIOD_CFG_VAL;
+
+ if (SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL <= time_elapsed_since_ui_update) {
+ time_elapsed_since_ui_update -= SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL;
+ sl_btmesh_hsl_saturation_on_ui_update(current_saturation);
+ }
+ }
+ }
+
+ sl_btmesh_hsl_saturation_cb(current_saturation);
+}
+
+/*******************************************************************************
+ * Set LED hue in given transition time.
+ *
+ * @param[in] hue Hue level.
+ * @param[in] transition_ms Transition time in milliseconds.
+ ******************************************************************************/
+void sl_btmesh_hsl_set_hue_level(uint16_t hue, uint32_t transition_ms)
+{
+#if SL_BTMESH_HSL_SERVER_MINIMUM_HUE_CFG_VAL != (0)
+ if (hue < SL_BTMESH_HSL_SERVER_MINIMUM_HUE_CFG_VAL) {
+ hue = SL_BTMESH_HSL_SERVER_MINIMUM_HUE_CFG_VAL;
+ }
+#endif
+
+#if SL_BTMESH_HSL_SERVER_MAXIMUM_HUE_CFG_VAL != (65535)
+ if (hue > SL_BTMESH_HSL_SERVER_MAXIMUM_HUE_CFG_VAL) {
+ hue = SL_BTMESH_HSL_SERVER_MAXIMUM_HUE_CFG_VAL;
+ }
+#endif
+
+ if (transition_ms == 0) {
+ current_hue = hue;
+
+ sl_btmesh_hsl_hue_cb(current_hue);
+
+ /* if a transition was in progress, cancel it */
+ if (hue_transitioning) {
+ hue_transitioning = 0;
+ sl_status_t sc = sl_simple_timer_stop(&hue_transition_timer);
+ app_assert_status_f(sc, "Failed to stop periodic Hue Transition Timer");
+ }
+ sl_btmesh_hsl_hue_on_ui_update(current_hue);
+ return;
+ }
+
+ hue_transtime_ticks = transition_ms;
+
+ start_hue = current_hue;
+ target_hue = hue;
+
+ hue_transtime_elapsed = 0;
+ hue_transitioning = 1;
+
+ // enabling timer IRQ -> the temperature is adjusted in timer interrupt
+ // gradually until target temperature is reached.
+ sl_status_t sc = sl_simple_timer_start(&hue_transition_timer,
+ SL_BTMESH_HSL_SERVER_HUE_UPDATE_PERIOD_CFG_VAL,
+ hue_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic Hue Transition Timer");
+
+ return;
+}
+
+/*******************************************************************************
+ * Set LED saturation in given transition time.
+ *
+ * @param[in] saturation Saturation level.
+ * @param[in] transition_ms Transition time in milliseconds.
+ ******************************************************************************/
+void sl_btmesh_hsl_set_saturation_level(uint16_t saturation, uint32_t transition_ms)
+{
+#if SL_BTMESH_HSL_SERVER_MINIMUM_SATURATION_CFG_VAL != (0)
+ if (saturation < SL_BTMESH_HSL_SERVER_MINIMUM_SATURATION_CFG_VAL) {
+ saturation = SL_BTMESH_HSL_SERVER_MINIMUM_SATURATION_CFG_VAL;
+ }
+#endif
+
+#if SL_BTMESH_HSL_SERVER_MAXIMUM_SATURATION_CFG_VAL != (65535)
+ if (saturation > SL_BTMESH_HSL_SERVER_MAXIMUM_SATURATION_CFG_VAL) {
+ saturation = SL_BTMESH_HSL_SERVER_MAXIMUM_SATURATION_CFG_VAL;
+ }
+#endif
+
+ if (transition_ms == 0) {
+ current_saturation = saturation;
+
+ sl_btmesh_hsl_saturation_cb(current_saturation);
+
+ /* if a transition was in progress, cancel it */
+ if (saturation_transitioning) {
+ saturation_transitioning = 0;
+ sl_status_t sc = sl_simple_timer_stop(&saturation_transition_timer);
+ app_assert_status_f(sc, "Failed to stop periodic Saturation Transition Timer");
+ }
+ sl_btmesh_hsl_saturation_on_ui_update(current_saturation);
+ return;
+ }
+
+ saturation_transtime_ticks = transition_ms;
+
+ start_saturation = current_saturation;
+ target_saturation = saturation;
+
+ saturation_transtime_elapsed = 0;
+ saturation_transitioning = 1;
+
+ // enabling timer IRQ -> the temperature is adjusted in timer interrupt
+ // gradually until target temperature is reached.
+ sl_status_t sc = sl_simple_timer_start(&saturation_transition_timer,
+ SL_BTMESH_HSL_SERVER_SATURATION_UPDATE_PERIOD_CFG_VAL,
+ saturation_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic Saturation Transition Timer");
+
+ return;
+}
+
+/*******************************************************************************
+ * Function for retrieving current hue.
+ *
+ * @return Current hue level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_hue(void)
+{
+ return(current_hue);
+}
+
+/*******************************************************************************
+ * Function for retrieving current saturation.
+ *
+ * @return Current saturation level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_saturation(void)
+{
+ return(current_saturation);
+}
+
+/** @} (end addtogroup HSL Transition Handler) */
diff --git a/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_signal_transition_handler.h b/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_signal_transition_handler.h
new file mode 100644
index 00000000000..6da376d8cbd
--- /dev/null
+++ b/app/btmesh/common/btmesh_hsl_server/sl_btmesh_hsl_signal_transition_handler.h
@@ -0,0 +1,105 @@
+/***************************************************************************//**
+ * @brief sl_btmesh_hsl_signal_transition_handler.h
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_HSL_SIGNAL_TRANSITION_H
+#define SL_BTMESH_HSL_SIGNAL_TRANSITION_H
+
+/***************************************************************************//**
+ * Function for retrieving current hue.
+ *
+ * @return Current hue level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_hue(void);
+
+/***************************************************************************//**
+ * Function for retrieving current saturation.
+ *
+ * @return Current saturation level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_saturation(void);
+
+/***************************************************************************//**
+ * Set LED hue in given transition time.
+ *
+ * @param[in] hue Hue level.
+ * @param[in] transition_ms Transition time in milliseconds.
+ ******************************************************************************/
+void sl_btmesh_hsl_set_hue_level(uint16_t hue,
+ uint32_t transition_ms);
+
+/***************************************************************************//**
+ * Set LED saturation in given transition time.
+ *
+ * @param[in] saturation Saturation level.
+ * @param[in] transition_ms Transition time in milliseconds.
+ ******************************************************************************/
+void sl_btmesh_hsl_set_saturation_level(uint16_t saturation,
+ uint32_t transition_ms);
+
+/*******************************************************************************
+ * Callback for setting Light Hue
+ *
+ * @param[in] hue Desired light hue level.
+ ******************************************************************************/
+void sl_btmesh_hsl_hue_cb(uint16_t hue);
+
+/*******************************************************************************
+ * Callback for setting Light Saturation
+ *
+ * @param[in] saturation Desired light saturation level.
+ ******************************************************************************/
+void sl_btmesh_hsl_saturation_cb(uint16_t saturation);
+
+/***************************************************************************//**
+ * Called when the UI shall be updated with the changed HSL Model state during
+ * a transition. The rate of this callback can be controlled by changing the
+ * SL_BTMESH_HSL_SERVER_HUE_UI_UPDATE_PERIOD_CFG_VAL macro.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] hue Hue value.
+ ******************************************************************************/
+void sl_btmesh_hsl_hue_on_ui_update(uint16_t hue);
+
+/***************************************************************************//**
+ * Called when the UI shall be updated with the changed HSL Model state during
+ * a transition. The rate of this callback can be controlled by changing the
+ * SL_BTMESH_HSL_SERVER_SATURATION_UI_UPDATE_PERIOD_CFG_VAL macro.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] saturation Saturation value.
+ ******************************************************************************/
+void sl_btmesh_hsl_saturation_on_ui_update(uint16_t saturation);
+
+#endif // SL_BTMESH_HSL_SIGNAL_TRANSITION_H
diff --git a/app/btmesh/common/btmesh_iv_update/config/sl_btmesh_iv_update_config.h b/app/btmesh/common/btmesh_iv_update/config/sl_btmesh_iv_update_config.h
new file mode 100644
index 00000000000..dbf45695623
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/config/sl_btmesh_iv_update_config.h
@@ -0,0 +1,83 @@
+/***************************************************************************//**
+ * @file
+ * @brief IV Update Component Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_IV_UPDATE_CONFIG_H
+#define SL_BTMESH_IV_UPDATE_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Enable logging
+// 1
+#define SL_BTMESH_IV_UPDATE_LOGGING_CFG_VAL 1
+
+// Automatic transition to IV Update Recovery mode
+// 1
+#define SL_BTMESH_IV_UPDATE_AUTO_RECOVERY 1
+
+// Backup of IV Update age
+// 1
+#define SL_BTMESH_IV_UPDATE_AGE_BACKUP_ENABLE 1
+
+// NVM key of the iv Update age (hexadecimal)
+// 0x3000
+#define SL_BTMESH_IV_UPDATE_AGE_NVM_KEY_CFG_VAL 0x3000
+
+// IV Update age backup period in seconds<0..4294967>
+// 10
+#define SL_BTMESH_IV_UPDATE_AGE_BACKUP_PERIOD_S 10
+
+//
+
+// IV Update by age
+// 1
+#define SL_BTMESH_IV_UPDATE_BY_AGE 1
+
+// IV Update Timeout<345600..4294967>
+// 345600
+#define SL_BTMESH_IV_UPDATE_FIX_TIMEOUT_S 345600
+
+//
+
+// IV Update by sequence number
+// 1
+#define SL_BTMESH_IV_UPDATE_BY_SEQ_NUM 1
+
+// Sequence number threshold<0..16777215>
+// 0x700000
+// When any element's sequence number gets above this, an IV Update is requested.
+#define SL_BTMESH_IV_UPDATE_SEQ_NUM_THRESHOLD 0x700000
+
+// Sequence number testing period in seconds <0..4294967>
+// 10
+#define SL_BTMESH_IV_UPDATE_SEQ_NUM_TESTING_PERIOD_S 10
+
+//
+
+// <<< end of configuration section >>>
+#endif
diff --git a/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update.c b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update.c
new file mode 100644
index 00000000000..555f2a0fc4c
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update.c
@@ -0,0 +1,84 @@
+/***************************************************************************//**
+ * @file
+ * @brief Automatic IV Update implementation
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_status.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#include
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_iv_update.h"
+#include "sl_btmesh_iv_update_config.h"
+
+#include "sl_btmesh_iv_update_age_backup.h"
+#include "sl_btmesh_iv_update_by_age.h"
+#include "sl_btmesh_iv_update_by_seq_num.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup iv_update
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Handling of mesh iv_update related events.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_iv_update_on_event(sl_btmesh_msg_t* evt)
+{
+#if SL_BTMESH_IV_UPDATE_AGE_BACKUP_ENABLE
+ sl_btmesh_iv_update_age_backup_on_event(evt);
+#endif
+#if SL_BTMESH_IV_UPDATE_BY_AGE
+ sl_btmesh_iv_update_by_age_on_event(evt);
+#endif
+#if SL_BTMESH_IV_UPDATE_BY_SEQ_NUM
+ sl_btmesh_iv_update_by_seq_num_on_event(evt);
+#endif
+#if SL_BTMESH_IV_UPDATE_AUTO_RECOVERY
+ if (SL_BT_MSG_ID(evt->header) == sl_btmesh_evt_node_ivrecovery_needed_id) {
+ sl_btmesh_node_set_ivrecovery_mode(true);
+ }
+#endif
+}
+
+/** @} (end addtogroup iv_update) */
diff --git a/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update.h b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update.h
new file mode 100644
index 00000000000..48446650a14
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update.h
@@ -0,0 +1,55 @@
+/***************************************************************************//**
+ * @file
+ * @brief Automatic IV Update component header
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_IV_UPDATE_H
+#define SL_BTMESH_IV_UPDATE_H
+
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * @defgroup iv_update Automatic IV Update Component
+ * @brief Automatic IV Update Implementation
+ * This component implements IV Update related functionality
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup iv_update
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Handling of mesh iv_update related events.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_iv_update_on_event(sl_btmesh_msg_t *evt);
+
+/** @} (end addtogroup iv_update) */
+
+#endif /* SL_BTMESH_IV_UPDATE_H */
diff --git a/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_age_backup.c b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_age_backup.c
new file mode 100644
index 00000000000..7513fe2f036
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_age_backup.c
@@ -0,0 +1,154 @@
+/***************************************************************************//**
+ * @file
+ * @brief IV Update age backup implementation
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_status.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_iv_update_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup iv_update
+ * @{
+ ******************************************************************************/
+
+// IV Update age in seconds
+static uint32_t age = 0;
+
+static sl_simple_timer_t iv_update_age_backup_timer;
+static void on_iv_update_age_backup_timer(sl_simple_timer_t *handle, void *data);
+
+static void age_backup_timer_start(void);
+static void backup_age(void);
+static void restore_age(void);
+
+/***************************************************************************//**
+ * Handling of mesh iv_update_age_backup related events.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_iv_update_age_backup_on_event(sl_btmesh_msg_t* evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ restore_age();
+ age_backup_timer_start();
+ }
+ break;
+ case sl_btmesh_evt_node_provisioned_id:
+ case sl_btmesh_evt_node_changed_ivupdate_state_id:
+ age = 0;
+ backup_age();
+ age_backup_timer_start();
+ break;
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * Restore the IV Update age from the nvm.
+ ******************************************************************************/
+static void restore_age(void)
+{
+ size_t age_len = sizeof(age);
+ sl_status_t sc = app_btmesh_nvm_read(SL_BTMESH_IV_UPDATE_AGE_NVM_KEY_CFG_VAL,
+ &age,
+ &age_len);
+ log_status_error_f(sc, "Failed to read from nvm" NL);
+ if (sc == SL_STATUS_OK) {
+ sc = sl_btmesh_node_set_iv_update_age(age);
+ app_assert_status_f(sc, "Failed to restore IV Update age");
+ }
+}
+
+/***************************************************************************//**
+ * Write the IV Update age to the nvm.
+ ******************************************************************************/
+static void backup_age(void)
+{
+ sl_status_t sc = app_btmesh_nvm_write(SL_BTMESH_IV_UPDATE_AGE_NVM_KEY_CFG_VAL,
+ &age,
+ sizeof(age));
+ log_status_error_f(sc, "Failed to backup IV Update age" NL);
+}
+
+/***************************************************************************//**
+ * Increment and backup IV Update age
+ ******************************************************************************/
+static void on_iv_update_age_backup_timer(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+ age += SL_BTMESH_IV_UPDATE_AGE_BACKUP_PERIOD_S;
+ backup_age();
+}
+
+/***************************************************************************//**
+ * Write the IV Update age to the nvm.
+ ******************************************************************************/
+static void age_backup_timer_start(void)
+{
+ sl_status_t sc = sl_simple_timer_start(&iv_update_age_backup_timer,
+ SL_BTMESH_IV_UPDATE_AGE_BACKUP_PERIOD_S * 1000,
+ on_iv_update_age_backup_timer,
+ NULL,
+ true);
+ app_assert_status_f(sc, "Failed to start timer");
+}
+
+#if SL_BTMESH_IV_UPDATE_AGE_BACKUP_ENABLE
+/***************************************************************************//**
+ * Strong implementation of the function declared in sl_btmesh_iv_update_by_age.h
+ * @return The restored IV Update age in seconds
+ ******************************************************************************/
+uint32_t get_iv_update_age(void)
+{
+ return age;
+}
+#endif
+
+/** @} (end addtogroup iv_update) */
diff --git a/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_age_backup.h b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_age_backup.h
new file mode 100644
index 00000000000..96873f98d84
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_age_backup.h
@@ -0,0 +1,49 @@
+/***************************************************************************//**
+ * @file
+ * @brief IV Update age backup header
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_IV_UPDATE_AGE_BACKUP_H
+#define SL_BTMESH_IV_UPDATE_AGE_BACKUP_H
+
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * @addtogroup iv_update
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Handling of mesh iv_update_age_backup related events.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_iv_update_age_backup_on_event(sl_btmesh_msg_t *evt);
+
+/** @} (end addtogroup iv_update) */
+
+#endif /* SL_BTMESH_IV_UPDATE_AGE_BACKUP_H */
diff --git a/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_age.c b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_age.c
new file mode 100644
index 00000000000..dbbcb455873
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_age.c
@@ -0,0 +1,150 @@
+/***************************************************************************//**
+ * @file
+ * @brief Automatic IV Update by age implementation
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_iv_update_by_age.h"
+#include "sl_btmesh_iv_update_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup iv_update
+ * @{
+ ******************************************************************************/
+
+#define NORMAL_OPERATION 0
+#define IV_UPDATE_IN_PROGRESS 1
+
+static sl_simple_timer_t age_timer;
+static void on_age_timer(sl_simple_timer_t *handle, void *data);
+
+static void age_timer_start(uint32_t timeout_s);
+static void age_timer_stop(void);
+
+/***************************************************************************//**
+ * Handling of mesh iv_update_by_age related events.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_iv_update_by_age_on_event(sl_btmesh_msg_t* evt)
+{
+ uint8_t iv_update_state;
+ uint32_t iv_index;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_node_get_ivupdate_state(&iv_index, &iv_update_state);
+ if (iv_update_state == NORMAL_OPERATION) {
+ uint32_t age = get_iv_update_age();
+ if (age < SL_BTMESH_IV_UPDATE_FIX_TIMEOUT_S) {
+ age_timer_start(SL_BTMESH_IV_UPDATE_FIX_TIMEOUT_S - age);
+ } else {
+ age_timer_start(0);
+ }
+ }
+ }
+ break;
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_node_get_ivupdate_state(&iv_index, &iv_update_state);
+ if (iv_update_state == NORMAL_OPERATION) {
+ age_timer_start(SL_BTMESH_IV_UPDATE_FIX_TIMEOUT_S);
+ }
+ break;
+ case sl_btmesh_evt_node_changed_ivupdate_state_id:
+ if (evt->data.evt_node_changed_ivupdate_state.state == NORMAL_OPERATION) {
+ age_timer_start(SL_BTMESH_IV_UPDATE_FIX_TIMEOUT_S);
+ } else {
+ age_timer_stop();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * Request IV Update on timer callback
+ ******************************************************************************/
+static void on_age_timer(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+ sl_status_t sc = sl_btmesh_node_request_ivupdate();
+ app_assert_status_f(sc, "Failed to request IV Update");
+}
+
+/***************************************************************************//**
+ * Start age timer
+ ******************************************************************************/
+static void age_timer_start(uint32_t timeout_s)
+{
+ sl_status_t sc = sl_simple_timer_start(&age_timer,
+ timeout_s * 1000,
+ on_age_timer,
+ NULL,
+ false);
+ app_assert_status_f(sc, "Failed to start timer");
+}
+
+/***************************************************************************//**
+ * Stop age timer
+ ******************************************************************************/
+static void age_timer_stop(void)
+{
+ sl_status_t sc = sl_simple_timer_stop(&age_timer);
+ app_assert_status_f(sc, "Failed to stop timer");
+}
+
+SL_WEAK uint32_t get_iv_update_age(void)
+{
+ return 0;
+}
+
+/** @} (end addtogroup iv_update) */
diff --git a/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_age.h b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_age.h
new file mode 100644
index 00000000000..ef61a95d79c
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_age.h
@@ -0,0 +1,57 @@
+/***************************************************************************//**
+ * @file
+ * @brief Automatic IV Update by age header
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_IV_UPDATE_BY_AGE_H
+#define SL_BTMESH_IV_UPDATE_BY_AGE_H
+
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * @addtogroup iv_update
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Handling of mesh iv_update_by_age related events.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_iv_update_by_age_on_event(sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * Weak function. Implementation should return the current IV Update age.
+ * Weak implementation returns zero. It is only called on node init event.
+ * iv_update_age_backup implements this function if it is enabled.
+ * @return IV Update age in seconds
+ ******************************************************************************/
+uint32_t get_iv_update_age(void);
+
+/** @} (end addtogroup iv_update) */
+
+#endif /* SL_BTMESH_IV_UPDATE_BY_AGE_H */
diff --git a/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_seq_num.c b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_seq_num.c
new file mode 100644
index 00000000000..ec76a8b41bf
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_seq_num.c
@@ -0,0 +1,149 @@
+/***************************************************************************//**
+ * @file
+ * @brief Automatic IV Update by sequence number implementation
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_iv_update_by_seq_num.h"
+#include "sl_btmesh_iv_update_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup iv_update
+ * @{
+ ******************************************************************************/
+
+#define NORMAL_OPERATION 0
+#define IV_UPDATE_IN_PROGRESS 1
+
+static sl_simple_timer_t seq_num_testing_timer;
+static void on_seq_num_testing_timer(sl_simple_timer_t *handle, void *data);
+
+static void seq_num_testing_start(void);
+static void seq_num_testing_stop(void);
+
+/***************************************************************************//**
+ * Handling of mesh iv_update related events.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_iv_update_by_seq_num_on_event(sl_btmesh_msg_t* evt)
+{
+ uint8_t iv_update_state;
+ uint32_t iv_index;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_node_get_ivupdate_state(&iv_index, &iv_update_state);
+ if (iv_update_state == NORMAL_OPERATION) {
+ seq_num_testing_start();
+ }
+ }
+ break;
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_node_get_ivupdate_state(&iv_index, &iv_update_state);
+ if (iv_update_state == NORMAL_OPERATION) {
+ seq_num_testing_start();
+ }
+ break;
+ case sl_btmesh_evt_node_changed_ivupdate_state_id:
+ if (evt->data.evt_node_changed_ivupdate_state.state == NORMAL_OPERATION) {
+ seq_num_testing_start();
+ } else {
+ seq_num_testing_stop();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * Check if any element's current sequence number is above the threshold
+ ******************************************************************************/
+static void on_seq_num_testing_timer(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+ sl_status_t sc;
+ for (uint16_t i = 0; i < SL_BTMESH_CONFIG_MAX_ELEMENTS; i++) {
+ uint32_t seqnum;
+ sc = sl_btmesh_node_get_element_seqnum(i, &seqnum);
+ app_assert_status_f(sc, "Failed to get sequence number");
+ if (seqnum > SL_BTMESH_IV_UPDATE_SEQ_NUM_THRESHOLD) {
+ sc = sl_btmesh_node_request_ivupdate();
+ log_status_error_f(sc, "Failed to request IV Update" NL);
+ return;
+ }
+ }
+}
+
+/***************************************************************************//**
+ * Start testing the sequence numbers
+ ******************************************************************************/
+static void seq_num_testing_start(void)
+{
+ sl_status_t sc = sl_simple_timer_start(&seq_num_testing_timer,
+ SL_BTMESH_IV_UPDATE_SEQ_NUM_TESTING_PERIOD_S * 1000,
+ on_seq_num_testing_timer,
+ NULL,
+ true);
+ app_assert_status_f(sc, "Failed to start timer");
+}
+
+/***************************************************************************//**
+ * Stop testing the sequence numbers
+ ******************************************************************************/
+static void seq_num_testing_stop(void)
+{
+ sl_status_t sc = sl_simple_timer_stop(&seq_num_testing_timer);
+ app_assert_status_f(sc, "Failed to stop timer");
+}
+
+/** @} (end addtogroup iv_update) */
diff --git a/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_seq_num.h b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_seq_num.h
new file mode 100644
index 00000000000..833589c0492
--- /dev/null
+++ b/app/btmesh/common/btmesh_iv_update/sl_btmesh_iv_update_by_seq_num.h
@@ -0,0 +1,49 @@
+/***************************************************************************//**
+ * @file
+ * @brief Automatic IV Update by sequence number header
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_IV_UPDATE_BY_SEQ_NUM_H
+#define SL_BTMESH_IV_UPDATE_BY_SEQ_NUM_H
+
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * @addtogroup iv_update
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Handling of mesh iv_update_by_seq_num related events.
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_iv_update_by_seq_num_on_event(sl_btmesh_msg_t *evt);
+
+/** @} (end addtogroup iv_update) */
+
+#endif /* SL_BTMESH_IV_UPDATE_BY_SEQ_NUM_H */
diff --git a/app/btmesh/common/btmesh_lc_server/btmesh_lc_server.dcd b/app/btmesh/common/btmesh_lc_server/btmesh_lc_server.dcd
new file mode 100644
index 00000000000..89e5f0041e3
--- /dev/null
+++ b/app/btmesh/common/btmesh_lc_server/btmesh_lc_server.dcd
@@ -0,0 +1,11 @@
+[
+ {
+ "name": "Light LC",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1000", "name":"Generic OnOff Server"},
+ {"mid":"0x130F", "name":"Light LC Server"},
+ {"mid":"0x1310", "name":"Light LC Setup Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_lc_server/config/sl_btmesh_lc_server_config.h b/app/btmesh/common/btmesh_lc_server/config/sl_btmesh_lc_server_config.h
new file mode 100644
index 00000000000..1dca95c346c
--- /dev/null
+++ b/app/btmesh/common/btmesh_lc_server/config/sl_btmesh_lc_server_config.h
@@ -0,0 +1,133 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_LC_SERVER_CONFIG_H
+#define SL_BTMESH_LC_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// LC Server configuration
+
+// Timeout [ms] for saving States of the model to NVM.
+// Default: 5000
+// Timeout [ms] for saving States of the model to NVM.
+#define SL_BTMESH_LC_SERVER_NVM_SAVE_TIME_CFG_VAL (5000)
+
+// PS Key for NVM Page where the States of the LC Model are saved.
+// Default: 0x4006
+// PS Key for NVM Page where the States of the LC Model are saved.
+#define SL_BTMESH_LC_SERVER_PS_KEY_CFG_VAL (0x4006)
+
+// PS Key for NVM Page where the Property State of the LC Model are saved.
+// Default: 0x4007
+// PS Key for NVM Page where the Property State of the LC Model are saved.
+#define SL_BTMESH_LC_SERVER_PROPERTY_PS_KEY_CFG_VAL (0x4007)
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for LC Server model specific messages for this component.
+#define SL_BTMESH_LC_SERVER_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// Customize LC Property states' default values
+#define SL_BTMESH_LC_SERVER_PROPERTY_STATE_DEFAULT_ENABLE_CFG_VAL 0
+
+// Time
+
+// Occupancy Delay [ms] <0x000000-0xFFFFFF>
+// Determines the delay for changing the LC Occupancy state upon receiving a Sensor Status message from an occupancy sensor.
+// LC Occupancy is a binary state that represents occupancy reported by an occupancy sensor.
+#define SL_BTMESH_LC_SERVER_TIME_OCCUPANCY_DELAY_DEFAULT_CFG_VAL 0
+
+// Fade On [ms] <0x000000-0xFFFFFF>
+// Determines the time the controlled lights fade to the level determined by the LC Lightness On state.
+#define SL_BTMESH_LC_SERVER_TIME_FADE_ON_DEFAULT_CFG_VAL 0
+
+// Run On [ms] <0x000000-0xFFFFFF>
+// Determines the time the controlled lights stay at the level determined by the LC Lightness On state since the occupancy input stopped detecting active occupancy information.
+#define SL_BTMESH_LC_SERVER_TIME_RUN_ON_DEFAULT_CFG_VAL 0
+
+// Fade [ms] <0x000000-0xFFFFFF>
+// Determines the time the controlled lights fade from the level determined by the LC Lightness On state to the level determined by the Lightness Prolong state.
+#define SL_BTMESH_LC_SERVER_TIME_FADE_DEFAULT_CFG_VAL 0
+
+// Prolong [ms] <0x000000-0xFFFFFF>
+// Determines the time the controlled lights stay at the level determined by the LC Lightness Prolong state.
+#define SL_BTMESH_LC_SERVER_TIME_PROLONG_DEFAULT_CFG_VAL 0
+
+// Fade Standby Auto [ms] <0x000000-0xFFFFFF>
+// Determines the time the controlled lights fade from the level determined by the LC Lightness Prolong state to the level determined by the LC Lightness Standby state when the transition is automatic.
+#define SL_BTMESH_LC_SERVER_TIME_FADE_STANDBY_AUTO_DEFAULT_CFG_VAL 0
+
+// Fade Standby Manual [ms] <0x000000-0xFFFFFF>
+// Determines the time the controlled lights fade from the level determined by the LC Lightness Prolong state to the level determined by the LC Lightness Standby state when the transition is triggered by a change in the LC Light OnOff state.
+#define SL_BTMESH_LC_SERVER_TIME_FADE_STANDBY_MANUAL_DEFAULT_CFG_VAL 0
+
+//
+
+// Lightness
+
+// On <0x0000-0xFFFF>
+// Determines the perceptive light lightness at the Occupancy and Run internal controller states.
+#define SL_BTMESH_LC_SERVER_LIGHTNESS_ON_DEFAULT_CFG_VAL 0
+
+// Prolong <0x0000-0xFFFF>
+// Determines the light lightness at the Prolong internal controller state.
+#define SL_BTMESH_LC_SERVER_LIGHTNESS_PROLONG_DEFAULT_CFG_VAL 0
+
+// Standby <0x0000-0xFFFF>
+// Determines the light lightness at the Standby internal controller state.
+#define SL_BTMESH_LC_SERVER_LIGHTNESS_STANDBY_DEFAULT_CFG_VAL 0
+
+//
+
+// Ambient
+
+// LuxLevel On [lux] <0x0000-0xFFFF>
+// Represents the level that determines if the controller transitions from the Light Control Standby state.
+#define SL_BTMESH_LC_SERVER_AMBIENT_LUX_LEVEL_ON_DEFAULT_CFG_VAL 0
+
+// LuxLevel Prolong [lux] <0x0000-0xFFFF>
+// Represents the required level in the Prolong state.
+#define SL_BTMESH_LC_SERVER_AMBIENT_LUX_LEVEL_PROLONG_DEFAULT_CFG_VAL 0
+
+// LuxLevel Standby [lux] <0x0000-0xFFFF>
+// Represents the required level in the Standby state.
+#define SL_BTMESH_LC_SERVER_AMBIENT_LUX_LEVEL_STANDBY_DEFAULT_CFG_VAL 0
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_LC_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_lc_server/sl_btmesh_lc_server.c b/app/btmesh/common/btmesh_lc_server/sl_btmesh_lc_server.c
new file mode 100644
index 00000000000..a733f0134ea
--- /dev/null
+++ b/app/btmesh/common/btmesh_lc_server/sl_btmesh_lc_server.c
@@ -0,0 +1,1570 @@
+/***************************************************************************//**
+ * @file
+ * @brief Bt Mesh LC Server module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+// C Standard Library headers
+#include
+#include
+
+#include
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "sl_btmesh_generic_model_capi_types.h"
+
+// Mesh specific headers
+#include "sl_btmesh_lib.h"
+#include "sl_btmesh_device_properties.h"
+#include "sl_btmesh_sensor.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_lc_server_config.h"
+#include "sl_btmesh_lc_server.h"
+#include "sl_btmesh_lighting_server.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup LC_Server
+ * @{
+ ******************************************************************************/
+
+#define FRACTION(num) (uint16_t)(((num) > 0 \
+ ? (num) - (int32_t)(num) \
+ : (int32_t)(num) - (num)) * 1000)
+
+/// No flags used for message
+#define NO_FLAGS 0
+/// Immediate transition time is 0 seconds
+#define IMMEDIATE 0
+/// Callback has no parameters
+#define NO_CALLBACK_DATA (void *)NULL
+/// High Priority
+#define HIGH_PRIORITY 0
+/// Values greater than max 37200000 are treated as unknown remaining time
+#define UNKNOWN_REMAINING_TIME 40000000
+/**
+ * @brief Binary state that determines the mode of operation of the controller
+ *
+ * - 0 The controller is turned off. The binding with the Light Lightness state
+ * is disabled.
+ * - 1 The controller is turned on. The binding with the Light Lightness state
+ * is enabled.
+ */
+#define LC_MODE_DEFAULT 0
+/**
+ * @brief Binary state that determines if a controller transitions from a
+ * standby state when an occupancy sensor reports occupancy
+ *
+ * - 0 The controller does not transition from a standby state when occupancy
+ * is reported.
+ * - 1 The controller may transition from a standby state when occupancy
+ * is reported.
+ */
+#define LC_OCCUPANCY_MODE_DEFAULT 1
+/**
+ * @brief Representing the integral coefficient that determines the integral
+ * part of the equation defining the output of the regulator
+ *
+ * 0.0 - 1000.0 Integral coefficient when increasing output
+ */
+#define LC_REGULATOR_KIU_DEFAULT 250.0
+/**
+ * @brief Representing the integral coefficient that determines the integral
+ * part of the equation defining the output of the regulator
+ *
+ * 0.0 - 1000.0 Integral coefficient when decreasing output
+ */
+#define LC_REGULATOR_KID_DEFAULT 25.0
+/**
+ * @brief Representing the proportional coefficient that determines the
+ * proportional part of the equation defining the output of the regulator
+ *
+ * 0.0 - 1000.0 Proportional coefficient when increasing output
+ */
+#define LC_REGULATOR_KPU_DEFAULT 80.0
+/**
+ * @brief Representing the proportional coefficient that determines the
+ * proportional part of the equation defining the output of the regulator
+ *
+ * 0.0 - 1000.0 Proportional coefficient when decreasing output
+ */
+#define LC_REGULATOR_KPD_DEFAULT 80.0
+/**
+ * @brief Representing the percentage accuracy of the regulator
+ *
+ * 0.0 - 100.0 Regulator accuracy (percentage)
+ *
+ * \note Representing half a percent, i.e. 4 represents 2.0 percent.
+ */
+#define LC_REGULATOR_ACCURACY_DEFAULT 4
+
+/*******************************************************************************
+ * Timer handle definitions.
+ ******************************************************************************/
+static sl_simple_timer_t lc_save_state_timer;
+static sl_simple_timer_t lc_save_property_state_timer;
+static sl_simple_timer_t lc_onoff_transition_timer;
+static sl_simple_timer_t lc_delayed_onoff_timer;
+
+// Timer callbacks
+static void lc_save_state_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lc_save_property_state_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lc_onoff_transition_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lc_delayed_onoff_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+/// LC state
+static PACKSTRUCT(struct lc_state {
+ uint8_t mode; /**< LC mode */
+ uint8_t occupancy_mode; /**< LC occupancy mode */
+ uint8_t light_onoff; /**< LC light onoff */
+ uint8_t onoff_current; /**< Current LC generic on/off value */
+ uint8_t onoff_target; /**< Target LC generic on/off value */
+}) lc_state;
+
+/// LC property state
+static PACKSTRUCT(struct lc_property_state {
+ /// Delay between receiving sensor occupancy message
+ /// and changing the Light LC Occupancy state
+ light_control_time_occupancy_delay time_occupancy_delay: 24;
+ /// Transition time from a standby state to a run state
+ light_control_time_fade_on time_fade_on: 24;
+ /// Duration of the run state after last occupancy was detected
+ light_control_time_run_on time_run_on: 24;
+ /// Transition time from a run state to a prolong state
+ light_control_time_fade time_fade: 24;
+ /// Duration of the prolong state
+ light_control_time_prolong time_prolong: 24;
+ /// Transition time from a prolong state to a standby state
+ /// when the transition is automatic
+ light_control_time_standby_auto time_fade_standby_auto: 24;
+ /// Transition time from a prolong state to a standby state
+ /// when the transition is triggered by a manual operation
+ light_control_time_standby_manual time_fade_standby_manual: 24;
+ /// Lightness level in a run state
+ uint16_t lightness_on;
+ /// Lightness level in a prolong state
+ uint16_t lightness_prolong;
+ /// Lightness level in a standby state
+ uint16_t lightness_standby;
+ /// Required Ambient LuxLevel level in the Run state
+ illuminance_t ambient_luxlevel_on: 24;
+ /// Required Ambient LuxLevel level in the Prolong state
+ illuminance_t ambient_luxlevel_prolong: 24;
+ /// Required Ambient LuxLevel level in the Standby state
+ illuminance_t ambient_luxlevel_standby: 24;
+ /// Integral coefficient of PI light regulator when increasing output
+ coefficient_t regulator_kiu;
+ /// Integral coefficient of PI light regulator when decreasing output
+ coefficient_t regulator_kid;
+ /// Proportional coefficient of PI light regulator when increasing output
+ coefficient_t regulator_kpu;
+ /// Proportional coefficient of PI light regulator when decreasing output
+ coefficient_t regulator_kpd;
+ /// Accuracy of PI light regulator
+ percentage_8_t regulator_accuracy;
+}) lc_property_state;
+
+/// copy of transition delay parameter, needed for delayed lc on/off request
+static uint32_t delayed_lc_onoff_trans = 0;
+
+static void lc_onoff_transition_complete(void);
+
+static void delayed_lc_onoff_request(void);
+
+/***************************************************************************//**
+ * Initialization of the models supported by this node.
+ * This function registers callbacks for each of the supported models.
+ ******************************************************************************/
+static void init_models(void);
+
+/***************************************************************************//**
+ * This function loads the saved light controller state from Persistent Storage
+ * and copies the data in the global variable lc_state.
+ * If PS key with ID 0x4005 does not exist or loading failed,
+ * lc_state is set to zero and some default values are written to it.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeeds, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lc_state_load(void)
+{
+ sl_status_t sc;
+ size_t ps_len = 0;
+ struct lc_state ps_data;
+
+ sc = sl_bt_nvm_load(SL_BTMESH_LC_SERVER_PS_KEY_CFG_VAL,
+ sizeof(ps_data),
+ &ps_len,
+ (uint8_t *)&ps_data);
+
+ // Set default values if ps_load fail or size of lc_state has changed
+ if ((sc != SL_STATUS_OK) || (ps_len != sizeof(lc_state))) {
+ memset(&lc_state, 0, sizeof(lc_state));
+ lc_state.mode = LC_MODE_DEFAULT;
+ lc_state.occupancy_mode = LC_OCCUPANCY_MODE_DEFAULT;
+
+ if (sc == SL_STATUS_OK) {
+ // The sl_bt_nvm_load call was successful but the size of the loaded data
+ // differs from the expected size therefore error code shall be set
+ sc = SL_STATUS_INVALID_STATE;
+ log_error("LC server lc_state loaded from PS with invalid size, "
+ "use defaults. (expected=%zd,actual=%zd)" NL,
+ sizeof(lc_state),
+ ps_len);
+ } else {
+ log_status_error_f(sc,
+ "LC server lc_state load from PS failed "
+ "or nvm is empty, use defaults." NL);
+ }
+ } else {
+ memcpy(&lc_state, &ps_data, ps_len);
+ }
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function saves the current light controller state in Persistent Storage
+ * so that the data is preserved over reboots and power cycles.
+ * The light controller state is hold in a global variable lc_state.
+ * A PS key with ID 0x4005 is used to store the whole structure.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static int lc_state_store(void)
+{
+ sl_status_t sc = sl_bt_nvm_save(SL_BTMESH_LC_SERVER_PS_KEY_CFG_VAL,
+ sizeof(struct lc_state),
+ (const uint8_t *)&lc_state);
+
+ log_status_error_f(sc,
+ "LC server lc_state store in PS failed." NL);
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function is called each time the light controller state in RAM
+ * is changed. It sets up a soft timer that will save the state in flash after
+ * small delay. The purpose is to reduce amount of unnecessary flash writes.
+ ******************************************************************************/
+static void lc_state_changed(void)
+{
+ sl_status_t sc = sl_simple_timer_start(&lc_save_state_timer,
+ SL_BTMESH_LC_SERVER_NVM_SAVE_TIME_CFG_VAL,
+ lc_save_state_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start LC State save timer");
+}
+
+/*******************************************************************************
+ * This function is getter for current light controller mode.
+ *
+ * @return current light controller mode
+ ******************************************************************************/
+uint8_t lc_get_mode(void)
+{
+ return lc_state.mode;
+}
+
+/*******************************************************************************
+ * Light Controller state update on power up sequence.
+ *
+ * @param[in] element Index of the element.
+ * @param[in] onpowerup Value of OnPowerUp state.
+ ******************************************************************************/
+void lc_onpowerup_update(uint16_t element, uint8_t onpowerup)
+{
+ sl_status_t sc_mode = SL_STATUS_OK;
+ sl_status_t sc_om = SL_STATUS_OK;
+ sl_status_t sc_onoff = SL_STATUS_OK;
+
+ switch (onpowerup) {
+ case MESH_GENERIC_ON_POWER_UP_STATE_OFF:
+ case MESH_GENERIC_ON_POWER_UP_STATE_ON:
+ lc_state.mode = 0;
+ lc_state.light_onoff = 0;
+ lc_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_OFF;
+ lc_state.onoff_target = MESH_GENERIC_ON_OFF_STATE_OFF;
+ sc_mode = sl_btmesh_lc_server_update_mode(element, lc_state.mode);
+ sc_om = sl_btmesh_lc_server_update_om(element, lc_state.occupancy_mode);
+ sc_onoff = sl_btmesh_lc_server_update_light_onoff(element,
+ lc_state.light_onoff,
+ IMMEDIATE);
+ break;
+
+ case MESH_GENERIC_ON_POWER_UP_STATE_RESTORE:
+ if (lc_state.mode == 0) {
+ sc_mode = sl_btmesh_lc_server_update_mode(element, lc_state.mode);
+ sc_om = sl_btmesh_lc_server_update_om(element, lc_state.occupancy_mode);
+ } else {
+ sc_mode = sl_btmesh_lc_server_update_mode(element, lc_state.mode);
+ sc_om = sl_btmesh_lc_server_update_om(element, lc_state.occupancy_mode);
+ if (lc_state.light_onoff == 0) {
+ sc_onoff = sl_btmesh_lc_server_update_light_onoff(element,
+ lc_state.light_onoff,
+ IMMEDIATE);
+ } else {
+ sc_onoff =
+ sl_btmesh_lc_server_update_light_onoff(element,
+ lc_state.light_onoff,
+ lc_property_state.time_fade_on);
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ log_btmesh_status_f(sc_mode,
+ "lc_server_update_mode failed (elem=%d)" NL,
+ element);
+
+ log_btmesh_status_f(sc_om,
+ "lc_server_update_om failed (elem=%d)" NL,
+ element);
+
+ log_btmesh_status_f(sc_onoff,
+ "lc_server_update_light_onoff failed (elem=%d)" NL,
+ element);
+
+ lc_state_changed();
+}
+
+/***************************************************************************//**
+ * This function loads the saved light controller property state from Persistent
+ * Storage and copies the data in the global variable lc_property_state.
+ * If PS key with ID 0x4006 does not exist or loading failed,
+ * lc_property_state is set to zero and some default values are written to it.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lc_property_state_load(void)
+{
+ sl_status_t sc;
+ size_t ps_len = 0;
+ struct lc_property_state ps_data;
+
+ sc = sl_bt_nvm_load(SL_BTMESH_LC_SERVER_PROPERTY_PS_KEY_CFG_VAL,
+ sizeof(ps_data),
+ &ps_len,
+ (uint8_t *)&ps_data);
+
+ // Set default values if ps_load fail or size of lc_property_state has changed
+ if ((sc != SL_STATUS_OK) || (ps_len != sizeof(lc_property_state))) {
+ memset(&lc_property_state, 0, sizeof(lc_property_state));
+#if SL_BTMESH_LC_SERVER_PROPERTY_STATE_DEFAULT_ENABLE_CFG_VAL
+ lc_property_state.time_occupancy_delay =
+ SL_BTMESH_LC_SERVER_TIME_OCCUPANCY_DELAY_DEFAULT_CFG_VAL;
+ lc_property_state.time_fade_on = SL_BTMESH_LC_SERVER_TIME_FADE_ON_DEFAULT_CFG_VAL;
+ lc_property_state.time_run_on = SL_BTMESH_LC_SERVER_TIME_RUN_ON_DEFAULT_CFG_VAL;
+ lc_property_state.time_fade = SL_BTMESH_LC_SERVER_TIME_FADE_DEFAULT_CFG_VAL;
+ lc_property_state.time_prolong = SL_BTMESH_LC_SERVER_TIME_PROLONG_DEFAULT_CFG_VAL;
+ lc_property_state.time_fade_standby_auto =
+ SL_BTMESH_LC_SERVER_TIME_FADE_STANDBY_AUTO_DEFAULT_CFG_VAL;
+ lc_property_state.time_fade_standby_manual =
+ SL_BTMESH_LC_SERVER_TIME_FADE_STANDBY_MANUAL_DEFAULT_CFG_VAL;
+ lc_property_state.lightness_on = SL_BTMESH_LC_SERVER_LIGHTNESS_ON_DEFAULT_CFG_VAL;
+ lc_property_state.lightness_prolong = SL_BTMESH_LC_SERVER_LIGHTNESS_PROLONG_DEFAULT_CFG_VAL;
+ lc_property_state.lightness_standby = SL_BTMESH_LC_SERVER_LIGHTNESS_STANDBY_DEFAULT_CFG_VAL;
+ lc_property_state.ambient_luxlevel_on =
+ SL_BTMESH_LC_SERVER_AMBIENT_LUX_LEVEL_ON_DEFAULT_CFG_VAL;
+ lc_property_state.ambient_luxlevel_prolong =
+ SL_BTMESH_LC_SERVER_AMBIENT_LUX_LEVEL_PROLONG_DEFAULT_CFG_VAL;
+ lc_property_state.ambient_luxlevel_standby =
+ SL_BTMESH_LC_SERVER_AMBIENT_LUX_LEVEL_STANDBY_DEFAULT_CFG_VAL;
+#endif // SL_BTMESH_LC_SERVER_PROPERTY_STATE_DEFAULT_ENABLE_CFG_VAL
+ lc_property_state.regulator_kiu = LC_REGULATOR_KIU_DEFAULT;
+ lc_property_state.regulator_kid = LC_REGULATOR_KID_DEFAULT;
+ lc_property_state.regulator_kpu = LC_REGULATOR_KPU_DEFAULT;
+ lc_property_state.regulator_kpd = LC_REGULATOR_KPD_DEFAULT;
+ lc_property_state.regulator_accuracy = LC_REGULATOR_ACCURACY_DEFAULT;
+
+ if (sc == SL_STATUS_OK) {
+ // The sl_bt_nvm_load call was successful but the size of the loaded data
+ // differs from the expected size therefore error code shall be set
+ sc = SL_STATUS_INVALID_STATE;
+ log_error("LC server lc_property_state loaded from PS with invalid size, "
+ "use defaults. (expected=%zd,actual=%zd)" NL,
+ sizeof(lc_property_state),
+ ps_len);
+ } else {
+ log_status_error_f(sc,
+ "LC server lc_property_state load from PS failed "
+ "or nvm is empty, use defaults." NL);
+ }
+ } else {
+ memcpy(&lc_property_state, &ps_data, ps_len);
+ }
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function saves the current light controller property state in Persistent
+ * Storage so that the data is preserved over reboots and power cycles.
+ * The light controller property state is hold in a global variable
+ * lc_property_state. A PS key with ID 0x4006 is used to store the
+ * whole structure.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static int lc_property_state_store(void)
+{
+ sl_status_t sc;
+
+ sc = sl_bt_nvm_save(SL_BTMESH_LC_SERVER_PROPERTY_PS_KEY_CFG_VAL,
+ sizeof(struct lc_property_state),
+ (const uint8_t *)&lc_property_state);
+
+ log_status_error_f(sc,
+ "LC server lc_property_state store in PS failed." NL);
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function is called each time the light controller property state in RAM
+ * is changed. It sets up a soft timer that will save the state in flash after
+ * small delay. The purpose is to reduce amount of unnecessary flash writes.
+ ******************************************************************************/
+static void lc_property_state_changed(void)
+{
+ sl_status_t sc = sl_simple_timer_start(&lc_save_property_state_timer,
+ SL_BTMESH_LC_SERVER_NVM_SAVE_TIME_CFG_VAL,
+ lc_save_property_state_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start LC Property Save timer");
+}
+
+/***************************************************************************//**
+ * This function update property in stack based on property data.
+ *
+ * @param[in] element Index of the element.
+ * @param[in] property_data Pointer to property data array that contains:
+ * - property ID in first two bytes,
+ * - length of data in third byte,
+ * - property value in the next bytes.
+ ******************************************************************************/
+static void update_property(uint16_t element, const uint8_t *property_data)
+{
+ uint16_t property_id = (uint16_t)property_data[0]
+ | ((uint16_t)property_data[1] << 8);
+ sl_status_t sc =
+ sl_btmesh_lc_setup_server_update_property(element,
+ property_id,
+ property_data[2],
+ &property_data[3]);
+
+ log_btmesh_status_f(sc,
+ "lc_setup_server_update_property failed "
+ "(elem=%d,property=0x%04x)" NL,
+ element,
+ property_id);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Time Occupancy Delay property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_time_occupancy_delay_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ light_control_time_occupancy_delay delay = lc_property_state.time_occupancy_delay;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_TIME_OCCUPANCY_DELAY,
+ property_data,
+ (uint8_t *)&delay);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Time Fade On property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_time_fade_on_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ light_control_time_fade_on fade_on = lc_property_state.time_fade_on;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_TIME_FADE_ON,
+ property_data,
+ (uint8_t *)&fade_on);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Time Run On property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_time_run_on_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ light_control_time_run_on run_on = lc_property_state.time_run_on;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_TIME_RUN_ON,
+ property_data,
+ (uint8_t *)&run_on);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Time Fade property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_time_fade_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ light_control_time_fade fade = lc_property_state.time_fade;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_TIME_FADE,
+ property_data,
+ (uint8_t *)&fade);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Time Prolong property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_time_prolong_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ light_control_time_prolong prolong = lc_property_state.time_prolong;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_TIME_PROLONG,
+ property_data,
+ (uint8_t *)&prolong);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Time Fade Standby Auto property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_time_fade_standby_auto_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ light_control_time_standby_auto standby_auto = lc_property_state.time_fade_standby_auto;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_TIME_FADE_STANDBY_AUTO,
+ property_data,
+ (uint8_t *)&standby_auto);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Time Fade Standby Manual property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_time_fade_standby_manual_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ light_control_time_standby_manual standby_manual = lc_property_state.time_fade_standby_manual;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_TIME_FADE_STANDBY_MANUAL,
+ property_data,
+ (uint8_t *)&standby_manual);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Lightness On property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_lightness_on_update(uint16_t element)
+{
+ uint8_t property_data[5];
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_LIGHTNESS_ON,
+ property_data,
+ (uint8_t *)&lc_property_state.lightness_on);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Lightness Prolong property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_lightness_prolong_update(uint16_t element)
+{
+ uint8_t property_data[5];
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_LIGHTNESS_PROLONG,
+ property_data,
+ (uint8_t *)&lc_property_state.lightness_prolong);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Lightness Standby property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_lightness_standby_update(uint16_t element)
+{
+ uint8_t property_data[5];
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_LIGHTNESS_STANDBY,
+ property_data,
+ (uint8_t *)&lc_property_state.lightness_standby);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Ambient LuxLevel On property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_ambient_luxlevel_on_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ illuminance_t ambient_luxlevel_on = lc_property_state.ambient_luxlevel_on;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_AMBIENT_LUXLEVEL_ON,
+ property_data,
+ (uint8_t *)&ambient_luxlevel_on);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Ambient LuxLevel Prolong property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_ambient_luxlevel_prolong_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ illuminance_t ambient_luxlevel_prolong = lc_property_state.ambient_luxlevel_prolong;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_AMBIENT_LUXLEVEL_PROLONG,
+ property_data,
+ (uint8_t *)&ambient_luxlevel_prolong);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Ambient LuxLevel Standby property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_ambient_luxlevel_standby_update(uint16_t element)
+{
+ uint8_t property_data[6];
+ illuminance_t ambient_luxlevel_standby = lc_property_state.ambient_luxlevel_standby;
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_AMBIENT_LUXLEVEL_STANDBY,
+ property_data,
+ (uint8_t *)&ambient_luxlevel_standby);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Regulator Kiu property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_regulator_kiu_update(uint16_t element)
+{
+ uint8_t property_data[7];
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_REGULATOR_KIU,
+ property_data,
+ (uint8_t *)&lc_property_state.regulator_kiu);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Regulator Kid property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_regulator_kid_update(uint16_t element)
+{
+ uint8_t property_data[7];
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_REGULATOR_KID,
+ property_data,
+ (uint8_t *)&lc_property_state.regulator_kid);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Regulator Kpu property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_regulator_kpu_update(uint16_t element)
+{
+ uint8_t property_data[7];
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_REGULATOR_KPU,
+ property_data,
+ (uint8_t *)&lc_property_state.regulator_kpu);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Regulator Kpd property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_regulator_kpd_update(uint16_t element)
+{
+ uint8_t property_data[7];
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_REGULATOR_KPD,
+ property_data,
+ (uint8_t *)&lc_property_state.regulator_kpd);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update Light LC Regulator Accuracy property in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_regulator_accuracy_update(uint16_t element)
+{
+ uint8_t property_data[4];
+ mesh_sensor_data_to_buf(LIGHT_CONTROL_REGULATOR_ACCURACY,
+ property_data,
+ (uint8_t *)&lc_property_state.regulator_accuracy);
+ update_property(element, property_data);
+}
+
+/***************************************************************************//**
+ * This function update all light controller properties in stack.
+ *
+ * @param[in] element Index of the element.
+ ******************************************************************************/
+static void lc_property_state_update(uint16_t element)
+{
+ lc_time_occupancy_delay_update(element);
+ lc_time_fade_on_update(element);
+ lc_time_run_on_update(element);
+ lc_time_fade_update(element);
+ lc_time_prolong_update(element);
+ lc_time_fade_standby_auto_update(element);
+ lc_time_fade_standby_manual_update(element);
+ lc_lightness_on_update(element);
+ lc_lightness_prolong_update(element);
+ lc_lightness_standby_update(element);
+ lc_ambient_luxlevel_on_update(element);
+ lc_ambient_luxlevel_prolong_update(element);
+ lc_ambient_luxlevel_standby_update(element);
+ lc_regulator_kiu_update(element);
+ lc_regulator_kid_update(element);
+ lc_regulator_kpu_update(element);
+ lc_regulator_kpd_update(element);
+ lc_regulator_accuracy_update(element);
+}
+
+/*******************************************************************************
+ * LC initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ *
+ * @param[in] element Index of the element where LC model is initialized.
+ *
+ * @return Status of the initialization operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+sl_status_t sl_btmesh_lc_init(void)
+{
+ // Initialize lc server models
+ const uint16_t element = BTMESH_LC_SERVER_LIGHT_LC;
+ sl_status_t result;
+ sl_status_t sc;
+
+ result = sl_btmesh_lc_server_init(element);
+ log_status_f(result,
+ "sl_btmesh_lc_server_init failed (elem=%d)" NL,
+ element);
+
+ memset(&lc_state, 0, sizeof(lc_state));
+ lc_state_load();
+
+ memset(&lc_property_state, 0, sizeof(lc_property_state));
+ lc_property_state_load();
+
+ // Set the regulator interval to 100 milliseconds. If you want to use shorter
+ // intervals, you should disable some logs in order not to affect performance.
+ sc = sl_btmesh_lc_server_set_regulator_interval(element, 100);
+ log_status_f(sc, "sl_btmesh_lc_server_init failed (elem=%d)" NL, element);
+
+ lc_property_state_update(element);
+ lc_property_state_changed();
+
+ lc_onpowerup_update(element, sl_btmesh_get_lightness_onpowerup());
+
+ init_models();
+
+ // The status code of the sl_btmesh_lc_server_init is returned because the
+ // successful initialization of the btmesh stack lc feature is essential
+ // for the proper behavior of the module while the improper setup of some
+ // properties and states are not that critical.
+ return result;
+}
+
+/***************************************************************************//**
+ * Handling of lc server mode updated event.
+ *
+ * @param[in] evt Pointer to lc server mode updated event.
+ ******************************************************************************/
+static void handle_lc_server_mode_updated_event(
+ sl_btmesh_evt_lc_server_mode_updated_t *evt)
+{
+ lc_state.mode = evt->mode_value;
+ lc_state_changed();
+}
+
+/***************************************************************************//**
+ * Handling of lc server occupancy mode updated event.
+ *
+ * @param[in] evt Pointer to lc server occupancy mode updated event.
+ ******************************************************************************/
+static void handle_lc_server_om_updated_event(
+ sl_btmesh_evt_lc_server_om_updated_t *evt)
+{
+ log_info("evt:sl_btmesh_evt_lc_server_om_updated_id, om=%u" NL, evt->om_value);
+ lc_state.occupancy_mode = evt->om_value;
+ lc_state_changed();
+}
+
+/***************************************************************************//**
+ * Handling of lc server light onoff updated event.
+ *
+ * @param[in] evt Pointer to lc server light onoff updated event.
+ ******************************************************************************/
+static void handle_lc_server_light_onoff_updated_event(
+ sl_btmesh_evt_lc_server_light_onoff_updated_t *evt)
+{
+ lc_state.light_onoff = evt->onoff_state;
+ lc_state_changed();
+}
+
+/***************************************************************************//**
+ * Handling of lc server linear output updated event.
+ *
+ * @param[in] evt Pointer to lc server linear output updated event.
+ ******************************************************************************/
+static void handle_lc_server_linear_output_updated_event(
+ sl_btmesh_evt_lc_server_linear_output_updated_t *evt)
+{
+ // Convert from linear to actual lightness value
+ uint32_t lightness = (uint32_t)sqrt(65535
+ * (uint32_t)(evt->linear_output_value));
+ // Update LED
+ sl_btmesh_lighting_set_level(lightness, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * Printing the float number using integers.
+ *
+ * @param[in] number Number to print.
+ ******************************************************************************/
+static void print_float(float number)
+{
+ if (number > INT32_MAX) {
+ log_append_info("> %ld", INT32_MAX);
+ } else if (number < INT32_MIN) {
+ log_append_info("< %ld", INT32_MIN);
+ } else {
+ log_append_info("%ld.%03u", (int32_t)number, FRACTION(number));
+ }
+}
+
+/***************************************************************************//**
+ * Handling of lc setup server set property event.
+ *
+ * @param[in] evt Pointer to lc setup server set property event.
+ ******************************************************************************/
+static void handle_lc_setup_server_set_property(
+ sl_btmesh_evt_lc_setup_server_set_property_t *evt)
+{
+ for (int i = 0; i < evt->property_value.len; i++) {
+ log_append_info("%2.2x", evt->property_value.data[i]);
+ }
+ log_append_info(NL);
+
+ switch (evt->property_id) {
+ case LIGHT_CONTROL_TIME_OCCUPANCY_DELAY:
+ lc_property_state.time_occupancy_delay =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_TIME_OCCUPANCY_DELAY,
+ evt->property_value.data)
+ .time_millisecond_24;
+ log_info("Light Control Time Occupancy Delay = %u.%03us" NL,
+ lc_property_state.time_occupancy_delay / 1000,
+ lc_property_state.time_occupancy_delay % 1000);
+ break;
+
+ case LIGHT_CONTROL_TIME_FADE_ON:
+ lc_property_state.time_fade_on =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_TIME_FADE_ON,
+ evt->property_value.data)
+ .time_millisecond_24;
+ log_info("Light Control Time Fade On = %u.%03us" NL,
+ lc_property_state.time_fade_on / 1000,
+ lc_property_state.time_fade_on % 1000);
+ break;
+
+ case LIGHT_CONTROL_TIME_RUN_ON:
+ lc_property_state.time_run_on =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_TIME_RUN_ON,
+ evt->property_value.data)
+ .time_millisecond_24;
+ log_info("Light Control Time Run On = %u.%03us" NL,
+ lc_property_state.time_run_on / 1000,
+ lc_property_state.time_run_on % 1000);
+ break;
+
+ case LIGHT_CONTROL_TIME_FADE:
+ lc_property_state.time_fade =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_TIME_FADE,
+ evt->property_value.data)
+ .time_millisecond_24;
+ log_info("Light Control Time Fade = %u.%03us" NL,
+ lc_property_state.time_fade / 1000,
+ lc_property_state.time_fade % 1000);
+ break;
+
+ case LIGHT_CONTROL_TIME_PROLONG:
+ lc_property_state.time_prolong =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_TIME_PROLONG,
+ evt->property_value.data)
+ .time_millisecond_24;
+ log_info("Light Control Time Prolong = %u.%03us" NL,
+ lc_property_state.time_prolong / 1000,
+ lc_property_state.time_prolong % 1000);
+ break;
+
+ case LIGHT_CONTROL_TIME_FADE_STANDBY_AUTO:
+ lc_property_state.time_fade_standby_auto =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_TIME_FADE_STANDBY_AUTO,
+ evt->property_value.data)
+ .time_millisecond_24;
+ log_info("Light Control Time Fade Standby Auto = %u.%03us" NL,
+ lc_property_state.time_fade_standby_auto / 1000,
+ lc_property_state.time_fade_standby_auto % 1000);
+ break;
+
+ case LIGHT_CONTROL_TIME_FADE_STANDBY_MANUAL:
+ lc_property_state.time_fade_standby_manual =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_TIME_FADE_STANDBY_MANUAL,
+ evt->property_value.data)
+ .time_millisecond_24;
+ log_info("Light Control Time Fade Standby Manual = %u.%03us" NL,
+ lc_property_state.time_fade_standby_manual / 1000,
+ lc_property_state.time_fade_standby_manual % 1000);
+ break;
+
+ case LIGHT_CONTROL_LIGHTNESS_ON:
+ lc_property_state.lightness_on =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_LIGHTNESS_ON,
+ evt->property_value.data)
+ .uint16;
+ log_info("Light Control Lightness On = %u" NL,
+ lc_property_state.lightness_on);
+ break;
+
+ case LIGHT_CONTROL_LIGHTNESS_PROLONG:
+ lc_property_state.lightness_prolong =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_LIGHTNESS_PROLONG,
+ evt->property_value.data)
+ .uint16;
+ log_info("Light Control Lightness Prolong = %u" NL,
+ lc_property_state.lightness_prolong);
+ break;
+
+ case LIGHT_CONTROL_LIGHTNESS_STANDBY:
+ lc_property_state.lightness_standby =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_LIGHTNESS_STANDBY,
+ evt->property_value.data)
+ .uint16;
+ log_info("Light Control Lightness Standby = %u" NL,
+ lc_property_state.lightness_standby);
+ break;
+
+ case LIGHT_CONTROL_AMBIENT_LUXLEVEL_ON:
+ lc_property_state.ambient_luxlevel_on =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_AMBIENT_LUXLEVEL_ON,
+ evt->property_value.data)
+ .illuminance;
+ log_info("Light Control Ambient LuxLevel On = %u.%02ulux" NL,
+ lc_property_state.ambient_luxlevel_on / 100,
+ lc_property_state.ambient_luxlevel_on % 100);
+ break;
+
+ case LIGHT_CONTROL_AMBIENT_LUXLEVEL_PROLONG:
+ lc_property_state.ambient_luxlevel_prolong =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_AMBIENT_LUXLEVEL_PROLONG,
+ evt->property_value.data)
+ .illuminance;
+ log_info("Light Control Ambient LuxLevel Prolong = %u.%02ulux" NL,
+ lc_property_state.ambient_luxlevel_prolong / 100,
+ lc_property_state.ambient_luxlevel_prolong % 100);
+ break;
+
+ case LIGHT_CONTROL_AMBIENT_LUXLEVEL_STANDBY:
+ lc_property_state.ambient_luxlevel_standby =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_AMBIENT_LUXLEVEL_STANDBY,
+ evt->property_value.data)
+ .illuminance;
+ log_info("Light Control Ambient LuxLevel Standby = %u.%02ulux" NL,
+ lc_property_state.ambient_luxlevel_standby / 100,
+ lc_property_state.ambient_luxlevel_standby % 100);
+ break;
+
+ case LIGHT_CONTROL_REGULATOR_KIU:
+ lc_property_state.regulator_kiu =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_REGULATOR_KIU,
+ evt->property_value.data)
+ .coefficient;
+ log_info("Light Control Regulator Kiu = ");
+ print_float(lc_property_state.regulator_kiu);
+ log_append_info(NL);
+ break;
+
+ case LIGHT_CONTROL_REGULATOR_KID:
+ lc_property_state.regulator_kid =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_REGULATOR_KID,
+ evt->property_value.data)
+ .coefficient;
+ log_info("Light Control Regulator Kid = ");
+ print_float(lc_property_state.regulator_kid);
+ log_append_info(NL);
+ break;
+
+ case LIGHT_CONTROL_REGULATOR_KPU:
+ lc_property_state.regulator_kpu =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_REGULATOR_KPU,
+ evt->property_value.data)
+ .coefficient;
+ log_info("Light Control Regulator Kpu = ");
+ print_float(lc_property_state.regulator_kpu);
+ log_append_info(NL);
+ break;
+
+ case LIGHT_CONTROL_REGULATOR_KPD:
+ lc_property_state.regulator_kpd =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_REGULATOR_KPD,
+ evt->property_value.data)
+ .coefficient;
+ log_info("Light Control Regulator Kpd = ");
+ print_float(lc_property_state.regulator_kpd);
+ log_nl();
+ break;
+
+ case LIGHT_CONTROL_REGULATOR_ACCURACY:
+ lc_property_state.regulator_accuracy =
+ mesh_sensor_data_from_buf(LIGHT_CONTROL_REGULATOR_ACCURACY,
+ evt->property_value.data)
+ .percentage;
+ if (lc_property_state.regulator_accuracy == 0xFF) {
+ log_info("Light Control Regulator Accuracy = Value is not known" NL);
+ } else {
+ log_info("Light Control Regulator Accuracy = %u.%u%%" NL,
+ lc_property_state.regulator_accuracy / 2,
+ (lc_property_state.regulator_accuracy % 2) * 5);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ lc_property_state_changed();
+}
+
+/*******************************************************************************
+ * Handle LC Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_lc_server_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_lc_server_mode_updated_id:
+ handle_lc_server_mode_updated_event(
+ &(evt->data.evt_lc_server_mode_updated));
+ break;
+
+ case sl_btmesh_evt_lc_server_om_updated_id:
+ handle_lc_server_om_updated_event(
+ &(evt->data.evt_lc_server_om_updated));
+ break;
+
+ case sl_btmesh_evt_lc_server_light_onoff_updated_id:
+ handle_lc_server_light_onoff_updated_event(
+ &(evt->data.evt_lc_server_light_onoff_updated));
+ break;
+
+ case sl_btmesh_evt_lc_server_linear_output_updated_id:
+ handle_lc_server_linear_output_updated_event(
+ &(evt->data.evt_lc_server_linear_output_updated));
+ break;
+
+ case sl_btmesh_evt_lc_setup_server_set_property_id:
+ handle_lc_setup_server_set_property(
+ &(evt->data.evt_lc_setup_server_set_property));
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_lc_init();
+ break;
+
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_lc_init();
+ }
+ break;
+
+ case sl_btmesh_evt_node_reset_id:
+ sl_bt_nvm_erase(SL_BTMESH_LC_SERVER_PS_KEY_CFG_VAL);
+ sl_bt_nvm_erase(SL_BTMESH_LC_SERVER_PROPERTY_PS_KEY_CFG_VAL);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * @addtogroup LC_GenericOnOff
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to LC generic on/off request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lc_onoff_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t sc;
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_on_off;
+ current.on_off.on = lc_state.onoff_current;
+
+ target.kind = mesh_generic_state_on_off;
+ target.on_off.on = lc_state.onoff_target;
+
+ sc = mesh_lib_generic_server_respond(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ 0x00);
+
+ log_status_error_f(sc,
+ "LC server respond failed "
+ "(claddr=0x%04x,mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ client_addr,
+ MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ current.kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Update LC generic on/off state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lc_onoff_update(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t sc;
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_on_off;
+ current.on_off.on = lc_state.onoff_current;
+
+ target.kind = mesh_generic_state_on_off;
+ target.on_off.on = lc_state.onoff_target;
+
+ sc = mesh_lib_generic_server_update(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+
+ log_status_error_f(sc,
+ "LC server state update failed "
+ "(mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ current.kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Update LC generic on/off state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lc_onoff_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t sc;
+
+ sc = lc_onoff_update(element_index, remaining_ms);
+ if (sc == SL_STATUS_OK) {
+ sc = mesh_lib_generic_server_publish(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_on_off);
+ log_btmesh_status_f(sc,
+ "LC server state publish failed "
+ "(mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_on_off);
+ }
+ return sc;
+}
+
+/*******************************************************************************
+ * This function process the requests for the LC generic on/off model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void lc_onoff_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+ sl_status_t sc;
+
+ log_info("LC ON/OFF request: requested state=<%s>, transition=%lu, delay=%u" NL,
+ request->on_off ? "ON" : "OFF", transition_ms, delay_ms);
+
+ if (lc_state.onoff_current == request->on_off) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Turning LC light <%s>" NL, request->on_off ? "ON" : "OFF");
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lc_state.onoff_current = request->on_off;
+ lc_state.onoff_target = request->on_off;
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the lc light change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lc_state.onoff_target = request->on_off;
+ sc = sl_simple_timer_start(&lc_delayed_onoff_timer,
+ delay_ms,
+ lc_delayed_onoff_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start LC Delayed Onoff Timer");
+ // store transition parameter for later use
+ delayed_lc_onoff_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lc_state.onoff_target = request->on_off;
+ if (lc_state.onoff_target == MESH_GENERIC_ON_OFF_STATE_ON) {
+ lc_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_ON;
+ }
+ lc_onoff_update(element_index, transition_ms);
+
+ // lc current state will be updated when transition is complete
+ sc = sl_simple_timer_start(&lc_onoff_transition_timer,
+ transition_ms,
+ lc_onoff_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start LC Onoff Transition timer");
+ }
+ lc_state_changed();
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+ // State has changed, so the current scene number is reset
+ sc = sl_btmesh_scene_server_reset_register(element_index);
+
+ // The function can fail if there is no scene server model in the element or
+ // the btmesh_stack_scene_server component is not present. Both of these
+ // are configuration issues so assert can be used.
+ app_assert_status_f(sc, "Failed to reset scene register");
+#endif
+ }
+
+ uint32_t remaining_ms = delay_ms + transition_ms;
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ lc_onoff_response(element_index, client_addr, appkey_index, remaining_ms);
+ }
+ lc_onoff_update_and_publish(element_index, remaining_ms);
+}
+
+/*******************************************************************************
+ * This function is a handler for LC generic on/off change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void lc_onoff_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (current->on_off.on != lc_state.onoff_current) {
+ log_info("LC ON/OFF state changed %u to %u" NL,
+ lc_state.onoff_current,
+ current->on_off.on);
+
+ lc_state.onoff_current = current->on_off.on;
+ lc_state_changed();
+ } else {
+ log_info("Dummy LC ON/OFF change - same state as before" NL);
+ }
+}
+
+/*******************************************************************************
+ * This function is a handler for LC generic on/off recall event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void lc_onoff_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+
+ log_info("LC Generic ON/OFF recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lc_state.onoff_target = current->on_off.on;
+ } else {
+ lc_state.onoff_target = target->on_off.on;
+ }
+
+ if (lc_state.onoff_current == lc_state.onoff_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall ON/OFF state <%s> with transition=%lu ms" NL,
+ lc_state.onoff_target ? "ON" : "OFF",
+ transition_ms);
+
+ if (transition_ms == IMMEDIATE) {
+ lc_state.onoff_current = current->on_off.on;
+ } else {
+ if (lc_state.onoff_target == MESH_GENERIC_ON_OFF_STATE_ON) {
+ lc_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_ON;
+ }
+ // lc current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lc_onoff_transition_timer,
+ transition_ms,
+ lc_onoff_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start LC Onoff Transition timer");
+ }
+ lc_state_changed();
+ }
+
+ lc_onoff_update_and_publish(element_index, transition_ms);
+}
+
+/***************************************************************************//**
+ * This function is called when a LC on/off request
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void lc_onoff_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lc_state.onoff_current = lc_state.onoff_target;
+
+ log_info("Transition complete. New state is %s" NL,
+ lc_state.onoff_current ? "ON" : "OFF");
+
+ lc_state_changed();
+ lc_onoff_update_and_publish(BTMESH_LC_SERVER_LIGHT_LC,
+ IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for LC on/off request has completed.
+ ******************************************************************************/
+static void delayed_lc_onoff_request(void)
+{
+ log_info("Starting delayed LC ON/OFF request: %u -> %u, %lu ms" NL,
+ lc_state.onoff_current,
+ lc_state.onoff_target,
+ delayed_lc_onoff_trans);
+
+ if (delayed_lc_onoff_trans == 0) {
+ // no transition delay, update state immediately
+
+ lc_state.onoff_current = lc_state.onoff_target;
+ lc_state_changed();
+ lc_onoff_update_and_publish(BTMESH_LC_SERVER_LIGHT_LC,
+ delayed_lc_onoff_trans);
+ } else {
+ if (lc_state.onoff_target == MESH_GENERIC_ON_OFF_STATE_ON) {
+ lc_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_ON;
+ lc_onoff_update(BTMESH_LC_SERVER_LIGHT_LC, delayed_lc_onoff_trans);
+ }
+
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lc_onoff_transition_timer,
+ delayed_lc_onoff_trans,
+ lc_onoff_transition_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start LC Onoff Transition timer");
+ }
+}
+
+/***************************************************************************//**
+ * Initialization of the models supported by this node.
+ * This function registers callbacks for each of the supported models.
+ ******************************************************************************/
+static void init_models(void)
+{
+ sl_status_t sc;
+ sc = mesh_lib_generic_server_register_handler(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ BTMESH_LC_SERVER_LIGHT_LC,
+ lc_onoff_request,
+ lc_onoff_change,
+ lc_onoff_recall);
+ app_assert_status_f(sc,
+ "LC server failed to register handlers (mdl=0x%04x,elem=%d)",
+ MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ BTMESH_LC_SERVER_LIGHT_LC);
+}
+
+/**************************************************************************//**
+ * Timer Callbacks
+ *****************************************************************************/
+static void lc_save_state_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // save the light controller state
+ lc_state_store();
+}
+
+static void lc_save_property_state_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // save the light controller property state
+ lc_property_state_store();
+}
+
+static void lc_onoff_transition_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for lc on/off request has completed, update the lc state
+ lc_onoff_transition_complete();
+}
+
+static void lc_delayed_onoff_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for lc on/off request has passed, now process the request
+ delayed_lc_onoff_request();
+}
+/** @} (end addtogroup LC_Server) */
diff --git a/app/btmesh/common/btmesh_lc_server/sl_btmesh_lc_server.h b/app/btmesh/common/btmesh_lc_server/sl_btmesh_lc_server.h
new file mode 100644
index 00000000000..9555a422dac
--- /dev/null
+++ b/app/btmesh/common/btmesh_lc_server/sl_btmesh_lc_server.h
@@ -0,0 +1,77 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_lc_server.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_LC_SERVER_H
+#define SL_BTMESH_LC_SERVER_H
+
+#include "sl_btmesh_generic_model_capi_types.h"
+
+/***************************************************************************//**
+ * @defgroup LC Light Controller Module
+ * @brief LC Module Implementation
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LC
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * LC initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ *
+ * @param[in] element Index of the element where LC model is initialized.
+ *
+ * @return Status of the initialization operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+sl_status_t sl_btmesh_lc_init(void);
+
+/***************************************************************************//**
+ * This function is getter for current light controller mode.
+ *
+ * @return current light controller mode
+ ******************************************************************************/
+uint8_t sl_btmesh_lc_get_mode(void);
+
+/***************************************************************************//**
+ * Handle LC Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_lc_server_on_event(sl_btmesh_msg_t *evt);
+
+/** @} (end addtogroup LC) */
+
+#endif /* SL_BTMESH_LC_SERVER_H */
diff --git a/app/btmesh/common/btmesh_lighting_client/btmesh_lighting_client.dcd b/app/btmesh/common/btmesh_lighting_client/btmesh_lighting_client.dcd
new file mode 100644
index 00000000000..df4925958e1
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_client/btmesh_lighting_client.dcd
@@ -0,0 +1,10 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1001", "name":"Generic On/Off Client"},
+ {"mid":"0x1302", "name":"Light Lightness Client"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_lighting_client/config/sl_btmesh_lighting_client_config.h b/app/btmesh/common/btmesh_lighting_client/config/sl_btmesh_lighting_client_config.h
new file mode 100644
index 00000000000..5628e188aa9
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_client/config/sl_btmesh_lighting_client_config.h
@@ -0,0 +1,77 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_LIGHTING_CLIENT_CONFIG_H
+#define SL_BTMESH_LIGHTING_CLIENT_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Light Lightness Client configuration
+
+// Lighting model restransmission count
+// Default: 3
+// Lighting model restransmission count (How many times Lighting model messages are to be sent out for reliability).
+#define SL_BTMESH_LIGHT_RETRANSMISSION_COUNT_CFG_VAL (3)
+
+// Lighting model restransmission timeout
+// Default: 50
+// Lighting model restransmission timeout.
+#define SL_BTMESH_LIGHT_RETRANSMISSION_TIMEOUT_CFG_VAL (50)
+
+// ONOFF model restransmission count
+// Default: 3
+// ONOFF model restransmission count (How many times ONOFF model messages are to be sent out for reliability).
+#define SL_BTMESH_ONOFF_RETRANSMISSION_COUNT_CFG_VAL (3)
+
+// ONOFF model restransmission timeout
+// Default: 50
+// ONOFF model restransmission timeout.
+#define SL_BTMESH_ONOFF_RETRANSMISSION_TIMEOUT_CFG_VAL (50)
+
+// Enable lightness value wraparound
+// Default: 0
+// If the lightness reaches the max or min value then it wraps around.
+#define SL_BTMESH_LIGHT_LIGHTNESS_WRAP_ENABLED_CFG_VAL (0)
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for Lighting Client model specific messages for this component.
+#define SL_BTMESH_LIGHTING_CLIENT_LOGGING_CFG_VAL (1)
+
+// Log text when sending a On/Off Model Message is successful.
+// Set Log text for successfully sending an On/Off Model Message.
+#define SL_BTMESH_LIGHTING_ONOFF_LOGGING_CLIENT_PUBLISH_SUCCESS_CFG_VAL "CTL On/off request sent, trid = %u, delay = %u\r\n"
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_LIGHTING_CLIENT_CONFIG_H
diff --git a/app/btmesh/common/btmesh_lighting_client/sl_btmesh_lighting_client.c b/app/btmesh/common/btmesh_lighting_client/sl_btmesh_lighting_client.c
new file mode 100644
index 00000000000..0eac48e12a8
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_client/sl_btmesh_lighting_client.c
@@ -0,0 +1,382 @@
+/***************************************************************************//**
+ * @file
+ * @brief Bt Mesh Lighting Client module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+/* C Standard Library headers */
+#include
+#include
+
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "sl_btmesh_api.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "sl_btmesh_generic_model_capi_types.h"
+#include "sl_btmesh_lib.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_lighting_client_config.h"
+#include "sl_btmesh_lighting_client.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup Lighting
+ * @{
+ ******************************************************************************/
+
+/// Parameter ignored for publishing
+#define IGNORED 0
+/// No flags used for message
+#define NO_FLAGS 0
+/// Immediate transition time is 0 seconds
+#define IMMEDIATE 0
+/// High Priority
+#define HIGH_PRIORITY 0
+/// Callback has not parameters
+#define NO_CALLBACK_DATA (void *)NULL
+/// Maximum lightness percentage value
+#define LIGHTNESS_PCT_MAX 100
+/// Delay unit value for request for ctl messages in millisecond
+#define REQ_DELAY_MS 50
+
+/// periodic timer handle
+static sl_simple_timer_t onoff_retransmission_timer;
+
+/// periodic timer callback
+static void onoff_retransmission_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+/// periodic timer handle
+static sl_simple_timer_t light_retransmission_timer;
+
+/// periodic timer callback
+static void light_retransmission_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+/// current position of the switch
+static uint8_t switch_pos = 0;
+/// number of on/off requests to be sent
+static uint8_t onoff_request_count;
+/// on/off transaction identifier
+static uint8_t onoff_trid = 0;
+/// lightness level percentage
+static uint8_t lightness_percent = 0;
+/// lightness level percentage when switching ON
+static uint8_t lightness_percent_switch_on = LIGHTNESS_PCT_MAX;
+/// lightness level converted from percentage to actual value, range 0..65535
+static uint16_t lightness_level = 0;
+/// number of lightness requests to be sent
+static uint8_t lightness_request_count;
+/// lightness transaction identifier
+static uint8_t lightness_trid = 0;
+
+/***************************************************************************//**
+ * This function publishes one generic on/off request to change the state
+ * of light(s) in the group. Global variable switch_pos holds the latest
+ * desired light state, possible values are:
+ * switch_pos = 1 -> PB1 was pressed long (above 1s), turn lights on
+ * switch_pos = 0 -> PB0 was pressed long (above 1s), turn lights off
+ *
+ * param[in] retrans Indicates if this is the first request or a retransmission,
+ * possible values are 0 = first request, 1 = retransmission.
+ *
+ * @note This application sends multiple generic on/off requests for each
+ * long button press to improve reliability.
+ * The transaction ID is not incremented in case of a retransmission.
+ ******************************************************************************/
+static void send_onoff_request(uint8_t retrans)
+{
+ struct mesh_generic_request req;
+ const uint32_t transtime = 0; // using zero transition time by default
+ sl_status_t sc;
+
+ req.kind = mesh_generic_request_on_off;
+ req.on_off = switch_pos ? MESH_GENERIC_ON_OFF_STATE_ON : MESH_GENERIC_ON_OFF_STATE_OFF;
+
+ // Increment transaction ID for each request, unless it's a retransmission
+ if (retrans == 0) {
+ onoff_trid++;
+ }
+
+ // Delay for the request is calculated so that the last request will have
+ // a zero delay and each of the previous request have delay that increases
+ // in 50 ms steps. For example, when using three on/off requests
+ // per button press the delays are set as 100, 50, 0 ms
+ uint16_t delay = (onoff_request_count - 1) * REQ_DELAY_MS;
+
+ sc = mesh_lib_generic_client_publish(MESH_GENERIC_ON_OFF_CLIENT_MODEL_ID,
+ BTMESH_LIGHTING_CLIENT_MAIN,
+ onoff_trid,
+ &req,
+ transtime, // transition time in ms
+ delay,
+ NO_FLAGS // flags
+ );
+
+ if (sc == SL_STATUS_OK) {
+ log_info("CTL On/off request sent, trid = %u, delay = %u" NL, onoff_trid, delay);
+ } else {
+ log_btmesh_status_f(sc, "On/Off Client Publish failed" NL);
+ }
+
+ // Keep track of how many requests has been sent
+ if (onoff_request_count > 0) {
+ onoff_request_count--;
+ }
+}
+
+/***************************************************************************//**
+ * This function publishes one light lightness request to change the lightness
+ * level of light(s) in the group. Global variable lightness_level holds
+ * the latest desired light level.
+ *
+ * param[in] retrans Indicates if this is the first request or a retransmission,
+ * possible values are 0 = first request, 1 = retransmission.
+ *
+ * @note This application sends multiple lightness requests for each
+ * short button press to improve reliability.
+ * The transaction ID is not incremented in case of a retransmission.
+ ******************************************************************************/
+static void send_lightness_request(uint8_t retrans)
+{
+ struct mesh_generic_request req;
+ sl_status_t sc;
+
+ req.kind = mesh_lighting_request_lightness_actual;
+ req.lightness = lightness_level;
+
+ // Increment transaction ID for each request, unless it's a retransmission
+ if (retrans == 0) {
+ lightness_trid++;
+ }
+
+ // Delay for the request is calculated so that the last request will have
+ // a zero delay and each of the previous request have delay that increases
+ // in 50 ms steps. For example, when using three lightness requests
+ // per button press the delays are set as 100, 50, 0 ms
+ uint16_t delay = (lightness_request_count - 1) * REQ_DELAY_MS;
+
+ sc = mesh_lib_generic_client_publish(MESH_LIGHTING_LIGHTNESS_CLIENT_MODEL_ID,
+ BTMESH_LIGHTING_CLIENT_MAIN,
+ lightness_trid,
+ &req,
+ IMMEDIATE, // transition
+ delay,
+ NO_FLAGS // flags
+ );
+
+ if (sc == SL_STATUS_OK) {
+ log_info("Lightness request sent, trid = %u, delay = %u" NL,
+ lightness_trid,
+ delay);
+ } else {
+ log_btmesh_status_f(sc, "Lightness Client Publish failed" NL);
+ }
+
+ // Keep track of how many requests has been sent
+ if (lightness_request_count > 0) {
+ lightness_request_count--;
+ }
+}
+
+/*******************************************************************************
+ * This function change the lightness and sends it to the server.
+ *
+ * @param[in] change_percentage Defines lightness percentage change,
+ * possible values are -100% - + 100%.
+ *
+ ******************************************************************************/
+void sl_btmesh_change_lightness(int8_t change_percentage)
+{
+ // Adjust light brightness, using Light Lightness model
+ if (change_percentage > 0) {
+ lightness_percent += change_percentage;
+ if (lightness_percent > LIGHTNESS_PCT_MAX) {
+#if (SL_BTMESH_LIGHT_LIGHTNESS_WRAP_ENABLED_CFG_VAL != 0)
+ lightness_percent = 0;
+#else
+ lightness_percent = LIGHTNESS_PCT_MAX;
+#endif
+ }
+ } else {
+ if (lightness_percent < (-change_percentage)) {
+#if (SL_BTMESH_LIGHT_LIGHTNESS_WRAP_ENABLED_CFG_VAL != 0)
+ lightness_percent = LIGHTNESS_PCT_MAX;
+#else
+ lightness_percent = 0;
+#endif
+ } else {
+ lightness_percent += change_percentage;
+ }
+ }
+
+ if (lightness_percent != 0) {
+ lightness_percent_switch_on = lightness_percent;
+ }
+ sl_btmesh_set_lightness(lightness_percent);
+}
+
+/*******************************************************************************
+ * This function change the lightness and send it to the server.
+ *
+ * @param[in] new_lightness_percentage Defines new lightness value as percentage
+ * Valid values 0-100 %
+ *
+ *
+ ******************************************************************************/
+void sl_btmesh_set_lightness(uint8_t new_lightness_percentage)
+{
+ // Adjust light brightness, using Light Lightness model
+ if (new_lightness_percentage <= LIGHTNESS_PCT_MAX) {
+ lightness_percent = new_lightness_percentage;
+ } else {
+ return;
+ }
+
+ lightness_level = lightness_percent * 0xFFFF / LIGHTNESS_PCT_MAX;
+ log("Set lightness to %u %% / level %u K" NL, lightness_percent, lightness_level);
+ // Request is sent multiple times to improve reliability
+ lightness_request_count = SL_BTMESH_LIGHT_RETRANSMISSION_COUNT_CFG_VAL;
+
+ send_lightness_request(0); // Send the first request
+
+ // If there are more requests to send, start a repeating soft timer
+ // to trigger retransmission of the request after 50 ms delay
+ if (lightness_request_count > 0) {
+ sl_status_t sc = sl_simple_timer_start(&light_retransmission_timer,
+ SL_BTMESH_LIGHT_RETRANSMISSION_TIMEOUT_CFG_VAL,
+ light_retransmission_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic timer");
+ }
+}
+
+/*******************************************************************************
+ * This function change the switch position and send it to the server.
+ *
+ * @param[in] position Defines switch position change, possible values are:
+ * - SL_BTMESH_LIGHTING_CLIENT_OFF
+ * - SL_BTMESH_LIGHTING_CLIENT_ON
+ * - SL_BTMESH_LIGHTING_CLIENT_TOGGLE
+ *
+ ******************************************************************************/
+void sl_btmesh_change_switch_position(uint8_t position)
+{
+ if (position != SL_BTMESH_LIGHTING_CLIENT_TOGGLE) {
+ switch_pos = position;
+ } else {
+ switch_pos = 1 - switch_pos; // Toggle switch state
+ }
+
+ // Turns light ON or OFF, using Generic OnOff model
+ if (switch_pos) {
+ log("Turn light(s) on" NL);
+ lightness_percent = lightness_percent_switch_on;
+ } else {
+ log("Turn light(s) off" NL);
+ lightness_percent = 0;
+ }
+ // Request is sent 3 times to improve reliability
+ onoff_request_count = SL_BTMESH_ONOFF_RETRANSMISSION_COUNT_CFG_VAL;
+
+ send_onoff_request(0); // Send the first request
+
+ // If there are more requests to send, start a repeating soft timer
+ // to trigger retransmission of the request after 50 ms delay
+ if (onoff_request_count > 0) {
+ sl_status_t sc = sl_simple_timer_start(&onoff_retransmission_timer,
+ SL_BTMESH_ONOFF_RETRANSMISSION_TIMEOUT_CFG_VAL,
+ onoff_retransmission_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic timer");
+ }
+}
+
+uint16_t sl_btmesh_get_lightness(void)
+{
+ return lightness_level;
+}
+
+/***************************************************************************//**
+ * @addtogroup btmesh_light_clnt_tim_cb Timer Callbacks
+ * @{
+ ******************************************************************************/
+/***************************************************************************//**
+ * Switch position retransmission function
+ * @param[in] handle pointer to handle instance
+ * @param[in] data pointer to input data
+ ******************************************************************************/
+static void onoff_retransmission_timer_cb(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+
+ send_onoff_request(1); // param 1 indicates that this is a retransmission
+ // stop retransmission timer if it was the last attempt
+ if (onoff_request_count == 0) {
+ sl_status_t sc = sl_simple_timer_stop(&onoff_retransmission_timer);
+ app_assert_status_f(sc, "Failed to stop periodic timer");
+ }
+}
+
+/***************************************************************************//**
+ * Lightness value retransmission function
+ * @param[in] handle pointer to handle instance
+ * @param[in] data pointer to input data
+ ******************************************************************************/
+static void light_retransmission_timer_cb(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+
+ send_lightness_request(1); // Retransmit lightness message
+ // Stop retransmission timer if it was the last attempt
+ if (lightness_request_count == 0) {
+ sl_status_t sc = sl_simple_timer_stop(&light_retransmission_timer);
+ app_assert_status_f(sc, "Failed to stop periodic timer");
+ }
+}
+/** @} (end addtogroup btmesh_light_clnt_tim_cb) */
+/** @} (end addtogroup Lighting) */
diff --git a/app/btmesh/common/btmesh_lighting_client/sl_btmesh_lighting_client.h b/app/btmesh/common/btmesh_lighting_client/sl_btmesh_lighting_client.h
new file mode 100644
index 00000000000..0037099fbd6
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_client/sl_btmesh_lighting_client.h
@@ -0,0 +1,75 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_lighting_client.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_LIGHTING_CLIENT_H
+#define SL_BTMESH_LIGHTING_CLIENT_H
+
+#define SL_BTMESH_LIGHTING_CLIENT_OFF 0 ///< Set switch state to off
+#define SL_BTMESH_LIGHTING_CLIENT_ON 1 ///< Set switch state to on
+#define SL_BTMESH_LIGHTING_CLIENT_TOGGLE 2 ///< Toggle switch state
+
+/*******************************************************************************
+ * This function change the lightness and send it to the server.
+ *
+ * @param[in] change Defines lightness percentage change, possible values are
+ * -100% - + 100%.
+ *
+ ******************************************************************************/
+void sl_btmesh_change_lightness(int8_t change_percentage);
+
+/*******************************************************************************
+ * This function change the lightness and send it to the server.
+ *
+ * @param[in] new_lightness_percentage Defines new lightness value as percentage
+ * Valid values 0-100 %
+ *
+ ******************************************************************************/
+void sl_btmesh_set_lightness(uint8_t new_lightness_percentage);
+
+/***************************************************************************//**
+ * This function change the switch position and send it to the server.
+ *
+ * @param[in] position Defines switch position change, possible values are:
+ * - SL_BTMESH_LIGHTING_CLIENT_OFF
+ * - SL_BTMESH_LIGHTING_CLIENT_ON
+ * - SL_BTMESH_LIGHTING_CLIENT_TOGGLE
+ *
+ ******************************************************************************/
+void sl_btmesh_change_switch_position(uint8_t position);
+
+/***************************************************************************//**
+ * Get lightness.
+ *
+ * This function returns actual lightness to set.
+ *
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness(void);
+
+#endif // SL_BTMESH_LIGHTING_CLIENT_H
diff --git a/app/btmesh/common/btmesh_lighting_server/btmesh_lighting_server.dcd b/app/btmesh/common/btmesh_lighting_server/btmesh_lighting_server.dcd
new file mode 100644
index 00000000000..c726569cdb4
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_server/btmesh_lighting_server.dcd
@@ -0,0 +1,15 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1000", "name":"Generic OnOff Server"},
+ {"mid":"0x1002", "name":"Generic Level Server"},
+ {"mid":"0x1004", "name":"Generic Default Transition Time Server"},
+ {"mid":"0x1006", "name":"Generic Power OnOff Server"},
+ {"mid":"0x1007", "name":"Generic Power OnOff Setup Server"},
+ {"mid":"0x1300", "name":"Light Lightness Server"},
+ {"mid":"0x1301", "name":"Light Lightness Setup Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_lighting_server/config/sl_btmesh_lighting_server_config.h b/app/btmesh/common/btmesh_lighting_server/config/sl_btmesh_lighting_server_config.h
new file mode 100644
index 00000000000..4b99db69cef
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_server/config/sl_btmesh_lighting_server_config.h
@@ -0,0 +1,110 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_LIGHTING_SERVER_CONFIG_H
+#define SL_BTMESH_LIGHTING_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Light Lightness Server configuration
+
+// Timeout [ms] for saving States of the model to NVM.
+// Default: 5000
+// Timeout [ms] for saving States of the model to NVM.
+#define SL_BTMESH_LIGHTING_SERVER_NVM_SAVE_TIME_CFG_VAL (5000)
+
+// PS Key for NVM Page where the States of the Lighting Model are saved.
+// Default: 0x4004
+// PS Key for NVM Page where the States of the Lighting Model are saved.
+#define SL_BTMESH_LIGHTING_SERVER_PS_KEY_CFG_VAL (0x4004)
+
+// Periodicity [ms] for updating the PWM duty cycle during a transition.
+// Default: 1
+// Periodicity [ms] for updating the PWM duty cycle during a transition.
+#define SL_BTMESH_LIGHTING_SERVER_PWM_UPDATE_PERIOD_CFG_VAL (1)
+
+// for updating the UI with lightness level during a transition.
+// Default: 100
+// Periodicity [ms] for updating the UI with lightness level during a transition.
+#define SL_BTMESH_LIGHTING_SERVER_UI_UPDATE_PERIOD_CFG_VAL (100)
+
+// Timer value for minimum PWM duty cycle.
+// Default: 1
+// Timer value for minimum PWM duty cycle.
+#define SL_BTMESH_LIGHTING_SERVER_PWM_MINIMUM_BRIGHTNESS_CFG_VAL (1)
+
+// Timer value for maximum PWM duty cycle.
+// Default: 0xFFFE
+// Timer value for minimum PWM duty cycle.
+#define SL_BTMESH_LIGHTING_SERVER_PWM_MAXIMUM_BRIGHTNESS_CFG_VAL (0xFFFE)
+
+// Lightness Range
+
+// Minimum lightness value <0x0001-0xFFFF>
+// Determines the minimum non-zero lightness an element is configured to emit.
+// Default: 0x0001
+#define SL_BTMESH_LIGHTING_SERVER_LIGHTNESS_MIN_CFG_VAL 0x0001
+
+// Maximum lightness value <0x0001-0xFFFF>
+// Determines the maximum lightness an element is configured to emit.
+// The value of the Light Lightness Range Max state shall be greater than
+// or equal to the value of the Light Lightness Range Min state.
+// Default: 0xFFFF
+#define SL_BTMESH_LIGHTING_SERVER_LIGHTNESS_MAX_CFG_VAL 0xFFFF
+
+//
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for Lighting Server model specific messages for this component.
+#define SL_BTMESH_LIGHTING_SERVER_LOGGING_CFG_VAL (1)
+
+// Enable debug prints for each server state changed event.
+// Default: 0
+// Enable debug prints for each server state changed event.
+#define SL_BTMESH_LIGHTING_SERVER_DEBUG_PRINTS_FOR_STATE_CHANGE_EVENTS_CFG_VAL (0)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+// The PWM update period shall not be greater than the UI update period
+#if (SL_BTMESH_LIGHTING_SERVER_UI_UPDATE_PERIOD_CFG_VAL) < (SL_BTMESH_LIGHTING_SERVER_PWM_UPDATE_PERIOD_CFG_VAL)
+#error "The SL_BTMESH_LIGHTING_SERVER_PWM_UPDATE_PERIOD_CFG_VAL shall be less than SL_BTMESH_LIGHTING_SERVER_UI_UPDATE_PERIOD_CFG_VAL."
+#endif
+
+// Lightness maximum value cannot be less than minimum value
+#if (SL_BTMESH_LIGHTING_SERVER_LIGHTNESS_MAX_CFG_VAL) < (SL_BTMESH_LIGHTING_SERVER_LIGHTNESS_MIN_CFG_VAL)
+#error The value of the Lightness Range Max shall be greater than or equal to \
+ the value of the Lightness Range Min state.
+#endif // (SL_BTMESH_LIGHTING_SERVER_LIGHTNESS_MAX_CFG_VAL) < (SL_BTMESH_LIGHTING_SERVER_LIGHTNESS_MIN_CFG_VAL)
+
+#endif // SL_BTMESH_LIGHTING_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_level_transition_handler.c b/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_level_transition_handler.c
new file mode 100644
index 00000000000..03a8e46b3a4
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_level_transition_handler.c
@@ -0,0 +1,236 @@
+/***************************************************************************//**
+ * @file
+ * @brief Lighting Level Transition Handler Module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#include "sl_btmesh_lighting_level_transition_handler.h"
+#include "sl_btmesh_lighting_server_config.h"
+
+/***************************************************************************//**
+ * @addtogroup Lighting Level Transition Handler
+ * @{
+ ******************************************************************************/
+
+/// No flags used for message
+#define NO_FLAGS 0
+/// Immediate transition time is 0 seconds
+#define IMMEDIATE 0
+/// Callback has not parameters
+#define NO_CALLBACK_DATA (void *)NULL
+/// High Priority
+#define HIGH_PRIORITY 0
+
+/// current lightness level
+static uint16_t current_level;
+/// starting level of lightness transition
+static uint16_t start_level;
+/// target level of lightness transition
+static uint16_t target_level;
+
+/// lightness transition time in timer ticks
+static uint32_t level_transtime_ticks;
+/// time elapsed from lightness transition start
+static uint32_t level_transtime_elapsed;
+/// non-zero if lightness transition is active
+static uint8_t level_transitioning;
+
+static sl_simple_timer_t transition_timer;
+
+// Timer callbacks
+static void transition_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+////////////////////////////////////////////////////////////////////////////////
+// Lighting Callbacks //
+////////////////////////////////////////////////////////////////////////////////
+
+SL_WEAK void sl_btmesh_lighting_level_pwm_cb(uint16_t level)
+{
+ (void)level;
+}
+
+SL_WEAK void sl_btmesh_lighting_server_on_ui_update(uint16_t lightness_level)
+{
+ (void) lightness_level;
+}
+
+/***************************************************************************//**
+ * Timer Callback for LEDs transitions.
+ ******************************************************************************/
+static void transition_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+
+ // Initialize the variable to UI update period in order to trigger a UI update
+ // at the beginning of the transition.
+ static uint16_t time_elapsed_since_ui_update =
+ SL_BTMESH_LIGHTING_SERVER_UI_UPDATE_PERIOD_CFG_VAL;
+
+ if (!level_transitioning) {
+ sl_status_t sc = sl_simple_timer_stop(&transition_timer);
+ app_assert_status_f(sc, "Failed to stop Periodic Level Transition Timer");
+ return;
+ } else {
+ level_transtime_elapsed++;
+
+ if (level_transtime_elapsed >= level_transtime_ticks) {
+ // transition complete
+ level_transitioning = 0;
+ current_level = target_level;
+
+ // Set the variable to UI update period in order to trigger a UI update
+ // at the beginning of the next transition.
+ time_elapsed_since_ui_update = SL_BTMESH_LIGHTING_SERVER_UI_UPDATE_PERIOD_CFG_VAL;
+
+ // Trigger a UI update in order to provide the target values at the end
+ // of the current transition
+ sl_btmesh_lighting_server_on_ui_update(current_level);
+ } else {
+ // calculate current PWM duty cycle based on elapsed transition time
+ if (target_level >= start_level) {
+ current_level = start_level
+ + (target_level - start_level)
+ * (uint64_t)level_transtime_elapsed
+ / level_transtime_ticks;
+ } else {
+ current_level = start_level
+ - (start_level - target_level)
+ * (uint64_t)level_transtime_elapsed
+ / level_transtime_ticks;
+ }
+
+ // When transition is ongoing generate an event to application once every
+ // SL_BTMESH_CTL_SERVER_UI_UPDATE_PERIOD_CFG_VAL ms because the event is used to update display
+ // status and therefore the rate should not be too high
+ time_elapsed_since_ui_update += SL_BTMESH_LIGHTING_SERVER_PWM_UPDATE_PERIOD_CFG_VAL;
+
+ if (SL_BTMESH_LIGHTING_SERVER_UI_UPDATE_PERIOD_CFG_VAL <= time_elapsed_since_ui_update) {
+ time_elapsed_since_ui_update -= SL_BTMESH_LIGHTING_SERVER_UI_UPDATE_PERIOD_CFG_VAL;
+ sl_btmesh_lighting_server_on_ui_update(current_level);
+ }
+ }
+ }
+
+ sl_btmesh_lighting_level_pwm_cb(current_level);
+}
+
+/*******************************************************************************
+ * Set LED lightness level in given transition time.
+ *
+ * @param[in] level Lightness level.
+ * @param[in] transition_ms Transition time in milliseconds.
+ ******************************************************************************/
+void sl_btmesh_lighting_set_level(uint16_t level, uint32_t transition_ms)
+{
+ if (transition_ms == 0) {
+ current_level = level;
+
+ sl_btmesh_lighting_level_pwm_cb(current_level);
+
+ /* if a transition was in progress, cancel it */
+ if (level_transitioning) {
+ level_transitioning = 0;
+ sl_status_t sc = sl_simple_timer_stop(&transition_timer);
+ app_assert_status_f(sc, "Failed to stop Periodic Level Transition Timer");
+ }
+ sl_btmesh_lighting_server_on_ui_update(current_level);
+ return;
+ }
+
+ level_transtime_ticks = transition_ms;
+
+ start_level = current_level;
+ target_level = level;
+
+ level_transtime_elapsed = 0;
+ level_transitioning = 1;
+
+ // enabling timer IRQ -> the PWM level is adjusted in timer interrupt
+ // gradually until target level is reached.
+ sl_status_t sc = sl_simple_timer_start(&transition_timer,
+ SL_BTMESH_LIGHTING_SERVER_PWM_UPDATE_PERIOD_CFG_VAL,
+ transition_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic Transition Timer");
+
+ return;
+}
+
+/*******************************************************************************
+ * Set LEDs state. Possible states are defined in macros.
+ *
+ * @param[in] state State to set.
+ ******************************************************************************/
+void sl_btmesh_set_state(int state)
+{
+ static int toggle = 0;
+
+ switch (state) {
+ case LED_STATE_OFF:
+ sl_btmesh_lighting_set_level(SL_BTMESH_LIGHTING_SERVER_PWM_MINIMUM_BRIGHTNESS_CFG_VAL, 0);
+ break;
+ case LED_STATE_ON:
+ sl_btmesh_lighting_set_level(SL_BTMESH_LIGHTING_SERVER_PWM_MAXIMUM_BRIGHTNESS_CFG_VAL, 0);
+ break;
+ case LED_STATE_PROV:
+ if (++toggle % 2) {
+ sl_btmesh_lighting_set_level(SL_BTMESH_LIGHTING_SERVER_PWM_MINIMUM_BRIGHTNESS_CFG_VAL, 0);
+ } else {
+ sl_btmesh_lighting_set_level(SL_BTMESH_LIGHTING_SERVER_PWM_MAXIMUM_BRIGHTNESS_CFG_VAL, 0);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*******************************************************************************
+ * Function for retrieving actual lightness level.
+ *
+ * @return Actual lightness level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_level(void)
+{
+ return(current_level);
+}
+
+/** @} (end addtogroup Lighting Level Transition Handler) */
diff --git a/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_level_transition_handler.h b/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_level_transition_handler.h
new file mode 100644
index 00000000000..6d5d9933f71
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_level_transition_handler.h
@@ -0,0 +1,80 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_lighting_level_transition_handler.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_LIGHTING_LEVEL_TRANSITION_H
+#define SL_BTMESH_LIGHTING_LEVEL_TRANSITION_H
+
+#define LED_STATE_OFF 0 /**< light off (both LEDs turned off) */
+#define LED_STATE_ON 1 /**< light on (both LEDs turned on) */
+#define LED_STATE_PROV 2 /**< provisioning (LEDs blinking) */
+
+/*******************************************************************************
+ * Set LED lightness level in given transition time.
+ *
+ * @param[in] level Lightness level.
+ * @param[in] transition_ms Transition time in milliseconds.
+ ******************************************************************************/
+void sl_btmesh_lighting_set_level(uint16_t level, uint32_t transition_ms);
+
+/*******************************************************************************
+ * Set LEDs state. Possible states are defined in macros.
+ *
+ * @param[in] state State to set.
+ ******************************************************************************/
+void sl_btmesh_set_state(int state);
+
+/*******************************************************************************
+ * Function for retrieving actual lightness level.
+ *
+ * @return Actual lightness level.
+ ******************************************************************************/
+uint16_t sl_btmesh_get_level(void);
+
+/*******************************************************************************
+ * Callback for setting Light Lightness by PWM level (0x0001 - FFFE)
+ *
+ * @return Desired lightness PWM level.
+ ******************************************************************************/
+void sl_btmesh_lighting_level_pwm_cb(uint16_t level);
+
+/***************************************************************************//**
+ * Called when the UI shall be updated with the changed state of
+ * lightning server during a transition. The rate of this callback can be
+ * controlled by changing the SL_BTMESH_LIGHTING_SERVER_UI_UPDATE_PERIOD_CFG_VAL macro.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] lightness_level lightness level (0x0001 - FFFE)
+ ******************************************************************************/
+void sl_btmesh_lighting_server_on_ui_update(uint16_t lightness_level);
+
+#endif // SL_BTMESH_LIGHTING_LEVEL_TRANSITION_H
diff --git a/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_server.c b/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_server.c
new file mode 100644
index 00000000000..519fbb3f375
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_server.c
@@ -0,0 +1,2773 @@
+/***************************************************************************//**
+ * @file
+ * @brief Lighting Server module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+// C Standard Library headers
+#include
+#include
+#include
+
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_btmesh_api.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+// Bluetooth stack headers
+#include "sl_btmesh_generic_model_capi_types.h"
+#include "sl_btmesh_lib.h"
+
+#include "sl_btmesh_lighting_server.h"
+#include "sl_btmesh_lighting_server_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup Lighting Server
+ * @{
+ ******************************************************************************/
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+#define scene_server_reset_register(elem_index) \
+ scene_server_reset_register_impl(elem_index)
+#else
+#define scene_server_reset_register(elem_index)
+#endif
+
+/// No flags used for message
+#define NO_FLAGS 0
+/// Immediate transition time is 0 seconds
+#define IMMEDIATE 0
+/// Callback has no parameters
+#define NO_CALLBACK_DATA (void *)NULL
+/// High Priority
+#define HIGH_PRIORITY 0
+/// Values greater than max 37200000 are treated as unknown remaining time
+#define UNKNOWN_REMAINING_TIME 40000000
+
+/**
+ * @brief Default value of Lightness Last
+ *
+ * Stores the last known non-zero value of lightness actual.
+ */
+#define LIGHTNESS_LAST_DEFAULT 0xFFFF
+/**
+ * @brief Default value of Lightness Default
+ *
+ * Representing a default lightness level for lightness actual.
+ */
+#define LIGHTNESS_DEFAULT_DEFAULT 0x0000
+
+/// Lightbulb state
+static PACKSTRUCT(struct lightbulb_state {
+ // On/Off Server state
+ uint8_t onoff_current; /**< Current generic on/off value */
+ uint8_t onoff_target; /**< Target generic on/off value */
+
+ // Transition Time Server state
+ uint8_t transtime; /**< Transition time */
+
+ // On Power Up Server state
+ uint8_t onpowerup; /**< On Power Up value */
+
+ // Lightness server
+ uint16_t lightness_current; /**< Current lightness value */
+ uint16_t lightness_target; /**< Target lightness value */
+ uint16_t lightness_last; /**< Last lightness value */
+ uint16_t lightness_default; /**< Default lightness value */
+ uint16_t lightness_min; /**< Minimum lightness value */
+ uint16_t lightness_max; /**< Maximum lightness value */
+
+ // Primary Generic Level
+ int16_t pri_level_current; /**< Current primary generic level value */
+ int16_t pri_level_target; /**< Target primary generic level value */
+}) lightbulb_state;
+
+/// copy of transition delay parameter, needed for delayed on/off request
+static uint32_t delayed_onoff_trans = 0;
+/// copy of transition delay parameter, needed for delayed lightness request
+static uint32_t delayed_lightness_trans = 0;
+/// copy of lightness request kind, needed for delayed lightness request
+static mesh_generic_state_t lightness_kind = mesh_generic_state_last;
+/// copy of transition delay parameter, needed for
+/// delayed primary generic level request
+static uint32_t delayed_pri_level_trans = 0;
+/// copy of generic request kind, needed for delayed primary generic request
+static mesh_generic_request_t pri_level_request_kind = mesh_generic_request_level;
+/// copy of move transition parameter for primary generic request
+static uint32_t move_pri_level_trans = 0;
+/// copy of move delta parameter for primary generic request
+static int16_t move_pri_level_delta = 0;
+
+static void lightbulb_state_changed(void);
+static void lightbulb_state_validate_and_correct(void);
+
+static sl_status_t generic_server_respond(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms,
+ uint8_t response_flags);
+
+static sl_status_t generic_server_update(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms);
+
+static sl_status_t generic_server_publish(uint16_t model_id,
+ uint16_t element_index,
+ mesh_generic_state_t kind);
+
+static void generic_server_register_handler(uint16_t model_id,
+ uint16_t elem_index,
+ mesh_lib_generic_server_client_request_cb cb,
+ mesh_lib_generic_server_change_cb ch,
+ mesh_lib_generic_server_recall_cb recall);
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+static void scene_server_reset_register_impl(uint16_t elem_index);
+#endif
+
+// Timer handles
+static sl_simple_timer_t lighting_pri_level_move_timer;
+static sl_simple_timer_t lighting_transition_complete_timer;
+static sl_simple_timer_t lighting_level_transition_complete_timer;
+static sl_simple_timer_t lighting_onoff_transition_complete_timer;
+static sl_simple_timer_t lighting_delayed_pri_level_timer;
+static sl_simple_timer_t lighting_delayed_lightness_request_timer;
+static sl_simple_timer_t lighting_delayed_onoff_request_timer;
+static sl_simple_timer_t lighting_state_store_timer;
+
+// Timer callbacks
+static void lighting_pri_level_move_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lighting_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lighting_level_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lighting_onoff_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lighting_delayed_pri_level_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lighting_delayed_lightness_request_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lighting_delayed_onoff_request_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+static void lighting_state_store_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+/*******************************************************************************
+ * Get current lightness value
+ *
+ * @return Current lightness
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness_current(void)
+{
+ return lightbulb_state.lightness_current;
+}
+
+/*******************************************************************************
+ * Set current lightness value
+ *
+ * @param[in] lightness Current lightness
+ ******************************************************************************/
+void sl_btmesh_set_lightness_current(uint16_t lightness)
+{
+ if (lightness != lightbulb_state.lightness_current) {
+ lightbulb_state.lightness_current = lightness;
+ lightbulb_state_changed();
+ }
+}
+
+/*******************************************************************************
+ * Get target lightness value
+ *
+ * @return Target lightness
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness_target(void)
+{
+ return lightbulb_state.lightness_target;
+}
+
+/*******************************************************************************
+ * Set target lightness value
+ *
+ * @param[in] lightness Target lightness
+ ******************************************************************************/
+void sl_btmesh_set_lightness_target(uint16_t lightness)
+{
+ if (lightness != lightbulb_state.lightness_target) {
+ lightbulb_state.lightness_target = lightness;
+ lightbulb_state_changed();
+ }
+}
+
+/*******************************************************************************
+ * Get default lightness value
+ *
+ * @return Default lightness
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness_default(void)
+{
+ return lightbulb_state.lightness_default;
+}
+
+/*******************************************************************************
+ * Set default lightness value
+ *
+ * @param[in] lightness Default lightness
+ ******************************************************************************/
+void sl_btmesh_set_lightness_default(uint16_t lightness)
+{
+ if (lightness != lightbulb_state.lightness_default) {
+ lightbulb_state.lightness_default = lightness;
+ lightbulb_state_changed();
+ }
+}
+
+/*******************************************************************************
+ * Set last lightness value
+ *
+ * @param[in] lightness Last lightness
+ ******************************************************************************/
+void sl_btmesh_set_lightness_last(uint16_t lightness)
+{
+ if (lightness != lightbulb_state.lightness_last) {
+ lightbulb_state.lightness_last = lightness;
+ lightbulb_state_changed();
+ }
+}
+
+/*******************************************************************************
+ * Gets default lightness value on power up
+ *
+ * @return Default lightness on power up
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness_onpowerup(void)
+{
+ return lightbulb_state.onpowerup;
+}
+
+#if defined(SL_BTMESH_LIGHTING_SERVER_DEBUG_PRINTS_FOR_STATE_CHANGE_EVENTS_CFG_VAL) \
+ && SL_BTMESH_LIGHTING_SERVER_DEBUG_PRINTS_FOR_STATE_CHANGE_EVENTS_CFG_VAL
+/***************************************************************************//**
+ * This function prints debug information for mesh server state change event.
+ *
+ * @param[in] evt Pointer to mesh_lib_generic_server_state_changed event.
+ ******************************************************************************/
+static void server_state_changed(sl_btmesh_evt_generic_server_state_changed_t *evt)
+{
+ int i;
+
+ log_info("State changed: ");
+ log_append_info("Model ID %4.4x, type %2.2x ", evt->model_id, evt->type);
+ for (i = 0; i < evt->parameters.len; i++) {
+ log_append_info("%2.2x ", evt->parameters.data[i]);
+ }
+ log_append_info(NL);
+}
+#endif // LOG_ENABLE
+
+/*******************************************************************************
+ * Handle ligthing server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_lighting_server_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_lighting_server_init();
+ break;
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_lighting_server_init();
+ }
+ break;
+ case sl_btmesh_evt_node_reset_id:
+ sl_bt_nvm_erase(SL_BTMESH_LIGHTING_SERVER_PS_KEY_CFG_VAL);
+ break;
+ case sl_btmesh_evt_generic_server_state_changed_id:
+#if defined(SL_BTMESH_LIGHTING_SERVER_DEBUG_PRINTS_FOR_STATE_CHANGE_EVENTS_CFG_VAL) \
+ && SL_BTMESH_LIGHTING_SERVER_DEBUG_PRINTS_FOR_STATE_CHANGE_EVENTS_CFG_VAL
+ server_state_changed(&(evt->data.evt_generic_server_state_changed));
+#endif // LOG_ENABLE
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * This function convert mesh format of default transition time to milliseconds.
+ *
+ * @return Default transition time in milliseconds.
+ ******************************************************************************/
+uint32_t sl_btmesh_get_default_transition_time(void)
+{
+ return mesh_lib_transition_time_to_ms(lightbulb_state.transtime);
+}
+
+/***************************************************************************//**
+ * \defgroup GenericOnOff
+ * \brief Generic OnOff Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup GenericOnOff
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to generic on/off request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t onoff_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_on_off;
+ current.on_off.on = lightbulb_state.onoff_current;
+
+ target.kind = mesh_generic_state_on_off;
+ target.on_off.on = lightbulb_state.onoff_target;
+
+ return generic_server_respond(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update generic on/off state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t onoff_update(uint16_t element_index, uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_on_off;
+ current.on_off.on = lightbulb_state.onoff_current;
+
+ target.kind = mesh_generic_state_on_off;
+ target.on_off.on = lightbulb_state.onoff_target;
+
+ return generic_server_update(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update generic on/off state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t onoff_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+
+ e = onoff_update(element_index, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_on_off);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the generic on/off model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void onoff_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+
+ log_info("ON/OFF request: requested state=<%s>, transition=%lu, delay=%u" NL,
+ request->on_off ? "ON" : "OFF", transition_ms, delay_ms);
+
+ if (lightbulb_state.onoff_current == request->on_off) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Turning light bulb <%s>" NL, request->on_off ? "ON" : "OFF");
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.onoff_current = request->on_off;
+ lightbulb_state.onoff_target = request->on_off;
+ if (lightbulb_state.onoff_current == MESH_GENERIC_ON_OFF_STATE_OFF) {
+ lightbulb_state.lightness_target = 0;
+ } else {
+ // restore last brightness
+ lightbulb_state.lightness_target = lightbulb_state.lightness_last;
+ }
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target, IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the light change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.onoff_target = request->on_off;
+ sl_status_t sc = sl_simple_timer_start(&lighting_delayed_onoff_request_timer,
+ delay_ms,
+ lighting_delayed_onoff_request_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed ON/OFF Request timer");
+ // store transition parameter for later use
+ delayed_onoff_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.onoff_target = request->on_off;
+ if (lightbulb_state.onoff_target == MESH_GENERIC_ON_OFF_STATE_ON) {
+ lightbulb_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_ON;
+ }
+
+ onoff_update(element_index, transition_ms);
+
+ if (request->on_off == MESH_GENERIC_ON_OFF_STATE_OFF) {
+ lightbulb_state.lightness_target = 0;
+ } else {
+ // restore last brightness
+ lightbulb_state.lightness_target = lightbulb_state.lightness_last;
+ }
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ transition_ms);
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_onoff_transition_complete_timer,
+ transition_ms,
+ lighting_onoff_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start ON/OFF Transition Complete timer");
+ }
+ lightbulb_state_changed();
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+
+ uint32_t remaining_ms = delay_ms + transition_ms;
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ onoff_response(element_index, client_addr, appkey_index, remaining_ms);
+ }
+ onoff_update_and_publish(element_index, remaining_ms);
+
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_lightness_actual);
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic on/off change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void onoff_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (current->on_off.on != lightbulb_state.onoff_current) {
+ log_info("ON/OFF state changed %u to %u" NL,
+ lightbulb_state.onoff_current,
+ current->on_off.on);
+
+ lightbulb_state.onoff_current = current->on_off.on;
+ lightbulb_state_changed();
+ } else {
+ log_info("Dummy ON/OFF change - same state as before" NL);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic on/off recall event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void onoff_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+
+ log_info("Generic ON/OFF recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.onoff_target = current->on_off.on;
+ } else {
+ lightbulb_state.onoff_target = target->on_off.on;
+ }
+
+ if (lightbulb_state.onoff_current == lightbulb_state.onoff_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall ON/OFF state <%s> with transition=%lu ms" NL,
+ lightbulb_state.onoff_target ? "ON" : "OFF",
+ transition_ms);
+
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.onoff_current = current->on_off.on;
+ } else {
+ if (lightbulb_state.onoff_target == MESH_GENERIC_ON_OFF_STATE_ON) {
+ lightbulb_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_ON;
+ }
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_onoff_transition_complete_timer,
+ transition_ms,
+ lighting_onoff_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start ON/OFF Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+
+ onoff_update_and_publish(element_index, transition_ms);
+}
+
+/***************************************************************************//**
+ * This function is called when a light on/off request
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void onoff_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.onoff_current = lightbulb_state.onoff_target;
+
+ log_info("Transition complete. New state is %s" NL,
+ lightbulb_state.onoff_current ? "ON" : "OFF");
+
+ lightbulb_state_changed();
+ onoff_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for light on/off request has completed.
+ ******************************************************************************/
+static void delayed_onoff_request(void)
+{
+ log_info("Starting delayed ON/OFF request: %u -> %u, %lu ms" NL,
+ lightbulb_state.onoff_current,
+ lightbulb_state.onoff_target,
+ delayed_onoff_trans);
+
+ if (delayed_onoff_trans == 0) {
+ // no transition delay, update state immediately
+
+ lightbulb_state.onoff_current = lightbulb_state.onoff_target;
+ if (lightbulb_state.onoff_current == MESH_GENERIC_ON_OFF_STATE_OFF) {
+ sl_btmesh_set_state(LED_STATE_OFF);
+ } else {
+ // restore last brightness level
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_last, IMMEDIATE);
+ lightbulb_state.lightness_current = lightbulb_state.lightness_last;
+ }
+
+ lightbulb_state_changed();
+
+ onoff_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN,
+ delayed_onoff_trans);
+ } else {
+ if (lightbulb_state.onoff_target == MESH_GENERIC_ON_OFF_STATE_OFF) {
+ lightbulb_state.lightness_target = 0;
+ } else {
+ // restore last brightness level, with transition delay
+ lightbulb_state.lightness_target = lightbulb_state.lightness_last;
+ lightbulb_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_ON;
+
+ onoff_update(BTMESH_LIGHTING_SERVER_MAIN, delayed_onoff_trans);
+ }
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ delayed_onoff_trans);
+
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_onoff_transition_complete_timer,
+ delayed_onoff_trans,
+ lighting_onoff_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start ON/OFF Transition Complete timer");
+ }
+}
+
+/** @} (end addtogroup GenericOnOff) */
+
+/***************************************************************************//**
+ * \defgroup GenericPowerOnOff
+ * \brief Generic Power OnOff Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup GenericPowerOnOff
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to generic power on/off request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t power_onoff_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index)
+{
+ struct mesh_generic_state current;
+ current.kind = mesh_generic_state_on_power_up;
+ current.on_power_up.on_power_up = lightbulb_state.onpowerup;
+
+ return generic_server_respond(MESH_GENERIC_POWER_ON_OFF_SETUP_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ NULL,
+ 0,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update generic power on/off state.
+ *
+ * @param[in] element_index Server model element index.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t power_onoff_update(uint16_t element_index)
+{
+ struct mesh_generic_state current;
+ current.kind = mesh_generic_state_on_power_up;
+ current.on_power_up.on_power_up = lightbulb_state.onpowerup;
+
+ return generic_server_update(MESH_GENERIC_POWER_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ NULL,
+ 0);
+}
+
+/***************************************************************************//**
+ * Update generic power on/off state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t power_onoff_update_and_publish(uint16_t element_index)
+{
+ sl_status_t e;
+
+ e = power_onoff_update(element_index);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_GENERIC_POWER_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_on_power_up);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the generic power on/off model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void power_onoff_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+ (void)transition_ms;
+ (void)delay_ms;
+
+ log_info("ON POWER UP request received; state=<%s>" NL,
+ lightbulb_state.onpowerup == 0 ? "OFF"
+ : lightbulb_state.onpowerup == 1 ? "ON"
+ : "RESTORE");
+
+ if (lightbulb_state.onpowerup == request->on_power_up) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log("Setting onpowerup to <%s>" NL,
+ request->on_power_up == 0 ? "OFF"
+ : request->on_power_up == 1 ? "ON"
+ : "RESTORE");
+ lightbulb_state.onpowerup = request->on_power_up;
+ lightbulb_state_changed();
+ }
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ power_onoff_response(element_index, client_addr, appkey_index);
+ }
+ power_onoff_update_and_publish(element_index);
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic power on/off change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void power_onoff_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)current;
+ (void)target;
+ (void)remaining_ms;
+ // TODO
+}
+
+/** @} (end addtogroup GenericPowerOnOff) */
+
+/***************************************************************************//**
+ * \defgroup GenericTransitionTime
+ * \brief Generic Default Transition Time Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup GenericTransitionTime
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to generic default transition time request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t transtime_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index)
+{
+ struct mesh_generic_state current;
+ current.kind = mesh_generic_state_transition_time;
+ current.transition_time.time = lightbulb_state.transtime;
+
+ return generic_server_respond(MESH_GENERIC_TRANSITION_TIME_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ NULL,
+ 0,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update generic default transition time state.
+ *
+ * @param[in] element_index Server model element index.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t transtime_update(uint16_t element_index)
+{
+ struct mesh_generic_state current;
+ current.kind = mesh_generic_state_transition_time;
+ current.transition_time.time = lightbulb_state.transtime;
+
+ return generic_server_update(MESH_GENERIC_TRANSITION_TIME_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ NULL,
+ 0);
+}
+
+/***************************************************************************//**
+ * Update generic default transition time state and publish model state
+ * to the network.
+ *
+ * @param[in] element_index Server model element index.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t transtime_update_and_publish(uint16_t element_index)
+{
+ sl_status_t e;
+
+ e = transtime_update(element_index);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_GENERIC_TRANSITION_TIME_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_transition_time);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the generic default transition time
+ * model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void transtime_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+ (void)transition_ms;
+ (void)delay_ms;
+
+ log_info("Transition time request received; state=<0x%x>" NL,
+ lightbulb_state.transtime);
+
+ if (lightbulb_state.transtime == request->transition_time) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Setting transtime to <0x%x>" NL, request->transition_time);
+ lightbulb_state.transtime = request->transition_time;
+ lightbulb_state_changed();
+ }
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ transtime_response(element_index, client_addr, appkey_index);
+ }
+ transtime_update_and_publish(element_index);
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic default transition time change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void transtime_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)current;
+ (void)target;
+ (void)remaining_ms;
+ // TODO
+}
+
+/** @} (end addtogroup GenericTransitionTime) */
+
+/****************************************************************************//**
+ * \defgroup LightLightness
+ * \brief Light Lightness Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightLightness
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Convert from lightness actual to lightness linear value.
+ *
+ * @param[in] actual Actual value that is converted.
+ *
+ * @return Linear value.
+ ******************************************************************************/
+static uint16_t actual2linear(uint16_t actual)
+{
+ uint32_t linear = ((uint32_t)actual * actual + 65534) / 65535;
+ return (uint16_t)linear;
+}
+
+/***************************************************************************//**
+ * Convert from lightness linear to lightness actual value.
+ *
+ * @param[in] linear Linear value that is converted.
+ *
+ * @return Actual value.
+ ******************************************************************************/
+static uint16_t linear2actual(uint16_t linear)
+{
+ uint32_t actual = (uint32_t)sqrt(65535 * (uint32_t)linear);
+ return (uint16_t)actual;
+}
+
+/***************************************************************************//**
+ * Response to light lightness request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ * @param[in] kind Type of state used in light lightness response.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightness_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms,
+ mesh_generic_state_t kind)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = kind;
+ if (kind == mesh_lighting_state_lightness_actual) {
+ current.lightness.level = lightbulb_state.lightness_current;
+ } else {
+ current.lightness.level = actual2linear(lightbulb_state.lightness_current);
+ }
+
+ target.kind = kind;
+ if (kind == mesh_lighting_state_lightness_actual) {
+ target.lightness.level = lightbulb_state.lightness_target;
+ } else {
+ target.lightness.level = actual2linear(lightbulb_state.lightness_target);
+ }
+
+ return generic_server_respond(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update light lightness state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ * @param[in] kind Type of state used in light lightness update.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightness_update(uint16_t element_index,
+ uint32_t remaining_ms,
+ mesh_generic_state_t kind)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = kind;
+ if (kind == mesh_lighting_state_lightness_actual) {
+ current.lightness.level = lightbulb_state.lightness_current;
+ } else {
+ current.lightness.level = actual2linear(lightbulb_state.lightness_current);
+ }
+
+ target.kind = kind;
+ if (kind == mesh_lighting_state_lightness_actual) {
+ target.lightness.level = lightbulb_state.lightness_target;
+ } else {
+ target.lightness.level = actual2linear(lightbulb_state.lightness_target);
+ }
+
+ return generic_server_update(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update light lightness state and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ * @param[in] kind Type of state used in light lightness update and publish.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightness_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms,
+ mesh_generic_state_t kind)
+{
+ sl_status_t e;
+
+ e = lightness_update(element_index, remaining_ms, kind);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ element_index,
+ kind);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light lightness model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void lightness_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+
+ uint16_t actual_request = 0;
+
+ switch (request->kind) {
+ case mesh_lighting_request_lightness_actual:
+ lightness_kind = mesh_lighting_state_lightness_actual;
+ actual_request = request->lightness;
+ break;
+
+ case mesh_lighting_request_lightness_linear:
+ lightness_kind = mesh_lighting_state_lightness_linear;
+ actual_request = linear2actual(request->lightness);
+ break;
+
+ default:
+ break;
+ }
+
+ log_info("lightness_request: level=%u, transition=%lu, delay=%u" NL,
+ actual_request, transition_ms, delay_ms);
+
+ if (lightbulb_state.lightness_current == actual_request) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Setting lightness to <%u>" NL, actual_request);
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.lightness_current = actual_request;
+ lightbulb_state.lightness_target = actual_request;
+ if (actual_request != 0) {
+ lightbulb_state.lightness_last = actual_request;
+ }
+
+ // update LED PWM duty cycle
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current,
+ IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the light change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.lightness_target = actual_request;
+ sl_status_t sc = sl_simple_timer_start(&lighting_delayed_lightness_request_timer,
+ delay_ms,
+ lighting_delayed_lightness_request_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Lightness Request timer");
+ // store transition parameter for later use
+ delayed_lightness_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.lightness_target = actual_request;
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_transition_complete_timer,
+ transition_ms,
+ lighting_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Lighting Transition Complete timer");
+ }
+ lightbulb_state_changed();
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+
+ uint32_t remaining_ms = delay_ms + transition_ms;
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ lightness_response(element_index,
+ client_addr,
+ appkey_index,
+ remaining_ms,
+ lightness_kind);
+ }
+
+ lightness_update_and_publish(element_index, remaining_ms, lightness_kind);
+
+ // publish to bound states
+ if (lightness_kind == mesh_lighting_state_lightness_actual) {
+ generic_server_publish(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_lightness_linear);
+ } else {
+ generic_server_publish(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_lightness_actual);
+ }
+ generic_server_publish(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_on_off);
+
+ generic_server_publish(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_level);
+
+#ifdef SL_CATALOG_BTMESH_CTL_SERVER_PRESENT
+ generic_server_publish(MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_ctl);
+#endif
+
+#ifdef SL_CATALOG_BTMESH_HSL_SERVER_PRESENT
+ generic_server_publish(MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_hsl);
+#endif
+}
+
+/***************************************************************************//**
+ * This function is a handler for light lightness change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void lightness_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (current->kind != mesh_lighting_state_lightness_actual) {
+ // if kind is not 'actual' then just report the change here, no change to light state
+ log_info("lightness_change, kind %u, value %u" NL,
+ current->kind,
+ current->lightness.level);
+ return;
+ }
+
+ if (lightbulb_state.lightness_current != current->lightness.level) {
+ log_info("Lightness update: from %u to %u" NL,
+ lightbulb_state.lightness_current,
+ current->lightness.level);
+ lightbulb_state.lightness_current = current->lightness.level;
+ lightbulb_state_changed();
+ } else {
+ log_info("Lightness update -same value (%d)" NL,
+ lightbulb_state.lightness_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light lightness recall event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void lightness_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+
+ log_info("Light Lightness recall" NL);
+ if (current->kind != mesh_lighting_state_lightness_actual) {
+ return;
+ }
+
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.lightness_target = current->lightness.level;
+ } else {
+ lightbulb_state.lightness_target = target->lightness.level;
+ }
+
+ if (lightbulb_state.lightness_current == lightbulb_state.lightness_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall lightness to %u with transition=%lu ms" NL,
+ lightbulb_state.lightness_target,
+ transition_ms);
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ transition_ms);
+
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.lightness_current = current->lightness.level;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_transition_complete_timer,
+ transition_ms,
+ lighting_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Lighting Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+
+ lightness_update_and_publish(element_index,
+ transition_ms,
+ mesh_lighting_state_lightness_actual);
+}
+
+/***************************************************************************//**
+ * This function is called when a light lightness request
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void lightness_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.lightness_current = lightbulb_state.lightness_target;
+ if (lightbulb_state.lightness_target != 0) {
+ lightbulb_state.lightness_last = lightbulb_state.lightness_target;
+ }
+
+ log_info("Transition complete. New level is %u" NL,
+ lightbulb_state.lightness_current);
+
+ lightbulb_state_changed();
+ lightness_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN,
+ IMMEDIATE,
+ lightness_kind);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for light lightness request has completed.
+ ******************************************************************************/
+static void delayed_lightness_request(void)
+{
+ log_info("Starting delayed lightness request: level %u -> %u, %lu ms" NL,
+ lightbulb_state.lightness_current,
+ lightbulb_state.lightness_target,
+ delayed_lightness_trans);
+
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ delayed_lightness_trans);
+
+ if (delayed_lightness_trans == 0) {
+ // no transition delay, update state immediately
+ lightbulb_state.lightness_current = lightbulb_state.lightness_target;
+ if (lightbulb_state.lightness_target != 0) {
+ lightbulb_state.lightness_last = lightbulb_state.lightness_target;
+ }
+
+ lightbulb_state_changed();
+ lightness_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN,
+ delayed_lightness_trans,
+ lightness_kind);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_transition_complete_timer,
+ delayed_lightness_trans,
+ lighting_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Lighting Transition Complete timer");
+ }
+}
+
+/** @} (end addtogroup LightLightness) */
+
+/***************************************************************************//**
+ * \defgroup LightLightnessSetup
+ * \brief Light Lightness Setup Server model.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup LightLightnessSetup
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to light lightness setup request.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] kind Type of state used in light lightness setup response.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightness_setup_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ mesh_generic_state_t kind)
+{
+ struct mesh_generic_state current;
+
+ current.kind = kind;
+
+ switch (kind) {
+ case mesh_lighting_state_lightness_default:
+ current.lightness.level = lightbulb_state.lightness_default;
+ break;
+
+ case mesh_lighting_state_lightness_range:
+ current.lightness_range.min = lightbulb_state.lightness_min;
+ current.lightness_range.max = lightbulb_state.lightness_max;
+ break;
+
+ default:
+ break;
+ }
+
+ return generic_server_respond(MESH_LIGHTING_LIGHTNESS_SETUP_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ NULL,
+ 0,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update light lightness setup state.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] kind Type of state used in light lightness setup update.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightness_setup_update(uint16_t element_index,
+ mesh_generic_state_t kind)
+{
+ struct mesh_generic_state current;
+
+ current.kind = kind;
+
+ switch (kind) {
+ case mesh_lighting_state_lightness_default:
+ current.lightness.level = lightbulb_state.lightness_default;
+ break;
+
+ case mesh_lighting_state_lightness_range:
+ current.lightness_range.min = lightbulb_state.lightness_min;
+ current.lightness_range.max = lightbulb_state.lightness_max;
+ break;
+
+ default:
+ break;
+ }
+
+ return generic_server_update(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ NULL,
+ 0);
+}
+
+/***************************************************************************//**
+ * This function process the requests for the light lightness setup model.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void lightness_setup_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+ (void)transition_ms;
+ (void)delay_ms;
+
+ mesh_generic_state_t kind = mesh_generic_state_last;
+ switch (request->kind) {
+ case mesh_lighting_request_lightness_default:
+ kind = mesh_lighting_state_lightness_default;
+ log_info("lightness_setup_request: state=lightness_default, default_lightness=%u" NL,
+ request->lightness);
+
+ if (lightbulb_state.lightness_default == request->lightness) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Setting default lightness to <%u>" NL, request->lightness);
+ lightbulb_state.lightness_default = request->lightness;
+ lightbulb_state_changed();
+ }
+ break;
+
+ case mesh_lighting_request_lightness_range:
+ kind = mesh_lighting_state_lightness_range;
+ log_info("lightness_setup_request: state=lightness_range, min_lightness=%u, max_lightness=%u" NL,
+ request->lightness_range.min, request->lightness_range.max);
+
+ if ((lightbulb_state.lightness_min == request->lightness_range.min)
+ && (lightbulb_state.lightness_max == request->lightness_range.max)) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ if (lightbulb_state.lightness_min != request->lightness_range.min) {
+ log_info("Setting min lightness to <%u>" NL,
+ request->lightness_range.min);
+ lightbulb_state.lightness_min = request->lightness_range.min;
+ if (lightbulb_state.lightness_current < request->lightness_range.min
+ && lightbulb_state.lightness_current != 0) {
+ lightbulb_state.lightness_current = request->lightness_range.min;
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current,
+ IMMEDIATE);
+ }
+ }
+ if (lightbulb_state.lightness_max != request->lightness_range.max) {
+ log_info("Setting max lightness to <%u>" NL,
+ request->lightness_range.max);
+ lightbulb_state.lightness_max = request->lightness_range.max;
+ if (lightbulb_state.lightness_current > request->lightness_range.max) {
+ lightbulb_state.lightness_current = request->lightness_range.max;
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current,
+ IMMEDIATE);
+ }
+ }
+ lightbulb_state_changed();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ lightness_setup_response(element_index, client_addr, appkey_index, kind);
+ } else {
+ lightness_setup_update(element_index, kind);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for light lightness setup change event.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void lightness_setup_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ switch (current->kind) {
+ case mesh_lighting_state_lightness_default:
+ if (lightbulb_state.lightness_default != current->lightness.level) {
+ log_info("Default lightness update: from %u to %u" NL,
+ lightbulb_state.lightness_default,
+ current->lightness.level);
+ lightbulb_state.lightness_default = current->lightness.level;
+ lightbulb_state_changed();
+ } else {
+ log_info("Default lightness update -same value (%u)" NL,
+ lightbulb_state.lightness_default);
+ }
+ break;
+
+ case mesh_lighting_state_lightness_range:
+ if (lightbulb_state.lightness_min != current->lightness_range.min) {
+ log_info("Min lightness update: from %u to %u" NL,
+ lightbulb_state.lightness_min,
+ current->lightness_range.min);
+ lightbulb_state.lightness_min = current->lightness_range.min;
+ lightbulb_state_changed();
+ } else {
+ log_info("Min lightness update -same value (%u)" NL,
+ lightbulb_state.lightness_min);
+ }
+
+ if (lightbulb_state.lightness_max != current->lightness_range.max) {
+ log_info("Max lightness update: from %u to %u" NL,
+ lightbulb_state.lightness_max,
+ current->lightness_range.max);
+ lightbulb_state.lightness_max = current->lightness_range.max;
+ lightbulb_state_changed();
+ } else {
+ log_info("Max lightness update -same value (%u)" NL,
+ lightbulb_state.lightness_max);
+ }
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup LightLightnessSetup) */
+
+/***************************************************************************//**
+ * \defgroup PriGenericLevel
+ * \brief Generic Level Server model on primary element.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup PriGenericLevel
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Response to generic level request on primary element.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] appkey_index The application key index used in encrypting.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the response operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t pri_level_response(uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_level;
+ current.level.level = lightbulb_state.pri_level_current;
+
+ target.kind = mesh_generic_state_level;
+ target.level.level = lightbulb_state.pri_level_target;
+
+ return generic_server_respond(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ client_addr,
+ appkey_index,
+ ¤t,
+ &target,
+ remaining_ms,
+ 0x00);
+}
+
+/***************************************************************************//**
+ * Update generic level state on primary element.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t pri_level_update(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ struct mesh_generic_state current, target;
+
+ current.kind = mesh_generic_state_level;
+ current.level.level = lightbulb_state.pri_level_current;
+
+ target.kind = mesh_generic_state_level;
+ target.level.level = lightbulb_state.pri_level_target;
+
+ return generic_server_update(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ ¤t,
+ &target,
+ remaining_ms);
+}
+
+/***************************************************************************//**
+ * Update generic level state on primary element
+ * and publish model state to the network.
+ *
+ * @param[in] element_index Server model element index.
+ * @param[in] remaining_ms The remaining time in milliseconds.
+ *
+ * @return Status of the update and publish operation.
+ * Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t pri_level_update_and_publish(uint16_t element_index,
+ uint32_t remaining_ms)
+{
+ sl_status_t e;
+
+ e = pri_level_update(element_index, remaining_ms);
+ if (e == SL_STATUS_OK) {
+ e = generic_server_publish(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ element_index,
+ mesh_generic_state_level);
+ }
+
+ return e;
+}
+
+/***************************************************************************//**
+ * Schedule next generic level move request on primary element.
+ *
+ * @param[in] remaining_delta The remaining level delta to the target state.
+ ******************************************************************************/
+static void pri_level_move_schedule_next_request(int32_t remaining_delta)
+{
+ uint32_t transition_ms = 0;
+ if (abs(remaining_delta) < abs(move_pri_level_delta)) {
+ transition_ms = (uint32_t)(((int64_t)move_pri_level_trans * remaining_delta)
+ / move_pri_level_delta);
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ transition_ms);
+ } else {
+ transition_ms = move_pri_level_trans;
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current
+ + move_pri_level_delta,
+ move_pri_level_trans);
+ }
+ sl_status_t sc = sl_simple_timer_start(&lighting_pri_level_move_timer,
+ transition_ms,
+ lighting_pri_level_move_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Pri Level timer");
+}
+
+/***************************************************************************//**
+ * Handle generic level move request on primary element.
+ ******************************************************************************/
+static void pri_level_move_request(void)
+{
+ log_info("Primary level move: level %d -> %d, delta %d in %lu ms" NL,
+ lightbulb_state.pri_level_current,
+ lightbulb_state.pri_level_target,
+ move_pri_level_delta,
+ move_pri_level_trans);
+
+ int32_t remaining_delta = (int32_t)lightbulb_state.pri_level_target
+ - lightbulb_state.pri_level_current;
+
+ if (abs(remaining_delta) < abs(move_pri_level_delta)) {
+ // end of move level as it reached target state
+ lightbulb_state.pri_level_current = lightbulb_state.pri_level_target;
+ lightbulb_state.lightness_current = lightbulb_state.lightness_target;
+ } else {
+ lightbulb_state.pri_level_current += move_pri_level_delta;
+ lightbulb_state.lightness_current += move_pri_level_delta;
+ }
+ lightbulb_state_changed();
+ pri_level_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN,
+ UNKNOWN_REMAINING_TIME);
+
+ remaining_delta = (int32_t)lightbulb_state.pri_level_target
+ - lightbulb_state.pri_level_current;
+ if (remaining_delta != 0) {
+ pri_level_move_schedule_next_request(remaining_delta);
+ }
+}
+
+/***************************************************************************//**
+ * Stop generic level move on primary element.
+ ******************************************************************************/
+static void pri_level_move_stop(void)
+{
+ // Cancel timers
+ sl_status_t sc = sl_simple_timer_stop(&lighting_delayed_pri_level_timer);
+ app_assert_status_f(sc, "Failed to stop Delayed Primary Level timer");
+ sc = sl_simple_timer_stop(&lighting_pri_level_move_timer);
+ app_assert_status_f(sc, "Failed to stop Primary Level Move timer");
+ //Reset move parameters
+ move_pri_level_delta = 0;
+ move_pri_level_trans = 0;
+}
+
+/***************************************************************************//**
+ * This function process the requests for the generic level model
+ * on primary element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] client_addr Address of the client model which sent the message.
+ * @param[in] server_addr Address the message was sent to.
+ * @param[in] appkey_index The application key index used in encrypting the request.
+ * @param[in] request Pointer to the request structure.
+ * @param[in] transition_ms Requested transition time (in milliseconds).
+ * @param[in] delay_ms Delay time (in milliseconds).
+ * @param[in] request_flags Message flags. Bitmask of the following:
+ * - Bit 0: Nonrelayed. If nonzero indicates
+ * a response to a nonrelayed request.
+ * - Bit 1: Response required. If nonzero client
+ * expects a response from the server.
+ ******************************************************************************/
+static void pri_level_request(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t server_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_request *request,
+ uint32_t transition_ms,
+ uint16_t delay_ms,
+ uint8_t request_flags)
+{
+ (void)model_id;
+ (void)server_addr;
+
+ uint16_t lightness;
+ uint32_t remaining_ms = UNKNOWN_REMAINING_TIME;
+
+ switch (request->kind) {
+ case mesh_generic_request_level:
+ log_info("pri_level_request (generic): level=%d, transition=%lu, delay=%u" NL,
+ request->level, transition_ms, delay_ms);
+
+ pri_level_move_stop();
+ if (lightbulb_state.pri_level_current == request->level) {
+ log_info("Request for current state received; no op" NL);
+ lightbulb_state.pri_level_target = request->level;
+ } else {
+ log_info("Setting pri_level to <%d>" NL, request->level);
+
+ lightness = request->level + 32768;
+
+ if (transition_ms == 0 && delay_ms == 0) { // Immediate change
+ lightbulb_state.pri_level_current = request->level;
+ lightbulb_state.pri_level_target = request->level;
+ lightbulb_state.lightness_current = lightness;
+ lightbulb_state.lightness_target = lightness;
+
+ // update LED Level
+ sl_btmesh_lighting_set_level(lightness, IMMEDIATE);
+ } else if (delay_ms > 0) {
+ // a delay has been specified for the change. Start a soft timer
+ // that will trigger the change after the given delay
+ // Current state remains as is for now
+ lightbulb_state.pri_level_target = request->level;
+ lightbulb_state.lightness_target = lightness;
+ pri_level_request_kind = mesh_generic_request_level;
+ sl_status_t sc = sl_simple_timer_start(&lighting_delayed_pri_level_timer,
+ delay_ms,
+ lighting_delayed_pri_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Primary Level timer");
+
+ // store transition parameter for later use
+ delayed_pri_level_trans = transition_ms;
+ } else {
+ // no delay but transition time has been set.
+ lightbulb_state.pri_level_target = request->level;
+ lightbulb_state.lightness_target = lightness;
+ sl_btmesh_lighting_set_level(lightness, transition_ms);
+
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_level_transition_complete_timer,
+ transition_ms,
+ lighting_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Primary Level Transition Complete timer");
+ }
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+
+ remaining_ms = delay_ms + transition_ms;
+ break;
+
+ case mesh_generic_request_level_move: {
+ log_info("pri_level_request (move): delta=%d, transition=%lu, delay=%u" NL,
+ request->level, transition_ms, delay_ms);
+ // Store move parameters
+ move_pri_level_delta = request->level;
+ move_pri_level_trans = transition_ms;
+
+ int16_t requested_level = 0;
+ if (move_pri_level_delta > 0) {
+ requested_level = 0x7FFF; // Max level value
+ } else if (move_pri_level_delta < 0) {
+ requested_level = 0x8000; // Min level value
+ }
+
+ if (lightbulb_state.pri_level_current == requested_level) {
+ log_info("Request for current state received; no op" NL);
+ lightbulb_state.pri_level_target = requested_level;
+ remaining_ms = IMMEDIATE;
+ } else {
+ log_info("Setting pri_level to <%d>" NL, requested_level);
+
+ lightness = requested_level + 32768;
+
+ if (delay_ms > 0) {
+ // a delay has been specified for the move. Start a soft timer
+ // that will trigger the move after the given delay
+ // Current state remains as is for now
+ lightbulb_state.pri_level_target = requested_level;
+ lightbulb_state.lightness_target = lightness;
+ pri_level_request_kind = mesh_generic_request_level_move;
+ sl_status_t sc = sl_simple_timer_start(&lighting_delayed_pri_level_timer,
+ delay_ms,
+ lighting_delayed_pri_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Primary Level timer");
+ } else {
+ // no delay so start move
+ lightbulb_state.pri_level_target = requested_level;
+ lightbulb_state.lightness_target = lightness;
+
+ int32_t remaining_delta = (int32_t)lightbulb_state.pri_level_target
+ - lightbulb_state.pri_level_current;
+ pri_level_move_schedule_next_request(remaining_delta);
+ }
+ remaining_ms = UNKNOWN_REMAINING_TIME;
+
+ // State has changed, so the current scene number is reset
+ scene_server_reset_register(element_index);
+ }
+ break;
+ }
+
+ case mesh_generic_request_level_halt:
+ log_info("pri_level_request (halt)" NL);
+
+ // Set current state
+ lightbulb_state.lightness_current = sl_btmesh_get_level();
+ lightbulb_state.lightness_target = lightbulb_state.lightness_current;
+ lightbulb_state.pri_level_current = lightbulb_state.lightness_current
+ - 32768;
+ lightbulb_state.pri_level_target = lightbulb_state.pri_level_current;
+ if (delay_ms > 0) {
+ // a delay has been specified for the move halt. Start a soft timer
+ // that will trigger the move halt after the given delay
+ // Current state remains as is for now
+ remaining_ms = delay_ms;
+ pri_level_request_kind = mesh_generic_request_level_halt;
+ sl_status_t sc = sl_simple_timer_start(&lighting_delayed_pri_level_timer,
+ delay_ms,
+ lighting_delayed_pri_level_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Delayed Primary Level timer");
+ } else {
+ pri_level_move_stop();
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current,
+ IMMEDIATE);
+ remaining_ms = IMMEDIATE;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ lightbulb_state_changed();
+
+ if (request_flags & MESH_REQUEST_FLAG_RESPONSE_REQUIRED) {
+ pri_level_response(element_index, client_addr, appkey_index, remaining_ms);
+ }
+ pri_level_update_and_publish(element_index, remaining_ms);
+
+ // publish to bound states
+ generic_server_publish(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ element_index,
+ mesh_lighting_state_lightness_actual);
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic level change event
+ * on primary element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] remaining_ms Time (in milliseconds) remaining before transition
+ * from current state to target state is complete.
+ ******************************************************************************/
+static void pri_level_change(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ (void)model_id;
+ (void)element_index;
+ (void)target;
+ (void)remaining_ms;
+
+ if (lightbulb_state.pri_level_current != current->level.level) {
+ log_info("Primary level update: from %d to %d" NL,
+ lightbulb_state.pri_level_current,
+ current->level.level);
+ lightbulb_state.pri_level_current = current->level.level;
+ lightbulb_state_changed();
+ pri_level_move_stop();
+ } else {
+ log_info("Primary level update -same value (%d)" NL,
+ lightbulb_state.pri_level_current);
+ }
+}
+
+/***************************************************************************//**
+ * This function is a handler for generic level recall event on primary element.
+ *
+ * @param[in] model_id Server model ID.
+ * @param[in] element_index Server model element index.
+ * @param[in] current Pointer to current state structure.
+ * @param[in] target Pointer to target state structure.
+ * @param[in] transition_ms Transition time (in milliseconds).
+ ******************************************************************************/
+static void pri_level_recall(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t transition_ms)
+{
+ (void)model_id;
+
+ log_info("Primary Generic Level recall" NL);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.pri_level_target = current->level.level;
+ } else {
+ lightbulb_state.pri_level_target = target->level.level;
+ }
+
+ if (lightbulb_state.pri_level_current == lightbulb_state.pri_level_target) {
+ log_info("Request for current state received; no op" NL);
+ } else {
+ log_info("Recall pri_level to %d with transition=%lu ms" NL,
+ lightbulb_state.pri_level_target,
+ transition_ms);
+ if (transition_ms == IMMEDIATE) {
+ lightbulb_state.pri_level_current = current->level.level;
+ } else {
+ // lightbulb current state will be updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_level_transition_complete_timer,
+ transition_ms,
+ lighting_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Primary Level Transition Complete timer");
+ }
+ lightbulb_state_changed();
+ }
+
+ pri_level_update_and_publish(element_index, transition_ms);
+}
+
+/***************************************************************************//**
+ * This function is called when a generic level request on primary element
+ * with non-zero transition time has completed.
+ ******************************************************************************/
+static void pri_level_transition_complete(void)
+{
+ // transition done -> set state, update and publish
+ lightbulb_state.pri_level_current = lightbulb_state.pri_level_target;
+ lightbulb_state.lightness_current = lightbulb_state.lightness_target;
+
+ log_info("Transition complete. New pri_level is %d" NL,
+ lightbulb_state.pri_level_current);
+
+ lightbulb_state_changed();
+ pri_level_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN, IMMEDIATE);
+}
+
+/***************************************************************************//**
+ * This function is called when delay for generic level request
+ * on primary element has completed.
+ ******************************************************************************/
+static void delayed_pri_level_request(void)
+{
+ log_info("Starting delayed primary level request: level %d -> %d, %lu ms" NL,
+ lightbulb_state.pri_level_current,
+ lightbulb_state.pri_level_target,
+ delayed_pri_level_trans);
+
+ switch (pri_level_request_kind) {
+ case mesh_generic_request_level:
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ delayed_pri_level_trans);
+
+ if (delayed_pri_level_trans == 0) {
+ // no transition delay, update state immediately
+ lightbulb_state.pri_level_current = lightbulb_state.pri_level_target;
+ lightbulb_state.lightness_current = lightbulb_state.lightness_target;
+
+ lightbulb_state_changed();
+ pri_level_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN,
+ delayed_pri_level_trans);
+ } else {
+ // state is updated when transition is complete
+ sl_status_t sc = sl_simple_timer_start(&lighting_level_transition_complete_timer,
+ delayed_pri_level_trans,
+ lighting_level_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Primary Level Transition Complete timer");
+ }
+ break;
+
+ case mesh_generic_request_level_move:
+ pri_level_move_schedule_next_request((int32_t)lightbulb_state.pri_level_target
+ - lightbulb_state.pri_level_current);
+ pri_level_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN,
+ UNKNOWN_REMAINING_TIME);
+ break;
+
+ case mesh_generic_request_level_halt:
+ // Set current state
+ lightbulb_state.lightness_current = sl_btmesh_get_level();
+ lightbulb_state.lightness_target = lightbulb_state.lightness_current;
+ lightbulb_state.pri_level_current = lightbulb_state.lightness_current
+ - 32768;
+ lightbulb_state.pri_level_target = lightbulb_state.pri_level_current;
+ pri_level_move_stop();
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current,
+ IMMEDIATE);
+ pri_level_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN, IMMEDIATE);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup PriGenericLevel) */
+
+/***************************************************************************//**
+ * Initialization of the models supported by this node.
+ * This function registers callbacks for each of the supported models.
+ ******************************************************************************/
+static void init_models(void)
+{
+ generic_server_register_handler(MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ onoff_request,
+ onoff_change,
+ onoff_recall);
+
+ generic_server_register_handler(MESH_GENERIC_POWER_ON_OFF_SETUP_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ power_onoff_request,
+ power_onoff_change,
+ NULL);
+
+ generic_server_register_handler(MESH_GENERIC_TRANSITION_TIME_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ transtime_request,
+ transtime_change,
+ NULL);
+
+ generic_server_register_handler(MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ lightness_request,
+ lightness_change,
+ lightness_recall);
+
+ generic_server_register_handler(MESH_LIGHTING_LIGHTNESS_SETUP_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ lightness_setup_request,
+ lightness_setup_change,
+ NULL);
+
+ generic_server_register_handler(MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ BTMESH_LIGHTING_SERVER_MAIN,
+ pri_level_request,
+ pri_level_change,
+ pri_level_recall);
+}
+
+/***************************************************************************//**
+ * This function loads the saved light state from Persistent Storage and
+ * copies the data in the global variable lightbulb_state.
+ * If PS key with ID SL_BTMESH_LIGHTING_SERVER_PS_KEY_CFG_VAL does not exist or loading failed,
+ * lightbulb_state is set to zero and some default values are written to it.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightbulb_state_load(void)
+{
+ sl_status_t sc;
+ size_t ps_len = 0;
+ struct lightbulb_state ps_data;
+
+ sc = sl_bt_nvm_load(SL_BTMESH_LIGHTING_SERVER_PS_KEY_CFG_VAL,
+ sizeof(ps_data),
+ &ps_len,
+ (uint8_t *)&ps_data);
+
+ // Set default values if ps_load fail or size of lightbulb_state has changed
+ if ((sc != SL_STATUS_OK) || (ps_len != sizeof(struct lightbulb_state))) {
+ memset(&lightbulb_state, 0, sizeof(struct lightbulb_state));
+ lightbulb_state.lightness_last = LIGHTNESS_LAST_DEFAULT;
+ lightbulb_state.lightness_default = LIGHTNESS_DEFAULT_DEFAULT;
+ lightbulb_state.lightness_min = SL_BTMESH_LIGHTING_SERVER_LIGHTNESS_MIN_CFG_VAL;
+ lightbulb_state.lightness_max = SL_BTMESH_LIGHTING_SERVER_LIGHTNESS_MAX_CFG_VAL;
+
+ // Check if default values are valid and correct them if needed
+ lightbulb_state_validate_and_correct();
+
+ if (sc == SL_STATUS_OK) {
+ // The sl_bt_nvm_load call was successful but the size of the loaded data
+ // differs from the expected size therefore error code shall be set
+ sc = SL_STATUS_INVALID_STATE;
+ log_error("Lighting server lightbulb state loaded from PS with invalid size, "
+ "use defaults. (expected=%zd,actual=%zd)" NL,
+ sizeof(struct lightbulb_state),
+ ps_len);
+ } else {
+ log_status_error_f(sc,
+ "Lighting server lightbulb state load from PS failed "
+ "or nvm is empty, use defaults." NL);
+ }
+ } else {
+ memcpy(&lightbulb_state, &ps_data, ps_len);
+ }
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function validates the lighbulb_state and change it if it is against
+ * the specification.
+ ******************************************************************************/
+static void lightbulb_state_validate_and_correct(void)
+{
+ if (lightbulb_state.lightness_min > lightbulb_state.lightness_max) {
+ lightbulb_state.lightness_min = lightbulb_state.lightness_max;
+ }
+ if (lightbulb_state.lightness_default) {
+ if (lightbulb_state.lightness_default < lightbulb_state.lightness_min) {
+ lightbulb_state.lightness_default = lightbulb_state.lightness_min;
+ }
+ if (lightbulb_state.lightness_default > lightbulb_state.lightness_max) {
+ lightbulb_state.lightness_default = lightbulb_state.lightness_max;
+ }
+ }
+ if (lightbulb_state.lightness_current < lightbulb_state.lightness_min) {
+ lightbulb_state.lightness_current = lightbulb_state.lightness_min;
+ }
+ if (lightbulb_state.lightness_current > lightbulb_state.lightness_max) {
+ lightbulb_state.lightness_current = lightbulb_state.lightness_max;
+ }
+ if (lightbulb_state.lightness_target < lightbulb_state.lightness_min) {
+ lightbulb_state.lightness_target = lightbulb_state.lightness_min;
+ }
+ if (lightbulb_state.lightness_target > lightbulb_state.lightness_max) {
+ lightbulb_state.lightness_target = lightbulb_state.lightness_max;
+ }
+}
+
+/***************************************************************************//**
+ * This function saves the current light state in Persistent Storage so that
+ * the data is preserved over reboots and power cycles.
+ * The light state is hold in a global variable lightbulb_state.
+ * A PS key with ID SL_BTMESH_LIGHTING_SERVER_PS_KEY_CFG_VAL is used to store the whole struct.
+ *
+ * @return Returns SL_STATUS_OK (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+static sl_status_t lightbulb_state_store(void)
+{
+ sl_status_t sc;
+
+ sc = sl_bt_nvm_save(SL_BTMESH_LIGHTING_SERVER_PS_KEY_CFG_VAL,
+ sizeof(struct lightbulb_state),
+ (const uint8_t*)&lightbulb_state);
+
+ log_status_error_f(sc,
+ "Lighting server lightbulb state store in PS failed." NL);
+
+ return sc;
+}
+
+/***************************************************************************//**
+ * This function is called each time the lightbulb state in RAM is changed.
+ * It sets up a soft timer that will save the state in flash after small delay.
+ * The purpose is to reduce amount of unnecessary flash writes.
+ ******************************************************************************/
+static void lightbulb_state_changed(void)
+{
+ sl_status_t sc = sl_simple_timer_start(&lighting_state_store_timer,
+ SL_BTMESH_LIGHTING_SERVER_NVM_SAVE_TIME_CFG_VAL,
+ lighting_state_store_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start State Store timer");
+}
+
+/*******************************************************************************
+ * Lightbulb state initialization.
+ * This is called at each boot if provisioning is already done.
+ * Otherwise this function is called after provisioning is completed.
+ ******************************************************************************/
+void sl_btmesh_lighting_server_init(void)
+{
+ memset(&lightbulb_state, 0, sizeof(struct lightbulb_state));
+
+ lightbulb_state_load();
+
+ // Handle on power up behavior
+ uint32_t transition_ms = sl_btmesh_get_default_transition_time();
+ switch (lightbulb_state.onpowerup) {
+ case MESH_GENERIC_ON_POWER_UP_STATE_OFF:
+ log_info("On power up state is OFF" NL);
+ lightbulb_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_OFF;
+ lightbulb_state.onoff_target = MESH_GENERIC_ON_OFF_STATE_OFF;
+ lightbulb_state.lightness_current = 0;
+ lightbulb_state.lightness_target = 0;
+ sl_btmesh_set_state(LED_STATE_OFF);
+ break;
+
+ case MESH_GENERIC_ON_POWER_UP_STATE_ON:
+ log_info("On power up state is ON" NL);
+ lightbulb_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_ON;
+ lightbulb_state.onoff_target = MESH_GENERIC_ON_OFF_STATE_ON;
+ if (lightbulb_state.lightness_default == 0) {
+ lightbulb_state.lightness_current = lightbulb_state.lightness_last;
+ lightbulb_state.lightness_target = lightbulb_state.lightness_last;
+ } else {
+ lightbulb_state.lightness_current = lightbulb_state.lightness_default;
+ lightbulb_state.lightness_target = lightbulb_state.lightness_default;
+ }
+ if (transition_ms > 0) {
+ lightbulb_state.lightness_current = 0;
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current,
+ IMMEDIATE);
+ sl_status_t sc =
+ sl_simple_timer_start(&lighting_transition_complete_timer,
+ transition_ms,
+ lighting_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Lighting Transition Complete timer");
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ transition_ms);
+ } else {
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ IMMEDIATE);
+ }
+ break;
+
+ case MESH_GENERIC_ON_POWER_UP_STATE_RESTORE:
+ log_info("On power up state is RESTORE" NL);
+#ifdef SL_CATALOG_BTMESH_LC_SERVER_PRESENT
+ if (lc_get_mode() == 0)
+#endif
+ {
+ if (transition_ms > 0 && lightbulb_state.lightness_target > 0) {
+ lightbulb_state.lightness_current = 0;
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current,
+ IMMEDIATE);
+ sl_status_t sc =
+ sl_simple_timer_start(&lighting_transition_complete_timer,
+ transition_ms,
+ lighting_transition_complete_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start Lighting Transition Complete timer");
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_target,
+ transition_ms);
+ } else {
+ lightbulb_state.lightness_current = lightbulb_state.lightness_target;
+ sl_btmesh_lighting_set_level(lightbulb_state.lightness_current,
+ IMMEDIATE);
+ }
+
+ if (lightbulb_state.lightness_current) {
+ lightbulb_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_ON;
+ } else {
+ lightbulb_state.onoff_current = MESH_GENERIC_ON_OFF_STATE_OFF;
+ }
+
+ if (lightbulb_state.lightness_target) {
+ lightbulb_state.onoff_target = MESH_GENERIC_ON_OFF_STATE_ON;
+ } else {
+ lightbulb_state.onoff_target = MESH_GENERIC_ON_OFF_STATE_OFF;
+ }
+ }
+
+ break;
+
+ default:
+ break;
+ }
+
+ lightbulb_state_changed();
+ init_models();
+ transtime_update(BTMESH_LIGHTING_SERVER_MAIN);
+ lightness_setup_update(BTMESH_LIGHTING_SERVER_MAIN,
+ mesh_lighting_state_lightness_default);
+
+ lightness_setup_update(BTMESH_LIGHTING_SERVER_MAIN,
+ mesh_lighting_state_lightness_range);
+
+ power_onoff_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN);
+
+#ifdef SL_CATALOG_BTMESH_LC_PRESENT
+ if (lc_get_mode() == 0)
+#endif
+ {
+ onoff_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN,
+ IMMEDIATE);
+
+ lightness_update_and_publish(BTMESH_LIGHTING_SERVER_MAIN,
+ IMMEDIATE,
+ mesh_lighting_state_lightness_actual);
+ }
+}
+
+/***************************************************************************//**
+ * @addtogroup BtmeshWrappers
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_respond to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_respond(uint16_t model_id,
+ uint16_t element_index,
+ uint16_t client_addr,
+ uint16_t appkey_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms,
+ uint8_t response_flags)
+{
+ sl_status_t sc = mesh_lib_generic_server_respond(model_id,
+ element_index,
+ client_addr,
+ appkey_index,
+ current,
+ target,
+ remaining_ms,
+ response_flags);
+
+ log_status_error_f(sc,
+ "Lighting server respond failed "
+ "(claddr=0x%04x,mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ client_addr,
+ model_id,
+ element_index,
+ current->kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_update to log if the Btmesh API call
+ * results in error. The parameters and the return value of the wrapper and
+ * the wrapped functions are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_update(uint16_t model_id,
+ uint16_t element_index,
+ const struct mesh_generic_state *current,
+ const struct mesh_generic_state *target,
+ uint32_t remaining_ms)
+{
+ sl_status_t sc = mesh_lib_generic_server_update(model_id,
+ element_index,
+ current,
+ target,
+ remaining_ms);
+
+ log_status_error_f(sc,
+ "Lighting server state update failed "
+ "(mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ model_id,
+ element_index,
+ current->kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_publish to log if the Btmesh API call
+ * results in error. The parameters and the return value of the two functions
+ * are the same.
+ ******************************************************************************/
+static sl_status_t generic_server_publish(uint16_t model_id,
+ uint16_t element_index,
+ mesh_generic_state_t kind)
+{
+ sl_status_t sc;
+
+ sc = mesh_lib_generic_server_publish(model_id, element_index, kind);
+
+ log_btmesh_status_f(sc,
+ "Lighting server state publish failed "
+ "(mdl=0x%04x,elem=%d,state=0x%04x)" NL,
+ model_id,
+ element_index,
+ kind);
+ return sc;
+}
+
+/***************************************************************************//**
+ * Wrapper for mesh_lib_generic_server_register_handler with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ ******************************************************************************/
+static void generic_server_register_handler(uint16_t model_id,
+ uint16_t elem_index,
+ mesh_lib_generic_server_client_request_cb cb,
+ mesh_lib_generic_server_change_cb ch,
+ mesh_lib_generic_server_recall_cb recall)
+{
+ sl_status_t sc =
+ mesh_lib_generic_server_register_handler(model_id, elem_index, cb, ch, recall);
+
+ app_assert_status_f(sc,
+ "Lighting server failed to register handlers "
+ "(mdl=0x%04x,elem=%d)",
+ model_id,
+ elem_index);
+}
+
+#ifdef SL_CATALOG_BTMESH_SCENE_SERVER_PRESENT
+/***************************************************************************//**
+ * Wrapper for sl_btmesh_scene_server_reset_register with an assert which
+ * detects if the Btmesh API call results in error. The parameters of the two
+ * functions are the same but the wrapper does not have return value.
+ * The scene server register shall be reset if the state of the model changes in
+ * order to clear the current scene.
+ * This function is available only if the btmesh_scene_server component is added
+ * to the project.
+ ******************************************************************************/
+static void scene_server_reset_register_impl(uint16_t elem_index)
+{
+ sl_status_t sc = sl_btmesh_scene_server_reset_register(elem_index);
+
+ // The function can fail if there is no scene server model in the element or
+ // the btmesh_stack_scene_server component is not present. Both of these
+ // are configuration issues so assert can be used.
+ app_assert_status_f(sc, "Lighting server failed to reset scene register.");
+}
+#endif
+
+/** @} (end addtogroup BtmeshWrappers) */
+
+// -----------------------------------------------------------------------------
+// Timer Callbacks
+
+/***************************************************************************//**
+ * Callback for the timer handling generic level move
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to the timer data
+ ******************************************************************************/
+static void lighting_pri_level_move_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // handling of generic level move, update the lightbulb state
+ pri_level_move_request();
+}
+
+/***************************************************************************//**
+ * Callback for the timer handling lightness request transition
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to the timer data
+ ******************************************************************************/
+static void lighting_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a lightness request has completed
+ // update the lightbulb state
+ lightness_transition_complete();
+}
+
+/***************************************************************************//**
+ * Callback for the timer handling primary generic level request transition
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to the timer data
+ ******************************************************************************/
+static void lighting_level_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for a primary generic level request has completed,
+ // update the lightbulb state
+ pri_level_transition_complete();
+}
+
+/***************************************************************************//**
+ * Callback for the timer handling on/off request transition
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to the timer data
+ ******************************************************************************/
+static void lighting_onoff_transition_complete_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // transition for an on/off request has completed,
+ // update the lightbulb state
+ onoff_transition_complete();
+}
+
+/***************************************************************************//**
+ * Callback for the timer handling delayed primary generic level request
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to the timer data
+ ******************************************************************************/
+static void lighting_delayed_pri_level_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a primary generic level request has passed,
+ // now process the request
+ delayed_pri_level_request();
+}
+
+/***************************************************************************//**
+ * Callback for the timer handling delayed lightness request
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to the timer data
+ ******************************************************************************/
+static void lighting_delayed_lightness_request_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for a lightness request has passed, now process the request
+ delayed_lightness_request();
+}
+
+/***************************************************************************//**
+ * Callback for the timer handling delayed on/off request
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to the timer data
+ ******************************************************************************/
+static void lighting_delayed_onoff_request_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // delay for an on/off request has passed, now process the request
+ delayed_onoff_request();
+}
+
+/***************************************************************************//**
+ * Callback for the timer handling storage state change
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to the timer data
+ ******************************************************************************/
+static void lighting_state_store_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ // save the lightbulb state
+ lightbulb_state_store();
+}
+
+/** @} (end addtogroup Lighting Server) */
diff --git a/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_server.h b/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_server.h
new file mode 100644
index 00000000000..9a5ec2fe6ce
--- /dev/null
+++ b/app/btmesh/common/btmesh_lighting_server/sl_btmesh_lighting_server.h
@@ -0,0 +1,120 @@
+/***************************************************************************//**
+ * @file
+ * @brief btmesh_lighting_server.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_LIGHTING_SERVER_H
+#define SL_BTMESH_LIGHTING_SERVER_H
+
+#include "sl_btmesh_lighting_level_transition_handler.h"
+
+/***************************************************************************//**
+ * Lighting Server initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @return Status of the initialization operation.
+ * Returns bg_err_success (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+void sl_btmesh_lighting_server_init(void);
+
+/***************************************************************************//**
+ * Handling of mesh lighting server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_lighting_server_on_event(sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * Get current lightness value
+ *
+ * @return Current lightness
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness_current(void);
+
+/***************************************************************************//**
+ * Set current lightness value
+ *
+ * @param[in] lightness Current lightness
+ ******************************************************************************/
+void sl_btmesh_set_lightness_current(uint16_t lightness);
+
+/***************************************************************************//**
+ * Get target lightness value
+ *
+ * @return Target lightness
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness_target(void);
+
+/***************************************************************************//**
+ * Set target lightness value
+ *
+ * @param[in] lightness Target lightness
+ ******************************************************************************/
+void sl_btmesh_set_lightness_target(uint16_t lightness);
+
+/***************************************************************************//**
+ * Get default lightness value
+ *
+ * @return Default lightness
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness_default(void);
+
+/***************************************************************************//**
+ * Set default lightness value
+ *
+ * @param[in] lightness Default lightness
+ ******************************************************************************/
+void sl_btmesh_set_lightness_default(uint16_t lightness);
+
+/***************************************************************************//**
+ * Set last lightness value
+ *
+ * @param[in] lightness Last lightness
+ ******************************************************************************/
+void sl_btmesh_set_lightness_last(uint16_t lightness);
+
+/***************************************************************************//**
+ * This function convert mesh format of default transition time to milliseconds.
+ *
+ * @return Default transition time in milliseconds.
+ ******************************************************************************/
+uint32_t sl_btmesh_get_default_transition_time(void);
+
+/***************************************************************************//**
+ * Get default lightness value on power up
+ *
+ * @return Default lightness on power up
+ ******************************************************************************/
+uint16_t sl_btmesh_get_lightness_onpowerup(void);
+
+#endif // SL_BTMESH_LIGHTING_SERVER_H
diff --git a/app/btmesh/common/btmesh_lpn/config/sl_btmesh_lpn_config.h b/app/btmesh/common/btmesh_lpn/config/sl_btmesh_lpn_config.h
new file mode 100644
index 00000000000..602a09e69a5
--- /dev/null
+++ b/app/btmesh/common/btmesh_lpn/config/sl_btmesh_lpn_config.h
@@ -0,0 +1,112 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_LPN_CONFIG_H
+#define SL_BTMESH_LPN_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// LPN configuration
+
+// Minimum queue length the friend must support <2-128>
+// Default: 4
+// Minimum queue length the friend must support. Choose an appropriate value based on the expected message
+// frequency and LPN sleep period, because new messages push out the least recent messages from the friend queue.
+// Note that the given value is rounded up to the nearest power of 2
+#define SL_BTMESH_LPN_MIN_QUEUE_LENGTH_CFG_VAL (4)
+
+// Poll timeout in milliseconds <1000-345599900:100>
+// Default: 5000
+// Poll timeout in milliseconds, which is the longest time that LPN sleeps in between querying its friend
+// for queued messages. Long poll timeout allows the LPN to sleep for longer periods, at the expense of increased
+// latency for receiving messages. Note that the given value is rounded up to the nearest 100 ms
+#define SL_BTMESH_LPN_POLL_TIMEOUT_CFG_VAL (5000)
+
+// Receive delay in milliseconds <10-255>
+// Default: 50
+// Receive delay in milliseconds. Receive delay is the time between the LPN sending a request and listening
+// for a response. Receive delay allows the friend node time to prepare the message and LPN to sleep
+#define SL_BTMESH_LPN_RECEIVE_DELAY_CFG_VAL (50)
+
+// The number of retry attempts to repeat <0-10>
+// Default: 8
+// Request retry is the number of retry attempts to repeat e.g., the friend poll message
+// if the friend update was not received by the LPN
+#define SL_BTMESH_LPN_REQUEST_RETRIES_CFG_VAL (8)
+
+// Time interval between retry attempts in milliseconds <0-100>
+// Default: 100
+// Time interval between retry attempts in milliseconds
+#define SL_BTMESH_LPN_RETRY_INTERVAL_CFG_VAL (100)
+
+// Initialization timeouts
+
+// Timeout for initializing LPN after an already provisioned Node is initialized
+// Default: 30000
+// Timeout for initializing LPN after an already provisioned Node is initialized. It can delay friend
+// establishment to wait for possible Configuration Messages
+#define SL_BTMESH_LPN_TIMEOUT_AFTER_PROVISIONED_CFG_VAL (30000)
+
+// Timeout for initializing LPN after Security Key was added
+// Default: 5000
+// Timeout for initializing LPN after Security Key was added. It can delay friend establishment
+// to wait for possible other Configuration Messages
+#define SL_BTMESH_LPN_TIMEOUT_AFTER_KEY_CFG_VAL (5000)
+
+// Timeout for initializing LPN after the Configuration Model changed
+// Default: 5000
+// Timeout for initializing LPN after the Configuration Model changed. It can delay friend establishment
+// to wait for possible other Configuration Messages
+#define SL_BTMESH_LPN_TIMEOUT_AFTER_CONFIG_MODEL_CHANGED_CFG_VAL (5000)
+
+// Timeout for initializing LPN after the Configuration Model Set Message
+// Default: 5000
+// Timeout for initializing LPN after the Configuration Model Set Message. It can delay friend establishment
+// to wait for possible other Configuration Messages
+#define SL_BTMESH_LPN_TIMEOUT_AFTER_CONFIG_SET_CFG_VAL (5000)
+
+//
+
+// Timeout between retries to find a friend
+// Default: 2000
+// Timeout between retries to find a friend
+#define SL_BTMESH_LPN_FRIEND_FIND_TIMEOUT_CFG_VAL (2000)
+
+// Enable Logging
+// Default: 1
+// Enable or disable Logging for LPN specific messages for this component.
+#define SL_BTMESH_LPN_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_LPN_CONFIG_H
diff --git a/app/btmesh/common/btmesh_lpn/sl_btmesh_lpn.c b/app/btmesh/common/btmesh_lpn/sl_btmesh_lpn.c
new file mode 100644
index 00000000000..11a4b815b46
--- /dev/null
+++ b/app/btmesh/common/btmesh_lpn/sl_btmesh_lpn.c
@@ -0,0 +1,562 @@
+/***************************************************************************//**
+ * @file
+ * @brief Low Power Node implementation
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_config.h"
+
+#include
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_lpn.h"
+#include "sl_btmesh_lpn_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup lpn
+ * @{
+ ******************************************************************************/
+
+/// High Priority
+#define HIGH_PRIORITY 0
+/// No Timer Options
+#define NO_FLAGS 0
+/// Callback has not parameters
+#define NO_CALLBACK_DATA (void *)NULL
+
+/// Friend finding timer handler
+static sl_simple_timer_t lpn_friend_find_timer;
+/// Node configuration timer handler
+static sl_simple_timer_t lpn_node_configured_timer;
+/// High throughput timer handler
+static sl_simple_timer_t lpn_high_throughput_timer;
+
+/// High throughput timing list head
+static sl_btmesh_lpn_high_throughput_timer_t *lpn_high_throughput_head;
+
+/// Friend finding timer callback
+static void lpn_friend_find_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+/// Node configuration timer callback
+static void lpn_node_configured_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+/// High throughput timer callback
+static void lpn_high_throughput_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+/// Flag for indicating that lpn feature is active
+static uint8_t lpn_active = 0;
+
+/// Number of active proxy connections
+static uint8_t num_mesh_proxy_conn = 0;
+
+/// Address of the friend node if the friendship is active otherwise it is zero
+static uint16_t friend_address = 0;
+
+// Stores the netkey index of the network which the low power node belongs to
+static uint16_t lpn_friend_netkey_idx = 0;
+
+static void lpn_establish_friendship(void);
+
+static void set_configuration_timer(uint32_t delay);
+
+/***************************************************************************//**
+ * Initialize LPN functionality with configuration and friendship establishment.
+ ******************************************************************************/
+void sl_btmesh_lpn_feature_init(void)
+{
+ sl_status_t result = SL_STATUS_OK;
+ size_t netkey_bytes_written;
+
+ // The sl_btmesh_node_get_networks API does not copy any netkey indexes if the
+ // buffer is not long enough for all netkey indexes. It is expected that only
+ // one netkey is present in the device but this array is allocated to provide
+ // space for SL_BTMESH_CONFIG_MAX_NETKEYS netkey indexes to be on the safe
+ // side.
+ uint8_t netkey_bytes[SL_BTMESH_CONFIG_MAX_NETKEYS * 2];
+
+ // Do not initialize LPN if lpn is currently active
+ // or any GATT proxy connection is opened
+ if (lpn_active || num_mesh_proxy_conn) {
+ return;
+ }
+
+ // Initialize LPN functionality.
+ result = sl_btmesh_lpn_init();
+ if (result != SL_STATUS_OK) {
+ log_status_error_f(result,
+ "LPN initialization failed" NL);
+ return;
+ }
+ lpn_active = 1;
+ log_info("LPN initialized" NL);
+ sl_btmesh_lpn_on_init();
+
+ // Configure LPN minimum friend queue length
+ result = sl_btmesh_lpn_config(sl_btmesh_lpn_queue_length,
+ SL_BTMESH_LPN_MIN_QUEUE_LENGTH_CFG_VAL);
+ if (result != SL_STATUS_OK) {
+ log_status_error_f(result,
+ "LPN queue configuration failed" NL);
+ return;
+ }
+ // Configure LPN poll timeout
+ result = sl_btmesh_lpn_config(sl_btmesh_lpn_poll_timeout,
+ SL_BTMESH_LPN_POLL_TIMEOUT_CFG_VAL);
+ if (result != SL_STATUS_OK) {
+ log_status_error_f(result,
+ "LPN poll timeout configuration failed" NL);
+ return;
+ }
+ // Configure LPN receive delay
+ result = sl_btmesh_lpn_config(sl_btmesh_lpn_receive_delay,
+ SL_BTMESH_LPN_RECEIVE_DELAY_CFG_VAL);
+ if (result != SL_STATUS_OK) {
+ log_status_error_f(result,
+ "LPN receive delay configuration failed" NL);
+ return;
+ }
+ // Configure LPN request retries
+ result = sl_btmesh_lpn_config(sl_btmesh_lpn_request_retries,
+ SL_BTMESH_LPN_REQUEST_RETRIES_CFG_VAL);
+ if (result != SL_STATUS_OK) {
+ log_status_error_f(result,
+ "LPN request retries configuration failed" NL);
+ return;
+ }
+ // Configure LPN retry interval
+ result = sl_btmesh_lpn_config(sl_btmesh_lpn_retry_interval,
+ SL_BTMESH_LPN_RETRY_INTERVAL_CFG_VAL);
+ if (result != SL_STATUS_OK) {
+ log_status_error_f(result,
+ "LPN retry interval configuration failed" NL);
+ return;
+ }
+
+ // It is necessary to determine the netkey index because it is assigned by the
+ // provisioner device and it can't be assumed that it is always 0 or other
+ // constant value. If the node is part of multiple networks then it tries to
+ // establish friendship with the first netkey in the list.
+ result = sl_btmesh_node_get_networks(sizeof(netkey_bytes),
+ &netkey_bytes_written,
+ &netkey_bytes[0]);
+
+ if (result != SL_STATUS_OK) {
+ log_status_error_f(result,
+ "LPN get networks request failed" NL);
+ } else if (netkey_bytes_written < 2) {
+ // Defensive programming: this should not occur because at this point the
+ // node is provisioned therefore at least one netkey shall be saved in the
+ // node
+ log_error("LPN get networks provided invalid number of netkey bytes (0x%zu)" NL,
+ netkey_bytes_written);
+ } else {
+ // The get networks API provides the netkeys in little endian format
+ lpn_friend_netkey_idx = (netkey_bytes[1] << 8) | netkey_bytes[0];
+
+ // Establish friendship with the lpn_friend_netkey_idx network key
+ // The lpn_establish_friendship function uses global variable to identify
+ // the network index because it does not change after provisioning but the
+ // this function is called after a proxy connection is closed too and the
+ // sl_btmesh_lpn_terminate_friendship API needs the netkey index as well so
+ // the netkey index shall be stored.
+ lpn_establish_friendship();
+ }
+
+ // Set initial head value to NULL
+ lpn_high_throughput_head = NULL;
+}
+
+/***************************************************************************//**
+ * Deinitialize LPN functionality.
+ ******************************************************************************/
+void sl_btmesh_lpn_feature_deinit(void)
+{
+ sl_status_t result = 0;
+
+ if (!lpn_active) {
+ return; // lpn feature is currently inactive
+ }
+
+ // Cancel friend finding timer
+ sl_status_t sc = sl_simple_timer_stop(&lpn_friend_find_timer);
+ app_assert_status_f(sc, "Failed to stop timer");
+
+ // Terminate friendship if exist
+ result = sl_btmesh_lpn_terminate_friendship(lpn_friend_netkey_idx);
+ log_status_error_f(result, "Friendship termination failed" NL);
+ // turn off lpn feature
+ result = sl_btmesh_lpn_deinit();
+ log_status_error_f(result, "LPN deinit failed" NL);
+
+ lpn_active = 0;
+ friend_address = 0;
+ log_info("LPN deinitialized" NL);
+ sl_btmesh_lpn_on_deinit();
+}
+
+/*******************************************************************************
+* Return if the friendship is active between LPN and friend node
+*******************************************************************************/
+bool sl_btmesh_lpn_is_friendship_active(void)
+{
+ return (0 != friend_address);
+}
+
+/*******************************************************************************
+* Poll the Friend node for stored messages and security updates
+*******************************************************************************/
+sl_status_t sl_btmesh_lpn_poll_request(void)
+{
+ return sl_btmesh_lpn_poll(lpn_friend_netkey_idx);
+}
+
+/*******************************************************************************
+ * Handling of mesh lpn events.
+ * It handles:
+ * - lpn_friendship_established_id
+ * - lpn_friendship_failed_id
+ * - lpn_friendship_terminated_id
+ *
+ * @param[in] evt Pointer to incoming lpn event.
+ ******************************************************************************/
+void sl_btmesh_lpn_on_event(sl_btmesh_msg_t* evt)
+{
+ sl_btmesh_evt_node_initialized_t *data;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ data = (sl_btmesh_evt_node_initialized_t *)&(evt->data);
+ if (data->provisioned) {
+ sl_btmesh_lpn_feature_init();
+ }
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ set_configuration_timer(SL_BTMESH_LPN_TIMEOUT_AFTER_PROVISIONED_CFG_VAL);
+ break;
+
+ case sl_btmesh_evt_node_model_config_changed_id:
+ set_configuration_timer(SL_BTMESH_LPN_TIMEOUT_AFTER_CONFIG_MODEL_CHANGED_CFG_VAL);
+ break;
+
+ case sl_btmesh_evt_node_config_set_id:
+ set_configuration_timer(SL_BTMESH_LPN_TIMEOUT_AFTER_CONFIG_SET_CFG_VAL);
+ break;
+
+ case sl_btmesh_evt_node_key_added_id:
+ set_configuration_timer(SL_BTMESH_LPN_TIMEOUT_AFTER_KEY_CFG_VAL);
+ break;
+
+ case sl_btmesh_evt_lpn_friendship_established_id:
+ friend_address = evt->data.evt_lpn_friendship_established.friend_address;
+ sl_btmesh_lpn_on_friendship_established(friend_address);
+ break;
+
+ case sl_btmesh_evt_lpn_friendship_failed_id: {
+ sl_btmesh_lpn_on_friendship_failed(
+ evt->data.evt_lpn_friendship_failed.reason);
+
+ // try again after timer expires
+ sl_status_t sc = sl_simple_timer_start(&lpn_friend_find_timer,
+ SL_BTMESH_LPN_FRIEND_FIND_TIMEOUT_CFG_VAL,
+ lpn_friend_find_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start timer");
+
+ break;
+ }
+
+ case sl_btmesh_evt_lpn_friendship_terminated_id:
+ sl_btmesh_lpn_on_friendship_terminated(
+ evt->data.evt_lpn_friendship_terminated.reason);
+ friend_address = 0;
+ if (num_mesh_proxy_conn == 0) {
+ // try again after timer expires
+ sl_status_t sc = sl_simple_timer_start(&lpn_friend_find_timer,
+ SL_BTMESH_LPN_FRIEND_FIND_TIMEOUT_CFG_VAL,
+ lpn_friend_find_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start timer");
+ }
+ break;
+
+ /* Proxy Events*/
+ case sl_btmesh_evt_proxy_connected_id:
+ num_mesh_proxy_conn++;
+ // turn off lpn feature after GATT proxy connection is opened
+ sl_btmesh_lpn_feature_deinit();
+ break;
+
+ case sl_btmesh_evt_proxy_disconnected_id:
+ if (num_mesh_proxy_conn > 0) {
+ if (--num_mesh_proxy_conn == 0) {
+ // Initialize lpn when there is no active proxy connection
+ sl_btmesh_lpn_feature_init();
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+sl_status_t sl_btmesh_lpn_high_throughput_register(sl_btmesh_lpn_high_throughput_timer_t *handle,
+ uint32_t timeout,
+ sl_btmesh_lpn_high_throughput_operation_mode_t mode)
+{
+ sl_status_t retval = SL_STATUS_OK;
+
+ handle->timeout = timeout;
+ handle->mode = mode;
+ if (lpn_high_throughput_head == NULL) {
+ // If head is not yet set, set this as the head
+ lpn_high_throughput_head = handle;
+ // Since head was not set, the timer is turned off as well
+ sl_status_t sc = sl_simple_timer_start(&lpn_high_throughput_timer,
+ handle->timeout,
+ lpn_high_throughput_timer_cb,
+ NULL,
+ false);
+ log_status_error_f(sc, "Failed to start timer" NL);
+ } else if (lpn_high_throughput_head == handle) {
+ // If only one element is registered, the loop below would not detect it
+ retval = SL_STATUS_ALREADY_EXISTS;
+ } else {
+ sl_btmesh_lpn_high_throughput_timer_t *tmp;
+ for (tmp = lpn_high_throughput_head; tmp->next != NULL; tmp = tmp->next) {
+ if (handle == tmp) {
+ retval = SL_STATUS_ALREADY_EXISTS;
+ }
+ }
+ // In case the last element is the one given
+ if (handle == tmp) {
+ retval = SL_STATUS_ALREADY_EXISTS;
+ }
+ // SL_STATUS_OK indicates that no match has been found; append to the end
+ if (SL_STATUS_OK == retval) {
+ // Put handle at the end of the list
+ tmp->next = handle;
+ }
+ }
+ // SL_STATUS_OK indicates successful append, i.e. handle is at the end of list
+ if (SL_STATUS_OK == retval) {
+ // Indicate list end
+ handle->next = NULL;
+ }
+
+ // If the registered handler requests a faster timer that's already running,
+ // restart timer with the faster.
+ // This might also be the case for re-registering a slowing timer as well.
+ if (lpn_high_throughput_timer.timeout_ms > handle->timeout) {
+ sl_status_t sc = sl_simple_timer_start(&lpn_high_throughput_timer,
+ handle->timeout,
+ lpn_high_throughput_timer_cb,
+ NULL,
+ false);
+ log_status_error_f(sc, "Failed to start timer" NL);
+ }
+ return retval;
+}
+
+sl_status_t sl_btmesh_lpn_high_throughput_unregister(sl_btmesh_lpn_high_throughput_timer_t *handle)
+{
+ if (lpn_high_throughput_head == NULL) {
+ // If no head, nothing is to be unregistered
+ return SL_STATUS_NOT_FOUND;
+ } else if (lpn_high_throughput_head == handle) {
+ // Special case for the head being unregistered
+ // This also works if only one element have been registered
+ lpn_high_throughput_head = lpn_high_throughput_head->next;
+ handle->next = NULL;
+ return SL_STATUS_OK;
+ } else {
+ sl_btmesh_lpn_high_throughput_timer_t *tmp;
+ // Find the entry to be removed and remove it
+ for (tmp = lpn_high_throughput_head; tmp->next != NULL; tmp = tmp->next) {
+ if (handle == tmp->next) {
+ tmp->next = tmp->next->next;
+ handle->next = NULL;
+ return SL_STATUS_OK;
+ }
+ }
+ }
+ // No element was found in the loop above
+ return SL_STATUS_NOT_FOUND;
+}
+
+/***************************************************************************//**
+ * Establishes friendship and logs if the request fails
+ ******************************************************************************/
+static void lpn_establish_friendship(void)
+{
+ sl_status_t result;
+
+ log_info("Trying to find a friend..." NL);
+ result = sl_btmesh_lpn_establish_friendship(lpn_friend_netkey_idx);
+
+ log_status_error_f(result, "Friend not found" NL);
+}
+
+/***************************************************************************//**
+ * Set the timer that delay LPN initialization to enable quick configuration
+ * over advertising bearer.
+ *
+ * @param[in] delay Time to set for the timer.
+ ******************************************************************************/
+static void set_configuration_timer(uint32_t delay)
+{
+ sl_status_t sc = sl_simple_timer_start(&lpn_node_configured_timer,
+ delay,
+ lpn_node_configured_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start timer");
+}
+
+/**************************************************************************//**
+ * @addtogroup btmesh_lpn_tmr_cb Timer Callbacks
+ * @{
+ *****************************************************************************/
+static void lpn_friend_find_timer_cb(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+
+ lpn_establish_friendship();
+}
+
+static void lpn_node_configured_timer_cb(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+
+ if (!lpn_active) {
+ log_info("Trying to initialize lpn..." NL);
+ sl_btmesh_lpn_feature_init();
+ }
+}
+
+static void lpn_high_throughput_timer_cb(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+
+ // Execute main operation; poll friend
+ sl_btmesh_lpn_poll(lpn_friend_netkey_idx);
+
+ // If all element were unregistered beforehand, simply return
+ if (lpn_high_throughput_head == NULL) {
+ return;
+ }
+
+ // Find fastest timeout handler
+ sl_btmesh_lpn_high_throughput_timer_t *tmp;
+ sl_btmesh_lpn_high_throughput_timer_t *fastest = lpn_high_throughput_head;
+ for (tmp = lpn_high_throughput_head;
+ tmp != NULL; tmp = tmp->next) {
+ if (fastest->timeout > tmp->timeout) {
+ fastest = tmp;
+ }
+ }
+
+ if (fastest->timeout < SL_BTMESH_LPN_POLL_TIMEOUT_CFG_VAL) {
+ // Start a timer with the timeout value
+ sl_status_t sc = sl_simple_timer_start(&lpn_high_throughput_timer,
+ fastest->timeout,
+ lpn_high_throughput_timer_cb,
+ NULL,
+ false);
+ app_assert_status_f(sc, "Failed to start timer");
+
+ switch (fastest->mode) {
+ case SL_BTMESH_LPN_HIGH_THROUGHPUT_SLOWING:
+ // If this is set to be slowed down, increase timeout
+ fastest->timeout <<= 1;
+ break;
+ default:
+ // Intentionally left empty
+ break;
+ }
+ } else {
+ // If the timer has a slower timeout than the default, unregister it
+ sl_btmesh_lpn_high_throughput_unregister(fastest);
+ }
+}
+
+/** @} (end addtogroup btmesh_lpn_tmr_cb) */
+
+/// @addtogroup btmesh_lpn_weak_cb Weak implementation of Callbacks
+/// @{
+
+SL_WEAK void sl_btmesh_lpn_on_init(void)
+{
+}
+
+SL_WEAK void sl_btmesh_lpn_on_deinit(void)
+{
+}
+
+SL_WEAK void sl_btmesh_lpn_on_friendship_established(uint16_t node_address)
+{
+ (void) node_address;
+}
+
+SL_WEAK void sl_btmesh_lpn_on_friendship_failed(uint16_t reason)
+{
+ (void) reason;
+}
+
+SL_WEAK void sl_btmesh_lpn_on_friendship_terminated(uint16_t reason)
+{
+ (void) reason;
+}
+
+/// @} (end addtogroup btmesh_lpn_weak_cb)
+
+/** @} (end addtogroup lpn) */
diff --git a/app/btmesh/common/btmesh_lpn/sl_btmesh_lpn.h b/app/btmesh/common/btmesh_lpn/sl_btmesh_lpn.h
new file mode 100644
index 00000000000..1fa9e581d44
--- /dev/null
+++ b/app/btmesh/common/btmesh_lpn/sl_btmesh_lpn.h
@@ -0,0 +1,188 @@
+/***************************************************************************//**
+ * @file
+ * @brief Low Power Node feature header
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_LPN_H
+#define SL_BTMESH_LPN_H
+
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * @defgroup lpn Low Power Node Component
+ * @brief Low Power Node feature Implementation
+ * This component implements Low Power Node feature.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup lpn
+ * @{
+ ******************************************************************************/
+
+/// Operational modes for the high throughput timer
+typedef enum {
+ /// Normal, periodic operation with the provided value
+ SL_BTMESH_LPN_HIGH_THROUGHPUT_NORMAL,
+ /// Slows down over time, doubling its timeout after every expiration
+ SL_BTMESH_LPN_HIGH_THROUGHPUT_SLOWING
+} sl_btmesh_lpn_high_throughput_operation_mode_t;
+
+/// Handler structure for the high throughput timer
+typedef struct sl_btmesh_lpn_high_throughput_timer_s {
+ /// The timeout in milliseconds
+ uint32_t timeout;
+ /// The operational mode
+ sl_btmesh_lpn_high_throughput_operation_mode_t mode;
+ /// Pointer to the next registered handler
+ struct sl_btmesh_lpn_high_throughput_timer_s *next;
+} sl_btmesh_lpn_high_throughput_timer_t;
+/***************************************************************************//**
+ * Initialize LPN functionality. This function is called automatically by the
+ * LPN component.
+ *
+ ******************************************************************************/
+void sl_btmesh_lpn_feature_init(void);
+
+/***************************************************************************//**
+ * Deinitialize LPN functionality. This function is called automatically by the
+ * LPN component.
+ ******************************************************************************/
+void sl_btmesh_lpn_feature_deinit(void);
+
+/***************************************************************************//**
+ * Return if the friendship is active between LPN and friend node
+ *
+ * @return Friendship active state
+ * @retval true If friendship is active with a friend node
+ * @retval false If friendship is not established with any friend node
+ ******************************************************************************/
+bool sl_btmesh_lpn_is_friendship_active(void);
+
+/***************************************************************************//**
+ * Poll the Friend node for stored messages and security updates
+ *
+ * This command may be used if the application is expecting to receive messages
+ * at a specific time. However, it is not required for correct operation,
+ * because the procedure will be performed autonomously before the poll timeout
+ * expires.
+ *
+ * @return SL_STATUS_OK if successful. Error code otherwise.
+ ******************************************************************************/
+sl_status_t sl_btmesh_lpn_poll_request(void);
+
+/***************************************************************************//**
+ * Handling of mesh lpn events.
+ *
+ * @param[in] pEvt Pointer to incoming lpn event.
+ ******************************************************************************/
+void sl_btmesh_lpn_on_event(sl_btmesh_msg_t *pEvt);
+
+/***************************************************************************//**
+ * Registers high throughput request
+ *
+ * @param[in] handle Pointer to the handler of the high throughput timer
+ * structure, provided by the user
+ * @param[in] timeout The timeout of this timer, in milliseconds
+ * @param[in] mode The mode of operation
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK Timer successfully registered
+ * @retval SL_STATUS_ALREADY_EXISTS Timer is already present, list unchanged
+ ******************************************************************************/
+sl_status_t sl_btmesh_lpn_high_throughput_register(sl_btmesh_lpn_high_throughput_timer_t *handle,
+ uint32_t timeout,
+ sl_btmesh_lpn_high_throughput_operation_mode_t mode);
+
+/***************************************************************************//**
+ * Unregisters high throughput request
+ *
+ * @note Will not stop the already running timer if it belongs to the handler
+ * passed.
+ *
+ * @param[in] handle Pointer to the handler to be unregistered
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK Timer successfully unregistered
+ * @retval SL_STATUS_ALREADY_EXISTS Timer was not present in the list
+ ******************************************************************************/
+sl_status_t sl_btmesh_lpn_high_throughput_unregister(sl_btmesh_lpn_high_throughput_timer_t *handle);
+
+/***************************************************************************//**
+ * Called when the Low Power Node is initialized.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+void sl_btmesh_lpn_on_init(void);
+
+/***************************************************************************//**
+ * Called when the Low Power Node is deinitialized.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+void sl_btmesh_lpn_on_deinit(void);
+
+/***************************************************************************//**
+ * Called when the Low Power Node establishes friendship with another node
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] friend_address Node address of the new friend
+ ******************************************************************************/
+void sl_btmesh_lpn_on_friendship_established(uint16_t friend_address);
+
+/***************************************************************************//**
+ * Called when the friendship establishment attempt of Low Power Node fails
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] reason Reason for friendship establishment failure
+ ******************************************************************************/
+void sl_btmesh_lpn_on_friendship_failed(uint16_t reason);
+
+/***************************************************************************//**
+ * Called when friendship that was successfully established has been terminated
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] reason Reason for friendship termination
+ ******************************************************************************/
+void sl_btmesh_lpn_on_friendship_terminated(uint16_t reason);
+
+/** @} (end addtogroup lpn) */
+
+#endif /* SL_BTMESH_LPN_H */
diff --git a/app/btmesh/common/btmesh_provisionee/btmesh_provisionee_validation.lua b/app/btmesh/common/btmesh_provisionee/btmesh_provisionee_validation.lua
new file mode 100644
index 00000000000..1c65b9e96cb
--- /dev/null
+++ b/app/btmesh/common/btmesh_provisionee/btmesh_provisionee_validation.lua
@@ -0,0 +1,43 @@
+-- Provisionee validation script
+
+local oob_enable = slc.config('SL_BTMESH_PROVISIONEE_OOB_ENABLE_CFG_VAL')
+
+local no_oob = slc.config('SL_BTMESH_PROVISIONEE_AUTH_NO_OOB_CFG_VAL').value == "1"
+local static_oob = slc.config('SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB_CFG_VAL').value == "1"
+local output_oob = slc.config('SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_CFG_VAL').value == "1"
+local input_oob = slc.config('SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_CFG_VAL').value == "1"
+
+local output_blink = slc.config('SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK_CFG_VAL').value == "1"
+local output_beep = slc.config('SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP_CFG_VAL').value == "1"
+local output_vibrate = slc.config('SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE_CFG_VAL').value == "1"
+local output_numeric = slc.config('SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC_CFG_VAL').value == "1"
+local output_alpha = slc.config('SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA_CFG_VAL').value == "1"
+
+local input_push = slc.config('SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH_CFG_VAL').value == "1"
+local input_twist = slc.config('SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST_CFG_VAL').value == "1"
+local input_numeric = slc.config('SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC_CFG_VAL').value == "1"
+local input_alpha = slc.config('SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA_CFG_VAL').value == "1"
+
+if oob_enable.value == "1" then
+ if not (no_oob or static_oob or output_oob or input_oob) then
+ validation.error(
+ 'No authentication method enabled',
+ validation.target_for_defines({ oob_enable.id }),
+ 'Enable at least one authentication method (No OOB, Static OOB, Output OOB, Input OOB) or disable OOB')
+ end
+ if output_oob and
+ not (output_blink or output_beep or output_vibrate or output_numeric or output_alpha) then
+ validation.error(
+ 'No output method enabled',
+ validation.target_for_defines({ oob_enable.id }),
+ 'Enable at least one output method or disable Output OOB'
+ )
+ end
+ if input_oob and not (input_push or input_twist or input_numeric or input_alpha) then
+ validation.error(
+ 'No input method enabled',
+ validation.target_for_defines({ oob_enable.id }),
+ 'Enable at least one input method or disable Input OOB'
+ )
+ end
+end
diff --git a/app/btmesh/common/btmesh_provisionee/config/sl_btmesh_provisionee_config.h b/app/btmesh/common/btmesh_provisionee/config/sl_btmesh_provisionee_config.h
new file mode 100644
index 00000000000..3f710e0a392
--- /dev/null
+++ b/app/btmesh/common/btmesh_provisionee/config/sl_btmesh_provisionee_config.h
@@ -0,0 +1,272 @@
+/***************************************************************************//**
+ * @file
+ * @brief Node init and provision config
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_PROVISIONEE_CONFIG_H
+#define SL_BTMESH_PROVISIONEE_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Enable logging
+// 1
+#define SL_BTMESH_PROVISIONEE_LOGGING_CFG_VAL 1
+
+// Automatic unprovisioned device beaconing
+// Start unprovisioned device beaconing automatically with the selected bearers after node initialization is complete.
+// <0=> Disable
+// <1=> PB-ADV
+// <2=> PB-GATT
+// <3=> PB-ADV and PB-GATT
+// 3
+#define SL_BTMESH_PROVISIONEE_AUTO_UNPROV_BEACONING_CFG_VAL 3
+
+// Initialize provisioning records
+// 0
+#define SL_BTMESH_PROVISIONEE_INIT_PROV_RECORDS_CFG_VAL 0
+
+// Enable OOB (out-of-band) settings
+// 1
+#define SL_BTMESH_PROVISIONEE_OOB_ENABLE_CFG_VAL 0
+
+// OOB public key
+// Use the ECC key stored in the persistent storage during provisioning instead of an ephemeral key.
+// 1
+ #define SL_BTMESH_PROVISIONEE_OOB_PUBLIC_KEY_ENABLE_CFG_VAL 0
+
+// No OOB support
+// Support authentication without OOB data
+// 1
+ #define SL_BTMESH_PROVISIONEE_AUTH_NO_OOB_CFG_VAL 1
+//
+
+// Static OOB support
+// Support authentication with static OOB data
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB_CFG_VAL 0
+//
+
+// Output OOB support
+// Support authentication with output OOB data
+// 1
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_CFG_VAL 0
+// Maximum size <1..8>
+// Maximum size of output OOB supported
+// 1
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_SIZE_CFG_VAL 1
+// Blink
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK_CFG_VAL 0
+// Beep
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP_CFG_VAL 0
+// Vibrate
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE_CFG_VAL 0
+// Numeric
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC_CFG_VAL 0
+// Alphanumeric
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA_CFG_VAL 0
+//
+
+// Input OOB support
+// Support authentication with input OOB data
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_CFG_VAL 0
+// Maximum size <1..8>
+// Maximum size of input OOB supported
+// 1
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_SIZE_CFG_VAL 1
+// Push
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH_CFG_VAL 0
+// Twist
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST_CFG_VAL 0
+// Numeric
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC_CFG_VAL 0
+// Alphanumeric
+// 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA_CFG_VAL 0
+//
+
+// OOB Information
+// Other
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_OTHER_CFG_VAL 0
+// Electronic / URI
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_URI_CFG_VAL 0
+// 2D machine-readable code
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_2D_MR_CODE_CFG_VAL 0
+// Bar code
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_BAR_CODE_CFG_VAL 0
+// Near Field Communication (NFC)
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_NFC_CFG_VAL 0
+// Number
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_NUMBER_CFG_VAL 0
+// String
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_STRING_CFG_VAL 0
+
+// CBP PROV_RECORDS RFU_9 RFU_10
+
+// On box
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_BOX_CFG_VAL 0
+// Inside box
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_IN_BOX_CFG_VAL 0
+// On piece of paper
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_PAPER_CFG_VAL 0
+// Inside manual
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_IN_MANUAL_CFG_VAL 0
+// On device
+// 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_DEVICE_CFG_VAL 0
+//
+
+//
+// <<< end of configuration section >>>
+
+#if SL_BTMESH_PROVISIONEE_OOB_ENABLE_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_PUBLIC_KEY_ENABLE SL_BTMESH_PROVISIONEE_OOB_PUBLIC_KEY_ENABLE_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_NO_OOB SL_BTMESH_PROVISIONEE_AUTH_NO_OOB_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_CFG_VAL
+
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_OTHER SL_BTMESH_PROVISIONEE_OOB_INFO_OTHER_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_URI SL_BTMESH_PROVISIONEE_OOB_INFO_URI_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_2D_MR_CODE SL_BTMESH_PROVISIONEE_OOB_INFO_2D_MR_CODE_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_BAR_CODE SL_BTMESH_PROVISIONEE_OOB_INFO_BAR_CODE_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_NFC SL_BTMESH_PROVISIONEE_OOB_INFO_NFC_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_NUMBER SL_BTMESH_PROVISIONEE_OOB_INFO_NUMBER_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_STRING SL_BTMESH_PROVISIONEE_OOB_INFO_STRING_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_BOX SL_BTMESH_PROVISIONEE_OOB_INFO_ON_BOX_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_IN_BOX SL_BTMESH_PROVISIONEE_OOB_INFO_IN_BOX_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_PAPER SL_BTMESH_PROVISIONEE_OOB_INFO_ON_PAPER_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_IN_MANUAL SL_BTMESH_PROVISIONEE_OOB_INFO_IN_MANUAL_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_DEVICE SL_BTMESH_PROVISIONEE_OOB_INFO_ON_DEVICE_CFG_VAL
+#else
+ #define SL_BTMESH_PROVISIONEE_OOB_PUBLIC_KEY_ENABLE 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_NO_OOB 1
+ #define SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB 0
+
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_OTHER 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_URI 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_2D_MR_CODE 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_BAR_CODE 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_NFC 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_NUMBER 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_STRING 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_BOX 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_IN_BOX 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_PAPER 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_IN_MANUAL 0
+ #define SL_BTMESH_PROVISIONEE_OOB_INFO_ON_DEVICE 0
+#endif
+
+#define SL_BTMESH_PROVISIONEE_AUTH_METHODS \
+ ((SL_BTMESH_PROVISIONEE_AUTH_NO_OOB << 0) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB << 1) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB << 2) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB << 3))
+
+#define SL_BTMESH_PROVISIONEE_OOB_INFO \
+ ((SL_BTMESH_PROVISIONEE_OOB_INFO_OTHER << 0) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_URI << 1) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_2D_MR_CODE << 2) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_BAR_CODE << 3) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_NFC << 4) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_NUMBER << 5) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_STRING << 6) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_ON_BOX << 11) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_IN_BOX << 12) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_ON_PAPER << 13) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_IN_MANUAL << 14) \
+ | (SL_BTMESH_PROVISIONEE_OOB_INFO_ON_DEVICE << 15))
+
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_SIZE SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_SIZE_CFG_VAL
+
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA_CFG_VAL
+#else
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_SIZE 0
+
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA 0
+#endif
+
+#define SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ACTIONS \
+ ((SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK << 0) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP << 1) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE << 2) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC << 3) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA << 4))
+
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_SIZE SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_SIZE_CFG_VAL
+
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC_CFG_VAL
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA_CFG_VAL
+#else
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_SIZE 0
+
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC 0
+ #define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA 0
+#endif
+
+#define SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ACTIONS \
+ ((SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH << 0) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST << 1) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC << 2) \
+ | (SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA << 3))
+
+#endif // SL_BTMESH_PROVISIONEE_CONFIG_H
diff --git a/app/btmesh/common/btmesh_provisionee/sl_btmesh_provisionee.c b/app/btmesh/common/btmesh_provisionee/sl_btmesh_provisionee.c
new file mode 100644
index 00000000000..cc220592a13
--- /dev/null
+++ b/app/btmesh/common/btmesh_provisionee/sl_btmesh_provisionee.c
@@ -0,0 +1,343 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_status.h"
+#include "sl_common.h"
+
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#include
+#include "app_assert.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_provisionee.h"
+#include "sl_btmesh_provisionee_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup provisionee
+ * @{
+ ******************************************************************************/
+
+// Suppress compiler warning of unused static function
+#define SL_UNUSED __attribute__((unused))
+
+#define AUTH_VAL_SIZE_MAX 32
+
+static size_t auth_val_size = AUTH_VAL_SIZE_MAX;
+
+// -----------------------------------------------------------------------------
+// Static Function Declarations
+// -----------------------------------------------------------------------------
+/***************************************************************************//**
+ * Dispatch the execution to one of the callback functions
+ * based on the provided input action.
+ *
+ * @param[in] input_action field from ::sl_btmesh_evt_node_input_oob_request_t
+ * @param[in] input_size field from ::sl_btmesh_evt_node_input_oob_request_t
+ ******************************************************************************/
+SL_UNUSED static void on_input_oob_request(uint8_t input_action, uint8_t input_size);
+
+/***************************************************************************//**
+ * Dispatch the execution to one of the callback functions
+ * based on the provided output action.
+ *
+ * @param[in] output_action field from ::sl_btmesh_evt_node_display_output_oob_t
+ * @param[in] data field from ::sl_btmesh_evt_node_display_output_oob_t
+ ******************************************************************************/
+SL_UNUSED static void on_output_oob_data(uint8_t output_action, uint8array* data);
+
+/***************************************************************************//**
+ * Convert a big endian uint8array's lowest 4 bytes to an uint32_t number
+ *
+ * @param[in] data uint8array containing the authentication value
+ * @return the converted number
+ ******************************************************************************/
+SL_UNUSED static uint32_t oob_data_to_num(uint8array *data);
+
+void sl_bt_provisionee_on_event(sl_bt_msg_t* evt)
+{
+ sl_status_t sc;
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_bt_evt_system_boot_id:
+ #if SL_BTMESH_PROVISIONEE_OOB_ENABLE_CFG_VAL
+ sc = sl_btmesh_node_init_oob(SL_BTMESH_PROVISIONEE_OOB_PUBLIC_KEY_ENABLE,
+ SL_BTMESH_PROVISIONEE_AUTH_METHODS,
+ SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ACTIONS,
+ SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_SIZE,
+ SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ACTIONS,
+ SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_SIZE,
+ SL_BTMESH_PROVISIONEE_OOB_INFO);
+ #else
+ sc = sl_btmesh_node_init();
+ #endif
+ log_status_error_f(sc, "Initialization failed" NL);
+ sl_btmesh_provisionee_on_init(sc);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void sl_btmesh_provisionee_on_event(sl_btmesh_msg_t* evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ if (!(evt->data.evt_node_initialized.provisioned)) {
+ sl_status_t sc;
+ #if SL_BTMESH_PROVISIONEE_INIT_PROV_RECORDS_CFG_VAL
+ sc = sl_btmesh_node_init_provisioning_records();
+ app_assert_status_f(sc, "Failed to init provisioning records");
+ #endif
+ #if SL_BTMESH_PROVISIONEE_AUTO_UNPROV_BEACONING_CFG_VAL
+ sc = sl_btmesh_node_start_unprov_beaconing(SL_BTMESH_PROVISIONEE_AUTO_UNPROV_BEACONING_CFG_VAL);
+ app_assert_status_f(sc, "Failed to start unprovisioned beaconing");
+ #endif
+ }
+ break;
+
+ #if (SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB || SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB)
+ case sl_btmesh_evt_node_start_received_id:
+ auth_val_size = evt->data.evt_node_start_received.algorithm == 0 ? 16 : 32;
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB
+ case sl_btmesh_evt_node_display_output_oob_id:
+ on_output_oob_data(evt->data.evt_node_display_output_oob.output_action,
+ &(evt->data.evt_node_display_output_oob.data));
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB
+ case sl_btmesh_evt_node_input_oob_request_id:
+ on_input_oob_request(evt->data.evt_node_input_oob_request.input_action,
+ evt->data.evt_node_input_oob_request.input_size);
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB
+ case sl_btmesh_evt_node_static_oob_request_id:
+ sl_btmesh_on_static_oob_request(auth_val_size);
+ break;
+ #endif
+ default:
+ break;
+ }
+}
+
+sl_status_t sl_btmesh_provisionee_input_oob_num(uint32_t oob_num)
+{
+ app_assert(auth_val_size >= 4, "auth_val_size should be at least 4.");
+ app_assert(auth_val_size <= AUTH_VAL_SIZE_MAX, "auth_val_size should be at most AUTH_VAL_SIZE_MAX.");
+ uint8_t auth_val_buff[AUTH_VAL_SIZE_MAX] = { 0 };
+ uint8_t last_index = auth_val_size - 1;
+ for (uint8_t i = 0; i < 4; i++) {
+ auth_val_buff[last_index - i] = (oob_num >> (8 * i)) & 0xFF;
+ }
+ return sl_btmesh_node_send_input_oob_request_response(auth_val_size, auth_val_buff);
+}
+
+sl_status_t sl_btmesh_provisionee_input_oob_str(char* oob_str)
+{
+ uint8_t auth_val_buff[AUTH_VAL_SIZE_MAX] = { 0 };
+ strncpy((char*)auth_val_buff, oob_str, AUTH_VAL_SIZE_MAX);
+ return sl_btmesh_node_send_input_oob_request_response(auth_val_size, auth_val_buff);
+}
+
+static void on_input_oob_request(uint8_t input_action, uint8_t input_size)
+{
+ (void)input_size;
+ switch (input_action) {
+ #if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH
+ case sl_btmesh_node_oob_input_action_push:
+ sl_btmesh_on_input_oob_request_push(input_size);
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST
+ case sl_btmesh_node_oob_input_action_twist:
+ sl_btmesh_on_input_oob_request_twist(input_size);
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC
+ case sl_btmesh_node_oob_input_action_numeric:
+ sl_btmesh_on_input_oob_request_numeric(input_size);
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA
+ case sl_btmesh_node_oob_input_action_alpha:
+ sl_btmesh_on_input_oob_request_alphanumeric(input_size);
+ break;
+ #endif
+
+ default:
+ break;
+ }
+}
+
+static uint32_t oob_data_to_num(uint8array *data)
+{
+ app_assert(data->len >= 4, "Data should be at least 4 byte long");
+ uint32_t num = 0;
+ uint8_t last_index = data->len - 1;
+ for (uint8_t i = 0; i < 4; i++) {
+ num |= data->data[last_index - i] << (8 * i);
+ }
+ return num;
+}
+
+static void on_output_oob_data(uint8_t output_action, uint8array* data)
+{
+ (void)data;
+ switch (output_action) {
+ #if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK
+ case sl_btmesh_node_oob_output_action_blink:
+ sl_btmesh_on_output_oob_blink(oob_data_to_num(data));
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP
+ case sl_btmesh_node_oob_output_action_beep:
+ sl_btmesh_on_output_oob_beep(oob_data_to_num(data));
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE
+ case sl_btmesh_node_oob_output_action_vibrate:
+ sl_btmesh_on_output_oob_vibrate(oob_data_to_num(data));
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC
+ case sl_btmesh_node_oob_output_action_numeric:
+ sl_btmesh_on_output_oob_numeric(oob_data_to_num(data));
+ break;
+ #endif
+
+ #if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA
+ case sl_btmesh_node_oob_output_action_alpha:
+ sl_btmesh_on_output_oob_alphanumeric((char*)(data->data));
+ break;
+ #endif
+
+ default:
+ break;
+ }
+}
+
+SL_WEAK void sl_btmesh_provisionee_on_init(sl_status_t result)
+{
+ (void)result;
+}
+
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH
+SL_WEAK void sl_btmesh_on_input_oob_request_push(uint8_t input_size)
+{
+ (void)input_size;
+ log_info("Input OOB request push" NL);
+}
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST
+SL_WEAK void sl_btmesh_on_input_oob_request_twist(uint8_t input_size)
+{
+ (void)input_size;
+ log_info("Input OOB request twist" NL);
+}
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC
+SL_WEAK void sl_btmesh_on_input_oob_request_numeric(uint8_t input_size)
+{
+ (void)input_size;
+ log_info("Input OOB request numeric" NL);
+}
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA
+SL_WEAK void sl_btmesh_on_input_oob_request_alpha(uint8_t input_size)
+{
+ (void)input_size;
+ log_info("Input OOB request alphanumeric" NL);
+}
+#endif
+
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK
+SL_WEAK void sl_btmesh_on_output_oob_blink(uint32_t oob_num)
+{
+ log_info("Output OOB blink: %lu" NL, oob_num);
+}
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP
+SL_WEAK void sl_btmesh_on_output_oob_beep(uint32_t oob_num)
+{
+ log_info("Output OOB beep: %lu" NL, oob_num);
+}
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE
+SL_WEAK void sl_btmesh_on_output_oob_vibrate(uint32_t oob_num)
+{
+ log_info("Output OOB vibrate: %lu" NL, oob_num);
+}
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC
+SL_WEAK void sl_btmesh_on_output_oob_numeric(uint32_t oob_num)
+{
+ log_info("Output OOB numeric: %lu" NL, oob_num);
+}
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA
+SL_WEAK void sl_btmesh_on_output_oob_alphanumeric(char* oob_str)
+{
+ log_info("Output OOB alphanumeric: %s" NL, oob_str);
+}
+#endif
+
+#if SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB
+SL_WEAK void sl_btmesh_on_static_oob_request(size_t oob_size)
+{
+ (void)oob_size;
+ log_info("Static OOB request" NL);
+}
+#endif
+
+/** @} (end addtogroup provisionee) */
diff --git a/app/btmesh/common/btmesh_provisionee/sl_btmesh_provisionee.h b/app/btmesh/common/btmesh_provisionee/sl_btmesh_provisionee.h
new file mode 100644
index 00000000000..d8d4ef9a28e
--- /dev/null
+++ b/app/btmesh/common/btmesh_provisionee/sl_btmesh_provisionee.h
@@ -0,0 +1,237 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_PROVISIONEE_H
+#define SL_BTMESH_PROVISIONEE_H
+
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_provisionee_config.h"
+
+/***************************************************************************//**
+ * @defgroup provisionee
+ * @brief
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup provisionee
+ * @{
+ ******************************************************************************/
+
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH
+/***************************************************************************//**
+ * This function is called when input oob authentication is needed with push action.
+ * Respond to it by calling the sl_btmesh_provisionee_input_oob_num() function.
+ *
+ * @param[in] input_size Input OOB size
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs that it was called.
+ ******************************************************************************/
+void sl_btmesh_on_input_oob_request_push(uint8_t input_size);
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST
+/***************************************************************************//**
+ * This function is called when input oob authentication is needed with twist action.
+ * Respond to it by calling the sl_btmesh_provisionee_input_oob_num() function.
+ *
+ * @param[in] input_size Input OOB size
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs that it was called.
+ ******************************************************************************/
+void sl_btmesh_on_input_oob_request_twist(uint8_t input_size);
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC
+/***************************************************************************//**
+ * This function is called when input oob authentication is needed with
+ * numeric input action.
+ * Respond to it by calling the sl_btmesh_provisionee_input_oob_num() function.
+ *
+ * @param[in] input_size Input OOB size
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs that it was called.
+ ******************************************************************************/
+void sl_btmesh_on_input_oob_request_numeric(uint8_t input_size);
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_ALPHA
+/***************************************************************************//**
+ * This function is called when input oob authentication is needed with
+ * alphanumeric input action.
+ * Respond to it by calling the sl_btmesh_provisionee_input_oob_str() function.
+ *
+ * @param[in] input_size Input OOB size
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs that it was called.
+ ******************************************************************************/
+void sl_btmesh_on_input_oob_request_alphanumeric(uint8_t input_size);
+
+/***************************************************************************//**
+ * This function must be called when the alphanumeric input action completed.
+ *
+ * @param[in] oob_str the string containing the alphanumeric authentication value
+ * @return sl_status_t
+ ******************************************************************************/
+sl_status_t sl_btmesh_provisionee_input_oob_str(char* oob_str);
+#endif
+
+#if SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_PUSH \
+ || SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_TWIST \
+ || SL_BTMESH_PROVISIONEE_AUTH_INPUT_OOB_NUMERIC
+/***************************************************************************//**
+ * This function must be called when a numeric input action completed.
+ *
+ * @param[in] oob_num the number containing the authentication value
+ * @return sl_status_t
+ ******************************************************************************/
+sl_status_t sl_btmesh_provisionee_input_oob_num(uint32_t oob_num);
+#endif
+
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BLINK
+/***************************************************************************//**
+ * This function is called when output oob authentication is needed with
+ * blink output action.
+ *
+ * @param[in] oob_num number to output by blinking
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs the oob_num parameter.
+ ******************************************************************************/
+void sl_btmesh_on_output_oob_blink(uint32_t oob_num);
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_BEEP
+/***************************************************************************//**
+ * This function is called when output oob authentication is needed with
+ * beep output action.
+ *
+ * @param[in] oob_num number to output by beeping
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs the oob_num parameter.
+ ******************************************************************************/
+void sl_btmesh_on_output_oob_beep(uint32_t oob_num);
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_VIBRATE
+/***************************************************************************//**
+ * This function is called when output oob authentication is needed with
+ * vibrate output action.
+ *
+ * @param[in] oob_num number to output by vibrating
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs the oob_num parameter.
+ ******************************************************************************/
+void sl_btmesh_on_output_oob_vibrate(uint32_t oob_num);
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_NUMERIC
+/***************************************************************************//**
+ * This function is called when output oob authentication is needed with
+ * numeric output action.
+ *
+ * @param[in] oob_num number to display
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs the oob_num parameter.
+ ******************************************************************************/
+void sl_btmesh_on_output_oob_numeric(uint32_t oob_num);
+#endif
+#if SL_BTMESH_PROVISIONEE_AUTH_OUTPUT_OOB_ALPHA
+/***************************************************************************//**
+ * This function is called when output oob authentication is needed with
+ * alphanumeric output action.
+ *
+ * @param[in] oob_str string to display
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs the oob_num parameter.
+ ******************************************************************************/
+void sl_btmesh_on_output_oob_alphanumeric(char* oob_str);
+#endif
+
+#if SL_BTMESH_PROVISIONEE_AUTH_STATIC_OOB
+/***************************************************************************//**
+ * This function is called when static oob authentication is needed.
+ * Respond to it by calling the sl_btmesh_node_send_static_oob_request_response()
+ * function.
+ *
+ * @param[in] oob_size size of the required AuthValue buffer
+ *
+ * This is a callback which should be implemented in the application.
+ * @note If no implementation is provided in the application then a default weak
+ * implementation is provided which logs that it was called.
+ ******************************************************************************/
+void sl_btmesh_on_static_oob_request(size_t oob_size);
+#endif
+
+/***************************************************************************//**
+ * This function is called after the provisionee called the sl_btmesh_node_init()
+ * or the sl_btmesh_node_init_oob() function.
+ *
+ * @param[in] result return value of the function call
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+void sl_btmesh_provisionee_on_init(sl_status_t result);
+
+/***************************************************************************//**
+ * Handle BT Mesh events for the provisionee.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_btmesh_provisionee_on_event(sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * Handle BLE events for the provisionee.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event.
+ ******************************************************************************/
+void sl_bt_provisionee_on_event(sl_bt_msg_t* evt);
+
+/** @} (end addtogroup provisionee) */
+
+#endif /* SL_BTMESH_PROVISIONEE_H */
diff --git a/app/btmesh/common/btmesh_provisioning_decorator/config/sl_btmesh_provisioning_decorator_config.h b/app/btmesh/common/btmesh_provisioning_decorator/config/sl_btmesh_provisioning_decorator_config.h
new file mode 100644
index 00000000000..1778262a8cd
--- /dev/null
+++ b/app/btmesh/common/btmesh_provisioning_decorator/config/sl_btmesh_provisioning_decorator_config.h
@@ -0,0 +1,51 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_PROVISIONING_DECORATOR_CONFIG_H
+#define SL_BTMESH_PROVISIONING_DECORATOR_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Provisioning decorator configuration
+
+// Enable Logging
+// Default: 1
+// Enable or disable Logging for Provisioning Decorator specific messages for this component.
+#define SL_BTMESH_PROVISIONING_DECORATOR_LOGGING_CFG_VAL (1)
+
+//
+
+// Timeout for system restart after provisioning fails
+#define SL_BTMESH_PROVISIONING_DECORATOR_RESTART_TIMER_TIMEOUT_CFG_VAL (2000)
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_PROVISIONING_DECORATOR_CONFIG_H
diff --git a/app/btmesh/common/btmesh_provisioning_decorator/sl_btmesh_provisioning_decorator.c b/app/btmesh/common/btmesh_provisioning_decorator/sl_btmesh_provisioning_decorator.c
new file mode 100644
index 00000000000..5703a2662af
--- /dev/null
+++ b/app/btmesh/common/btmesh_provisioning_decorator/sl_btmesh_provisioning_decorator.c
@@ -0,0 +1,210 @@
+/***************************************************************************//**
+ * @file
+ * @brief Provisioning decorator module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+
+#include "em_common.h"
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_provisioning_decorator.h"
+#include "sl_btmesh_provisioning_decorator_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup ProvisioningDecorator
+ * @{
+ ******************************************************************************/
+
+/// High Priority
+#define HIGH_PRIORITY 0
+/// No Timer Options
+#define NO_FLAGS 0
+/// Callback has no parameters
+#define NO_CALLBACK_DATA (void *)NULL
+
+// periodic timer handle
+static sl_simple_timer_t restart_timer;
+// periodic timer callback
+static void prov_decor_restart_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+// -----------------------------------------------------------------------------
+// Provisioning Callbacks
+
+/*******************************************************************************
+ * Called at node initialization time to provide provisioning information
+ *
+ * @param[in] provisioned true: provisioned, false: unprovisioned
+ * @param[in] address Unicast address of the primary element of the node.
+ Ignored if unprovisioned.
+ * @param[in] iv_index IV index for the first network of the node
+ Ignored if unprovisioned.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+SL_WEAK void sl_btmesh_on_provision_init_status(bool provisioned,
+ uint16_t address,
+ uint32_t iv_index)
+{
+ (void) provisioned;
+ (void) address;
+ (void) iv_index;
+}
+
+/*******************************************************************************
+ * Called when the Provisioning starts
+ *
+ * @param[in] result Result code. 0: success, non-zero: error
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+SL_WEAK void sl_btmesh_on_node_provisioning_started(uint16_t result)
+{
+ (void) result;
+}
+
+/*******************************************************************************
+ * Called when the Provisioning finishes successfully
+ *
+ * @param[in] address Unicast address of the primary element of the node.
+ Ignored if unprovisioned.
+ * @param[in] iv_index IV index for the first network of the node
+ Ignored if unprovisioned.
+
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+SL_WEAK void sl_btmesh_on_node_provisioned(uint16_t address,
+ uint32_t iv_index)
+{
+ (void) address;
+ (void) iv_index;
+}
+
+/*******************************************************************************
+ * Called when the Provisioning fails
+ *
+ * @param[in] result Result code. 0: success, non-zero: error
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+SL_WEAK void sl_btmesh_on_node_provisioning_failed(uint16_t result)
+{
+ (void) result;
+}
+
+// -----------------------------------------------------------------------------
+// Provisioning Decorator Callbacks
+
+/*******************************************************************************
+ * Handling of Provisioning Decorator stack events.
+ *
+ * @param[in] evt Event type
+ ******************************************************************************/
+void sl_btmesh_handle_provisioning_decorator_event(sl_btmesh_msg_t *evt)
+{
+ if (NULL == evt) {
+ return;
+ }
+
+ // Handle events
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ sl_btmesh_on_provision_init_status(evt->data.evt_node_initialized.provisioned,
+ evt->data.evt_node_initialized.address,
+ evt->data.evt_node_initialized.iv_index);
+ break;
+
+ case sl_btmesh_evt_node_provisioning_started_id:
+ sl_btmesh_on_node_provisioning_started(evt->data.evt_node_provisioning_started.result);
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_on_node_provisioned(evt->data.evt_node_provisioned.address,
+ evt->data.evt_node_provisioned.iv_index);
+ break;
+
+ case sl_btmesh_evt_node_provisioning_failed_id: {
+ sl_btmesh_on_node_provisioning_failed(evt->data.evt_node_provisioning_failed.result);
+
+ log_info("BT mesh system reset timer is started with %d ms timeout." NL,
+ SL_BTMESH_PROVISIONING_DECORATOR_RESTART_TIMER_TIMEOUT_CFG_VAL);
+
+ sl_status_t sc =
+ sl_simple_timer_start(&restart_timer,
+ SL_BTMESH_PROVISIONING_DECORATOR_RESTART_TIMER_TIMEOUT_CFG_VAL,
+ prov_decor_restart_timer_cb,
+ NO_CALLBACK_DATA,
+ false);
+ app_assert_status_f(sc, "Failed to start timer");
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * Called when the restart timer expires.
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to callback data
+ ******************************************************************************/
+static void prov_decor_restart_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+ sl_bt_system_reset(0);
+}
+
+/** @} (end addtogroup ProvisioningDecorator) */
diff --git a/app/btmesh/common/btmesh_provisioning_decorator/sl_btmesh_provisioning_decorator.h b/app/btmesh/common/btmesh_provisioning_decorator/sl_btmesh_provisioning_decorator.h
new file mode 100644
index 00000000000..649d8bdae45
--- /dev/null
+++ b/app/btmesh/common/btmesh_provisioning_decorator/sl_btmesh_provisioning_decorator.h
@@ -0,0 +1,102 @@
+/***************************************************************************//**
+ * @file
+ * @brief provisiong_decorator.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_PROVISIONING_DECORATOR_H
+#define SL_BTMESH_PROVISIONING_DECORATOR_H
+
+/***************************************************************************//**
+ * Callbacks
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Called at node initialization time to provide provisioning information
+ *
+ * @param[in] provisioned true: provisioned, false: unprovisioned
+ * @param[in] address Unicast address of the primary element of the node.
+ Ignored if unprovisioned.
+ * @param[in] iv_index IV index for the first network of the node
+ Ignored if unprovisioned.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+void sl_btmesh_on_provision_init_status(bool provisioned,
+ uint16_t address,
+ uint32_t iv_index);
+
+/***************************************************************************//**
+ * Called when the Provisioning starts
+ *
+ * @param[in] result Result code. 0: success, non-zero: error
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+void sl_btmesh_on_node_provisioning_started(uint16_t result);
+
+/***************************************************************************//**
+ * Called when the Provisioning finishes successfully
+ *
+ * @param[in] address Unicast address of the primary element of the node.
+ Ignored if unprovisioned.
+ * @param[in] iv_index IV index for the first network of the node
+ Ignored if unprovisioned.
+
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+void sl_btmesh_on_node_provisioned(uint16_t address,
+ uint32_t iv_index);
+
+/***************************************************************************//**
+ * Called when the Provisioning fails
+ *
+ * @param[in] result Result code. 0: success, non-zero: error
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+void sl_btmesh_on_node_provisioning_failed(uint16_t result);
+
+/***************************************************************************//**
+ * Functions which are automatically called when the component is selected
+ ******************************************************************************/
+/***************************************************************************//**
+ * Handling of Provisioning Decorator stack events.
+ *
+ * @param[in] evt Pointer to the event type
+ ******************************************************************************/
+void sl_btmesh_handle_provisioning_decorator_event(sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_PROVISIONING_DECORATOR_H
diff --git a/app/btmesh/common/btmesh_remote_provisioning_client/btmesh_remote_provisioning_client.dcd b/app/btmesh/common/btmesh_remote_provisioning_client/btmesh_remote_provisioning_client.dcd
new file mode 100644
index 00000000000..194a982d4e7
--- /dev/null
+++ b/app/btmesh/common/btmesh_remote_provisioning_client/btmesh_remote_provisioning_client.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x0005", "name":"Remote Provisioning Client"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_remote_provisioning_client/config/sl_btmesh_remote_provisioning_client_config.h b/app/btmesh/common/btmesh_remote_provisioning_client/config/sl_btmesh_remote_provisioning_client_config.h
new file mode 100644
index 00000000000..a7fb54be020
--- /dev/null
+++ b/app/btmesh/common/btmesh_remote_provisioning_client/config/sl_btmesh_remote_provisioning_client_config.h
@@ -0,0 +1,46 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Remote Provisioning Client Component - Default Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_REMOTE_PROVISIONING_CLIENT_CONFIG_H
+#define SL_BTMESH_REMOTE_PROVISIONING_CLIENT_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Remote Provisioning Client configuration
+
+// Enable Logging
+// Default: 0
+// Enable / disable Logging for remote provisioning client model specific messages for this component
+#define SL_BTMESH_REMOTE_PROVISIONING_CLIENT_LOGGING_CFG_VAL 0
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_REMOTE_PROVISIONING_CLIENT_CONFIG_H
diff --git a/app/btmesh/common/btmesh_remote_provisioning_client/sl_btmesh_remote_provisioning_client.c b/app/btmesh/common/btmesh_remote_provisioning_client/sl_btmesh_remote_provisioning_client.c
new file mode 100644
index 00000000000..3bbd858118f
--- /dev/null
+++ b/app/btmesh/common/btmesh_remote_provisioning_client/sl_btmesh_remote_provisioning_client.c
@@ -0,0 +1,88 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Remote Provisioning Client Component
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_btmesh_api.h"
+#include "sl_bt_api.h"
+#include "sl_status.h"
+
+#include "app_assert.h"
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_remote_provisioning_client_config.h"
+#include "sl_btmesh_remote_provisioning_client.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup remote_provisioning_client BT Mesh Remote Provisioning Client
+ * @brief BT Mesh Remote provisioning client component implementation
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Initialize Remote Provisioning Client component
+ ******************************************************************************/
+static void mesh_remote_provisioning_client_init(void);
+
+/*******************************************************************************
+ * Handle Remote Provisioning Client events
+ ******************************************************************************/
+void sli_btmesh_handle_remote_provisioning_client_on_event(const sl_btmesh_msg_t *evt)
+{
+ if (NULL == evt) {
+ return;
+ }
+
+ // Handle events
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_prov_initialized_id:
+ mesh_remote_provisioning_client_init();
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void mesh_remote_provisioning_client_init(void)
+{
+ sl_status_t sc = sl_btmesh_remote_provisioning_client_init();
+
+ app_assert_status_f(sc, "Failed to initialize remote provisioning client");
+
+ log_info("Remote provisioning client initialized" NL);
+}
+
+/** @} (end addtogroup remote_provisioning_client) */
diff --git a/app/btmesh/common/btmesh_remote_provisioning_client/sl_btmesh_remote_provisioning_client.h b/app/btmesh/common/btmesh_remote_provisioning_client/sl_btmesh_remote_provisioning_client.h
new file mode 100644
index 00000000000..f9d090c2d0e
--- /dev/null
+++ b/app/btmesh/common/btmesh_remote_provisioning_client/sl_btmesh_remote_provisioning_client.h
@@ -0,0 +1,46 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Remote Provisioning Client Component
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_REMOTE_PROVISIONING_CLIENT_H
+#define SL_BTMESH_REMOTE_PROVISIONING_CLIENT_H
+
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * Handle Remote Provisioning Client events
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component
+ *
+ * @param[in] evt Pointer to incoming event
+ ******************************************************************************/
+void sli_btmesh_handle_remote_provisioning_client_on_event(const sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_REMOTE_PROVISIONING_CLIENT_H
diff --git a/app/btmesh/common/btmesh_remote_provisioning_server/btmesh_remote_provisioning_server.dcd b/app/btmesh/common/btmesh_remote_provisioning_server/btmesh_remote_provisioning_server.dcd
new file mode 100644
index 00000000000..9de275160bf
--- /dev/null
+++ b/app/btmesh/common/btmesh_remote_provisioning_server/btmesh_remote_provisioning_server.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x0004", "name":"Remote Provisioning Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_remote_provisioning_server/config/sl_btmesh_remote_provisioning_server_config.h b/app/btmesh/common/btmesh_remote_provisioning_server/config/sl_btmesh_remote_provisioning_server_config.h
new file mode 100644
index 00000000000..508116fb009
--- /dev/null
+++ b/app/btmesh/common/btmesh_remote_provisioning_server/config/sl_btmesh_remote_provisioning_server_config.h
@@ -0,0 +1,56 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Remote Provisioning Server Component - Default Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_REMOTE_PROVISIONING_SERVER_CONFIG_H
+#define SL_BTMESH_REMOTE_PROVISIONING_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+#define REMOTE_PROVISIONING_SERVER_ADVERTISING_BEARER 1
+#define REMOTE_PROVISIONING_SERVER_GATT_BEARER 2
+
+// Remote Provisioning Server configuration
+
+// Default Bearer Type
+// Advertising Bearer
+// GATT bearer
+// Default: REMOTE_PROVISIONING_SERVER_ADVERTISING_BEARER
+// Set default bearer to be used in remote provisioning
+#define SL_BTMESH_REMOTE_PROVISIONING_SERVER_BEARER_TYPE REMOTE_PROVISIONING_SERVER_ADVERTISING_BEARER
+
+// Enable Logging
+// Default: 0
+// Enable / disable Logging for remote provisioning server model specific messages for this component
+#define SL_BTMESH_REMOTE_PROVISIONING_SERVER_LOGGING_CFG_VAL 0
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_REMOTE_PROVISIONING_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_remote_provisioning_server/sl_btmesh_remote_provisioning_server.c b/app/btmesh/common/btmesh_remote_provisioning_server/sl_btmesh_remote_provisioning_server.c
new file mode 100644
index 00000000000..286c9e10dd4
--- /dev/null
+++ b/app/btmesh/common/btmesh_remote_provisioning_server/sl_btmesh_remote_provisioning_server.c
@@ -0,0 +1,96 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Remote Provisioning Server Component
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_btmesh_api.h"
+#include "sl_bt_api.h"
+#include "sl_status.h"
+
+#include "app_assert.h"
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_remote_provisioning_server_config.h"
+#include "sl_btmesh_remote_provisioning_server.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup remote_provisioning_server BT Mesh Remote Provisioning Server
+ * @brief BT Mesh Remote provisioning server component implementation
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Initialize Remote Provisioning Server component
+ ******************************************************************************/
+static void mesh_remote_provisioning_server_init(void);
+
+/*******************************************************************************
+ * Handle Remote Provisioning Server events
+ ******************************************************************************/
+void sli_btmesh_handle_remote_provisioning_server_on_event(const sl_btmesh_msg_t *evt)
+{
+ if (NULL == evt) {
+ return;
+ }
+
+ // Handle events
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ mesh_remote_provisioning_server_init();
+ }
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ mesh_remote_provisioning_server_init();
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void mesh_remote_provisioning_server_init(void)
+{
+ sl_status_t sc = sl_btmesh_remote_provisioning_server_init();
+ app_assert_status_f(sc, "Failed to initialize remote provisioning server");
+
+ sc = sl_btmesh_remote_provisioning_server_set_default_bearer(SL_BTMESH_REMOTE_PROVISIONING_SERVER_BEARER_TYPE);
+ app_assert_status_f(sc, "Failed to set default bearer");
+
+ log_info("Remote provisining server initialized" NL);
+}
+
+/** @} (end addtogroup remote_provisioning_server) */
diff --git a/app/btmesh/common/btmesh_remote_provisioning_server/sl_btmesh_remote_provisioning_server.h b/app/btmesh/common/btmesh_remote_provisioning_server/sl_btmesh_remote_provisioning_server.h
new file mode 100644
index 00000000000..ad8a123b62f
--- /dev/null
+++ b/app/btmesh/common/btmesh_remote_provisioning_server/sl_btmesh_remote_provisioning_server.h
@@ -0,0 +1,46 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Remote Provisioning Server Component
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_REMOTE_PROVISIONING_SERVER_H
+#define SL_BTMESH_REMOTE_PROVISIONING_SERVER_H
+
+#include "sl_btmesh_api.h"
+
+/***************************************************************************//**
+ * Handle Remote Provisioning Server events
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component
+ *
+ * @param[in] evt Pointer to incoming event
+ ******************************************************************************/
+void sli_btmesh_handle_remote_provisioning_server_on_event(const sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_REMOTE_PROVISIONING_SERVER_H
diff --git a/app/btmesh/common/btmesh_scene_client/btmesh_scene_client.dcd b/app/btmesh/common/btmesh_scene_client/btmesh_scene_client.dcd
new file mode 100644
index 00000000000..901f0bdf9d2
--- /dev/null
+++ b/app/btmesh/common/btmesh_scene_client/btmesh_scene_client.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1205", "name":"Scene Client"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_scene_client/config/sl_btmesh_scene_client_config.h b/app/btmesh/common/btmesh_scene_client/config/sl_btmesh_scene_client_config.h
new file mode 100644
index 00000000000..bd9747a6423
--- /dev/null
+++ b/app/btmesh/common/btmesh_scene_client/config/sl_btmesh_scene_client_config.h
@@ -0,0 +1,58 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_SCENE_CLIENT_CONFIG_H
+#define SL_BTMESH_SCENE_CLIENT_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Scene Client configuration
+
+// Scene model restransmission count
+// Default: 3
+// Scene model restransmission count (How many times Scene model messages are to be sent out for reliability).
+#define SL_BTMESH_SCENE_CLIENT_RETRANSMISSION_COUNT_CFG_VAL (3)
+
+// Scene model restransmission timeout
+// Default: 50
+// Scene model restransmission timeout.
+#define SL_BTMESH_SCENE_CLIENT_RETRANSMISSION_TIMEOUT_CFG_VAL (50)
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for Scene Client model specific messages for this component.
+#define SL_BTMESH_SCENE_CLIENT_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_SCENE_CLIENT_CONFIG_H
diff --git a/app/btmesh/common/btmesh_scene_client/sl_btmesh_scene_client.c b/app/btmesh/common/btmesh_scene_client/sl_btmesh_scene_client.c
new file mode 100644
index 00000000000..8f8367f02c2
--- /dev/null
+++ b/app/btmesh/common/btmesh_scene_client/sl_btmesh_scene_client.c
@@ -0,0 +1,209 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT mesh scene client module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "em_common.h"
+#include "sl_status.h"
+
+#include "sl_btmesh_api.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_scene_client_config.h"
+#include "sl_btmesh_scene_client.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup Scene Client
+ * @{
+ ******************************************************************************/
+
+/// High Priority
+#define HIGH_PRIORITY 0
+/// Callback has not parameters
+#define NO_CALLBACK_DATA (void *)NULL
+/// Immediate transition time is 0 seconds
+#define IMMEDIATE 0
+/// Parameter ignored for publishing
+#define IGNORED 0
+/// No flags used for message
+#define NO_FLAGS 0
+/// Delay unit value for request for ctl messages in millisecond
+#define REQ_DELAY_MS 50
+
+/// Address zero is used in scene client commands to indicate that
+/// message should be published
+static const uint16_t PUBLISH_ADDRESS = 0x0000;
+
+/// currently selected scene
+static uint16_t scene_number = 0;
+/// number of scene requests to be sent
+static uint8_t scene_request_count;
+/// scene transaction identifier
+static uint8_t scene_trid = 0;
+
+/// periodic timer handle
+static sl_simple_timer_t app_scene_retransmission_timer;
+
+/// periodic timer callback
+static void scene_retransmission_timer_cb(sl_simple_timer_t *handle,
+ void *data);
+
+/***************************************************************************//**
+ * This function publishes one scene recall request to recall selected scene.
+ * Global variable scene_number holds the latest desired scene state.
+ *
+ * param[in] retrans Indicates if this is the first request or a retransmission,
+ * possible values are 0 = first request, 1 = retransmission.
+ *
+ * @note This application sends multiple scene requests for each
+ * very long button press to improve reliability.
+ * The transaction ID is not incremented in case of a retransmission.
+ ******************************************************************************/
+static void send_scene_recall_request(uint8_t retrans)
+{
+ // Increment transaction ID for each request, unless it's a retransmission
+ if (retrans == 0) {
+ scene_trid++;
+ }
+
+ // Delay for the request is calculated so that the last request will have
+ // a zero delay and each of the previous request have delay that increases
+ // in 50 ms steps. For example, when using three scene requests
+ // per button press the delays are set as 100, 50, 0 ms
+ uint16_t delay = (scene_request_count - 1) * REQ_DELAY_MS;
+
+ sl_status_t sc = sl_btmesh_scene_client_recall(PUBLISH_ADDRESS,
+ BTMESH_SCENE_CLIENT_MAIN,
+ scene_number,
+ IGNORED,
+ NO_FLAGS,
+ scene_trid,
+ IMMEDIATE,
+ delay);
+
+ if (SL_STATUS_OK == sc) {
+ log_info("Scene request sent, trid = %u, delay = %u" NL, scene_trid, delay);
+ } else {
+ log_btmesh_status_f(sc, "Scene recall failed" NL);
+ }
+
+ // Keep track of how many requests has been sent
+ if (scene_request_count > 0) {
+ scene_request_count--;
+ }
+}
+
+/*******************************************************************************
+ * This function select scene and send it to the server.
+ *
+ * @param[in] scene_to_recall Scene to recall, possible values 1-255.
+ *
+ ******************************************************************************/
+void sl_btmesh_select_scene(uint8_t scene_to_recall)
+{
+ // Scene number 0 is prohibited
+ if (scene_to_recall == 0) {
+ return;
+ }
+
+ scene_number = scene_to_recall;
+
+ // Recall scene using Scene Client model
+ log_info("Recall scene number %u" NL, scene_number);
+ // Request is sent multiple times to improve reliability
+ scene_request_count = SL_BTMESH_SCENE_CLIENT_RETRANSMISSION_COUNT_CFG_VAL;
+
+ send_scene_recall_request(0); // Send the first request
+
+ // If there are more requests to send, start a repeating soft timer
+ // to trigger retransmission of the request after 50 ms delay
+ if (scene_request_count > 0) {
+ sl_status_t sc = sl_simple_timer_start(&app_scene_retransmission_timer,
+ SL_BTMESH_SCENE_CLIENT_RETRANSMISSION_TIMEOUT_CFG_VAL,
+ scene_retransmission_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic timer");
+ }
+}
+
+/**************************************************************************//**
+ * Handle Sensor Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt pointer to event descriptor
+ *****************************************************************************/
+void sl_btmesh_handle_scene_client_on_event(sl_btmesh_msg_t *evt)
+{
+ sl_status_t sc;
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ sc = sl_btmesh_scene_client_init(BTMESH_SCENE_CLIENT_MAIN);
+ app_assert_status_f(sc, "Failed to init scene client model");
+ break;
+ }
+}
+
+/**************************************************************************//**
+ * Timer Callbacks
+ * @param[in] handle pointer to handle instance
+ * @param[in] data pointer to input data
+ *****************************************************************************/
+static void scene_retransmission_timer_cb(sl_simple_timer_t *handle,
+ void *data)
+{
+ (void)data;
+ (void)handle;
+
+ send_scene_recall_request(1); // Retransmit scene message
+ // Stop retransmission timer if it was the last attempt
+ if (scene_request_count == 0) {
+ sl_status_t sc = sl_simple_timer_stop(&app_scene_retransmission_timer);
+ app_assert_status_f(sc, "Failed to stop periodic timer");
+ }
+}
+
+/** @} (end addtogroup Scene Client) */
diff --git a/app/btmesh/common/btmesh_scene_client/sl_btmesh_scene_client.h b/app/btmesh/common/btmesh_scene_client/sl_btmesh_scene_client.h
new file mode 100644
index 00000000000..c057730bb0b
--- /dev/null
+++ b/app/btmesh/common/btmesh_scene_client/sl_btmesh_scene_client.h
@@ -0,0 +1,51 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_scene_client.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_SCENE_CLIENT_H
+#define SL_BTMESH_SCENE_CLIENT_H
+
+/*******************************************************************************
+ * This function select scene and send it to the server.
+ *
+ * @param[in] scene_to_recall Scene to recall, possible values 1-255.
+ *
+ ******************************************************************************/
+void sl_btmesh_select_scene(uint8_t scene_to_recall);
+
+/**************************************************************************//**
+ * Handle Sensor Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ *****************************************************************************/
+void sl_btmesh_handle_scene_client_on_event(sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_SCENE_CLIENT_H
diff --git a/app/btmesh/common/btmesh_scene_server/btmesh_scene_server.dcd b/app/btmesh/common/btmesh_scene_server/btmesh_scene_server.dcd
new file mode 100644
index 00000000000..0f12e96af15
--- /dev/null
+++ b/app/btmesh/common/btmesh_scene_server/btmesh_scene_server.dcd
@@ -0,0 +1,10 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1203", "name":"Scene Server"},
+ {"mid":"0x1204", "name":"Scene Setup Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_scene_server/sl_btmesh_scene_server.c b/app/btmesh/common/btmesh_scene_server/sl_btmesh_scene_server.c
new file mode 100644
index 00000000000..a98c40c2eeb
--- /dev/null
+++ b/app/btmesh/common/btmesh_scene_server/sl_btmesh_scene_server.c
@@ -0,0 +1,89 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT mesh scene server module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "app_assert.h"
+#include "sl_btmesh_scene_server.h"
+
+/***************************************************************************//**
+ * @addtogroup Scene Server
+ * @{
+ ******************************************************************************/
+
+/*******************************************************************************
+ * Scenes initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ *
+ * @param[in] element Index of the element where scenes models are initialized.
+ *
+ * @return Status of the initialization operation.
+ * Returns bg_err_success (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+uint16_t sl_btmesh_scenes_init(void)
+{
+ // Initialize scenes server models
+ sl_status_t result;
+ result = sl_btmesh_scene_server_init(BTMESH_SCENE_SERVER_MAIN);
+ app_assert_status_f(result, "Failed to init scene server model");
+
+ result = sl_btmesh_scene_setup_server_init(BTMESH_SCENE_SERVER_MAIN);
+ app_assert_status_f(result, "Failed to init scene setup server model");
+
+ return result;
+}
+
+/*******************************************************************************
+ * Handling of mesh scene events.
+ *
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @param[in] evt Pointer to incoming scene server event.
+ ******************************************************************************/
+void sl_btmesh_scene_server_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_scenes_init();
+ break;
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_scenes_init();
+ }
+ break;
+ }
+}
+
+/** @} (end addtogroup Scene Server) */
diff --git a/app/btmesh/common/btmesh_scene_server/sl_btmesh_scene_server.h b/app/btmesh/common/btmesh_scene_server/sl_btmesh_scene_server.h
new file mode 100644
index 00000000000..93fb8c85fca
--- /dev/null
+++ b/app/btmesh/common/btmesh_scene_server/sl_btmesh_scene_server.h
@@ -0,0 +1,56 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_scene_server.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_SCENE_SERVER_H
+#define SL_BTMESH_SCENE_SERVER_H
+
+/***************************************************************************//**
+ * Scenes initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @param[in] element Index of the element where scenes models are initialized.
+ *
+ * @return Status of the initialization operation.
+ * Returns bg_err_success (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+uint16_t sl_btmesh_scenes_init(void);
+
+/***************************************************************************//**
+ * Handling of mesh scene events.
+ *
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @param[in] evt Pointer to incoming scene server event.
+ ******************************************************************************/
+void sl_btmesh_scene_server_on_event(sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_SCENE_SERVER_H
diff --git a/app/btmesh/common/btmesh_scheduler_server/btmesh_scheduler_server.dcd b/app/btmesh/common/btmesh_scheduler_server/btmesh_scheduler_server.dcd
new file mode 100644
index 00000000000..d2f4ee62360
--- /dev/null
+++ b/app/btmesh/common/btmesh_scheduler_server/btmesh_scheduler_server.dcd
@@ -0,0 +1,10 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1206", "name":"Scheduler Server"},
+ {"mid":"0x1207", "name":"Scheduler Setup Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_scheduler_server/config/sl_btmesh_scheduler_server_config.h b/app/btmesh/common/btmesh_scheduler_server/config/sl_btmesh_scheduler_server_config.h
new file mode 100644
index 00000000000..28e6853aff7
--- /dev/null
+++ b/app/btmesh/common/btmesh_scheduler_server/config/sl_btmesh_scheduler_server_config.h
@@ -0,0 +1,48 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_SCHEDULER_SERVER_CONFIG_H
+#define SL_BTMESH_SCHEDULER_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Scheduler Server configuration
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for Scheduler Server model specific messages for this component.
+#define SL_BTMESH_SCHEDULER_SERVER_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_SCHEDULER_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_scheduler_server/sl_btmesh_scheduler_server.c b/app/btmesh/common/btmesh_scheduler_server/sl_btmesh_scheduler_server.c
new file mode 100644
index 00000000000..543eeebf8ae
--- /dev/null
+++ b/app/btmesh/common/btmesh_scheduler_server/sl_btmesh_scheduler_server.c
@@ -0,0 +1,188 @@
+/***************************************************************************//**
+ * @file
+ * @brief Scheduler Server module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#include
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_scheduler_server.h"
+#include "sl_btmesh_scheduler_server_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup Scheduler Server
+ * @{
+ ******************************************************************************/
+
+/*******************************************************************************
+ * Scheduler initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ *
+ * @param[in] element Index of the element where scheduler model is initialized.
+ *
+ * @return Status of the initialization operation.
+ * Returns bg_err_success (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+uint16_t sl_btmesh_scheduler_init(void)
+{
+ // Initialize scheduler server models
+ sl_status_t result = sl_btmesh_scheduler_server_init(BTMESH_SCHEDULER_SERVER_MAIN);
+
+ log_status_error_f(result, "sl_btmesh_scheduler_server_init failed" NL);
+
+ return result;
+}
+
+/***************************************************************************//**
+ * Handling of scheduler server action changed event.
+ *
+ * @param[in] evt Pointer to scheduler server action changed event.
+ ******************************************************************************/
+static void handle_scheduler_server_action_changed_event(
+ sl_btmesh_evt_scheduler_server_action_changed_t *evt)
+{
+ log_info("evt:gecko_evt_mesh_scheduler_server_action_changed_id, \
+elem_index=%u, index=%u, ", evt->elem_index, evt->index);
+ if (evt->year == 100) {
+ log_append_info("year=Every, ");
+ } else {
+ log_append_info("year=%u, ", evt->year);
+ }
+ log_append_info("month=0x%03x, ", evt->month);
+ if (evt->day) {
+ log_append_info("day=0x%02x, ", evt->day);
+ } else {
+ log_append_info("day=Every, ");
+ }
+ if (evt->hour == 0x18) {
+ log_append_info("hour=Every, ");
+ } else if (evt->hour == 0x19) {
+ log_append_info("hour=Random, ");
+ } else {
+ log_append_info("hour=%u, ", evt->hour);
+ }
+ if (evt->minute == 0x3c) {
+ log_append_info("minute=Every, ");
+ } else if (evt->minute == 0x3d) {
+ log_append_info("minute=Every 15, ");
+ } else if (evt->minute == 0x3e) {
+ log_append_info("minute=Every 20, ");
+ } else if (evt->minute == 0x3f) {
+ log_append_info("minute=Random, ");
+ } else {
+ log_append_info("minute=%u, ", evt->minute);
+ }
+ if (evt->second == 0x3c) {
+ log_append_info("second=Every, ");
+ } else if (evt->second == 0x3d) {
+ log_append_info("second=Every 15, ");
+ } else if (evt->second == 0x3e) {
+ log_append_info("second=Every 20, ");
+ } else if (evt->second == 0x3f) {
+ log_append_info("second=Random, ");
+ } else {
+ log_append_info("second=%u, ", evt->second);
+ }
+ log_append_info("day of the week=0x%02x, ", evt->day_of_week);
+ log_append_info("action=");
+ switch (evt->action) {
+ case 0x0:
+ log_append_info("Turn Off");
+ break;
+
+ case 0x1:
+ log_append_info("Turn On");
+ break;
+
+ case 0x2:
+ log_append_info("Scene Recall");
+ break;
+
+ case 0xf:
+ log_append_info("No action");
+ break;
+
+ default:
+ break;
+ }
+ log_append_info(", transition time=%lu ms, ", evt->transition_time_ms);
+ if (evt->scene_number) {
+ log_append_info("scene number=0x%04x", evt->scene_number);
+ } else {
+ log_append_info("scene number=No scene");
+ }
+ log_append_info(NL);
+}
+
+/*******************************************************************************
+ * Handling of mesh scheduler events.
+ * It handles:
+ * - scheduler_server_action_changed
+ *
+ * @param[in] evt Pointer to incoming scheduler event.
+ ******************************************************************************/
+void sl_btmesh_scheduler_server_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_scheduler_server_action_changed_id:
+ handle_scheduler_server_action_changed_event(
+ &(evt->data.evt_scheduler_server_action_changed));
+ break;
+
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_scheduler_init();
+ }
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_scheduler_init();
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup Scheduler Server) */
diff --git a/app/btmesh/common/btmesh_scheduler_server/sl_btmesh_scheduler_server.h b/app/btmesh/common/btmesh_scheduler_server/sl_btmesh_scheduler_server.h
new file mode 100644
index 00000000000..eb862f87dae
--- /dev/null
+++ b/app/btmesh/common/btmesh_scheduler_server/sl_btmesh_scheduler_server.h
@@ -0,0 +1,54 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_scheduler_server.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_SCHEDULER_SERVER_H
+#define SL_BTMESH_SCHEDULER_SERVER_H
+
+/***************************************************************************//**
+ * Scheduler initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @return Status of the initialization operation.
+ * Returns bg_err_success (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+uint16_t sl_btmesh_scheduler_init(void);
+
+/***************************************************************************//**
+ * Handling of mesh scheduler events.
+ *
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @param[in] evt Pointer to incoming time event.
+ ******************************************************************************/
+void sl_btmesh_scheduler_server_on_event(sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_SCHEDULER_SERVER_H
diff --git a/app/btmesh/common/btmesh_self_provisioning_cli/config/sl_btmesh_self_provisioning_cli_config.h b/app/btmesh/common/btmesh_self_provisioning_cli/config/sl_btmesh_self_provisioning_cli_config.h
new file mode 100644
index 00000000000..adabbd89564
--- /dev/null
+++ b/app/btmesh/common/btmesh_self_provisioning_cli/config/sl_btmesh_self_provisioning_cli_config.h
@@ -0,0 +1,48 @@
+/***************************************************************************//**
+ * @file sl_btmesh_self_provisioning_cli_config.h
+ * @brief Self Provisioning Component Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_SELF_PROVISIONING_CLI_CONFIG_H
+#define SL_BTMESH_SELF_PROVISIONING_CLI_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Self Provisioning configuration
+
+// Enable Logging
+// Default: 1
+// Enable / disable UART Logging for Self Provisioning specific messages for this component.
+#define SL_BTMESH_SELF_PROVISIONING_CLI_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_SELF_PROVISIONING_CLI_CONFIG_H
diff --git a/app/btmesh/common/btmesh_self_provisioning_cli/sl_btmesh_self_provisioning_cli.c b/app/btmesh/common/btmesh_self_provisioning_cli/sl_btmesh_self_provisioning_cli.c
new file mode 100644
index 00000000000..85ac8dbe45d
--- /dev/null
+++ b/app/btmesh/common/btmesh_self_provisioning_cli/sl_btmesh_self_provisioning_cli.c
@@ -0,0 +1,246 @@
+/***************************************************************************//**
+ * @file sl_btmesh_self_provisioning.c
+ * @brief Self Provisioning module
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "em_common.h"
+#include "sl_cli.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_self_provisioning_cli_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup SelfProvisioning
+ * @{
+ ******************************************************************************/
+
+// Key position
+#define KEY_INDEX 0
+// Old key data. Old key data can be queried only during the key refresh.
+#define OLD_KEY_DATA 0
+// Current key data
+#define CURRENT_KEY_DATA 1
+// Key length
+#define KEY_LENGTH 16
+// Address length
+#define ADDRESS_LENGTH 2
+// Current IV Index used in the network
+#define IV_INDEX 0
+// Current operation is not a key refresh
+#define NOT_KEY_REFRESH 0
+// Network key index to which the application key is bound
+#define APPKEY_BOUND_TO_NETKEY 0
+
+/***************************************************************************//**
+ // Static Function Declarations
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Self provisioning according to input parameters
+ *
+ * @dev_key[in] - device key
+ * @dev_key[in] - network key
+ * @dev_key[in] - primary element address
+ ******************************************************************************/
+static void sl_btmesh_self_provisioning(aes_key_128 dev_key,
+ aes_key_128 net_key,
+ uint16_t address);
+
+/***************************************************************************//**
+ * Add an application key locally.
+ *
+ * @app_key[in] - application key
+ ******************************************************************************/
+static void sl_btmesh_add_app_key(aes_key_128 app_key);
+
+/***************************************************************************//**
+ * CLI Callback
+ * @param[in] arguments pointer to CLI arguments
+ ******************************************************************************/
+#ifdef SL_CATALOG_CLI_PRESENT
+void sl_btmesh_self_provisioning_from_cli(sl_cli_command_arg_t *arguments)
+{
+ size_t arg_length;
+ uint8_t *input_arguments;
+ aes_key_128 network_key;
+ aes_key_128 device_key;
+ uint16_t primary_address;
+
+ // Get the first argument - device key
+ input_arguments = sl_cli_get_argument_hex(arguments, 0, &arg_length);
+ if (arg_length != KEY_LENGTH) {
+ log_warning("Wrong device key length: %u, should be %d"NL, arg_length, KEY_LENGTH);
+ return;
+ }
+ memcpy(device_key.data, input_arguments, arg_length);
+
+ // Get the second argument - network key
+ input_arguments = sl_cli_get_argument_hex(arguments, 1, &arg_length);
+ if (arg_length != KEY_LENGTH) {
+ log_warning("Wrong network key length: %u, should be %d"NL, arg_length, KEY_LENGTH);
+ return;
+ }
+ memcpy(network_key.data, input_arguments, arg_length);
+
+ // Get the third argument - primary element address
+ input_arguments = sl_cli_get_argument_hex(arguments, 2, &arg_length);
+ if (arg_length != ADDRESS_LENGTH) {
+ log_warning("Wrong primary element address length: %u, should be %d"NL, arg_length, ADDRESS_LENGTH);
+ return;
+ }
+ memcpy(&primary_address, input_arguments, arg_length);
+
+ sl_btmesh_self_provisioning(device_key, network_key, primary_address);
+}
+
+void sl_btmesh_system_reset_from_cli(sl_cli_command_arg_t *arguments)
+{
+ (void)arguments;
+ sl_bt_system_reset(0);
+}
+
+void sl_btmesh_add_app_key_from_cli(sl_cli_command_arg_t *arguments)
+{
+ size_t arg_length;
+ uint8_t *input_arguments;
+ aes_key_128 application_key;
+
+ // Get the argument - application key
+ input_arguments = sl_cli_get_argument_hex(arguments, 0, &arg_length);
+ if (arg_length != KEY_LENGTH) {
+ log_warning("Wrong device key length: %u, should be %d"NL, arg_length, KEY_LENGTH);
+ return;
+ }
+ memcpy(application_key.data, input_arguments, arg_length);
+
+ sl_btmesh_add_app_key(application_key);
+}
+#endif // SL_CATALOG_CLI_PRESENT
+
+/***************************************************************************//**
+ // Helper functions
+ ******************************************************************************/
+void sl_btmesh_self_provisioning(aes_key_128 dev_key,
+ aes_key_128 net_key,
+ uint16_t address)
+{
+ sl_status_t sc;
+ uint32_t key_count;
+
+ log_info("Self provisioning started"NL);
+
+ // Get the stored network key number
+ sc = sl_btmesh_node_get_key_count(sl_btmesh_node_key_type_net, &key_count);
+ if (sc != SL_STATUS_OK) {
+ log_status_warning_f(sc, "Failed to get key count" NL);
+ return;
+ }
+
+ // If there is stored network key the node is considered already provisioned
+ if (key_count != 0) {
+ log_warning("Netkey already stored: %ld"NL, key_count);
+ return;
+ }
+
+ // Stop the unprovisioned beaconing
+ sc = sl_btmesh_node_stop_unprov_beaconing();
+ if (sc != SL_STATUS_OK) {
+ log_status_warning_f(sc, "Failed to stop unprovisioned beaconing" NL);
+ return;
+ }
+
+ // Save the provisioning data to make the device reachable and configurable
+ // in the Provisioner's network.
+ sc = sl_btmesh_node_set_provisioning_data(dev_key,
+ net_key,
+ KEY_INDEX,
+ IV_INDEX,
+ address,
+ NOT_KEY_REFRESH);
+ if (sc != SL_STATUS_OK) {
+ log_status_warning_f(sc, "Failed to set provisioning data" NL);
+ return;
+ }
+
+ log_info("Self provisioning successful" NL);
+
+ // The device must be reset after saving provisioning data.
+ sl_bt_system_reset(0);
+}
+
+void sl_btmesh_add_app_key(aes_key_128 app_key)
+{
+ sl_status_t sc;
+ aes_key_128 app_key_check;
+ uint16_t id;
+ uint16_t netkey_index;
+
+ // Add application key
+ sc = sl_btmesh_test_add_local_key(sl_btmesh_test_key_type_app,
+ app_key,
+ KEY_INDEX,
+ APPKEY_BOUND_TO_NETKEY);
+ if (sc != SL_STATUS_OK) {
+ log_status_warning_f(sc, "Failed to add app key" NL);
+ return;
+ }
+
+ // Get the application key
+ sc = sl_btmesh_node_get_key(sl_btmesh_test_key_type_app,
+ KEY_INDEX,
+ CURRENT_KEY_DATA,
+ &id,
+ &netkey_index,
+ &app_key_check);
+ if (sc != SL_STATUS_OK) {
+ log_status_warning_f(sc, "Failed to get app key" NL);
+ return;
+ }
+
+ log_info("Application key added: ");
+ for (size_t count = 0; count < KEY_LENGTH; count++) {
+ log_append_info("%02x", app_key_check.data[count]);
+ }
+ log_nl();
+}
+
+/** @} (end addtogroup SelfProvisioning) */
diff --git a/app/btmesh/common/btmesh_sensor_client/btmesh_sensor_client.dcd b/app/btmesh/common/btmesh_sensor_client/btmesh_sensor_client.dcd
new file mode 100644
index 00000000000..d5ddb4af2e8
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_client/btmesh_sensor_client.dcd
@@ -0,0 +1,9 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1102", "name":"Sensor Client"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_sensor_client/config/sl_btmesh_sensor_client_config.h b/app/btmesh/common/btmesh_sensor_client/config/sl_btmesh_sensor_client_config.h
new file mode 100644
index 00000000000..42097930f5f
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_client/config/sl_btmesh_sensor_client_config.h
@@ -0,0 +1,53 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_SENSOR_CLIENT_CONFIG_H
+#define SL_BTMESH_SENSOR_CLIENT_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Sensor Client configuration
+
+// How many sensors can fit on screen
+// Default: 5
+// Defines the number of sensors which can fit on the LCD screen.
+#define SL_BTMESH_SENSOR_CLIENT_DISPLAYED_SENSORS_CFG_VAL (5)
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for Sensor Client model specific messages for this component.
+#define SL_BTMESH_SENSOR_CLIENT_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_SENSOR_CLIENT_CONFIG_H
diff --git a/app/btmesh/common/btmesh_sensor_client/sl_btmesh_sensor_client.c b/app/btmesh/common/btmesh_sensor_client/sl_btmesh_sensor_client.c
new file mode 100644
index 00000000000..bc1dfee4b8d
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_client/sl_btmesh_sensor_client.c
@@ -0,0 +1,459 @@
+/***************************************************************************//**
+ * @file
+ * @brief Sensor client module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#include "app_assert.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_sensor_client_config.h"
+#include "sl_btmesh_sensor_client.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup SensorClient
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup SensorClient
+ * @{
+ ******************************************************************************/
+
+/// Number of supported properties
+#define PROPERTIES_NUMBER 3
+/// Parameter ignored for publishing
+#define IGNORED 0
+/// No flags used for message
+#define NO_FLAGS 0
+/// The size of descriptor is 8 bytes
+#define SIZE_OF_DESCRIPTOR 8
+/// Size of property ID in bytes
+#define PROPERTY_ID_SIZE 2
+/// Size of property header in bytes
+#define PROPERTY_HEADER_SIZE 3
+/// Sensor index value for not registered devices
+#define SENSOR_INDEX_NOT_FOUND 0xFF
+
+// Address zero is used in sensor client commands to indicate that
+// the message should be published
+static const uint16_t PUBLISH_ADDRESS = 0x0000;
+
+typedef struct {
+ uint16_t address_table[SL_BTMESH_SENSOR_CLIENT_DISPLAYED_SENSORS_CFG_VAL];
+ uint8_t count;
+} mesh_registered_device_properties_address_t;
+
+static bool mesh_address_already_exists(mesh_registered_device_properties_address_t* property,
+ uint16_t address);
+static uint8_t mesh_get_sensor_index(mesh_registered_device_properties_address_t* property,
+ uint16_t address);
+static void mesh_sensor_client_init(void);
+
+static mesh_registered_device_properties_address_t registered_devices = {
+ .count = 0,
+};
+
+static mesh_device_properties_t registering_property = DEVICE_PROPERTY_INVALID;
+
+// -----------------------------------------------------------------------------
+// Sensor Model Callbacks
+
+SL_WEAK void sl_btmesh_sensor_client_on_discovery_started(uint16_t property_id)
+{
+ (void)property_id;
+}
+
+SL_WEAK void sl_btmesh_sensor_client_on_new_device_found(uint16_t property_id,
+ uint16_t address)
+{
+ (void)property_id;
+ (void)address;
+}
+
+SL_WEAK void sl_btmesh_sensor_client_on_new_temperature_data(uint8_t sensor_idx,
+ uint16_t address,
+ sl_btmesh_sensor_client_data_status_t status,
+ temperature_8_t temperature)
+{
+ (void) sensor_idx;
+ (void) address;
+ (void) status;
+ (void) temperature;
+}
+
+SL_WEAK void sl_btmesh_sensor_client_on_new_people_count_data(uint8_t sensor_idx,
+ uint16_t address,
+ sl_btmesh_sensor_client_data_status_t status,
+ count16_t people_count)
+{
+ (void) sensor_idx;
+ (void) address;
+ (void) status;
+ (void) people_count;
+}
+
+SL_WEAK void sl_btmesh_sensor_client_on_new_illuminance_data(uint8_t sensor_idx,
+ uint16_t address,
+ sl_btmesh_sensor_client_data_status_t status,
+ illuminance_t illuminance)
+{
+ (void) sensor_idx;
+ (void) address;
+ (void) status;
+ (void) illuminance;
+}
+
+/*******************************************************************************
+ * Publishing of sensor client get descriptor request for currently displayed
+ * property id. It also resets the registered devices counter.
+ ******************************************************************************/
+sl_status_t sl_btmesh_sensor_client_update_registered_devices(mesh_device_properties_t property)
+{
+ sl_status_t sc;
+ registered_devices.count = 0;
+ memset(registered_devices.address_table,
+ 0,
+ sizeof(registered_devices.address_table));
+ registering_property = property;
+
+ sl_btmesh_sensor_client_on_discovery_started(property);
+
+ sc = sl_btmesh_sensor_client_get_descriptor(PUBLISH_ADDRESS,
+ BTMESH_SENSOR_CLIENT_MAIN,
+ IGNORED,
+ NO_FLAGS,
+ property);
+ if (SL_STATUS_OK == sc) {
+ log_info("Registration of devices for property ID %4.4x started" NL, property);
+ } else {
+ log_btmesh_status_f(sc,
+ "Registration of devices for property ID %4.4x failed" NL,
+ property);
+ }
+ return sc;
+}
+
+/***************************************************************************//**
+ * Handling of sensor client descriptor status event.
+ *
+ * @param[in] evt Pointer to sensor client descriptor status event.
+ ******************************************************************************/
+static void handle_sensor_client_descriptor_status(
+ sl_btmesh_evt_sensor_client_descriptor_status_t *evt)
+{
+ sensor_descriptor_t descriptor;
+ if (evt->descriptors.len >= SIZE_OF_DESCRIPTOR) {
+ mesh_lib_sensor_descriptors_from_buf(&descriptor,
+ evt->descriptors.data,
+ SIZE_OF_DESCRIPTOR);
+ uint8_t number_of_devices = registered_devices.count;
+ if (descriptor.property_id == registering_property
+ && number_of_devices < SL_BTMESH_SENSOR_CLIENT_DISPLAYED_SENSORS_CFG_VAL
+ && !mesh_address_already_exists(®istered_devices,
+ evt->server_address)) {
+ registered_devices.address_table[number_of_devices] = evt->server_address;
+ registered_devices.count = number_of_devices + 1;
+ sl_btmesh_sensor_client_on_new_device_found(descriptor.property_id,
+ evt->server_address);
+ }
+ }
+}
+
+/*******************************************************************************
+ * Publishing of sensor client get request for currently displayed property id.
+ ******************************************************************************/
+sl_status_t sl_btmesh_sensor_client_get_sensor_data(mesh_device_properties_t property)
+{
+ sl_status_t sc;
+
+ sc = sl_btmesh_sensor_client_get(PUBLISH_ADDRESS,
+ BTMESH_SENSOR_CLIENT_MAIN,
+ IGNORED,
+ NO_FLAGS,
+ property);
+
+ if (SL_STATUS_OK == sc) {
+ log_info("Get Sensor Data from property ID %4.4x started" NL, property);
+ } else {
+ log_btmesh_status_f(sc,
+ "Get Sensor Data from property ID %4.4x failed" NL,
+ property);
+ }
+ return sc;
+}
+
+/***************************************************************************//**
+ * Handling of sensor client status event.
+ *
+ * @param[in] evt Pointer to sensor client status event.
+ ******************************************************************************/
+static void handle_sensor_client_status(sl_btmesh_evt_sensor_client_status_t *evt)
+{
+ uint8_t *sensor_data = evt->sensor_data.data;
+ uint8_t data_len = evt->sensor_data.len;
+ uint8_t pos = 0;
+ while (pos < data_len) {
+ if (data_len - pos > PROPERTY_ID_SIZE) {
+ mesh_device_properties_t property_id = (mesh_device_properties_t)(sensor_data[pos]
+ + (sensor_data[pos + 1] << 8));
+ uint8_t property_len = sensor_data[pos + PROPERTY_ID_SIZE];
+ uint8_t *property_data = NULL;
+
+ if (mesh_address_already_exists(®istered_devices, evt->server_address)) {
+ sl_btmesh_sensor_client_data_status_t status;
+ uint16_t address;
+ uint8_t sensor_idx;
+
+ if (property_len && (data_len - pos > PROPERTY_HEADER_SIZE)) {
+ property_data = &sensor_data[pos + PROPERTY_HEADER_SIZE];
+ }
+
+ address = evt->server_address;
+ sensor_idx = mesh_get_sensor_index(®istered_devices, address);
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
+
+ switch (property_id) {
+ case PEOPLE_COUNT:
+ {
+ count16_t people_count = SL_BTMESH_SENSOR_CLIENT_PEOPLE_COUNT_UNKNOWN;
+
+ if (property_len == 2) {
+ mesh_device_property_t new_property = mesh_sensor_data_from_buf(PEOPLE_COUNT,
+ property_data);
+ people_count = new_property.count16;
+
+ if (people_count == SL_BTMESH_SENSOR_CLIENT_PEOPLE_COUNT_UNKNOWN) {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN;
+ } else {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_VALID;
+ }
+ } else {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
+ }
+
+ sl_btmesh_sensor_client_on_new_people_count_data(sensor_idx,
+ address,
+ status,
+ people_count);
+ break;
+ }
+
+ case PRESENT_AMBIENT_TEMPERATURE:
+ {
+ temperature_8_t temperature = SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN;
+
+ if (property_len == 1) {
+ mesh_device_property_t new_property = mesh_sensor_data_from_buf(PRESENT_AMBIENT_TEMPERATURE,
+ property_data);
+ temperature = new_property.temperature_8;
+
+ if (temperature == SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN) {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN;
+ } else {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_VALID;
+ }
+ } else {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
+ }
+
+ sl_btmesh_sensor_client_on_new_temperature_data(sensor_idx,
+ address,
+ status,
+ temperature);
+ break;
+ }
+
+ case PRESENT_AMBIENT_LIGHT_LEVEL:
+ {
+ illuminance_t illuminance = SL_BTMESH_SENSOR_CLIENT_ILLUMINANCE_UNKNOWN;
+
+ if (property_len == 3) {
+ mesh_device_property_t new_property = mesh_sensor_data_from_buf(PRESENT_AMBIENT_LIGHT_LEVEL,
+ property_data);
+ illuminance = new_property.illuminance;
+
+ if (illuminance == SL_BTMESH_SENSOR_CLIENT_ILLUMINANCE_UNKNOWN) {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN;
+ } else {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_VALID;
+ }
+ } else {
+ status = SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE;
+ }
+
+ sl_btmesh_sensor_client_on_new_illuminance_data(sensor_idx,
+ address,
+ status,
+ illuminance);
+ break;
+ }
+
+ default:
+ log_info("Unsupported property id %4.4x" NL, property_id);
+ break;
+ }
+ }
+ pos += PROPERTY_HEADER_SIZE + property_len;
+ } else {
+ pos = data_len;
+ }
+ }
+}
+
+/*******************************************************************************
+ * Handling of mesh sensor client events.
+ * It handles:
+ * - sensor_client_descriptor_status
+ * - sensor_client_status
+ *
+ * @param[in] evt Pointer to incoming sensor server event.
+ ******************************************************************************/
+static void handle_sensor_client_events(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_sensor_client_descriptor_status_id:
+ handle_sensor_client_descriptor_status(
+ &(evt->data.evt_sensor_client_descriptor_status));
+ break;
+
+ case sl_btmesh_evt_sensor_client_status_id:
+ handle_sensor_client_status(
+ &(evt->data.evt_sensor_client_status));
+ break;
+
+ default:
+ break;
+ }
+}
+
+/*******************************************************************************
+ * Handle Sensor Client events.
+ ******************************************************************************/
+void sl_btmesh_handle_sensor_client_on_event(sl_btmesh_msg_t *evt)
+{
+ if (NULL == evt) {
+ return;
+ }
+
+ // Handle events
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ mesh_sensor_client_init();
+ }
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ mesh_sensor_client_init();
+ break;
+
+ case sl_btmesh_evt_sensor_client_descriptor_status_id:
+ case sl_btmesh_evt_sensor_client_status_id:
+ handle_sensor_client_events(evt);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+ * Check if the mesh address already exists or not.
+ *
+ * @param[in] property Pointer to registered devices' properties
+ * @param[in] address Mesh address to check
+ *
+ * @return true: The address exists
+ * false: The address doesn't exist
+ ******************************************************************************/
+static bool mesh_address_already_exists(mesh_registered_device_properties_address_t *property,
+ uint16_t address)
+{
+ bool address_exists = false;
+ if (property != NULL) {
+ for (int i = 0; i < SL_BTMESH_SENSOR_CLIENT_DISPLAYED_SENSORS_CFG_VAL; i++) {
+ if (address == property->address_table[i]) {
+ address_exists = true;
+ break;
+ }
+ }
+ }
+ return address_exists;
+}
+
+/***************************************************************************//**
+ * Gets the sensor index.
+ *
+ * @param[in] property Pointer to registered devices' properties
+ * @param[in] address Mesh address of the sensor
+ *
+ * @return Index of the sensor
+ ******************************************************************************/
+static uint8_t mesh_get_sensor_index(mesh_registered_device_properties_address_t *property,
+ uint16_t address)
+{
+ uint8_t sensor_index = SENSOR_INDEX_NOT_FOUND;
+ if (property != NULL) {
+ for (int i = 0; i < SL_BTMESH_SENSOR_CLIENT_DISPLAYED_SENSORS_CFG_VAL; i++) {
+ if (address == property->address_table[i]) {
+ sensor_index = i;
+ break;
+ }
+ }
+ }
+ return sensor_index;
+}
+
+/***************************************************************************//**
+ * Initializes sensor client component
+ ******************************************************************************/
+static void mesh_sensor_client_init(void)
+{
+ sl_status_t sc = sl_btmesh_sensor_client_init();
+
+ app_assert_status_f(sc, "Failed to initialize sensor client");
+}
+
+/** @} (end addtogroup SensorClient) */
diff --git a/app/btmesh/common/btmesh_sensor_client/sl_btmesh_sensor_client.h b/app/btmesh/common/btmesh_sensor_client/sl_btmesh_sensor_client.h
new file mode 100644
index 00000000000..d1f773be812
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_client/sl_btmesh_sensor_client.h
@@ -0,0 +1,201 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_sensor_client.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_SENSOR_CLIENT_H
+#define SL_BTMESH_SENSOR_CLIENT_H
+
+#include "sl_btmesh_sensor.h"
+#include "sl_status.h"
+#include "sl_enum.h"
+
+// -----------------------------------------------------------------------------
+// Sensor Data Constants which represents the unknown value of each sensors
+
+#define SL_BTMESH_SENSOR_CLIENT_ILLUMINANCE_UNKNOWN ((illuminance_t) 0xFFFFFF)
+#define SL_BTMESH_SENSOR_CLIENT_PEOPLE_COUNT_UNKNOWN ((count16_t) 0xFFFF)
+#define SL_BTMESH_SENSOR_CLIENT_TEMPERATURE_UNKNOWN ((temperature_8_t) 0x7F)
+
+/***************************************************************************//**
+ * Enumeration representing the status of the sensor data received from the
+ * sensor server.
+ ******************************************************************************/
+SL_ENUM(sl_btmesh_sensor_client_data_status_t){
+ /// Valid sensor data
+ SL_BTMESH_SENSOR_CLIENT_DATA_VALID = 0,
+
+ /// No valid measured data is available
+ SL_BTMESH_SENSOR_CLIENT_DATA_UNKNOWN = 1,
+
+ /// No sensor is available on the sensor server
+ SL_BTMESH_SENSOR_CLIENT_DATA_NOT_AVAILABLE = 2
+};
+
+// -----------------------------------------------------------------------------
+// Callbacks
+
+/***************************************************************************//**
+ * Called when Sensor Server discovery is started
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ ******************************************************************************/
+void sl_btmesh_sensor_client_on_discovery_started(uint16_t property_id);
+
+/***************************************************************************//**
+ * Called when a Device with the current Device Property ID was found
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] property_id New property ID for which the new device
+ * was registered.
+ * @param[in] address Address of the new device.
+ ******************************************************************************/
+void sl_btmesh_sensor_client_on_new_device_found(uint16_t property_id,
+ uint16_t address);
+
+/***************************************************************************//**
+ * Called when temperature sensor data is received from one of the
+ * registered devices
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] sensor_idx The sensor index represents the order the sensor
+ * servers were registered.
+ * @param[in] address Address of the sensor server.
+ * @param[in] status Determines if the data is valid, available or unknown
+ * @param[in] temperature Measured temperature on the sensor server
+ * in 0.5 degree Celsius steps.
+ ******************************************************************************/
+void sl_btmesh_sensor_client_on_new_temperature_data(uint8_t sensor_idx,
+ uint16_t address,
+ sl_btmesh_sensor_client_data_status_t status,
+ temperature_8_t temperature);
+
+/***************************************************************************//**
+ * Called when people count sensor data is received from one of the
+ * registered devices.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] sensor_idx The sensor index represents the order the sensor
+ * servers were registered.
+ * @param[in] address Address of the sensor server.
+ * @param[in] status Determines if the data is valid, available or unknown
+ * @param[in] temperature Measured people count on the sensor server.
+ ******************************************************************************/
+void sl_btmesh_sensor_client_on_new_people_count_data(uint8_t sensor_idx,
+ uint16_t address,
+ sl_btmesh_sensor_client_data_status_t status,
+ count16_t people_count);
+
+/***************************************************************************//**
+ * Called when illuminance sensor data is received from one of the
+ * registered devices.
+ *
+ * This is a callback which can be implemented in the application.
+ * @note If no implementation is provided in the application,
+ * then the default weak implementation will be an empty function.
+ *
+ * @param[in] sensor_idx The sensor index represents the order the sensor
+ * servers were registered.
+ * @param[in] address Address of the sensor server.
+ * @param[in] status Determines if the data is valid, available or unknown
+ * @param[in] illuminance Measured illuminance on the sensor server in lux with
+ * a resolution of 0.01.
+ * (0xFFFFFF represents unknown data)
+ ******************************************************************************/
+void sl_btmesh_sensor_client_on_new_illuminance_data(uint8_t sensor_idx,
+ uint16_t address,
+ sl_btmesh_sensor_client_data_status_t status,
+ illuminance_t illuminance);
+
+// -----------------------------------------------------------------------------
+// Functions
+
+/***************************************************************************//**
+ * Updating the list of registered devices.
+ * All previously registered device will be lost.
+ *
+ * Generated callback:
+ * SL_WEAK void sl_btmesh_sensor_client_on_discovery_started(uint16_t property_id);
+ * SL_WEAK void sl_btmesh_sensor_client_on_new_device_found(uint16_t property_id,
+ * uint16_t address);
+ *
+ * @param[in] property New property ID for which devices need to be
+ * registered.
+ *
+ * @return Status of the operation.
+ * Returns SL_STATUS_OK(0) if succeeded, non-zero otherwise.
+ ******************************************************************************/
+sl_status_t sl_btmesh_sensor_client_update_registered_devices(mesh_device_properties_t property);
+
+/***************************************************************************//**
+ * Requesting new sensor data for the currently selected sensor property ID.
+ *
+ * Generated callback:
+ *
+ * Called callback signature depends on the received sensor data type.
+ * The <> symbols marks the part which is different due to the different sensor
+ * types in these callbacks.
+ *
+ * SL_WEAK void sl_btmesh_sensor_client_on_new__data(
+ * uint8_t sensor_idx,
+ * uint16_t address,
+ * sl_btmesh_sensor_client_data_status_t status,
+ * )
+ *
+ * @param[in] property New property ID for which devices need to be registered.
+ *
+ * @return Status of the operation.
+ * Returns SL_STATUS_OK(0) if succeeded, non-zero otherwise.
+ ******************************************************************************/
+sl_status_t sl_btmesh_sensor_client_get_sensor_data(mesh_device_properties_t property);
+
+// -----------------------------------------------------------------------------
+// Functions which are automatically called when the component is selected
+
+/***************************************************************************//**
+ * Handle Sensor Client events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ * @param[in] evt Pointer to incoming event
+ ******************************************************************************/
+void sl_btmesh_handle_sensor_client_on_event(sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_SENSOR_CLIENT_H
diff --git a/app/btmesh/common/btmesh_sensor_people_count/config/sl_btmesh_sensor_people_count_config.h b/app/btmesh/common/btmesh_sensor_people_count/config/sl_btmesh_sensor_people_count_config.h
new file mode 100644
index 00000000000..5480313f646
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_people_count/config/sl_btmesh_sensor_people_count_config.h
@@ -0,0 +1,149 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_SENSOR_PEOPLE_COUNT_CONFIG_H
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_TYPE_DISCRETE_VALUE_CFG_VAL 0
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_TYPE_PERCENTAGE_CFG_VAL 1
+
+// Bluetooth Mesh - People Count
+
+// Sensor attributes
+
+// Positive tolerance of sensor.
+// <0-4095:1>
+// Default: 0 (Unspecified)
+// 12-bit Positive Tolerance value (1 - 4095) or Unspecified (0). The value is derived as ERR_P [%] = 100 [%] * x / 4095
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_POSITIVE_TOLERANCE_CFG_VAL 0
+
+// Negative tolerance of sensor.
+// <0-4095:1>
+// Default: 0 (Unspecified)
+// 12-bit Negative Tolerance value (1 - 4095) or Unspecified (0). The value is derived as ERR_N [%] = 100 [%] * x / 4095
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_NEGATIVE_TOLERANCE_CFG_VAL 0
+
+// Sampling function
+// Unspecified
+// Instantaneous sampling
+// Arithmetic mean
+// Root mean square
+// Poll
+// Maximum value
+// Minimum value
+// Cumulative moving average updated with the frequency given by Sensor Update Interval
+// Number of "events" over the period of time defined by the Measurement Period
+// Reserved for Future Use
+// Default: Unspecified
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_SAMPLING_FUNCTION_CFG_VAL SAMPLING_UNSPECIFIED
+
+// Measurement Period of sensor.
+// <0-255:1>
+// Default: 0 (Not Applicable)
+// 8 bit value (1 - 255) or Not Applicable (0). Time period in seconds is derived as T [s] = 1.1 ^ (x - 64)
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_MEASUREMENT_PERIOD_CFG_VAL 0
+
+// Update Interval of sensor.
+// <0-255:1>
+// Default: 0 (Not Applicable)
+// 8 bit value (1 - 255) or Not Applicable (0). Time period in seconds is derived as T [s] = 1.1 ^ (x - 64)
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_UPDATE_INTERVAL_CFG_VAL 0
+
+//
+
+// Sensor cadence
+// Enables Cadence.
+// Default: 0
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_CADENCE_ENABLE_CFG_VAL 0
+
+// Fast Cadence Period Divisor
+// <0-15:1>
+// Default: 0 (Divisor of 1)
+// 7 bit value (0-15), other values are Prohibited. The value is represented as a 2 ^ n divisor of the Publish Period.
+// For example value 0x00 would have a divisor of 1, the Publish Period would not change.
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_FAST_CADENCE_PERIOD_DIVISOR_CFG_VAL 0
+
+// Status Trigger Type
+// Discrete Value
+// Percentage
+// Default: SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_TYPE_DISCRETE_VALUE_CFG_VAL
+// Defines the unit and format of the Status Trigger Delta fields
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_TYPE_CFG_VAL SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_TYPE_DISCRETE_VALUE_CFG_VAL
+
+// Status Trigger Delta Down
+// <0-65535:1>
+// Default: 0
+// The Status Trigger Delta Down field shall control the negative change of a measured quantity that
+// triggers publication of a Sensor Status message. The format is defined by the Status Trigger Type field.
+// In case of percentage Status Trigger Type the value is represented unitless with a resolution of 0.01 percent,
+// e.g. value 1534 represents 15.34%. In case of discrete Status Trigger Type the format represents
+// the people count value.
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_DELTA_DOWN_CFG_VAL 0
+
+// Status Trigger Delta Up
+// <0-65535:1>
+// Default: 0
+// The Status Trigger Delta Up field shall control the positive change of a measured quantity that
+// triggers publication of a Sensor Status message. The format is defined by the Status Trigger Type field.
+// In case of percentage Status Trigger Type the value is represented unitless with a resolution of 0.01 percent,
+// e.g. value 1534 represents 15.34%. In case of discrete Status Trigger Type the format represents
+// the people count value.
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_DELTA_UP_CFG_VAL 0
+
+// Status Min Interval
+// <0-26:1>
+// Default: 0
+// 8 bit value (0-26), other values are Prohibited. The value is represented as a 2 ^ n milliseconds.
+// For example, the value 10 would represent an interval of 1024ms.
+// The Status Min Interval field shall control the minimum interval between publishing two consecutive Sensor Status messages.
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_MIN_INTERVAL_CFG_VAL 0
+
+// Fast Cadence Low
+// <0-65535:1>
+// Default: 0
+// The Fast Cadence Low field shall define the lower boundary of a range of measured quantities when
+// the publishing cadence is increased as defined by the Fast Cadence Period Divisor field.
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_FAST_CADENCE_LOW_CFG_VAL 0
+
+// Fast Cadence High
+// <0-65535:1>
+// Default: 0
+// The Fast Cadence High field shall define the upper boundary of a range of measured quantities when
+// the publishing cadence is increased as defined by the Fast Cadence Period Divisor field.
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_FAST_CADENCE_HIGH_CFG_VAL 0
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_SENSOR_PEOPLE_COUNT_CONFIG_H
diff --git a/app/btmesh/common/btmesh_sensor_people_count/sl_btmesh_sensor_people_count.c b/app/btmesh/common/btmesh_sensor_people_count/sl_btmesh_sensor_people_count.c
new file mode 100644
index 00000000000..82a187695cf
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_people_count/sl_btmesh_sensor_people_count.c
@@ -0,0 +1,137 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh People count sensor implementation
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_CLI_PRESENT
+#include "sl_cli.h"
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#define log(...) app_log(__VA_ARGS__)
+#else
+#define log(...)
+#endif // SL_CATALOG_APP_LOG_PRESENT
+#endif // SL_CATALOG_CLI_PRESENT
+
+#include "sl_btmesh_sensor_people_count.h"
+#include "sl_btmesh_sensor_people_count_config.h"
+
+/***************************************************************************//**
+ * @addtogroup Sensor
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup PeopleCount
+ * @{
+ ******************************************************************************/
+
+/// People count
+static count16_t people_count = SL_BTMESH_SENSOR_PEOPLE_COUNT_VALUE_IS_NOT_KNOWN;
+
+/*******************************************************************************
+ * Set the people count value. It could be used to initialize the sensor.
+ *
+ * @param[in] people_count People count value to set
+ ******************************************************************************/
+void sl_btmesh_set_people_count(count16_t people_count_value)
+{
+ people_count = people_count_value;
+}
+
+/*******************************************************************************
+ * Get the current people count value measured by sensor.
+ *
+ * @return Current value of people count.
+ ******************************************************************************/
+count16_t sl_btmesh_get_people_count(void)
+{
+ return people_count;
+}
+
+/*******************************************************************************
+ * Increase people count value by one. After exceeding the maximum value it set
+ * people count to value is not known.
+ ******************************************************************************/
+void sl_btmesh_people_count_increase(void)
+{
+ if (people_count < SL_BTMESH_SENSOR_PEOPLE_COUNT_VALUE_IS_NOT_KNOWN) {
+ people_count += 1;
+ }
+}
+
+/*******************************************************************************
+ * Decrease people count value by one if value is known and greater than 0.
+ ******************************************************************************/
+void sl_btmesh_people_count_decrease(void)
+{
+ if (people_count > 0
+ && people_count < SL_BTMESH_SENSOR_PEOPLE_COUNT_VALUE_IS_NOT_KNOWN) {
+ people_count -= 1;
+ }
+}
+
+/**************************************************************************//**
+ * CLI Callbacks
+ *****************************************************************************/
+#ifdef SL_CATALOG_CLI_PRESENT
+void people_count_increase_from_cli(sl_cli_command_arg_t *arguments)
+{
+ (void)arguments;
+ sl_btmesh_people_count_increase();
+}
+
+void people_count_decrease_from_cli(sl_cli_command_arg_t *arguments)
+{
+ (void)arguments;
+ sl_btmesh_people_count_decrease();
+}
+
+void people_count_set_from_cli(sl_cli_command_arg_t *arguments)
+{
+ uint16_t value;
+ value = sl_cli_get_argument_uint16(arguments, 0);
+ sl_btmesh_set_people_count(value);
+}
+
+void people_count_get_from_cli(sl_cli_command_arg_t *arguments)
+{
+ (void)arguments;
+ log("%u\r\n", people_count);
+}
+
+#endif // SL_CATALOG_CLI_PRESENT
+
+/** @} (end addtogroup PeopleCount) */
+/** @} (end addtogroup Sensor) */
diff --git a/app/btmesh/common/btmesh_sensor_people_count/sl_btmesh_sensor_people_count.h b/app/btmesh/common/btmesh_sensor_people_count/sl_btmesh_sensor_people_count.h
new file mode 100644
index 00000000000..e800bac55a9
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_people_count/sl_btmesh_sensor_people_count.h
@@ -0,0 +1,82 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh People count sensor header
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_SENSOR_PEOPLE_COUNT_H
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_H
+
+#include "sl_btmesh_device_properties.h"
+
+#define SL_BTMESH_SENSOR_PEOPLE_COUNT_VALUE_IS_NOT_KNOWN (0xFFFF)
+
+/***************************************************************************//**
+ * @defgroup PeopleCount People Count Sensor Module
+ * @brief People Count Sensor Module Implementation
+ * This module simulate the people count sensor behavior.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup Sensor
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup PeopleCount
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Set the people count value. It could be used to initialize the sensor.
+ *
+ * @param[in] people_count People count value to set
+ ******************************************************************************/
+void sl_btmesh_set_people_count(count16_t people_count);
+
+/***************************************************************************//**
+ * Get the current people count value measured by sensor.
+ *
+ * @return Current value of people count.
+ ******************************************************************************/
+count16_t sl_btmesh_get_people_count(void);
+
+/***************************************************************************//**
+ * Increase people count value by one. After exceeding the maximum value it set
+ * people count to value is not known.
+ ******************************************************************************/
+void sl_btmesh_people_count_increase(void);
+
+/***************************************************************************//**
+ * Decrease people count value by one if value is known and greater than 0.
+ ******************************************************************************/
+void sl_btmesh_people_count_decrease(void);
+
+/** @} (end addtogroup PeopleCount) */
+/** @} (end addtogroup Sensor) */
+
+#endif /* SL_BTMESH_SENSOR_PEOPLE_COUNT_H */
diff --git a/app/btmesh/common/btmesh_sensor_server/btmesh_sensor_server.dcd b/app/btmesh/common/btmesh_sensor_server/btmesh_sensor_server.dcd
new file mode 100644
index 00000000000..5f8c2d806fc
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_server/btmesh_sensor_server.dcd
@@ -0,0 +1,10 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1100", "name":"Sensor Server"},
+ {"mid":"0x1101", "name":"Sensor Setup Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_sensor_server/config/sl_btmesh_sensor_server_config.h b/app/btmesh/common/btmesh_sensor_server/config/sl_btmesh_sensor_server_config.h
new file mode 100644
index 00000000000..f0f1d2f882a
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_server/config/sl_btmesh_sensor_server_config.h
@@ -0,0 +1,47 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_SENSOR_SERVER_CONFIG_H
+#define SL_BTMESH_SENSOR_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Sensor Server configuration
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for Sensor Server model specific messages for this component.
+#define SL_BTMESH_SENSOR_SERVER_LOGGING_CFG_VAL (1)
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_SENSOR_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server.c b/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server.c
new file mode 100644
index 00000000000..711148729f6
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server.c
@@ -0,0 +1,863 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Sensor Server Instances
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+// C Standard Library headers
+#include
+#include
+#include "sl_status.h"
+// Bluetooth stack headers
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_sensor.h"
+#include "sl_btmesh_dcd.h"
+#include "sl_simple_timer.h"
+
+#include "app_assert.h"
+#include "em_common.h"
+
+#include "sl_board_control_config.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#ifdef SL_CATALOG_SENSOR_RHT_PRESENT
+#include "sl_sensor_rht_config.h"
+#include "sl_sensor_rht.h"
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+
+#ifdef SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+#include "sl_btmesh_sensor_people_count_config.h"
+#include "sl_btmesh_sensor_people_count.h"
+#endif // SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+
+#ifdef SL_CATALOG_SENSOR_LIGHT_PRESENT
+#include "sl_sensor_light_config.h"
+#include "sl_sensor_light.h"
+#elif defined(SL_CATALOG_SENSOR_LUX_PRESENT)
+#include "sl_sensor_lux_config.h"
+#include "sl_sensor_lux.h"
+#endif
+
+#include "sl_btmesh_sensor_server.h"
+#include "sl_btmesh_sensor_server_config.h"
+#include "sl_btmesh_sensor_server_cadence.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup Sensor
+ * @{
+ ******************************************************************************/
+
+#define SENSOR_SERVER_SEND_FAILED_TEXT "Sensor server send %s failed" NL
+#define SENSOR_SETUP_SERVER_SEND_FAILED_TEXT "Sensor setup server send %s failed" NL
+
+/// The unused 0 address is used for publishing
+#define PUBLISH_TO_ALL_NODES 0
+/// Parameter ignored for publishing
+#define IGNORED 0
+/// No flags used for message
+#define NO_FLAGS 0
+/// Pre-scale value for temperature sensor raw data
+#define TEMPERATURE_PRE_SCALE 2
+/// Offset value for temperature sensor pre-scaled value
+#define TEMPERATURE_OFFSET 499
+/// Scale value for temperature sensor final value
+#define TEMPERATURE_SCALE_VAL 1000
+/// Length of sensor data buffer
+#define SENSOR_DATA_BUF_LEN 15
+/// Property ID indicating reading every sensor
+#define PROPERTY_ID_ALL 0
+/// Buffer length for get cadence parameters
+#define SENSOR_CADENCE_BUF_LEN 10
+/// Acknowledgement request mask
+#define SET_CADENCE_ACK_FLAG 2
+/// Callback has no parameters
+#define NO_CALLBACK_DATA (void *)NULL
+/// Multiplier for seconds conversion to millisenconds
+#define SEC_TO_MS 1000
+/// Sensor Update Interval formula power extractor constant
+#define EXTRACTOR_CONSTANT 64
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+#ifdef SL_CATALOG_SENSOR_RHT_PRESENT
+temperature_8_t get_temperature(void);
+bool rht_initialized;
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+
+#if defined(SL_CATALOG_SENSOR_LIGHT_PRESENT) \
+ || defined(SL_CATALOG_SENSOR_LUX_PRESENT)
+illuminance_t get_light(void);
+#endif //SL_CATALOG_SENSOR_LIGHT_PRESENT || SL_CATALOG_SENSOR_LUX_PRESENT
+
+#if SENSOR_THERMOMETER_CADENCE || SENSOR_PEOPLE_COUNT_CADENCE
+static uint32_t prev_publish_timeout;
+static sl_btmesh_evt_sensor_server_publish_t publish_period;
+
+// -------------------------------
+// Periodic timer handles
+static sl_simple_timer_t sensor_server_data_timer;
+static sl_simple_timer_t sensor_server_publish_timer;
+
+// -------------------------------
+// Periodic timer callbacks
+static void sensor_server_data_timer_cb(sl_simple_timer_t *handle, void *data);
+static void sensor_server_publish_timer_cb(sl_simple_timer_t *handle, void *data);
+#endif
+
+void sl_btmesh_sensor_server_node_init(void)
+{
+ /// Descriptors of supported sensors
+ /* The following properties are defined
+ * 1. People count property (property ID: 0x004C)
+ * 2. Present ambient light property (property ID: 0x004E)
+ * 3. Present ambient temperature property (property ID: 0x004F)
+ * NOTE: the properties must be ordered in ascending order by property ID
+ */
+ static const sensor_descriptor_t descriptors[] = {
+#ifdef SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+ {
+ .property_id = PEOPLE_COUNT,
+ .positive_tolerance = SL_BTMESH_SENSOR_PEOPLE_COUNT_POSITIVE_TOLERANCE_CFG_VAL,
+ .negative_tolerance = SL_BTMESH_SENSOR_PEOPLE_COUNT_NEGATIVE_TOLERANCE_CFG_VAL,
+ .sampling_function = SL_BTMESH_SENSOR_PEOPLE_COUNT_SAMPLING_FUNCTION_CFG_VAL,
+ .measurement_period = SL_BTMESH_SENSOR_PEOPLE_COUNT_MEASUREMENT_PERIOD_CFG_VAL,
+ .update_interval = SL_BTMESH_SENSOR_PEOPLE_COUNT_UPDATE_INTERVAL_CFG_VAL
+ },
+#endif // SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+#if defined(SL_BOARD_ENABLE_SENSOR_LIGHT) \
+ && SL_BOARD_ENABLE_SENSOR_LIGHT
+#ifdef SL_CATALOG_SENSOR_LIGHT_PRESENT
+ {
+ .property_id = PRESENT_AMBIENT_LIGHT_LEVEL,
+ .positive_tolerance = SENSOR_LIGHT_POSITIVE_TOLERANCE,
+ .negative_tolerance = SENSOR_LIGHT_NEGATIVE_TOLERANCE,
+ .sampling_function = SENSOR_LIGHT_SAMPLING_FUNCTION,
+ .measurement_period = SENSOR_LIGHT_MEASUREMENT_PERIOD,
+ .update_interval = SENSOR_LIGHT_UPDATE_INTERVAL
+ },
+#elif defined(SL_CATALOG_SENSOR_LUX_PRESENT)
+ {
+ .property_id = PRESENT_AMBIENT_LIGHT_LEVEL,
+ .positive_tolerance = SENSOR_LUX_POSITIVE_TOLERANCE,
+ .negative_tolerance = SENSOR_LUX_NEGATIVE_TOLERANCE,
+ .sampling_function = SENSOR_LUX_SAMPLING_FUNCTION,
+ .measurement_period = SENSOR_LUX_MEASUREMENT_PERIOD,
+ .update_interval = SENSOR_LUX_UPDATE_INTERVAL
+ },
+#endif // SL_CATALOG_SENSOR_LIGHT_PRESENT, SL_CATALOG_SENSOR_LUX_PRESENT
+#endif // SL_BOARD_ENABLE_SENSOR_LIGHT
+#if defined(SL_CATALOG_SENSOR_RHT_PRESENT) \
+ && defined(SL_BOARD_ENABLE_SENSOR_RHT) \
+ && SL_BOARD_ENABLE_SENSOR_RHT
+ {
+ .property_id = PRESENT_AMBIENT_TEMPERATURE,
+ .positive_tolerance = SENSOR_THERMOMETER_POSITIVE_TOLERANCE,
+ .negative_tolerance = SENSOR_THERMOMETER_NEGATIVE_TOLERANCE,
+ .sampling_function = SENSOR_THERMOMETER_SAMPLING_FUNCTION,
+ .measurement_period = SENSOR_THERMOMETER_MEASUREMENT_PERIOD,
+ .update_interval = SENSOR_THERMOMETER_UPDATE_INTERVAL
+ },
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+ };
+
+ uint16_t status = mesh_lib_sensor_server_init(BTMESH_SENSOR_SERVER_MAIN,
+ sizeof(descriptors)
+ / sizeof(sensor_descriptor_t),
+ descriptors);
+ app_assert_status_f(status, "Sensor Init Error");
+
+#ifdef SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+ sl_btmesh_set_people_count(0);
+#endif // SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+#if defined(SL_BOARD_ENABLE_SENSOR_LIGHT) && SL_BOARD_ENABLE_SENSOR_LIGHT
+#ifdef SL_CATALOG_SENSOR_LIGHT_PRESENT
+ {
+ sl_status_t sc;
+ sc = sl_sensor_light_init();
+ log_status_error_f(sc, "Ambient light and UV index sensor initialization failed." NL);
+ }
+#endif // SL_CATALOG_SENSOR_LIGHT_PRESENT
+#ifdef SL_CATALOG_SENSOR_LUX_PRESENT
+ {
+ sl_status_t sc;
+ sc = sl_sensor_lux_init();
+ log_status_error_f(sc, "Ambient light sensor initialization failed." NL);
+ }
+#endif // SL_CATALOG_SENSOR_LUX_PRESENT
+#endif // SL_BOARD_ENABLE_SENSOR_LIGHT
+#if defined(SL_CATALOG_SENSOR_RHT_PRESENT) \
+ && defined(SL_BOARD_ENABLE_SENSOR_RHT) \
+ && SL_BOARD_ENABLE_SENSOR_RHT
+ {
+ sl_status_t sc;
+ sc = sl_sensor_rht_init();
+ log_status_error_f(sc, "Relative Humidity and Temperature sensor initialization failed." NL);
+#if SENSOR_THERMOMETER_CADENCE
+ if (sc != SL_STATUS_OK) {
+ rht_initialized = false;
+ } else {
+ rht_initialized = true;
+ }
+#endif // SENSOR_THERMOMETER_CADENCE
+ }
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+
+#if SENSOR_PEOPLE_COUNT_CADENCE && SENSOR_THERMOMETER_CADENCE
+ uint32_t update_interval;
+ sl_btmesh_sensor_people_count_cadence_init(0);
+ if (rht_initialized == true) {
+ sl_btmesh_sensor_thermometer_cadence_init(get_temperature());
+ update_interval = MIN(SENSOR_THERMOMETER_UPDATE_INTERVAL, SL_BTMESH_SENSOR_PEOPLE_COUNT_UPDATE_INTERVAL_CFG_VAL);
+ } else {
+ update_interval = SL_BTMESH_SENSOR_PEOPLE_COUNT_UPDATE_INTERVAL_CFG_VAL;
+ }
+#elif SENSOR_PEOPLE_COUNT_CADENCE
+ uint32_t update_interval;
+ sl_btmesh_sensor_people_count_cadence_init(0);
+ update_interval = SL_BTMESH_SENSOR_PEOPLE_COUNT_UPDATE_INTERVAL_CFG_VAL;
+#elif SENSOR_THERMOMETER_CADENCE
+ uint32_t update_interval;
+ if (rht_initialized == true) {
+ sl_btmesh_sensor_thermometer_cadence_init(get_temperature());
+ update_interval = SENSOR_THERMOMETER_UPDATE_INTERVAL;
+ } else {
+ update_interval = 0;
+ }
+#endif
+
+#if SENSOR_PEOPLE_COUNT_CADENCE || SENSOR_THERMOMETER_CADENCE
+ if (update_interval != 0) {
+ sl_status_t sc = sl_simple_timer_start(&sensor_server_data_timer,
+ ((uint32_t)(pow((double)1.1, ((double)update_interval - 64)) * 1000)),
+ sensor_server_data_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic sensor_server_data_timer");
+ }
+#endif
+}
+
+/***************************************************************************//**
+ * Handling of sensor server get request event.
+ * It sending sensor status message with data for all of supported Properties ID,
+ * if there is no Property ID field in request. If request contains Property ID
+ * that is supported, functions reply with the sensor status message with data
+ * for this Property ID, in other case the message contains no data.
+ *
+ * @param[in] evt Pointer to sensor server get request event.
+ ******************************************************************************/
+static void handle_sensor_server_get_request(
+ sl_btmesh_evt_sensor_server_get_request_t *evt)
+{
+ // A slot for all sensor data
+ uint8_t sensor_data[SENSOR_DATA_BUF_LEN];
+ uint8_t len = 0;
+ sl_status_t sc;
+
+ (void)evt;
+#ifdef SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+ if ((evt->property_id == PEOPLE_COUNT)
+ || (evt->property_id == PROPERTY_ID_ALL)) {
+ count16_t people_count = sl_btmesh_get_people_count();
+ sl_btmesh_sensor_server_on_people_count_measurement(people_count);
+ len += mesh_sensor_data_to_buf(PEOPLE_COUNT,
+ &sensor_data[len],
+ (uint8_t*)&people_count);
+ }
+#endif // SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+#if defined(SL_BOARD_ENABLE_SENSOR_LIGHT) \
+ && SL_BOARD_ENABLE_SENSOR_LIGHT
+#if defined(SL_CATALOG_SENSOR_LIGHT_PRESENT) \
+ || defined(SL_CATALOG_SENSOR_LUX_PRESENT)
+ if ((evt->property_id == PRESENT_AMBIENT_LIGHT_LEVEL)
+ || (evt->property_id == PROPERTY_ID_ALL)) {
+ illuminance_t light = get_light();
+ len += mesh_sensor_data_to_buf(PRESENT_AMBIENT_LIGHT_LEVEL,
+ &sensor_data[len],
+ (uint8_t*)&light);
+ }
+#endif // SL_CATALOG_SENSOR_LIGHT_PRESENT || SL_CATALOG_SENSOR_LUX_PRESENT
+#endif // SL_BOARD_ENABLE_SENSOR_LIGHT
+#if defined(SL_CATALOG_SENSOR_RHT_PRESENT) \
+ && defined(SL_BOARD_ENABLE_SENSOR_RHT) \
+ && SL_BOARD_ENABLE_SENSOR_RHT
+ if ((evt->property_id == PRESENT_AMBIENT_TEMPERATURE)
+ || (evt->property_id == PROPERTY_ID_ALL)) {
+ temperature_8_t temperature = get_temperature();
+ len += mesh_sensor_data_to_buf(PRESENT_AMBIENT_TEMPERATURE,
+ &sensor_data[len],
+ (uint8_t*)&temperature);
+ }
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+ if (len > 0) {
+ sc = sl_btmesh_sensor_server_send_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ len,
+ sensor_data);
+ } else {
+ sensor_data[0] = evt->property_id & 0xFF;
+ sensor_data[1] = ((evt->property_id) >> 8) & 0xFF;
+ sensor_data[2] = 0; // Length is 0 for unsupported property_id
+ sc = sl_btmesh_sensor_server_send_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ 3,
+ sensor_data);
+ }
+ log_status_error_f(sc,
+ SENSOR_SERVER_SEND_FAILED_TEXT,
+ "status");
+}
+
+/***************************************************************************//**
+ * Handling of sensor server get column request event.
+ * Used Property IDs does not have sensor series column state,
+ * so reply has the same data as request according to specification.
+ *
+ * @param[in] evt Pointer to sensor server get column request event.
+ ******************************************************************************/
+static void handle_sensor_server_get_column_request(
+ sl_btmesh_evt_sensor_server_get_column_request_t *evt)
+{
+ sl_status_t sc;
+ sc = sl_btmesh_sensor_server_send_column_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ evt->property_id,
+ evt->column_ids.len,
+ evt->column_ids.data);
+
+ log_status_error_f(sc,
+ SENSOR_SERVER_SEND_FAILED_TEXT,
+ "column status");
+}
+
+/***************************************************************************//**
+ * Handling of sensor server get series request event.
+ * Used Property IDs does not have sensor series column state,
+ * so reply has only Property ID according to specification.
+ *
+ * @param[in] evt Pointer to sensor server get series request event.
+ ******************************************************************************/
+static void handle_sensor_server_get_series_request(
+ sl_btmesh_evt_sensor_server_get_series_request_t *evt)
+{
+ sl_status_t sc;
+ sc = sl_btmesh_sensor_server_send_series_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ evt->property_id,
+ 0,
+ NULL);
+
+ log_status_error_f(sc,
+ SENSOR_SERVER_SEND_FAILED_TEXT,
+ "series status");
+}
+
+/***************************************************************************//**
+ * It is used for sensor states publishing
+ *
+ * @return none
+ ******************************************************************************/
+static void sensor_server_publish(void)
+{
+ uint8_t sensor_data[SENSOR_DATA_BUF_LEN];
+ uint8_t len = 0;
+
+#ifdef SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+ count16_t people_count = sl_btmesh_get_people_count();
+ sl_btmesh_sensor_server_on_people_count_measurement(people_count);
+ len += mesh_sensor_data_to_buf(PEOPLE_COUNT,
+ &sensor_data[len],
+ (uint8_t*)&people_count);
+#endif // SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+
+#if defined(SL_BOARD_ENABLE_SENSOR_LIGHT) && SL_BOARD_ENABLE_SENSOR_LIGHT
+#if defined(SL_CATALOG_SENSOR_LIGHT_PRESENT) \
+ || defined(SL_CATALOG_SENSOR_LUX_PRESENT)
+ illuminance_t light = get_light();
+ len += mesh_sensor_data_to_buf(PRESENT_AMBIENT_LIGHT_LEVEL,
+ &sensor_data[len],
+ (uint8_t*)&light);
+#endif // SL_CATALOG_SENSOR_LIGHT_PRESENT || SL_CATALOG_SENSOR_LUX_PRESENT
+#endif // SL_BOARD_ENABLE_SENSOR_LIGHT
+
+#if defined(SL_BOARD_ENABLE_SENSOR_RHT) && SL_BOARD_ENABLE_SENSOR_RHT
+#ifdef SL_CATALOG_SENSOR_RHT_PRESENT
+ temperature_8_t temperature = get_temperature();
+ len += mesh_sensor_data_to_buf(PRESENT_AMBIENT_TEMPERATURE,
+ &sensor_data[len],
+ (uint8_t*) &temperature);
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+#endif // SL_BOARD_ENABLE_SENSOR_RHT
+
+ if (len > 0) {
+ sl_status_t sc = sl_btmesh_sensor_server_send_status(PUBLISH_TO_ALL_NODES,
+ BTMESH_SENSOR_SERVER_MAIN,
+ IGNORED,
+ NO_FLAGS,
+ len,
+ sensor_data);
+ log_btmesh_status_f(sc, SENSOR_SERVER_SEND_FAILED_TEXT, "status");
+ }
+}
+
+/***************************************************************************//**
+ * Handling of sensor server publish event.
+ * Indicates that the publishing period timer elapsed and updates the current
+ * publishing period that can be used to estimate the next tick, e.g., when
+ * the state should be reported at higher frequency.
+ *
+ * @param[in] evt Pointer to sensor server publish request event structure
+ ******************************************************************************/
+static void handle_sensor_server_publish_event(
+ sl_btmesh_evt_sensor_server_publish_t *evt)
+{
+#if SENSOR_THERMOMETER_CADENCE || SENSOR_PEOPLE_COUNT_CADENCE
+ publish_period = *evt;
+
+#else
+ (void)evt;
+ sensor_server_publish();
+#endif
+}
+
+/***************************************************************************//**
+ * Handling of sensor setup server get cadence request event.
+ *
+ * @param[in] evt Pointer to sensor server get cadence request event.
+ ******************************************************************************/
+static void handle_sensor_setup_server_get_cadence_request(
+ sl_btmesh_evt_sensor_setup_server_get_cadence_request_t *evt)
+{
+ sl_status_t sc;
+ uint16_t buff_len = 0;
+ uint8_t* buff_addr = NULL;
+
+#if SENSOR_THERMOMETER_CADENCE || SENSOR_PEOPLE_COUNT_CADENCE
+ uint8_t cadence_status_buf[SENSOR_CADENCE_BUF_LEN];
+#endif
+
+#if SENSOR_THERMOMETER_CADENCE
+ if (evt->property_id == PRESENT_AMBIENT_TEMPERATURE) {
+ buff_len = sl_btmesh_sensor_thermometer_get_cadence(SENSOR_CADENCE_BUF_LEN, cadence_status_buf);
+ buff_addr = cadence_status_buf;
+ }
+#endif // SENSOR_THERMOMETER_CADENCE
+
+#if SENSOR_PEOPLE_COUNT_CADENCE
+ if (evt->property_id == PEOPLE_COUNT) {
+ buff_len = sl_btmesh_sensor_people_count_get_cadence(SENSOR_CADENCE_BUF_LEN, cadence_status_buf);
+ buff_addr = cadence_status_buf;
+ }
+#endif // SENSOR_THERMOMETER_CADENCE
+
+ sc = sl_btmesh_sensor_setup_server_send_cadence_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ evt->property_id,
+ buff_len,
+ buff_addr);
+ log_status_error_f(sc,
+ SENSOR_SETUP_SERVER_SEND_FAILED_TEXT,
+ "cadence status");
+}
+
+/***************************************************************************//**
+ * Handling of sensor setup server set cadence request event.
+ *
+ * @param[in] evt Pointer to sensor server set cadence request event.
+ ******************************************************************************/
+static void handle_sensor_setup_server_set_cadence_request(
+ sl_btmesh_evt_sensor_setup_server_set_cadence_request_t *evt)
+{
+ bool param_validity = true;
+ uint16_t buff_len = 0;
+ uint8_t* buff_addr = NULL;
+
+#if SENSOR_THERMOMETER_CADENCE || SENSOR_PEOPLE_COUNT_CADENCE
+ uint8_t cadence_status_buf[SENSOR_CADENCE_BUF_LEN];
+#endif
+
+#if SENSOR_THERMOMETER_CADENCE
+ if (evt->property_id == PRESENT_AMBIENT_TEMPERATURE) {
+ // store incoming cadence parameters
+ param_validity = sl_btmesh_sensor_thermometer_set_cadence(evt);
+ if (((evt->flags & SET_CADENCE_ACK_FLAG) == SET_CADENCE_ACK_FLAG)
+ && (param_validity == true)) {
+ // prepare buffer for cadence status response
+ buff_len = sl_btmesh_sensor_thermometer_get_cadence(SENSOR_CADENCE_BUF_LEN, cadence_status_buf);
+ buff_addr = cadence_status_buf;
+ }
+ }
+#endif // SENSOR_THERMOMETER_CADENCE
+
+#if SENSOR_PEOPLE_COUNT_CADENCE
+ if (evt->property_id == PEOPLE_COUNT) {
+ // store incoming cadence parameters
+ param_validity = sl_btmesh_sensor_people_count_set_cadence(evt);
+ if (((evt->flags & SET_CADENCE_ACK_FLAG) == SET_CADENCE_ACK_FLAG)
+ && (param_validity == true)) {
+ // prepare buffer for cadence status response
+ buff_len = sl_btmesh_sensor_people_count_get_cadence(SENSOR_CADENCE_BUF_LEN, cadence_status_buf);
+ buff_addr = cadence_status_buf;
+ }
+ }
+#endif // SENSOR_PEOPLE_COUNT_CADENCE
+ if (((evt->flags & SET_CADENCE_ACK_FLAG) == SET_CADENCE_ACK_FLAG)
+ && (param_validity == true)) {
+ sl_status_t sc = sl_btmesh_sensor_setup_server_send_cadence_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ evt->property_id,
+ buff_len,
+ buff_addr);
+ log_status_error_f(sc,
+ SENSOR_SETUP_SERVER_SEND_FAILED_TEXT,
+ "cadence status");
+ }
+}
+
+/***************************************************************************//**
+ * Handling of sensor setup server get settings request event.
+ * Settings are not supported now, so reply has only Property ID
+ * according to specification.
+ *
+ * @param[in] evt Pointer to sensor server get settings request event.
+ ******************************************************************************/
+static void handle_sensor_setup_server_get_settings_request(
+ sl_btmesh_evt_sensor_setup_server_get_settings_request_t *evt)
+{
+ sl_status_t sc;
+ sc = sl_btmesh_sensor_setup_server_send_settings_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ evt->property_id,
+ 0,
+ NULL);
+ log_status_error_f(sc,
+ SENSOR_SETUP_SERVER_SEND_FAILED_TEXT,
+ "settings status");
+}
+
+/***************************************************************************//**
+ * Handling of sensor setup server get setting request event.
+ * Settings are not supported now, so reply has only Property ID
+ * and Sensor Property ID according to specification.
+ *
+ * @param[in] evt Pointer to sensor server get setting request event.
+ ******************************************************************************/
+static void handle_sensor_setup_server_get_setting_request(
+ sl_btmesh_evt_sensor_setup_server_get_setting_request_t *evt)
+{
+ sl_status_t sc;
+ sc = sl_btmesh_sensor_setup_server_send_setting_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ evt->property_id,
+ evt->setting_id,
+ 0,
+ NULL);
+ log_status_error_f(sc,
+ SENSOR_SETUP_SERVER_SEND_FAILED_TEXT,
+ "setting status");
+}
+
+/***************************************************************************//**
+ * Handling of sensor setup server set setting request event.
+ * Settings are not supported now, so reply has only Property ID
+ * and Sensor Property ID according to specification.
+ *
+ * @param[in] evt Pointer to sensor server set setting request event.
+ ******************************************************************************/
+static void handle_sensor_setup_server_set_setting_request(
+ sl_btmesh_evt_sensor_setup_server_set_setting_request_t *evt)
+{
+ sl_status_t sc;
+ sc = sl_btmesh_sensor_setup_server_send_setting_status(evt->client_address,
+ BTMESH_SENSOR_SERVER_MAIN,
+ evt->appkey_index,
+ NO_FLAGS,
+ evt->property_id,
+ evt->setting_id,
+ 0,
+ NULL);
+ log_status_error_f(sc,
+ SENSOR_SETUP_SERVER_SEND_FAILED_TEXT,
+ "setting status");
+}
+
+/***************************************************************************//**
+ * Handling of mesh events by sensor server component.
+ * It handles:
+ * - node_initialized
+ * - node_provisioned
+ * - sensor_server_get_request
+ * - sensor_server_get_column_request
+ * - sensor_server_get_series_request
+ * - sensor_setup_server_get_cadence_request
+ * - sensor_setup_server_set_cadence_request
+ * - sensor_setup_server_get_settings_request
+ * - sensor_setup_server_get_setting_request
+ * - sensor_setup_server_set_setting_request
+ *
+ * @param[in] evt Pointer to incoming sensor server event.
+ ******************************************************************************/
+void sl_btmesh_handle_sensor_server_events(sl_btmesh_msg_t* evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_sensor_server_node_init();
+ }
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_sensor_server_node_init();
+ break;
+
+ case sl_btmesh_evt_sensor_server_get_request_id:
+ handle_sensor_server_get_request(
+ &(evt->data.evt_sensor_server_get_request));
+ break;
+
+ case sl_btmesh_evt_sensor_server_get_column_request_id:
+ handle_sensor_server_get_column_request(
+ &(evt->data.evt_sensor_server_get_column_request));
+ break;
+
+ case sl_btmesh_evt_sensor_server_get_series_request_id:
+ handle_sensor_server_get_series_request(
+ &(evt->data.evt_sensor_server_get_series_request));
+ break;
+
+ case sl_btmesh_evt_sensor_server_publish_id:
+ handle_sensor_server_publish_event(
+ &(evt->data.evt_sensor_server_publish));
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_get_cadence_request_id:
+ handle_sensor_setup_server_get_cadence_request(
+ &(evt->data.evt_sensor_setup_server_get_cadence_request));
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_set_cadence_request_id:
+ handle_sensor_setup_server_set_cadence_request(
+ &(evt->data.evt_sensor_setup_server_set_cadence_request));
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_get_settings_request_id:
+ handle_sensor_setup_server_get_settings_request(
+ &(evt->data.evt_sensor_setup_server_get_settings_request));
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_get_setting_request_id:
+ handle_sensor_setup_server_get_setting_request(
+ &(evt->data.evt_sensor_setup_server_get_setting_request));
+ break;
+
+ case sl_btmesh_evt_sensor_setup_server_set_setting_request_id:
+ handle_sensor_setup_server_set_setting_request(
+ &(evt->data.evt_sensor_setup_server_set_setting_request));
+ break;
+
+ default:
+ break;
+ }
+}
+
+#ifdef SL_CATALOG_SENSOR_RHT_PRESENT
+/***************************************************************************//**
+ * Get the current temperature value measured by sensor.
+ *
+ * @return Current value of temperature.
+ ******************************************************************************/
+temperature_8_t get_temperature(void)
+{
+ int32_t temp_data = 0;
+ uint32_t temp_rh = 0;
+ temperature_8_t temperature = SL_BTMESH_SENSOR_TEMPERATURE_VALUE_UNKNOWN;
+ sl_status_t sc = sl_sensor_rht_get(&temp_rh, &temp_data);
+ if (sc == SL_STATUS_OK) {
+ temp_data = (((temp_data
+ * TEMPERATURE_PRE_SCALE)
+ + TEMPERATURE_OFFSET)
+ / TEMPERATURE_SCALE_VAL);
+ temperature = (temperature_8_t)temp_data;
+ } else if (sc != SL_STATUS_NOT_INITIALIZED) {
+ log_warning("Invalid temperature reading: %lu %ld" NL,
+ temp_rh,
+ temp_data);
+ }
+ sl_btmesh_sensor_server_on_temperature_measurement(temperature);
+ return temperature;
+}
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+
+#if defined(SL_CATALOG_SENSOR_LIGHT_PRESENT) \
+ || defined(SL_CATALOG_SENSOR_LUX_PRESENT)
+/***************************************************************************//**
+ * Get the current light value measured by sensor.
+ *
+ * @return Current value of light reading.
+ ******************************************************************************/
+illuminance_t get_light(void)
+{
+ float lux;
+ illuminance_t light = SL_BTMESH_SENSOR_LIGHT_VALUE_UNKNOWN;
+ sl_status_t sc;
+#ifdef SL_CATALOG_SENSOR_LIGHT_PRESENT
+ float uvi;
+
+ sc = sl_sensor_light_get(&lux, &uvi);
+#elif defined(SL_CATALOG_SENSOR_LUX_PRESENT)
+ sc = sl_sensor_lux_get(&lux);
+#endif // SL_CATALOG_SENSOR_LIGHT_PRESENT
+ if (sc == SL_STATUS_OK) {
+ light = (illuminance_t)lux;
+ } else if (sc != SL_STATUS_NOT_INITIALIZED) {
+ log_warning("Invalid light reading: %6lulx" NL, (illuminance_t)lux);
+ }
+ sl_btmesh_sensor_server_on_light_measurement(light);
+ return light;
+}
+#endif // SL_CATALOG_SENSOR_LIGHT_PRESENT || SL_CATALOG_SENSOR_LUX_PRESENT
+
+/***************************************************************************//**
+ * Timer Callbacks
+ ******************************************************************************/
+#if SENSOR_THERMOMETER_CADENCE || SENSOR_PEOPLE_COUNT_CADENCE
+/***************************************************************************//**
+ * Get measured value from sensors and analyze cadence conditions timer callback
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to callback data
+ *
+ * @return none
+ ******************************************************************************/
+static void sensor_server_data_timer_cb(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+ uint32_t publ_timer_thermometer = publish_period.period_ms;
+ uint32_t publ_timer_people_count = publish_period.period_ms;
+ uint32_t publ_timeout;
+ sl_status_t sc;
+
+#if SENSOR_THERMOMETER_CADENCE
+ if (rht_initialized == true) {
+ publ_timer_thermometer = sl_btmesh_sensor_thermometer_handle_cadence(get_temperature(), publish_period);
+ }
+#endif // SENSOR_THERMOMETER_CADENCE
+
+#if SENSOR_PEOPLE_COUNT_CADENCE
+ publ_timer_people_count = sl_btmesh_sensor_people_count_handle_cadence(sl_btmesh_get_people_count(), publish_period);
+#endif // SENSOR_PEOPLE_COUNT_CADENCE
+
+ if (publ_timer_thermometer > publ_timer_people_count) {
+ publ_timeout = publ_timer_people_count;
+ } else {
+ publ_timeout = publ_timer_thermometer;
+ }
+
+ if (prev_publish_timeout != publ_timeout) {
+ log_info("Publishing period: %d ms" NL, publ_timeout);
+ //Stop publish timer
+ sc = sl_simple_timer_stop(&sensor_server_publish_timer);
+
+ app_assert_status_f(sc, "Failed to stop periodic sensor_server_publish_timer");
+
+ //Restart publishing timer with the new timer value
+ sc = sl_simple_timer_start(&sensor_server_publish_timer,
+ publ_timeout,
+ sensor_server_publish_timer_cb,
+ NO_CALLBACK_DATA,
+ true);
+ app_assert_status_f(sc, "Failed to start periodic sensor_server_publish_timer");
+ }
+ prev_publish_timeout = publ_timeout;
+}
+
+/***************************************************************************//**
+ * Publish sensor status timer callback
+ *
+ * @param[in] handle Pointer to the timer handle
+ * @param[in] data Pointer to callback data
+ *
+ * @return none
+ ******************************************************************************/
+static void sensor_server_publish_timer_cb(sl_simple_timer_t *handle, void *data)
+{
+ (void)data;
+ (void)handle;
+
+ sensor_server_publish();
+}
+#endif
+/**************************************************************************//**
+ * @addtogroup btmesh_sens_srv_cb_weak Weak implementation of callbacks
+ * @{
+ *****************************************************************************/
+SL_WEAK void sl_btmesh_sensor_server_on_temperature_measurement(temperature_8_t temperature)
+{
+ (void) temperature;
+}
+
+SL_WEAK void sl_btmesh_sensor_server_on_light_measurement(illuminance_t light)
+{
+ (void) light;
+}
+
+SL_WEAK void sl_btmesh_sensor_server_on_people_count_measurement(count16_t people)
+{
+ (void) people;
+}
+/** @} (end addtogroup btmesh_sens_srv_cb_weak) */
+
+/** @} (end addtogroup Sensor) */
diff --git a/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server.h b/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server.h
new file mode 100644
index 00000000000..50cd9571830
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server.h
@@ -0,0 +1,84 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_sensor_server.h
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_SENSOR_SERVER_H
+#define SL_BTMESH_SENSOR_SERVER_H
+
+#include "sl_btmesh_device_properties.h"
+
+#define SL_BTMESH_SENSOR_LIGHT_VALUE_UNKNOWN (0xFFFFFFFF)
+#define SL_BTMESH_SENSOR_TEMPERATURE_VALUE_UNKNOWN (0x7F)
+
+/**************************************************************************//**
+ * Initialize Sensor Server.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ *****************************************************************************/
+void sl_btmesh_sensor_server_node_init(void);
+
+/**************************************************************************//**
+ * Handle Sensor Server events.
+ *
+ * This function is called automatically by Universal Configurator after
+ * enabling the component.
+ *
+ *****************************************************************************/
+void sl_btmesh_handle_sensor_server_events(sl_btmesh_msg_t* pEvt);
+
+/***************************************************************************//**
+ * Called when a temperature measurement is done
+ * @note If no implementation is provided in the application then a default weak
+ * implementation if provided which is a no-operation. (empty function)
+ *
+ * @param[in] temperature Temperature value in 0.5 degree Celsius steps
+ ******************************************************************************/
+void sl_btmesh_sensor_server_on_temperature_measurement(temperature_8_t temperature);
+
+/***************************************************************************//**
+ * Called when a light measurement is done
+ * @note If no implementation is provided in the application then a default weak
+ * implementation if provided which is a no-operation. (empty function)
+ *
+ * @param[in] temperature Temperature value in lux
+ ******************************************************************************/
+void sl_btmesh_sensor_server_on_light_measurement(illuminance_t light);
+
+/***************************************************************************//**
+ * Called when a people count sensor measurement is done
+ * @note If no implementation is provided in the application then a default weak
+ * implementation if provided which is a no-operation. (empty function)
+ *
+ * @param[in] temperature People count sensor value
+ ******************************************************************************/
+void sl_btmesh_sensor_server_on_people_count_measurement(count16_t people);
+
+#endif // SL_BTMESH_SENSOR_SERVER_H
diff --git a/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server_cadence.c b/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server_cadence.c
new file mode 100644
index 00000000000..5146eccf2df
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server_cadence.c
@@ -0,0 +1,544 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Sensor Server cadence handler
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+// C Standard Library headers
+#include
+#include "sl_status.h"
+// Bluetooth stack headers
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_sensor.h"
+
+#include "app_assert.h"
+#include "em_common.h"
+
+#include "sl_board_control_config.h"
+
+#ifdef SL_CATALOG_SENSOR_RHT_PRESENT
+#include "sl_sensor_rht_config.h"
+#include "sl_sensor_rht.h"
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+
+#ifdef SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+#include "sl_btmesh_sensor_people_count_config.h"
+#include "sl_btmesh_sensor_people_count.h"
+#endif // SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+
+#include "sl_btmesh_sensor_server.h"
+#include "sl_btmesh_sensor_server_cadence.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/// Shift 7 positions
+#define STATUS_TRIGGER_TYPE_SHIFT 7
+/// Shift 8 positions
+#define FAST_CADENCE_DELTA_SHIFT 8
+/// Length of people count sensor get cadence status buffer
+#define SENSOR_PEOPLE_COUNT_GET_CADENCE_BUF_LEN 10
+/// Length of thermometer (discrete trigger type) get cadence status buffer
+#define SENSOR_THERMOMETER_DISCRETE_GET_CADENCE_BUF_LEN 6
+/// Length of thermometer (percentage trigger type) get cadence status buffer
+#define SENSOR_THERMOMETER_PERCENTAGE_GET_CADENCE_BUF_LEN 8
+/// Length of people count sensor set cadence parameter buffer
+#define SENSOR_PEOPLE_COUNT_CADENCE_PARAM_LEN 9
+/// Length of thermometer (discrete trigger type) set cadence status buffer
+#define SENSOR_THERMOMETER_CADENCE_PERCENTAGE_PARAM_LEN 7
+/// Length of thermometer (percentage trigger type) set cadence status buffer
+#define SENSOR_THERMOMETER_CADENCE_DISCRETE_PARAM_LEN 5
+/// Cadence period divisor parameter maximum value
+#define MAX_PERIOD_DIVISOR 0x0F
+/// Cadence trigger type parameter maximum value
+#define MAX_TRIGGER_TYPE 0x01
+/// Cadence minimum interval parameter maximum value
+#define MAX_PUBLISHING_MIN_INTERVAL 0x1A
+/// Percentage 100.00%
+#define PERCENTAGE_FULL 10000
+
+// Define the sensor index
+// RHT sensor present and cadence enabled
+#if SENSOR_THERMOMETER_CADENCE
+#define SENSOR_RHT_INDEX 0
+
+// RHT and people count sensor present
+#if SENSOR_PEOPLE_COUNT_CADENCE
+#define SENSOR_PEOPLE_COUNT_INDEX 1
+#endif // SENSOR_PEOPLE_COUNT_CADENCE
+
+// RHT sensor not present, people count sensor present
+#elif SENSOR_PEOPLE_COUNT_CADENCE
+#define SENSOR_PEOPLE_COUNT_INDEX 0
+#endif // SENSOR_PEOPLE_COUNT_CADENCE
+
+#if SENSOR_THERMOMETER_CADENCE || SENSOR_PEOPLE_COUNT_CADENCE
+// Cadence parameters of supported sensors
+static struct sensort_cadence_state cadences[SENSOR_THERMOMETER_CADENCE
+ + SENSOR_PEOPLE_COUNT_CADENCE];
+
+static int delta_abs (int val);
+#endif
+
+#if SENSOR_PEOPLE_COUNT_CADENCE
+/// Previously measured people count value
+static count16_t prev_people_count_data;
+
+// People count sensor internals
+static bool sensor_people_count_fast_cadence(count16_t people_count);
+static bool sensor_people_count_delta_cadence(count16_t people_count);
+#endif // SENSOR_PEOPLE_COUNT_CADENCE
+
+#if SENSOR_THERMOMETER_CADENCE
+/// Previously measured temperature value
+static temperature_8_t prev_temp_data;
+
+// Thermometer sensor internals
+static bool sensor_thermometer_fast_cadence(temperature_8_t current_temperature);
+static bool sensor_thermometer_delta_cadence(temperature_8_t current_temperature);
+#endif // SENSOR_THERMOMETER_CADENCE
+
+#if SENSOR_PEOPLE_COUNT_CADENCE
+
+void sl_btmesh_sensor_people_count_cadence_init(count16_t people_count)
+{
+ static uint16_t delta_down = SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_DELTA_DOWN_CFG_VAL;
+ static uint16_t delta_up = SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_DELTA_UP_CFG_VAL;
+
+ static uint16_t cadence_low = SL_BTMESH_SENSOR_PEOPLE_COUNT_FAST_CADENCE_LOW_CFG_VAL;
+ static uint16_t cadence_high = SL_BTMESH_SENSOR_PEOPLE_COUNT_FAST_CADENCE_HIGH_CFG_VAL;
+
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].property_id = PEOPLE_COUNT;
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].period_divisor = SL_BTMESH_SENSOR_PEOPLE_COUNT_FAST_CADENCE_PERIOD_DIVISOR_CFG_VAL;
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_type = SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_TYPE_CFG_VAL;
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].min_interval = SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_MIN_INTERVAL_CFG_VAL;
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_low.size = sizeof(cadence_low);
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_low.value = (uint8_t*)(&cadence_low);
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_high.size = sizeof(cadence_high);
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_high.value = (uint8_t*)(&cadence_high);
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_down.size = sizeof(delta_down);
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_down.value = (uint8_t*)(&delta_down);
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_up.size = sizeof(delta_up);
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_up.value = (uint8_t*)(&delta_up);
+
+ prev_people_count_data = people_count;
+}
+
+uint32_t sl_btmesh_sensor_people_count_handle_cadence(count16_t people_count, sl_btmesh_evt_sensor_server_publish_t publish_period)
+{
+ uint32_t ret_timer_value = publish_period.period_ms;
+
+ // Check if the measured value requires publishing period modification
+ if (sensor_people_count_delta_cadence(people_count)
+ || sensor_people_count_fast_cadence(people_count)) {
+ // Calculate new publishing timer value
+ ret_timer_value = publish_period.period_ms / (1 << cadences[SENSOR_PEOPLE_COUNT_INDEX].period_divisor);
+ if (ret_timer_value < (uint32_t)(1 << cadences[SENSOR_PEOPLE_COUNT_INDEX].min_interval)) {
+ ret_timer_value = (uint32_t)(1 << cadences[SENSOR_PEOPLE_COUNT_INDEX].min_interval);
+ }
+ }
+ prev_people_count_data = people_count;
+
+ return ret_timer_value;
+}
+
+bool sl_btmesh_sensor_people_count_set_cadence(sl_btmesh_evt_sensor_setup_server_set_cadence_request_t* evt)
+{
+ bool ret_val = false;
+
+ // Check if the received parameters are in valid range
+ if ((SENSOR_PEOPLE_COUNT_CADENCE_PARAM_LEN == evt->params.len)
+ && (MAX_PERIOD_DIVISOR >= evt->period_divisor)
+ && (MAX_TRIGGER_TYPE >= evt->trigger_type)
+ && (MAX_PUBLISHING_MIN_INTERVAL >= evt->params.data[4])) {
+ ret_val = true;
+ }
+
+ // Store incoming people count sensor cadence parameters
+ if (true == ret_val) {
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].period_divisor = evt->period_divisor;
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_type = evt->trigger_type;
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_down.value[0] = evt->params.data[0];
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_down.value[1] = evt->params.data[1];
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_up.value[0] = evt->params.data[2];
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_up.value[1] = evt->params.data[3];
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].min_interval = evt->params.data[4];
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_low.value[0] = evt->params.data[5];
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_low.value[1] = evt->params.data[6];
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_high.value[0] = evt->params.data[7];
+ cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_high.value[1] = evt->params.data[8];
+ }
+
+ return ret_val;
+}
+
+uint16_t sl_btmesh_sensor_people_count_get_cadence(uint8_t length, uint8_t* get_cadence_buffer)
+{
+ uint16_t buffer_len = 0;
+
+ if (SENSOR_PEOPLE_COUNT_GET_CADENCE_BUF_LEN <= length) {
+ get_cadence_buffer[0] = cadences[SENSOR_PEOPLE_COUNT_INDEX].period_divisor
+ | (cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_type << STATUS_TRIGGER_TYPE_SHIFT);
+ get_cadence_buffer[1] = cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_down.value[0];
+ get_cadence_buffer[2] = cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_down.value[1];
+ get_cadence_buffer[3] = cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_up.value[0];
+ get_cadence_buffer[4] = cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_up.value[1];
+ get_cadence_buffer[5] = cadences[SENSOR_PEOPLE_COUNT_INDEX].min_interval;
+ get_cadence_buffer[6] = cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_low.value[0];
+ get_cadence_buffer[7] = cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_low.value[1];
+ get_cadence_buffer[8] = cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_high.value[0];
+ get_cadence_buffer[9] = cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_high.value[1];
+ buffer_len = SENSOR_PEOPLE_COUNT_GET_CADENCE_BUF_LEN;
+ }
+
+ return buffer_len;
+}
+
+/***************************************************************************//**
+ * Check if the measured value requires publishing period modification.
+ *
+ * @param[in] people_count People count sensor data value
+ *
+ * @return True if publishing period modification is required false otherwise
+ ******************************************************************************/
+static bool sensor_people_count_fast_cadence(count16_t people_count)
+{
+ bool ret_val = false;
+ count16_t fast_cadence_high = (count16_t)(cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_high.value[0]
+ | (cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_high.value[1] << FAST_CADENCE_DELTA_SHIFT));
+ count16_t fast_cadence_low = (count16_t)(cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_low.value[0]
+ | (cadences[SENSOR_PEOPLE_COUNT_INDEX].fast_cadence_low.value[1] << FAST_CADENCE_DELTA_SHIFT));
+
+ // Check if the Fast Cadence High value is equal or higher than the Fast Cadence Low value
+ if (fast_cadence_high >= fast_cadence_low) {
+ // If the measured value is within the closed interval of
+ // [Fast Cadence Low, Fast Cadence High], Sensor Status publishing period
+ // modification is required.
+ if ((people_count >= fast_cadence_low)
+ && (people_count <= fast_cadence_high)) {
+ ret_val = true;
+ }
+ // Check if the Fast Cadence High value is lower than the Fast Cadence Low value
+ } else {
+ // If the measured value is lower than the Fast Cadence High value or is
+ // higher than the Fast Cadence Low value, Sensor Status publishing period
+ // modification is required.
+ if ((people_count < fast_cadence_high)
+ || (people_count > fast_cadence_low)) {
+ ret_val = true;
+ }
+ }
+
+ return ret_val;
+}
+
+/***************************************************************************//**
+ * Check if the change of the measured value requires publishing period modification.
+ *
+ * @param[in] people_count People count sensor data value
+ *
+ * @return True if publishing period modification is required false otherwise
+ ******************************************************************************/
+static bool sensor_people_count_delta_cadence(count16_t people_count)
+{
+ bool ret_val = false;
+ uint16_t delta_percent;
+ uint16_t delta_cadence_up = (uint16_t)(cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_up.value[0]
+ | (cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_up.value[1] << FAST_CADENCE_DELTA_SHIFT));
+ uint16_t delta_cadence_down = (uint16_t)(cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_down.value[0]
+ | (cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_delta_down.value[1] << FAST_CADENCE_DELTA_SHIFT));
+
+ // Check the unit and format of the Status Trigger Delta Down and
+ // the Status Trigger Delta Up fields.
+ if (SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_TYPE_DISCRETE_VALUE_CFG_VAL == cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_type) {
+ // If the temperature change is rising and the measured quantity change
+ // exceeds the configured Status Trigger Delta Up value, Sensor Status
+ // publishing period modification is required.
+ if ((people_count > prev_people_count_data)
+ && ((people_count - prev_people_count_data) >= (count16_t)delta_cadence_up)) {
+ ret_val = true;
+ // If the temperature change is falling and the measured quantity change
+ // exceeds the configured Status Trigger Delta Down value, Sensor Status
+ // publishing period modification is required.
+ } else if ((people_count < prev_people_count_data)
+ && ((prev_people_count_data - people_count) >= (count16_t)delta_cadence_down)) {
+ ret_val = true;
+ } else {
+ }
+ // Same check with measured value represented unitless as percentage
+ } else if ((SL_BTMESH_SENSOR_PEOPLE_COUNT_STATUS_TRIGGER_TYPE_PERCENTAGE_CFG_VAL == cadences[SENSOR_PEOPLE_COUNT_INDEX].status_trigger_type)
+ && (prev_people_count_data != people_count)) {
+ if (!prev_people_count_data) {
+ delta_percent = PERCENTAGE_FULL;
+ } else {
+ delta_percent = delta_abs((delta_abs(prev_people_count_data - people_count) * PERCENTAGE_FULL) / prev_people_count_data);
+ }
+
+ if ((people_count > prev_people_count_data) && (delta_percent >= delta_cadence_up)) {
+ ret_val = true;
+ } else if ((people_count < prev_people_count_data) && (delta_percent >= delta_cadence_down)) {
+ ret_val = true;
+ } else {
+ }
+ } else {
+ }
+
+ return ret_val;
+}
+
+#endif // SENSOR_PEOPLE_COUNT_CADENCE
+
+#if SENSOR_THERMOMETER_CADENCE
+
+void sl_btmesh_sensor_thermometer_cadence_init(temperature_8_t temperature)
+{
+ static uint16_t delta_down = SENSOR_THERMOMETER_STATUS_TRIGGER_DELTA_DOWN;
+ static uint16_t delta_up = SENSOR_THERMOMETER_STATUS_TRIGGER_DELTA_UP;
+
+ static uint8_t cadence_low = SENSOR_THERMOMETER_FAST_CADENCE_LOW;
+ static uint8_t cadence_high = SENSOR_THERMOMETER_FAST_CADENCE_HIGH;
+
+ cadences[SENSOR_RHT_INDEX].property_id = PRESENT_AMBIENT_TEMPERATURE;
+ cadences[SENSOR_RHT_INDEX].period_divisor = SENSOR_THERMOMETER_FAST_CADENCE_PERIOD_DIVISOR;
+ cadences[SENSOR_RHT_INDEX].status_trigger_type = SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE;
+ cadences[SENSOR_RHT_INDEX].min_interval = SENSOR_THERMOMETER_STATUS_MIN_INTERVAL;
+ cadences[SENSOR_RHT_INDEX].fast_cadence_low.size = sizeof(cadence_low);
+ cadences[SENSOR_RHT_INDEX].fast_cadence_low.value = (uint8_t*)(&cadence_low);
+ cadences[SENSOR_RHT_INDEX].fast_cadence_high.size = sizeof(cadence_high);
+ cadences[SENSOR_RHT_INDEX].fast_cadence_high.value = (uint8_t*)(&cadence_high);
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value = (uint8_t*)(&delta_down);
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value = (uint8_t*)(&delta_up);
+
+ if (SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_DISCRETE_VALUE == cadences[SENSOR_RHT_INDEX].status_trigger_type) {
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.size = sizeof(uint8_t);
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.size = sizeof(uint8_t);
+ } else {
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.size = sizeof(delta_down);
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.size = sizeof(delta_up);
+ }
+
+ prev_temp_data = temperature;
+}
+
+uint32_t sl_btmesh_sensor_thermometer_handle_cadence(temperature_8_t temperature, sl_btmesh_evt_sensor_server_publish_t publish_period)
+{
+ uint32_t ret_timer_value = publish_period.period_ms;
+
+ // Check if the measured value requires publishing period modification
+ if (sensor_thermometer_delta_cadence(temperature)
+ || sensor_thermometer_fast_cadence(temperature)) {
+ // Calculate publishing timer value
+ ret_timer_value = publish_period.period_ms / (1 << cadences[SENSOR_RHT_INDEX].period_divisor);
+ if (ret_timer_value < (uint32_t)(1 << cadences[SENSOR_RHT_INDEX].min_interval)) {
+ ret_timer_value = (uint32_t)(1 << cadences[SENSOR_RHT_INDEX].min_interval);
+ }
+ }
+ prev_temp_data = temperature;
+
+ return ret_timer_value;
+}
+
+bool sl_btmesh_sensor_thermometer_set_cadence(sl_btmesh_evt_sensor_setup_server_set_cadence_request_t* evt)
+{
+ bool ret_val = false;
+
+ // Check if the received parameters are in valid range
+ if ((MAX_PERIOD_DIVISOR >= evt->period_divisor)
+ && (MAX_TRIGGER_TYPE >= evt->trigger_type)) {
+ if ((SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_PERCENTAGE == evt->trigger_type)
+ && (SENSOR_THERMOMETER_CADENCE_PERCENTAGE_PARAM_LEN == evt->params.len)
+ && (MAX_PUBLISHING_MIN_INTERVAL >= evt->params.data[4])) {
+ ret_val = true;
+ } else if ((SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_DISCRETE_VALUE == evt->trigger_type)
+ && (SENSOR_THERMOMETER_CADENCE_DISCRETE_PARAM_LEN == evt->params.len)
+ && (MAX_PUBLISHING_MIN_INTERVAL >= evt->params.data[2])) {
+ ret_val = true;
+ } else {
+ }
+ }
+
+ // store incoming thermometer sensor cadence parameters
+ if (true == ret_val) {
+ if ( SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_PERCENTAGE == evt->trigger_type) {
+ cadences[SENSOR_RHT_INDEX].period_divisor = evt->period_divisor;
+ cadences[SENSOR_RHT_INDEX].status_trigger_type = evt->trigger_type;
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[0] = evt->params.data[1];
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[1] = evt->params.data[0];
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[0] = evt->params.data[3];
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[1] = evt->params.data[2];
+ cadences[SENSOR_RHT_INDEX].min_interval = evt->params.data[4];
+ cadences[SENSOR_RHT_INDEX].fast_cadence_low.value[0] = evt->params.data[5];
+ cadences[SENSOR_RHT_INDEX].fast_cadence_high.value[0] = evt->params.data[6];
+ // In case of SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_DISCRETE_VALUE trigger type
+ } else {
+ cadences[SENSOR_RHT_INDEX].period_divisor = evt->period_divisor;
+ cadences[SENSOR_RHT_INDEX].status_trigger_type = evt->trigger_type;
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[0] = evt->params.data[0];
+ cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[0] = evt->params.data[1];
+ cadences[SENSOR_RHT_INDEX].min_interval = evt->params.data[2];
+ cadences[SENSOR_RHT_INDEX].fast_cadence_low.value[0] = evt->params.data[3];
+ cadences[SENSOR_RHT_INDEX].fast_cadence_high.value[0] = evt->params.data[4];
+ }
+ }
+
+ return ret_val;
+}
+
+uint16_t sl_btmesh_sensor_thermometer_get_cadence(uint8_t length, uint8_t* get_cadence_buffer)
+{
+ uint16_t buffer_len = 0;
+
+ if ((SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_PERCENTAGE == cadences[SENSOR_RHT_INDEX].status_trigger_type)
+ && (SENSOR_THERMOMETER_PERCENTAGE_GET_CADENCE_BUF_LEN <= length)) {
+ get_cadence_buffer[0] = cadences[SENSOR_RHT_INDEX].period_divisor
+ | (cadences[SENSOR_RHT_INDEX].status_trigger_type << STATUS_TRIGGER_TYPE_SHIFT);
+ get_cadence_buffer[1] = cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[1];
+ get_cadence_buffer[2] = cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[0];
+ get_cadence_buffer[3] = cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[1];
+ get_cadence_buffer[4] = cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[0];
+ get_cadence_buffer[5] = cadences[SENSOR_RHT_INDEX].min_interval;
+ get_cadence_buffer[6] = cadences[SENSOR_RHT_INDEX].fast_cadence_low.value[0];
+ get_cadence_buffer[7] = cadences[SENSOR_RHT_INDEX].fast_cadence_high.value[0];
+ buffer_len = SENSOR_THERMOMETER_PERCENTAGE_GET_CADENCE_BUF_LEN;
+ } else if ((SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_DISCRETE_VALUE == cadences[SENSOR_RHT_INDEX].status_trigger_type)
+ && (SENSOR_THERMOMETER_DISCRETE_GET_CADENCE_BUF_LEN <= length)) {
+ get_cadence_buffer[0] = cadences[SENSOR_RHT_INDEX].period_divisor
+ | (cadences[SENSOR_RHT_INDEX].status_trigger_type << STATUS_TRIGGER_TYPE_SHIFT);
+ get_cadence_buffer[1] = cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[0];
+ get_cadence_buffer[2] = cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[0];
+ get_cadence_buffer[3] = cadences[SENSOR_RHT_INDEX].min_interval;
+ get_cadence_buffer[4] = cadences[SENSOR_RHT_INDEX].fast_cadence_low.value[0];
+ get_cadence_buffer[5] = cadences[SENSOR_RHT_INDEX].fast_cadence_high.value[0];
+ buffer_len = SENSOR_THERMOMETER_DISCRETE_GET_CADENCE_BUF_LEN;
+ } else {
+ }
+
+ return buffer_len;
+}
+
+/***************************************************************************//**
+ * Check if the measured value requires publishing period modification.
+ *
+ * @param[in] temperature Thermometer sensor data value.
+ *
+ * @return True if modification is required false otherwise
+ ******************************************************************************/
+static bool sensor_thermometer_fast_cadence(temperature_8_t temperature)
+{
+ bool ret_val = false;
+
+ // Check if the Fast Cadence High value is equal or higher than the Fast Cadence Low value
+ if ((temperature_8_t)(cadences[SENSOR_RHT_INDEX].fast_cadence_high.value[0])
+ >= (temperature_8_t)(cadences[SENSOR_RHT_INDEX].fast_cadence_low.value[0])) {
+ // If the measured value is within the closed interval of
+ // [Fast Cadence Low, Fast Cadence High], Sensor Status publishing period
+ // modification is required.
+ if ((temperature >= (temperature_8_t)(cadences[SENSOR_RHT_INDEX].fast_cadence_low.value[0]))
+ && (temperature <= (temperature_8_t)(cadences[SENSOR_RHT_INDEX].fast_cadence_high.value[0]))) {
+ ret_val = true;
+ }
+ // If the Fast Cadence High value is lower than the Fast Cadence Low value
+ } else {
+ // If the measured value is lower than the Fast Cadence High value or is
+ // higher than the Fast Cadence Low value, Sensor Status publishing period
+ // modification is required.
+ if ((temperature < (temperature_8_t)(cadences[SENSOR_RHT_INDEX].fast_cadence_high.value[0]))
+ || (temperature > (temperature_8_t)(cadences[SENSOR_RHT_INDEX].fast_cadence_low.value[0]))) {
+ ret_val = true;
+ }
+ }
+
+ return ret_val;
+}
+
+/***************************************************************************//**
+ * Check if the change of the measured value requires publishing period modification.
+ *
+ * @param[in] temperature Thermometer sensor data value.
+ *
+ * @return True if modification is required false otherwise
+ ******************************************************************************/
+static bool sensor_thermometer_delta_cadence(temperature_8_t temperature)
+{
+ bool ret_val = false;
+ uint16_t delta_percent;
+ uint16_t percent_delta_up_value;
+ uint16_t percent_delta_down_value;
+
+ // Check the unit and format of the Status Trigger Delta Down and
+ // the Status Trigger Delta Up fields.
+ if (SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_DISCRETE_VALUE == cadences[SENSOR_RHT_INDEX].status_trigger_type) {
+ // If the temperature change is rising edge and the measured quantity change
+ // exceeds the configured Status Trigger Delta Up value, the Sensor Status
+ // messages are published more frequently.
+ if ((temperature > prev_temp_data)
+ && ((temperature - prev_temp_data) >= (temperature_8_t)(cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[0]))) {
+ ret_val = true;
+ // If the temperature change is rising edge and the measured quantity change
+ // exceeds the configured Status Trigger Delta Up value, the Sensor Status
+ // messages are published more frequently.
+ } else if ((temperature < prev_temp_data)
+ && ((prev_temp_data - temperature) >= (temperature_8_t)(cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[0]))) {
+ ret_val = true;
+ } else {
+ }
+ // Same check with measured value represented unitless as percentage
+ } else if ((SENSOR_THERMOMETER_STATUS_TRIGGER_TYPE_PERCENTAGE == cadences[SENSOR_RHT_INDEX].status_trigger_type)
+ && (prev_temp_data != temperature)) {
+ percent_delta_down_value = (uint16_t)(cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[0]
+ | (cadences[SENSOR_RHT_INDEX].status_trigger_delta_down.value[1] << FAST_CADENCE_DELTA_SHIFT));
+ percent_delta_up_value = (uint16_t)(cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[0]
+ | (cadences[SENSOR_RHT_INDEX].status_trigger_delta_up.value[1] << FAST_CADENCE_DELTA_SHIFT));
+
+ if (!prev_temp_data) {
+ delta_percent = PERCENTAGE_FULL;
+ } else {
+ delta_percent = delta_abs((delta_abs(prev_temp_data - temperature) * PERCENTAGE_FULL) / prev_temp_data);
+ }
+
+ if ((temperature > prev_temp_data) && (delta_percent >= percent_delta_up_value)) {
+ ret_val = true;
+ } else if ((temperature < prev_temp_data) && (delta_percent >= percent_delta_down_value)) {
+ ret_val = true;
+ } else {
+ }
+ } else {
+ }
+
+ return ret_val;
+}
+
+#endif // SENSOR_THERMOMETER_CADENCE
+
+#if SENSOR_THERMOMETER_CADENCE || SENSOR_PEOPLE_COUNT_CADENCE
+// Return the absolute value of val.
+static int delta_abs(int val)
+{
+ return val < 0 ? -val : val;
+}
+#endif
diff --git a/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server_cadence.h b/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server_cadence.h
new file mode 100644
index 00000000000..5bc2b4ef2d0
--- /dev/null
+++ b/app/btmesh/common/btmesh_sensor_server/sl_btmesh_sensor_server_cadence.h
@@ -0,0 +1,146 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_sensor_server_cadence.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_SENSOR_SERVER_CADENCE_H
+#define SL_BTMESH_SENSOR_SERVER_CADENCE_H
+
+#include "sl_btmesh_device_properties.h"
+#include "sl_btmesh_api.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_SENSOR_RHT_PRESENT
+#include "sl_sensor_rht_config.h"
+#endif // SL_CATALOG_SENSOR_RHT_PRESENT
+
+#ifdef SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+#include "sl_btmesh_sensor_people_count_config.h"
+#endif // SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT
+
+#if defined(SL_CATALOG_SENSOR_RHT_PRESENT) \
+ && defined(SL_BOARD_ENABLE_SENSOR_RHT) \
+ && SL_BOARD_ENABLE_SENSOR_RHT \
+ && SENSOR_THERMOMETER_CADENCE_ENABLE
+
+#define SENSOR_THERMOMETER_CADENCE 1
+#else
+#define SENSOR_THERMOMETER_CADENCE 0
+#endif
+
+#if defined(SL_CATALOG_BTMESH_SENSOR_PEOPLE_COUNT_PRESENT) \
+ && SL_BTMESH_SENSOR_PEOPLE_COUNT_CADENCE_ENABLE_CFG_VAL
+
+#define SENSOR_PEOPLE_COUNT_CADENCE 1
+#else
+#define SENSOR_PEOPLE_COUNT_CADENCE 0
+#endif
+
+#if SENSOR_PEOPLE_COUNT_CADENCE
+/***************************************************************************//**
+ * Initialize people count sensor cadence internals.
+ *
+ * @param[in] people_count People count sensor data value.
+ *
+ * @return none
+ ******************************************************************************/
+void sl_btmesh_sensor_people_count_cadence_init(count16_t people_count);
+
+/***************************************************************************//**
+ * Handle thermometer measured value cadence condition
+ *
+ * @param[in] people_count People count sensor data value.
+ * @param[in] publish_period Publish event data structure
+ *
+ * @return publishing timer value based on cadence parameters and measured value in ms
+ ******************************************************************************/
+uint32_t sl_btmesh_sensor_people_count_handle_cadence(count16_t people_count, sl_btmesh_evt_sensor_server_publish_t publish_period);
+
+/***************************************************************************//**
+ * Update thermometer sensor cadence internal parameters with input cadence data.
+ *
+ * @param[in] evt Pointer to sensor server set cadence request event.
+ *
+ * @return True if received parameters are in valid range false otherwise
+ ******************************************************************************/
+bool sl_btmesh_sensor_people_count_set_cadence(sl_btmesh_evt_sensor_setup_server_set_cadence_request_t* evt);
+
+/***************************************************************************//**
+ * Fill the input buffer with people count sensor cadence parameters.
+ *
+ * @param[out] get_cadence_buffer Parameters data buffer
+ *
+ * @return Parameters data buffer length
+ ******************************************************************************/
+uint16_t sl_btmesh_sensor_people_count_get_cadence(uint8_t length, uint8_t* get_cadence_buffer);
+
+#endif // SENSOR_PEOPLE_COUNT_CADENCE
+
+#if SENSOR_THERMOMETER_CADENCE
+/***************************************************************************//**
+ * Initialize temperature sensor cadence internals.
+ *
+ * @param[in] temperature Thermometer sensor data value.
+ *
+ * @return none
+ ******************************************************************************/
+void sl_btmesh_sensor_thermometer_cadence_init(temperature_8_t temperature);
+
+/***************************************************************************//**
+ * Handle thermometer measured value cadence condition
+ *
+ * @param[in] temperature Thermometer sensor data value.
+ * @param[in] publish_period Publish event data structure
+ *
+ * @return publishing timer value based on cadence parameters and measured value in ms
+ ******************************************************************************/
+uint32_t sl_btmesh_sensor_thermometer_handle_cadence(temperature_8_t temperature, sl_btmesh_evt_sensor_server_publish_t publish_period);
+
+/***************************************************************************//**
+ * Update thermometer sensor cadence internal parameters with input cadence data.
+ *
+ * @param[in] evt Pointer to sensor server set cadence request event.
+ *
+ * @return True if received parameters are in valid range false otherwise
+ ******************************************************************************/
+bool sl_btmesh_sensor_thermometer_set_cadence(sl_btmesh_evt_sensor_setup_server_set_cadence_request_t* evt);
+
+/***************************************************************************//**
+ * Fill the input buffer with thermometer sensor cadence parameters.
+ *
+ * @param[out] get_cadence_buffer Parameters data buffer
+ *
+ * @return Parameters data buffer length
+ ******************************************************************************/
+uint16_t sl_btmesh_sensor_thermometer_get_cadence(uint8_t length, uint8_t* get_cadence_buffer);
+#endif // SENSOR_THERMOMETER_CADENCE
+
+#endif // SL_BTMESH_SENSOR_SERVER_CADENCE_H
diff --git a/app/btmesh/common/btmesh_time_server/btmesh_time_server.dcd b/app/btmesh/common/btmesh_time_server/btmesh_time_server.dcd
new file mode 100644
index 00000000000..89405b34c3a
--- /dev/null
+++ b/app/btmesh/common/btmesh_time_server/btmesh_time_server.dcd
@@ -0,0 +1,10 @@
+[
+ {
+ "name": "Main",
+ "location": "0x0000",
+ "sig_models" : [
+ {"mid":"0x1200", "name":"Time Server"},
+ {"mid":"0x1201", "name":"Time Setup Server"}
+ ]
+ }
+]
\ No newline at end of file
diff --git a/app/btmesh/common/btmesh_time_server/config/sl_btmesh_time_server_config.h b/app/btmesh/common/btmesh_time_server/config/sl_btmesh_time_server_config.h
new file mode 100644
index 00000000000..655f39bf4d8
--- /dev/null
+++ b/app/btmesh/common/btmesh_time_server/config/sl_btmesh_time_server_config.h
@@ -0,0 +1,48 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_TIME_SERVER_CONFIG_H
+#define SL_BTMESH_TIME_SERVER_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Time Server configuration
+
+// Enable Logging
+// Default: 1
+// Enable / disable Logging for Time Server model specific messages for this component.
+#define SL_BTMESH_TIME_SERVER_LOGGING_CFG_VAL (1)
+
+//
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_TIME_SERVER_CONFIG_H
diff --git a/app/btmesh/common/btmesh_time_server/sl_btmesh_time_server.c b/app/btmesh/common/btmesh_time_server/sl_btmesh_time_server.c
new file mode 100644
index 00000000000..771c1528173
--- /dev/null
+++ b/app/btmesh/common/btmesh_time_server/sl_btmesh_time_server.c
@@ -0,0 +1,217 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Time Server module
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "em_common.h"
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_dcd.h"
+
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
+
+#ifdef SL_CATALOG_APP_LOG_PRESENT
+#include "app_log.h"
+#endif // SL_CATALOG_APP_LOG_PRESENT
+
+#include "sl_btmesh_time_server.h"
+#include "sl_btmesh_time_server_config.h"
+
+// Warning! The app_btmesh_util shall be included after the component configuration
+// header file in order to provide the component specific logging macro.
+#include "app_btmesh_util.h"
+
+/***************************************************************************//**
+ * @addtogroup Time Server
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Time initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ *
+ * @return Status of the initialization operation.
+ * Returns bg_err_success (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+uint16_t sl_btmesh_time_init(void)
+{
+ // Initialize time server models
+ sl_status_t result = sl_btmesh_time_server_init(BTMESH_TIME_SERVER_MAIN);
+ log_status_error_f(result, "sl_btmesh_time_server_init failed" NL);
+
+ return result;
+}
+
+/***************************************************************************//**
+ * Handling of time server time updated event.
+ *
+ * @param[in] evt Pointer to time server time updated event.
+ ******************************************************************************/
+static void handle_time_server_time_updated_event(
+ sl_btmesh_evt_time_server_time_updated_t *evt)
+{
+ log_info("evt:gecko_evt_mesh_time_server_time_updated_id, tai_seconds=0x");
+ // print only 40 bits as this is size of TAI Seconds
+ log_append_info("%2.2x", (uint8_t)((evt->tai_seconds >> 32) & 0xFF));
+ log_append_info("%4.4x", (uint16_t)((evt->tai_seconds >> 16) & 0xFFFF));
+ log_append_info("%4.4x, ", (uint16_t)(evt->tai_seconds & 0xFFFF));
+ log_append_info("subsecond=%u, uncertainty=%u ms, time_authority=%u, tai_utc_delta=%ld, \
+ time_zone_offset=%d" NL,
+ evt->subsecond,
+ (uint16_t)evt->uncertainty * 10,
+ evt->time_authority,
+ evt->tai_utc_delta,
+ evt->time_zone_offset);
+ (void)evt;
+}
+
+/***************************************************************************//**
+ * Handling of time server time zone offset updated event.
+ *
+ * @param[in] evt Pointer to time server time zone offset updated event.
+ ******************************************************************************/
+static void handle_time_server_time_zone_offset_updated_event(
+ sl_btmesh_evt_time_server_time_zone_offset_updated_t *evt)
+{
+ log_info("evt:gecko_evt_mesh_time_server_time_zone_offset_updated_id, \
+ zone_offset_current=%d, zone_offset_new=%d, tai_of_zone_change=0x",
+ evt->time_zone_offset_current,
+ evt->time_zone_offset_new);
+ // print only 40 bits as this is size of TAI of Zone Change
+ log_append_info("%2.2x", (uint8_t)((evt->tai_of_zone_change >> 32) & 0xFF));
+ log_append_info("%4.4x", (uint16_t)((evt->tai_of_zone_change >> 16) & 0xFFFF));
+ log_append_info("%4.4x", (uint16_t)(evt->tai_of_zone_change & 0xFFFF));
+ log_append_info(NL);
+ (void)evt;
+}
+
+/***************************************************************************//**
+ * Handling of time server tai utc delta updated event.
+ *
+ * @param[in] evt Pointer to time server tai utc delta updated event.
+ ******************************************************************************/
+static void handle_time_server_tai_utc_delta_updated_event(
+ sl_btmesh_evt_time_server_tai_utc_delta_updated_t *evt)
+{
+ log_info("evt:gecko_evt_mesh_time_server_tai_utc_delta_updated_id, \
+ tai_utc_delta_current=%ld, tai_utc_delta_new=%ld, tai_of_delta_change=0x",
+ evt->tai_utc_delta_current,
+ evt->tai_utc_delta_new);
+ // print only 40 bits as this is size of TAI of Delta Change
+ log_append_info("%2.2x", (uint8_t)((evt->tai_of_delta_change >> 32) & 0xFF));
+ log_append_info("%4.4x", (uint16_t)((evt->tai_of_delta_change >> 16) & 0xFFFF));
+ log_append_info("%4.4x", (uint16_t)(evt->tai_of_delta_change & 0xFFFF));
+ log_append_info(NL);
+ (void)evt;
+}
+
+/***************************************************************************//**
+ * Handling of time server time role updated event.
+ *
+ * @param[in] evt Pointer to time server time role updated event.
+ ******************************************************************************/
+static void handle_time_server_time_role_updated_event(
+ sl_btmesh_evt_time_server_time_role_updated_t *evt)
+{
+ log_info("evt:gecko_evt_mesh_time_server_time_role_updated_id, time_role=");
+ switch (evt->time_role) {
+ case sl_btmesh_time_client_time_role_none:
+ log_append_info("None");
+ break;
+
+ case sl_btmesh_time_client_time_role_authority:
+ log_append_info("Authority");
+ break;
+
+ case sl_btmesh_time_client_time_role_relay:
+ log_append_info("Relay");
+ break;
+
+ case sl_btmesh_time_client_time_role_client:
+ log_append_info("Client");
+ break;
+
+ default:
+ break;
+ }
+ log_append_info(NL);
+}
+
+/*******************************************************************************
+ * Handling of mesh time events.
+ * It handles:
+ * - time_server_time_updated
+ * - time_server_time_zone_offset_updated
+ * - time_server_tai_utc_delta_updated
+ * - time_server_time_role_updated
+ *
+ * @param[in] evt Pointer to incoming time event.
+ ******************************************************************************/
+void sl_btmesh_time_server_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_time_server_time_updated_id:
+ handle_time_server_time_updated_event(
+ &(evt->data.evt_time_server_time_updated));
+ break;
+
+ case sl_btmesh_evt_time_server_time_zone_offset_updated_id:
+ handle_time_server_time_zone_offset_updated_event(
+ &(evt->data.evt_time_server_time_zone_offset_updated));
+ break;
+
+ case sl_btmesh_evt_time_server_tai_utc_delta_updated_id:
+ handle_time_server_tai_utc_delta_updated_event(
+ &(evt->data.evt_time_server_tai_utc_delta_updated));
+ break;
+
+ case sl_btmesh_evt_time_server_time_role_updated_id:
+ handle_time_server_time_role_updated_event(
+ &(evt->data.evt_time_server_time_role_updated));
+ break;
+
+ case sl_btmesh_evt_node_initialized_id:
+ if (evt->data.evt_node_initialized.provisioned) {
+ sl_btmesh_time_init();
+ }
+ break;
+
+ case sl_btmesh_evt_node_provisioned_id:
+ sl_btmesh_time_init();
+ break;
+
+ default:
+ break;
+ }
+}
+
+/** @} (end addtogroup Time Server) */
diff --git a/app/btmesh/common/btmesh_time_server/sl_btmesh_time_server.h b/app/btmesh/common/btmesh_time_server/sl_btmesh_time_server.h
new file mode 100644
index 00000000000..3e33471735f
--- /dev/null
+++ b/app/btmesh/common/btmesh_time_server/sl_btmesh_time_server.h
@@ -0,0 +1,59 @@
+/***************************************************************************//**
+ * @file
+ * @brief sl_btmesh_time_server.h
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_TIME_SERVER_H
+#define SL_BTMESH_TIME_SERVER_H
+
+/***************************************************************************//**
+ * Scenes initialization.
+ * This should be called at each boot if provisioning is already done.
+ * Otherwise this function should be called after provisioning is completed.
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @return Status of the initialization operation.
+ * Returns bg_err_success (0) if succeed, non-zero otherwise.
+ ******************************************************************************/
+uint16_t sl_btmesh_scenes_init(void);
+
+/***************************************************************************//**
+ * Handling of mesh time events.
+ * It handles:
+ * - time_server_time_updated
+ * - time_server_time_zone_offset_updated
+ * - time_server_tai_utc_delta_updated
+ * - time_server_time_role_updated
+ *
+ * It is called automatically by the Universal Configurator Framework
+ *
+ * @param[in] evt Pointer to incoming time event.
+ ******************************************************************************/
+void sl_btmesh_time_server_on_event(sl_btmesh_msg_t *evt);
+
+#endif // SL_BTMESH_TIME_SERVER_H
diff --git a/app/btmesh/common/btmesh_wstk_lcd/config/sl_btmesh_wstk_lcd_config.h b/app/btmesh/common/btmesh_wstk_lcd/config/sl_btmesh_wstk_lcd_config.h
new file mode 100644
index 00000000000..092e8642e36
--- /dev/null
+++ b/app/btmesh/common/btmesh_wstk_lcd/config/sl_btmesh_wstk_lcd_config.h
@@ -0,0 +1,221 @@
+/***************************************************************************//**
+ * @file
+ * @brief
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+#ifndef SL_BTMESH_WSTK_LCD_CONFIG_H
+#define SL_BTMESH_WSTK_LCD_CONFIG_H
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// LCD rows configuration
+
+// Row for Device Name
+// Default: 1
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Device Name will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_NAME_CFG_VAL (1)
+
+// Row for Node Status
+// Default: 2
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Node Status will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_STATUS_CFG_VAL (2)
+
+// Row for Connection Status
+// Default: 3
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Connection Status will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_CONNECTION_CFG_VAL (3)
+
+// Row for Friend status in a Friendship
+// Default: 4
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Friend status in a Friendship will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_FRIEND_CFG_VAL (4)
+
+// Row for LPN status in a Friendship
+// Default: 4
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the LPN status in a Friendship will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_LPN_CFG_VAL (4)
+
+// Sensor Server
+
+// Row for People Count
+// Default: 5
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the People Count will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_PEOPLE_COUNT_CFG_VAL (5)
+
+// Row for Server Temperature
+// Default: 6
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Server Temperature will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_TEMPERATURE_CFG_VAL (6)
+
+// Row for Illuminance
+// Default: 7
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Illuminance will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_ILLUMINANCE_CFG_VAL (7)
+
+//
+
+// Sensor Client
+
+// Row for Sensor Data
+// Default: 5
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the First row of Sensor Data read by the client will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_SENSOR_DATA_CFG_VAL (5)
+
+//
+
+// Lighting
+
+// Row for Lightness
+// Default: 5
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Lightness will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_LIGHTNESS_CFG_VAL (5)
+
+// Row for Color Temperature
+// Default: 6
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Color Temperature will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_COLOR_TEMPERATURE_CFG_VAL (6)
+
+// Row for Hue
+// Default: 6
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Hue will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_HUE_CFG_VAL (6)
+
+// Row for Delta UV
+// Default: 7
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Delta UV will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_DELTAUV_CFG_VAL (7)
+
+// Row for Saturation
+// Default: 7
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Saturation will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_SATURATION_CFG_VAL (7)
+
+//
+
+// Firmware Update
+
+// Firmware Update Status
+// Default: 5
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Firmware Update Status will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_STATUS_CFG_VAL (5)
+
+// Firmware Update Messages
+// Default: 6
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Firmware Update Messages will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_FW_UPDATE_MESSAGES_CFG_VAL (6)
+
+//
+
+// BLOB Transfer
+
+// BLOB Transfer Status
+// Default: 7
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the BLOB Transfer Status will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_BLOB_STATUS_CFG_VAL (7)
+
+// BLOB Transfer ID
+// Default: 8
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the BLOB Transfer ID will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_BLOB_ID_CFG_VAL (8)
+
+// BLOB Transfer Progress
+// Default: 9
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the BLOB Transfer Progress will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_BLOB_PROGRESS_CFG_VAL (9)
+
+//
+
+// Distributor
+
+// Row for Fw Distribution Server Firmware List
+// Default: 5
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Firmware List will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_DIST_FW_LIST_CFG_VAL (5)
+
+// Row for Fw Distribution Server Upload Progress
+// Default: 6
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Upload Progress will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_DIST_UPLOAD_PROGRESS_CFG_VAL (6)
+
+// Row for Fw Distribution Server Node List
+// Default: 7
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Node List will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_DIST_NODE_LIST_CFG_VAL (7)
+
+// Row for Fw Distribution Server State
+// Default: 8
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the State will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_DIST_STATE_CFG_VAL (8)
+
+// Row for Fw Distribution Server Distribution Progress
+// Default: 9
+// <1-9:1>
+// LCD on WSTKs have 9 rows. Out of these the Distribution Progress will be printed in the row specified here.
+#define SL_BTMESH_WSTK_LCD_ROW_DIST_DISTRIBUTION_PROGRESS_CFG_VAL (9)
+
+//
+
+//
+
+// LCD texts
+
+// Text for initializing graphics.
+// Text for initializing graphics
+#define SL_BTMESH_WSTK_LCD_GRAPH_INIT_TEXT_CFG_VAL "SILICON LABORATORIES\nBluetooth Mesh Demo\n\n"
+
+// Text to be printed on the LCD when it is initialized.
+// Text to be printed on the LCD when it is initialized
+#define SL_BTMESH_WSTK_LCD_INIT_TEXT_CFG_VAL "initializing"
+
+//
+
+// <<< end of configuration section >>>
+
+#endif // SL_BTMESH_WSTK_LCD_CONFIG_H
diff --git a/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_graphics.c b/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_graphics.c
new file mode 100644
index 00000000000..3690be2bddf
--- /dev/null
+++ b/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_graphics.c
@@ -0,0 +1,185 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Graphics: Draws the graphics on the display
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+// standard headers
+#include
+#include
+
+#include "app_assert.h"
+#include "em_types.h"
+#include "glib.h"
+#include "dmd.h"
+
+// Own header
+#include "sl_btmesh_wstk_graphics.h"
+
+/// Left arrow polygon vertices
+#define LEFT_ARROW_7x6 { 1, 123, 8, 120, 8, 126 }
+/// Right arrow polygon vertices
+#define RIGHT_ARROW_7x6 { 119, 120, 126, 123, 119, 126 }
+
+// -----------------------------------------------------------------------------
+// Local Variables
+
+// Global glib context
+static GLIB_Context_t glibContext;
+// Current line number stored for printing text
+static uint8_t graphLineNum = 0;
+// Device name string
+static char *deviceHeader = NULL;
+
+// -----------------------------------------------------------------------------
+// Static Function Declarations
+
+/***************************************************************************//**
+ * @brief Print the given string center aligned
+ *
+ * @note The string may contain several lines separated by new line
+ * characters ('\n'). Each line will be printed center aligned.
+ *
+ * @param[in] pContext Context
+ * @param[in] pString String to be displayed
+ *
+ * @return Status of printing to LCD
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL In case of failure
+ ******************************************************************************/
+static sl_status_t graphPrintCenter(GLIB_Context_t *pContext, char *pString);
+
+// -----------------------------------------------------------------------------
+// Function Definitions
+
+void graphInit(char *header)
+{
+ EMSTATUS status;
+
+ // Initialize the DMD module for the DISPLAY device driver.
+ status = DMD_init(0);
+ app_assert_status_f(status, "DMD init error");
+
+ status = GLIB_contextInit(&glibContext);
+ app_assert_status_f(status, "GLIB init error");
+
+ glibContext.backgroundColor = White;
+ glibContext.foregroundColor = Black;
+
+ // Use Narrow font
+ status = GLIB_setFont(&glibContext, (GLIB_Font_t *)&GLIB_FontNarrow6x8);
+ app_assert_status_f(status, "Font setting error");
+ deviceHeader = header;
+}
+
+sl_status_t graphWriteString(char *string)
+{
+ sl_status_t ret = SL_STATUS_OK;
+ EMSTATUS status;
+ status = GLIB_clear(&glibContext);
+ if (GLIB_OK != status) {
+ return SL_STATUS_FAIL;
+ }
+
+ // Reset line number, print header and device name
+ graphLineNum = 0;
+ ret = graphPrintCenter(&glibContext, deviceHeader);
+ if (SL_STATUS_OK != ret) {
+ return ret;
+ }
+
+ // Print the string below the header center aligned
+ ret = graphPrintCenter(&glibContext, string);
+ if (SL_STATUS_OK != ret) {
+ return ret;
+ }
+
+ status = DMD_updateDisplay();
+ if (DMD_OK != status) {
+ return SL_STATUS_FAIL;
+ }
+ return ret;
+}
+
+sl_status_t graphDrawArrow(sl_btmesh_LCD_arrow arrow)
+{
+ EMSTATUS status = DMD_OK;
+ const int32_t left_points[] = LEFT_ARROW_7x6;
+ const int32_t right_points[] = RIGHT_ARROW_7x6;
+ if ((arrow & SL_BTMESH_LCD_LEFT_ARROW) != 0) {
+ status = GLIB_drawPolygon(&glibContext, 3, left_points);
+ }
+ if (((arrow & SL_BTMESH_LCD_RIGHT_ARROW) != 0) && (DMD_OK == status)) {
+ status = GLIB_drawPolygon(&glibContext, 3, right_points);
+ }
+
+ if (DMD_OK == status) {
+ status = DMD_updateDisplay();
+ }
+
+ return DMD_OK == status ? SL_STATUS_OK : SL_STATUS_FAIL;
+}
+
+// -----------------------------------------------------------------------------
+// Static Function Definitions
+
+static sl_status_t graphPrintCenter(GLIB_Context_t *pContext, char *pString)
+{
+ do {
+ char* nextToken;
+ uint8_t len;
+
+ // Search for the next important token (new line or terminating NULL)
+ for (nextToken = pString;
+ ((*nextToken != '\n') && (*nextToken != '\0'));
+ nextToken++) {
+ ;
+ }
+
+ len = nextToken - pString;
+ // Print the line if it is not null length
+ if (len) {
+ uint8_t strWidth = len * pContext->font.fontWidth;
+ uint8_t posX = (pContext->pDisplayGeometry->xSize - strWidth) >> 1;
+ uint8_t posY = ((pContext->font.lineSpacing + pContext->font.fontHeight)
+ * graphLineNum)
+ + pContext->font.lineSpacing;
+ EMSTATUS status = GLIB_drawString(pContext, pString, len, posX, posY, 0);
+ if (GLIB_OK != status && GLIB_ERROR_NOTHING_TO_DRAW != status) {
+ return SL_STATUS_FAIL;
+ }
+ }
+ pString = nextToken;
+ // If the token at the end of the line is new line character,
+ // then increase line number
+ if (*nextToken == '\n') {
+ graphLineNum++;
+ pString++;
+ }
+ } while (*pString); // while terminating NULL is not reached
+ return SL_STATUS_OK;
+}
diff --git a/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_graphics.h b/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_graphics.h
new file mode 100644
index 00000000000..358f7ff0531
--- /dev/null
+++ b/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_graphics.h
@@ -0,0 +1,87 @@
+/***************************************************************************//**
+ * @file
+ * @brief Displays text on the LCD
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_WSTK_GRAPHICS_H
+#define SL_BTMESH_WSTK_GRAPHICS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "sl_status.h"
+
+/// Enumeration of possible arrows to be drawn
+typedef enum {
+ /// Left arrow
+ SL_BTMESH_LCD_LEFT_ARROW = 1 << 0,
+ /// Right arrow
+ SL_BTMESH_LCD_RIGHT_ARROW = 1 << 1,
+ /// Left arrow and right arrow
+ SL_BTMESH_LCD_LEFT_RIGHT_ARROW = SL_BTMESH_LCD_LEFT_ARROW
+ | SL_BTMESH_LCD_RIGHT_ARROW
+} sl_btmesh_LCD_arrow;
+
+// -----------------------------------------------------------------------------
+// Public Function Declarations
+
+/***************************************************************************//**
+ * @brief Initialize graphics stack.
+ *
+ * @param[in] header Header Text on display
+ ******************************************************************************/
+void graphInit(char *header);
+
+/***************************************************************************//**
+ * @brief Display a string on the LCD center aligned.
+ *
+ * @param[in] string String to be displayed
+ *
+ * @returns Status of the command.
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL In case of failure
+ ******************************************************************************/
+sl_status_t graphWriteString(char *string);
+
+/***************************************************************************//**
+ * Draw arrows on the page.
+ *
+ * @param[in] arrow Arrow to be drawn
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL In case of failure
+ ******************************************************************************/
+sl_status_t graphDrawArrow(sl_btmesh_LCD_arrow arrow);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SL_BTMESH_WSTK_GRAPHICS_H */
diff --git a/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_lcd.c b/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_lcd.c
new file mode 100644
index 00000000000..df5def2ce7e
--- /dev/null
+++ b/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_lcd.c
@@ -0,0 +1,213 @@
+/***************************************************************************//**
+ * @file
+ * @brief LCD driver implementation file
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include
+#include
+#include "sl_btmesh_wstk_graphics.h"
+#include "sl_btmesh_wstk_lcd.h"
+#include "sl_btmesh_wstk_lcd_config.h"
+#include "sl_malloc.h"
+
+#include "app_assert.h"
+
+/***************************************************************************//**
+ * Refresh selected page.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL If display manipulation failed
+ ******************************************************************************/
+static sl_status_t refresh_page();
+
+/// Array for storing the LCD content
+static char *LCD_data = NULL;
+/// Number of pages stored
+static uint8_t page_cnt = 0;
+/// Page displayed
+static uint8_t page_select = 0;
+
+/***************************************************************************//**
+ * @addtogroup disp_interface
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * Call a callback function at the given frequency.
+ *
+ * @param[in] pFunction Pointer to function that should be called at the
+ * given frequency.
+ * @param[in] argument Argument to be given to the function.
+ * @param[in] frequency Frequency at which to call function at.
+ *
+ * @return Status code of the operation.
+ *
+ * @note This is needed by the LCD driver
+ ******************************************************************************/
+int rtcIntCallbackRegister(void (*pFunction)(void *),
+ void *argument,
+ unsigned int frequency)
+{
+ (void)pFunction;
+ (void)argument;
+ (void)frequency;
+
+ return 0;
+}
+
+sl_status_t sl_btmesh_LCD_init(void)
+{
+ graphInit(SL_BTMESH_WSTK_LCD_GRAPH_INIT_TEXT_CFG_VAL);
+
+ return sl_btmesh_LCD_write(SL_BTMESH_WSTK_LCD_INIT_TEXT_CFG_VAL, SL_BTMESH_WSTK_LCD_ROW_STATUS_CFG_VAL);
+}
+
+sl_status_t sl_btmesh_LCD_write(const char *str, uint8_t row)
+{
+ return sl_btmesh_LCD_write_paged(str, row, 0);
+}
+
+sl_status_t sl_btmesh_LCD_write_paged(const char *str, uint8_t row, uint8_t page)
+{
+ char *pPage;
+ char *pRow;
+
+ if (row > LCD_ROW_MAX || row < 1) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+
+ uint8_t page_cnt_tmp = page_cnt;
+ if (page >= page_cnt) {
+ page_cnt = page + 1;
+ LCD_data = sl_realloc(LCD_data, LCD_ROW_MAX * LCD_ROW_LEN * page_cnt);
+ app_assert(LCD_data != NULL, "LCD allocation error.");
+ memset(&LCD_data[LCD_ROW_MAX * LCD_ROW_LEN * page_cnt_tmp],
+ 0,
+ LCD_ROW_MAX * LCD_ROW_LEN * (page_cnt - page_cnt_tmp));
+ }
+
+ pPage = &(LCD_data[page * LCD_ROW_MAX * LCD_ROW_LEN]);
+
+ pRow = &(pPage[(row - 1) * LCD_ROW_LEN]);
+
+ strcpy(pRow, str);
+
+ pPage[LCD_ROW_MAX * LCD_ROW_LEN - 1] = '\0';
+
+ if ((page == page_select) || ((page - 1) == page_select)) {
+ return refresh_page();
+ } else {
+ // Not necessary to print hidden page
+ return SL_STATUS_OK;
+ }
+}
+
+sl_status_t sl_btmesh_LCD_remove_page(uint8_t page)
+{
+ if ((page == 0) || (page >= page_cnt)) {
+ return SL_STATUS_INVALID_INDEX;
+ }
+ if (page != (page_cnt - 1)) {
+ memcpy(&LCD_data[LCD_ROW_MAX * LCD_ROW_LEN * page],
+ &LCD_data[LCD_ROW_MAX * LCD_ROW_LEN * (page + 1)],
+ LCD_ROW_MAX * LCD_ROW_LEN * (page_cnt - page));
+ }
+ LCD_data = sl_realloc(LCD_data, LCD_ROW_MAX * LCD_ROW_LEN * --page_cnt);
+ app_assert(LCD_data != NULL, "LCD allocation error.");
+ // Make sure display shows consistent data
+ if (page == page_select) {
+ return sl_btmesh_LCD_select_page(0);
+ } else if (page < page_select) {
+ return sl_btmesh_LCD_prev_page();
+ }
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_LCD_clear_pages(void)
+{
+ LCD_data = sl_realloc(LCD_data, LCD_ROW_MAX * LCD_ROW_LEN);
+ app_assert(LCD_data != NULL, "LCD allocation error.");
+ memset(LCD_data, 0, LCD_ROW_MAX * LCD_ROW_LEN);
+ page_cnt = 1;
+ return sl_btmesh_LCD_select_page(0);
+}
+
+sl_status_t sl_btmesh_LCD_select_page(uint8_t page)
+{
+ if (page < page_cnt) {
+ page_select = page;
+ return refresh_page();
+ }
+ return SL_STATUS_INVALID_INDEX;
+}
+
+sl_status_t sl_btmesh_LCD_next_page(void)
+{
+ if (page_select == page_cnt - 1) {
+ return SL_STATUS_OK;
+ }
+ ++page_select;
+ return refresh_page();
+}
+
+sl_status_t sl_btmesh_LCD_prev_page(void)
+{
+ if (page_select == 0) {
+ return SL_STATUS_OK;
+ }
+ --page_select;
+ return refresh_page();
+}
+
+/** @} (end addtogroup disp_interface) */
+
+static sl_status_t refresh_page()
+{
+ char LCD_message[LCD_ROW_MAX * LCD_ROW_LEN] = { 0 };
+ char *pPage;
+ char *pRow;
+ pPage = &(LCD_data[page_select * LCD_ROW_MAX * LCD_ROW_LEN]);
+
+ for (int i = 0; i < LCD_ROW_MAX; ++i) {
+ pRow = &(pPage[i * LCD_ROW_LEN]);
+ strcat(LCD_message, pRow);
+ strcat(LCD_message, "\n");
+ }
+
+ sl_status_t status = graphWriteString(LCD_message);
+
+ if ((page_select > 0) && (SL_STATUS_OK == status)) {
+ status = graphDrawArrow(SL_BTMESH_LCD_LEFT_ARROW);
+ }
+ if ((page_select < page_cnt - 1) && (SL_STATUS_OK == status)) {
+ status = graphDrawArrow(SL_BTMESH_LCD_RIGHT_ARROW);
+ }
+
+ return status;
+}
diff --git a/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_lcd.h b/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_lcd.h
new file mode 100644
index 00000000000..85f78c6f41f
--- /dev/null
+++ b/app/btmesh/common/btmesh_wstk_lcd/sl_btmesh_wstk_lcd.h
@@ -0,0 +1,178 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh WSTK LCD driver header file
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef SL_BTMESH_WSTK_LCD_H
+#define SL_BTMESH_WSTK_LCD_H
+
+#include
+#include "sl_status.h"
+#include "sl_btmesh_wstk_lcd_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/***************************************************************************//**
+ * \defgroup lcd_driver LCD Driver
+ * \brief Driver for SPI LCD Display.
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup disp_interface
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup lcd_driver
+ * @{
+ ******************************************************************************/
+
+/*******************************************************************************
+ * LCD content can be updated one row at a time using function LCD_write().
+ * Row number is passed as parameter, the possible values are defined below.
+ ******************************************************************************/
+/** up to 9 rows available on screen */
+#define LCD_ROW_MAX 9
+/** up to 21 characters, plus line break each row */
+#define LCD_ROW_LEN 22
+
+/***************************************************************************//**
+ * Initialize LCD.
+ *
+ * Called once at startup.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL If writing header failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_LCD_init(void);
+
+/***************************************************************************//**
+ * This function is used to write one line on the LCD.
+ *
+ * @note This uses @ref sl_btmesh_LCD_write_paged to write onto page 0.
+ *
+ * @param[in] str Pointer to string which is displayed in the specified row.
+ * @param[in] row Selects which line of LCD display is written,
+ * possible values are defined as LCD_ROW_xxx.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_PARAMETER If row is invalid
+ * @retval SL_STATUS_FAIL If writing failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_LCD_write(const char *str, uint8_t row);
+
+/***************************************************************************//**
+ * This function is used to write one line on the LCD onto the given page.
+ *
+ * Pages are handled dynamically
+ *
+ * @param[in] str Pointer to string which is displayed in the specified row.
+ * @param[in] row Selects which line of LCD display is written,
+ * possible values are defined as LCD_ROW_xxx.
+ * @param[in] page Page identifier. Zero-based.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_PARAMETER If row is invalid
+ * @retval SL_STATUS_FAIL If writing failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_LCD_write_paged(const char *str,
+ uint8_t row,
+ uint8_t page);
+
+/***************************************************************************//**
+ * Select page to be displayed and display it.
+ *
+ * @param[in] page Page index to display. Zero-based.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL If page refresh failed
+ * @retval SL_STATUS_INVALID_INDEX If page index is out of range
+ ******************************************************************************/
+sl_status_t sl_btmesh_LCD_select_page(uint8_t page);
+
+/***************************************************************************//**
+ * Select next page to be displayed and display it.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success, or last page is displayed
+ * @retval SL_STATUS_FAIL If page refresh failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_LCD_next_page(void);
+
+/***************************************************************************//**
+ * Select previous page to be displayed and display it.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success, or last page is displayed
+ * @retval SL_STATUS_FAIL If page refresh failed
+ ******************************************************************************/
+sl_status_t sl_btmesh_LCD_prev_page(void);
+
+/***************************************************************************//**
+ * Remove page.
+ *
+ * If currently displayed page is removed, display a valid one. If last page is
+ * removed, display the first one. If page is removed from before the currently
+ * displayed page refresh page so consistency is upheld.
+ *
+ * Page 0 can't be removed.
+ *
+ * @param[in] page Page index to remove. Zero-based.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_INDEX If page is out of range or 0.
+ * @retval SL_STATUS_FAIL If page needed to be refreshed and was unsuccessful
+ ******************************************************************************/
+sl_status_t sl_btmesh_LCD_remove_page(uint8_t page);
+
+/***************************************************************************//**
+ * Remove every page except the first.
+ *
+ * Refreshes page.
+ *
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_FAIL If page refresh is unsuccessful
+ ******************************************************************************/
+sl_status_t sl_btmesh_LCD_clear_pages(void);
+
+/** @} (end addtogroup lcd_driver) */
+/** @} (end addtogroup disp_interface) */
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
+
+#endif /* SL_BTMESH_WSTK_LCD_H */
diff --git a/app/btmesh/common/ncp_btmesh_dfu/ncp_btmesh_dfu.c b/app/btmesh/common/ncp_btmesh_dfu/ncp_btmesh_dfu.c
new file mode 100644
index 00000000000..828f23c1d4e
--- /dev/null
+++ b/app/btmesh/common/ncp_btmesh_dfu/ncp_btmesh_dfu.c
@@ -0,0 +1,185 @@
+/***************************************************************************//**
+ * @file
+ * @brief Implementation of FW Update Server application level functionality
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include
+
+#include "em_common.h"
+#include "sl_bt_api.h"
+
+#include "ncp_btmesh_dfu.h"
+#include "sl_status.h"
+
+/// Maximum length of the version infromation
+#define VER_INFO_MAX_LENGTH 106
+/// Length of the Company ID in bytes
+#define CID_LENGTH 2
+/// Maximum length of firmware URI
+#define URI_MAX_LENGTH 255
+
+/// Storage entry structure
+typedef PACKSTRUCT (struct storage_s {
+ /// Length of the FWID
+ uint8_t fwid_len;
+ /// FWID data
+ ///
+ /// @details Contains 2 bytes of CID (little-endian) and up to 106 bytes of
+ /// version information.
+ uint8_t *fwid_data;
+ /// URI length
+ uint8_t uri_len;
+ /// URI data
+ ///
+ /// @details Contains up to 255 bytes.
+ uint8_t *uri_data;
+}) storage_t;
+
+PACKSTRUCT(struct {
+ /// Current length of the storage
+ uint8_t storage_len;
+ /// Storage data
+ storage_t * storage;
+}) dfu = { .storage_len = 0, .storage = NULL }; // Initialize values to 0
+
+/***************************************************************************//**
+ * (Re)allocates storage based on the index of the FW
+ *
+ * @param idx Index of the firmware for which reallocate storage if needed
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NO_MORE_RESOURCE In case of memory allocation error
+ ******************************************************************************/
+static sl_status_t allocate_storage(uint8_t idx);
+
+static sl_status_t allocate_storage(uint8_t idx)
+{
+ void *ptr;
+
+ if (idx >= dfu.storage_len) {
+ ptr = calloc((idx + 1), sizeof(storage_t));
+ if (NULL == ptr) {
+ return SL_STATUS_NO_MORE_RESOURCE;
+ }
+ if (dfu.storage != NULL) {
+ memcpy(ptr, dfu.storage, sizeof(storage_t) * dfu.storage_len);
+ }
+ free(dfu.storage);
+ dfu.storage = ptr;
+ dfu.storage_len = idx + 1;
+ }
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t mesh_platform_get_installed_firmware_information(uint8_t index,
+ uint8_t *fwid_len,
+ const uint8_t **fwid_ptr,
+ uint8_t *uri_len,
+ const uint8_t **uri_ptr)
+{
+ if (index < dfu.storage_len) {
+ *fwid_len = dfu.storage[index].fwid_len;
+ *fwid_ptr = dfu.storage[index].fwid_data;
+ *uri_len = dfu.storage[index].uri_len;
+ *uri_ptr = dfu.storage[index].uri_data;
+ return SL_STATUS_OK;
+ }
+
+ return SL_STATUS_NOT_FOUND;
+}
+
+sl_status_t sl_btmesh_ncp_dfu_set_fwid(uint8_t idx, uint8_t len, uint8_t *data)
+{
+ void *ptr;
+ if ((len < CID_LENGTH) || (len > (CID_LENGTH + VER_INFO_MAX_LENGTH))) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+ sl_status_t sc = allocate_storage(idx);
+ if (SL_STATUS_OK != sc) {
+ return sc;
+ }
+ ptr = realloc(dfu.storage[idx].fwid_data, len);
+ if (NULL == ptr) {
+ return SL_STATUS_NO_MORE_RESOURCE;
+ }
+ dfu.storage[idx].fwid_len = len;
+ dfu.storage[idx].fwid_data = ptr;
+ memcpy(dfu.storage[idx].fwid_data, data, len);
+
+ return SL_STATUS_OK;
+}
+
+sl_status_t sl_btmesh_ncp_dfu_set_uri(uint8_t idx,
+ uint8_t type,
+ uint8_t len,
+ uint8_t *data)
+{
+ void *ptr;
+
+ switch (type) {
+ case USER_SUB_CMD_URI_ID_WRT: {
+ sl_status_t sc = allocate_storage(idx);
+ if (SL_STATUS_OK != sc) {
+ return sc;
+ }
+ ptr = realloc(dfu.storage[idx].uri_data, len);
+ if (NULL == ptr) {
+ return SL_STATUS_NO_MORE_RESOURCE;
+ }
+ dfu.storage[idx].uri_data = ptr;
+ dfu.storage[idx].uri_len = len;
+ memcpy(dfu.storage[idx].uri_data, data, len);
+ }
+ break;
+ case USER_SUB_CMD_URI_ID_APP:
+ if (idx >= dfu.storage_len) {
+ // This index has not been initialized yet
+ return SL_STATUS_NOT_FOUND;
+ }
+ if (dfu.storage[idx].uri_len + len > URI_MAX_LENGTH) {
+ // According to the specification, URI can't be longer than 255 bytes
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+ // Allocate memory to fit appended value
+ ptr = realloc(dfu.storage[idx].uri_data, dfu.storage[idx].uri_len + len);
+ if (NULL == ptr) {
+ return SL_STATUS_NO_MORE_RESOURCE;
+ }
+ dfu.storage[idx].uri_data = ptr;
+ // Append data to the existing URI
+ memcpy(&dfu.storage[idx].uri_data[dfu.storage[idx].uri_len], data, len);
+ // Increment URI length
+ dfu.storage[idx].uri_len += len;
+ break;
+ default:
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+
+ return SL_STATUS_OK;
+}
diff --git a/app/btmesh/common/ncp_btmesh_dfu/ncp_btmesh_dfu.h b/app/btmesh/common/ncp_btmesh_dfu/ncp_btmesh_dfu.h
new file mode 100644
index 00000000000..3700997cd62
--- /dev/null
+++ b/app/btmesh/common/ncp_btmesh_dfu/ncp_btmesh_dfu.h
@@ -0,0 +1,74 @@
+/***************************************************************************//**
+ * @file
+ * @brief Definitions for FW Update Server application level functionality
+ *******************************************************************************
+ * # License
+ * Copyright 2020 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef DFU_H
+#define DFU_H
+
+#include "sl_status.h"
+
+// User sub-command to write FW URI
+#define USER_SUB_CMD_URI_ID_WRT '0'
+// User sub-command to append to FW URI
+#define USER_SUB_CMD_URI_ID_APP '1'
+
+/***************************************************************************//**
+ * Sets the Firmware ID for the given index, as part of the FW information
+ *
+ * @param idx Index of the FW
+ * @param len Length of the data
+ * @param data Firmware ID data; containing CID and version information
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_INVALID_PARAMETER If length is less than 2 or greater
+ than 108
+ * @retval SL_STATUS_NO_MORE_RESOURCE In case memory allocation error
+ ******************************************************************************/
+sl_status_t sl_btmesh_ncp_dfu_set_fwid(uint8_t idx, uint8_t len, uint8_t *data);
+
+/***************************************************************************//**
+ * Sets the URI for given index, as part of the FW information
+ *
+ * @param idx Index of the FW
+ * @param type Type of the command; '0' erases and writes new URI,
+ * '1' appends to existing URI.
+ * @param len Length of the data
+ * @param data The data to write or append
+ * @return Status code
+ * @retval SL_STATUS_OK In case of success
+ * @retval SL_STATUS_NO_MORE_RESOURCE In case of memory allocation error
+ * @retval SL_STATUS_INVALID_PARAMETER In case the combined length of the URI
+ * is longer than 255 bytes
+ ******************************************************************************/
+sl_status_t sl_btmesh_ncp_dfu_set_uri(uint8_t idx,
+ uint8_t type,
+ uint8_t len,
+ uint8_t *data);
+
+#endif // DFU_H
diff --git a/app/btmesh/common_host/btmesh_app_prov/btmesh_app_prov.c b/app/btmesh/common_host/btmesh_app_prov/btmesh_app_prov.c
new file mode 100644
index 00000000000..d2b21bea119
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_prov/btmesh_app_prov.c
@@ -0,0 +1,882 @@
+/**************************************************************************//**
+* @file
+* @brief BT Mesh Provisioner Application component - CLI handler
+*******************************************************************************
+* # License
+* Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+*******************************************************************************
+*
+* SPDX-License-Identifier: Zlib
+*
+* The licensor of this software is Silicon Laboratories Inc.
+*
+* This software is provided 'as-is', without any express or implied
+* warranty. In no event will the authors be held liable for any damages
+* arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not
+* claim that you wrote the original software. If you use this software
+* in a product, an acknowledgment in the product documentation would be
+* appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+* misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+*
+******************************************************************************/
+
+// ----------------------------------------------------------------------------
+// Includes
+
+// standard library headers
+#include
+
+// app-related headers
+#include "app.h"
+#include "btmesh_app_prov.h"
+#include "app_log.h"
+#include "app_assert.h"
+#include "sl_simple_timer.h"
+
+#include "sl_common.h"
+#include "btmesh_prov.h"
+#include "btmesh_db.h"
+#include "btmesh_key_export.h"
+
+#include "app_conf.h"
+
+// ----------------------------------------------------------------------------
+// Macros
+
+// ----------------------------------------------------------------------------
+// Static Function Declarations
+
+/**************************************************************************//**
+ * Handle --nodeinfo functionality
+ *
+ *****************************************************************************/
+static void handle_nodeinfo(void);
+
+/**************************************************************************//**
+ * Handle --nodelist functionality
+ *
+ ****************************************************************************/
+static void handle_nodelist(void);
+
+/**************************************************************************//**
+ * Handle --provision functionality
+ *
+ ****************************************************************************/
+static void handle_provision(void);
+
+/**************************************************************************//**
+ * Handle --remove functionality
+ *
+ ****************************************************************************/
+static void handle_remove(void);
+
+/**************************************************************************//**
+ * Handle --key-refresh functionality
+ *
+ ****************************************************************************/
+static void handle_key_refresh(void);
+
+/**************************************************************************//**
+ * Handle --key-export functionality
+ *
+ ****************************************************************************/
+static void handle_key_export(void);
+
+/**************************************************************************//**
+ * Handle --reset functionality
+ *
+ ****************************************************************************/
+static void handle_reset(void);
+
+/**************************************************************************//**
+ * Handle --scan functionality
+ *
+ ****************************************************************************/
+static void handle_scan(void);
+
+/**************************************************************************//**
+ * Exit in command line mode, call btmesh_app_prov_end_of_cmd() in UI mode
+ *
+ ****************************************************************************/
+static void end_command(void);
+
+/**************************************************************************//**
+ * Node removal job status callback
+ *
+ * @param[in] job The job this function is called from
+ ****************************************************************************/
+static void app_on_remove_node_job_status(const btmesh_conf_job_t *job);
+
+/**************************************************************************//**
+ * Callback for the timer used during scanning
+ *
+ * @param[in] timer Pointer to the timer used
+ * @param[in] data Data from the timer
+ ****************************************************************************/
+static void app_on_scan_timer(sl_simple_timer_t *timer, void *data);
+
+/**************************************************************************//**
+ * Callback for the timer used during provisioner reset
+ *
+ * @param[in] timer Pointer to the timer used
+ * @param[in] data Data from the timer
+ ****************************************************************************/
+static void app_on_reset_timer(sl_simple_timer_t *timer, void *data);
+
+// ----------------------------------------------------------------------------
+// Static Variables
+
+static prov_mode_t app_prov_mode = PROV_CMD_LINE_MODE;
+/// Flag stating that the btmeshprov_initialized event has arrived
+static bool initialized = false;
+/// Number of networks already present on the provisioner at startup
+static uint16_t networks_on_startup = 0;
+/// UUID passed as an argument
+static uuid_128 command_uuid;
+/// The state of the current command
+static command_state_t command_state = INIT;
+/// The command in use
+static command_t command = NONE;
+/// Flag indicating that a provisioning session has started
+static bool prov_started = false;
+/// Number of known devices in the DDB list
+static uint16_t ddb_count = 0;
+/// Flag for network database status
+static bool db_ready = false;
+static uint32_t phase_timeout_s = DEFAULT_PHASE_TIMEOUT_S;
+/// Timer for scanning for unprovisioned devices
+static sl_simple_timer_t scan_timer;
+/// Timer for provisioner node reset
+static sl_simple_timer_t reset_timer;
+/// Command line options
+static struct option prov_long_options[PROV_OPTLENGTH] = {
+ { "nodeinfo", required_argument, 0, 'i' },
+ { "nodelist", no_argument, 0, 'l' },
+ { "provision", required_argument, 0, 'p' },
+ { "remove", required_argument, 0, 'r' },
+ { "key-refresh", required_argument, 0, 'k' },
+ { "key-export", required_argument, 0, 'x' },
+ { "reset", no_argument, 0, 'e' },
+ { "scan", no_argument, 0, 's' }
+};
+
+// ----------------------------------------------------------------------------
+// Function definitions
+
+sl_status_t btmesh_app_prov_init(int opt, char *optarg)
+{
+ sl_status_t sc = SL_STATUS_OK;
+
+ switch (opt) {
+ case 'p':
+ // provision selected node
+ command = PROVISION;
+ command_state = START;
+ char *pos = optarg;
+ size_t len = strlen(optarg);
+ // Parse input for UUID
+ app_parse_uuid(pos, len, &command_uuid);
+ break;
+ case 'l':
+ // List all nodes in network
+ app_log_info("Nodelist" APP_LOG_NEW_LINE);
+ command = NODELIST;
+ command_state = START;
+ break;
+ case 'i': {
+ // Info about selected node
+ app_log_info("Nodeinfo: %s" APP_LOG_NEW_LINE, optarg);
+ char *pos = optarg;
+ size_t len = strlen(optarg);
+ // Parse input for UUID
+ app_parse_uuid(pos, len, &command_uuid);
+ command = NODEINFO;
+ command_state = START;
+ break;
+ }
+ case 'r': {
+ // Remove node from network
+ app_log_info("Remove: %s" APP_LOG_NEW_LINE, optarg);
+ char *pos = optarg;
+ size_t len = strlen(optarg);
+ // Parse input for UUID
+ app_parse_uuid(pos, len, &command_uuid);
+ command = REMOVE_NODE;
+ command_state = START;
+ break;
+ }
+ case 'k': {
+ // Refresh network keys
+ app_log_info("Key refresh with phase timeout: %s" APP_LOG_NEW_LINE, optarg);
+ long long timeout = strtoll(optarg, NULL, 0);
+ app_assert(timeout > 0ll && timeout < (96 * 3600), "Invalid argument");
+ phase_timeout_s = timeout;
+ command = KEY_REFRESH;
+ command_state = START;
+ break;
+ }
+ case 'x': {
+ // Export keys
+ app_log_info("Export keys to: %s" APP_LOG_NEW_LINE, optarg);
+ btmesh_key_export_set_file_name(optarg);
+ command = KEY_EXPORT;
+ command_state = START;
+ break;
+ }
+ case 's':
+ // Scan for unprovisioned nodes
+ app_log_info("Scan" APP_LOG_NEW_LINE);
+ command = SCAN;
+ command_state = START;
+ break;
+ case 'e':
+ // Factory reset
+ app_log_info("Factory reset" APP_LOG_NEW_LINE);
+ command = RESET;
+ command_state = START;
+ break;
+ // Process options for other modules.
+ default:
+ sc = SL_STATUS_NOT_FOUND;
+ break;
+ }
+ return sc;
+}
+
+void btmesh_app_prov_process_action(void)
+{
+ if (initialized == true) {
+ switch (command) {
+ case SCAN:
+ handle_scan();
+ break;
+ case PROVISION:
+ handle_provision();
+ break;
+ case NODELIST:
+ handle_nodelist();
+ break;
+ case NODEINFO:
+ handle_nodeinfo();
+ break;
+ case REMOVE_NODE:
+ handle_remove();
+ break;
+ case KEY_REFRESH:
+ handle_key_refresh();
+ break;
+ case KEY_EXPORT:
+ handle_key_export();
+ break;
+ case RESET:
+ handle_reset();
+ break;
+ default:
+ break;
+ }
+ if (app_prov_mode == PROV_UI_MODE) {
+ btmesh_app_prov_handle_ui();
+ }
+ }
+}
+
+void btmesh_app_prov_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_prov_initialized_id: {
+ // Device successfully initialized in provisioner mode
+ sl_btmesh_evt_prov_initialized_t *initialized_evt;
+ initialized_evt = (sl_btmesh_evt_prov_initialized_t *)&(evt->data);
+
+ initialized = true;
+ networks_on_startup = initialized_evt->networks;
+
+ if (INIT == command_state) {
+ command_state = START;
+ }
+ break;
+ }
+
+ case sl_btmesh_evt_prov_ddb_list_id: {
+ // DDB List event
+ if (NODELIST == command) {
+ // If nodelist is requested, print the information
+ // This event can be fired from elsewhere, no logs needed in that case
+ sl_btmesh_evt_prov_ddb_list_t *ddb_list_evt;
+ ddb_list_evt = (sl_btmesh_evt_prov_ddb_list_t *)&(evt->data);
+ uuid_128 uuid = ddb_list_evt->uuid;
+ uint16_t address = ddb_list_evt->address;
+ uint8_t elements = ddb_list_evt->elements;
+ if (BTMESH_APP_PROV_NCP_ADDRESS != address) {
+ app_log_nl();
+ app_log_info("Address: 0x%04x" APP_LOG_NEW_LINE, address);
+ app_log_info("Element count: %d" APP_LOG_NEW_LINE, elements);
+ app_log_info("UUID: ");
+ btmesh_app_prov_append_uuid(&uuid);
+ }
+ }
+ break;
+ }
+ case sl_btmesh_evt_prov_key_refresh_node_update_id: {
+ sl_btmesh_evt_prov_key_refresh_node_update_t *evt_data;
+ evt_data = &(evt->data.evt_prov_key_refresh_node_update);
+ app_log_info(" Phase %d %s: ", evt_data->phase, evt_data->failure ? "failed" : "succeed");
+ btmesh_app_prov_append_uuid(&evt_data->uuid);
+ break;
+ }
+ case sl_btmesh_evt_prov_key_refresh_phase_update_id: {
+ uint8_t phase = evt->data.evt_prov_key_refresh_phase_update.phase;
+ app_log_info("Key refresh phase %d" APP_LOG_NEW_LINE, phase);
+ break;
+ }
+ case sl_btmesh_evt_prov_key_refresh_complete_id: {
+ uint16_t result = evt->data.evt_prov_key_refresh_complete.result;
+ if (result == SL_STATUS_OK) {
+ app_log_info("Key refresh succeed" APP_LOG_NEW_LINE);
+ } else {
+ app_log_error("Key refresh failed with error code 0x%04x." APP_LOG_NEW_LINE, result);
+ }
+ command_state = FINISHED;
+ break;
+ }
+ case sl_btmesh_evt_node_reset_id:
+ // Node reset successful
+ app_log_info("Reset system" APP_LOG_NEW_LINE);
+ sl_bt_system_reset(0);
+ break;
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Add additional event handlers here as your application requires! //
+ ///////////////////////////////////////////////////////////////////////////
+
+ // -------------------------------
+ // Default event handler.
+ default:
+ break;
+ }
+ // Let other modules handle their events too
+ btmesh_key_export_on_event(evt);
+ btmesh_prov_on_event(evt);
+ btmesh_conf_on_event(evt);
+}
+
+// ----------------------------------------------------------------------------
+// App logic functions
+
+void handle_scan(void)
+{
+ switch (command_state) {
+ case START: {
+ sl_status_t sc;
+ sc = btmesh_prov_start_scanning();
+ if (SL_STATUS_OK != sc) {
+ app_log_warning("Failed to start scanning" APP_LOG_NEW_LINE);
+ } else {
+ app_log_info("Scanning started" APP_LOG_NEW_LINE);
+ }
+ command_state = IN_PROGRESS;
+ // Let the provisioner scan unprovisioned nodes
+ sl_simple_timer_start(&scan_timer, // Timer pointer
+ SCAN_TIMER_MS, // Timer duration
+ app_on_scan_timer, // Timer callback
+ NULL, // Timer data, not needed
+ false); // Not periodic
+ break;
+ }
+ case IN_PROGRESS:
+ // Do nothing, timer callback will trigger next state
+ break;
+ case FINISHED: {
+ sl_status_t sc;
+ sc = btmesh_prov_stop_scanning();
+ if (SL_STATUS_OK != sc) {
+ app_log_warning("Failed to stop scanning" APP_LOG_NEW_LINE);
+ } else {
+ app_log_nl();
+ sc = btmesh_prov_list_unprovisioned_nodes();
+ if (SL_STATUS_EMPTY == sc) {
+ app_log_warning("No unprovisioned beaconing nodes were found" APP_LOG_NEW_LINE);
+ }
+ app_log_info("Scanning stopped" APP_LOG_NEW_LINE);
+ }
+ end_command();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void handle_provision(void)
+{
+ static bd_addr mac = { 0 };
+ static uint16_t netkey_index = APP_NETKEY_IDX;
+
+ switch (command_state) {
+ case START: {
+ bool param_ready = true;
+ if (app_prov_mode == PROV_UI_MODE) {
+ param_ready = btmesh_app_prov_get_uuid_from_unprov_list(&command_uuid);
+ }
+ if (true == param_ready) {
+ if (app_prov_mode == PROV_UI_MODE) {
+ btmesh_db_node_t* node = btmesh_db_node_get_by_uuid(command_uuid);
+ if (node != NULL) {
+ bool cbp_capability = node->prov.oob_capabilities & BTMESH_OOB_FLAG_CBP_CAPABLE;
+ btmesh_app_prov_set_cbp_capability(cbp_capability);
+ }
+ }
+ app_log_info("Starting provisioning session" APP_LOG_NEW_LINE);
+ app_log_nl();
+ // Try to create a network with index 0.
+ // If one is already present, SL_STATUS_BT_MESH_ALREADY_EXISTS is handled
+ sl_status_t sc = btmesh_prov_create_network(netkey_index, 0, 0);
+ app_assert((sc == SL_STATUS_OK || sc == SL_STATUS_BT_MESH_ALREADY_EXISTS),
+ "Failed to create network" APP_LOG_NEW_LINE);
+ if (SL_STATUS_OK == sc) {
+ networks_on_startup++;
+ db_ready = true;
+ uint8_t appkey_data[16];
+ size_t appkey_length;
+ // If a new network is created then application key is created as
+ // well because the appkeys are bound to network keys.
+ // If the network already exists then it is not necessary to create
+ // appkey because it has already been created.
+ // Note: Output buffer is mandatory for create appkey API function
+ sl_status_t sc = btmesh_prov_create_appkey(netkey_index,
+ APP_CONF_APPKEY_INDEX,
+ 0,
+ NULL,
+ sizeof(appkey_data),
+ &appkey_length,
+ &appkey_data[0]);
+ app_assert_status_f(sc, "Failed to create appkey" APP_LOG_NEW_LINE);
+ }
+
+ sc = btmesh_prov_setup_provisioning(netkey_index, command_uuid, HOST_PROV_PB_ADV, 0);
+ app_assert_status_f(sc, "Provisioning failed" APP_LOG_NEW_LINE);
+ command_state = IN_PROGRESS;
+ }
+ break;
+ }
+ case IN_PROGRESS:
+ // Handle CBP if present, otherwise finish provisioning
+ // MAC address is unknown here, but the database requires a bd_addr struct
+ // so we use a 0 here.
+ // Note: this won't affect provisioning as only UUID is used there
+ btmesh_app_prov_handle_cbp(netkey_index, command_uuid, mac, HOST_PROV_PB_ADV);
+ break;
+ case FINISHED:
+ prov_started = false;
+ app_log_filter_threshold_set(APP_LOG_LEVEL_INFO);
+ app_log_nl();
+ app_log_info("Provisioning session finished" APP_LOG_NEW_LINE);
+ end_command();
+ break;
+ default:
+ break;
+ }
+}
+
+void handle_nodelist(void)
+{
+ switch (command_state) {
+ case START: {
+ // Check if any networks are present on the node on startup
+ if (0 < networks_on_startup) {
+ sl_status_t sc;
+ app_log_info("Querying DDB list" APP_LOG_NEW_LINE);
+ sc = btmesh_prov_list_ddb_entries(&ddb_count);
+ if (SL_STATUS_OK != sc) {
+ app_log_error("Failed to list DDB entries" APP_LOG_NEW_LINE);
+ command_state = FINISHED;
+ break;
+ }
+ if (ddb_count == 0) {
+ // The count is synchronous, but individual nodes' info are sent
+ // via sl_btmesh_on_event
+ app_log_info("No nodes present in the network" APP_LOG_NEW_LINE);
+ command_state = FINISHED;
+ } else {
+ command_state = IN_PROGRESS;
+ }
+ } else {
+ app_log_info("No networks present on the device" APP_LOG_NEW_LINE);
+ command_state = FINISHED;
+ }
+ break;
+ }
+ case IN_PROGRESS:
+ // Wait for all nodes' information
+ break;
+ case FINISHED:
+ end_command();
+ break;
+ default:
+ break;
+ }
+}
+
+void handle_nodeinfo(void)
+{
+ switch (command_state) {
+ case START: {
+ bool param_ready = true;
+ if (app_prov_mode == PROV_UI_MODE) {
+ param_ready = btmesh_app_prov_get_uuid_from_prov_list(&command_uuid);
+ }
+ if ((true == param_ready) && (true == db_ready)) {
+ // Node information can only be obtained after the database is ready
+ app_log_info("Querying node information" APP_LOG_NEW_LINE);
+ app_log_nl();
+ if (app_prov_mode == PROV_UI_MODE) {
+ app_log_filter_threshold_set(APP_LOG_LEVEL_WARNING);
+ }
+ command_state = IN_PROGRESS;
+ app_conf_print_nodeinfo_by_uuid(command_uuid);
+ }
+ break;
+ }
+ case IN_PROGRESS:
+ // Wait for configurator to finish nodeinfo printing
+ break;
+ case FINISHED:
+ app_log_filter_threshold_set(APP_LOG_LEVEL_INFO);
+ end_command();
+ break;
+ default:
+ break;
+ }
+}
+
+void handle_remove(void)
+{
+ switch (command_state) {
+ case START: {
+ sl_status_t sc;
+ bool param_ready = true;
+ if (app_prov_mode == PROV_UI_MODE) {
+ param_ready = btmesh_app_prov_get_uuid_from_prov_list(&command_uuid);
+ }
+ if ((true == param_ready) && (true == db_ready)) {
+ app_log_info("Unprovisioning..." APP_LOG_NEW_LINE);
+ sc = btmesh_prov_remove_node_by_uuid(command_uuid,
+ app_on_remove_node_job_status);
+ if (SL_STATUS_OK != sc) {
+ command_state = FINISHED;
+ } else {
+ command_state = IN_PROGRESS;
+ }
+ // DDB info is queried on startup
+ command_state = IN_PROGRESS;
+ }
+ break;
+ }
+ case IN_PROGRESS:
+ // Wait for confirmation callback
+ break;
+ case FINISHED:
+ app_log_info("Refresh keys to prevent trashcan attacks." APP_LOG_NEW_LINE);
+ end_command();
+ break;
+ default:
+ break;
+ }
+}
+
+void handle_key_refresh(void)
+{
+ switch (command_state) {
+ case START: {
+ sl_status_t sc;
+ const uint8_t app_keys[] = { 0, 0 };
+ sc = sl_btmesh_prov_phase_timeout_set(phase_timeout_s);
+ app_log_status_f(sc, "Failed to set phase timeout" APP_LOG_NEW_LINE);
+ sc = sl_btmesh_prov_start_key_refresh(0, 1, sizeof(app_keys), app_keys);
+ app_assert_status_f(sc, "Key refresh failed" APP_LOG_NEW_LINE);
+ app_log_info("Key refresh started" APP_LOG_NEW_LINE);
+ command_state = IN_PROGRESS;
+ break;
+ }
+ case IN_PROGRESS:
+ // Wait for prov_key_refresh_complete event
+ break;
+ case FINISHED:
+ end_command();
+ break;
+ default:
+ break;
+ }
+}
+void handle_key_export(void)
+{
+ switch (command_state) {
+ case START:
+ btmesh_key_export_start();
+ command_state = IN_PROGRESS;
+ break;
+ case IN_PROGRESS:
+ btmesh_key_export_task();
+ if (btmesh_key_export_is_idle()) {
+ command_state = FINISHED;
+ }
+ break;
+ case FINISHED:
+ end_command();
+ break;
+ default:
+ break;
+ }
+}
+
+void handle_reset(void)
+{
+ switch (command_state) {
+ case START:
+ app_log_info("Initiating node reset" APP_LOG_NEW_LINE);
+ sl_btmesh_node_reset();
+ // Timer to let the NVM clear properly
+ sl_simple_timer_start(&reset_timer, // Timer pointer
+ RESET_TIMER_MS, // Timer duration
+ app_on_reset_timer, // Timer callback
+ NULL, // Timer data, not needed
+ false); // Not periodic
+ command_state = IN_PROGRESS;
+ break;
+ case IN_PROGRESS:
+ // Do nothing, timer callback will trigger next state
+ break;
+ case FINISHED: {
+ btmesh_db_network_t *db_network = btmesh_db_network_get_network((uint16_t)APP_NETKEY_IDX);
+ if (NULL != db_network) {
+ btmesh_db_remove_network(db_network);
+ networks_on_startup--;
+ }
+ app_log_info("Resetting hardware" APP_LOG_NEW_LINE);
+ sl_bt_system_reset(sl_bt_system_boot_mode_normal);
+ exit(EXIT_SUCCESS);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void end_command(void)
+{
+ if (app_prov_mode == PROV_UI_MODE) {
+ btmesh_app_prov_end_of_cmd();
+ } else {
+ exit(EXIT_SUCCESS);
+ }
+}
+
+bool btmesh_app_prov_get_ddb_status(void)
+{
+ return db_ready;
+}
+
+void btmesh_app_prov_get_command(command_t *prov_command, command_state_t *prov_command_state)
+{
+ if (prov_command != NULL) {
+ *prov_command = command;
+ }
+ if (prov_command_state != NULL) {
+ *prov_command_state = command_state;
+ }
+}
+
+void btmesh_app_prov_set_command(command_t prov_command)
+{
+ command = prov_command;
+}
+
+void btmesh_app_prov_set_command_state(command_state_t prov_command_state)
+{
+ command_state = prov_command_state;
+}
+
+void btmesh_app_prov_get_cmd_options(struct option **prov_options)
+{
+ *prov_options = prov_long_options;
+}
+
+void btmesh_app_prov_set_mode(prov_mode_t prov_mode)
+{
+ app_prov_mode = prov_mode;
+
+ app_log_debug("Prov mode: %s" APP_LOG_NEW_LINE, app_prov_mode == PROV_UI_MODE ? "PROV_UI_MODE" : "PROV_CMD_LINE_MODE");
+}
+
+prov_mode_t btmesh_app_prov_get_mode(void)
+{
+ return app_prov_mode;
+}
+
+uint16_t btmesh_app_prov_get_networks_number(void)
+{
+ return networks_on_startup;
+}
+// ----------------------------------------------------------------------------
+// Callbacks
+
+void btmesh_app_prov_on_nodeinfo_end(void)
+{
+ command_state = FINISHED;
+}
+void btmesh_prov_on_unprovisioned_node_list_evt(uint16_t id,
+ uuid_128 uuid,
+ uint16_t oob_capabilities)
+{
+ app_log_info("Unprovisioned node" APP_LOG_NEW_LINE);
+ app_log_info("ID: %d" APP_LOG_NEW_LINE, id);
+ app_log_info("UUID: ");
+ btmesh_app_prov_append_uuid(&uuid);
+ app_log_info("OOB Capabilities: 0x%04x" APP_LOG_NEW_LINE, oob_capabilities);
+ app_log_nl();
+}
+
+void btmesh_prov_on_provisioned_node_list_evt(uint16_t id,
+ uuid_128 uuid,
+ uint16_t primary_address)
+{
+ app_log_info("Provisioned node" APP_LOG_NEW_LINE);
+ app_log_info("ID: %d" APP_LOG_NEW_LINE, id);
+
+ app_log_info("UUID: ");
+ btmesh_app_prov_append_uuid(&uuid);
+
+ app_log_info("Address: 0x%04x" APP_LOG_NEW_LINE, primary_address);
+ app_log_nl();
+}
+
+void btmesh_app_prov_append_uuid(uuid_128 *uuid)
+{
+ app_log_append_info("%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x" APP_LOG_NEW_LINE,
+ uuid->data[0],
+ uuid->data[1],
+ uuid->data[2],
+ uuid->data[3],
+ uuid->data[4],
+ uuid->data[5],
+ uuid->data[6],
+ uuid->data[7],
+ uuid->data[8],
+ uuid->data[9],
+ uuid->data[10],
+ uuid->data[11],
+ uuid->data[12],
+ uuid->data[13],
+ uuid->data[14],
+ uuid->data[15]);
+}
+
+void btmesh_prov_on_device_provisioned_evt(uint16_t address, uuid_128 uuid)
+{
+ app_log_info("Device provisioned" APP_LOG_NEW_LINE);
+ app_log_info("UUID: ");
+ btmesh_app_prov_append_uuid(&uuid);
+ app_log_info("Address: 0x%04x" APP_LOG_NEW_LINE, address);
+ app_log_nl();
+
+ if (PROV_UI_MODE == app_prov_mode) {
+ app_log_filter_threshold_set(APP_LOG_LEVEL_WARNING);
+ }
+
+ sl_status_t sc = app_conf_start_node_configuration(APP_NETKEY_IDX,
+ address);
+ if (SL_STATUS_OK != sc) {
+ app_log_status_error_f(sc, "Failed to start configuration procedure." APP_LOG_NEW_LINE);
+ command_state = FINISHED;
+ }
+}
+
+void btmesh_prov_on_ddb_list_ready(uint16_t count)
+{
+ if (0 == count) {
+ app_log_debug("No nodes in DDB" APP_LOG_NEW_LINE);
+ } else {
+ if (NODELIST == command) {
+ // DDB list requested by user
+ app_log_debug("All nodes in DDB listed" APP_LOG_NEW_LINE);
+ // Finish only if not requested by UI mode
+ command_state = FINISHED;
+ }
+ }
+ // Some commands can only start after the database is ready
+ db_ready = true;
+}
+
+void app_on_remove_node_job_status(const btmesh_conf_job_t *job)
+{
+ btmesh_prov_delete_ddb_entry(command_uuid);
+ if (BTMESH_CONF_JOB_RESULT_SUCCESS == job->result) {
+ app_log_info("Node removed from network" APP_LOG_NEW_LINE);
+ } else {
+ app_log_error("Remove node from network failed, removed from DDB anyway" APP_LOG_NEW_LINE);
+ }
+ command_state = FINISHED;
+}
+
+SL_WEAK void btmesh_app_prov_end_of_cmd(void)
+{
+ exit(EXIT_SUCCESS);
+}
+
+SL_WEAK void btmesh_app_prov_handle_ui(void)
+{
+}
+
+SL_WEAK bool btmesh_app_prov_get_uuid_from_unprov_list(uuid_128 *command_uuid)
+{
+ (void)command_uuid;
+
+ return true;
+}
+
+SL_WEAK bool btmesh_app_prov_get_uuid_from_prov_list(uuid_128 *command_uuid)
+{
+ (void)command_uuid;
+
+ return true;
+}
+
+SL_WEAK void btmesh_app_prov_handle_cbp(uint16_t netkey_index,
+ uuid_128 uuid,
+ bd_addr mac_address,
+ uint8_t bearer_type)
+{
+ // If CBP is not present, provisioning of the selected device must be started
+ // at this point
+ if (prov_started == false) {
+ sl_status_t sc = btmesh_prov_provision_adv_device(netkey_index, uuid, mac_address, bearer_type, 0);
+ app_assert_status_f(sc, "Provisioning failed" APP_LOG_NEW_LINE);
+ prov_started = true;
+ }
+}
+
+SL_WEAK void btmesh_app_prov_set_cbp_capability(bool capability)
+{
+ (void)capability;
+}
+
+// ----------------------------------------------------------------------------
+// Private function definitions
+
+static void app_on_scan_timer(sl_simple_timer_t *timer, void *data)
+{
+ command_state = FINISHED;
+}
+
+static void app_on_reset_timer(sl_simple_timer_t *timer, void *data)
+{
+ command_state = FINISHED;
+}
diff --git a/app/btmesh/common_host/btmesh_app_prov/btmesh_app_prov.h b/app/btmesh/common_host/btmesh_app_prov/btmesh_app_prov.h
new file mode 100644
index 00000000000..5a4e0d80f6b
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_prov/btmesh_app_prov.h
@@ -0,0 +1,258 @@
+/**************************************************************************//**
+ * @file
+ * @brief BT Mesh Provisioner Application component - CLI handler
+ ******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ ******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ *****************************************************************************/
+
+#ifndef BTMESH_APP_PROV_H
+#define BTMESH_APP_PROV_H
+
+#include
+#include
+#include "sl_status.h"
+#include "sl_btmesh_api.h"
+#include "app.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// Optstring argument for getopt
+#define PROV_OPTLENGTH 8
+
+/// NCP node address
+#define BTMESH_APP_PROV_NCP_ADDRESS 0x2001
+
+/// Array terminate element length
+#define LAST_OPTION_ELEMENT 1
+
+/// Optstring argument for getopt
+#define PROV_OPTSTRING "ei:lp:k:x:r:s"
+
+/// Usage info
+#define PROV_USAGE \
+ " [--nodeinfo ]" \
+ " [--nodelist]" \
+ " [--provision ]" \
+ " [--remove ]" \
+ " [--key-refresh ]" \
+ " [--key-export ]" \
+ " [--reset]" \
+ " [--scan]"
+
+// Options info
+#define PROV_OPTIONS \
+ "\nHost Provisioner-related options:\n" \
+ " -i --nodeinfo Print DCD information about a node in the network\n" \
+ " The unique identifier of the node.\n" \
+ " -l --nodelist List all nodes present in the provisioner's device " \
+ "database (DDB)\n" \
+ " -p --provision Provision a node\n" \
+ " The UUID of the node to be provisioned. " \
+ "Can be acquired by --scan.\n" \
+ " -r --remove Remove the given node from the Mesh network\n" \
+ " The UUID of the node to be removed\n" \
+ " -k --key-refresh Refresh the network key and app key\n" \
+ " Phase timeout in seconds\n" \
+ " -x --key-export Export the network, app, and device keys in JSON\n" \
+ " Output file name" \
+ " -e --reset Factory reset the provisioner. Note: This command does " \
+ "not remove existing devices from the network.\n" \
+ " -s --scan Scan and list unprovisioned beaconing nodes\n" \
+ "\nUUID shall be a string containing 16 separate octets, e.g. " \
+ "\"00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff\"\n" \
+ "The separator can be any character, but in case of a whitespace character " \
+ "this example requires quotation marks around the string.\n\n"
+
+// ----------------------------------------------------------------------------
+// Enums
+
+/// Current provisioner mode, either single run command line mode or ui, menu mode
+typedef enum {
+ PROV_CMD_LINE_MODE,
+ PROV_UI_MODE
+}prov_mode_t;
+
+/**************************************************************************//**
+ * Provisioner Command Line Application Init
+ *
+ * @param[in] opt Command option
+ * @param[in] optarg Command argument
+ * @retval SL_STATUS_OK if command started successfully
+ * SL_STATUS_NOT_FOUND if command is unknown
+ *****************************************************************************/
+sl_status_t btmesh_app_prov_init(int opt, char *optarg);
+
+/**************************************************************************//**
+ * Provisioner Application Process Action
+ *
+ *****************************************************************************/
+void btmesh_app_prov_process_action(void);
+
+/**************************************************************************//**
+ * Bluetooth Mesh stack event handler
+ *
+ * @param[in] evt Event coming from the Bluetooth Mesh stack
+ *****************************************************************************/
+void btmesh_app_prov_on_event(sl_btmesh_msg_t *evt);
+
+/**************************************************************************//**
+ * Get the provisioner command and command status
+ *
+ * @param[out] prov_command Current command
+ * @param[out] prov_command_state Current command status
+ *****************************************************************************/
+void btmesh_app_prov_get_command(command_t *prov_command, command_state_t *prov_command_state);
+
+/**************************************************************************//**
+ * Set the provisioner command
+ *
+ * @param[in] prov_command New command
+ *****************************************************************************/
+void btmesh_app_prov_set_command(command_t prov_command);
+
+/**************************************************************************//**
+ * Set the provisioner command status
+ *
+ * @param[out] prov_command_state New command status
+ *****************************************************************************/
+void btmesh_app_prov_set_command_state(command_state_t prov_command_state);
+
+/**************************************************************************//**
+ * Get the command options struct array address
+ *
+ * @param[out] prov_options Options struct array address
+ *****************************************************************************/
+void btmesh_app_prov_get_cmd_options(struct option **prov_options);
+
+/**************************************************************************//**
+ * Get the command options struct array address for CBP
+ *
+ * @param[out] prov_options Options struct array address
+ *****************************************************************************/
+void btmesh_cbp_prov_get_cmd_options(struct option **prov_options);
+
+/**************************************************************************//**
+ * Handle the main menu in UI mode
+ *
+ *****************************************************************************/
+void btmesh_app_prov_handle_ui(void);
+
+/**************************************************************************//**
+ * Get UUID from UI for provisioning
+ *
+ * @param[out] command_uuid The variable to fill with the parsed data
+ * @retval false the returned uuid is not valid yet
+ * true if valid uuid was returned
+ *****************************************************************************/
+bool btmesh_app_prov_get_uuid_from_unprov_list(uuid_128 *command_uuid);
+
+/**************************************************************************//**
+ * Get UUID from UI from rovisioned node list
+ *
+ * @param[out] command_uuid The variable to fill with UUID
+ * @retval false the returned uuid is not valid yet
+ * true if valid uuid was returned
+ *****************************************************************************/
+bool btmesh_app_prov_get_uuid_from_prov_list(uuid_128 *command_uuid);
+
+/**************************************************************************//**
+ * Get ddb status after startup
+ *
+ * @retval ddb status
+ * false if ddb is not ready yet after startup
+ * true if ddb is ready after startup
+ *****************************************************************************/
+bool btmesh_app_prov_get_ddb_status(void);
+
+/**************************************************************************//**
+ * Set provisioner mode, command line or ui mode
+ *
+ * @param[in] prov_mode New provisioner mode
+ *****************************************************************************/
+void btmesh_app_prov_set_mode(prov_mode_t prov_mode);
+
+/**************************************************************************//**
+ * Get provisioner mode command line or menu UI mode
+ *
+ * @retval PROV_CMD_LINE_MODE command line mode
+ * PROV_UI_MODE menu UI mode mode
+ *****************************************************************************/
+prov_mode_t btmesh_app_prov_get_mode(void);
+
+/**************************************************************************//**
+ * Callback to inform when the nodeinfo query ends
+ *
+ *****************************************************************************/
+void btmesh_app_prov_on_nodeinfo_end(void);
+
+/**************************************************************************//**
+ * Callback to inform when the command ends
+ *
+ *****************************************************************************/
+void btmesh_app_prov_end_of_cmd(void);
+
+/**************************************************************************//**
+ * Helper script to print a given UUID
+ *
+ * @param[in] uuid Pointer to the UUID
+ *****************************************************************************/
+void btmesh_app_prov_append_uuid(uuid_128 *uuid);
+
+/**************************************************************************//**
+ * Get ddb status after startup
+ *
+ * @retval networks number
+ *****************************************************************************/
+uint16_t btmesh_app_prov_get_networks_number(void);
+
+/***************************************************************************//**
+* Handle certificate-based provisioning
+*
+* @param[in] netkey_index Netkey index of the network
+* @param[in] uuid UUID of the device to be provisioned
+* @param[in] mac_address MAC address of the device to be provisioned
+* @param[in] bearer_type Type of the provisioning bearer layer. Can be
+* PB-ADV (0) or PB-GATT (1).
+*******************************************************************************/
+void btmesh_app_prov_handle_cbp(uint16_t netkey_index,
+ uuid_128 uuid,
+ bd_addr mac_address,
+ uint8_t bearer_type);
+
+/***************************************************************************//**
+* Inform the CBP handler if the provisioned node is capable of CBP or not
+*
+* @param[in] capability true if the node is CBP-capable, false otherwise
+*******************************************************************************/
+void btmesh_app_prov_set_cbp_capability(bool capability);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // BTMESH_APP_PROV_H
diff --git a/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_certificate_validation.c b/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_certificate_validation.c
new file mode 100644
index 00000000000..15bcd89d006
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_certificate_validation.c
@@ -0,0 +1,381 @@
+/***************************************************************************//**
+* @file
+* @brief BT Mesh Host Provisioner component - Certificate Validation
+********************************************************************************
+* # License
+* Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+********************************************************************************
+*
+* SPDX-License-Identifier: Zlib
+*
+* The licensor of this software is Silicon Laboratories Inc.
+*
+* This software is provided 'as-is', without any express or implied
+* warranty. In no event will the authors be held liable for any damages
+* arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not
+* claim that you wrote the original software. If you use this software
+* in a product, an acknowledgment in the product documentation would be
+* appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+* misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+*
+*******************************************************************************/
+
+// -----------------------------------------------------------------------------
+// Includes
+
+// standard library headers
+#include
+
+// OpenSSL X.509 header
+#include
+#include
+#include
+#include
+#include
+#include
+
+// app-related headers
+#include "btmesh_app_certificate_validation.h"
+#include "app_log.h"
+
+// -----------------------------------------------------------------------------
+// Macros
+
+/// Length of UUID in the canonical fields-and-hyphens format
+#define UUID_CANONICAL_STRING_LEN 36
+
+/// Version field of the certificate, decimal 2 = v3
+#define CBP_CERTIFICATE_VERSION 2
+
+// -----------------------------------------------------------------------------
+// Static Function Declarations
+
+/***************************************************************************//**
+* Extract the public key from an X509 certificate
+*
+* @param[in] cert An X.509 certificate
+* @param[in] key_size public key size
+* @param[out] pub_key Pointer to the place to extract the public key
+* @return Status of the key extraction
+* @retval SL_STATUS_OK if the key is successfully extracted
+* Error code otherwise
+*******************************************************************************/
+static sl_status_t btmesh_app_prov_cbp_extract_public_key(X509 *cert, size_t key_size, uint8_t* key);
+/***************************************************************************//**
+* Get the common name string from an X509 certificate
+*
+* @param[in] cert An X.509 certificate
+* @return Pointer to a null-terminated char string
+* containing the common name field
+*******************************************************************************/
+static const unsigned char* btmesh_app_prov_cbp_get_x509_common_name(X509 *cert);
+
+/***************************************************************************//**
+* Check if the UUID in the X.509 certificate matches the UUID of the device
+* currently provisioned
+*
+* @param[in] cert The certificate
+* @param[in] uuid UUID of the device being provisioned
+* @return Status of the validation
+* @retval SL_STATUS_OK if the two match
+* Error code otherwise
+*******************************************************************************/
+static sl_status_t btmesh_app_prov_cbp_validate_uuid(X509 *cert, uuid_128 *uuid);
+
+/***************************************************************************//**
+* Validate the device certificate with a given root certificate
+*
+* @param[in] cert The device certificate
+* @param[in] root The root certificate
+* @return Status of the validation
+* @retval SL_STATUS_OK if the certificate is valid
+* Error code otherwise
+*******************************************************************************/
+sl_status_t btmesh_app_prov_cbp_validate_certificate_with_root(X509 *cert, X509 *root);
+
+/***************************************************************************//**
+* Validate certificate's expiration times
+*
+* @param[in] cert The device certificate
+* @return Status of the validation
+* @retval SL_STATUS_OK if the certificate is valid
+* Error code otherwise
+*******************************************************************************/
+sl_status_t btmesh_app_prov_cbp_validate_expiration(X509 *cert);
+
+/***************************************************************************//**
+* Validate the data fields of a certificate
+*
+* @param[in] cert The device certificate
+* @return Status of the validation
+* @retval SL_STATUS_OK if the certificate is valid
+* Error code otherwise
+*******************************************************************************/
+sl_status_t btmesh_app_prov_cbp_validate_data_fields(X509 *cert);
+
+/*******************************************************************************
+* Validate the received certificate
+*******************************************************************************/
+sl_status_t btmesh_prov_validate_certificate(
+ const unsigned char *raw_cert,
+ size_t cert_len,
+ const char *root_cert_path,
+ uuid_128 *uuid,
+ size_t key_size,
+ uint8_t *pub_key
+ )
+{
+ sl_status_t sc;
+ // OpenSSL setup
+ OpenSSL_add_all_algorithms();
+
+ // Device certificate
+ X509 *cert = d2i_X509(NULL, &raw_cert, cert_len);
+ if (cert == NULL) {
+ app_log_error("Failed to parse certificate" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+
+ // Root certificate
+ FILE *fp = fopen(root_cert_path, "r");
+ if (fp == NULL) {
+ app_log_error("Failed to open root certificate" APP_LOG_NEW_LINE);
+ X509_free(cert);
+ return SL_STATUS_FAIL;
+ }
+ X509 *root = PEM_read_X509(fp, NULL, NULL, NULL);
+ if (root == NULL) {
+ app_log_error("Failed to parse root certificate" APP_LOG_NEW_LINE);
+ X509_free(cert);
+ fclose(fp);
+ return SL_STATUS_FAIL;
+ }
+ // Root CA file loaded into OpenSSL, handle can now be closed
+ fclose(fp);
+
+ if ( btmesh_app_prov_cbp_validate_data_fields(cert) == SL_STATUS_OK
+ && btmesh_app_prov_cbp_validate_uuid(cert, uuid) == SL_STATUS_OK
+ && btmesh_app_prov_cbp_validate_certificate_with_root(cert, root) == SL_STATUS_OK
+ && btmesh_app_prov_cbp_validate_expiration(cert) == SL_STATUS_OK
+ && btmesh_app_prov_cbp_extract_public_key(cert, key_size, pub_key) == SL_STATUS_OK ) {
+ sc = SL_STATUS_OK;
+ app_log_info("Certificate validated successfully" APP_LOG_NEW_LINE);
+ } else {
+ sc = SL_STATUS_FAIL;
+ }
+
+ X509_free(cert);
+ X509_free(root);
+ return sc;
+}
+
+/*******************************************************************************
+* Extract the raw pubic key from the certificate
+*******************************************************************************/
+sl_status_t btmesh_app_prov_cbp_extract_public_key(X509 *cert, size_t key_size, uint8_t* key)
+{
+ // Extract public key in OpenSSL's format
+ EVP_PKEY *pubkey = X509_get0_pubkey(cert);
+ if (pubkey == NULL) {
+ app_log_error("Failed to get public key" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ // Check if the Key ID matches the required algorithm: id-ecPublicKey
+ int key_type = EVP_PKEY_id(pubkey);
+ if (key_type != NID_X9_62_id_ecPublicKey) {
+ app_log_error("Wrong public key algorithm" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ // Extract EC key in OpenSSL's format
+ EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pubkey);
+ if (ec_key == NULL) {
+ app_log_error("Failed to get EC key" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ // Setup OpenSSL conversion parameters to extract the raw public key
+ const EC_GROUP *group = EC_KEY_get0_group(ec_key);
+ if (group == NULL) {
+ app_log_error("Failed to get EC group" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ point_conversion_form_t form = EC_GROUP_get_point_conversion_form(group);
+
+ // Extract raw public key - the function allocates memory
+ unsigned char *raw_pubkey;
+ size_t key_len = EC_KEY_key2buf(ec_key, form, &raw_pubkey, NULL);
+ if (key_len == 0) {
+ app_log_error("Failed to get EC public key" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ // Copy raw key to the parameter
+ memcpy(key, &raw_pubkey[1], key_size);
+ OPENSSL_free(raw_pubkey);
+ return SL_STATUS_OK;
+}
+
+/*******************************************************************************
+* Get the common name string from an X509 certificate
+*******************************************************************************/
+const unsigned char* btmesh_app_prov_cbp_get_x509_common_name(X509 *cert)
+{
+ X509_NAME *subject_name = X509_get_subject_name(cert);
+ int lastpos = -1;
+ lastpos = X509_NAME_get_index_by_NID(subject_name, NID_commonName, lastpos);
+ X509_NAME_ENTRY *e = X509_NAME_get_entry(subject_name, lastpos);
+ ASN1_STRING *d = X509_NAME_ENTRY_get_data(e);
+ return ASN1_STRING_get0_data(d);
+}
+
+/*******************************************************************************
+* Check if the UUID in the X.509 certificate matches the UUID of the device
+* currently provisioned
+*******************************************************************************/
+sl_status_t btmesh_app_prov_cbp_validate_uuid(X509 *cert, uuid_128 *uuid)
+{
+ const char *common_name = (const char *)btmesh_app_prov_cbp_get_x509_common_name(cert);
+ app_log_debug("Common name: %s" APP_LOG_NEW_LINE, common_name);
+ uuid_128 common_uuid = { 0 };
+ int len = strlen((const char *)common_name);
+ // Parse UUID
+ int s = sscanf(common_name,
+ "%2hhx%2hhx%2hhx%2hhx-%2hhx%2hhx-%2hhx%2hhx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx",
+ &common_uuid.data[0],
+ &common_uuid.data[1],
+ &common_uuid.data[2],
+ &common_uuid.data[3],
+ &common_uuid.data[4],
+ &common_uuid.data[5],
+ &common_uuid.data[6],
+ &common_uuid.data[7],
+ &common_uuid.data[8],
+ &common_uuid.data[9],
+ &common_uuid.data[10],
+ &common_uuid.data[11],
+ &common_uuid.data[12],
+ &common_uuid.data[13],
+ &common_uuid.data[14],
+ &common_uuid.data[15]);
+ // Compare the received string to the device under provision
+ if (len != UUID_CANONICAL_STRING_LEN || s < 16 || memcmp(uuid, &common_uuid, sizeof(uuid_128)) != 0) {
+ app_log_error("UUID of the device and in the certificate does not match" APP_LOG_NEW_LINE);
+ return SL_STATUS_INVALID_PARAMETER;
+ } else {
+ return SL_STATUS_OK;
+ }
+}
+/*******************************************************************************
+* Authenticate device certificate with root certificate
+*******************************************************************************/
+sl_status_t btmesh_app_prov_cbp_validate_certificate_with_root(X509 *cert, X509 *root)
+{
+ X509_STORE *store = X509_STORE_new();
+ if (store == NULL) {
+ app_log_error("Unable to create X.509 store" APP_LOG_NEW_LINE);
+ return SL_STATUS_ALLOCATION_FAILED;
+ }
+ X509_STORE_CTX *store_ctx = X509_STORE_CTX_new();
+ if (store_ctx == NULL) {
+ app_log_error("Unable to create X.509 store context" APP_LOG_NEW_LINE);
+ X509_STORE_free(store);
+ return SL_STATUS_ALLOCATION_FAILED;
+ }
+
+ sl_status_t sc = SL_STATUS_FAIL;
+ if (X509_STORE_add_cert(store, root) != 1) {
+ app_log_error("Unable to add root certificate to store" APP_LOG_NEW_LINE);
+ } else if (X509_STORE_CTX_init(store_ctx, store, cert, NULL) != 1) {
+ app_log_error("Unable to init store context" APP_LOG_NEW_LINE);
+ } else if (X509_verify_cert(store_ctx) != 1) {
+ app_log_error("Unable to verify certificate" APP_LOG_NEW_LINE);
+ } else {
+ sc = SL_STATUS_OK;
+ }
+
+ X509_STORE_CTX_free(store_ctx);
+ X509_STORE_free(store);
+
+ return sc;
+}
+
+/*******************************************************************************
+* Validate certificate's expiration times
+*******************************************************************************/
+sl_status_t btmesh_app_prov_cbp_validate_expiration(X509 *cert)
+{
+ int status = 0;
+ // Start date
+ status = X509_cmp_current_time(X509_get_notBefore(cert));
+ if (status != -1) {
+ // -1 means certificate time <= now
+ app_log_error("Certificate is not yet valid" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ // Expiry date
+ status = X509_cmp_current_time(X509_get_notAfter(cert));
+ if (status != 1) {
+ // 1 means certificate time > now
+ app_log_error("Certificate has expired" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+
+ return SL_STATUS_OK;
+}
+
+/*******************************************************************************
+* Validate the data fields of a certificate
+*******************************************************************************/
+sl_status_t btmesh_app_prov_cbp_validate_data_fields(X509 *cert)
+{
+ // Version field shall be set to v3, decimal value 2
+ long version = X509_get_version(cert);
+ if (version != CBP_CERTIFICATE_VERSION) {
+ app_log_error("Incorrect certificate version" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ // Signature algorithm shall be ecdsa-with-SHA256
+ int signature = X509_get_signature_nid(cert);
+ if (signature != NID_ecdsa_with_SHA256) {
+ app_log_error("Incorrect certificate algorithm" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ // Check not allowed extensions
+ const STACK_OF(X509_EXTENSION) * extensions = X509_get0_extensions(cert);
+ const int not_allowed_extensions[] = {
+ NID_subject_alt_name,
+ NID_issuer_alt_name,
+ NID_name_constraints,
+ NID_policy_constraints,
+ NID_ext_key_usage,
+ NID_inhibit_any_policy,
+ };
+ const int not_allowed_ext_size = sizeof(not_allowed_extensions) / sizeof(int);
+ void *extension_ptr = NULL;
+ // If any of the not allowed extensions is present, the certificate is invalid
+ for (int i = 0; i < not_allowed_ext_size; i++) {
+ extension_ptr = X509V3_get_d2i(extensions, not_allowed_extensions[i], NULL, NULL);
+ if (extension_ptr != NULL) {
+ app_log_error("Not allowed extensions" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ }
+ // Key usage shall be present and it shall be set as keyAgreement
+ // UINT32_MAX indicates key usage field is not present
+ uint32_t key_usage = X509_get_key_usage(cert);
+ if (key_usage == UINT32_MAX) {
+ app_log_error("Key usage field is not present" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ if ((key_usage & KU_KEY_AGREEMENT) == 0) {
+ app_log_error("keyAgreement is not set in key usage" APP_LOG_NEW_LINE);
+ return SL_STATUS_FAIL;
+ }
+ return SL_STATUS_OK;
+}
diff --git a/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_certificate_validation.h b/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_certificate_validation.h
new file mode 100644
index 00000000000..e54d65cfed4
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_certificate_validation.h
@@ -0,0 +1,62 @@
+/**************************************************************************//**
+ * @file
+ * @brief BT Mesh Host Provisioner component - Certificate Validation
+ ******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ ******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ *****************************************************************************/
+#ifndef CERTIFICATE_VALIDATION_H
+#define CERTIFICATE_VALIDATION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "sl_status.h"
+#include "sl_bgapi.h"
+
+/***************************************************************************//**
+* Validate the received certificate and extract public key
+*
+* @param[in] raw_cert Pointer to the device certificate
+* @param[in] cert_len Length of the certificate
+* @param[in] root_cert_path Path of the root certificate
+* @param[in] uuid UUID of the device
+* @param[in] key_size size of pub_key in bytes
+* @param[out] pub_key Pointer to the extracted public key
+* @return Status of the validation and kex extraction
+*******************************************************************************/
+sl_status_t btmesh_prov_validate_certificate(const unsigned char *raw_cert,
+ size_t cert_len,
+ const char *root_cert_path,
+ uuid_128 *uuid,
+ size_t key_size,
+ uint8_t *pub_key);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //CERTIFICATE_VALIDATION_H
diff --git a/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_prov_cbp.c b/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_prov_cbp.c
new file mode 100644
index 00000000000..6f34bca6d07
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_prov_cbp.c
@@ -0,0 +1,461 @@
+/***************************************************************************//**
+* @file
+* @brief BT Mesh Host Provisioner component - Certificate-Based Provisioning
+********************************************************************************
+* # License
+* Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+********************************************************************************
+*
+* SPDX-License-Identifier: Zlib
+*
+* The licensor of this software is Silicon Laboratories Inc.
+*
+* This software is provided 'as-is', without any express or implied
+* warranty. In no event will the authors be held liable for any damages
+* arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not
+* claim that you wrote the original software. If you use this software
+* in a product, an acknowledgment in the product documentation would be
+* appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+* misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+*
+*******************************************************************************/
+
+// -----------------------------------------------------------------------------
+// Includes
+
+// standard library headers
+#include
+#include
+#include
+
+// app-related headers
+#include "app.h"
+#include "app_assert.h"
+#include "app_log.h"
+#include "btmesh_app_prov.h"
+#include "btmesh_app_prov_cbp.h"
+#include "btmesh_prov.h"
+#include "sl_common.h"
+#include "sl_bt_api.h"
+#include "btmesh_app_certificate_validation.h"
+
+// -----------------------------------------------------------------------------
+// Macros
+
+/// EC public key size without the leading 0x04
+#define CBP_PUBLIC_KEY_SIZE 64
+/// Flag indicating if an OOB public key is required or not
+#define CBP_OOB_REQUIREMENT_PUBLIC_KEY 1
+/// Flag indicating OOB authorization method
+#define CBP_AUTH_METHOD_FLAG 0
+/// Flag indicating output action, if any
+#define CBP_OUTPUT_ACTION_FLAG 0
+/// Flag indicating input action, if any
+#define CBP_INPUT_ACTION_FLAG 0
+/// Minimum size of the in/outout action, 0 if not used
+#define CBP_MINIMUM_OOB_ACTION_SIZE 0
+/// Maximum size of the in/outout action, 0 if not used
+#define CBP_MAXIMUM_OOB_ACTION_SIZE 0
+
+/// Suspend provisioning at link open
+#define CBP_PROVISIONING_SUSPEND_AT_LINK_OPEN 2
+/// Provisioning has been suspended at link open
+#define CBP_PROVISIONING_SUSPENDED_LINK_OPEN 1
+
+// -----------------------------------------------------------------------------
+// Enums, structs, typedefs
+
+/// Possible states of CBP
+typedef enum cbp_state_e{
+ CBP_NONE,
+ CBP_WAITING_FOR_PROVISION_SUSPEND,
+ CBP_STARTED,
+ CBP_WAITING_FOR_PROVISION_RECORDS_LIST,
+ CBP_HANDLE_PROVISION_RECORDS_LIST,
+ CBP_WAITING_FOR_PROVISION_RECORD_DATA,
+ CBP_VALIDATING,
+ CBP_WAITING_FOR_PKEY_REQUEST,
+ CBP_SEND_PKEY_RESPONSE,
+ CBP_ERROR,
+ CBP_DONE
+} cbp_state_t;
+
+// -----------------------------------------------------------------------------
+// Static Function Declarations
+
+/***************************************************************************//**
+ * Handle provisioning records list event
+ *
+ * @param[in] result Result of the event
+ * @param[in] uuid UUID of the device
+ * @param[in] list Pointer to the list of provisioning results array
+ ******************************************************************************/
+static void btmesh_prov_on_provisioning_records_list_received(uint16_t result,
+ uuid_128 uuid,
+ uint8array *list);
+
+/***************************************************************************//**
+* Handle provisioning record data event
+*
+* @param[in] result Result of the event
+* @param[in] uuid UUID of the device
+* @param[in] record Provisioning record ID
+* @param[in] final Flag indicating the last part of the data
+* @param[in] data Pointer to the received data array
+*******************************************************************************/
+static void btmesh_prov_on_provisioning_record_data_received(uint16_t result,
+ uuid_128 uuid,
+ uint16_t record,
+ uint8_t final,
+ uint8array *data);
+
+// -----------------------------------------------------------------------------
+// Static Variables
+
+/// CBP is to be used in provisioning
+bool cbp_enabled = false;
+/// Current state of CBP handling
+cbp_state_t cbp_state = CBP_NONE;
+/// Pointer to the device certificate
+uint8_t *certificate = NULL;
+/// Length of the certificate
+static size_t certificate_len = 0;
+/// Public key extracted from certificate
+uint8_t pkey[CBP_PUBLIC_KEY_SIZE] = { 0 };
+/// UUID of the device to be provisioned
+uuid_128 cbp_uuid = { 0 };
+/// Command line options
+static struct option cbp_long_options[CBP_OPTLENGTH] = {
+ { "cbp", no_argument, 0, 'c' }
+};
+
+// -----------------------------------------------------------------------------
+// Function definitions
+
+sl_status_t btmesh_cbp_init(int cmd_opt, char *cmd_optarg)
+{
+ sl_status_t sc = SL_STATUS_OK;
+
+ switch (cmd_opt) {
+ case 'c':
+ cbp_enabled = true;
+ break;
+ default:
+ sc = SL_STATUS_NOT_FOUND;
+ break;
+ }
+ return sc;
+}
+
+/***************************************************************************//**
+* BT Mesh event handler for CBP
+*******************************************************************************/
+void btmesh_cbp_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_prov_provisioning_records_list_id:
+ {
+ // The list shows all provisioning records, e.g. all certificates present on the device
+ sl_btmesh_evt_prov_provisioning_records_list_t *prov_records_list_evt;
+ prov_records_list_evt = (sl_btmesh_evt_prov_provisioning_records_list_t *)&(evt->data);
+ uint16_t result = prov_records_list_evt->result;
+ uuid_128 uuid = prov_records_list_evt->uuid;
+ uint8array *list = &prov_records_list_evt->list;
+ btmesh_prov_on_provisioning_records_list_received(result,
+ uuid,
+ list);
+ break;
+ }
+ case sl_btmesh_evt_prov_provisioning_record_data_id:
+ {
+ // Data of a requested provisioning record, i.e. the certificate itself
+ // This is a recurring event until final == 1 is received
+ sl_btmesh_evt_prov_provisioning_record_data_t *prov_record_data_evt;
+ prov_record_data_evt = (sl_btmesh_evt_prov_provisioning_record_data_t *)&(evt->data);
+ uint16_t result = prov_record_data_evt->result;
+ uuid_128 uuid = prov_record_data_evt->uuid;
+ uint16_t record = prov_record_data_evt->record;
+ uint8_t final = prov_record_data_evt->final;
+ uint8array *data = &prov_record_data_evt->data;
+ btmesh_prov_on_provisioning_record_data_received(result,
+ uuid,
+ record,
+ final,
+ data);
+ break;
+ }
+ case sl_btmesh_evt_prov_provisioning_suspended_id:
+ {
+ // For CBP provisioning must be suspended at link open to fetch and validate
+ // the device certificate(s)
+ sl_btmesh_evt_prov_provisioning_suspended_t *prov_suspended_evt;
+ prov_suspended_evt = (sl_btmesh_evt_prov_provisioning_suspended_t *)&(evt->data);
+ uint8_t reason = prov_suspended_evt->reason;
+ if (reason == CBP_PROVISIONING_SUSPENDED_LINK_OPEN) {
+ app_log_debug("Provisioning suspended, link open" APP_LOG_NEW_LINE);
+ cbp_state = CBP_STARTED;
+ } else {
+ app_log_info("Provisioning suspended, reason: 0x%02x" APP_LOG_NEW_LINE, reason);
+ cbp_state = CBP_ERROR;
+ }
+ break;
+ }
+ case sl_btmesh_evt_prov_oob_pkey_request_id:
+ app_log_debug("pkey request received" APP_LOG_NEW_LINE);
+ // The device asks for the public key of its previously sent device certificate
+ if (cbp_state == CBP_WAITING_FOR_PKEY_REQUEST) {
+ // Only switch to the response state if we actually anticipate the pkey request
+ cbp_state = CBP_SEND_PKEY_RESPONSE;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+/***************************************************************************//**
+* Wrapper for provisioning records initialization
+*******************************************************************************/
+sl_status_t btmesh_prov_init_provisioning_records(void)
+{
+ return sl_btmesh_prov_init_provisioning_records();
+}
+
+/***************************************************************************//**
+* Setup certificate-based provisioning
+*******************************************************************************/
+sl_status_t btmesh_prov_setup_cbp(uuid_128 uuid)
+{
+ sl_status_t sc = SL_STATUS_OK;
+ if (cbp_enabled == true) {
+ app_log_debug("Setup CBP" APP_LOG_NEW_LINE);
+ // Set OOB requirements so CBP is expected
+ // The second parameter indicates OOB public key usage
+ sc = sl_btmesh_prov_set_oob_requirements(uuid,
+ CBP_OOB_REQUIREMENT_PUBLIC_KEY,
+ CBP_AUTH_METHOD_FLAG,
+ CBP_OUTPUT_ACTION_FLAG,
+ CBP_INPUT_ACTION_FLAG,
+ CBP_MINIMUM_OOB_ACTION_SIZE,
+ CBP_MAXIMUM_OOB_ACTION_SIZE);
+ if (sc != SL_STATUS_OK) {
+ app_log_status_error_f(sc, "Failed to set OOB requirements" APP_LOG_NEW_LINE);
+ return sc;
+ }
+
+ // Set provisioning suspend event right after link open
+ // This lets provision_adv_device() start the provisioning session
+ // but will instantly suspend it so the provisioner can get the
+ // provisioning record data (i.e. the device certificate)
+ sc = sl_btmesh_prov_set_provisioning_suspend_event(CBP_PROVISIONING_SUSPEND_AT_LINK_OPEN);
+ if (sc != SL_STATUS_OK) {
+ app_log_status_error_f(sc, "Failed to set provisioning suspend event" APP_LOG_NEW_LINE);
+ return sc;
+ }
+ cbp_uuid = uuid;
+ cbp_state = CBP_NONE;
+ }
+ return sc;
+}
+
+/***************************************************************************//**
+* Handle certificate-based provisioning
+*******************************************************************************/
+void btmesh_app_prov_handle_cbp(uint16_t netkey_index,
+ uuid_128 uuid,
+ bd_addr mac_address,
+ uint8_t bearer_type)
+{
+ if (cbp_enabled == true) {
+ switch (cbp_state) {
+ case CBP_NONE:
+ {
+ app_log_info("Starting CBP provisioning" APP_LOG_NEW_LINE);
+ if (bearer_type != HOST_PROV_PB_REMOTE) {
+ // Initial state, start provisioning. A suspended event shall occur soon
+ sl_status_t sc = btmesh_prov_provision_adv_device(netkey_index, uuid, mac_address, bearer_type, 0);
+ if (sc != SL_STATUS_OK) {
+ app_log_status_error_f(sc, "Failed to start provisioning" APP_LOG_NEW_LINE);
+ cbp_state = CBP_ERROR;
+ return;
+ }
+ }
+ app_log_debug("Waiting for provision suspend event" APP_LOG_NEW_LINE);
+ cbp_state = CBP_WAITING_FOR_PROVISION_SUSPEND;
+ break;
+ }
+ case CBP_WAITING_FOR_PROVISION_SUSPEND:
+ // Wait for the provision suspended event. Next state called from event handler.
+ break;
+ case CBP_STARTED:
+ {
+ // Provisioning suspended, acquire provisioning records list
+ app_log_debug("Provisioning suspended, getting provisioning records list" APP_LOG_NEW_LINE);
+ sl_status_t sc;
+ sc = sl_btmesh_prov_get_provisioning_records_list(cbp_uuid);
+ if (sc != SL_STATUS_OK) {
+ app_log_status_error_f(sc, "Failed to get provisioning records list" APP_LOG_NEW_LINE);
+ cbp_state = CBP_ERROR;
+ return;
+ }
+ cbp_state = CBP_WAITING_FOR_PROVISION_RECORDS_LIST;
+ break;
+ }
+ case CBP_WAITING_FOR_PROVISION_RECORDS_LIST:
+ // Wait for the records list. Next state called form event handler.
+ break;
+ case CBP_HANDLE_PROVISION_RECORDS_LIST:
+ {
+ // Handle records list if more than one certificates are present on the device.
+ // In this example only one device certificate is present, its record ID is always 1.
+ uint16_t record = 1;
+ sl_status_t sc = sl_btmesh_prov_get_provisioning_record_data(cbp_uuid, record);
+ if (sc != SL_STATUS_OK) {
+ app_log_error("Failed to get provisioning record data" APP_LOG_NEW_LINE);
+ cbp_state = CBP_ERROR;
+ return;
+ }
+ cbp_state = CBP_WAITING_FOR_PROVISION_RECORD_DATA;
+ break;
+ }
+ case CBP_WAITING_FOR_PROVISION_RECORD_DATA:
+ // Wait for the record data. Next state called form event handler.
+ break;
+ case CBP_VALIDATING:
+ {
+ // Validate the device certificate. Check expiration, UUID, etc.
+ app_log_info("Validating device certificate" APP_LOG_NEW_LINE);
+ sl_status_t sc = btmesh_prov_validate_certificate(
+ certificate,
+ certificate_len,
+ "CA/ca-certificate.crt",
+ &cbp_uuid,
+ CBP_PUBLIC_KEY_SIZE,
+ pkey
+ );
+ free(certificate);
+ certificate_len = 0;
+ if (sc != SL_STATUS_OK) {
+ app_log_error("Failed to validate certificate" APP_LOG_NEW_LINE);
+ cbp_state = CBP_ERROR;
+ return;
+ }
+
+ // Continue provisioning if the certificate is valid
+ sc = sl_btmesh_prov_continue_provisioning(cbp_uuid);
+ if (sc != SL_STATUS_OK) {
+ app_log_error("Failed to continue provisioning" APP_LOG_NEW_LINE);
+ cbp_state = CBP_ERROR;
+ return;
+ }
+ cbp_state = CBP_WAITING_FOR_PKEY_REQUEST;
+ break;
+ }
+
+ case CBP_WAITING_FOR_PKEY_REQUEST:
+ // Wait for pkey request. Next state called form event handler.
+ break;
+
+ case CBP_SEND_PKEY_RESPONSE:
+ {
+ app_log_debug("Sending pkey response" APP_LOG_NEW_LINE);
+ // Send back the extracted EC public key to the device
+ size_t pkey_len = CBP_PUBLIC_KEY_SIZE;
+ sl_status_t sc = sl_btmesh_prov_send_oob_pkey_response(cbp_uuid, pkey_len, pkey);
+ if (sc != SL_STATUS_OK) {
+ app_log_status_error_f(sc, "Failed to send pkey response" APP_LOG_NEW_LINE);
+ cbp_state = CBP_ERROR;
+ return;
+ }
+ cbp_state = CBP_DONE;
+ break;
+ }
+ case CBP_ERROR:
+ // Handle any errors and inform the user about failure
+ btmesh_prov_on_provision_failed_evt(0x04, cbp_uuid);
+ break;
+ case CBP_DONE:
+ // CBP finished successfully. The application will handle the provisioning
+ // finished (or failed) event independently.
+ break;
+ default:
+ break;
+ }
+ } else if (bearer_type != HOST_PROV_PB_REMOTE) {
+ // If CBP is not requested, provisioning of the selected device must be started
+ // at this point. If remote provisioning is ongoing, then this is handled.
+ static bool prov_started = false;
+ if (prov_started == false) {
+ sl_status_t sc = btmesh_prov_provision_adv_device(netkey_index, uuid, mac_address, bearer_type, 0);
+ app_assert_status_f(sc, "Provisioning failed" APP_LOG_NEW_LINE);
+ prov_started = true;
+ }
+ }
+}
+
+void btmesh_app_prov_set_cbp_capability(bool capability)
+{
+ app_log_debug("CBP set to %s" APP_LOG_NEW_LINE, capability ? "true" : "false");
+ cbp_enabled = capability;
+}
+
+// -----------------------------------------------------------------------------
+// Callbacks
+
+/*******************************************************************************
+ * Handle provisioning records list event
+ ******************************************************************************/
+void btmesh_prov_on_provisioning_records_list_received(uint16_t result,
+ uuid_128 uuid,
+ uint8array *list)
+{
+ // Handle multiple (intermediate) certificates here
+ cbp_state = CBP_HANDLE_PROVISION_RECORDS_LIST;
+}
+
+/*******************************************************************************
+* Handle provisioning record data event
+*******************************************************************************/
+void btmesh_prov_on_provisioning_record_data_received(uint16_t result,
+ uuid_128 uuid,
+ uint16_t record,
+ uint8_t final,
+ uint8array *data)
+{
+ // Store device certificate and extract the EC public key from it
+ certificate_len += data->len;
+ certificate = (uint8_t *)realloc(certificate, certificate_len);
+ if (certificate == NULL) {
+ app_log_error("Failed to allocate memory for CBP certificate" APP_LOG_NEW_LINE);
+ cbp_state = CBP_ERROR;
+ return;
+ }
+ memcpy(certificate + certificate_len - data->len, data->data, data->len);
+ if (final == 1) {
+ // The raw certificate is to be used in validation, no free here
+ cbp_state = CBP_VALIDATING;
+ }
+}
+
+/***************************************************************************//**
+* Check if CBP-related options are enabled
+*******************************************************************************/
+sl_status_t btmesh_cbp_on_check_cmd_options(int cmd_opt, char *cmd_optarg)
+{
+ return btmesh_cbp_init(cmd_opt, cmd_optarg);
+}
+
+void btmesh_cbp_on_build_cmd_options(struct option *long_options)
+{
+ if (NULL != long_options) {
+ memcpy(long_options, cbp_long_options, CBP_OPTLENGTH * sizeof(struct option));
+ }
+}
diff --git a/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_prov_cbp.h b/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_prov_cbp.h
new file mode 100644
index 00000000000..0cf32467b63
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_prov_cbp/btmesh_app_prov_cbp.h
@@ -0,0 +1,56 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Host Provisioner command line build only.
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef BTMESH_APP_PROV_CBP_H
+#define BTMESH_APP_PROV_CBP_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+// Optstring argument for getopt
+#define CBP_OPTLENGTH 1
+// Optstring argument for getopt
+#define CBP_OPTSTRING "c"
+
+/// Usage info
+#define CBP_USAGE \
+ " [--cbp]"
+
+// Options info
+#define CBP_OPTIONS \
+ "Certificate-based provisioning-related options:\n" \
+ " -c --cbp Use certificate-based provisioning\n\n" \
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // BTMESH_APP_PROV_CBP_H
diff --git a/app/btmesh/common_host/btmesh_app_prov_ui/btmesh_app_prov_ui.c b/app/btmesh/common_host/btmesh_app_prov_ui/btmesh_app_prov_ui.c
new file mode 100644
index 00000000000..d7ef63627d5
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_prov_ui/btmesh_app_prov_ui.c
@@ -0,0 +1,444 @@
+/***************************************************************************//**
+* @file
+* @brief BT Mesh Host Provisioner Example Project - UI mode handler.
+*******************************************************************************
+* # License
+* Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+*******************************************************************************
+*
+* SPDX-License-Identifier: Zlib
+*
+* The licensor of this software is Silicon Laboratories Inc.
+*
+* This software is provided 'as-is', without any express or implied
+* warranty. In no event will the authors be held liable for any damages
+* arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not
+* claim that you wrote the original software. If you use this software
+* in a product, an acknowledgment in the product documentation would be
+* appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+* misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+*
+*******************************************************************************/
+
+// -----------------------------------------------------------------------------
+// Includes
+
+#include "app.h"
+#include "app_assert.h"
+#include "app_log.h"
+#include "app_sleep.h"
+#include "btmesh_app_prov.h"
+#include "btmesh_prov.h"
+#include "sl_common.h"
+
+#include "btmesh_app_prov_ui.h"
+#ifdef SL_CATALOG_APP_REMOTE_PROV_PRESENT
+#include "btmesh_app_remote_prov_ui.h"
+#endif
+
+#include
+#include
+
+// -----------------------------------------------------------------------------
+// Macros
+
+#if defined(POSIX) && POSIX == 1
+ #define CLEARSCR "clear"
+ #include
+ #include
+ #include
+#else
+ #define CLEARSCR "cls"
+ #include
+ #include
+#endif // defined(POSIX) && POSIX == 1
+
+/// Buffer length for user input
+#define INPUT_BUFFER_LEN 100
+/// Time to sleep between user input checking in microseconds
+#define INPUT_SLEEP_US 10000
+
+// -----------------------------------------------------------------------------
+// Static Function Declarations
+
+/***************************************************************************//**
+* Helper script to print instructions for provisioning
+*
+*******************************************************************************/
+static void select_unprovisioned_node_instruction(void);
+
+/***************************************************************************//**
+* Helper script to print instructions for unprovisioning
+*
+*******************************************************************************/
+static void select_provisioned_node_instruction(void);
+
+/***************************************************************************//**
+* The user input handler called in a new thread
+*
+* @param[in] ptr Unused
+*******************************************************************************/
+static void *handle_input(void *ptr);
+
+/***************************************************************************//**
+* Peek the standard input
+*
+* @param[out] buf Character array to save the input to
+* @param[in] buffer_length Length of buffer
+* @param[out] num_read Actual number of characters read
+* @return Status of the peek.
+* @retval true If user input is ready
+* @retval false If user input is still being processed
+*******************************************************************************/
+static bool peek_stdin(char *buf, int buffer_length, int *num_read);
+
+// -----------------------------------------------------------------------------
+// Static Variables
+
+static volatile bool run = true;
+/// Flag indicating the user input is ready to be processed
+static volatile bool input_ready = false;
+/// Flag indicating the end of command execution
+static volatile bool command_end = false;
+/// User input handler thread
+static pthread_t thread_input;
+/// Buffer for user input
+static char input_buffer[INPUT_BUFFER_LEN];
+/// UI state
+sl_ui_state ui_state = UI_MENU;
+
+// -----------------------------------------------------------------------------
+// Public function definitions
+
+void btmesh_app_prov_handle_ui(void)
+{
+ switch (ui_state) {
+ case UI_MENU: {
+ system(CLEARSCR);
+ app_log(PROV_UI_MENU);
+ input_ready = false;
+ ui_state = UI_WAITING_FOR_CMD;
+ if (0 == thread_input) {
+ int ret = pthread_create(&thread_input, NULL, handle_input, NULL);
+ if (0 != ret) {
+ app_log_critical("Failed to create input thread!" APP_LOG_NL);
+ exit(EXIT_FAILURE);
+ }
+ }
+ break;
+ }
+ case UI_WAITING_FOR_CMD:
+ if (true == input_ready) {
+ char c = input_buffer[0];
+ switch (c) {
+ case SCAN_COMMAND:
+ btmesh_app_prov_set_command(SCAN);
+ btmesh_app_prov_set_command_state(START);
+ ui_state = UI_CMD_PROCESSING;
+ break;
+ case PROVISION_COMMAND:
+ btmesh_app_prov_set_command(PROVISION);
+ btmesh_app_prov_set_command_state(START);
+ ui_state = UI_CMD_PROCESSING;
+ break;
+ case NODELIST_COMMAND:
+ btmesh_app_prov_set_command(NODELIST);
+ btmesh_app_prov_set_command_state(START);
+ ui_state = UI_CMD_PROCESSING;
+ break;
+ case NODEINFO_COMMAND:
+ btmesh_app_prov_set_command(NODEINFO);
+ btmesh_app_prov_set_command_state(START);
+ ui_state = UI_CMD_PROCESSING;
+ break;
+ case REMOVE_NODE_COMMAND:
+ btmesh_app_prov_set_command(REMOVE_NODE);
+ btmesh_app_prov_set_command_state(START);
+ ui_state = UI_CMD_PROCESSING;
+ break;
+ case KEY_REFRESH_COMMAND:
+ btmesh_app_prov_set_command(KEY_REFRESH);
+ btmesh_app_prov_set_command_state(START);
+ ui_state = UI_CMD_PROCESSING;
+ break;
+ case KEY_EXPORT_COMMAND:
+ btmesh_app_prov_set_command(KEY_EXPORT);
+ btmesh_app_prov_set_command_state(START);
+ ui_state = UI_CMD_PROCESSING;
+ break;
+ case RESET_COMMAND:
+ btmesh_app_prov_set_command(RESET);
+ btmesh_app_prov_set_command_state(START);
+ ui_state = UI_CMD_PROCESSING;
+ break;
+ case EXIT_COMMAND:
+ ui_state = UI_EXIT;
+ run = false;
+ break;
+ default: {
+ sl_status_t sc = btmesh_app_prov_ui_handle_remote_prov_cmd(c);
+ if (SL_STATUS_OK != sc) {
+ btmesh_app_prov_end_of_cmd();
+ } else {
+ ui_state = UI_CMD_PROCESSING;
+ }
+ break;
+ }
+ }
+ }
+ break;
+ case UI_CMD_PROCESSING:
+ break;
+ case UI_WAITING_FOR_INPUT:
+ if ((true == input_ready) && (true == command_end)) {
+ command_end = false;
+ ui_state = UI_MENU;
+ }
+ break;
+
+ case UI_EXIT:
+ run = false;
+ pthread_join(thread_input, NULL);
+ //btmesh_prov_free_remote_serverlist();
+ exit(EXIT_SUCCESS);
+ break;
+ default:
+ break;
+ }
+}
+
+void btmesh_app_prov_end_of_cmd(void)
+{
+ btmesh_app_prov_set_command(NONE);
+ btmesh_app_prov_set_command_state(START);
+ app_log_nl();
+ app_log("Press enter to continue..." APP_LOG_NEW_LINE);
+ input_ready = false;
+ command_end = true;
+ ui_state = UI_WAITING_FOR_INPUT;
+}
+
+bool btmesh_app_prov_get_uuid_from_unprov_list(uuid_128 *command_uuid)
+{
+ bool return_value = false;
+ sl_status_t sc;
+
+ if (UI_CMD_PROCESSING == ui_state) {
+ select_unprovisioned_node_instruction();
+ } else if ((UI_WAITING_FOR_INPUT == ui_state) && (true == input_ready)) {
+ size_t len = strlen(input_buffer);
+ if (ADDRESS_LEN_WITHOUT_PREFIX > len) {
+ uint16_t id = (uint16_t)atoi(input_buffer);
+ sc = btmesh_prov_get_unprov_uuid_by_id(id, command_uuid);
+ if (SL_STATUS_OK != sc) {
+ btmesh_app_prov_end_of_cmd();
+ } else {
+ return_value = true;
+ ui_state = UI_CMD_PROCESSING;
+ }
+ } else if (UUID_LEN_WITHOUT_SEPARATORS == len
+ || UUID_LEN_WITH_SEPARATORS == len) {
+ app_parse_uuid(input_buffer, len, command_uuid);
+ return_value = true;
+ ui_state = UI_CMD_PROCESSING;
+ } else {
+ app_log_error("Invalid input format!" APP_LOG_NEW_LINE);
+ btmesh_app_prov_end_of_cmd();
+ }
+ }
+ return return_value;
+}
+
+bool btmesh_app_prov_get_uuid_from_prov_list(uuid_128 *command_uuid)
+{
+ bool return_value = false;
+ sl_status_t sc;
+
+ if (UI_CMD_PROCESSING == ui_state) {
+ select_provisioned_node_instruction();
+ } else if ((UI_WAITING_FOR_INPUT == ui_state) && (true == input_ready)) {
+ size_t len = strlen(input_buffer);
+ if (ADDRESS_LEN_WITHOUT_PREFIX > len) {
+ uint16_t id = (uint16_t)atoi(input_buffer);
+ sc = btmesh_prov_get_prov_uuid_by_id(id, command_uuid);
+ if (SL_STATUS_OK != sc) {
+ btmesh_app_prov_end_of_cmd();
+ } else {
+ return_value = true;
+ ui_state = UI_CMD_PROCESSING;
+ }
+ } else if (ADDRESS_LEN_WITHOUT_PREFIX == len
+ || ADDRESS_LEN_WITH_PREFIX == len) {
+ uint16_t addr;
+ app_parse_address(input_buffer, len, &addr);
+ sc = btmesh_prov_get_prov_uuid_by_address(addr, command_uuid);
+ if (SL_STATUS_OK != sc) {
+ btmesh_app_prov_end_of_cmd();
+ } else {
+ return_value = true;
+ ui_state = UI_CMD_PROCESSING;
+ }
+ } else if (UUID_LEN_WITHOUT_SEPARATORS == len
+ || UUID_LEN_WITH_SEPARATORS == len) {
+ app_parse_uuid(input_buffer, len, command_uuid);
+ return_value = true;
+ ui_state = UI_CMD_PROCESSING;
+ } else {
+ app_log_error("Invalid input format!" APP_LOG_NEW_LINE);
+ btmesh_app_prov_end_of_cmd();
+ }
+ }
+ return return_value;
+}
+
+bool btmesh_app_prov_ui_get_input_buffer(char **ui_input_buffer)
+{
+ bool return_value = false;
+ if (UI_CMD_PROCESSING == ui_state) {
+ input_ready = false;
+ ui_state = UI_WAITING_FOR_INPUT;
+ } else if ((UI_WAITING_FOR_INPUT == ui_state) && (true == input_ready)) {
+ *ui_input_buffer = &(input_buffer[0]);
+ ui_state = UI_CMD_PROCESSING;
+ return_value = true;
+ }
+
+ return return_value;
+}
+
+sl_ui_state btmesh_app_prov_ui_get_ui_state(void)
+{
+ return ui_state;
+}
+
+// -----------------------------------------------------------------------------
+// Private function definitions
+
+void *handle_input(void *ptr)
+{
+ int num_read = 0;
+ while (run) {
+ if (false == input_ready) {
+ if (peek_stdin(input_buffer, sizeof(input_buffer), &num_read)) {
+ input_buffer[num_read - 1] = '\0';
+ app_log_debug("Command received: %s" APP_LOG_NEW_LINE, input_buffer);
+ app_log_debug("Length: %d" APP_LOG_NEW_LINE, num_read);
+ input_ready = true;
+ }
+ }
+ app_sleep_us(INPUT_SLEEP_US);
+ }
+ return 0;
+}
+
+static bool peek_stdin(char *buf, int buffer_length, int *num_read)
+{
+#if defined(POSIX) && POSIX == 1
+ // Setup an instant non-blocking peek
+ fd_set rfds;
+ struct timeval tv = { .tv_sec = 0, .tv_usec = 0 };
+ FD_ZERO(&rfds);
+ FD_SET(0, &rfds);
+ // Peek standard input
+ int retval = select(1, &rfds, NULL, NULL, &tv);
+ if (-1 == retval) {
+ // Some kind of error happened
+ app_log_error("Can't check stdin" APP_LOG_NEW_LINE);
+ return false;
+ } else if (0 == retval) {
+ // No input data available
+ return false;
+ } else {
+ // Input data available
+ *num_read = read(STDIN_FILENO, buf, buffer_length);
+ if (-1 == *num_read) {
+ app_log_error("Can't read from stdin" APP_LOG_NEW_LINE);
+ }
+ return true;
+ }
+#else
+ static int current_length = 0;
+ if (_kbhit()) {
+ // Get the last character while echoing it back
+ buf[current_length] = _getche();
+ current_length++;
+ *num_read = current_length;
+ if (13 == buf[current_length - 1]) { // Enter pressed
+ // Fake the newline char to behave like read()
+ buf[current_length - 1] = '\n';
+ current_length = 0;
+ // Write a newline so it behaves like POSIX
+ app_log_nl();
+ return true;
+ } else if (8 == buf[current_length - 1]) { // Backspace
+ current_length--;
+ if (0 != current_length) {
+ current_length--;
+ }
+ return false;
+ } else if (current_length == buffer_length) { // Buffer length reached
+ buf[buffer_length - 1] = '\n';
+ current_length = 0;
+ // As a full buffer is considered as a finished command, add a newline here as well
+ app_log_nl();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ return false;
+#endif // defined(POSIX) && POSIX == 1
+}
+
+static void select_unprovisioned_node_instruction(void)
+{
+ sl_status_t sc;
+
+ app_log("Select a node to provision" APP_LOG_NEW_LINE);
+ app_log("Type either the ID or UUID from the list below" APP_LOG_NEW_LINE);
+ app_log_nl();
+ sc = btmesh_prov_list_unprovisioned_nodes();
+ if (SL_STATUS_EMPTY == sc) {
+ app_log_info("No unprovisioned nodes available" APP_LOG_NEW_LINE);
+ app_log_info("Try scanning for unprovisioned nodes first" APP_LOG_NEW_LINE);
+ btmesh_app_prov_end_of_cmd();
+ } else {
+ input_ready = false;
+ ui_state = UI_WAITING_FOR_INPUT;
+ }
+}
+
+static void select_provisioned_node_instruction(void)
+{
+ sl_status_t sc;
+
+ app_log("Select a node" APP_LOG_NEW_LINE);
+ app_log("Type either the ID, UUID or address from the list below" APP_LOG_NEW_LINE);
+ app_log_nl();
+ sc = btmesh_prov_list_provisioned_nodes();
+ if (SL_STATUS_EMPTY == sc) {
+ app_log_info("No provisioned nodes available" APP_LOG_NEW_LINE);
+ btmesh_app_prov_end_of_cmd();
+ } else {
+ input_ready = false;
+ ui_state = UI_WAITING_FOR_INPUT;
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Callbacks
+
+SL_WEAK sl_status_t btmesh_app_prov_ui_handle_remote_prov_cmd(char cmd_id)
+{
+ (void)cmd_id;
+
+ return SL_STATUS_NOT_FOUND;
+}
diff --git a/app/btmesh/common_host/btmesh_app_prov_ui/btmesh_app_prov_ui.h b/app/btmesh/common_host/btmesh_app_prov_ui/btmesh_app_prov_ui.h
new file mode 100644
index 00000000000..9f80fc67b84
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_prov_ui/btmesh_app_prov_ui.h
@@ -0,0 +1,103 @@
+/**************************************************************************//**
+ * @file
+ * @brief BT Mesh Host Provisioner Example Project.
+ ******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ ******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ *****************************************************************************/
+
+#ifndef BTMESH_APP_PROV_UI_H
+#define BTMESH_APP_PROV_UI_H
+
+#include "sl_status.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EXIT_COMMAND 'q'
+#define SCAN_COMMAND 's'
+#define PROVISION_COMMAND 'p'
+#define NODELIST_COMMAND 'l'
+#define NODEINFO_COMMAND 'i'
+#define REMOVE_NODE_COMMAND 'r'
+#define KEY_REFRESH_COMMAND 'k'
+#define KEY_EXPORT_COMMAND 'x'
+#define RESET_COMMAND 'e'
+
+#ifndef SL_CATALOG_APP_REMOTE_PROV_PRESENT
+#define PROV_UI_MENU \
+ "Please select a functionality\n\n" \
+ "s - Scan available nodes\n" \
+ "p - Provision a beaconing node\n" \
+ "l - List nodes in the network\n" \
+ "i - Information about a node in the network\n" \
+ "r - Remove node from the network\n" \
+ "k - Refresh the network key and app key\n" \
+ "x - Export the network keys, app keys and device keys\n" \
+ "e - Reset provisioner node and exit application\n" \
+ "q - Exit application\n\n"
+#endif
+
+/// UI state
+typedef enum ui_state_e{
+ UI_MENU,
+ UI_WAITING_FOR_CMD,
+ UI_CMD_PROCESSING,
+ UI_WAITING_FOR_INPUT,
+ UI_EXIT
+} sl_ui_state;
+
+/**************************************************************************//**
+ * Remote provisioning related command handler
+ *
+ * @param[in] cmd_id Command id
+ * @retval SL_STATUS_NOT_FOUND for unknown command id
+ * SL_STATUS_OK for handled command id
+ *****************************************************************************/
+sl_status_t btmesh_app_prov_ui_handle_remote_prov_cmd(char cmd_id);
+
+/**************************************************************************//**
+ * Get UI state
+ *
+ * @retval Current state of the ui (sl_ui_state)
+ *****************************************************************************/
+sl_ui_state btmesh_app_prov_ui_get_ui_state(void);
+
+/**************************************************************************//**
+ * Get buffer with input console content
+ *
+ * @param[out] ui_input_buffer data read from input console
+ * @retval Current state of the ui
+ * @retval false if buffer is empty or content is invalid
+ * true if buffer content vas updated with is valid data
+ *****************************************************************************/
+bool btmesh_app_prov_ui_get_input_buffer(char **ui_input_buffer);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // BTMESH_APP_PROV_UI_H
diff --git a/app/btmesh/common_host/btmesh_app_remote_prov/btmesh_app_remote_prov.c b/app/btmesh/common_host/btmesh_app_remote_prov/btmesh_app_remote_prov.c
new file mode 100644
index 00000000000..82615f84bd5
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_remote_prov/btmesh_app_remote_prov.c
@@ -0,0 +1,434 @@
+/**************************************************************************//**
+* @file
+* @brief BT Mesh Remote Provisioner Application component - CLI handler
+*******************************************************************************
+* # License
+* Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+*******************************************************************************
+*
+* SPDX-License-Identifier: Zlib
+*
+* The licensor of this software is Silicon Laboratories Inc.
+*
+* This software is provided 'as-is', without any express or implied
+* warranty. In no event will the authors be held liable for any damages
+* arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not
+* claim that you wrote the original software. If you use this software
+* in a product, an acknowledgment in the product documentation would be
+* appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+* misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+*
+******************************************************************************/
+
+// ----------------------------------------------------------------------------
+// Includes
+
+#include "app.h"
+#include "btmesh_app_prov.h"
+#include "btmesh_app_remote_prov.h"
+#include "app_log.h"
+#include "app_assert.h"
+
+#include "sl_common.h"
+#include "sl_bt_api.h"
+#include "btmesh_prov.h"
+#include "btmesh_remote_prov.h"
+// ----------------------------------------------------------------------------
+// Macros
+
+// ----------------------------------------------------------------------------
+// Static Function Declarations
+
+/**************************************************************************//**
+* Handle --remote_serverlist functionality
+*
+******************************************************************************/
+static void handle_remote_serverlist(void);
+
+/**************************************************************************//**
+* Handle --remote_scan functionality
+*
+******************************************************************************/
+static void handle_remote_scan(void);
+
+/**************************************************************************//**
+* Handle --remote_provisioning functionality
+*
+******************************************************************************/
+static void handle_remote_provision(void);
+
+// ----------------------------------------------------------------------------
+// Static Variables
+
+/// Flag stating that the btmeshprov_initialized event has arrived
+static bool initialized = false;
+/// UUID passed as an argument
+static uuid_128 command_uuid;
+/// OOB capabilities of the node to be provisioned
+static uint16_t oob_capabilities = 0;
+/// The state of the current command
+static command_state_t command_state = INIT;
+/// The command in use
+static command_t command = NONE;
+/// Remote provisioner server address
+static uint16_t remote_prov_server_addr = 0;
+/// Command line options
+static struct option remote_prov_long_options[REMOTE_PROV_OPTLENGTH] = {
+ { "remote_serverlist", no_argument, 0, 'm' },
+ { "remote_scan", required_argument, 0, 'o' },
+ { "remote_provision", required_argument, 0, 'v' },
+ { "remote_server", required_argument, 0, 'a' }
+};
+
+// ----------------------------------------------------------------------------
+// Function definitions
+
+sl_status_t btmesh_app_remote_prov_init(int cmd_opt, char *cmd_optarg)
+{
+ sl_status_t sc = SL_STATUS_OK;
+
+ switch (cmd_opt) {
+ case 'm':
+ // Scan for remote provisioning servers
+ app_log_info("Remote serverlist" APP_LOG_NEW_LINE);
+ command = REMOTE_SERVERLIST;
+ command_state = START;
+ break;
+ case 'o':
+ // Remote scan for unprovisioned nodes
+ app_log_info("Remote scan" APP_LOG_NEW_LINE);
+ command = REMOTE_SCAN;
+ command_state = START;
+ remote_prov_server_addr = (uint16_t)strtol(cmd_optarg, NULL, 16);
+ break;
+ case 'v':
+ // Remote provision the selected unprovisioned node
+ command = REMOTE_PROVISION;
+ command_state = START;
+ char *pos = cmd_optarg;
+ size_t len = strlen(cmd_optarg);
+ // Parse input for UUID
+ app_parse_uuid(pos, len, &command_uuid);
+ break;
+ case 'a':
+ // Remote provision server address selection
+ remote_prov_server_addr = (uint16_t)strtol(cmd_optarg, NULL, 16);
+ break;
+ default:
+ sc = SL_STATUS_NOT_FOUND;
+ break;
+ }
+ return sc;
+}
+
+void btmesh_app_remote_prov_process_action(void)
+{
+ if (initialized == true) {
+ switch (command) {
+ case REMOTE_SERVERLIST:
+ handle_remote_serverlist();
+ break;
+ case REMOTE_SCAN:
+ handle_remote_scan();
+ break;
+ case REMOTE_PROVISION:
+ handle_remote_provision();
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void btmesh_app_remote_prov_on_event(sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_prov_initialized_id: {
+ // Device successfully initialized in provisioner mode
+ app_log_info("Remote provisioning client init" APP_LOG_NEW_LINE);
+ app_log_nl();
+ sl_status_t sc = sl_btmesh_remote_provisioning_client_init();
+ app_assert_status_f(sc, "Failed to init remote provisioning client" APP_LOG_NEW_LINE);
+ if (INIT == command_state) {
+ command_state = START;
+ }
+ initialized = true;
+
+ break;
+ }
+ ///////////////////////////////////////////////////////////////////////////
+ // Add additional event handlers here as your application requires! //
+ ///////////////////////////////////////////////////////////////////////////
+
+ // -------------------------------
+ // Default event handler.
+ default:
+
+ break;
+ }
+
+ // Let other modules handle their events too
+ btmesh_remote_prov_on_event(evt);
+}
+
+// ----------------------------------------------------------------------------
+// App logic functions
+
+void handle_remote_serverlist(void)
+{
+ switch (command_state) {
+ case START: {
+ uint16_t networks_number = btmesh_app_prov_get_networks_number();
+ if (0 < networks_number) {
+ bool db_stat;
+ db_stat = btmesh_app_prov_get_ddb_status();
+ if (true == db_stat) {
+ sl_status_t sc = btmesh_remote_prov_start_server_capabilities_scan(APP_NETKEY_IDX);
+ if (SL_STATUS_OK != sc) {
+ app_log_info("Failed to start server scanning" APP_LOG_NEW_LINE);
+ command_state = FINISHED;
+ } else {
+ app_log_info("Scanning for remote provisioner servers" APP_LOG_NEW_LINE);
+ app_log_nl();
+ command_state = IN_PROGRESS;
+ }
+ }
+ } else {
+ app_log_nl();
+ app_log_info("No networks are present on the device" APP_LOG_NEW_LINE);
+ command_state = FINISHED;
+ }
+ break;
+ }
+ case IN_PROGRESS:
+ break;
+ case FINISHED: {
+ prov_mode_t prov_mode = btmesh_app_prov_get_mode();
+ app_log_nl();
+ app_log_info("Scanning for remote provisioner servers completed" APP_LOG_NEW_LINE);
+ if (prov_mode == PROV_UI_MODE) {
+ btmesh_app_remote_prov_end_of_cmd();
+ } else {
+ btmesh_prov_free_remote_serverlist();
+ exit(EXIT_SUCCESS);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void handle_remote_scan(void)
+{
+ sl_status_t sc;
+ switch (command_state) {
+ case START: {
+ bool param_ready = true;
+ prov_mode_t prov_mode = btmesh_app_prov_get_mode();
+ if (prov_mode == PROV_UI_MODE) {
+ param_ready = btmesh_app_remote_prov_get_server_address(&remote_prov_server_addr);
+ }
+ if (true == param_ready) {
+ sc = btmesh_remote_prov_start_device_scan_by_address(0,
+ remote_prov_server_addr,
+ REMOTE_PROV_SCANNING_TIMEOUT);
+ if (SL_STATUS_OK != sc) {
+ app_log_info("Failed to start remote scanning" APP_LOG_NEW_LINE);
+ } else {
+ app_log_info("Remote scanning for unprovisioned nodes started" APP_LOG_NEW_LINE);
+ }
+ command_state = IN_PROGRESS;
+ }
+ break;
+ }
+ case IN_PROGRESS:
+ break;
+ case FINISHED: {
+ prov_mode_t prov_mode = btmesh_app_prov_get_mode();
+ app_log_nl();
+ app_log_info("Remote scanning completed" APP_LOG_NEW_LINE);
+ if (prov_mode == PROV_UI_MODE) {
+ btmesh_app_remote_prov_end_of_cmd();
+ } else {
+ exit(EXIT_SUCCESS);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void handle_remote_provision(void)
+{
+ static bd_addr mac = { 0 };
+ switch (command_state) {
+ case START: {
+ bool param_ready = true;
+ prov_mode_t prov_mode = btmesh_app_prov_get_mode();
+ if (prov_mode == PROV_UI_MODE) {
+ param_ready = btmesh_app_remote_prov_get_prov_data(&remote_prov_server_addr, &command_uuid, &oob_capabilities);
+ }
+ if (true == param_ready) {
+ if (remote_prov_server_addr) {
+ if (prov_mode == PROV_UI_MODE) {
+ bool cbp_capability = oob_capabilities & BTMESH_OOB_FLAG_CBP_CAPABLE;
+ btmesh_app_prov_set_cbp_capability(cbp_capability);
+ }
+ app_log_info("Remote provisioning..." APP_LOG_NEW_LINE);
+ uint16_t netkey_index = 0;
+ sl_status_t sc = btmesh_remote_prov_start_provisioning(netkey_index,
+ remote_prov_server_addr,
+ command_uuid,
+ HOST_PROV_PB_ADV,
+ REMOTE_PROV_ATTENTION_TIMER);
+ app_assert_status_f(sc, "Provisioning failed" APP_LOG_NEW_LINE);
+ command_state = IN_PROGRESS;
+ } else {
+ app_log_info("Remote provisioning failed, invalid server address: 0x%04x" APP_LOG_NEW_LINE,
+ remote_prov_server_addr);
+ exit(EXIT_FAILURE);
+ }
+ }
+ break;
+ }
+ case IN_PROGRESS:
+ // Handle CBP if present, else don't do anything
+ btmesh_app_prov_handle_cbp(APP_NETKEY_IDX, command_uuid, mac, HOST_PROV_PB_REMOTE);
+ break;
+ case FINISHED: {
+ prov_mode_t prov_mode = btmesh_app_prov_get_mode();
+ app_log_filter_threshold_set(APP_LOG_LEVEL_INFO);
+ app_log_nl();
+ app_log_info("Provisioning finished" APP_LOG_NEW_LINE);
+ if (prov_mode == PROV_UI_MODE) {
+ btmesh_app_remote_prov_end_of_cmd();
+ } else {
+ exit(EXIT_SUCCESS);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void btmesh_app_remote_prov_get_command(command_t *remote_prov_command, command_state_t *remote_prov_command_state)
+{
+ if (remote_prov_command != NULL) {
+ *remote_prov_command = command;
+ }
+ if (remote_prov_command_state != NULL) {
+ *remote_prov_command_state = command_state;
+ }
+}
+
+void btmesh_app_remote_prov_set_command(command_t remote_prov_command)
+{
+ command = remote_prov_command;
+}
+
+void btmesh_app_remote_prov_set_command_state(command_state_t remote_prov_command_state)
+{
+ command_state = remote_prov_command_state;
+}
+
+// ----------------------------------------------------------------------------
+// Callbacks
+
+void btmesh_remote_prov_on_client_scan_capabilities(uint16_t server, uint8_t max_items)
+{
+ app_log_info("Remote provisioning server address: 0x%04x" APP_LOG_NEW_LINE, server);
+}
+
+void btmesh_remote_prov_on_client_scan_capabilities_finished(void)
+{
+ command_state = FINISHED;
+}
+
+void btmesh_remote_prov_on_client_scan_report(uuid_128 uuid)
+{
+ app_log_nl();
+ app_log_info("Unprovisioned device UUID: ");
+ btmesh_app_prov_append_uuid(&uuid);
+}
+
+void btmesh_remote_prov_on_client_scan_finished(void)
+{
+ command_state = FINISHED;
+}
+
+void btmesh_app_on_provision_failed(void)
+{
+ if (command_state == IN_PROGRESS) {
+ app_log_warning("Remote provision failed"APP_LOG_NEW_LINE);
+ command_state = FINISHED;
+ }
+}
+
+void btmesh_app_on_node_configuration_end(void)
+{
+ if (command_state == IN_PROGRESS) {
+ command_state = FINISHED;
+ }
+}
+
+void btmesh_app_on_build_cmd_options(struct option *long_options)
+{
+ if (NULL != long_options) {
+ memcpy(long_options, remote_prov_long_options, REMOTE_PROV_OPTLENGTH * sizeof(struct option));
+ }
+}
+
+sl_status_t btmesh_app_on_check_cmd_options(int cmd_opt, char *cmd_optarg)
+{
+ return btmesh_app_remote_prov_init(cmd_opt, cmd_optarg);
+}
+
+SL_WEAK void btmesh_app_remote_prov_end_of_cmd(void)
+{
+ exit(EXIT_SUCCESS);
+}
+
+SL_WEAK bool btmesh_app_remote_prov_get_server_address(uint16_t *ui_server_address)
+{
+ (void)ui_server_address;
+
+ return true;
+}
+
+SL_WEAK bool btmesh_app_remote_prov_get_prov_data(uint16_t *remote_prov_server_addr,
+ uuid_128 *remote_prov_uuid,
+ uint16_t *remote_prov_oob_capabilities)
+{
+ (void)remote_prov_server_addr;
+ (void)remote_prov_uuid;
+ (void)remote_prov_oob_capabilities;
+
+ return true;
+}
+
+SL_WEAK void btmesh_app_prov_handle_cbp(uint16_t netkey_index,
+ uuid_128 uuid,
+ bd_addr mac_address,
+ uint8_t bearer_type)
+{
+ (void)netkey_index;
+ (void)uuid;
+ (void)mac_address;
+ (void)bearer_type;
+}
+
+SL_WEAK void btmesh_app_prov_set_cbp_capability(bool capability)
+{
+ (void)capability;
+}
diff --git a/app/btmesh/common_host/btmesh_app_remote_prov/btmesh_app_remote_prov.h b/app/btmesh/common_host/btmesh_app_remote_prov/btmesh_app_remote_prov.h
new file mode 100644
index 00000000000..4bed2af9fab
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_remote_prov/btmesh_app_remote_prov.h
@@ -0,0 +1,180 @@
+/**************************************************************************//**
+ * @file
+ * @brief BT Mesh Remote Provisioner Application component - CLI handler
+ ******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ ******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ *****************************************************************************/
+
+#ifndef BTMESH_APP_REMOTE_PROV_H
+#define BTMESH_APP_REMOTE_PROV_H
+
+#include
+#include "sl_status.h"
+#include "btmesh_app_prov.h"
+#include "ncp_host.h"
+
+#ifdef SL_CATALOG_APP_PROV_CBP_PRESENT
+#include "btmesh_app_prov_cbp.h"
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/// Optstring argument for getopt
+#define REMOTE_PROV_OPTLENGTH 4
+
+/// Optstring argument for getopt
+#define REMOTE_PROV_OPTSTRING "a:mo:v:"
+
+/// Usage info
+#define REMOTE_PROV_USAGE \
+ " [--remote_provision --remote_server ]" \
+ " [--remote_scan ]" \
+ " [--remote_serverlist]" \
+
+// Options info
+#define REMOTE_PROV_OPTIONS \
+ "\nRemote Provisioner-related options:\n" \
+ " -v --remote_provision -a --remote_server \n" \
+ " Remote provision an unprovisioned node\n" \
+ " The UUID of the node to be provisioned.\n" \
+ " Can be acquired by --remote_scan.\n" \
+ " The server addres for remote provisioning\n" \
+ " Can be acquired by --remote_serverlist.\n" \
+ " -o --remote_scan \n" \
+ " Scan and list remote unprovisioned nodes\n" \
+ " The server addres used for executing the " \
+ "remote scan operation\n" \
+ " Can be acquired by --remote_serverlist command.\n" \
+ " -m --remote_serverlist List all available remote servers for further " \
+ "remote operations\n\n" \
+ "The parameter should be a 16 bit hexadecimal number, the usage " \
+ "of the 0x before the number is optional\n\n"
+
+// If CBP is not used, define its macros
+#ifndef SL_CATALOG_APP_PROV_CBP_PRESENT
+#define CBP_OPTLENGTH 0
+#define CBP_OPTSTRING
+#define CBP_USAGE
+#define CBP_OPTIONS
+#endif // SL_CATALOG_APP_PROV_CBP_PRESENT
+
+// Size of long options struct array size
+#define LONG_OPT_SIZE PROV_OPTLENGTH + REMOTE_PROV_OPTLENGTH + CBP_OPTLENGTH + LAST_OPTION_ELEMENT
+// Optstring argument for getopt.
+#define OPTSTRING NCP_HOST_OPTSTRING PROV_OPTSTRING REMOTE_PROV_OPTSTRING CBP_OPTSTRING "h"
+
+// Usage info.
+#define USAGE "\n%s " NCP_HOST_USAGE PROV_USAGE REMOTE_PROV_USAGE CBP_USAGE " [-h]\n"
+
+// Options info.
+#define OPTIONS \
+ "\nConnection options\n" \
+ NCP_HOST_OPTIONS \
+ PROV_OPTIONS \
+ REMOTE_PROV_OPTIONS \
+ CBP_OPTIONS \
+ " -h Print this help message.\n"
+
+/**************************************************************************//**
+* Remote Provisioner Command Line Application Init
+*
+* @param[in] cmd_opt Command option
+* @param[in] cmd_optarg Command argument
+* @retval SL_STATUS_OK if command started successfully
+* SL_STATUS_NOT_FOUND if command is unknown
+******************************************************************************/
+sl_status_t btmesh_app_remote_prov_init(int cmd_opt, char *cmd_optarg);
+
+/**************************************************************************//**
+* Remote Provisioner Application Process Action
+*
+******************************************************************************/
+void btmesh_app_remote_prov_process_action(void);
+
+/**************************************************************************//**
+ * Bluetooth Mesh stack event handler
+ *
+ * @param[in] evt Event coming from the Bluetooth Mesh stack
+ *****************************************************************************/
+void btmesh_app_remote_prov_on_event(sl_btmesh_msg_t *evt);
+
+/**************************************************************************//**
+ * Get the remote provisioner command and command status
+ *
+ * @param[out] remote_prov_command Current command
+ * @param[out] remote_prov_command_state Current command status
+ *****************************************************************************/
+void btmesh_app_remote_prov_get_command(command_t *remote_prov_command, command_state_t *remote_prov_command_state);
+
+/**************************************************************************//**
+ * Set the remote provisioner command
+ *
+ * @param[in] remote_prov_command New command
+ *****************************************************************************/
+void btmesh_app_remote_prov_set_command(command_t remote_prov_command);
+
+/**************************************************************************//**
+ * Set the remote provisioner command status
+ *
+ * @param[out] remote_prov_command_state New command status
+ *****************************************************************************/
+void btmesh_app_remote_prov_set_command_state(command_state_t remote_prov_command_state);
+
+/**************************************************************************//**
+ * Callback to inform when the command ends
+ *
+ *****************************************************************************/
+void btmesh_app_remote_prov_end_of_cmd(void);
+
+/**************************************************************************//**
+ * Get remote provisioning server address from UI
+ *
+ * @param[out] ui_server_address The variable to fill with server address
+ * @retval false the returned server address is not valid
+ * true if valid server address was returned
+ *****************************************************************************/
+bool btmesh_app_remote_prov_get_server_address(uint16_t *ui_server_address);
+
+/**************************************************************************//**
+ * Get remote provisioning server address and unprovisioned node uuid from UI
+ *
+ * @param[out] remote_prov_server_addr The variable to fill with server address
+ * @param[out] remote_prov_uuid The variable to fill with unprovisioned node uuid
+ * @param[out] remote_prov_oob_capabilities The variable to fill with unprovisioned
+ * node OOB capability bitmask
+ * @retval false the returned provisioning data is not valid
+ * true if valid provisioning data was returned
+ *****************************************************************************/
+bool btmesh_app_remote_prov_get_prov_data(uint16_t *remote_prov_server_addr,
+ uuid_128 *remote_prov_uuid,
+ uint16_t *remote_prov_oob_capabilities);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // BTMESH_APP_REMOTE_PROV_H
diff --git a/app/btmesh/common_host/btmesh_app_remote_prov_ui/btmesh_app_remote_prov_ui.c b/app/btmesh/common_host/btmesh_app_remote_prov_ui/btmesh_app_remote_prov_ui.c
new file mode 100644
index 00000000000..f244ef746e0
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_remote_prov_ui/btmesh_app_remote_prov_ui.c
@@ -0,0 +1,226 @@
+/**************************************************************************//**
+* @file
+* @brief BT Mesh Host Remote Provisioner Example Project - UI mode handler.
+*******************************************************************************
+* # License
+* Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+*******************************************************************************
+*
+* SPDX-License-Identifier: Zlib
+*
+* The licensor of this software is Silicon Laboratories Inc.
+*
+* This software is provided 'as-is', without any express or implied
+* warranty. In no event will the authors be held liable for any damages
+* arising from the use of this software.
+*
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+*
+* 1. The origin of this software must not be misrepresented; you must not
+* claim that you wrote the original software. If you use this software
+* in a product, an acknowledgment in the product documentation would be
+* appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+* misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+*
+******************************************************************************/
+
+// ----------------------------------------------------------------------------
+// Includes
+#include
+
+#include "app_log.h"
+#include "app_assert.h"
+#include "btmesh_app_remote_prov.h"
+#include "btmesh_app_prov_ui.h"
+#include "btmesh_remote_prov.h"
+#include "btmesh_app_remote_prov_ui.h"
+
+// ----------------------------------------------------------------------------
+// Static Variables
+
+/// The remote provisioner server address used throughout remote provisioning process
+static uint16_t remote_address;
+
+// ----------------------------------------------------------------------------
+// Function definitions
+
+sl_status_t btmesh_app_prov_ui_handle_remote_prov_cmd(char cmd_id)
+{
+ sl_status_t sc = SL_STATUS_NOT_FOUND;
+
+ switch (cmd_id) {
+ case REMOTE_SERVERLIST_COMMAND:
+ btmesh_app_remote_prov_set_command(REMOTE_SERVERLIST);
+ btmesh_app_remote_prov_set_command_state(START);
+ sc = SL_STATUS_OK;
+ break;
+ case REMOTE_SCAN_COMMAND:
+ btmesh_app_remote_prov_set_command(REMOTE_SCAN);
+ btmesh_app_remote_prov_set_command_state(START);
+ sc = SL_STATUS_OK;
+ break;
+ case REMOTE_PROVISION_COMMAND:
+ btmesh_app_remote_prov_set_command(REMOTE_PROVISION);
+ btmesh_app_remote_prov_set_command_state(START);
+ sc = SL_STATUS_OK;
+ break;
+ default:
+ break;
+ }
+ return sc;
+}
+
+bool btmesh_app_remote_prov_get_server_address(uint16_t *ui_server_address)
+{
+ bool return_value = false;
+ sl_status_t sc;
+
+ sl_ui_state prov_ui_state = btmesh_app_prov_ui_get_ui_state();
+
+ if (UI_CMD_PROCESSING == prov_ui_state) {
+ app_log("Select a remote provisioning server" APP_LOG_NEW_LINE);
+ app_log("Type either the ID or the address from the list below" APP_LOG_NEW_LINE);
+ app_log_nl();
+ sc = btmesh_remote_prov_list_active_servers();
+ app_log_nl();
+ if (SL_STATUS_EMPTY == sc) {
+ app_log_info("No remote provisioning server nodes available" APP_LOG_NEW_LINE);
+ app_log_info("Check the available active remote provisioning server list first" APP_LOG_NEW_LINE);
+ btmesh_app_remote_prov_end_of_cmd();
+ } else {
+ // push ui in UI_WAITING_FOR_INPUT state and waiting for server address
+ // return value is not important
+ btmesh_app_prov_ui_get_input_buffer(NULL);
+ }
+ } else if (UI_WAITING_FOR_INPUT == prov_ui_state) {
+ char *ui_input_data;
+ return_value = btmesh_app_prov_ui_get_input_buffer(&ui_input_data);
+ if (true == return_value) {
+ size_t len = strlen(ui_input_data);
+ if (ADDRESS_LEN_WITHOUT_PREFIX > len) {
+ uint16_t id = (uint16_t)atoi(ui_input_data);
+ sc = btmesh_remote_prov_get_server_address_by_id(id, ui_server_address);
+ if (SL_STATUS_OK != sc) {
+ app_log_warning("Server with ID: %d not found"APP_LOG_NEW_LINE, id);
+ return_value = false;
+ }
+ } else if (ADDRESS_LEN_WITHOUT_PREFIX == len
+ || ADDRESS_LEN_WITH_PREFIX == len) {
+ app_parse_address(ui_input_data, len, ui_server_address);
+ } else {
+ app_log_error("Invalid input format!" APP_LOG_NEW_LINE);
+ return_value = false;
+ }
+ }
+ }
+
+ return return_value;
+}
+
+bool btmesh_app_remote_prov_get_prov_data(uint16_t *remote_prov_server_addr,
+ uuid_128 *remote_prov_uuid,
+ uint16_t *remote_prov_oob_capabilities)
+{
+ bool return_value = false;
+ sl_status_t sc;
+ uint16_t id;
+ char *ui_input_data = NULL;
+ size_t len;
+
+ sl_ui_state prov_ui_state = btmesh_app_prov_ui_get_ui_state();
+
+ // get server address from UI
+ if (0 == remote_address) {
+ return_value = btmesh_app_remote_prov_get_server_address(&remote_address);
+ if (true == return_value) {
+ if (0 != remote_address) {
+ return_value = false;
+ app_log_info("Selected remote provisioning server address 0x%04x"APP_LOG_NEW_LINE, remote_address);
+ } else {
+ app_log_warning("Invalid remote server address 0x%04x"APP_LOG_NEW_LINE, remote_address);
+ return_value = false;
+ }
+ }
+ } else {
+ if (UI_CMD_PROCESSING == prov_ui_state) {
+ app_log_nl();
+ app_log("Select the unprovisioned node for remote provisioning" APP_LOG_NEW_LINE);
+ app_log("Type either the ID, or UUID from the list below" APP_LOG_NEW_LINE);
+ app_log_nl();
+ sc = btmesh_remote_prov_list_unprovisioned_nodes_by_addr(remote_address);
+ if (SL_STATUS_EMPTY == sc) {
+ app_log_nl();
+ app_log_info("No unprovisioned nodes available" APP_LOG_NEW_LINE);
+ app_log_info("Try remote scanning with server: 0x%04x for unprovisioned nodes first"APP_LOG_NEW_LINE, remote_address);
+ btmesh_app_remote_prov_end_of_cmd();
+ } else {
+ // push ui in UI_WAITING_FOR_INPUT state and waiting for unprovisioned node uuid
+ // return value is not important
+ btmesh_app_prov_ui_get_input_buffer(NULL);
+ }
+ } else if (UI_WAITING_FOR_INPUT == prov_ui_state) {
+ return_value = btmesh_app_prov_ui_get_input_buffer(&ui_input_data);
+ if (true == return_value) {
+ len = strlen(ui_input_data);
+ sc = SL_STATUS_OK;
+ if (ADDRESS_LEN_WITHOUT_PREFIX > len) {
+ // Unprovisioned node by ID
+ id = (uint16_t)atoi(ui_input_data);
+ sc = btmesh_remote_prov_get_unprov_uuid_by_id(id, remote_address, remote_prov_uuid, remote_prov_oob_capabilities);
+ } else if (UUID_LEN_WITHOUT_SEPARATORS == len
+ || UUID_LEN_WITH_SEPARATORS == len) {
+ // Unprovisioned node by UUID
+ app_parse_uuid(ui_input_data, len, remote_prov_uuid);
+ } else {
+ app_log_error("Invalid input format!" APP_LOG_NEW_LINE);
+ return_value = false;
+ }
+ if (SL_STATUS_EMPTY == sc) {
+ app_log_nl();
+ app_log_info("No unprovisioned nodes available" APP_LOG_NEW_LINE);
+ app_log_info("Try scanning for unprovisioned nodes first" APP_LOG_NEW_LINE);
+ return_value = false;
+ btmesh_app_remote_prov_end_of_cmd();
+ } else {
+ *remote_prov_server_addr = remote_address;
+ remote_address = 0;
+ }
+ }
+ }
+ }
+
+ return return_value;
+}
+
+// ----------------------------------------------------------------------------
+// Private function definitions
+
+// ----------------------------------------------------------------------------
+// Callbacks
+
+void btmesh_remote_prov_on_unprovisioned_node_list(uint16_t id,
+ uuid_128 uuid)
+{
+ app_log_info("Unprovisioned node" APP_LOG_NEW_LINE);
+ app_log_info("ID: %d" APP_LOG_NEW_LINE, id);
+ app_log_info("UUID: ");
+ btmesh_app_prov_append_uuid(&uuid);
+ app_log_nl();
+}
+
+void btmesh_app_remote_prov_end_of_cmd(void)
+{
+ btmesh_app_remote_prov_set_command(NONE);
+ btmesh_app_remote_prov_set_command_state(START);
+ remote_address = 0;
+ btmesh_app_prov_end_of_cmd();
+}
+
+void btmesh_remote_prov_on_serverlist(uint16_t id, uint16_t server)
+{
+ app_log_info("%u. Remote provisioning server: 0x%04x"APP_LOG_NEW_LINE, id, server);
+}
diff --git a/app/btmesh/common_host/btmesh_app_remote_prov_ui/btmesh_app_remote_prov_ui.h b/app/btmesh/common_host/btmesh_app_remote_prov_ui/btmesh_app_remote_prov_ui.h
new file mode 100644
index 00000000000..2e961d47370
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_app_remote_prov_ui/btmesh_app_remote_prov_ui.h
@@ -0,0 +1,60 @@
+/**************************************************************************//**
+ * @file
+ * @brief BT Mesh Host Remote Provisioner Example Project.
+ ******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ ******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ *****************************************************************************/
+
+#ifndef BTMESH_APP_REMOTE_PROV_UI_H
+#define BTMESH_APP_REMOTE_PROV_UI_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define REMOTE_SERVERLIST_COMMAND 'm'
+#define REMOTE_SCAN_COMMAND 'o'
+#define REMOTE_PROVISION_COMMAND 'v'
+
+#define PROV_UI_MENU \
+ "Please select a functionality\n\n" \
+ "s - Scan available nodes\n" \
+ "p - Provision a beaconing node\n" \
+ "l - List nodes in the network\n" \
+ "i - Information about a node in the network\n" \
+ "r - Remove node from the network\n" \
+ "k - Refresh the network key and app key\n" \
+ "m - List available remote provisioning servers\n" \
+ "o - Remote scan unprovisioned nodes\n" \
+ "v - Remote provision an unprovisioned node\n" \
+ "e - Reset provisioner node\n" \
+ "q - Exit application\n\n"
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // BTMESH_APP_REMOTE_PROV_UI_H
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf.c b/app/btmesh/common_host/btmesh_conf/btmesh_conf.c
new file mode 100644
index 00000000000..6c9a783ca80
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf.c
@@ -0,0 +1,937 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "btmesh_conf.h"
+#include "btmesh_conf_config.h"
+#include "btmesh_conf_distributor.h"
+#include "btmesh_conf_job.h"
+#include "btmesh_conf_task.h"
+#include "btmesh_db.h"
+
+#include
+#include
+
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_model_specification_defs.h"
+#include "sl_btmesh_model_specification_v1_1_defs.h"
+
+#include "app_log.h"
+#include "app_assert.h"
+
+// The BT Mesh Configurator dynamic memory allocation and deallocation is
+// managed by create and destroy functions. The following rules were established
+// to have consistent, uniform and correct interface and implementation:
+// Create functions:
+// => Req01: Allocation and initialization of objects.
+// => Req02: The return type of the function shall be a pointer type to the
+// created struct. The parameters are object dependent. (~constructor)
+// => Req03: Create shall return a pointer to the allocated and initialized object
+// => Req04: Create shall validate the parameters first and return NULL in case
+// of invalid parameters.
+// => Req05: If the parameters are valid then create shall allocate the dynamic
+// memory for itself.
+// - This can mean multiple dynamic memory allocation calls.
+// - Example: Allocation of struct and allocation of array member of the
+// allocated struct
+// => Req06: Create shall invoke the create function of other classes to
+// initialize aggregated objects.
+// => Req07: If any allocation fails except for the allocation of the object
+// itself then the destroy function shall be called and NULL shall
+// be returned. Every unallocated field shall be NULL when the destroy
+// function is called.
+// - Note: The destroy function shall be able to distinguish the allocated
+// and not allocated pointers. If malloc allocates dynamic memory
+// then the memory of the object is uninitialized so the pointers
+// in the objects are uninitialized as well.
+// => Req08: The attributes of the object shall be initialized after allocation
+// was successful.
+//
+// Destroy function:
+// => Req20: Deallocation and deinitialization of objects.
+// => Req21: Destroy function shall have void return type and it shall have
+// one parameter which is a pointer to the struct which shall be
+// deallocated. This parameter shall be named to self.
+// => Req22: The destroy function shall return immediately if the self
+// parameter is NULL. (same behavior as free stdlib function)
+// => Req23: The memory deallocation of dynamic memory of the object and
+// destroy function calls of aggregated objects shall be executed
+// in reverse order of allocation.
+// => Req24: The destroy function shall support the deallocation of partially
+// created objects.
+// - This is necessary in order to support the call of destroy function
+// inside create function when the allocation fails. (see Req06)
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_internal BT Mesh Configurator Internal
+ * @brief Internal type definitions, variables and functions.
+ * @{
+ ******************************************************************************/
+/// Local log new line shortcut definition
+#define NL APP_LOG_NL
+
+/// BT Mesh configuration server model ID
+#define BTMESH_CONF_CONFIG_SERVER_MODEL_ID 0x0000
+/// BT Mesh configuration client model ID
+#define BTMESH_CONF_CONFIG_CLIENT_MODEL_ID 0x0001
+/// BT Mesh health server model ID
+#define BTMESH_CONF_HEALTH_SERVER_MODEL_ID 0x0002
+/// BT Mesh health client model ID
+#define BTMESH_CONF_HEALTH_CLIENT_MODEL_ID 0x0003
+
+/// BT Mesh Stack BGAPI class ID mask
+#define BTMESH_CONF_CLASS_ID_MASK (0x00FF0000UL)
+/// BT Mesh Stack BGAPI class ID shift
+#define BTMESH_CONF_CLASS_ID_SHIFT (16)
+/// Return BT Mesh Stack BGAPI class ID from the event identifier
+#define BTMESH_CONF_GET_CLASS_ID(event_id) (((event_id) & BTMESH_CONF_CLASS_ID_MASK) \
+ >> BTMESH_CONF_CLASS_ID_SHIFT)
+/// Class ID of BT Mesh Stack BGAPI Configuration Client class
+#define BTMESH_CONF_CLASS_ID BTMESH_CONF_GET_CLASS_ID(sl_btmesh_cmd_config_client_get_dcd_id)
+
+/// BT Mesh Model Information
+typedef struct {
+ const char *name; ///< Name of the model based
+ uint16_t id; ///< Model identifier (BT Mesh specification)
+ bool appkey_binding : 1; ///< Model application key binding support
+ bool subscription : 1; ///< Model subscription support
+ bool publication : 1; ///< Model publication support
+} btmesh_conf_model_info_t;
+
+/***************************************************************************//**
+ * Provides model information belonging to the specified Bluetooth SIG model ID.
+ *
+ * @param[in] sig_model_id Bluetooth SIG model identifier
+ * @returns Pointer to the model information
+ * @retval NULL if the model ID does not exists in @ref model_info_array
+ ******************************************************************************/
+static const btmesh_conf_model_info_t *get_model_info(uint16_t sig_model_id);
+
+/***************************************************************************//**
+ * Submit a configuration job for execution.
+ * If @auto_destroy_on_submit_failure parameter is true and submit operation
+ * fails then the configuration job is deallocated (destroyed).
+ *
+ * @param[in] job Configuration job which aggregates the configuration tasks
+ * @param[in] auto_destroy_on_submit_failure If it is true then the configuration
+ * job is deallocated after the submit operation fails.
+ * @returns Status of the job submit operation.
+ * @retval SL_STATUS_OK If the job is submitted successfully.
+ * @retval SL_STATUS_INVALID_STATE If the BT Mesh configurator is not initialized.
+ ******************************************************************************/
+static sl_status_t submit_job(btmesh_conf_job_t *job,
+ bool auto_destroy_on_submit_failure);
+
+/// BT Mesh model information array which contains the information about
+/// Bluetooth SIG Mesh models.
+/// @warning The model information shall be in ascending order of model ID.
+static const btmesh_conf_model_info_t model_info_array[];
+
+/// Number of elements in model_info_array
+static const uint16_t model_info_count;
+
+/// BT Mesh Configuration Distributor singleton pointer
+static btmesh_conf_distributor_t *btmesh_conf_dist = NULL;
+/// BT Mesh Configurator callback active flag
+static bool btmesh_conf_callback_active = false;
+
+/** @} (end addtogroup btmesh_conf_internal) */
+
+sl_status_t btmesh_conf_init(void)
+{
+ sl_status_t sc = SL_STATUS_OK;
+ for (uint16_t idx = 1; idx < model_info_count; idx++) {
+ // Model information array shall be ordered by model id to use binary search
+ if (model_info_array[idx].id <= model_info_array[idx - 1].id) {
+ sc = SL_STATUS_INVALID_CONFIGURATION;
+ break;
+ }
+ }
+
+ if (SL_STATUS_OK == sc) {
+ btmesh_conf_dist = btmesh_conf_distributor_create(SL_BTMESH_CONF_EXECUTOR_COUNT_CFG_VAL);
+ if (NULL == btmesh_conf_dist) {
+ sc = SL_STATUS_ALLOCATION_FAILED;
+ } else {
+ sc = SL_STATUS_OK;
+ }
+ }
+ return sc;
+}
+
+sl_status_t btmesh_conf_deinit(void)
+{
+ sl_status_t deinit_status;
+ if (false != btmesh_conf_callback_active) {
+ deinit_status = SL_STATUS_INVALID_STATE;
+ } else {
+ btmesh_conf_distributor_destroy(btmesh_conf_dist);
+ btmesh_conf_dist = NULL;
+ deinit_status = SL_STATUS_OK;
+ }
+ return deinit_status;
+}
+
+sl_status_t btmesh_conf_submit_job(btmesh_conf_job_t *job)
+{
+ bool auto_destroy_on_submit_failure = false;
+
+#if 0 != SL_BTMESH_CONF_JOB_AUTO_DESTROY_ON_SUBMIT_FAILURE_CFG_VAL
+ if (false != job->auto_destroy) {
+ auto_destroy_on_submit_failure = true;
+ }
+#endif
+
+ return submit_job(job, auto_destroy_on_submit_failure);
+}
+
+sl_status_t btmesh_conf_dcd_get(uint16_t netkey_index,
+ uint16_t server_address,
+ uint8_t page,
+ btmesh_conf_on_job_notification_t on_job_notification)
+{
+ if (NULL == btmesh_conf_dist) {
+ // BT Mesh Configurator is uninitialized
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ btmesh_conf_task_t *task = btmesh_conf_task_dcd_get_create(page);
+ if (NULL == task) {
+ return SL_STATUS_ALLOCATION_FAILED;
+ }
+
+ btmesh_conf_job_t *job = btmesh_conf_job_create(netkey_index,
+ server_address,
+ task,
+ on_job_notification,
+ BTMESH_CONF_VARG_NULL,
+ true,
+ NULL);
+ if (NULL == job) {
+ task->destroy(task);
+ return SL_STATUS_ALLOCATION_FAILED;
+ }
+
+ // The job is created internally and therefore if the submit fails then it
+ // shall be destroyed internally as well so auto_destroy_on_submit_failure
+ // parameter is set to true.
+ return submit_job(job, true);
+}
+
+sl_status_t btmesh_conf_reset_node(uint16_t netkey_index,
+ uint16_t server_address,
+ btmesh_conf_on_job_notification_t on_job_notification)
+{
+ if (NULL == btmesh_conf_dist) {
+ // BT Mesh Configurator is uninitialized
+ return SL_STATUS_INVALID_STATE;
+ }
+
+ btmesh_conf_task_t *task = btmesh_conf_task_reset_node_create();
+ if (NULL == task) {
+ return SL_STATUS_ALLOCATION_FAILED;
+ }
+
+ btmesh_conf_job_t *job = btmesh_conf_job_create(netkey_index,
+ server_address,
+ task,
+ on_job_notification,
+ BTMESH_CONF_VARG_NULL,
+ true,
+ NULL);
+ if (NULL == job) {
+ task->destroy(task);
+ return SL_STATUS_ALLOCATION_FAILED;
+ }
+
+ // The job is created internally and therefore if the submit fails then it
+ // shall be destroyed internally as well so auto_destroy_on_submit_failure
+ // parameter is set to true.
+ return submit_job(job, true);
+}
+
+void btmesh_conf_on_event(const sl_btmesh_msg_t *evt)
+{
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_prov_initialized_id:
+ {
+ sl_status_t sc;
+ sc = btmesh_conf_init();
+ app_assert_status_f(sc, "Failed to init configurator component." NL);
+
+ sc = sl_btmesh_config_client_set_default_timeout(SL_BTMESH_CONF_REQUEST_TIMEOUT_MS_CFG_VAL,
+ SL_BTMESH_CONF_LPN_REQUEST_TIMEOUT_MS_CFG_VAL);
+ app_assert_status_f(sc, "Failed to set config client default timeout.");
+ break;
+ }
+ }
+
+ if (NULL != btmesh_conf_dist) {
+ // Forward events to the distributor if it exists (initialized)
+ btmesh_conf_distributor_on_event(btmesh_conf_dist, evt);
+ }
+}
+
+void btmesh_conf_step(void)
+{
+ if (NULL != btmesh_conf_dist) {
+ // Execute distributor step function if it exists (initialized)
+ btmesh_conf_distributor_step(btmesh_conf_dist);
+ }
+}
+
+void btmesh_conf_on_job_notification(btmesh_conf_job_t *const job)
+{
+ // If job status notification callback is set then the application is notified
+ // about the changed job status
+ if (NULL != job->on_job_notification) {
+ btmesh_conf_callback_active = true;
+ job->on_job_notification(job);
+ btmesh_conf_callback_active = false;
+ }
+
+ // If the auto destroy is set then the application does not need the config
+ // job instance after the notification so it shall be deallocated
+ if (false != job->auto_destroy) {
+ btmesh_conf_job_destroy(job);
+ }
+}
+
+sl_status_t btmesh_conf_get_sig_model_attributes(uint16_t model_id,
+ btmesh_conf_sig_model_attr_bitmask_t *attributes)
+{
+ if (NULL == attributes) {
+ return SL_STATUS_NULL_POINTER;
+ }
+
+ const btmesh_conf_model_info_t *model_info = get_model_info(model_id);
+ if (NULL == model_info) {
+ return SL_STATUS_NOT_FOUND;
+ }
+
+ *attributes =
+ (model_info->appkey_binding ? BTMESH_CONF_SIG_MODEL_SUPPORTS_APPKEY_BINDING : 0)
+ | (model_info->subscription ? BTMESH_CONF_SIG_MODEL_SUPPORTS_SUBSCRIPTION : 0)
+ | (model_info->publication ? BTMESH_CONF_SIG_MODEL_SUPPORTS_PUBLICATION : 0);
+ return SL_STATUS_OK;
+}
+
+const char *btmesh_conf_sig_model_id_to_string(uint16_t model_id)
+{
+ const btmesh_conf_model_info_t *model_info = get_model_info(model_id);
+ if (NULL == model_info) {
+ // If the model ID is not found then return valid string in order to make
+ // easier to use this function with snprintf like functions
+ return "UnknownSigModel";
+ } else {
+ return model_info->name;
+ }
+}
+
+bool btmesh_conf_is_configuration_event(uint32_t event_id)
+{
+ uint8_t class_id = BTMESH_CONF_GET_CLASS_ID(event_id);
+ return (class_id == BTMESH_CONF_CLASS_ID);
+}
+
+sl_status_t btmesh_conf_get_handle_from_event(const sl_btmesh_msg_t *evt,
+ uint32_t *handle)
+{
+ sl_status_t sc = SL_STATUS_OK;
+ if ((NULL == handle) || (NULL == evt)) {
+ sc = SL_STATUS_NULL_POINTER;
+ } else {
+ uint32_t event_id = SL_BT_MSG_ID(evt->header);
+ switch (event_id) {
+ case sl_btmesh_evt_config_client_request_modified_id:
+ *handle = evt->data.evt_config_client_request_modified.handle;
+ break;
+ case sl_btmesh_evt_config_client_netkey_status_id:
+ *handle = evt->data.evt_config_client_netkey_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_netkey_list_id:
+ *handle = evt->data.evt_config_client_netkey_list.handle;
+ break;
+ case sl_btmesh_evt_config_client_netkey_list_end_id:
+ *handle = evt->data.evt_config_client_netkey_list_end.handle;
+ break;
+ case sl_btmesh_evt_config_client_appkey_status_id:
+ *handle = evt->data.evt_config_client_appkey_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_appkey_list_id:
+ *handle = evt->data.evt_config_client_appkey_list.handle;
+ break;
+ case sl_btmesh_evt_config_client_appkey_list_end_id:
+ *handle = evt->data.evt_config_client_appkey_list_end.handle;
+ break;
+ case sl_btmesh_evt_config_client_binding_status_id:
+ *handle = evt->data.evt_config_client_binding_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_bindings_list_id:
+ *handle = evt->data.evt_config_client_bindings_list.handle;
+ break;
+ case sl_btmesh_evt_config_client_bindings_list_end_id:
+ *handle = evt->data.evt_config_client_bindings_list_end.handle;
+ break;
+ case sl_btmesh_evt_config_client_model_pub_status_id:
+ *handle = evt->data.evt_config_client_model_pub_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_model_sub_status_id:
+ *handle = evt->data.evt_config_client_model_sub_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_subs_list_id:
+ *handle = evt->data.evt_config_client_subs_list.handle;
+ break;
+ case sl_btmesh_evt_config_client_subs_list_end_id:
+ *handle = evt->data.evt_config_client_subs_list_end.handle;
+ break;
+ case sl_btmesh_evt_config_client_heartbeat_pub_status_id:
+ *handle = evt->data.evt_config_client_heartbeat_pub_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_heartbeat_sub_status_id:
+ *handle = evt->data.evt_config_client_heartbeat_sub_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_beacon_status_id:
+ *handle = evt->data.evt_config_client_beacon_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_default_ttl_status_id:
+ *handle = evt->data.evt_config_client_default_ttl_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_gatt_proxy_status_id:
+ *handle = evt->data.evt_config_client_gatt_proxy_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_relay_status_id:
+ *handle = evt->data.evt_config_client_relay_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_network_transmit_status_id:
+ *handle = evt->data.evt_config_client_network_transmit_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_identity_status_id:
+ *handle = evt->data.evt_config_client_identity_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_friend_status_id:
+ *handle = evt->data.evt_config_client_friend_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_key_refresh_phase_status_id:
+ *handle = evt->data.evt_config_client_key_refresh_phase_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_lpn_polltimeout_status_id:
+ *handle = evt->data.evt_config_client_lpn_polltimeout_status.handle;
+ break;
+ case sl_btmesh_evt_config_client_dcd_data_id:
+ *handle = evt->data.evt_config_client_dcd_data.handle;
+ break;
+ case sl_btmesh_evt_config_client_dcd_data_end_id:
+ *handle = evt->data.evt_config_client_dcd_data_end.handle;
+ break;
+ case sl_btmesh_evt_config_client_reset_status_id:
+ *handle = evt->data.evt_config_client_reset_status.handle;
+ break;
+ default:
+ app_assert(false == btmesh_conf_is_configuration_event(event_id),
+ "Unhandled configuration event.");
+ sc = SL_STATUS_NOT_FOUND;
+ break;
+ }
+ }
+ return sc;
+}
+
+bool btmesh_conf_any_severe_log_level(uint8_t start_level)
+{
+ bool enabled = false;
+ for (int16_t level = start_level; 0 <= level; level--) {
+ enabled = enabled || _app_log_check_level(level);
+ }
+ return enabled;
+}
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_internal BT Mesh Configurator Internal
+ * @{
+ ******************************************************************************/
+static sl_status_t submit_job(btmesh_conf_job_t *job,
+ bool auto_destroy_on_submit_failure)
+{
+ sl_status_t sc;
+ if (NULL == btmesh_conf_dist) {
+ // BT Mesh Configurator is uninitialized
+ sc = SL_STATUS_INVALID_STATE;
+ } else {
+ sc = btmesh_conf_distributor_submit_job(btmesh_conf_dist, job);
+
+ if ((SL_STATUS_OK != sc) && (false != auto_destroy_on_submit_failure)) {
+ btmesh_conf_job_destroy(job);
+ }
+ }
+ return sc;
+}
+
+static const btmesh_conf_model_info_t *get_model_info(uint16_t sig_model_id)
+{
+ int32_t start_idx = 0;
+ int32_t end_idx = model_info_count - 1;
+ int32_t middle_idx;
+ // Binary search in the model information array which is ordered by ascending
+ // model identifiers. The ascending order is checked by btmesh_conf_init.
+ while (start_idx <= end_idx) {
+ middle_idx = (start_idx + end_idx) / 2;
+ if (sig_model_id < model_info_array[middle_idx].id) {
+ end_idx = middle_idx - 1;
+ } else if (model_info_array[middle_idx].id < sig_model_id) {
+ start_idx = middle_idx + 1;
+ } else {
+ return &model_info_array[middle_idx];
+ }
+ }
+ return NULL;
+}
+
+// WARNING! The model information shall be in ascending order of model ID.
+static const btmesh_conf_model_info_t model_info_array[] = {
+ {
+ .id = BTMESH_CONF_CONFIG_SERVER_MODEL_ID,
+ .name = "Configuration Server",
+ .appkey_binding = false,
+ .subscription = false,
+ .publication = false,
+ },
+ {
+ .id = BTMESH_CONF_CONFIG_CLIENT_MODEL_ID,
+ .name = "Configuration Client",
+ .appkey_binding = false,
+ .subscription = false,
+ .publication = false,
+ },
+ {
+ .id = BTMESH_CONF_HEALTH_SERVER_MODEL_ID,
+ .name = "Health Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = BTMESH_CONF_HEALTH_CLIENT_MODEL_ID,
+ .name = "Health Client",
+ .appkey_binding = true,
+ .subscription = false,
+ .publication = false,
+ },
+ {
+ .id = MESH_REMOTE_PROV_SERVER_MODEL_ID,
+ .name = "Remote Provisioning Server",
+ .appkey_binding = false,
+ .subscription = false,
+ .publication = false,
+ },
+ {
+ .id = MESH_REMOTE_PROV_CLIENT_MODEL_ID,
+ .name = "Remote Provisioning Client",
+ .appkey_binding = false,
+ .subscription = false,
+ .publication = false,
+ },
+ {
+ .id = MESH_GENERIC_ON_OFF_SERVER_MODEL_ID,
+ .name = "Generic OnOff Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_ON_OFF_CLIENT_MODEL_ID,
+ .name = "Generic OnOff Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_LEVEL_SERVER_MODEL_ID,
+ .name = "Generic Level Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_LEVEL_CLIENT_MODEL_ID,
+ .name = "Generic Level Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_TRANSITION_TIME_SERVER_MODEL_ID,
+ .name = "Generic Default Transition Time Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_TRANSITION_TIME_CLIENT_MODEL_ID,
+ .name = "Generic Default Transition Time Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_POWER_ON_OFF_SERVER_MODEL_ID,
+ .name = "Generic Power OnOff Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_POWER_ON_OFF_SETUP_SERVER_MODEL_ID,
+ .name = "Generic Power OnOff Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_GENERIC_POWER_ON_OFF_CLIENT_MODEL_ID,
+ .name = "Generic Power OnOff Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_POWER_LEVEL_SERVER_MODEL_ID,
+ .name = "Generic Power Level Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_POWER_LEVEL_SETUP_SERVER_MODEL_ID,
+ .name = "Generic Power Level Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_GENERIC_POWER_LEVEL_CLIENT_MODEL_ID,
+ .name = "Generic Power Level Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_BATTERY_SERVER_MODEL_ID,
+ .name = "Generic Battery Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_BATTERY_CLIENT_MODEL_ID,
+ .name = "Generic Battery Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_LOCATION_SERVER_MODEL_ID,
+ .name = "Generic Location Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_LOCATION_SETUP_SERVER_MODEL_ID,
+ .name = "Generic Location Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_GENERIC_LOCATION_CLIENT_MODEL_ID,
+ .name = "Generic Location Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_ADMIN_PROPERTY_SERVER_MODEL_ID,
+ .name = "Generic Admin Property Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_MANUF_PROPERTY_SERVER_MODEL_ID,
+ .name = "Generic Manufacturer Property Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_USER_PROPERTY_SERVER_MODEL_ID,
+ .name = "Generic User Property Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_CLIENT_PROPERTY_SERVER_MODEL_ID,
+ .name = "Generic Client Property Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_GENERIC_PROPERTY_CLIENT_MODEL_ID,
+ .name = "Generic Property Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_SENSOR_SERVER_MODEL_ID,
+ .name = "Sensor Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_SENSOR_SETUP_SERVER_MODEL_ID,
+ .name = "Sensor Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_SENSOR_CLIENT_MODEL_ID,
+ .name = "Sensor Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_TIME_SERVER_MODEL_ID,
+ .name = "Time Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_TIME_SETUP_SERVER_MODEL_ID,
+ .name = "Time Setup Server",
+ .appkey_binding = true,
+ .subscription = false,
+ .publication = false,
+ },
+ {
+ .id = MESH_TIME_CLIENT_MODEL_ID,
+ .name = "Time Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_SCENE_SERVER_MODEL_ID,
+ .name = "Scene Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_SCENE_SETUP_SERVER_MODEL_ID,
+ .name = "Scene Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_SCENE_CLIENT_MODEL_ID,
+ .name = "Scene Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_SCHEDULER_SERVER_MODEL_ID,
+ .name = "Scheduler Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_SCHEDULER_SETUP_SERVER_MODEL_ID,
+ .name = "Scheduler Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_SCHEDULER_CLIENT_MODEL_ID,
+ .name = "Scheduler Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_LIGHTNESS_SERVER_MODEL_ID,
+ .name = "Light Lightness Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_LIGHTNESS_SETUP_SERVER_MODEL_ID,
+ .name = "Light Lightness Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_LIGHTING_LIGHTNESS_CLIENT_MODEL_ID,
+ .name = "Light Lightness Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_CTL_SERVER_MODEL_ID,
+ .name = "Light CTL Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_CTL_SETUP_SERVER_MODEL_ID,
+ .name = "Light CTL Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_LIGHTING_CTL_CLIENT_MODEL_ID,
+ .name = "Light CTL Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_CTL_TEMPERATURE_SERVER_MODEL_ID,
+ .name = "Light CTL Temperature Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_HSL_SERVER_MODEL_ID,
+ .name = "Light HSL Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_HSL_SETUP_SERVER_MODEL_ID,
+ .name = "Light HSL Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_LIGHTING_HSL_CLIENT_MODEL_ID,
+ .name = "Light HSL Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_HSL_HUE_SERVER_MODEL_ID,
+ .name = "Light HSL Hue Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_HSL_SATURATION_SERVER_MODEL_ID,
+ .name = "Light HSL Saturation Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_XYL_SERVER_MODEL_ID,
+ .name = "Light xyL Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LIGHTING_XYL_SETUP_SERVER_MODEL_ID,
+ .name = "Light xyL Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = false,
+ },
+ {
+ .id = MESH_LIGHTING_XYL_CLIENT_MODEL_ID,
+ .name = "Light xyL Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LC_SERVER_MODEL_ID,
+ .name = "Light LC Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LC_SETUP_SERVER_MODEL_ID,
+ .name = "Light LC Setup Server",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ },
+ {
+ .id = MESH_LC_CLIENT_MODEL_ID,
+ .name = "Light LC Client",
+ .appkey_binding = true,
+ .subscription = true,
+ .publication = true,
+ }
+};
+
+static const uint16_t model_info_count = sizeof(model_info_array)
+ / sizeof(model_info_array[0]);
+
+/** @} (end addtogroup btmesh_conf_internal) */
+/** @} (end addtogroup btmesh_conf) */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf.h b/app/btmesh/common_host/btmesh_conf/btmesh_conf.h
new file mode 100644
index 00000000000..0b6d59d6866
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf.h
@@ -0,0 +1,317 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef BTMESH_CONF_H
+#define BTMESH_CONF_H
+
+#include "btmesh_conf_types.h"
+#include "btmesh_conf_job.h"
+#include "btmesh_conf_task.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @brief Robust execution of configuration jobs over BT Mesh Stack.
+ *
+ * BT Mesh configuration jobs can be created from one or more configuration tasks.
+ * In order to create configuration jobs and tasks the following header files
+ * shall be checked:
+ * - BT Mesh Configuration Job interface can be found in btmesh_conf_job.h
+ * header file (@ref btmesh_conf_job group).
+ * - BT Mesh Configuration Task interface can be found in btmesh_conf_task.h
+ * header file (@ref btmesh_conf_task group).
+ *
+ * The created jobs can be submitted for execution to BT Mesh Configurator by
+ * calling the @ref btmesh_conf_submit_job.
+ *
+ * If the application provides a callback (@ref btmesh_conf_on_job_notification_t)
+ * when it creates the configuration job then the application is notified about
+ * changed job status.
+ * @n The btmesh_conf_job_t::result can be checked in the notification callback
+ * to figure out if the configuration job was successful or not.
+ * The allocation and deallocation and ownership of configuration jobs are
+ * described in @ref btmesh_conf_submit_job.
+ *
+ * There are some common configuration use cases when the execution of job with
+ * a single configuration task is necessary. Some utility functions are provided
+ * in order to simplify the submission of these common configuration jobs:
+ * - @ref btmesh_conf_dcd_get
+ * - @ref btmesh_conf_reset_node
+ *
+ * These utility functions create configuration jobs and tasks and submit them
+ * with @ref btmesh_conf_submit_job function the same way as the application
+ * would do. The finished configuration job which is created by utility function
+ * is deallocated automatically.
+ * @{
+ ******************************************************************************/
+
+/// Information about the supported configuration operations on a model
+typedef enum {
+ /// No attribute flags
+ BTMESH_CONF_SIG_MODEL_ATTRIBUTES_NONE = 0,
+ /// Subscription supported
+ BTMESH_CONF_SIG_MODEL_SUPPORTS_SUBSCRIPTION = (1 << 0),
+ /// Publication supported
+ BTMESH_CONF_SIG_MODEL_SUPPORTS_PUBLICATION = (1 << 1),
+ /// Application key binding supported
+ BTMESH_CONF_SIG_MODEL_SUPPORTS_APPKEY_BINDING = (1 << 2)
+} btmesh_conf_sig_model_attr_bitmask_t;
+
+/***************************************************************************//**
+ * Initialize the BT Mesh Configurator component
+ *
+ * @returns Status of the initialization.
+ * @retval SL_STATUS_OK If the initialization is successful.
+ * @retval SL_STATUS_ALLOCATION_FAILED If the dynamic memory allocation of
+ * internal data structures is failed.
+ * @retval SL_STATUS_INVALID_CONFIGURATION If the configured model information
+ * is not valid. (array elems are not arranged in ascending order by model ID)
+ *
+ * The initialization is performed automatically when the BT Mesh Stack triggers
+ * the sl_btmesh_evt_prov_initialized_id event. The application code shall call
+ * this function if it deinitialized the configurator component beforehand
+ * (@ref btmesh_conf_deinit).
+ ******************************************************************************/
+sl_status_t btmesh_conf_init(void);
+
+/***************************************************************************//**
+ * Deinitialize the BT Mesh Configurator component
+ *
+ * @returns Status of the deinitialization.
+ * @retval SL_STATUS_OK If the deinitialization is successful.
+ * @retval SL_STATUS_INVALID_STATE If the deinitialization can't be started
+ * because it is initiated from @ref btmesh_conf_job_t::on_job_notification
+ * callback.
+ *
+ * Deallocates the internal data structures and stops the processing of BT Mesh
+ * stack events. Deinitialization shall not be called from
+ * @ref btmesh_conf_job_t::on_job_notification callback.
+ *
+ ******************************************************************************/
+sl_status_t btmesh_conf_deinit(void);
+
+/***************************************************************************//**
+ * Submit a configuration job for execution
+ *
+ * @param[in] job Configuration job which aggregates the configuration tasks
+ * @returns Status of the job submission.
+ * @retval SL_STATUS_OK If the job is submitted successfully.
+ * @retval SL_STATUS_INVALID_STATE If the BT Mesh configurator is not initialized.
+ *
+ * Each configuration job aggregates configuration tasks which targets the same
+ * remote node (configuration server model).
+ *
+ * If a configuration job is submitted to the BT Mesh Configurator then it is
+ * added to the end of a wait queue. Therefore it is not guaranteed that the
+ * execution a job is started immediately.
+ * Configuration jobs with the same server addresses are executed in the order
+ * of submission however jobs with different server addresses might be executed
+ * in parallel.
+ *
+ * If the submit operation is successful then the BT Mesh Configurator takes
+ * over the ownership of the job and consequently the application is no longer
+ * allowed to modify the submitted job or its tasks.
+ * If the submit operation fails and @ref btmesh_conf_job_t::auto_destroy is set
+ * and @ref SL_BTMESH_CONF_JOB_AUTO_DESTROY_ON_SUBMIT_FAILURE_CFG_VAL is turned on then the
+ * job is deallocated automatically in the submit call and consequently the job
+ * shall not be referenced when the submit returns with failure.
+ *
+ * If task execution path ends in the configuration job then the job ends as
+ * well and @ref btmesh_conf_job_t::on_job_notification callback is called with
+ * the result. The btmesh_conf_job_t::result structure member contains the
+ * result of the job.
+ * If the @ref btmesh_conf_job_t::auto_destroy is set to true then the job and
+ * its tasks are deallocated automatically after the callback returns.
+ *
+ * @warning If the auto destroy feature is used then the job shall not be
+ * referenced after the callback is executed.
+ *
+ * @note Te BT Mesh Stack does not allow the parallel execution of two
+ * configuration requests with the same server address (primary element).
+ * The limitation is added to the BT Mesh Stack because the configuration
+ * messages don't contain any transaction identifier in accordance with BT
+ * Mesh Profile specification so the status messages could not be differentiated.
+ *
+ ******************************************************************************/
+sl_status_t btmesh_conf_submit_job(btmesh_conf_job_t *job);
+
+/***************************************************************************//**
+ * Submit a configuration job with a DCD get task
+ *
+ * @param[in] netkey_index Network key used to encrypt request on network layer
+ * @param[in] server_address Destination node primary element address
+ * @param[in] page Composition data page to query
+ * @param[in] on_job_notification Callback to notify the app about job status
+ * @returns Status of the DCD get request.
+ * @retval SL_STATUS_OK If the job is submitted successfully.
+ * @retval SL_STATUS_INVALID_STATE If the BT Mesh configurator is not initialized.
+ * @retval SL_STATUS_ALLOCATION_FAILED If allocation of job or task is failed.
+ *
+ * Configuration job with DCD get task is created automatically and submitted
+ * for execution. The allocation and deallocation of task and job is handled
+ * automatically in this function.
+ *
+ * @note It is a quite common usecase to get the DCD first and based on the
+ * content of the DCD further configuration tasks are defined and executed.
+ * @n This utility function makes it easier to get the DCD however it is
+ * possible to query the DCD with the following (basic) API calls as well:
+ * - @ref btmesh_conf_task_dcd_get_create
+ * - @ref btmesh_conf_job_create
+ * - @ref btmesh_conf_submit_job
+ ******************************************************************************/
+sl_status_t btmesh_conf_dcd_get(uint16_t netkey_index,
+ uint16_t server_address,
+ uint8_t page,
+ btmesh_conf_on_job_notification_t on_job_notification);
+
+/***************************************************************************//**
+ * Submit a configuration job with a reset node task
+ *
+ * @param[in] netkey_index Network key used to encrypt request on network layer
+ * @param[in] server_address Destination node primary element address
+ * @param[in] on_job_notification Callback to notify the app about job status
+ * @returns Status of the reset node request.
+ * @retval SL_STATUS_OK If the job is submitted successfully.
+ * @retval SL_STATUS_INVALID_STATE If the BT Mesh configurator is not initialized.
+ * @retval SL_STATUS_ALLOCATION_FAILED If allocation of job or task is failed.
+ *
+ * Configuration job with reset node task is created automatically and submitted
+ * for execution. The allocation and deallocation of task and job is handled
+ * automatically in this function.
+ *
+ * @note This utility function makes it easier to reset a node however it is
+ * possible to reset a node with the following (basic) API calls as well:
+ * - @ref btmesh_conf_task_reset_node_create
+ * - @ref btmesh_conf_job_create
+ * - @ref btmesh_conf_submit_job
+ ******************************************************************************/
+sl_status_t btmesh_conf_reset_node(uint16_t netkey_index,
+ uint16_t server_address,
+ btmesh_conf_on_job_notification_t on_job_notification);
+
+/***************************************************************************//**
+ * BT Mesh Configurator event handler
+ *
+ * @param[in] evt BT Mesh Stack event
+ *
+ * The BT Mesh Configurator event handler shall be called with event provided
+ * sl_btmesh_pop_event or sl_btmesh_wait_event BT Mesh Stack API functions.
+ ******************************************************************************/
+void btmesh_conf_on_event(const sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * Process one step of BT Mesh Configurator
+ *
+ * The step API shall be called periodically from the main loop of the application.
+ ******************************************************************************/
+void btmesh_conf_step(void);
+
+/***************************************************************************//**
+ * Job status notification callback for lower layers
+ *
+ * @param[in] job Reference of the job with changed status
+ *
+ * This callback is called when the status of the job changes because its
+ * execution is ended. The result of the job is provided in the
+ * @ref btmesh_conf_job_t::result structure member.
+ * This callback calls btmesh_conf_job_t::on_job_notification with the @p job
+ * to notify the application. If btmesh_conf_job_t::on_job_notification is NULL
+ * then the application is not notified.
+ ******************************************************************************/
+void btmesh_conf_on_job_notification(btmesh_conf_job_t *const job);
+
+/***************************************************************************//**
+ * Provides the handle value from Configuration Client events of BT Mesh Stack
+ *
+ * @param[in] evt BT Mesh Stack event
+ * @param[out] handle Handle from configuration client event is stored here
+ * @returns Status of the handle retrieval from event.
+ * @retval SL_STATUS_OK If config client handle is retrieved successfully.
+ * @retval SL_STATUS_NOT_FOUND If the given event is not a config client event.
+ * @retval SL_STATUS_NULL_POINTER If any of its parameter is a NULL pointer.
+ ******************************************************************************/
+sl_status_t btmesh_conf_get_handle_from_event(const sl_btmesh_msg_t *evt,
+ uint32_t *handle);
+
+/***************************************************************************//**
+ * Provide the model attributes of the given BT Mesh SIG model ID
+ *
+ * @param[in] model_id Model ID for the BT Mesh SIG model
+ * @param[out] attributes Attributes of the model is stored here
+ * @returns Status of the get model attributes.
+ * @retval SL_STATUS_OK If model attributes are provided successfully.
+ * @retval SL_STATUS_NOT_FOUND If the model ID is unknown.
+ * @retval SL_STATUS_NULL_POINTER If NULL pointer is given as @ref attributes.
+ ******************************************************************************/
+sl_status_t btmesh_conf_get_sig_model_attributes(uint16_t model_id,
+ btmesh_conf_sig_model_attr_bitmask_t *attributes);
+
+/***************************************************************************//**
+ * Return string representation of the given BT Mesh SIG model ID
+ *
+ * @param[in] model_id Model ID for the BT Mesh SIG model
+ * @returns String representation of BT Mesh SIG model ID (model name)
+ * @retval "UnknownSigModel" If the model ID is unknown.
+ *
+ * @note This function always returns a valid string which makes it easier to
+ * use this function with snprintf like functions because those expects valid
+ * (non-NULL) for %s format specifier.
+ ******************************************************************************/
+const char *btmesh_conf_sig_model_id_to_string(uint16_t model_id);
+
+/***************************************************************************//**
+ * Check if a given BT Mesh Stack event is a configuration client event
+ *
+ * @param[in] event_id Identifier of the given BT Mesh Stack event
+ * @retval true If the event is a configuration client event.
+ * @retval false If the event is NOT a configuration client event.
+ ******************************************************************************/
+bool btmesh_conf_is_configuration_event(uint32_t event_id);
+
+/***************************************************************************//**
+ * Check if a given log level or other more severe log levels are enabled
+ *
+ * @param[in] start_level Least severe log level checked
+ * @retval true If given log level or other more severe log levels are enabled.
+ * @retval false If no severe log levels are enabled.
+ ******************************************************************************/
+bool btmesh_conf_any_severe_log_level(uint8_t start_level);
+
+/** @} (end addtogroup btmesh_conf) */
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* BTMESH_CONF_H */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_distributor.c b/app/btmesh/common_host/btmesh_conf/btmesh_conf_distributor.c
new file mode 100644
index 00000000000..86f8f4b5d07
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_distributor.c
@@ -0,0 +1,345 @@
+/***************************************************************************//**
+ * @file
+ * @brief BT Mesh Configurator Component - Job Distributor
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include
+
+#include "btmesh_conf.h"
+#include "btmesh_conf_distributor.h"
+#include "btmesh_conf_executor.h"
+#include "sl_slist.h"
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_distributor BT Mesh Configuration Distributor
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_distributor_internal \
+ * BT Mesh Configuration Distributor Internal
+ * @{
+ ******************************************************************************/
+/// BT Mesh Configuration Distributor schedule type
+typedef enum {
+ /// Schedule is executed immediately
+ BTMESH_CONF_SCHEDULE_NORMAL,
+ /// Schedule is deferred until the next @ref btmesh_conf_step
+ BTMESH_CONF_SCHEDULE_DEFERRED
+} btmesh_conf_schedule_t;
+
+/***************************************************************************//**
+ * Schedule one or more waiting @ref btmesh_conf_job for execution on one or
+ * more idle @ref btmesh_conf_executor.
+ *
+ * @param[in] self Pointer to the configuration distributor instance
+ * @param[in] schedule_type Normal or deferred scheduling
+ *
+ * If deferred scheduling type is specified then the scheduling is deferred
+ * until the next @ref btmesh_conf_step.
+ *
+ * The scheduling algorithm does not assign a configuration job to an idle
+ * executor if another configuration job is in progress with the same server
+ * address (same target node) so these jobs are skipped because only one BT Mesh
+ * Stack configuration request can be active with the same server address at any
+ * given point of time. Therefore, configuration jobs with different server
+ * addresses can be executed in parallel and configuration jobs with same server
+ * address are executed in the order of @ref btmesh_conf_distributor_submit_job
+ * function calls.
+ ******************************************************************************/
+static void btmesh_conf_distributor_schedule(btmesh_conf_distributor_t *const self,
+ btmesh_conf_schedule_t schedule_type);
+
+/***************************************************************************//**
+ * Start a configuration job on an idle configuration executor
+ *
+ * @param[in] self Pointer to the configuration distributor instance
+ * @param[in] job Configuration job which aggregates the configuration tasks
+ * @returns
+ * @retval SL_STATUS_OK Job is started on an executor
+ * @retval SL_STATUS_NO_MORE_RESOURCE All executors are busy with job execution
+ * @retval SL_STATUS_IN_PROGRESS Another job is ongoing with the same target node
+ * @retval SL_STATUS_FAIL Job start failed unexpectedly
+ ******************************************************************************/
+static sl_status_t btmesh_conf_distributor_start_job(btmesh_conf_distributor_t *const self,
+ btmesh_conf_job_t *job);
+
+/** @} (end addtogroup btmesh_conf_distributor_internal) */
+
+btmesh_conf_distributor_t *btmesh_conf_distributor_create(uint16_t executor_count)
+{
+ btmesh_conf_distributor_t* self;
+ uint16_t idx;
+
+ // Parameter checks: executor count shall be greater than zero
+ if (0 == executor_count) {
+ return NULL;
+ }
+
+ self = malloc(sizeof(btmesh_conf_distributor_t));
+
+ // If the allocation fails then NULL shall be returned
+ if (NULL == self) {
+ return NULL;
+ }
+
+ // Initialize configuration distributor struct members
+ self->executor_count = executor_count;
+ sl_slist_init(&self->waiting_jobs);
+ self->deferred_schedule_request = false;
+
+ // Allocate an array of pointers to configuration executors.
+ // This makes it possible to allocate configuration executors with
+ // btmesh_conf_executor_create function.
+ // The executor pointer array is allocated with calloc to initialize them to
+ // NULL to support the deallocation of partially created distributor with
+ // btmesh_conf_distributor_destroy function if this create function fails.
+ self->executors = (btmesh_conf_executor_t **)
+ calloc(self->executor_count, sizeof(btmesh_conf_executor_t *));
+
+ if (NULL == self->executors) {
+ // If the allocation of executor pointer array fails then the distributor
+ // shall destroy itself.
+ btmesh_conf_distributor_destroy(self);
+ return NULL;
+ }
+
+ for (idx = 0; idx < self->executor_count; idx++) {
+ // Create configuration executor and store the pointer in the array
+ self->executors[idx] = btmesh_conf_executor_create(idx, self);
+ if (NULL == self->executors[idx]) {
+ break;
+ }
+ }
+
+ // If at least one executor allocation fails then object creation shall fail
+ if (idx != self->executor_count) {
+ // If the allocation of any executor fails then the distributor shall
+ // destroy itself.
+ btmesh_conf_distributor_destroy(self);
+ return NULL;
+ }
+
+ return self;
+}
+
+void btmesh_conf_distributor_destroy(btmesh_conf_distributor_t* self)
+{
+ if (NULL == self) {
+ return;
+ }
+
+ // Destroy waiting jobs
+ while (NULL != self->waiting_jobs) {
+ btmesh_conf_job_t *job = SL_SLIST_ENTRY(sl_slist_pop(&self->waiting_jobs),
+ btmesh_conf_job_t,
+ list_elem);
+ btmesh_conf_job_destroy(job);
+ }
+
+ if (NULL != self->executors) {
+ for (uint16_t idx = 0; idx < self->executor_count; idx++) {
+ // All destroy interfaces shall handle null pointers and it is guaranteed
+ // by btmesh_conf_distributor_create that all non-allocated pointers in
+ // the array are set to NULL.
+ btmesh_conf_executor_destroy(self->executors[idx]);
+ }
+ // Deallocate the array of pointers
+ free(self->executors);
+ }
+ // Deallocate the configuration distributor instance itself
+ free(self);
+}
+
+sl_status_t btmesh_conf_distributor_submit_job(btmesh_conf_distributor_t *const self,
+ btmesh_conf_job_t* job)
+{
+ sl_status_t submit_job_status = SL_STATUS_OK;
+
+ if ((NULL == job) || (NULL == job->task_tree)) {
+ submit_job_status = SL_STATUS_NULL_POINTER;
+ } else if (BTMESH_CONF_JOB_RESULT_UNKNOWN != job->result) {
+ // The job was already executed
+ submit_job_status = SL_STATUS_INVALID_PARAMETER;
+ } else {
+ // Add configuration job to the wait queue
+ sl_slist_push_back(&self->waiting_jobs,
+ &job->list_elem);
+ btmesh_conf_distributor_schedule(self, BTMESH_CONF_SCHEDULE_DEFERRED);
+ }
+ return submit_job_status;
+}
+
+void btmesh_conf_distributor_on_job_notification(btmesh_conf_distributor_t *const self,
+ btmesh_conf_executor_t *const executor,
+ btmesh_conf_job_t *const job)
+{
+ // Notify the upper layer about the changed job status
+ btmesh_conf_on_job_notification(job);
+
+ // The configuration executor has just become idle so schedule is called to
+ // start the execution of a waiting job
+ btmesh_conf_distributor_schedule(self, BTMESH_CONF_SCHEDULE_NORMAL);
+}
+
+void btmesh_conf_distributor_on_event(btmesh_conf_distributor_t *const self,
+ const sl_btmesh_msg_t *evt)
+{
+ // Forward BT Mesh stack event to configuration executors
+ for (uint16_t idx = 0; idx < self->executor_count; idx++) {
+ btmesh_conf_executor_on_event(self->executors[idx], evt);
+ }
+}
+
+void btmesh_conf_distributor_step(btmesh_conf_distributor_t *const self)
+{
+ // Deferred scheduling request is executed in the cyclic step function
+ if (false != self->deferred_schedule_request) {
+ btmesh_conf_distributor_schedule(self, BTMESH_CONF_SCHEDULE_NORMAL);
+ }
+}
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_distributor_internal \
+ * BT Mesh Configuration Distributor Internal
+ * @{
+ ******************************************************************************/
+static sl_status_t btmesh_conf_distributor_start_job(btmesh_conf_distributor_t *const self,
+ btmesh_conf_job_t *job)
+{
+ btmesh_conf_executor_t *idle_executor = NULL;
+ sl_status_t sc = SL_STATUS_OK;
+
+ for (uint16_t exec_idx = 0; exec_idx < self->executor_count; exec_idx++) {
+ btmesh_conf_executor_status_t executor_status;
+
+ // Note: self->executors is an array of pointers to struct therefore the
+ // address of (&) operator should not be used here
+ btmesh_conf_executor_get_status(self->executors[exec_idx], &executor_status);
+
+ if ((BTMESH_CONF_EXEC_STATE_IDLE == executor_status.state)
+ && (NULL == idle_executor)) {
+ // First idle configuration executor is found
+ idle_executor = self->executors[exec_idx];
+ } else {
+ if ((executor_status.enc_netkey_index == job->enc_netkey_index)
+ && (executor_status.server_address == job->server_address)) {
+ // Only one configuration job with the same target node can be active at
+ // any given point of time however others can be in waiting state.
+ sc = SL_STATUS_IN_PROGRESS;
+ break;
+ }
+ }
+ }
+
+ if (SL_STATUS_OK == sc) {
+ if (NULL == idle_executor) {
+ // All executors are busy with another job
+ sc = SL_STATUS_NO_MORE_RESOURCE;
+ } else {
+ sc = btmesh_conf_executor_start_job(idle_executor, job);
+ }
+ }
+ return sc;
+}
+
+static void btmesh_conf_distributor_schedule(btmesh_conf_distributor_t *const self,
+ btmesh_conf_schedule_t schedule_type)
+{
+ if (BTMESH_CONF_SCHEDULE_DEFERRED == schedule_type) {
+ self->deferred_schedule_request = true;
+ } else {
+ sl_status_t start_job_status;
+ sl_slist_node_t **next_ptr_of_prev_list_elem = &self->waiting_jobs;
+
+ // Deferred schedule request is accepted when normal scheduling is performed
+ self->deferred_schedule_request = false;
+
+ // The first job in the waiting jobs queue is the one which is referenced
+ // by the waiting_jobs pointer (linked list head)
+ btmesh_conf_job_t *job = SL_SLIST_ENTRY(self->waiting_jobs,
+ btmesh_conf_job_t,
+ list_elem);
+ while (NULL != job) {
+ // If a job is removed from the wait queue (linked list) with sl_slist_pop
+ // then it is no longer possible to retrieve the next job because
+ // sl_slist_pop sets the next pointer of the job to NULL and consequently
+ // the next job is calculated at the beginning of each iteration.
+ btmesh_conf_job_t *next_job = SL_SLIST_ENTRY(job->list_elem.node,
+ btmesh_conf_job_t,
+ list_elem);
+
+ start_job_status = btmesh_conf_distributor_start_job(self, job);
+
+ if (SL_STATUS_OK == start_job_status) {
+ // In order to remove a job from the linked list the previous element
+ // of the linked list (or the head) shall be stored because this way the
+ // current job can be removed by sl_slist_pop which assigns simply the
+ // pointer of the previous element to the next element of linked list.
+ // Note: In case of sl_slist the elements and the head have the same type
+ // which makes the implementation more simple.
+ // The prev_list_elem is not modified because it remains the same
+ // due to the removal of the current element.
+ sl_slist_pop(next_ptr_of_prev_list_elem);
+ } else if (SL_STATUS_NO_MORE_RESOURCE == start_job_status) {
+ break; // All executors are busy with configuration jobs so stop scheduling
+ } else if (SL_STATUS_IN_PROGRESS == start_job_status) {
+ // Only one configuration job with the same target node can be active at
+ // any given point of time however others can be in waiting state so the
+ // iteration shall be continued.
+ next_ptr_of_prev_list_elem = &job->list_elem.node;
+ } else {
+ // The whole job is failed (all requests in job have failed in sequence)
+ // so the job shall be removed from the queue and the notification shall
+ // be triggered to notify the higher layers about the failure.
+ // The notification is not triggered by the executor because it was not
+ // possible to start the job at all consequently the ownership transfer
+ // to the specific executor is not completed because the job is still in
+ // the wait queue.
+ // Note: In order to remove the job from the queue the previous element
+ // of the linked list is stored. Therefore the job is removed with
+ // a simple pop operation from the list. (O(1) operation)
+ // The previous list element is not modified because it remains
+ // the same due to the removal of the current element.
+ sl_slist_pop(next_ptr_of_prev_list_elem);
+ btmesh_conf_on_job_notification(job);
+ }
+ // The iteration is continued with the next job in the wait queue
+ job = next_job;
+ }
+ }
+}
+
+/** @} (end addtogroup btmesh_conf_distributor_internal) */
+/** @} (end addtogroup btmesh_conf_distributor) */
+/** @} (end addtogroup btmesh_conf) */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_distributor.h b/app/btmesh/common_host/btmesh_conf/btmesh_conf_distributor.h
new file mode 100644
index 00000000000..e7cfbbd537e
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_distributor.h
@@ -0,0 +1,192 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component - Job Distributor
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef BTMESH_CONF_DISTRIBUTOR_H
+#define BTMESH_CONF_DISTRIBUTOR_H
+
+#include "sl_slist.h"
+#include "btmesh_conf_types.h"
+#include "btmesh_conf_executor.h"
+#include "btmesh_conf_job.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_distributor BT Mesh Configuration Distributor
+ * @brief BT Mesh Configuration Distributor supports the parallel execution of
+ * multiple configuration jobs by distributing the jobs between executors.
+ *
+ * BT Mesh Configuration Distributor instance is created by calling the
+ * @ref btmesh_conf_distributor_create function.
+ * The specified number (@p executor_count) of @ref btmesh_conf_executor instances
+ * are created which are assigned to the created distributor instance (composition).
+ *
+ * BT Mesh Configuration jobs can be submitted to configuration distributor
+ * with @ref btmesh_conf_distributor_submit_job function which stores the job in
+ * a wait queue.
+ *
+ * The configuration jobs are assigned to executors from the wait queue during
+ * distributor scheduling process. The BT Mesh Stack does not support parallel
+ * execution of two configuration job with the same server address.
+ * Therefore, the scheduling process guarantees that the configuration jobs with
+ * the same server addresses are executed one-by-one in the order of submission
+ * however jobs with different server addresses might be executed in parallel.
+ *
+ * The configuration executors report the job status through
+ * @ref btmesh_conf_distributor_on_job_notification function.
+ *
+ * @note The BT Mesh Stack does not allow the parallel execution of two
+ * configuration requests with the same server address (primary element).
+ * The limitation is added to the BT Mesh Stack because the configuration
+ * messages don't contain any transaction identifier in accordance with BT Mesh
+ * Profile specification so the status messages could not be differentiated.
+ *
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @brief BT Mesh Configuration Distributor supports the parallel execution of
+ * multiple configuration jobs by distributing the jobs between executors.
+ ******************************************************************************/
+struct btmesh_conf_distributor_t {
+ /// List of waiting jobs
+ sl_slist_node_t *waiting_jobs;
+ /// Number of @ref btmesh_conf_executor which belongs to this distributor.
+ uint16_t executor_count;
+ /// @brief Array of @ref btmesh_conf_executor references.
+ /// @n Array of pointers are used in order to make it possible to allocate the
+ /// executor instances by calling the @ref btmesh_conf_executor_create function.
+ btmesh_conf_executor_t **executors;
+ /// @brief Deferred schedule request flag is set when new configuration jobs
+ /// are submitted in order to indicate the @ref btmesh_conf_distributor_step
+ /// shall schedule the waiting jobs.
+ bool deferred_schedule_request;
+};
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Distributor instance with the specified number
+ * of configuration executors.
+ *
+ * @param[in] executor_count Number of configuration executors which shall be
+ * created and added to this distributor instance.
+ * @returns Created configuration distributor.
+ * @retval NULL If the configuration distributor creation fails.
+ ******************************************************************************/
+btmesh_conf_distributor_t *btmesh_conf_distributor_create(uint16_t executor_count);
+
+/***************************************************************************//**
+ * Deallocate the BT Mesh Configuration Distributor instance
+ *
+ * @param[in] self Pointer to configuration distributor which shall be destroyed
+ ******************************************************************************/
+void btmesh_conf_distributor_destroy(btmesh_conf_distributor_t *const self);
+
+/***************************************************************************//**
+ * Submit a configuration job for execution
+ *
+ * @param[in] self Pointer to the configuration distributor instance
+ * @param[in] job Configuration job which aggregates the configuration tasks
+ * @returns Status of the job submission.
+ * @retval SL_STATUS_OK If the job is submitted successfully.
+ *
+ * BT Mesh Configuration Distributor appends the submitted configuration job
+ * to the wait queue and requests a deferred scheduling in order to assign the
+ * job to an idle @ref btmesh_conf_executor. If all executors are busy then
+ * the configuration job remains in the wait queue until an executor transitions
+ * to idle state and there is no previously submitted configuration jobs which
+ * can be started.
+ *
+ * It is important to note that a configuration job might be executed sooner
+ * than another configuration job which was submitted earlier if the latter
+ * configurator job can't be started because one executor runs a third
+ * configuration job with the same server address.
+ *
+ * If the execution of a configuration job is finished then the configuration
+ * executor calls the @ref btmesh_conf_distributor_on_job_notification to notify
+ * the distributor.
+ ******************************************************************************/
+sl_status_t btmesh_conf_distributor_submit_job(btmesh_conf_distributor_t *const self,
+ btmesh_conf_job_t *job);
+
+/***************************************************************************//**
+ * BT Mesh Configuration Distributor job status notification callback for
+ * @ref btmesh_conf_executor
+ *
+ * @param[in] self Pointer to the configuration distributor instance
+ * @param[in] executor Pointer to the executor which ran the configuration job
+ * @param[in] job Pointer to the job with changed status
+ *
+ * This callback is called when the status of the job changes because its
+ * execution is ended.
+ ******************************************************************************/
+void btmesh_conf_distributor_on_job_notification(btmesh_conf_distributor_t *const self,
+ btmesh_conf_executor_t *const executor,
+ btmesh_conf_job_t *const job);
+
+/***************************************************************************//**
+ * Event handler of BT Mesh Configuration Distributor instance
+ *
+ * @param[in] self Pointer to the configuration distributor instance
+ * @param[in] evt BT Mesh Stack event
+ *
+ * BT Mesh Configuration Distributor forwards the BT Mesh Stack events to all
+ * of its @ref btmesh_conf_executor instances by calling the
+ * @ref btmesh_conf_executor_on_event function with each configuration executor
+ * instance.
+ ******************************************************************************/
+void btmesh_conf_distributor_on_event(btmesh_conf_distributor_t *const self,
+ const sl_btmesh_msg_t *evt);
+
+/***************************************************************************//**
+ * Process one step of BT Mesh Configurator
+ *
+ * @param[in] self Pointer to the configuration distributor instance
+ *
+ * The step function shall be called periodically by higher layer to perform
+ * deferred scheduling.
+ ******************************************************************************/
+void btmesh_conf_distributor_step(btmesh_conf_distributor_t *const self);
+
+/** @} (end addtogroup btmesh_conf_distributor) */
+/** @} (end addtogroup btmesh_conf) */
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* BTMESH_CONF_DISTRIBUTOR_H */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_executor.c b/app/btmesh/common_host/btmesh_conf/btmesh_conf_executor.c
new file mode 100644
index 00000000000..8d65918e135
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_executor.c
@@ -0,0 +1,790 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component - Job Executor
+ *******************************************************************************
+ * # License
+ * Copyright 2022 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "app_assert.h"
+#include "app_log.h"
+
+#include "btmesh_conf.h"
+#include "btmesh_conf_types.h"
+#include "btmesh_conf_config.h"
+#include "btmesh_conf_executor.h"
+#include "btmesh_conf_distributor.h"
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_executor BT Mesh Configuration Executor
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_executor_internal \
+ * BT Mesh Configuration Executor Internal
+ * @{
+ ******************************************************************************/
+/// Local log new line shortcut definition
+#define NL APP_LOG_NL
+
+/// BT Mesh Configuration Executor notification status
+typedef enum {
+ BTMESH_CONF_NOTIFICATION_ENABLE, ///< Jobs status notification is enabled
+ BTMESH_CONF_NOTIFICATION_DISABLE, ///< Jobs status notification is disabled
+} btmesh_conf_notification_status_t;
+
+/***************************************************************************//**
+ * Send configuration request to destination node.
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @param[in] notification_status Job status notification is enabled or disabled
+ * @returns Status of the configuration request.
+ * @retval SL_STATUS_OK Configuration request is accepted.
+ * @retval SL_STATUS_BUSY Request is rejected due to busy lower layers.
+ * @retval SL_STATUS_FAIL Requests are rejected due to unrecoverable error.
+ * Multiple task request could fail here. If one task request fails then the
+ * next task is set until one task request is not failed or the last task fails.
+ *
+ * The current configuration task specific configuration request is sent to the
+ * configuration server model on the primary element of destination node.
+ *
+ * If the btmesh_conf_task_t::conf_request is successful (SL_STATUS_OK) then
+ * an event timeout timer is started to detect missing events. This is extremely
+ * rare but the state machine would be stuck in case of missing events without
+ * this safety net.
+ *
+ * If the btmesh_conf_task_t::conf_request is rejected due to busy lower layers
+ * (SL_STATUS_BUSY) and the maximum number of retries is not reached then a timer
+ * is started with retry interval in order to try to send the configuration
+ * request again.
+ * The BT Mesh Stack can be busy due to unavailable resources for example when
+ * the maximum number of segmented TX messages is reached.
+ * See @a SL_BTMESH_CONFIG_MAX_SEND_SEGS and @a SL_BTMESH_CONFIG_APP_TXQ_SIZE
+ * BT Mesh Stack configuration defines in sl_btmesh_config.h.
+ *
+ * If the btmesh_conf_task_t::conf_request is rejected due unrecoverable error
+ * (SL_STATUS_FAIL) then the next configuration task is selected and the
+ * btmesh_conf_task_t::conf_request of next task is called and this procedure is
+ * repeated until the btmesh_conf_task_t::conf_request doesn't fail or the last
+ * task fails.
+ ******************************************************************************/
+static sl_status_t executor_conf_request(btmesh_conf_executor_t *const self,
+ btmesh_conf_notification_status_t notification_status);
+
+/***************************************************************************//**
+ * Process the status of configuration task event handler.
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @param[in] task_status Status of configuration task event handler
+ *
+ * The status of configuration task is returned by btmesh_conf_task_t::on_event.
+ *
+ * If the configuration task is successful (SL_STATUS_OK) then the executor sets
+ * the next configuration task and calls @ref executor_conf_request in order
+ * to send the configuration request to the destination node.
+ *
+ * If the configuration task fails due to server side error (SL_STATUS_FAIL) or
+ * due to unexpected critical error (SL_STATUS_ABORT) then the executor sets the
+ * next configuration task and calls @ref executor_conf_request in order
+ * to send the configuration request to the destination node.
+ * In case of SL_STATUS_ABORT the configuration request is canceled first
+ * because the it is still in progress.
+ *
+ * If configuration task timeout occurs (SL_STATUS_TIMEOUT) and the maximum
+ * number of retries is not reached then configuration request is sent to the
+ * destination node by @ref executor_conf_request function call.
+ * If the maximum number of retries is reached then the current configuration
+ * task fails and the executor sets the next configuration task and calls
+ * @ref executor_conf_request in order to send the configuration request to the
+ * destination node.
+ *
+ * If the configuration task is in progress (SL_STATUS_IN_PROGRESS) then the
+ * task is waiting for additional events so no operation is required.
+ *
+ * @note The next configuration task is set by @ref btmesh_conf_job_set_next_task
+ * based on the result of the current configuration task. If the current task
+ * is the last task then @ref executor_finish_job is called to transition into
+ * idle state and to notify the configuration distributor.
+ ******************************************************************************/
+static void executor_process_task_status(btmesh_conf_executor_t *const self,
+ sl_status_t task_status);
+
+/***************************************************************************//**
+ * Set next BT Mesh configuration task based on the current active task of a job
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @return Status of the next task setup
+ * @retval SL_STATUS_OK If next task is set properly.
+ * @retval SL_STATUS_NOT_FOUND If next is not set because this was the last task.
+ * @retval SL_STATUS_FAIL If fatal error occurred.
+ ******************************************************************************/
+static sl_status_t executor_set_next_task(btmesh_conf_executor_t *const self);
+
+/***************************************************************************//**
+ * Transition BT Mesh Configuration Executor to idle state and notify
+ * configuration distributor about the finished configuration job.
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @param[in] job_result Result of BT Mesh Configuration job
+ * @param[in] notification_status Job status notification is enabled or disabled
+ *
+ * The configuration distributor is notified only if @p notification_status is
+ * set to @p BTMESH_CONF_NOTIFICATION_ENABLE.
+ ******************************************************************************/
+static void executor_finish_job(btmesh_conf_executor_t *const self,
+ btmesh_conf_job_result_t job_result,
+ btmesh_conf_notification_status_t notification_status);
+
+/***************************************************************************//**
+ * Execute state transition of BT Mesh Configuration Executor instance
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @param[in] target_state Target state of the state transition
+ *
+ * State transition has three steps:
+ * - Exit action of source state
+ * - Transition action
+ * - Entry action of target state
+ * @note If source and target state of the transition is the same then the
+ * implementation considers it a self-transition and executes the exit and
+ * entry action of that state.
+ *
+ * The state transition function controls timeout and retry interval measurement.
+ * The lifecycle of each timer is bound to a state in order to guarantee:
+ * - Only one timer runs at any given point of time and therefore the timer
+ * structure in @ref btmesh_conf_executor_t can be shared.
+ * - Running timer is stopped when the current state is left.
+ * The retry counters are cleared at the beginning of each configuration task
+ * because each configuration task has the same amount of retry opportunities.
+ ******************************************************************************/
+static void executor_state_transition(btmesh_conf_executor_t *const self,
+ btmesh_conf_executor_state_t target_state);
+
+/***************************************************************************//**
+ * Provide string representation of BT Mesh Configuration Executor state.
+ *
+ * @param[in] state Configuration executor state
+ * @returns String representation of configuration executor state.
+ * @retval "unknown" If the state parameter contains invalid value.
+ ******************************************************************************/
+const char *executor_state_to_string(btmesh_conf_executor_state_t state)
+{
+ switch (state) {
+ case BTMESH_CONF_EXEC_STATE_IDLE:
+ return "idle";
+ case BTMESH_CONF_EXEC_STATE_TASK_SET:
+ return "task set";
+ case BTMESH_CONF_EXEC_STATE_TASK_EVENT_WAIT:
+ return "event wait";
+ case BTMESH_CONF_EXEC_STATE_TASK_REQUEST_BUSY:
+ return "request busy";
+ default:
+ return "invalid";
+ }
+}
+
+/***************************************************************************//**
+ * Callback which is called when the executor timer elapses
+ *
+ * @param[in] timer Pointer to the elapsed timer instance
+ * @param[in] data Pointer to the configuration executor instance
+ ******************************************************************************/
+static void executor_on_timer_elapsed(sl_simple_timer_t *timer, void *data);
+
+/***************************************************************************//**
+ * Provide information about retry occurrence during the execution of current
+ * configuration task.
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @returns Retry occurrence during the execution of current configuration task.
+ * @retval true If at least one retry occurred during task execution.
+ * @retval false If no retry occurred during task execution.
+ ******************************************************************************/
+static inline bool executor_retry_occurred(btmesh_conf_executor_t *const self)
+{
+ return (0 < self->local_retry_counter)
+ || (0 < self->communication_retry_counter);
+}
+
+/** @} (end addtogroup btmesh_conf_executor_internal) */
+
+btmesh_conf_executor_t *btmesh_conf_executor_create(uint16_t id,
+ btmesh_conf_distributor_t *const parent)
+{
+ btmesh_conf_executor_t *self;
+
+ if (NULL == parent) {
+ // If the parent parameter is NULL then the configuration executor creation
+ // fails and therefore NULL is returned to the caller.
+ return NULL;
+ }
+
+ self = malloc(sizeof(btmesh_conf_executor_t));
+
+ if (NULL != self) {
+ self->id = id;
+ self->parent = parent;
+ self->state = BTMESH_CONF_EXEC_STATE_IDLE;
+ self->current_job = NULL;
+ self->local_retry_counter = 0;
+ self->local_retry_max = SL_BTMESH_CONF_REQUEST_BUSY_RETRY_MAX_CFG_VAL;
+ self->communication_retry_counter = 0;
+ self->communication_retry_max = SL_BTMESH_CONF_COMMUNICATION_RETRY_MAX_CFG_VAL;
+ self->timer_active = false;
+ }
+ return self;
+}
+
+void btmesh_conf_executor_destroy(btmesh_conf_executor_t* self)
+{
+ // Destroy function shall behave as free if NULL pointer is passed
+ if (NULL == self) {
+ return;
+ }
+ if (BTMESH_CONF_EXEC_STATE_IDLE != self->state) {
+ // Configuration executor shall transition to idle state in order to
+ // stop the timer.
+ // Note: If the timer was not stopped then the timer callback would use a
+ // pointer to an already deallocated configuration executor instance.
+ executor_state_transition(self, BTMESH_CONF_EXEC_STATE_IDLE);
+ btmesh_conf_job_destroy(self->current_job);
+ }
+ free(self);
+}
+
+sl_status_t btmesh_conf_executor_start_job(btmesh_conf_executor_t *const self,
+ btmesh_conf_job_t *job)
+{
+ app_assert_s(NULL != self);
+ app_assert_s(NULL != job);
+
+ sl_status_t sc = SL_STATUS_INVALID_STATE;
+
+ if (BTMESH_CONF_EXEC_STATE_IDLE == self->state) {
+ self->current_job = job;
+ sc = executor_set_next_task(self);
+ if (SL_STATUS_OK == sc) {
+ // Notifications are not allowed to avoid destroying the job before
+ // it is removed from the distributor wait queue during scheduling
+ sc = executor_conf_request(self, BTMESH_CONF_NOTIFICATION_DISABLE);
+ // If the status of configuration request is busy then it means that the
+ // BT Mesh stack was not able to accept the request temporarily but the
+ // executor will try again when the retry time elapses.
+ // Therefore, the configuration job start is considered to be successful.
+ sc = (SL_STATUS_BUSY == sc) ? SL_STATUS_OK : sc;
+ } else {
+ // The configuration task which shall be executed first could not be set
+ sc = SL_STATUS_NOT_FOUND;
+ }
+ }
+ return sc;
+}
+
+void btmesh_conf_executor_get_status(btmesh_conf_executor_t *const self,
+ btmesh_conf_executor_status_t *status)
+{
+ app_assert_s(NULL != self);
+ app_assert_s(NULL != status);
+
+ status->state = self->state;
+ if (BTMESH_CONF_EXEC_STATE_IDLE == self->state) {
+ status->enc_netkey_index = BTMESH_CONF_NETKEY_INDEX_UNASSIGNED;
+ status->server_address = MESH_ADDR_UNASSIGNED;
+ } else {
+ status->enc_netkey_index = self->current_job->enc_netkey_index;
+ status->server_address = self->current_job->server_address;
+ }
+}
+
+void btmesh_conf_executor_on_event(btmesh_conf_executor_t *const self,
+ const sl_btmesh_msg_t *evt)
+{
+ uint32_t handle;
+
+ if (BTMESH_CONF_EXEC_STATE_TASK_EVENT_WAIT == self->state) {
+ sl_status_t handle_status = btmesh_conf_get_handle_from_event(evt, &handle);
+
+ // The configuration events shall be forwarded if the handle in the event
+ // is the same as the handle in the executor. The executor shall ignore
+ // events which belong to parallel configuration of other nodes.
+ // Note: other executors with configuration job targeting different config
+ // server might be active as well.
+ // Non-configuration events are always forwareded
+ bool forward_event = ((SL_STATUS_OK == handle_status)
+ && (handle == self->handle))
+ || (SL_STATUS_NOT_FOUND == handle_status);
+
+ if (false != forward_event) {
+ sl_status_t task_status;
+ btmesh_conf_job_t *job = self->current_job;
+ // Forward the BT Mesh Stack event with matching handle to the event
+ // handler of the current configuration task.
+ task_status = job->current_task->on_event(job->current_task,
+ job->enc_netkey_index,
+ job->server_address,
+ evt);
+ // Process the status of the current task after it handled this new
+ // BT Mesh Stack event
+ executor_process_task_status(self, task_status);
+ }
+ }
+}
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_executor_internal \
+ * BT Mesh Configuration Executor Internal
+ * @{
+ ******************************************************************************/
+static sl_status_t executor_conf_request(btmesh_conf_executor_t *const self,
+ btmesh_conf_notification_status_t notification_status)
+{
+ sl_status_t conf_request_status, set_next_task_status;
+ btmesh_conf_job_t *job = self->current_job;
+ bool conf_request_required = true;
+ bool current_task_failed = false;
+ char task_str[SL_BTMESH_CONF_MAX_LOG_MSG_FRAGMENT_SIZE_CFG_VAL];
+ char node_str[SL_BTMESH_CONF_MAX_LOG_MSG_FRAGMENT_SIZE_CFG_VAL];
+
+ // Loop is necessary because it is possible that BT Mesh Stack configuration
+ // request of the current task fails. If a configuration request fails with
+ // unrecoverable error then there are two possibilities:
+ // - If the negative branch (next_on_failure) of the current task
+ // (in task_tree) is NULL then there is no task which could be executed
+ // - If the negative branch (next_on_failure) of the current task
+ // (in task_tree) references another configuration task then it is set
+ // as the current task and the loop body is executed again.
+ // Note: In the worst case if there is a general error then multiple task in
+ // series can fail and whole job ends in this function call.
+ // For example if there is no node with given server address in device
+ // database and therefore it is not possible to retrieve the device key.
+ while (false != conf_request_required) {
+ // Call the configuration task specific BT Mesh Stack configuration request.
+ // Handle returned by the BT Mesh Stack is stored by the executor in order
+ // to forward configuration events with matching handle to the task in
+ // the btmesh_conf_executor_on_event function.
+ conf_request_status = job->current_task->conf_request(job->current_task,
+ job->enc_netkey_index,
+ job->server_address,
+ &self->handle);
+
+ // If any log level is enabled which is used in this function then it is
+ // necessary to build the log message fragments.
+ if (btmesh_conf_any_severe_log_level(APP_LOG_LEVEL_INFO)) {
+ int32_t str_retval;
+ // Get the string representation of the current configuration task
+ str_retval = job->current_task->to_string(job->current_task,
+ &task_str[0],
+ sizeof(task_str));
+ app_assert(0 <= str_retval, "String formatting failed." NL);
+
+ // Build log message fragment with node information
+ str_retval = snprintf(node_str,
+ sizeof(node_str),
+ "node (netkey_idx=%d,addr=0x%04x,handle=0x%08lx)",
+ job->enc_netkey_index,
+ job->server_address,
+ (unsigned long)self->handle);
+ app_assert(0 <= str_retval, "String formatting failed." NL);
+ }
+
+ // Defensive programming: make sure that the string is null terminated
+ task_str[sizeof(task_str) - 1] = '\0';
+ node_str[sizeof(node_str) - 1] = '\0';
+
+ switch (conf_request_status) {
+ case SL_STATUS_OK:
+ // Configuration request is successful so the configuration task shall
+ // wait for the results (BT Mesh Stack event).
+ app_log_level(executor_retry_occurred(self)
+ ? APP_LOG_LEVEL_WARNING
+ : APP_LOG_LEVEL_INFO,
+ "Task %s request%s to %s is sent." NL,
+ task_str,
+ executor_retry_occurred(self) ? " (retry)" : "",
+ node_str);
+ executor_state_transition(self, BTMESH_CONF_EXEC_STATE_TASK_EVENT_WAIT);
+ conf_request_required = false;
+ break;
+ case SL_STATUS_BUSY:
+ // Configuration request is busy which indicates that some resources in
+ // the BT Mesh Stack are not available temporarily so the configuration
+ // request shall be repeated later (retry).
+ if (self->local_retry_counter < self->local_retry_max) {
+ app_log_warning("Task %s request%s to %s is busy (result=0x%04lx)." NL,
+ task_str,
+ executor_retry_occurred(self) ? " (retry)" : "",
+ node_str,
+ (unsigned long) job->current_task->result);
+ self->local_retry_counter++;
+ executor_state_transition(self, BTMESH_CONF_EXEC_STATE_TASK_REQUEST_BUSY);
+ conf_request_required = false;
+ } else {
+ app_log_error("Task %s request%s to %s is failed (result=0x%04lx) "
+ "because too many configuration requests were busy." NL,
+ task_str,
+ executor_retry_occurred(self) ? " (retry)" : "",
+ node_str,
+ (unsigned long)job->current_task->result);
+ current_task_failed = true;
+ }
+ break;
+ default:
+ // Unrecoverable task failure
+ app_log_error("Task %s request%s to %s is failed (result=0x%04lx)." NL,
+ task_str,
+ executor_retry_occurred(self) ? " (retry)" : "",
+ node_str,
+ (unsigned long)job->current_task->result);
+ current_task_failed = true;
+ break;
+ }
+
+ if (false != current_task_failed) {
+ // The negative branch (next_on_failure) of the current task shall be
+ // set as the current task in the configuration job.
+ set_next_task_status = executor_set_next_task(self);
+ self->current_job->result = BTMESH_CONF_JOB_RESULT_FAIL;
+ if (SL_STATUS_OK != set_next_task_status) {
+ // If there is no further configuration task in the task_tree which
+ // can be executed then the whole job fails.
+ executor_finish_job(self,
+ BTMESH_CONF_JOB_RESULT_FAIL,
+ notification_status);
+ conf_request_required = false;
+ }
+ }
+ }
+
+ return conf_request_status;
+}
+
+static void executor_process_task_status(btmesh_conf_executor_t *const self,
+ sl_status_t task_status)
+{
+ bool current_task_finished = true;
+ bool retry_required = false;
+ btmesh_conf_job_t *job = self->current_job;
+ char task_str[SL_BTMESH_CONF_MAX_LOG_MSG_FRAGMENT_SIZE_CFG_VAL];
+ char node_str[SL_BTMESH_CONF_MAX_LOG_MSG_FRAGMENT_SIZE_CFG_VAL];
+
+ // If any log level is enabled which is used in this function then it is
+ // necessary to build the log message fragments.
+ if (btmesh_conf_any_severe_log_level(APP_LOG_LEVEL_INFO)) {
+ int32_t str_retval;
+ // Get the string representation of the current configuration task
+ str_retval = job->current_task->to_string(job->current_task,
+ &task_str[0],
+ sizeof(task_str));
+ app_assert(0 < str_retval, "String formatting failed." NL);
+
+ // Build log message fragment with node information
+ str_retval = snprintf(node_str,
+ sizeof(node_str),
+ "node (netkey_idx=%d,addr=0x%04x,handle=0x%08lx)",
+ job->enc_netkey_index,
+ job->server_address,
+ (unsigned long)self->handle);
+ app_assert(0 < str_retval, "String formatting failed." NL);
+ }
+ // Defensive programming: make sure that the string is null terminated
+ task_str[sizeof(task_str) - 1] = '\0';
+ node_str[sizeof(node_str) - 1] = '\0';
+
+ switch (task_status) {
+ case SL_STATUS_OK:
+ {
+ // The configuration task is finished successfully
+ app_log_info("Task %s of %s is completed successfully." NL,
+ task_str,
+ node_str);
+ current_task_finished = true;
+ break;
+ }
+
+ case SL_STATUS_IN_PROGRESS:
+ {
+ // Task is waiting for additional events so nothing shall be done here.
+ current_task_finished = false;
+ break;
+ }
+
+ case SL_STATUS_TIMEOUT:
+ {
+ // Configuration request in the BT Mesh Stack triggers message transmission
+ // to the configuration server which responds with a status message to the
+ // configuration client. If any of these messages are lost then the
+ // configuration request timeout occurs in the BT Mesh Stack.
+ // Note: BT Mesh Stack sends the acknowledged configuration messages which
+ // is more robust but even these messages might be lost.
+ if (self->communication_retry_counter < self->communication_retry_max) {
+ // In order to make the configuration procedure more robust the
+ // configuration requests are repeated a configurable amount of times.
+ // Note: it is possible that the node is not available because it was
+ // removed or turned off so it does not make sense to retry forever.
+ self->communication_retry_counter++;
+ retry_required = true;
+ current_task_finished = false;
+ } else {
+ // The maximum number of retries are exceeded so the task is failed
+ app_log_error("Task %s of %s is timed out (result=0x%04lx)." NL,
+ task_str,
+ node_str,
+ (unsigned long) job->current_task->result);
+ self->current_job->result = BTMESH_CONF_JOB_RESULT_FAIL;
+ current_task_finished = true;
+ }
+ break;
+ }
+
+ case SL_STATUS_FAIL:
+ {
+ // Error occurred because configuration server rejected the configuration
+ // request and responded to the configuration client with status message
+ // with non-zero error status.
+ app_log_error("Task %s of %s is failed (result=0x%04lx)." NL,
+ task_str,
+ node_str,
+ (unsigned long) job->current_task->result);
+ self->current_job->result = BTMESH_CONF_JOB_RESULT_FAIL;
+ current_task_finished = true;
+ break;
+ }
+
+ case SL_STATUS_ABORT:
+ {
+ // Local error occurred in the configuration task during the BT Mesh Stack
+ // event processing and the execution of the current task shall be aborted.
+ app_log_error("Task %s of %s is aborted (result=0x%04lx)." NL,
+ task_str,
+ node_str,
+ (unsigned long)job->current_task->result);
+ // Cancel the configuration request in the BT Mesh stack to make sure the
+ // next configuration task won't be rejected because the current one is
+ // considered active in the BT Mesh Stack.
+ sl_status_t cancel_req_status =
+ sl_btmesh_config_client_cancel_request(self->handle);
+ app_log_status_warning_f(cancel_req_status,
+ "Task %s request cancellation of %s is failed." NL,
+ task_str,
+ node_str);
+ self->current_job->result = BTMESH_CONF_JOB_RESULT_FAIL;
+ current_task_finished = true;
+ break;
+ }
+ }
+
+ if (false != retry_required) {
+ // It is not necessary to check the return value because the function handles
+ // the errors and the error code can't be returned to any SW modules
+ (void) executor_conf_request(self, BTMESH_CONF_NOTIFICATION_ENABLE);
+ } else if (false != current_task_finished) {
+ sl_status_t next_task_status = executor_set_next_task(self);
+ if (SL_STATUS_OK == next_task_status) {
+ // It is not necessary to check the return value because the function handles
+ // the errors and the error code can't be returned to any SW modules
+ (void) executor_conf_request(self, BTMESH_CONF_NOTIFICATION_ENABLE);
+ } else if (SL_STATUS_NOT_FOUND == next_task_status) {
+ btmesh_conf_job_result_t job_result = self->current_job->result;
+ // The last task of job is finished. If the job result was not set to
+ // failure by any task then the job shall be considered successful
+ if (BTMESH_CONF_JOB_RESULT_UNKNOWN == job_result) {
+ job_result = BTMESH_CONF_JOB_RESULT_SUCCESS;
+ }
+ executor_finish_job(self, job_result, BTMESH_CONF_NOTIFICATION_ENABLE);
+ } else {
+ // Critical error occurred during the setup of the next task
+ executor_finish_job(self,
+ BTMESH_CONF_JOB_RESULT_CRITICAL_ERROR,
+ BTMESH_CONF_NOTIFICATION_ENABLE);
+ }
+ }
+}
+
+static sl_status_t executor_set_next_task(btmesh_conf_executor_t *const self)
+{
+ sl_status_t next_task_status = btmesh_conf_job_set_next_task(self->current_job);
+ if (SL_STATUS_OK == next_task_status) {
+ executor_state_transition(self, BTMESH_CONF_EXEC_STATE_TASK_SET);
+ }
+ return next_task_status;
+}
+
+static void executor_finish_job(btmesh_conf_executor_t *const self,
+ btmesh_conf_job_result_t job_result,
+ btmesh_conf_notification_status_t notification_status)
+{
+ // The configuration executor shall transition to idle state before it notifies
+ // the configuration distributor in order to make it possible to start a new
+ // configuration job. Configuration distributor assumes if the job execution
+ // is ended then it is able to schedule new configuration job.
+ btmesh_conf_job_t *finished_job = self->current_job;
+ self->current_job->result = job_result;
+ self->current_job = NULL;
+ self->local_retry_counter = 0;
+ self->communication_retry_counter = 0;
+ executor_state_transition(self, BTMESH_CONF_EXEC_STATE_IDLE);
+ if (BTMESH_CONF_NOTIFICATION_ENABLE == notification_status) {
+ btmesh_conf_distributor_on_job_notification(self->parent,
+ self,
+ finished_job);
+ }
+}
+
+static void executor_state_transition(btmesh_conf_executor_t *const self,
+ btmesh_conf_executor_state_t target_state)
+{
+ app_assert(target_state < BTMESH_CONF_EXEC_STATE_COUNT,
+ "Invalid configuration executor state." NL);
+ btmesh_conf_executor_state_t source_state = self->state;
+
+ switch (source_state) {
+ case BTMESH_CONF_EXEC_STATE_TASK_REQUEST_BUSY:
+ if (false != self->timer_active) {
+ app_log_debug("BT Mesh Config Executor (id=%d) stops "
+ "request busy retry timer." NL,
+ self->id);
+ sl_status_t sc = sl_simple_timer_stop(&self->timer);
+ app_assert_status_f(sc, "Failed to stop btmesh_conf_executor timer." NL);
+ self->timer_active = false;
+ }
+ break;
+
+ case BTMESH_CONF_EXEC_STATE_TASK_EVENT_WAIT:
+ if (false != self->timer_active) {
+ app_log_debug("BT Mesh Config Executor (id=%d) stops "
+ "waiting for event timeout timer." NL,
+ self->id);
+ sl_status_t sc = sl_simple_timer_stop(&self->timer);
+ app_assert_status_f(sc, "Failed to stop btmesh_conf_executor timer." NL);
+ self->timer_active = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ self->state = target_state;
+ app_log_debug("BT Mesh Config Executor (id=%d) state transition to \"%s\"." NL,
+ self->id,
+ executor_state_to_string(target_state));
+
+ switch (target_state) {
+ case BTMESH_CONF_EXEC_STATE_TASK_SET:
+ // The retry counters are cleared because each configuration task has the
+ // same number of retry opportunities.
+ self->local_retry_counter = 0;
+ self->communication_retry_counter = 0;
+ break;
+
+ case BTMESH_CONF_EXEC_STATE_TASK_REQUEST_BUSY:
+ {
+ sl_status_t sc;
+ app_log_debug("BT Mesh Config Executor (id=%d) starts "
+ "request busy retry timer." NL,
+ self->id);
+ sc = sl_simple_timer_start(&self->timer,
+ SL_BTMESH_CONF_REQUEST_BUSY_RETRY_INTERVAL_MS_CFG_VAL,
+ executor_on_timer_elapsed,
+ self,
+ false);
+ app_assert_status_f(sc, "Failed to start btmesh_conf_executor timer." NL);
+ self->timer_active = true;
+ break;
+ }
+
+ case BTMESH_CONF_EXEC_STATE_TASK_EVENT_WAIT:
+ {
+ sl_status_t sc;
+ app_log_debug("BT Mesh Config Executor (id=%d) starts "
+ "waiting for event timeout timer." NL,
+ self->id);
+ sc = sl_simple_timer_start(&self->timer,
+ SL_BTMESH_CONF_EVENT_WAIT_TIMEOUT_MS_CFG_VAL,
+ executor_on_timer_elapsed,
+ self,
+ false);
+ app_assert_status_f(sc, "Failed to start btmesh_conf_executor timer." NL);
+ self->timer_active = true;
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+static void executor_on_timer_elapsed(sl_simple_timer_t *timer, void *data)
+{
+ btmesh_conf_executor_t *const self = (btmesh_conf_executor_t *const)data;
+ switch (self->state) {
+ case BTMESH_CONF_EXEC_STATE_TASK_REQUEST_BUSY:
+ app_log_debug("BT Mesh Config Executor (id=%d) "
+ "request busy retry timer has elapsed." NL,
+ self->id);
+ self->timer_active = false;
+ // The last configuration request was busy due to unavailable resources
+ // in the BT Mesh Stack. The retry interval has just elapsed and therefore
+ // configuration request retry is executed.
+ executor_conf_request(self, BTMESH_CONF_NOTIFICATION_ENABLE);
+ break;
+
+ case BTMESH_CONF_EXEC_STATE_TASK_EVENT_WAIT:
+ app_log_debug("BT Mesh Config Executor (id=%d) "
+ "waiting for event timeout timer has elapsed." NL,
+ self->id);
+ self->timer_active = false;
+ // No BT Mesh Stack configuration event was received with the matching
+ // configuration handle which can happen if the event is lost.
+ // If no configuration message is received from the configuration server
+ // then the BT Mesh Stack generates a configuration event with
+ // SL_STATUS_TIMEOUT result so it is guaranteed that an event is generated
+ // by the BT Mesh Stack under normal circumstances.
+ // A configuration event might be lost due to lack of resources or due to
+ // transfer errors during NCP communication.
+ // This is extremely rare but the executor state machine would be stuck in
+ // case of missing events without this safety net.
+ // The solution is to handle the missing event as if BT Mesh Stack
+ // configuration event with SL_STATUS_TIMEOUT result had been received
+ // because the status of the configuration request is not known so it
+ // makes sense to send the configuration request again (retry).
+ executor_process_task_status(self, SL_STATUS_TIMEOUT);
+ break;
+
+ default:
+ app_log_error("Configuration executor timeout occurred in unexpected state." NL);
+ break;
+ }
+}
+
+/** @} (end addtogroup btmesh_conf_executor_internal) */
+/** @} (end addtogroup btmesh_conf_executor) */
+/** @} (end addtogroup btmesh_conf) */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_executor.h b/app/btmesh/common_host/btmesh_conf/btmesh_conf_executor.h
new file mode 100644
index 00000000000..6baa0a6dca5
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_executor.h
@@ -0,0 +1,239 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component - Job Executor
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef BTMESH_CONF_EXECUTOR_H
+#define BTMESH_CONF_EXECUTOR_H
+
+#include "btmesh_conf_types.h"
+#include "btmesh_conf_job.h"
+#include "sl_simple_timer.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_executor BT Mesh Configuration Executor
+ * @brief BT Mesh Configuration Executor executes configuration jobs by executing
+ * the tasks in the task tree of the configuration job.
+ *
+ * Each executor shall have one parent @ref btmesh_conf_distributor.
+ * Parent distributor can command the executor to start configuration job
+ * execution by calling @ref btmesh_conf_executor_start_job function.
+ * The configuration executor reports the status of job execution by calling
+ * @ref btmesh_conf_distributor_on_job_notification function of parent distributor.
+ *
+ * The relationship between the configuration distributor and its executors is
+ * composition so the executors are owned by its parent distributor completely.
+ * Only the parent distributor is allowed to start configuration jobs because
+ * the executors always report its job statuses to the parent distributor.
+ *
+ * The robust execution of configuration tasks is guaranteed by the retry
+ * mechanism of the executor.
+ * @n Retry mechanism is used in the following cases:
+ * - Configuration request timeout in the BT Mesh Stack due to the following:
+ * - BT Mesh message sent to the configuration server is lost
+ * - BT Mesh status message sent by the configuration server is lost
+ * - Configuration event is not received maybe due to event lost over
+ * NCP communication (quite rare)
+ * - Configuration request is busy due to unavailable resources in the
+ * BT Mesh Stack
+ * @{
+ ******************************************************************************/
+
+typedef struct btmesh_conf_distributor_t btmesh_conf_distributor_t;
+
+/// State constants of BT Mesh Configuration Executor
+typedef enum btmesh_conf_executor_state_t {
+ /// Configuration Executor is ready to execute waiting configuration jobs
+ BTMESH_CONF_EXEC_STATE_IDLE,
+ /// Configuration Executor advanced to the next configuration task
+ BTMESH_CONF_EXEC_STATE_TASK_SET,
+ /// Configuration Executor forwards events to current task and waits for result
+ BTMESH_CONF_EXEC_STATE_TASK_EVENT_WAIT,
+ /// @brief Configuration Executor waits the retry time because the previous
+ /// task request was busy due to unavailable resources.
+ ///
+ /// For example the following conditions could lead to busy requests:
+ /// - Maximum number segments allowed for transmitted packets is exceeded
+ /// - Access Layer TX Queue Size
+ ///
+ /// See @a SL_BTMESH_CONFIG_MAX_SEND_SEGS and @a SL_BTMESH_CONFIG_APP_TXQ_SIZE
+ /// BT Mesh Stack configuration defines in sl_btmesh_config.h.
+ BTMESH_CONF_EXEC_STATE_TASK_REQUEST_BUSY,
+ /// Number of Configuration Executor states
+ BTMESH_CONF_EXEC_STATE_COUNT
+} btmesh_conf_executor_state_t;
+
+/// Current status of Configuration Executor
+typedef struct btmesh_conf_executor_status_t {
+ /// State of Configuration Executor
+ btmesh_conf_executor_state_t state;
+ /// @brief Network key index of the executed configuration job
+ /// @n It is valid if the @p state is not @ref BTMESH_CONF_EXEC_STATE_IDLE.
+ uint16_t enc_netkey_index;
+ /// @brief Destination node primary element address of executed configuration job
+ /// @n It is valid if the @p state is not @ref BTMESH_CONF_EXEC_STATE_IDLE.
+ uint16_t server_address;
+} btmesh_conf_executor_status_t;
+
+/***************************************************************************//**
+ * @brief BT Mesh Configuration Executor executes configuration jobs by executing
+ * the tasks in the task tree of the configuration job.
+ ******************************************************************************/
+typedef struct btmesh_conf_executor {
+ /// BT Mesh Configuration Distributor which the executor belongs to
+ btmesh_conf_distributor_t *parent;
+ /// Pointer to configuration job which is executed
+ btmesh_conf_job_t *current_job;
+ /// @brief BT Mesh stack configuration handle which is returned by the last
+ /// configuration request (BT Mesh Stack API call). Configuration executor
+ /// processes those configuration events only which matches this handle.
+ uint32_t handle;
+ /// Current state of the configuration executor
+ btmesh_conf_executor_state_t state;
+ /// @brief Identifier of BT Mesh Configuration Executor instance.
+ /// @n The identifier of configuration executor instance shall be unique in
+ /// the context of parent configuration distributor.
+ uint16_t id;
+ /// @brief Number of configuration task request retries were executed due to
+ /// busy BT Mesh Stack. The BT Mesh Stack might reject configuration client
+ /// requests temporarily due to lack of resources. For example maximum number
+ /// of parallel segmented message transmissions is reached.
+ /// @n The local refers to the fact that no messages were sent to the
+ /// configuration server because the BT Mesh Stack did not accept the request.
+ uint16_t local_retry_counter;
+ /// @brief Maximum number of configuration task request retries were executed
+ /// due to busy BT Mesh Stack.
+ /// @n The local refers to the fact that no messages were sent to the
+ /// configuration server because the BT Mesh Stack did not accept the request.
+ uint16_t local_retry_max;
+ /// @brief Number of configuration task request retries were executed already
+ /// due to BT Mesh Stack timeout. An ongoing BT Mesh Stack configuration client
+ /// request might timeout because the configuration request message sent to
+ /// the configuration server or the configuration status message sent back to
+ /// the configuration client is lost.
+ /// This counter is cleared at the beginning of each configuration task of the
+ /// configuration job.
+ uint16_t communication_retry_counter;
+ /// @brief Maximum number of configuration task request retries due to BT Mesh
+ /// Stack timeout which indicates lost configuration messages.
+ uint16_t communication_retry_max;
+ /// Timer to measure event timeout and configuration request retry interval
+ sl_simple_timer_t timer;
+ /// @brief Timer is active from the the moment it is started until it elapses
+ /// or until it is stopped explicitly.
+ bool timer_active;
+} btmesh_conf_executor_t;
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Executor instance which belongs to the
+ * specified configuration distributor
+ *
+ * @param[in] id Identifier of BT Mesh Configuration Executor instance
+ * @param[in] parent BT Mesh Configuration Distributor which executor belongs to
+ * @returns Created configuration executor.
+ * @retval NULL If the configuration executor creation fails.
+ *
+ * BT Mesh Configuration Executor is allocated and initialized with the provided
+ * BT Mesh Configuration Distributor parent. The initialized configuration
+ * executor is in idle state and therefore it does not execute any jobs.
+ * The identifier of configuration executor shall be unique in the context of
+ * parent configuration distributor.
+ ******************************************************************************/
+btmesh_conf_executor_t *btmesh_conf_executor_create(uint16_t id,
+ btmesh_conf_distributor_t *const parent);
+
+/***************************************************************************//**
+ * Deallocates the BT Mesh Configuration Executor instance
+ *
+ * @param[in] self Pointer to the configuration executor which shall be destroyed
+ *
+ * If the configuration executor runs a configuration job then the job and all
+ * of its tasks are deallocated as well.
+ ******************************************************************************/
+void btmesh_conf_executor_destroy(btmesh_conf_executor_t *const self);
+
+/***************************************************************************//**
+ * Start execution of the specified configuration job in a BT Mesh Configuration
+ * Executor instance
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @param[in] job Configuration job which shall be started
+ * @returns Status of configuration job execution start.
+ * @retval SL_STATUS_OK If the configuration job is started properly.
+ * @retval SL_STATUS_INVALID_STATE If the executor is busy with the execution of
+ * another configuration job.
+ * @retval SL_STATUS_NOT_FOUND If the first task of configuration job could not
+ * be set.
+ * @retval SL_STATUS_FAIL If one or more configuration requests are failed and
+ * no further configuration task remains in the task tree which can be executed.
+ ******************************************************************************/
+sl_status_t btmesh_conf_executor_start_job(btmesh_conf_executor_t *const self,
+ btmesh_conf_job_t *job);
+
+/***************************************************************************//**
+ * Get status BT Mesh Configuration Executor instance
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @param[out] status Pointer where status of the executor instance is written
+ ******************************************************************************/
+void btmesh_conf_executor_get_status(btmesh_conf_executor_t *const self,
+ btmesh_conf_executor_status_t *status);
+
+/***************************************************************************//**
+ * Event handler of BT Mesh Configuration Executor instance
+ *
+ * @param[in] self Pointer to the configuration executor instance
+ * @param[in] evt BT Mesh Stack event
+ *
+ * The BT Mesh Configuration Executor event handler forwards events to the
+ * current task of the configuration job which is executed at the moment.
+ * If the event is a configuration event then it is forwarded only if the handle
+ * in the event matches the handle returned by the last configuration BT Mesh
+ * Stack request which belongs to the same configuration task.
+ ******************************************************************************/
+void btmesh_conf_executor_on_event(btmesh_conf_executor_t *const self,
+ const sl_btmesh_msg_t *evt);
+
+/** @} (end addtogroup btmesh_conf_executor) */
+/** @} (end addtogroup btmesh_conf) */
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* BTMESH_CONF_EXECUTOR_H */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_job.c b/app/btmesh/common_host/btmesh_conf/btmesh_conf_job.c
new file mode 100644
index 00000000000..57b907ae21d
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_job.c
@@ -0,0 +1,139 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component - Job
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "sl_common.h"
+#include "btmesh_conf_job.h"
+#include "btmesh_conf_config.h"
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_job BT Mesh Configuration Job
+ * @{
+ ******************************************************************************/
+btmesh_conf_job_t *btmesh_conf_job_create_default(uint16_t enc_netkey_index,
+ uint16_t server_address,
+ btmesh_conf_task_t *task_tree,
+ btmesh_conf_on_job_notification_t on_job_notification)
+{
+ // Call general btmesh_conf_job_create with default parameters
+ return btmesh_conf_job_create(enc_netkey_index,
+ server_address,
+ task_tree,
+ on_job_notification,
+ BTMESH_CONF_VARG_NULL,
+ SL_BTMESH_CONF_JOB_AUTO_DESTROY_CFG_VAL,
+ NULL);
+}
+
+btmesh_conf_job_t *btmesh_conf_job_create(uint16_t enc_netkey_index,
+ uint16_t server_address,
+ btmesh_conf_task_t *task_tree,
+ btmesh_conf_on_job_notification_t on_job_notification,
+ btmesh_conf_varg_t job_status_param,
+ bool auto_destroy,
+ uint32_t *const job_id)
+{
+ btmesh_conf_job_t *self;
+ self = malloc(sizeof(btmesh_conf_job_t));
+
+ if (NULL != self) {
+ // Generate configuration job identifier to differentiate jobs easily
+ self->job_id = btmesh_conf_job_id_generator();
+ self->list_elem.node = NULL;
+ self->current_task = NULL;
+ self->enc_netkey_index = enc_netkey_index;
+ self->server_address = server_address;
+ self->task_tree = task_tree;
+ self->result = BTMESH_CONF_JOB_RESULT_UNKNOWN;
+ self->on_job_notification = on_job_notification;
+ self->job_status_param = job_status_param;
+ self->auto_destroy = auto_destroy;
+
+ if (NULL != job_id) {
+ // It is not mandatory to store the job identifier in the application
+ *job_id = self->job_id;
+ }
+ }
+ return self;
+}
+
+void btmesh_conf_job_destroy(btmesh_conf_job_t *const self)
+{
+ // Destroy function shall behave as free if NULL pointer is passed
+ if (NULL == self) {
+ return;
+ }
+ // Destroy all configuration tasks in task_tree
+ btmesh_conf_task_destroy(self->task_tree);
+ free(self);
+}
+
+sl_status_t btmesh_conf_job_set_next_task(btmesh_conf_job_t *const self)
+{
+ sl_status_t sc;
+ if (NULL == self->current_task) {
+ // First task of the configuration job shall be set
+ if (self->task_tree == NULL) {
+ sc = SL_STATUS_FAIL;
+ } else {
+ self->current_task = self->task_tree;
+ sc = SL_STATUS_OK;
+ }
+ } else {
+ // Set the next configuration task based on the result of the current task
+ if (SL_STATUS_OK == self->current_task->result) {
+ self->current_task = self->current_task->next_on_success;
+ } else {
+ self->current_task = self->current_task->next_on_failure;
+ }
+
+ if (NULL == self->current_task) {
+ // There is no additional task in the task_tree of the configuration job
+ sc = SL_STATUS_NOT_FOUND;
+ } else {
+ sc = SL_STATUS_OK;
+ }
+ }
+ return sc;
+}
+
+SL_WEAK uint32_t btmesh_conf_job_id_generator(void)
+{
+ // Job identifiers are generated by incrementing the previous one
+ static uint32_t job_id = 0;
+ return job_id++;
+}
+
+/** @} (end addtogroup btmesh_conf_job) */
+/** @} (end addtogroup btmesh_conf) */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_job.h b/app/btmesh/common_host/btmesh_conf/btmesh_conf_job.h
new file mode 100644
index 00000000000..14bf68d83e1
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_job.h
@@ -0,0 +1,208 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component - Job
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef BTMESH_CONF_JOB_H
+#define BTMESH_CONF_JOB_H
+
+#include "sl_slist.h"
+#include "btmesh_conf_types.h"
+#include "btmesh_conf_task.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_job BT Mesh Configuration Job
+ * @brief BT Mesh Configuration Job aggregates group of configuration tasks which
+ * targets the same configuration server model on a remote node.
+ * @{
+ ******************************************************************************/
+
+/// Result of BT Mesh Configuration job
+typedef enum btmesh_conf_job_result_t {
+ /// Every executed task of configuration job is successful
+ BTMESH_CONF_JOB_RESULT_SUCCESS,
+ /// At least one executed task is failed during configuration job execution
+ BTMESH_CONF_JOB_RESULT_FAIL,
+ /// Internal error occurred during configuration job execution
+ BTMESH_CONF_JOB_RESULT_CRITICAL_ERROR,
+ /// The configuration job is canceled because the configurator is deinitialized
+ BTMESH_CONF_JOB_RESULT_CANCEL,
+ /// The result of configuration job is not known yet
+ BTMESH_CONF_JOB_RESULT_UNKNOWN
+} btmesh_conf_job_result_t;
+
+/// Type definition of @ref struct btmesh_conf_job_t
+typedef struct btmesh_conf_job_t btmesh_conf_job_t;
+
+/***************************************************************************//**
+ * Type of BT Mesh Configuration Job status notification callback
+ *
+ * @param[in] job Configuration job with changed status
+ *
+ * If task execution path ends in the configuration job then the job ends as
+ * well and job status notification callback is called with the result.
+ ******************************************************************************/
+typedef void (*btmesh_conf_on_job_notification_t)(const btmesh_conf_job_t *job);
+
+/***************************************************************************//**
+ * @brief BT Mesh Configuration Job aggregates group of configuration tasks which
+ * targets the same configuration server model on a remote node.
+ ******************************************************************************/
+struct btmesh_conf_job_t {
+ /// List element (node) to support placement of jobs in singly-linked list
+ sl_slist_node_t list_elem;
+ /// Unique job identifier generated by @ref btmesh_conf_job_id_generator
+ uint32_t job_id;
+ /// Configuration tasks which shall be executed in the specified order
+ btmesh_conf_task_t *task_tree;
+ /// Active configuration task
+ btmesh_conf_task_t *current_task;
+ /// Network key used to encrypt the config requests on the network layer
+ uint16_t enc_netkey_index;
+ /// Destination node primary element address
+ uint16_t server_address;
+ /// Result of configuration job
+ btmesh_conf_job_result_t result;
+ /// @brief Configuration job status notification callback which is called when
+ /// the status of the job changes at the end of the job
+ void (*on_job_notification)(const btmesh_conf_job_t *job);
+ /// @brief Job status param can be set by the application to differentiate
+ /// config jobs if the same callback function is used in multiple jobs
+ btmesh_conf_varg_t job_status_param;
+ /// @brief Auto destroy deallocates the configuration job and its tasks
+ /// automatically after the job status notification callback returns.
+ /// If @ref btmesh_conf_submit_job operation fails and the definition
+ /// SL_BTMESH_CONF_JOB_AUTO_DESTROY_ON_SUBMIT_FAILURE_CFG_VAL is turned on
+ /// then the job is deallocated automatically on submit failure.
+ bool auto_destroy;
+};
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Job with default parameters
+ *
+ * @param[in] enc_netkey_index Network key used to encrypt the config requests
+ * @param[in] server_address Destination node primary element address
+ * @param[in] task_tree Config tasks which shall be executed in specified order
+ * @param[in] on_job_notification Configuration job status notification callback
+ * which is called when the status of the job changes at the end of the job
+ * @returns Created configuration job.
+ * @retval NULL If the configuration job creation fails.
+ *
+ * This function calls @ref btmesh_conf_job_create with the following parameters:
+ * - @a job_status_param: @ref BTMESH_CONF_VARG_NULL
+ * - @a auto_destroy: @ref SL_BTMESH_CONF_JOB_AUTO_DESTROY_CFG_VAL
+ * - @a job_id: NULL
+ ******************************************************************************/
+btmesh_conf_job_t *btmesh_conf_job_create_default(uint16_t enc_netkey_index,
+ uint16_t server_address,
+ btmesh_conf_task_t *task_tree,
+ btmesh_conf_on_job_notification_t on_job_notification);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Job with given tasks
+ *
+ * @param[in] enc_netkey_index Network key used to encrypt the config requests
+ * @param[in] server_address Destination node primary element address
+ * @param[in] task_tree Config tasks which shall be executed in specified order
+ * @param[in] on_job_notification Configuration job status notification callback
+ * which is called when the status of the job changes at the end of the job
+ * @param[in] job_status_param Job status param can be set by the application to
+ * differentiate config jobs if the same jobs status notification callback
+ * function is used in multiple jobs
+ * @param[in] auto_destroy Auto destroy deallocates the configuration job and
+ * its tasks automatically after the job status notification callback returns.
+ * If @ref btmesh_conf_submit_job operation fails and the definition
+ * @ref SL_BTMESH_CONF_JOB_AUTO_DESTROY_ON_SUBMIT_FAILURE_CFG_VAL is turned on then the
+ * job is deallocated automatically on submit failure.
+ * @param[out] job_id Unique job ID generated by @ref btmesh_conf_job_id_generator
+ * @returns Created configuration job.
+ * @retval NULL If the configuration job creation fails.
+ *
+ * Configuration job can be created if the tasks are created and linked first
+ * in order to provide the @p task_tree parameter. The created configuration
+ * job can be submitted for execution by calling @ref btmesh_conf_submit_job
+ * function.
+ ******************************************************************************/
+btmesh_conf_job_t *btmesh_conf_job_create(uint16_t enc_netkey_index,
+ uint16_t server_address,
+ btmesh_conf_task_t *task_tree,
+ btmesh_conf_on_job_notification_t on_job_notification,
+ btmesh_conf_varg_t job_status_param,
+ bool auto_destroy,
+ uint32_t *const job_id);
+
+/***************************************************************************//**
+ * Deallocates the BT Mesh Configuration Job instance and all of its tasks
+ *
+ * @param[in] self Pointer to the configuration job which shall be destroyed
+ ******************************************************************************/
+void btmesh_conf_job_destroy(btmesh_conf_job_t *const self);
+
+/***************************************************************************//**
+ * Return generated unique BT Mesh Configuration Job identifier.
+ *
+ * @returns Unique configuration job identifier
+ *
+ * Default implementation is provided in btmesh_conf_job.c and it generates job
+ * identifiers by incrementing the last one. If the deault implementation is not
+ * sufficient for the application then the function can be overridden in the
+ * application by function definition with strong symbol.
+ *
+ * @note Default implementation is provided with weak symbol.
+ ******************************************************************************/
+uint32_t btmesh_conf_job_id_generator(void);
+
+/***************************************************************************//**
+ * Set next BT Mesh configuration task based on the current active task of a job
+ *
+ * @param[in] self Pointer to the configuration job instance
+ * @return Status of the next task setup
+ * @retval SL_STATUS_OK If next task is set properly.
+ * @retval SL_STATUS_NOT_FOUND If next is not set because this was the last task.
+ * @retval SL_STATUS_FAIL If fatal error occurred.
+ ******************************************************************************/
+sl_status_t btmesh_conf_job_set_next_task(btmesh_conf_job_t *const self);
+
+/** @} (end addtogroup btmesh_conf_job) */
+/** @} (end addtogroup btmesh_conf) */
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* BTMESH_CONF_JOB_H */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_task.c b/app/btmesh/common_host/btmesh_conf/btmesh_conf_task.c
new file mode 100644
index 00000000000..85c017ab6ce
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_task.c
@@ -0,0 +1,3528 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component - Task
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#include "btmesh_conf_task.h"
+
+#include
+#include
+#include "sl_status.h"
+#include "sl_bt_api.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_capi_types.h"
+#include "app_log.h"
+#include "app_assert.h"
+#include "btmesh_db.h"
+#include "btmesh_conf.h"
+#include "btmesh_conf_task.h"
+#include "btmesh_conf_config.h"
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_task BT Mesh Configuration Task
+ * @{
+ ******************************************************************************/
+/// Local log new line shortcut definition
+#define NL APP_LOG_NL
+
+/***************************************************************************//**
+ * Provide 16-bit unsigned value from little endian byte array
+ *
+ * @param[in] ptr Minimum two byte long little endian byte array
+ * @returns 16-bit unsigned value read from byte array
+ ******************************************************************************/
+static uint16_t uint16_from_buf_le(const uint8_t *ptr)
+{
+ return ((uint16_t)ptr[0]) | ((uint16_t)ptr[1] << 8);
+}
+
+/***************************************************************************//**
+ * Collect all 16-bit unsigned values from a bytearray and append it to the
+ * passed unsigned 16-bit array by reallocating it to the proper size.
+ *
+ * @param[inout] buffer_ptr Pointer to the address of the existing dynamically
+ * allocated 16-bit array. The @p buffer_ptr shall have non-NULL value but
+ * the dereferenced value of @p buffer_ptr can be NULL.
+ * @param[inout] buffer_len Length of @p buffer_ptr 16-bit array.
+ * The new length of the reallocated buffer is stored here.
+ * @param[in] buffer_len_max Maximum length of @p buffer_ptr 16-bit array.
+ * @param[in] array_data Bytearray where the 16-bit unsigned values shall be
+ * collected from.
+ * @param[in] array_size Size of @p array_data bytearray.
+ * @returns Status of collection.
+ * @retval SL_STATUS_OK If every 16-bit value is collected from the bytearray
+ * and stored in the reallocated unsigned 16-bit array.
+ * @retval SL_STATUS_NULL_POINTER If @p buffer_ptr is NULL.
+ * @retval SL_STATUS_INVALID_COUNT If the @p buffer_len after reallocation would
+ * exceed @p buffer_len_max or the @p array_size is odd.
+ * @retval SL_STATUS_ALLOCATION_FAILED If the reallocation of the @p buffer_ptr
+ * fails. The original buffer is not deallocated in this case.
+ *
+ * The @p buffer_ptr pointer shall reference the address of an existing
+ * dynamically allocated (malloc, realloc, calloc) 16-bit array or the
+ * dereferenced value shall be a NULL pointer. If the dereferenced @p buffer_ptr
+ * is NULL then this function allocates a new buffer otherwise it reallocates an
+ * existing buffer. This behavior makes it possible to build an unsigned 16-bit
+ * array from multiple byte arrays by calling this function multiple times.
+ ******************************************************************************/
+static sl_status_t collect_uint16_le_data_from_array(uint16_t **const buffer_ptr,
+ uint16_t *const buffer_len,
+ uint16_t buffer_len_max,
+ const uint8_t *const array_data,
+ uint8_t array_size)
+{
+ const uint16_t elem_size = (uint16_t)sizeof(uint16_t);
+ sl_status_t collect_status = SL_STATUS_FAIL;
+ uint16_t current_buffer_len = *buffer_len;
+ uint32_t new_buffer_len = current_buffer_len + (array_size / elem_size);
+ if (NULL == buffer_ptr) {
+ // The buffer_ptr shall not be NULL because the pointer to reallocated
+ // unsigned 16-bit array shall be stored somewhere (avoid memory leaks)
+ collect_status = SL_STATUS_NULL_POINTER;
+ } else if ((0 != (array_size % elem_size))
+ || (buffer_len_max < new_buffer_len)) {
+ // The size of the new unsigned 16-bit array shall not exceed the maximum
+ // and the input bytearray which contains 16-bit values shall have even
+ // number of bytes.
+ collect_status = SL_STATUS_INVALID_COUNT;
+ } else {
+ // If the pointer parameter of realloc is NULL then it behaves like malloc
+ uint16_t *reallocated_buffer_ptr =
+ (uint16_t *)realloc(*buffer_ptr,
+ new_buffer_len * sizeof(uint16_t));
+
+ if (NULL == reallocated_buffer_ptr) {
+ collect_status = SL_STATUS_ALLOCATION_FAILED;
+ } else {
+ // Append the new 16-bit unsigned values
+ for (uint16_t data_idx = 0; data_idx < array_size; data_idx += 2) {
+ reallocated_buffer_ptr[current_buffer_len++] =
+ uint16_from_buf_le(&array_data[data_idx]);
+ }
+ *buffer_ptr = reallocated_buffer_ptr;
+ *buffer_len = current_buffer_len;
+ collect_status = SL_STATUS_OK;
+ }
+ }
+ return collect_status;
+}
+
+/***************************************************************************//**
+ * Process configuration request status from BT Mesh Stack API.
+ *
+ * @param[in] self Pointer to the configuration task
+ * @param[in] request_status Status code returned from BT Mesh Stack
+ * configuration request.
+ * @returns Mapped configuration request status.
+ * @retval SL_STATUS_OK If request is accepted.
+ * @retval SL_STATUS_BUSY If request is rejected due to busy lower layers.
+ * @retval SL_STATUS_FAIL If request is rejected due to unrecoverable error.
+ *
+ * The @ref btmesh_conf_task_t::result is set to @p request_status.
+ * The different BT Mesh Stack Configuration Client API functions have consistent
+ * return values so this function maps these common return values to match the
+ * interface description of @ref btmesh_conf_task_t::conf_request.
+ ******************************************************************************/
+static sl_status_t process_request_status_from_api(btmesh_conf_task_t *const self,
+ sl_status_t request_status)
+{
+ if (SL_STATUS_OK != request_status) {
+ // If BT Mesh Stack configuration request fails then the task fails so the
+ // configuration task result is set to specific BT Mesh Stack return value.
+ // If the configuration request is successful then configuration task result
+ // is not set because it will be determined by the BT Mesh Stack events.
+ self->result = request_status;
+ }
+
+ switch (request_status) {
+ case SL_STATUS_OK:
+ return request_status;
+ case SL_STATUS_NO_MORE_RESOURCE:
+ return SL_STATUS_BUSY;
+ default:
+ return SL_STATUS_FAIL;
+ }
+}
+
+/***************************************************************************//**
+ * Process event result from BT Mesh Stack configuration client events.
+ *
+ * @param[in] self Pointer to the configuration task
+ * @param[in] event_result Event result from the BT Mesh Stack configuration
+ * client event.
+ * @returns Mapped configuration event result.
+ * @retval SL_STATUS_OK If task is finished successfully.
+ * @retval SL_STATUS_TIMEOUT If task timed out while it was waiting for events.
+ * @retval SL_STATUS_FAIL If task failed due to server side error.
+ *
+ * The @ref btmesh_conf_task_t::result is set to @p event_result.
+ * Note: some BT Mesh Stack configuration client events does not have result
+ * member because these events are intermediate events.
+ ******************************************************************************/
+static sl_status_t process_event_result_from_api(btmesh_conf_task_t *const self,
+ sl_status_t event_result)
+{
+ self->result = event_result;
+
+ switch (event_result) {
+ case SL_STATUS_OK:
+ case SL_STATUS_TIMEOUT:
+ return event_result;
+ default:
+ return SL_STATUS_FAIL;
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Generic Configuration Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t btmesh_conf_task_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t btmesh_conf_task_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_create(btmesh_conf_task_id_t task_id)
+{
+ btmesh_conf_task_t *self;
+
+ self = malloc(sizeof(btmesh_conf_task_t));
+
+ if (NULL != self) {
+ self->task_id = task_id;
+ self->result = BTMESH_CONF_TASK_RESULT_UNKNOWN;
+ self->conf_request = btmesh_conf_task_request;
+ self->on_event = btmesh_conf_task_on_event;
+ self->to_string = btmesh_conf_task_to_string;
+ self->destroy = btmesh_conf_task_destroy;
+ self->next_on_success = NULL;
+ self->next_on_failure = NULL;
+ }
+ return self;
+}
+
+void btmesh_conf_task_destroy(btmesh_conf_task_t *self)
+{
+ // Destroy function shall behave as free if NULL pointer is passed
+ if (NULL == self) {
+ return;
+ }
+ // Avoid double destroy if next task is set unconditionally
+ if (self->next_on_failure == self->next_on_success
+ && NULL != self->next_on_success) {
+ self->next_on_failure->destroy(self->next_on_failure);
+ } else {
+ if (NULL != self->next_on_failure) {
+ self->next_on_failure->destroy(self->next_on_failure);
+ }
+ if (NULL != self->next_on_success) {
+ self->next_on_success->destroy(self->next_on_success);
+ }
+ }
+ free(self);
+}
+
+sl_status_t btmesh_conf_task_set_next_unconditional(btmesh_conf_task_t *self,
+ btmesh_conf_task_t *const next_task)
+{
+ if ((NULL == self) || (NULL == next_task)) {
+ return SL_STATUS_NULL_POINTER;
+ }
+ // The task tree shall be built from the root node to the leaf nodes which
+ // makes it possible to avoid cycle in the graph
+ if ((self == next_task)
+ || (next_task->next_on_success != NULL)
+ || (next_task->next_on_failure != NULL)) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+ self->next_on_failure = next_task;
+ self->next_on_success = next_task;
+ return SL_STATUS_OK;
+}
+
+sl_status_t btmesh_conf_task_set_next_on_success(btmesh_conf_task_t *self,
+ btmesh_conf_task_t *const next_task)
+{
+ if ((NULL == self) || (NULL == next_task)) {
+ return SL_STATUS_NULL_POINTER;
+ }
+ // The task tree shall be built from the root node to the leaf nodes which
+ // makes it possible to avoid cycle in the graph
+ if ((self == next_task)
+ || (next_task->next_on_success != NULL)
+ || (next_task->next_on_failure != NULL)) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+ self->next_on_success = next_task;
+ return SL_STATUS_OK;
+}
+
+sl_status_t btmesh_conf_task_set_next_on_failure(btmesh_conf_task_t *self,
+ btmesh_conf_task_t *const next_task)
+{
+ if ((NULL == self) || (NULL == next_task)) {
+ return SL_STATUS_NULL_POINTER;
+ }
+ // The task tree shall be built from the root node to the leaf nodes which
+ // makes it possible to avoid cycle in the graph
+ if ((self == next_task)
+ || (next_task->next_on_success != NULL)
+ || (next_task->next_on_failure != NULL)) {
+ return SL_STATUS_INVALID_PARAMETER;
+ }
+ self->next_on_failure = next_task;
+ return SL_STATUS_OK;
+}
+
+static sl_status_t btmesh_conf_task_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ return SL_STATUS_FAIL;
+}
+
+static sl_status_t btmesh_conf_task_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ return SL_STATUS_FAIL;
+}
+
+static int32_t btmesh_conf_task_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer, size, "generic (id=%d)", self->task_id);
+}
+
+// -----------------------------------------------------------------------------
+// Appkey Add Task Functions
+// -----------------------------------------------------------------------------
+
+static sl_status_t
+btmesh_conf_task_appkey_add_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_appkey_add_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_appkey_add_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_appkey_add_create(uint16_t appkey_index,
+ uint16_t netkey_index)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_APPKEY_ADD);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_appkey_add_request;
+ self->on_event = btmesh_conf_task_appkey_add_on_event;
+ self->destroy = btmesh_conf_task_appkey_add_destroy;
+ self->to_string = btmesh_conf_task_appkey_add_to_string;
+ self->ext.appkey.appkey_index = appkey_index;
+ self->ext.appkey.netkey_index = netkey_index;
+ }
+ return self;
+}
+
+void btmesh_conf_task_appkey_add_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_appkey_add_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint16_t appkey_index = self->ext.appkey.appkey_index;
+ uint16_t netkey_index = self->ext.appkey.netkey_index;
+
+ api_status = sl_btmesh_config_client_add_appkey(enc_netkey_index,
+ server_address,
+ appkey_index,
+ netkey_index,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_appkey_add_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_appkey_status_id:
+ {
+ const sl_btmesh_evt_config_client_appkey_status_t *appkey_status =
+ &evt->data.evt_config_client_appkey_status;
+
+ // If timeout is returned as event status then the upper layer might send
+ // the configuration message again. If configuration server receives the
+ // same Config AppKey Add message twice (same indexes with same appkey)
+ // then it sends back the Config AppKey Status message with success status.
+ sl_status_t event_result = appkey_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_appkey_add_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "appkey add (appkey_idx=%u,netkey_idx=%u)",
+ self->ext.appkey.appkey_index,
+ self->ext.appkey.netkey_index);
+}
+
+// -----------------------------------------------------------------------------
+// Appkey Remove Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_appkey_remove_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_appkey_remove_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_appkey_remove_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_appkey_remove_create(uint16_t appkey_index,
+ uint16_t netkey_index)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_APPKEY_REMOVE);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_appkey_remove_request;
+ self->on_event = btmesh_conf_task_appkey_remove_on_event;
+ self->destroy = btmesh_conf_task_appkey_remove_destroy;
+ self->to_string = btmesh_conf_task_appkey_remove_to_string;
+ self->ext.appkey.appkey_index = appkey_index;
+ self->ext.appkey.netkey_index = netkey_index;
+ }
+ return self;
+}
+
+void btmesh_conf_task_appkey_remove_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_appkey_remove_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint16_t appkey_index = self->ext.appkey.appkey_index;
+ uint16_t netkey_index = self->ext.appkey.netkey_index;
+
+ api_status = sl_btmesh_config_client_remove_appkey(enc_netkey_index,
+ server_address,
+ appkey_index,
+ netkey_index,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_appkey_remove_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_appkey_status_id:
+ {
+ const sl_btmesh_evt_config_client_appkey_status_t *appkey_status =
+ &evt->data.evt_config_client_appkey_status;
+
+ // If timeout is returned as event status then the upper layer might send
+ // the configuration message again. If configuration server receives the
+ // same Config AppKey Delete message twice (same indexes with same appkey)
+ // then it sends back the Config AppKey Status message with success status.
+ sl_status_t event_result = appkey_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_appkey_remove_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "appkey remove (appkey_idx=%u,netkey_idx=%u)",
+ self->ext.appkey.appkey_index,
+ self->ext.appkey.netkey_index);
+}
+
+// -----------------------------------------------------------------------------
+// Appkey List Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_appkey_list_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_appkey_list_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_appkey_list_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_appkey_list_create(uint16_t netkey_index)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_APPKEY_LIST);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_appkey_list_request;
+ self->on_event = btmesh_conf_task_appkey_list_on_event;
+ self->destroy = btmesh_conf_task_appkey_list_destroy;
+ self->to_string = btmesh_conf_task_appkey_list_to_string;
+ self->ext.appkey_list.netkey_index = netkey_index;
+ self->ext.appkey_list.appkey_indexes = NULL;
+ self->ext.appkey_list.appkey_count = 0;
+ }
+ return self;
+}
+
+void btmesh_conf_task_appkey_list_destroy(btmesh_conf_task_t *self)
+{
+ free(self->ext.appkey_list.appkey_indexes);
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_appkey_list_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint16_t netkey_index = self->ext.appkey_list.netkey_index;
+
+ // Make sure if this is a retry then dynamic memory allocated to store appkey
+ // data is deallocated in order to restore the initial state of the task.
+ if (0 < self->ext.appkey_list.appkey_count) {
+ free(self->ext.appkey_list.appkey_indexes);
+ self->ext.appkey_list.appkey_indexes = NULL;
+ self->ext.appkey_list.appkey_count = 0;
+ }
+
+ api_status = sl_btmesh_config_client_list_appkeys(enc_netkey_index,
+ server_address,
+ netkey_index,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_appkey_list_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_appkey_list_id:
+ {
+ const sl_btmesh_evt_config_client_appkey_list_t *appkey_list =
+ &evt->data.evt_config_client_appkey_list;
+ // This event might be generated more than once so it is possible that
+ // some appkeys has already been processed.
+
+ sl_status_t collect_status =
+ collect_uint16_le_data_from_array(&self->ext.appkey_list.appkey_indexes,
+ &self->ext.appkey_list.appkey_count,
+ BTMESH_CONF_MAX_APPKEY_COUNT,
+ appkey_list->appkey_indices.data,
+ appkey_list->appkey_indices.len);
+
+ if (SL_STATUS_OK != collect_status) {
+ app_log_status_error_f(collect_status,
+ "Node (netkey_idx=%d,addr=0x%04x) failed to "
+ "collect appkeys from appkey list event." NL,
+ enc_netkey_index,
+ server_address);
+ event_status = SL_STATUS_ABORT;
+ }
+ break;
+ }
+
+ case sl_btmesh_evt_config_client_appkey_list_end_id:
+ {
+ const sl_btmesh_evt_config_client_appkey_list_end_t *appkey_list_end =
+ &evt->data.evt_config_client_appkey_list_end;
+ sl_status_t event_result = appkey_list_end->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_appkey_list_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ int32_t required_space;
+ int32_t total_required_space = 0;
+ uint32_t remaining_size = size;
+ if (SL_STATUS_OK != self->result) {
+ // Response with the appkey list is not received from the remote node yet
+ return snprintf(buffer,
+ size,
+ "appkey list (netkey_idx=%u)",
+ self->ext.appkey_list.netkey_index);
+ }
+
+ required_space = snprintf(buffer,
+ size,
+ "appkey list (netkey_idx=%u,appkey_cnt=%d%s",
+ self->ext.appkey_list.netkey_index,
+ self->ext.appkey_list.appkey_count,
+ (0 == self->ext.appkey_list.appkey_count)
+ ? ")" : ",appkey_idxs=");
+ if (required_space < 0) {
+ return required_space;
+ } else if (remaining_size <= required_space) {
+ remaining_size = 0;
+ } else {
+ remaining_size -= required_space;
+ }
+ total_required_space += required_space;
+
+ uint16_t appkey_count = self->ext.appkey_list.appkey_count;
+ for (uint16_t idx = 0; idx < appkey_count; idx++) {
+ required_space = snprintf(&buffer[total_required_space],
+ remaining_size,
+ (idx != (appkey_count - 1)) ? "%d," : "%d)",
+ self->ext.appkey_list.appkey_indexes[idx]);
+ // The loop is not terminated if the buffer space runs out in order to
+ // calculate the total required buffer size.
+ // Note: return value of to_string functions shall be interpreted as snprintf
+ if (required_space < 0) {
+ return required_space;
+ } else if (remaining_size <= required_space) {
+ remaining_size = 0;
+ } else {
+ remaining_size -= required_space;
+ }
+ total_required_space += required_space;
+ }
+ return total_required_space;
+}
+
+// -----------------------------------------------------------------------------
+// Model Bind Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_bind_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_bind_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_bind_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_bind_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t appkey_index)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_BIND);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_bind_request;
+ self->on_event = btmesh_conf_task_model_bind_on_event;
+ self->destroy = btmesh_conf_task_model_bind_destroy;
+ self->to_string = btmesh_conf_task_model_bind_to_string;
+ self->ext.model_binding.elem_index = elem_index;
+ self->ext.model_binding.vendor_id = vendor_id;
+ self->ext.model_binding.model_id = model_id;
+ self->ext.model_binding.appkey_index = appkey_index;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_bind_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_bind_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t elem_index = self->ext.model_binding.elem_index;
+ uint16_t vendor_id = self->ext.model_binding.vendor_id;
+ uint16_t model_id = self->ext.model_binding.model_id;
+ uint16_t appkey_index = self->ext.model_binding.appkey_index;
+
+ api_status = sl_btmesh_config_client_bind_model(enc_netkey_index,
+ server_address,
+ elem_index,
+ vendor_id,
+ model_id,
+ appkey_index,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_bind_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_binding_status_id:
+ {
+ const sl_btmesh_evt_config_client_binding_status_t *binding_status =
+ &evt->data.evt_config_client_binding_status;
+
+ // If timeout is returned as event status then the upper layer might send
+ // the configuration message again. If configuration server receives the
+ // same Config Model App Bind message twice with same content then it
+ // sends back the Config Model App Status message with success status.
+ sl_status_t event_result = binding_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_bind_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "model bind (elem=%u,vendor=0x%04x,model=0x%04x,appkey_idx=%u)",
+ self->ext.model_binding.elem_index,
+ self->ext.model_binding.vendor_id,
+ self->ext.model_binding.model_id,
+ self->ext.model_binding.appkey_index);
+}
+
+// -----------------------------------------------------------------------------
+// Model Unbind Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_unbind_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_unbind_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_unbind_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_unbind_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t appkey_index)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_UNBIND);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_unbind_request;
+ self->on_event = btmesh_conf_task_model_unbind_on_event;
+ self->destroy = btmesh_conf_task_model_unbind_destroy;
+ self->to_string = btmesh_conf_task_model_unbind_to_string;
+ self->ext.model_binding.elem_index = elem_index;
+ self->ext.model_binding.vendor_id = vendor_id;
+ self->ext.model_binding.model_id = model_id;
+ self->ext.model_binding.appkey_index = appkey_index;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_unbind_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_unbind_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t elem_index = self->ext.model_binding.elem_index;
+ uint16_t vendor_id = self->ext.model_binding.vendor_id;
+ uint16_t model_id = self->ext.model_binding.model_id;
+ uint16_t appkey_index = self->ext.model_binding.appkey_index;
+
+ api_status = sl_btmesh_config_client_unbind_model(enc_netkey_index,
+ server_address,
+ elem_index,
+ vendor_id,
+ model_id,
+ appkey_index,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_unbind_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_binding_status_id:
+ {
+ const sl_btmesh_evt_config_client_binding_status_t *binding_status =
+ &evt->data.evt_config_client_binding_status;
+
+ // If timeout is returned as event status then the upper layer might send
+ // the configuration message again. If configuration server receives the
+ // same Config Model App Unbind message twice with same content then it
+ // sends back the Config Model App Status message with success status.
+ sl_status_t event_result = binding_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_unbind_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "model unbind (elem=%u,vendor=0x%04x,model=0x%04x,appkey_idx=%u)",
+ self->ext.model_binding.elem_index,
+ self->ext.model_binding.vendor_id,
+ self->ext.model_binding.model_id,
+ self->ext.model_binding.appkey_index);
+}
+
+// -----------------------------------------------------------------------------
+// Model Bindings List Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_bindings_list_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_bindings_list_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_bindings_list_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_bindings_list_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_BINDINGS_LIST);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_bindings_list_request;
+ self->on_event = btmesh_conf_task_model_bindings_list_on_event;
+ self->destroy = btmesh_conf_task_model_bindings_list_destroy;
+ self->to_string = btmesh_conf_task_model_bindings_list_to_string;
+ self->ext.model_bindings_list.elem_index = elem_index;
+ self->ext.model_bindings_list.vendor_id = vendor_id;
+ self->ext.model_bindings_list.model_id = model_id;
+ self->ext.model_bindings_list.appkey_indexes = NULL;
+ self->ext.model_bindings_list.appkey_count = 0;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_bindings_list_destroy(btmesh_conf_task_t *self)
+{
+ free(self->ext.model_bindings_list.appkey_indexes);
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_bindings_list_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t elem_index = self->ext.model_bindings_list.elem_index;
+ uint16_t vendor_id = self->ext.model_bindings_list.vendor_id;
+ uint16_t model_id = self->ext.model_bindings_list.model_id;
+
+ // Make sure if this is a retry then dynamic memory allocated to store appkey
+ // data is deallocated in order to restore the initial state of the task.
+ if (0 < self->ext.model_bindings_list.appkey_count) {
+ free(self->ext.model_bindings_list.appkey_indexes);
+ self->ext.model_bindings_list.appkey_indexes = NULL;
+ self->ext.model_bindings_list.appkey_count = 0;
+ }
+
+ api_status = sl_btmesh_config_client_list_bindings(enc_netkey_index,
+ server_address,
+ elem_index,
+ vendor_id,
+ model_id,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_bindings_list_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_bindings_list_id: {
+ const sl_btmesh_evt_config_client_bindings_list_t *bindings_list =
+ &evt->data.evt_config_client_bindings_list;
+ // This event might be generated more than once so it is possible that
+ // some appkeys has already been processed.
+ sl_status_t collect_status =
+ collect_uint16_le_data_from_array(&self->ext.model_bindings_list.appkey_indexes,
+ &self->ext.model_bindings_list.appkey_count,
+ BTMESH_CONF_MAX_APPKEY_COUNT,
+ bindings_list->appkey_indices.data,
+ bindings_list->appkey_indices.len);
+
+ if (SL_STATUS_OK != collect_status) {
+ app_log_status_error_f(collect_status,
+ "Node (netkey_idx=%d,addr=0x%04x) failed to "
+ "collect appkeys from bindings list event." NL,
+ enc_netkey_index,
+ server_address);
+ event_status = SL_STATUS_ABORT;
+ }
+ break;
+ }
+
+ case sl_btmesh_evt_config_client_bindings_list_end_id:
+ {
+ const sl_btmesh_evt_config_client_bindings_list_end_t *bindings_list_end =
+ &evt->data.evt_config_client_bindings_list_end;
+ sl_status_t event_result = bindings_list_end->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_bindings_list_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ int32_t required_space;
+ int32_t total_required_space = 0;
+ uint32_t remaining_size = size;
+ if (SL_STATUS_OK != self->result) {
+ // Response with the bindings list is not received from the remote node yet
+ return snprintf(buffer,
+ size,
+ "model bindings list (elem=%u,vendor=0x%04x,model=0x%04x)",
+ self->ext.model_bindings_list.elem_index,
+ self->ext.model_bindings_list.vendor_id,
+ self->ext.model_bindings_list.model_id);
+ }
+
+ required_space = snprintf(buffer,
+ size,
+ "model bindings list (elem=%u,vendor=0x%04x,"
+ "model=0x%04x,appkey_cnt=%d%s",
+ self->ext.model_bindings_list.elem_index,
+ self->ext.model_bindings_list.vendor_id,
+ self->ext.model_bindings_list.model_id,
+ self->ext.model_bindings_list.appkey_count,
+ (0 == self->ext.model_bindings_list.appkey_count)
+ ? ")" : ",appkey_idxs=");
+ if (required_space < 0) {
+ return required_space;
+ } else if (remaining_size <= required_space) {
+ remaining_size = 0;
+ } else {
+ remaining_size -= required_space;
+ }
+ total_required_space += required_space;
+
+ uint16_t appkey_count = self->ext.model_bindings_list.appkey_count;
+ for (uint16_t idx = 0; idx < appkey_count; idx++) {
+ required_space = snprintf(&buffer[total_required_space],
+ remaining_size,
+ (idx != (appkey_count - 1)) ? "%d," : "%d)",
+ self->ext.model_bindings_list.appkey_indexes[idx]);
+ // The loop is not terminated if the buffer space runs out in order to
+ // calculate the total required buffer size.
+ // Note: return value of to_string functions shall be interpreted as snprintf
+ if (required_space < 0) {
+ return required_space;
+ } else if (remaining_size <= required_space) {
+ remaining_size = 0;
+ } else {
+ remaining_size -= required_space;
+ }
+ total_required_space += required_space;
+ }
+ return total_required_space;
+}
+
+// -----------------------------------------------------------------------------
+// Model Publication Set Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_pub_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_pub_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_pub_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_pub_set_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t address,
+ uint16_t appkey_index,
+ uint8_t credentials,
+ uint8_t ttl,
+ uint32_t period_ms,
+ uint8_t retransmit_count,
+ uint16_t retransmit_interval_ms)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_PUB_SET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_pub_set_request;
+ self->on_event = btmesh_conf_task_model_pub_set_on_event;
+ self->destroy = btmesh_conf_task_model_pub_set_destroy;
+ self->to_string = btmesh_conf_task_model_pub_set_to_string;
+ self->ext.model_pub.elem_index = elem_index;
+ self->ext.model_pub.vendor_id = vendor_id;
+ self->ext.model_pub.model_id = model_id;
+ self->ext.model_pub.address = address;
+ self->ext.model_pub.appkey_index = appkey_index;
+ self->ext.model_pub.credentials = credentials;
+ self->ext.model_pub.ttl = ttl;
+ self->ext.model_pub.period_ms = period_ms;
+ self->ext.model_pub.retransmit_count = retransmit_count;
+ self->ext.model_pub.retransmit_interval_ms = retransmit_interval_ms;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_pub_set_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_pub_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ btmesh_conf_task_model_pub_t *model_pub = &self->ext.model_pub;
+ api_status = sl_btmesh_config_client_set_model_pub(enc_netkey_index,
+ server_address,
+ model_pub->elem_index,
+ model_pub->vendor_id,
+ model_pub->model_id,
+ model_pub->address,
+ model_pub->appkey_index,
+ model_pub->credentials,
+ model_pub->ttl,
+ model_pub->period_ms,
+ model_pub->retransmit_count,
+ model_pub->retransmit_interval_ms,
+ handle);
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_pub_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_model_pub_status_id:
+ {
+ const sl_btmesh_evt_config_client_model_pub_status_t *model_pub_status =
+ &evt->data.evt_config_client_model_pub_status;
+ sl_status_t event_result = model_pub_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_pub_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ btmesh_conf_task_model_pub_t *model_pub = &self->ext.model_pub;
+ return snprintf(buffer,
+ size,
+ "model pub set (elem=%u,vendor=0x%04x,model=0x%04x,addr=0x%04x,"
+ "appkey_idx=%u,cred=%u,ttl=%u,period_ms=%lums,tx_cnt=%u,tx_interval_ms=%u)",
+ model_pub->elem_index,
+ model_pub->vendor_id,
+ model_pub->model_id,
+ model_pub->address,
+ model_pub->appkey_index,
+ model_pub->credentials,
+ model_pub->ttl,
+ (unsigned long) model_pub->period_ms,
+ model_pub->retransmit_count,
+ model_pub->retransmit_interval_ms);
+}
+// -----------------------------------------------------------------------------
+// Model Publication Get Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_pub_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_pub_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_pub_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_pub_get_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_PUB_GET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_pub_get_request;
+ self->on_event = btmesh_conf_task_model_pub_get_on_event;
+ self->destroy = btmesh_conf_task_model_pub_get_destroy;
+ self->to_string = btmesh_conf_task_model_pub_get_to_string;
+ self->ext.model_pub.elem_index = elem_index;
+ self->ext.model_pub.vendor_id = vendor_id;
+ self->ext.model_pub.model_id = model_id;
+ self->ext.model_pub.address = BTMESH_CONF_PUB_ADDRESS_UNKNOWN;
+ self->ext.model_pub.appkey_index = BTMESH_CONF_PUB_APPKEY_INDEX_UNKNOWN;
+ self->ext.model_pub.credentials = BTMESH_CONF_PUB_CREDENTIALS_UNKNOWN;
+ self->ext.model_pub.ttl = BTMESH_CONF_PUB_TTL_UNKNOWN;
+ self->ext.model_pub.period_ms = BTMESH_CONF_PUB_PERIOD_MS_UNKNOWN;
+ self->ext.model_pub.retransmit_count = BTMESH_CONF_PUB_RETRANSMIT_CNT_UNKNOWN;
+ self->ext.model_pub.retransmit_interval_ms =
+ BTMESH_CONF_PUB_RETRANSMIT_INT_MS_UNKNOWN;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_pub_get_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_pub_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ btmesh_conf_task_model_pub_t *model_pub = &self->ext.model_pub;
+ api_status = sl_btmesh_config_client_get_model_pub(enc_netkey_index,
+ server_address,
+ model_pub->elem_index,
+ model_pub->vendor_id,
+ model_pub->model_id,
+ handle);
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_pub_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_model_pub_status_id:
+ {
+ const sl_btmesh_evt_config_client_model_pub_status_t *model_pub_status =
+ &evt->data.evt_config_client_model_pub_status;
+ sl_status_t event_result = model_pub_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ if (SL_STATUS_OK == event_result) {
+ self->ext.model_pub.address = model_pub_status->address;
+ self->ext.model_pub.appkey_index = model_pub_status->appkey_index;
+ self->ext.model_pub.credentials = model_pub_status->credentials;
+ self->ext.model_pub.ttl = model_pub_status->ttl;
+ self->ext.model_pub.period_ms = model_pub_status->period_ms;
+ self->ext.model_pub.retransmit_count =
+ model_pub_status->retransmit_count;
+ self->ext.model_pub.retransmit_interval_ms =
+ model_pub_status->retransmit_interval_ms;
+ }
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_pub_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ btmesh_conf_task_model_pub_t *model_pub = &self->ext.model_pub;
+ if (SL_STATUS_OK == self->result) {
+ return snprintf(buffer,
+ size,
+ "model pub get (elem=%u,vendor=0x%04x,model=0x%04x,addr=0x%04x,"
+ "appkey_idx=%u,cred=%u,ttl=%u,period_ms=%lums,tx_cnt=%u,tx_interval_ms=%u)",
+ model_pub->elem_index,
+ model_pub->vendor_id,
+ model_pub->model_id,
+ model_pub->address,
+ model_pub->appkey_index,
+ model_pub->credentials,
+ model_pub->ttl,
+ (unsigned long) model_pub->period_ms,
+ model_pub->retransmit_count,
+ model_pub->retransmit_interval_ms);
+ } else {
+ return snprintf(buffer,
+ size,
+ "model pub get (elem=%u,vendor=0x%04x,model=0x%04x)",
+ model_pub->elem_index,
+ model_pub->vendor_id,
+ model_pub->model_id);
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Model Subscription Add Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_sub_add_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_sub_add_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_sub_add_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_sub_add_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t sub_address)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_SUB_ADD);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_sub_add_request;
+ self->on_event = btmesh_conf_task_model_sub_add_on_event;
+ self->destroy = btmesh_conf_task_model_sub_add_destroy;
+ self->to_string = btmesh_conf_task_model_sub_add_to_string;
+ self->ext.model_sub.elem_index = elem_index;
+ self->ext.model_sub.vendor_id = vendor_id;
+ self->ext.model_sub.model_id = model_id;
+ self->ext.model_sub.sub_address = sub_address;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_sub_add_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_add_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ btmesh_conf_task_model_sub_t *model_sub = &self->ext.model_sub;
+ api_status = sl_btmesh_config_client_add_model_sub(enc_netkey_index,
+ server_address,
+ model_sub->elem_index,
+ model_sub->vendor_id,
+ model_sub->model_id,
+ model_sub->sub_address,
+ handle);
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_add_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_model_sub_status_id:
+ {
+ const sl_btmesh_evt_config_client_model_sub_status_t *model_sub_status =
+ &evt->data.evt_config_client_model_sub_status;
+ sl_status_t event_result = model_sub_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_sub_add_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ btmesh_conf_task_model_sub_t *model_sub = &self->ext.model_sub;
+ return snprintf(buffer,
+ size,
+ "model sub add (elem=%u,vendor=0x%04x,model=0x%04x,sub_addr=0x%04x)",
+ model_sub->elem_index,
+ model_sub->vendor_id,
+ model_sub->model_id,
+ model_sub->sub_address);
+}
+
+// -----------------------------------------------------------------------------
+// Model Subscription Remove Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_sub_remove_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_sub_remove_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_sub_remove_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_sub_remove_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t sub_address)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_SUB_REMOVE);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_sub_remove_request;
+ self->on_event = btmesh_conf_task_model_sub_remove_on_event;
+ self->destroy = btmesh_conf_task_model_sub_remove_destroy;
+ self->to_string = btmesh_conf_task_model_sub_remove_to_string;
+ self->ext.model_sub.elem_index = elem_index;
+ self->ext.model_sub.vendor_id = vendor_id;
+ self->ext.model_sub.model_id = model_id;
+ self->ext.model_sub.sub_address = sub_address;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_sub_remove_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_remove_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ btmesh_conf_task_model_sub_t *model_sub = &self->ext.model_sub;
+ api_status = sl_btmesh_config_client_remove_model_sub(enc_netkey_index,
+ server_address,
+ model_sub->elem_index,
+ model_sub->vendor_id,
+ model_sub->model_id,
+ model_sub->sub_address,
+ handle);
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_remove_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_model_sub_status_id:
+ {
+ const sl_btmesh_evt_config_client_model_sub_status_t *model_sub_status =
+ &evt->data.evt_config_client_model_sub_status;
+ sl_status_t event_result = model_sub_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_sub_remove_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ btmesh_conf_task_model_sub_t *model_sub = &self->ext.model_sub;
+ return snprintf(buffer,
+ size,
+ "model sub remove (elem=%u,vendor=0x%04x,model=0x%04x,sub_addr=0x%04x)",
+ model_sub->elem_index,
+ model_sub->vendor_id,
+ model_sub->model_id,
+ model_sub->sub_address);
+}
+
+// -----------------------------------------------------------------------------
+// Model Subscription Set Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_sub_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_sub_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_sub_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_sub_set_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t sub_address)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_SUB_SET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_sub_set_request;
+ self->on_event = btmesh_conf_task_model_sub_set_on_event;
+ self->destroy = btmesh_conf_task_model_sub_set_destroy;
+ self->to_string = btmesh_conf_task_model_sub_set_to_string;
+ self->ext.model_sub.elem_index = elem_index;
+ self->ext.model_sub.vendor_id = vendor_id;
+ self->ext.model_sub.model_id = model_id;
+ self->ext.model_sub.sub_address = sub_address;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_sub_set_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ btmesh_conf_task_model_sub_t *model_sub = &self->ext.model_sub;
+ api_status = sl_btmesh_config_client_set_model_sub(enc_netkey_index,
+ server_address,
+ model_sub->elem_index,
+ model_sub->vendor_id,
+ model_sub->model_id,
+ model_sub->sub_address,
+ handle);
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_model_sub_status_id:
+ {
+ const sl_btmesh_evt_config_client_model_sub_status_t *model_sub_status =
+ &evt->data.evt_config_client_model_sub_status;
+ sl_status_t event_result = model_sub_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_sub_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ btmesh_conf_task_model_sub_t *model_sub = &self->ext.model_sub;
+ return snprintf(buffer,
+ size,
+ "model sub set (elem=%u,vendor=0x%04x,model=0x%04x,sub_addr=0x%04x)",
+ model_sub->elem_index,
+ model_sub->vendor_id,
+ model_sub->model_id,
+ model_sub->sub_address);
+}
+
+// -----------------------------------------------------------------------------
+// Model Subscription Clear Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_sub_clear_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_sub_clear_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_sub_clear_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_sub_clear_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_SUB_CLEAR);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_sub_clear_request;
+ self->on_event = btmesh_conf_task_model_sub_clear_on_event;
+ self->destroy = btmesh_conf_task_model_sub_clear_destroy;
+ self->to_string = btmesh_conf_task_model_sub_clear_to_string;
+ self->ext.model_sub_clear.elem_index = elem_index;
+ self->ext.model_sub_clear.vendor_id = vendor_id;
+ self->ext.model_sub_clear.model_id = model_id;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_sub_clear_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_clear_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ btmesh_conf_task_model_sub_clear_t *model_sub_clear = &self->ext.model_sub_clear;
+ api_status = sl_btmesh_config_client_clear_model_sub(enc_netkey_index,
+ server_address,
+ model_sub_clear->elem_index,
+ model_sub_clear->vendor_id,
+ model_sub_clear->model_id,
+ handle);
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_clear_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_model_sub_status_id:
+ {
+ const sl_btmesh_evt_config_client_model_sub_status_t *model_sub_status =
+ &evt->data.evt_config_client_model_sub_status;
+ sl_status_t event_result = model_sub_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_sub_clear_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ btmesh_conf_task_model_sub_clear_t *model_sub_clear = &self->ext.model_sub_clear;
+ return snprintf(buffer,
+ size,
+ "model sub clear (elem=%u,vendor=0x%04x,model=0x%04x)",
+ model_sub_clear->elem_index,
+ model_sub_clear->vendor_id,
+ model_sub_clear->model_id);
+}
+
+// -----------------------------------------------------------------------------
+// Model Subscription List Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_model_sub_list_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_model_sub_list_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_model_sub_list_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_model_sub_list_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_MODEL_SUB_LIST);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_model_sub_list_request;
+ self->on_event = btmesh_conf_task_model_sub_list_on_event;
+ self->destroy = btmesh_conf_task_model_sub_list_destroy;
+ self->to_string = btmesh_conf_task_model_sub_list_to_string;
+ self->ext.model_sub_list.elem_index = elem_index;
+ self->ext.model_sub_list.vendor_id = vendor_id;
+ self->ext.model_sub_list.model_id = model_id;
+ self->ext.model_sub_list.addresses = NULL;
+ self->ext.model_sub_list.address_count = 0;
+ }
+ return self;
+}
+
+void btmesh_conf_task_model_sub_list_destroy(btmesh_conf_task_t *self)
+{
+ free(self->ext.model_sub_list.addresses);
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_list_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t elem_index = self->ext.model_sub_list.elem_index;
+ uint16_t vendor_id = self->ext.model_sub_list.vendor_id;
+ uint16_t model_id = self->ext.model_sub_list.model_id;
+
+ // Make sure if this is a retry then dynamic memory allocated to store address
+ // data is deallocated in order to restore the initial state of the task.
+ if (0 < self->ext.model_sub_list.address_count) {
+ free(self->ext.model_sub_list.addresses);
+ self->ext.model_sub_list.addresses = NULL;
+ self->ext.model_sub_list.address_count = 0;
+ }
+
+ api_status = sl_btmesh_config_client_list_subs(enc_netkey_index,
+ server_address,
+ elem_index,
+ vendor_id,
+ model_id,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_model_sub_list_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_subs_list_id: {
+ const sl_btmesh_evt_config_client_subs_list_t *subs_list =
+ &evt->data.evt_config_client_subs_list;
+ // This event might be generated more than once so it is possible that
+ // some addresses has already been processed.
+ sl_status_t collect_status =
+ collect_uint16_le_data_from_array(&self->ext.model_sub_list.addresses,
+ &self->ext.model_sub_list.address_count,
+ BTMESH_CONF_MAX_SUB_ADDR_COUNT,
+ subs_list->addresses.data,
+ subs_list->addresses.len);
+
+ if (SL_STATUS_OK != collect_status) {
+ app_log_status_error_f(collect_status,
+ "Node (netkey_idx=%d,addr=0x%04x) failed to "
+ "collect addresses from subs list event." NL,
+ enc_netkey_index,
+ server_address);
+ event_status = SL_STATUS_ABORT;
+ }
+ break;
+ }
+
+ case sl_btmesh_evt_config_client_subs_list_end_id:
+ {
+ const sl_btmesh_evt_config_client_subs_list_end_t *subs_list_end =
+ &evt->data.evt_config_client_subs_list_end;
+ sl_status_t event_result = subs_list_end->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_model_sub_list_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ int32_t required_space;
+ int32_t total_required_space = 0;
+ uint32_t remaining_size = size;
+ if (SL_STATUS_OK != self->result) {
+ // Response with the subs list is not received from the remote node yet
+ return snprintf(buffer,
+ size,
+ "model subs list (elem=%u,vendor=0x%04x,model=0x%04x)",
+ self->ext.model_sub_list.elem_index,
+ self->ext.model_sub_list.vendor_id,
+ self->ext.model_sub_list.model_id);
+ }
+
+ required_space = snprintf(buffer,
+ size,
+ "model subs list (elem=%u,vendor=0x%04x,"
+ "model=0x%04x,addr_cnt=%d%s",
+ self->ext.model_sub_list.elem_index,
+ self->ext.model_sub_list.vendor_id,
+ self->ext.model_sub_list.model_id,
+ self->ext.model_sub_list.address_count,
+ (0 == self->ext.model_sub_list.address_count)
+ ? ")" : ",addrs=");
+ if (required_space < 0) {
+ return required_space;
+ } else if (remaining_size <= required_space) {
+ remaining_size = 0;
+ } else {
+ remaining_size -= required_space;
+ }
+ total_required_space += required_space;
+
+ uint16_t addr_count = self->ext.model_sub_list.address_count;
+ for (uint16_t idx = 0; idx < addr_count; idx++) {
+ required_space = snprintf(&buffer[total_required_space],
+ remaining_size,
+ (idx != (addr_count - 1)) ? "0x%04x," : "0x%04x)",
+ self->ext.model_sub_list.addresses[idx]);
+ // The loop is not terminated if the buffer space runs out in order to
+ // calculate the total required buffer size.
+ // Note: return value of to_string functions shall be interpreted as snprintf
+ if (required_space < 0) {
+ return required_space;
+ } else if (remaining_size <= required_space) {
+ remaining_size = 0;
+ } else {
+ remaining_size -= required_space;
+ }
+ total_required_space += required_space;
+ }
+ return total_required_space;
+}
+
+// -----------------------------------------------------------------------------
+// Beacon Set Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_beacon_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_beacon_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_beacon_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_beacon_set_create(uint8_t value)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_BEACON_SET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_beacon_set_request;
+ self->on_event = btmesh_conf_task_beacon_set_on_event;
+ self->destroy = btmesh_conf_task_beacon_set_destroy;
+ self->to_string = btmesh_conf_task_beacon_set_to_string;
+ self->ext.beacon.value = value;
+ }
+ return self;
+}
+
+void btmesh_conf_task_beacon_set_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_beacon_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t value = self->ext.beacon.value;
+
+ api_status = sl_btmesh_config_client_set_beacon(enc_netkey_index,
+ server_address,
+ value,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_beacon_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_beacon_status_id:
+ {
+ const sl_btmesh_evt_config_client_beacon_status_t *beacon_status =
+ &evt->data.evt_config_client_beacon_status;
+
+ sl_status_t event_result = beacon_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_beacon_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "beacon set (value=%d)",
+ self->ext.beacon.value);
+}
+// -----------------------------------------------------------------------------
+// Beacon Get Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_beacon_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_beacon_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_beacon_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_beacon_get_create(void)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_BEACON_GET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_beacon_get_request;
+ self->on_event = btmesh_conf_task_beacon_get_on_event;
+ self->destroy = btmesh_conf_task_beacon_get_destroy;
+ self->to_string = btmesh_conf_task_beacon_get_to_string;
+ self->ext.beacon.value = BTMESH_CONF_BEACON_UNKNOWN;
+ }
+ return self;
+}
+
+void btmesh_conf_task_beacon_get_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_beacon_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+
+ api_status = sl_btmesh_config_client_get_beacon(enc_netkey_index,
+ server_address,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_beacon_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_beacon_status_id:
+ {
+ const sl_btmesh_evt_config_client_beacon_status_t *beacon_status =
+ &evt->data.evt_config_client_beacon_status;
+
+ sl_status_t event_result = beacon_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ if (SL_STATUS_OK == event_result) {
+ self->ext.beacon.value = beacon_status->value;
+ }
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_beacon_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ if (SL_STATUS_OK == self->result) {
+ return snprintf(buffer,
+ size,
+ "beacon get (value=%d)",
+ self->ext.beacon.value);
+ } else {
+ return snprintf(buffer,
+ size,
+ "beacon get");
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Default TTL Set Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_default_ttl_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_default_ttl_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_default_ttl_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_default_ttl_set_create(uint8_t default_ttl)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_DEFAULT_TTL_SET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_default_ttl_set_request;
+ self->on_event = btmesh_conf_task_default_ttl_set_on_event;
+ self->destroy = btmesh_conf_task_default_ttl_set_destroy;
+ self->to_string = btmesh_conf_task_default_ttl_set_to_string;
+ self->ext.default_ttl.ttl = default_ttl;
+ }
+ return self;
+}
+
+void btmesh_conf_task_default_ttl_set_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_default_ttl_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t default_ttl = self->ext.default_ttl.ttl;
+
+ api_status = sl_btmesh_config_client_set_default_ttl(enc_netkey_index,
+ server_address,
+ default_ttl,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_default_ttl_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_default_ttl_status_id:
+ {
+ const sl_btmesh_evt_config_client_default_ttl_status_t *default_ttl_status =
+ &evt->data.evt_config_client_default_ttl_status;
+
+ sl_status_t event_result = default_ttl_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_default_ttl_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "default ttl set (ttl=%d)",
+ self->ext.default_ttl.ttl);
+}
+// -----------------------------------------------------------------------------
+// Default TTL Get Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_default_ttl_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_default_ttl_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_default_ttl_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_default_ttl_get_create(void)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_DEFAULT_TTL_GET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_default_ttl_get_request;
+ self->on_event = btmesh_conf_task_default_ttl_get_on_event;
+ self->destroy = btmesh_conf_task_default_ttl_get_destroy;
+ self->to_string = btmesh_conf_task_default_ttl_get_to_string;
+ self->ext.default_ttl.ttl = BTMESH_CONF_DEFAULT_TTL_UNKNOWN;
+ }
+ return self;
+}
+
+void btmesh_conf_task_default_ttl_get_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_default_ttl_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+
+ api_status = sl_btmesh_config_client_get_default_ttl(enc_netkey_index,
+ server_address,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_default_ttl_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_default_ttl_status_id:
+ {
+ const sl_btmesh_evt_config_client_default_ttl_status_t *default_ttl_status =
+ &evt->data.evt_config_client_default_ttl_status;
+
+ sl_status_t event_result = default_ttl_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ if (SL_STATUS_OK == event_result) {
+ self->ext.default_ttl.ttl = default_ttl_status->value;
+ }
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_default_ttl_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ if (SL_STATUS_OK == self->result) {
+ return snprintf(buffer,
+ size,
+ "default ttl get (ttl=%d)",
+ self->ext.default_ttl.ttl);
+ } else {
+ return snprintf(buffer,
+ size,
+ "default ttl get");
+ }
+}
+
+// -----------------------------------------------------------------------------
+// GATT Proxy Set Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_gatt_proxy_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_gatt_proxy_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_gatt_proxy_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_gatt_proxy_set_create(uint8_t value)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_GATT_PROXY_SET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_gatt_proxy_set_request;
+ self->on_event = btmesh_conf_task_gatt_proxy_set_on_event;
+ self->destroy = btmesh_conf_task_gatt_proxy_set_destroy;
+ self->to_string = btmesh_conf_task_gatt_proxy_set_to_string;
+ self->ext.gatt_proxy.value = value;
+ }
+ return self;
+}
+
+void btmesh_conf_task_gatt_proxy_set_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_gatt_proxy_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t value = self->ext.gatt_proxy.value;
+
+ api_status = sl_btmesh_config_client_set_gatt_proxy(enc_netkey_index,
+ server_address,
+ value,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_gatt_proxy_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_gatt_proxy_status_id:
+ {
+ const sl_btmesh_evt_config_client_gatt_proxy_status_t *gatt_proxy_status =
+ &evt->data.evt_config_client_gatt_proxy_status;
+
+ sl_status_t event_result = gatt_proxy_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_gatt_proxy_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "gatt_proxy set (val=%d)",
+ self->ext.gatt_proxy.value);
+}
+
+// -----------------------------------------------------------------------------
+// GATT Proxy Get Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_gatt_proxy_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_gatt_proxy_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_gatt_proxy_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_gatt_proxy_get_create(void)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_GATT_PROXY_GET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_gatt_proxy_get_request;
+ self->on_event = btmesh_conf_task_gatt_proxy_get_on_event;
+ self->destroy = btmesh_conf_task_gatt_proxy_get_destroy;
+ self->to_string = btmesh_conf_task_gatt_proxy_get_to_string;
+ self->ext.gatt_proxy.value = BTMESH_CONF_PROXY_VALUE_UNKNOWN;
+ }
+ return self;
+}
+
+void btmesh_conf_task_gatt_proxy_get_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_gatt_proxy_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+
+ api_status = sl_btmesh_config_client_get_gatt_proxy(enc_netkey_index,
+ server_address,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_gatt_proxy_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_gatt_proxy_status_id:
+ {
+ const sl_btmesh_evt_config_client_gatt_proxy_status_t *gatt_proxy_status =
+ &evt->data.evt_config_client_gatt_proxy_status;
+
+ sl_status_t event_result = gatt_proxy_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ if (SL_STATUS_OK == event_result) {
+ self->ext.gatt_proxy.value = gatt_proxy_status->value;
+ }
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_gatt_proxy_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ if (SL_STATUS_OK == self->result) {
+ return snprintf(buffer,
+ size,
+ "gatt_proxy get (val=%d)",
+ self->ext.gatt_proxy.value);
+ } else {
+ return snprintf(buffer,
+ size,
+ "gatt_proxy get");
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Relay Set Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_relay_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_relay_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_relay_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_relay_set_create(uint8_t value,
+ uint8_t retransmit_count,
+ uint16_t retransmit_interval_ms)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_RELAY_SET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_relay_set_request;
+ self->on_event = btmesh_conf_task_relay_set_on_event;
+ self->destroy = btmesh_conf_task_relay_set_destroy;
+ self->to_string = btmesh_conf_task_relay_set_to_string;
+ self->ext.relay.value = value;
+ self->ext.relay.retransmit_count = retransmit_count;
+ self->ext.relay.retransmit_interval_ms = retransmit_interval_ms;
+ }
+ return self;
+}
+
+void btmesh_conf_task_relay_set_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_relay_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t value = self->ext.relay.value;
+ uint8_t retransmit_count = self->ext.relay.retransmit_count;
+ uint16_t retransmit_interval_ms = self->ext.relay.retransmit_interval_ms;
+
+ api_status = sl_btmesh_config_client_set_relay(enc_netkey_index,
+ server_address,
+ value,
+ retransmit_count,
+ retransmit_interval_ms,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_relay_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_relay_status_id:
+ {
+ const sl_btmesh_evt_config_client_relay_status_t *relay_status =
+ &evt->data.evt_config_client_relay_status;
+
+ sl_status_t event_result = relay_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_relay_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "relay set (val=%d,cnt=%d,interval=%dms)",
+ self->ext.relay.value,
+ self->ext.relay.retransmit_count,
+ self->ext.relay.retransmit_interval_ms);
+}
+
+// -----------------------------------------------------------------------------
+// Relay Get Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_relay_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_relay_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_relay_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_relay_get_create(void)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_RELAY_GET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_relay_get_request;
+ self->on_event = btmesh_conf_task_relay_get_on_event;
+ self->destroy = btmesh_conf_task_relay_get_destroy;
+ self->to_string = btmesh_conf_task_relay_get_to_string;
+ self->ext.relay.value = BTMESH_CONF_RELAY_VALUE_UNKNOWN;
+ self->ext.relay.retransmit_count = BTMESH_CONF_RELAY_RETRANSMIT_CNT_UNKNOWN;
+ self->ext.relay.retransmit_interval_ms =
+ BTMESH_CONF_RELAY_RETRANSMIT_INT_MS_UNKNOWN;
+ }
+ return self;
+}
+
+void btmesh_conf_task_relay_get_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_relay_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+
+ api_status = sl_btmesh_config_client_get_relay(enc_netkey_index,
+ server_address,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_relay_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_relay_status_id:
+ {
+ const sl_btmesh_evt_config_client_relay_status_t *relay_status =
+ &evt->data.evt_config_client_relay_status;
+
+ sl_status_t event_result = relay_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ if (SL_STATUS_OK == event_result) {
+ self->ext.relay.value = relay_status->relay;
+ self->ext.relay.retransmit_count = relay_status->retransmit_count;
+ self->ext.relay.retransmit_interval_ms =
+ relay_status->retransmit_interval_ms;
+ }
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_relay_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ if (SL_STATUS_OK == self->result) {
+ return snprintf(buffer,
+ size,
+ "relay get (val=%d,cnt=%d,interval=%dms)",
+ self->ext.relay.value,
+ self->ext.relay.retransmit_count,
+ self->ext.relay.retransmit_interval_ms);
+ } else {
+ return snprintf(buffer,
+ size,
+ "relay get");
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Network Transmit Count Set Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_network_transmit_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_network_transmit_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_network_transmit_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_network_transmit_set_create(uint8_t transmit_count,
+ uint16_t transmit_interval_ms)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_NETWORK_TRANSMIT_SET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_network_transmit_set_request;
+ self->on_event = btmesh_conf_task_network_transmit_set_on_event;
+ self->destroy = btmesh_conf_task_network_transmit_set_destroy;
+ self->to_string = btmesh_conf_task_network_transmit_set_to_string;
+ self->ext.network_transmit.transmit_count = transmit_count;
+ self->ext.network_transmit.transmit_interval_ms = transmit_interval_ms;
+ }
+ return self;
+}
+
+void btmesh_conf_task_network_transmit_set_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_network_transmit_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t transmit_count = self->ext.network_transmit.transmit_count;
+ uint16_t transmit_interval_ms =
+ self->ext.network_transmit.transmit_interval_ms;
+
+ api_status = sl_btmesh_config_client_set_network_transmit(enc_netkey_index,
+ server_address,
+ transmit_count,
+ transmit_interval_ms,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_network_transmit_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_network_transmit_status_id:
+ {
+ const sl_btmesh_evt_config_client_network_transmit_status_t *network_transmit_status =
+ &evt->data.evt_config_client_network_transmit_status;
+
+ sl_status_t event_result = network_transmit_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_network_transmit_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "network transmit set (cnt=%d,interval=%dms)",
+ self->ext.network_transmit.transmit_count,
+ self->ext.network_transmit.transmit_interval_ms);
+}
+
+// -----------------------------------------------------------------------------
+// Network Transmit Count Get Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_network_transmit_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_network_transmit_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_network_transmit_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_network_transmit_get_create(void)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_NETWORK_TRANSMIT_GET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_network_transmit_get_request;
+ self->on_event = btmesh_conf_task_network_transmit_get_on_event;
+ self->destroy = btmesh_conf_task_network_transmit_get_destroy;
+ self->to_string = btmesh_conf_task_network_transmit_get_to_string;
+ self->ext.network_transmit.transmit_count = BTMESH_CONF_NW_TRANSMIT_CNT_UNKNOWN;
+ self->ext.network_transmit.transmit_interval_ms =
+ BTMESH_CONF_NW_TRANSMIT_INT_MS_UNKNOWN;
+ }
+ return self;
+}
+
+void btmesh_conf_task_network_transmit_get_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_network_transmit_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+
+ api_status = sl_btmesh_config_client_get_network_transmit(enc_netkey_index,
+ server_address,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_network_transmit_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_network_transmit_status_id:
+ {
+ const sl_btmesh_evt_config_client_network_transmit_status_t *network_transmit_status =
+ &evt->data.evt_config_client_network_transmit_status;
+
+ sl_status_t event_result = network_transmit_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ if (SL_STATUS_OK == event_result) {
+ self->ext.network_transmit.transmit_count =
+ network_transmit_status->transmit_count;
+ self->ext.network_transmit.transmit_interval_ms =
+ network_transmit_status->transmit_interval_ms;
+ }
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_network_transmit_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ if (SL_STATUS_OK == self->result) {
+ return snprintf(buffer,
+ size,
+ "network transmit get (cnt=%d,interval=%dms)",
+ self->ext.network_transmit.transmit_count,
+ self->ext.network_transmit.transmit_interval_ms);
+ } else {
+ return snprintf(buffer,
+ size,
+ "network transmit get");
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Friend Set Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_friend_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_friend_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_friend_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_friend_set_create(uint8_t value)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_FRIEND_SET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_friend_set_request;
+ self->on_event = btmesh_conf_task_friend_set_on_event;
+ self->destroy = btmesh_conf_task_friend_set_destroy;
+ self->to_string = btmesh_conf_task_friend_set_to_string;
+ self->ext.friend.value = value;
+ }
+ return self;
+}
+
+void btmesh_conf_task_friend_set_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_friend_set_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+ uint8_t value = self->ext.friend.value;
+
+ api_status = sl_btmesh_config_client_set_friend(enc_netkey_index,
+ server_address,
+ value,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_friend_set_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_friend_status_id:
+ {
+ const sl_btmesh_evt_config_client_friend_status_t *friend_status =
+ &evt->data.evt_config_client_friend_status;
+
+ sl_status_t event_result = friend_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_friend_set_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "friend set (val=%d)",
+ self->ext.friend.value);
+}
+
+// -----------------------------------------------------------------------------
+// Friend Get Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_friend_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_friend_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_friend_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_friend_get_create(void)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_FRIEND_GET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_friend_get_request;
+ self->on_event = btmesh_conf_task_friend_get_on_event;
+ self->destroy = btmesh_conf_task_friend_get_destroy;
+ self->to_string = btmesh_conf_task_friend_get_to_string;
+ self->ext.friend.value = BTMESH_CONF_FRIEND_VALUE_UNKNOWN;
+ }
+ return self;
+}
+
+void btmesh_conf_task_friend_get_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_friend_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+
+ api_status = sl_btmesh_config_client_get_friend(enc_netkey_index,
+ server_address,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_friend_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_friend_status_id:
+ {
+ const sl_btmesh_evt_config_client_friend_status_t *friend_status =
+ &evt->data.evt_config_client_friend_status;
+
+ sl_status_t event_result = friend_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ if (SL_STATUS_OK == event_result) {
+ self->ext.friend.value = friend_status->value;
+ }
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_friend_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ if (SL_STATUS_OK == self->result) {
+ return snprintf(buffer,
+ size,
+ "friend get (val=%d)",
+ self->ext.friend.value);
+ } else {
+ return snprintf(buffer,
+ size,
+ "friend get");
+ }
+}
+
+// -----------------------------------------------------------------------------
+// DCD Get Task Functions
+// -----------------------------------------------------------------------------
+
+#define DCD_FEATURE_RELAY_BYTE 0x00
+#define DCD_FEATURE_PROXY_BYTE 0x00
+#define DCD_FEATURE_FRIEND_BYTE 0x00
+#define DCD_FEATURE_LOW_POWER_BYTE 0x00
+
+#define DCD_FEATURE_RELAY_MASK 0x01
+#define DCD_FEATURE_PROXY_MASK 0x02
+#define DCD_FEATURE_FRIEND_MASK 0x04
+#define DCD_FEATURE_LOW_POWER_MASK 0x08
+
+#define DCD_RAW_VENDOR_ID_SIZE 2
+
+typedef struct btmesh_conf_dcd_raw_element_t {
+ uint8_t location[2];
+ uint8_t num_sig_model_ids;
+ uint8_t num_vendor_model_ids;
+ uint8_t models[];
+} btmesh_conf_dcd_raw_element_t;
+
+typedef struct btmesh_conf_dcd_raw_header_t {
+ uint8_t cid[2];
+ uint8_t pid[2];
+ uint8_t vid[2];
+ uint8_t crpl[2];
+ uint8_t features[2];
+ uint8_t elements[];
+} btmesh_conf_dcd_raw_header_t;
+
+typedef enum btmesh_conf_task_dcd_iter_cmd_t {
+ BTMESH_CONF_TASK_DCD_ITER_CMD_CHECK_SIZE,
+ BTMESH_CONF_TASK_DCD_ITER_CMD_UPDATE_DB
+} btmesh_conf_task_dcd_iter_cmd_t;
+
+static sl_status_t btmesh_conf_task_dcd_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t btmesh_conf_task_dcd_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static sl_status_t btmesh_conf_task_dcd_process(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address);
+
+static sl_status_t btmesh_conf_task_dcd_element_iterate(uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const uint8_t *raw_dcd_data,
+ uint16_t raw_dcd_data_size,
+ btmesh_conf_task_dcd_iter_cmd_t command);
+
+static int32_t btmesh_conf_task_dcd_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_dcd_get_create(uint8_t page)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_DCD_GET);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_dcd_get_request;
+ self->on_event = btmesh_conf_task_dcd_get_on_event;
+ self->to_string = btmesh_conf_task_dcd_get_to_string;
+ self->destroy = btmesh_conf_task_dcd_get_destroy;
+ self->ext.dcd.page = page;
+ self->ext.dcd.raw_dcd_data = NULL;
+ self->ext.dcd.raw_dcd_data_size = 0;
+ }
+ return self;
+}
+
+void btmesh_conf_task_dcd_get_destroy(btmesh_conf_task_t *self)
+{
+ free(self->ext.dcd.raw_dcd_data);
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t btmesh_conf_task_dcd_get_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+
+ app_log_debug("Config task get dcd request "
+ "(nwkey=%d,addr=0x%04x,page=%d)" NL,
+ enc_netkey_index,
+ server_address,
+ self->ext.dcd.page);
+ // Make sure if this is a retry then dynamic memory allocated to store DCD data
+ // is deallocated in order to restore the initial state of the task.
+ if (0 != self->ext.dcd.raw_dcd_data_size) {
+ free(self->ext.dcd.raw_dcd_data);
+ self->ext.dcd.raw_dcd_data = NULL;
+ self->ext.dcd.raw_dcd_data_size = 0;
+ }
+
+ api_status = sl_btmesh_config_client_get_dcd(enc_netkey_index,
+ server_address,
+ self->ext.dcd.page,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t btmesh_conf_task_dcd_get_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_dcd_data_id:
+ {
+ const sl_btmesh_evt_config_client_dcd_data_t *evt_dcd_data =
+ &evt->data.evt_config_client_dcd_data;
+
+ const uint8_t *dcd_chunk_data = evt_dcd_data->data.data;
+ uint16_t dcd_chunk_size = evt_dcd_data->data.len;
+ uint16_t current_raw_dcd_data_size = self->ext.dcd.raw_dcd_data_size;
+ uint16_t new_raw_dcd_data_size = current_raw_dcd_data_size + dcd_chunk_size;
+ // If the pointer parameter of realloc is NULL then it behaves like malloc
+ uint8_t *reallocated_dcd_data_ptr = realloc(self->ext.dcd.raw_dcd_data,
+ new_raw_dcd_data_size);
+
+ if (NULL == reallocated_dcd_data_ptr) {
+ // The get dcd task shall be cancelled in the stack due to memory issues
+ // on the host machine. The allocated memory will be released when the
+ // task object is destroyed.
+ event_status = SL_STATUS_ABORT;
+ self->result = SL_STATUS_ALLOCATION_FAILED;
+ } else {
+ self->ext.dcd.raw_dcd_data = reallocated_dcd_data_ptr;
+ memcpy(&self->ext.dcd.raw_dcd_data[current_raw_dcd_data_size],
+ dcd_chunk_data,
+ dcd_chunk_size);
+ self->ext.dcd.raw_dcd_data_size = new_raw_dcd_data_size;
+ }
+ break;
+ }
+
+ case sl_btmesh_evt_config_client_dcd_data_end_id:
+ {
+ const sl_btmesh_evt_config_client_dcd_data_end_t *dcd_data_end_evt =
+ &evt->data.evt_config_client_dcd_data_end;
+
+ sl_status_t event_result = dcd_data_end_evt->result;
+ event_status = process_event_result_from_api(self, event_result);
+
+ if (SL_STATUS_OK == event_result) {
+ sl_status_t process_dcd_result;
+ process_dcd_result = btmesh_conf_task_dcd_process(self,
+ enc_netkey_index,
+ server_address);
+ self->result = process_dcd_result;
+ event_status = (SL_STATUS_OK == process_dcd_result)
+ ? SL_STATUS_OK : SL_STATUS_FAIL;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return event_status;
+}
+
+static sl_status_t btmesh_conf_task_dcd_process(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address)
+{
+ sl_status_t process_dcd_status = SL_STATUS_OK;
+ btmesh_db_node_t *node = btmesh_db_node_get_by_addr(server_address);
+ if (NULL == node) {
+ process_dcd_status = SL_STATUS_NOT_FOUND;
+ } else if (false != node->dcd_available) {
+ process_dcd_status = SL_STATUS_INVALID_STATE;
+ } else {
+ char node_str[SL_BTMESH_CONF_MAX_LOG_MSG_FRAGMENT_SIZE_CFG_VAL];
+ uint16_t remaining_size = self->ext.dcd.raw_dcd_data_size;
+ btmesh_conf_dcd_raw_header_t *dcd_raw_hdr =
+ (btmesh_conf_dcd_raw_header_t*) self->ext.dcd.raw_dcd_data;
+
+ if (remaining_size < sizeof(btmesh_conf_dcd_raw_header_t)) {
+ process_dcd_status = SL_STATUS_INVALID_COUNT;
+ } else {
+ if (btmesh_conf_any_severe_log_level(APP_LOG_LEVEL_INFO)) {
+ int32_t str_retval;
+ str_retval = snprintf(node_str,
+ sizeof(node_str),
+ "Node (netkey_idx=%d,addr=0x%04x)",
+ enc_netkey_index,
+ server_address);
+ app_assert(0 < str_retval, "String formatting failed." NL);
+ }
+ node_str[sizeof(node_str) - 1] = '\0';
+
+ uint16_t company_id = uint16_from_buf_le(&dcd_raw_hdr->cid[0]);
+ uint16_t product_id = uint16_from_buf_le(&dcd_raw_hdr->pid[0]);
+ uint16_t version_id = uint16_from_buf_le(&dcd_raw_hdr->vid[0]);
+ uint16_t min_replay_prot_list_len = uint16_from_buf_le(&dcd_raw_hdr->crpl[0]);
+
+ bool feature_relay = dcd_raw_hdr->features[DCD_FEATURE_RELAY_BYTE]
+ & DCD_FEATURE_RELAY_MASK;
+
+ bool feature_proxy = dcd_raw_hdr->features[DCD_FEATURE_PROXY_BYTE]
+ & DCD_FEATURE_PROXY_MASK;
+
+ bool feature_friend = dcd_raw_hdr->features[DCD_FEATURE_FRIEND_BYTE]
+ & DCD_FEATURE_FRIEND_MASK;
+
+ bool feature_low_power = dcd_raw_hdr->features[DCD_FEATURE_LOW_POWER_BYTE]
+ & DCD_FEATURE_LOW_POWER_MASK;
+
+ sl_status_t dcd_status = btmesh_db_node_dcd_set_header(server_address,
+ company_id,
+ product_id,
+ version_id,
+ min_replay_prot_list_len,
+ feature_relay,
+ feature_proxy,
+ feature_friend,
+ feature_low_power);
+ app_log_info("%s DCD company id: 0x%04x" NL, node_str, company_id);
+ app_log_info("%s DCD product id: 0x%04x" NL, node_str, product_id);
+ app_log_info("%s DCD version id: 0x%04x" NL, node_str, version_id);
+ app_log_info("%s DCD min replay prot list length: %d" NL,
+ node_str,
+ min_replay_prot_list_len);
+ app_log_info("%s DCD feature relay: %d" NL, node_str, feature_relay);
+ app_log_info("%s DCD feature proxy: %d" NL, node_str, feature_proxy);
+ app_log_info("%s DCD feature friend: %d" NL, node_str, feature_friend);
+ app_log_info("%s DCD feature lpn: %d" NL, node_str, feature_low_power);
+ if (SL_STATUS_OK != process_dcd_status) {
+ process_dcd_status = dcd_status;
+ } else {
+ sl_status_t check_dcd_status =
+ btmesh_conf_task_dcd_element_iterate(enc_netkey_index,
+ server_address,
+ self->ext.dcd.raw_dcd_data,
+ self->ext.dcd.raw_dcd_data_size,
+ BTMESH_CONF_TASK_DCD_ITER_CMD_CHECK_SIZE);
+ if (SL_STATUS_OK != check_dcd_status) {
+ process_dcd_status = dcd_status;
+ } else {
+ sl_status_t update_db_status =
+ btmesh_conf_task_dcd_element_iterate(enc_netkey_index,
+ server_address,
+ self->ext.dcd.raw_dcd_data,
+ self->ext.dcd.raw_dcd_data_size,
+ BTMESH_CONF_TASK_DCD_ITER_CMD_UPDATE_DB);
+ process_dcd_status = update_db_status;
+ if (SL_STATUS_OK != update_db_status) {
+ sl_status_t dcd_clear_status = btmesh_db_node_dcd_clear(server_address);
+ app_assert_status_f(dcd_clear_status,
+ "Failed to clear DCD of node (addr=0x%04x)" NL,
+ server_address);
+ } else {
+ sl_status_t dcd_available_status =
+ btmesh_db_node_dcd_set_available(server_address);
+ // This call should not fail because it can fail only if the node
+ // does not exists but in that case the previous calls would have
+ // failed as well.
+ app_assert_status_f(dcd_available_status,
+ "Failed to set DCD available of node (addr=0x%04x)" NL,
+ server_address);
+ }
+ }
+ }
+ }
+ }
+ return process_dcd_status;
+}
+
+/// Iterate over the elements and models of each element in the DCD and performs
+/// the following operations:
+/// - It checks if the size of the DCD raw data is consistent with its content
+/// - If @p command is set to BTMESH_CONF_TASK_DCD_ITER_CMD_UPDATE_DB then
+/// it updates the BT Mesh database with elements and models parsed from
+/// the raw DCD data. The parsed elements and models are logged.
+static sl_status_t btmesh_conf_task_dcd_element_iterate(uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const uint8_t *raw_dcd_data,
+ uint16_t raw_dcd_data_size,
+ btmesh_conf_task_dcd_iter_cmd_t command)
+{
+ const uint16_t sig_model_size = 2, vendor_model_size = 4;
+ uint16_t remaining_size = raw_dcd_data_size;
+ uint16_t data_idx = 0;
+ uint16_t element_index = 0;
+ char node_str[SL_BTMESH_CONF_MAX_LOG_MSG_FRAGMENT_SIZE_CFG_VAL];
+
+ if (remaining_size < sizeof(btmesh_conf_dcd_raw_header_t)) {
+ return SL_STATUS_INVALID_COUNT;
+ }
+ remaining_size -= sizeof(btmesh_conf_dcd_raw_header_t);
+ data_idx += sizeof(btmesh_conf_dcd_raw_header_t);
+
+ if (btmesh_conf_any_severe_log_level(APP_LOG_LEVEL_INFO)) {
+ int32_t str_retval;
+ str_retval = snprintf(node_str,
+ sizeof(node_str),
+ "Node (netkey_idx=%d,addr=0x%04x)",
+ enc_netkey_index,
+ server_address);
+ app_assert(0 < str_retval, "String formatting failed." NL);
+ }
+ node_str[sizeof(node_str) - 1] = '\0';
+
+ while (0 < remaining_size) {
+ uint16_t total_element_size;
+
+ if (remaining_size < sizeof(btmesh_conf_dcd_raw_element_t)) {
+ return SL_STATUS_INVALID_COUNT;
+ }
+ const btmesh_conf_dcd_raw_element_t *element =
+ (const btmesh_conf_dcd_raw_element_t *)&raw_dcd_data[data_idx];
+
+ uint8_t num_sig_model_ids = element->num_sig_model_ids;
+ uint8_t num_vendor_model_ids = element->num_vendor_model_ids;
+ uint16_t element_model_data_size = (num_sig_model_ids * sig_model_size)
+ + (num_vendor_model_ids * vendor_model_size);
+
+ total_element_size = sizeof(btmesh_conf_dcd_raw_element_t)
+ + element_model_data_size;
+
+ if (remaining_size < total_element_size) {
+ return SL_STATUS_INVALID_COUNT;
+ }
+
+ if (BTMESH_CONF_TASK_DCD_ITER_CMD_UPDATE_DB == command) {
+ uint16_t location = uint16_from_buf_le(&element->location[0]);
+ sl_status_t add_elem_status = btmesh_db_node_dcd_add_element(server_address,
+ location);
+ app_log_info("%s DCD element index %u with location 0x%04x "
+ "(sig_models=%u,vendor_models=%u)" NL,
+ node_str,
+ element_index,
+ location,
+ num_sig_model_ids,
+ num_vendor_model_ids);
+ if (SL_STATUS_OK != add_elem_status) {
+ return add_elem_status;
+ }
+
+ const uint8_t *models_data = element->models;
+ uint16_t model_data_idx;
+ btmesh_db_model_id_t model_id;
+
+ // Process SIG models
+ for (model_data_idx = 0;
+ model_data_idx < (num_sig_model_ids * sig_model_size);
+ model_data_idx += sig_model_size) {
+ model_id.vendor = MESH_SPEC_VENDOR_ID;
+ model_id.model = uint16_from_buf_le(&models_data[model_data_idx]);
+ sl_status_t add_mdl_status = btmesh_db_node_dcd_add_model(server_address,
+ element_index,
+ model_id);
+ app_log_info("%s DCD SIG model 0x%04x-%s (elem=%u)" NL,
+ node_str,
+ model_id.model,
+ btmesh_conf_sig_model_id_to_string(model_id.model),
+ element_index);
+ if (SL_STATUS_OK != add_mdl_status) {
+ return add_mdl_status;
+ }
+ }
+
+ // Process vendor models
+ for (; model_data_idx < element_model_data_size;
+ model_data_idx += vendor_model_size) {
+ model_id.vendor = uint16_from_buf_le(&models_data[model_data_idx]);
+ model_id.model = uint16_from_buf_le(&models_data[model_data_idx + sizeof(uint16_t)]);
+ sl_status_t add_mdl_status = btmesh_db_node_dcd_add_model(server_address,
+ element_index,
+ model_id);
+ app_log_info("%s DCD vendor model 0x%04x (elem=%u,vendor=0x%04x)" NL,
+ node_str,
+ model_id.model,
+ element_index,
+ model_id.vendor);
+ if (SL_STATUS_OK != add_mdl_status) {
+ return add_mdl_status;
+ }
+ }
+ }
+
+ remaining_size -= total_element_size;
+ data_idx += total_element_size;
+ element_index++;
+ }
+ return SL_STATUS_OK;
+}
+
+static int32_t btmesh_conf_task_dcd_get_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "DCD get (page=%d)",
+ self->ext.dcd.page);
+}
+
+// -----------------------------------------------------------------------------
+// Reset Node Task Functions
+// -----------------------------------------------------------------------------
+static sl_status_t
+btmesh_conf_task_reset_node_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+
+static sl_status_t
+btmesh_conf_task_reset_node_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+
+static int32_t btmesh_conf_task_reset_node_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+
+btmesh_conf_task_t *btmesh_conf_task_reset_node_create(void)
+{
+ btmesh_conf_task_t *self;
+ self = btmesh_conf_task_create(BTMESH_CONF_TASK_ID_RESET_NODE);
+ if (NULL != self) {
+ self->conf_request = btmesh_conf_task_reset_node_request;
+ self->on_event = btmesh_conf_task_reset_node_on_event;
+ self->destroy = btmesh_conf_task_reset_node_destroy;
+ self->to_string = btmesh_conf_task_reset_node_to_string;
+ }
+ return self;
+}
+
+void btmesh_conf_task_reset_node_destroy(btmesh_conf_task_t *self)
+{
+ btmesh_conf_task_destroy(self);
+}
+
+static sl_status_t
+btmesh_conf_task_reset_node_request(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle)
+{
+ sl_status_t request_status, api_status;
+
+ api_status = sl_btmesh_config_client_reset_node(enc_netkey_index,
+ server_address,
+ handle);
+
+ request_status = process_request_status_from_api(self, api_status);
+ return request_status;
+}
+
+static sl_status_t
+btmesh_conf_task_reset_node_on_event(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt)
+{
+ sl_status_t event_status = SL_STATUS_IN_PROGRESS;
+
+ switch (SL_BT_MSG_ID(evt->header)) {
+ case sl_btmesh_evt_config_client_reset_status_id:
+ {
+ const sl_btmesh_evt_config_client_reset_status_t *reset_status =
+ &evt->data.evt_config_client_reset_status;
+
+ sl_status_t event_result = reset_status->result;
+ event_status = process_event_result_from_api(self, event_result);
+ break;
+ }
+ }
+ return event_status;
+}
+
+static int32_t btmesh_conf_task_reset_node_to_string(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size)
+{
+ return snprintf(buffer,
+ size,
+ "node reset");
+}
+
+/** @} (end addtogroup btmesh_conf_task) */
+/** @} (end addtogroup btmesh_conf) */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_task.h b/app/btmesh/common_host/btmesh_conf/btmesh_conf_task.h
new file mode 100644
index 00000000000..c3cbb99c374
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_task.h
@@ -0,0 +1,1143 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component - Task
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef BTMESH_CONF_TASK_H
+#define BTMESH_CONF_TASK_H
+
+#include
+#include "sl_status.h"
+#include "sl_btmesh_api.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_task BT Mesh Configuration Task
+ * @brief BT Mesh Configuration Tasks implement the different configuration
+ * procedures to set and get configuration server state of a node.
+ * @n Each specific configuration task type implements the interface specified
+ * in @ref btmesh_conf_task_t structure.
+ * @{
+ ******************************************************************************/
+
+/// BT Mesh Configuration Task result is unknown because it is not finished yet
+#define BTMESH_CONF_TASK_RESULT_UNKNOWN ((sl_status_t) 0xFFFFFFFF)
+
+/// BT Mesh Configuration Task identifiers
+typedef enum btmesh_conf_task_id_t {
+ /// Add application key
+ BTMESH_CONF_TASK_ID_APPKEY_ADD,
+ /// Remove application key
+ BTMESH_CONF_TASK_ID_APPKEY_REMOVE,
+ /// List application keys (all)
+ BTMESH_CONF_TASK_ID_APPKEY_LIST,
+ /// Bind application key to model
+ BTMESH_CONF_TASK_ID_MODEL_BIND,
+ /// Unbind application key to model
+ BTMESH_CONF_TASK_ID_MODEL_UNBIND,
+ /// List application keys bound to model
+ BTMESH_CONF_TASK_ID_MODEL_BINDINGS_LIST,
+ /// Set model publication data
+ BTMESH_CONF_TASK_ID_MODEL_PUB_SET,
+ /// Get model publication data
+ BTMESH_CONF_TASK_ID_MODEL_PUB_GET,
+ /// Add address to subscription list of a model
+ BTMESH_CONF_TASK_ID_MODEL_SUB_ADD,
+ /// Overwrite existing address in the subscription list of a model
+ BTMESH_CONF_TASK_ID_MODEL_SUB_SET,
+ /// Remove address from the subscription list of a model
+ BTMESH_CONF_TASK_ID_MODEL_SUB_REMOVE,
+ /// Clear addresses from the subscription list of a model
+ BTMESH_CONF_TASK_ID_MODEL_SUB_CLEAR,
+ /// List addresses in the subscription list of a model
+ BTMESH_CONF_TASK_ID_MODEL_SUB_LIST,
+ /// Set Beacon
+ BTMESH_CONF_TASK_ID_BEACON_SET,
+ /// Get Beacon
+ BTMESH_CONF_TASK_ID_BEACON_GET,
+ /// Set default TTL
+ BTMESH_CONF_TASK_ID_DEFAULT_TTL_SET,
+ /// Get default TTL
+ BTMESH_CONF_TASK_ID_DEFAULT_TTL_GET,
+ /// Set proxy feature
+ BTMESH_CONF_TASK_ID_GATT_PROXY_SET,
+ /// Get proxy feature
+ BTMESH_CONF_TASK_ID_GATT_PROXY_GET,
+ /// Set relay feature (feature turn on/off and retransmission parameters)
+ BTMESH_CONF_TASK_ID_RELAY_SET,
+ /// Get relay feature (feature on/off and retransmission parameters)
+ BTMESH_CONF_TASK_ID_RELAY_GET,
+ /// Set network transmit count and interval
+ BTMESH_CONF_TASK_ID_NETWORK_TRANSMIT_SET,
+ /// Get network transmit count and interval
+ BTMESH_CONF_TASK_ID_NETWORK_TRANSMIT_GET,
+ /// Set friend feature
+ BTMESH_CONF_TASK_ID_FRIEND_SET,
+ /// Get friend feature
+ BTMESH_CONF_TASK_ID_FRIEND_GET,
+ /// Get Device Composition Data
+ BTMESH_CONF_TASK_ID_DCD_GET,
+ /// Reset remote node
+ BTMESH_CONF_TASK_ID_RESET_NODE,
+ /// Number of configuration tasks
+ BTMESH_CONF_TASK_ID_COUNT,
+ /// Invalid configuration task
+ BTMESH_CONF_TASK_ID_INVALID
+} btmesh_conf_task_id_t;
+
+/// Task specific data of add/remove application key data
+typedef struct {
+ /// Index of the application key
+ uint16_t appkey_index;
+ /// Index of the network key which the application key is bound to
+ uint16_t netkey_index;
+} btmesh_conf_task_appkey_t;
+
+/// Task specific data of list application keys (all)
+typedef struct {
+ /// Network key index for the key used as the query parameter
+ uint16_t netkey_index;
+ /// List of application key indexes which are bound to the network key index
+ uint16_t *appkey_indexes;
+ /// Number of application key indexes which are bound to the network key index
+ uint16_t appkey_count;
+} btmesh_conf_task_appkey_list_t;
+
+/// Task specific data of bind/unbind application key to model
+typedef struct {
+ /// Index of the element where the model resides on the node
+ uint8_t elem_index;
+ /// Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ uint16_t vendor_id;
+ /// Model ID for the model
+ uint16_t model_id;
+ /// Index of the application key bound to the model
+ uint16_t appkey_index;
+} btmesh_conf_task_model_binding_t;
+
+/// Task specific data of list application keys bound to model
+typedef struct {
+ /// Index of the element where the model resides on the node
+ uint8_t elem_index;
+ /// Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ uint16_t vendor_id;
+ /// Model ID for the model
+ uint16_t model_id;
+ /// List of key indexes for the application keys which are bound to a model
+ uint16_t *appkey_indexes;
+ /// Number of key indexes for the application keys which are bound to a model
+ uint16_t appkey_count;
+} btmesh_conf_task_model_bindings_list_t;
+
+/// Task specific data of set/get model publication data
+typedef struct {
+ /// Index of the element where the model resides on the node
+ uint8_t elem_index;
+ /// Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ uint16_t vendor_id;
+ /// Model ID for the model
+ uint16_t model_id;
+ /// @brief The unicast or group address to publish to.
+ /// It can also be the unassigned address to stop the model from publishing.
+ /// Note: Task type determines whether @p address or @p va_addres is used.
+ uint16_t address;
+ /// @brief The virtual address to publish to.
+ /// Note: Task type determines whether @p address or @p va_addres is used.
+ uuid_128 va_address;
+ /// The application key index to use for the published messages.
+ uint16_t appkey_index;
+ /// @brief Friendship credential flag. If zero, publication is done using
+ /// normal credentials. If one, it is done with friendship credentials,
+ /// meaning only the friend can decrypt the published message and relay it
+ /// forward using the normal credentials. The default value is 0.
+ uint8_t credentials;
+ /// Publication time-to-live value
+ uint8_t ttl;
+ /// @brief Publication period in milliseconds. Note that the resolution of the
+ /// publication period is limited by the specification to 100 ms up to a
+ /// period of 6.3 s, 1 s up to a period of 63 s, 10 s up to a period
+ /// of 630 s, and 10 minutes above that. Maximum period allowed is 630 minutes.
+ uint32_t period_ms;
+ /// Publication retransmission count. Valid values range from 0 to 7.
+ uint8_t retransmit_count;
+ /// @brief Publication retransmission interval in millisecond units. The range of
+ /// value is 50 to 1600 ms, and the resolution of the value is 50 milliseconds.
+ uint16_t retransmit_interval_ms;
+} btmesh_conf_task_model_pub_t;
+
+/// @brief Task specific data of the following model subscription list operations
+/// - Add address to model subscription list
+/// - Set address to model subscription list (clear list & add group address)
+/// - Remove address to model subscription list
+typedef struct {
+ /// Index of the element where the model resides on the node
+ uint8_t elem_index;
+ /// Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ uint16_t vendor_id;
+ /// Model ID for the model
+ uint16_t model_id;
+ /// @brief The group address in the subscription list.
+ /// Note: Task type determines whether @ref sub_address or @ref sub_address_va
+ /// is used.
+ uint16_t sub_address;
+ /// @brief The full virtual address in the subscription list.
+ /// Note: Task type determines whether @ref sub_address or @ref sub_address_va
+ /// is used.
+ uuid_128 sub_address_va;
+} btmesh_conf_task_model_sub_t;
+
+/// Task specific data of clear addresses from the subscription list of a model
+typedef struct {
+ /// Index of the element where the model resides on the node
+ uint8_t elem_index;
+ /// Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ uint16_t vendor_id;
+ /// Model ID for the model
+ uint16_t model_id;
+} btmesh_conf_task_model_sub_clear_t;
+
+/// Task specific data of list addresses in the subscription list of a model
+typedef struct {
+ /// Index of the element where the model resides on the node
+ uint8_t elem_index;
+ /// Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ uint16_t vendor_id;
+ /// Model ID for the model
+ uint16_t model_id;
+ /// @brief List of subscription addresses
+ /// @n If the subscription address list entry is a Label UUID
+ /// (full virtual address) then the corresponding virtual address
+ /// hash is returned in this event.
+ uint16_t *addresses;
+ /// Number of subscription addresses
+ uint16_t address_count;
+} btmesh_conf_task_model_sub_list_t;
+
+/// Task specific data of set/get Beacon
+typedef struct {
+ /// @brief Secure network beacon value. Valid values are:
+ /// - 0: Node is not broadcasting secure network beacons
+ /// - 1: Node is broadcasting secure network beacons
+ uint8_t value;
+} btmesh_conf_task_beacon_t;
+
+/// Task specific data of set/get default TTL
+typedef struct {
+ /// @brief Default TTL value. Valid value range is from 2 to 127 for relayed
+ /// PDUs, and 0 to indicate none - relayed PDUs
+ uint8_t ttl;
+} btmesh_conf_task_default_ttl_t;
+
+/// Task specific data of set/get proxy feature
+typedef struct {
+ /// @brief GATT proxy value. Valid values are:
+ /// - 0: Proxy feature is disabled
+ /// - 1: Proxy feature is enabled
+ uint8_t value;
+} btmesh_conf_task_gatt_proxy_t;
+
+/// Task specific data of set/get relay feature
+typedef struct {
+ /// @brief Relay value. Valid values are:
+ /// - 0: Relay feature is disabled
+ /// - 1: Relay feature is enabled
+ uint8_t value;
+ /// @brief Relay retransmit count. Valid values range from 0 to 7;
+ /// default value is 0 (no retransmissions).
+ uint8_t retransmit_count;
+ /// @brief Relay retransmit interval in milliseconds.
+ /// Valid values range from 10 ms to 320 ms, with a resolution of 10 ms.
+ /// The value is ignored if the retransmission count is set to zero.
+ uint16_t retransmit_interval_ms;
+} btmesh_conf_task_relay_t;
+
+/// Task specific data of set/get network transmit count and interval
+typedef struct {
+ /// @brief Network transmit count. Valid values range from 1 to 8;
+ /// default value is 1 (single transmission; no retransmissions).
+ uint8_t transmit_count;
+ /// @brief Network transmit interval in milliseconds.
+ /// Valid values range from 10 ms to 320 ms, with a resolution of 10 ms.
+ /// The value is ignored if the transmission count is set to one.
+ uint16_t transmit_interval_ms;
+} btmesh_conf_task_network_transmit_t;
+
+/// Task specific data of set/get friend feature
+typedef struct {
+ /// @brief Friend value. Valid values are:
+ /// - 0: Friend feature is not enabled
+ /// - 1: Friend feature is enabled
+ uint8_t value;
+} btmesh_conf_task_friend_t;
+
+/// Task specific data of get Device Composition Data (DCD)
+typedef struct {
+ /// Composition data page containing data
+ uint8_t page;
+ /// Raw DCD data of the remote node
+ uint8_t *raw_dcd_data;
+ /// Size of raw DCD data
+ uint16_t raw_dcd_data_size;
+} btmesh_conf_task_dcd_get_t;
+
+/// Union of BT Mesh Configuration Task specific data structures
+typedef union btmesh_conf_task_ext_t {
+ /// Task specific data of add/remove application key data
+ btmesh_conf_task_appkey_t appkey;
+ /// Task specific data of list application keys (all)
+ btmesh_conf_task_appkey_list_t appkey_list;
+ /// Task specific data of bind/unbind application key to model
+ btmesh_conf_task_model_binding_t model_binding;
+ /// Task specific data of list application keys bound to model
+ btmesh_conf_task_model_bindings_list_t model_bindings_list;
+ /// Task specific data of set/get model publication data
+ btmesh_conf_task_model_pub_t model_pub;
+ /// Task specific data of add/set/remove model subscription list operations
+ btmesh_conf_task_model_sub_t model_sub;
+ /// Task specific data of clear addresses from the subscription list of a model
+ btmesh_conf_task_model_sub_clear_t model_sub_clear;
+ /// Task specific data of list addresses in the subscription list of a model
+ btmesh_conf_task_model_sub_list_t model_sub_list;
+ /// Task specific data of set/get beacon
+ btmesh_conf_task_beacon_t beacon;
+ /// Task specific data of set/get default TTL
+ btmesh_conf_task_default_ttl_t default_ttl;
+ /// Task specific data of set/get proxy feature
+ btmesh_conf_task_gatt_proxy_t gatt_proxy;
+ /// Task specific data of set/get relay feature
+ btmesh_conf_task_relay_t relay;
+ /// Task specific data of set/get network transmit count and interval
+ btmesh_conf_task_network_transmit_t network_transmit;
+ /// Task specific data of set/get friend feature
+ btmesh_conf_task_friend_t friend;
+ /// Task specific data of get Device Composition Data (DCD)
+ btmesh_conf_task_dcd_get_t dcd;
+} btmesh_conf_task_ext_t;
+
+typedef struct btmesh_conf_task_t btmesh_conf_task_t;
+
+/// @brief BT Mesh Configuration Task is abstraction of configuration procedures
+/// @n The specific configuration tasks are instances of this structure with
+/// different task identifiers and function pointers (e.g. @p conf_request,
+/// @p on_event etc.) furthermore different members of the
+/// @p btmesh_conf_task_ext_t union is used based on the task type.
+struct btmesh_conf_task_t {
+ /// Task identifier which determines the specific task type
+ btmesh_conf_task_id_t task_id;
+ /// @brief Task result.
+ /// Initialized to @ref BTMESH_CONF_TASK_RESULT_UNKNOWN and it set to the
+ /// final value at the end of task.
+ /// @n If the configuration task was successful then it is set to SL_STATUS_OK.
+ /// @n If @p conf_request or @p on_event fails then it set to an error code
+ /// returned by the BT Mesh Stack.
+ sl_status_t result;
+ /// @brief Abstract configuration request which sends the task specific
+ /// configuration request to the configuration server on a node.
+ /// @param[in] self Pointer to the configuration task instance
+ /// @param[in] enc_netkey_index Network key used to encrypt the config request
+ /// @param[in] server_address Destination node primary element address
+ /// @param[out] handle BT Mesh Stack configuration handle which is returned
+ /// by the BT Mesh Stack configuration request (BT Mesh Stack API)
+ /// @return Task request status
+ /// @retval SL_STATUS_OK If request is accepted.
+ /// @retval SL_STATUS_BUSY If request is rejected due to busy lower layers.
+ /// @retval SL_STATUS_FAIL If request is rejected due to unrecoverable error.
+ sl_status_t (*conf_request)(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ uint32_t *handle);
+ /// @brief Abstract configuration event handler which processes the BT Mesh
+ /// Stack event.
+ /// @n Only those BT Mesh Configuration Client events shall be forwarded to
+ /// this task event handler which matches the configuration handle of the last
+ /// @p conf_request in order to make task implementation simpler.
+ /// @param[in] self Pointer to the configuration task instance
+ /// @param[in] enc_netkey_index Network key used to encrypt the config request
+ /// @param[in] server_address Destination node primary element address
+ /// @param[in] evt BT Mesh Stack event
+ /// @return Current status of the task.
+ /// @retval SL_STATUS_OK Task is finished successfully.
+ /// @retval SL_STATUS_IN_PROGRESS Task is waiting for additional events.
+ /// @retval SL_STATUS_TIMEOUT Task timed out while it was waiting for events.
+ /// @retval SL_STATUS_FAIL Task failed due to server side error.
+ /// @retval SL_STATUS_ABORT Task failed unexpectedly and config task shall be.
+ /// aborted so cancellation of config request is required.
+ sl_status_t (*on_event)(btmesh_conf_task_t *const self,
+ uint16_t enc_netkey_index,
+ uint16_t server_address,
+ const sl_btmesh_msg_t *evt);
+ /// @brief Abstract function which provides the string representation of the
+ /// configuration task.
+ /// @n The function always writes null terminated string into the @p buffer.
+ /// @param[in] self Pointer to the configuration task instance
+ /// @param[out] buffer Buffer where the string representation is written to
+ /// @param[in] size Size of the output @p buffer
+ /// @return Return value of functions shall be interpreted as snprintf.
+ int32_t (*to_string)(btmesh_conf_task_t *const self,
+ char *buffer,
+ uint32_t size);
+ /// @brief Deallocation of BT Mesh Configuration Task instance
+ /// @param[in] self Pointer to the configuration task instance
+ void (*destroy)(btmesh_conf_task_t *const self);
+ /// Next task if this task is successful (@p result is SL_STATUS_OK)
+ btmesh_conf_task_t *next_on_success;
+ /// Next task if this task fails (@p result is not SL_STATUS_OK)
+ btmesh_conf_task_t *next_on_failure;
+ /// @brief Task specific data (parameters, runtime data, results)
+ /// @n Union of task specific data structures.
+ btmesh_conf_task_ext_t ext;
+};
+
+/***************************************************************************//**
+ * Create generic BT Mesh Configuration Task
+ *
+ * @param[in] task_id Task identifier which shall be set
+ * @return Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ *
+ * Allocates and initializes a generic configuration task.
+ * @warning This function shall be called from derived (specific) configuration
+ * task create functions only to create an initialized task instance which
+ * is further customized by the specific configuration task create function.
+ * For example @ref btmesh_conf_task_appkey_add_create.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_create(btmesh_conf_task_id_t task_id);
+
+/***************************************************************************//**
+ * Deallocates the BT Mesh Configuration Task instance
+ *
+ * @param[in] self Pointer to the configuration task which shall be destroyed
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Set next task pointers of the BT Mesh Configuration Task to the specified
+ * other task.
+ *
+ * @param[in] self Pointer to the configuration task
+ * @param[in] next_task Next task which shall be executed after this task (@p self)
+ * @returns Status of next task setup.
+ * @retval SL_STATUS_OK If the next task is set successfully.
+ * @retval SL_STATUS_NULL_POINTER If @p self or @p next_task is NULL.
+ * @retval SL_STATUS_INVALID_PARAMETER If the @p self and @p next_task is the
+ * same or the next task of @p next_task is not NULL.
+ *
+ * The task which shall be executed after the end of @p self task is set to
+ * @p next_task. The @p next_task is independent from the result of @p self task.
+ *
+ * @warning It is expected that the @p next_task does not have any next task
+ * when this function is called in order to guarantee that there will be no
+ * cyclic references in the task tree. Therefore, the task tree shall be built
+ * in the order of execution. This is enforced by the implementation and
+ * SL_STATUS_INVALID_PARAMETER is returned if this rule is not respected.
+ ******************************************************************************/
+sl_status_t btmesh_conf_task_set_next_unconditional(btmesh_conf_task_t *self,
+ btmesh_conf_task_t *const next_task);
+
+/***************************************************************************//**
+ * Set next task pointer of the BT Mesh Configuration Task to the specified
+ * other task if this task is completed successfully.
+ *
+ * @param[in] self Pointer to the configuration task
+ * @param[in] next_task Next task which shall be executed if this task (@p self)
+ * is completed successfully.
+ * @returns Status of next task setup.
+ * @retval SL_STATUS_OK If the next task is set successfully.
+ * @retval SL_STATUS_NULL_POINTER If @p self or @p next_task is NULL.
+ * @retval SL_STATUS_INVALID_PARAMETER If the @p self and @p next_task is the
+ * same or the next task of @p next_task is not NULL.
+ *
+ * @warning It is expected that the @p next_task does not have any next task
+ * when this function is called in order to guarantee that there will be no
+ * cyclic references in the task tree. Therefore, the task tree shall be built
+ * in the order of execution. This is enforced by the implementation and
+ * SL_STATUS_INVALID_PARAMETER is returned if this rule is not respected.
+ ******************************************************************************/
+sl_status_t btmesh_conf_task_set_next_on_success(btmesh_conf_task_t *self,
+ btmesh_conf_task_t *const next_task);
+
+/***************************************************************************//**
+ * Set next task pointer of the BT Mesh Configuration Task to the specified
+ * other task if this task fails.
+ *
+ * @param[in] self Pointer to the configuration task
+ * @param[in] next_task Next task which shall be executed if this task
+ * (@p self) fails.
+ * @returns Status of next task setup.
+ * @retval SL_STATUS_OK If the next task is set successfully.
+ * @retval SL_STATUS_NULL_POINTER If @p self or @p next_task is NULL.
+ * @retval SL_STATUS_INVALID_PARAMETER If the @p self and @p next_task is the
+ * same or the next task of @p next_task is not NULL.
+ *
+ * @warning It is expected that the @p next_task does not have any next task
+ * when this function is called in order to guarantee that there will be no
+ * cyclic references in the task tree. Therefore, the task tree shall be built
+ * in the order of execution. This is enforced by the implementation and
+ * SL_STATUS_INVALID_PARAMETER is returned if this rule is not respected.
+ ******************************************************************************/
+sl_status_t btmesh_conf_task_set_next_on_failure(btmesh_conf_task_t *self,
+ btmesh_conf_task_t *const next_task);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to add an application key to a node.
+ *
+ * @param[in] appkey_index Index of the application key
+ * @param[in] netkey_index Index of the network key which the application key
+ * is bound to
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_appkey_add_create(uint16_t appkey_index,
+ uint16_t netkey_index);
+/***************************************************************************//**
+ * Deallocation of Add Application Key BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_appkey_add_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to remove an application key from a node.
+ *
+ * @param[in] appkey_index Index of the application key
+ * @param[in] netkey_index Index of the network key which the application key
+ * is bound to
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_appkey_remove_create(uint16_t appkey_index,
+ uint16_t netkey_index);
+
+/***************************************************************************//**
+ * Deallocation of Remove Application Key BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_appkey_remove_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to list the application keys on a node.
+ *
+ * @param[in] netkey_index Network key index for the key used as query parameter
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_appkey_list_create(uint16_t netkey_index);
+
+/***************************************************************************//**
+ * Deallocation of List Application Keys BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_appkey_list_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to bind an application key to a model.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node
+ * @param[in] vendor_id Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model
+ * @param[in] appkey_index Index of the application key to bind to the model
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_bind_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t appkey_index);
+
+/***************************************************************************//**
+ * Deallocation of Bind Model BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_bind_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to unbind an application key from a model.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node
+ * @param[in] vendor_id Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model
+ * @param[in] appkey_index Index of the application key to unbind from the model
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_unbind_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t appkey_index);
+/***************************************************************************//**
+ * Deallocation of Unbind Model BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_unbind_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to list application key bindings of a model.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node
+ * @param[in] vendor_id Vendor ID for the model (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_bindings_list_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id);
+
+/***************************************************************************//**
+ * Deallocation of BT List Model Bindings Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_bindings_list_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to set the model publication state.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node.
+ * @param[in] vendor_id Vendor ID for the model. (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model.
+ * @param[in] address The unicast or group address to publish to. It can also be
+ * the unassigned address to stop the model from publishing.
+ * @param[in] appkey_index Application key index to use for published messages.
+ * @param[in] credentials Friendship credential flag. The default value is 0.
+ * - If zero, publication is done using normal credentials.
+ * - If one, it is done with friendship credentials, meaning only the friend
+ * can decrypt the published message and relay it forward using the normal
+ * credentials.
+ * @param[in] ttl Publication time-to-live value
+ * @param[in] period_ms Publication period in milliseconds.
+ * Note that the resolution of the publication period is limited by the
+ * specification to 100ms up to a period of 6.3s, 1s up to a period of 63s,
+ * 10s up to a period of 630s, and 10 minutes above that.
+ * Maximum period allowed is 630 minutes.
+ * @param[in] retransmit_count Publication retransmission count. Valid values
+ * range from 0 to 7.
+ * @param[in] retransmit_interval_ms Publication retransmission interval in
+ * millisecond units. The range of value is 50 to 1600 ms, and the resolution
+ * of the value is 50 milliseconds.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_pub_set_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t address,
+ uint16_t appkey_index,
+ uint8_t credentials,
+ uint8_t ttl,
+ uint32_t period_ms,
+ uint8_t retransmit_count,
+ uint16_t retransmit_interval_ms);
+
+/***************************************************************************//**
+ * Deallocation of Set Model Publication BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_pub_set_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get the model publication state.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node.
+ * @param[in] vendor_id Vendor ID for the model. (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_pub_get_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id);
+/***************************************************************************//**
+ * Deallocation of Get Model Publication BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_pub_get_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to add an address to the model
+ * subscription list.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node.
+ * @param[in] vendor_id Vendor ID for the model. (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model.
+ * @param[in] sub_address Group address which shall be added to the subscription
+ * list of the model.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_sub_add_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t sub_address);
+
+/***************************************************************************//**
+ * Deallocation of Add Model Subscription BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_sub_add_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to set (overwrite) model subscription
+ * address list to a single address.
+ * See Config Model Subscription Overwrite BT Mesh message in foundation models
+ * chapter of BT Mesh Profile Specification.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node.
+ * @param[in] vendor_id Vendor ID for the model. (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model.
+ * @param[in] sub_address Group address which shall be added to the cleared
+ * subscription list of the model.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_sub_set_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t sub_address);
+
+/***************************************************************************//**
+ * Deallocation of Set Model Subscription BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_sub_set_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to remove an address from the model
+ * subscription list.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node.
+ * @param[in] vendor_id Vendor ID for the model. (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model.
+ * @param[in] sub_address Group address which shall be removed from the
+ * subscription list of the model.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_sub_remove_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id,
+ uint16_t sub_address);
+/***************************************************************************//**
+ * Deallocation of Remove Model Subscription BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_sub_remove_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to clear (empty) the model subscription
+ * address list.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node.
+ * @param[in] vendor_id Vendor ID for the model. (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_sub_clear_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id);
+
+/***************************************************************************//**
+ * Deallocation of Clear Model Subscription List BT Mesh Configuration
+ * Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_sub_clear_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get the subscription address list of
+ * a model.
+ *
+ * @param[in] elem_index Index of the element where the model resides on the node.
+ * @param[in] vendor_id Vendor ID for the model. (0xFFFF for Bluetooth SIG models)
+ * @param[in] model_id Model ID for the model.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_model_sub_list_create(uint8_t elem_index,
+ uint16_t vendor_id,
+ uint16_t model_id);
+/***************************************************************************//**
+ * Deallocation of List Model Subscription BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_model_sub_list_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to set node beacon state.
+ *
+ * @param[in] beacon Beacon value.
+ * Valid values are:
+ * - 0: Node is not broadcasting secure network beacons
+ * - 1: Node is broadcasting secure network beacons
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_beacon_set_create(uint8_t value);
+
+/***************************************************************************//**
+ * Deallocation of Set Beacon BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_beacon_set_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get node beacon state.
+ *
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_beacon_get_create(void);
+
+/***************************************************************************//**
+ * Deallocation of Get Beacon BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_beacon_get_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to set node default TTL state.
+ *
+ * @param[in] default_ttl Default TTL value. Valid value range is from 2 to 127
+ * for relayed PDUs, and 0 to indicate non-relayed PDUs.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_default_ttl_set_create(uint8_t default_ttl);
+
+/***************************************************************************//**
+ * Deallocation of Set Default TTL BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_default_ttl_set_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get node default TTL state.
+ *
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_default_ttl_get_create(void);
+
+/***************************************************************************//**
+ * Deallocation of Get Default TTL BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_default_ttl_get_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to set node GATT proxy state.
+ *
+ * @param[in] value GATT proxy value to set.
+ * Valid values are:
+ * - 0: Proxy feature is disabled
+ * - 1: Proxy feature is enabled
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_gatt_proxy_set_create(uint8_t value);
+
+/***************************************************************************//**
+ * Deallocation of Set GATT Proxy BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_gatt_proxy_set_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get node GATT proxy state.
+ *
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_gatt_proxy_get_create(void);
+
+/***************************************************************************//**
+ * Deallocation of Get GATT Proxy BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_gatt_proxy_get_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to set node relay state.
+ *
+ * @param[in] value Relay value to set.
+ * Valid values are:
+ * - 0: Relay feature is disabled
+ * - 1: Relay feature is enabled
+ * @param[in] retransmit_count Relay retransmit count. Valid values range from 0
+ * to 7; default value is 0 (no retransmissions).
+ * @param[in] retransmit_interval_ms Relay retransmit interval in milliseconds.
+ * Valid values range from 10 ms to 320 ms, with a resolution of 10 ms. The
+ * value is ignored if the retransmission count is set to zero.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_relay_set_create(uint8_t value,
+ uint8_t retransmit_count,
+ uint16_t retransmit_interval_ms);
+/***************************************************************************//**
+ * Deallocation of Set Relay BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_relay_set_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get node relay state.
+ *
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_relay_get_create(void);
+
+/***************************************************************************//**
+ * Deallocation of Get Relay BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_relay_get_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to set node network transmit state.
+ *
+ * @param[in] transmit_count Network transmit count. Valid values range from 1
+ * to 8; default value is 1 (single transmission; no retransmissions).
+ * @param[in] transmit_interval_ms Network transmit interval in milliseconds.
+ * Valid values range from 10 ms to 320 ms, with a resolution of 10 ms.
+ * The value is ignored if the transmission count is set to one.
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_network_transmit_set_create(uint8_t transmit_count,
+ uint16_t transmit_interval_ms);
+
+/***************************************************************************//**
+ * Deallocation of Set Network Transmit BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_network_transmit_set_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get node network transmit state.
+ *
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_network_transmit_get_create(void);
+
+/***************************************************************************//**
+ * Deallocation of Get Network Transmit BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_network_transmit_get_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to set node friend state.
+ *
+ * @param[in] value Friend value to set. Valid values are:
+ * - 0: Friend feature is not enabled
+ * - 1: Friend feature is enabled
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_friend_set_create(uint8_t value);
+
+/***************************************************************************//**
+ * Deallocation of Set Friend BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_friend_set_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get node friend state.
+ *
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_friend_get_create(void);
+
+/***************************************************************************//**
+ * Deallocation of Get Friend BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_friend_get_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to get composition data of a device.
+ *
+ * @param[in] page Composition data page to query
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_dcd_get_create(uint8_t page);
+
+/***************************************************************************//**
+ * Deallocation of Get DCD BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_dcd_get_destroy(btmesh_conf_task_t *self);
+
+/***************************************************************************//**
+ * Create BT Mesh Configuration Task to request a node to unprovision itself.
+ * Use this function when a node shall be removed from the network.
+ *
+ * @returns Created configuration task.
+ * @retval NULL If the configuration task creation fails.
+ ******************************************************************************/
+btmesh_conf_task_t *btmesh_conf_task_reset_node_create(void);
+
+/***************************************************************************//**
+ * Deallocation of Reset Node BT Mesh Configuration Task instance.
+ *
+ * @param[in] self Pointer to the configuration task instance
+ *
+ * If btmesh_conf_task_t::next_on_success or btmesh_conf_task_t::next_on_failure
+ * is set then those tasks are destroyed as well.
+ ******************************************************************************/
+void btmesh_conf_task_reset_node_destroy(btmesh_conf_task_t *self);
+
+/** @} (end addtogroup btmesh_conf_task) */
+/** @} (end addtogroup btmesh_conf) */
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* BTMESH_CONF_TASK_H */
diff --git a/app/btmesh/common_host/btmesh_conf/btmesh_conf_types.h b/app/btmesh/common_host/btmesh_conf/btmesh_conf_types.h
new file mode 100644
index 00000000000..b3d559dadde
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/btmesh_conf_types.h
@@ -0,0 +1,132 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef BTMESH_CONF_TYPES_H
+#define BTMESH_CONF_TYPES_H
+
+#include
+#include
+#include
+
+#include "sl_status.h"
+#include "sl_slist.h"
+#include "sl_btmesh_api.h"
+#include "sl_btmesh_capi_types.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf BT Mesh Configurator Component
+ * @{
+ ******************************************************************************/
+
+/***************************************************************************//**
+ * @addtogroup btmesh_conf_types BT Mesh Configuration Types
+ * @brief BT Mesh Configuration Types provides common definitions and types.
+ * @{
+ ******************************************************************************/
+
+/// Unknown network key index value
+#define BTMESH_CONF_NETKEY_INDEX_UNASSIGNED 0xFFFF
+
+/// Unknown model publication element index value
+#define BTMESH_CONF_PUB_ELEM_INDEX_UNKNOWN 0xFF
+/// Unknown model publication vendor ID value
+#define BTMESH_CONF_PUB_VENDOR_ID_UNKNOWN 0xFFFF
+/// Unknown model publication model ID value
+#define BTMESH_CONF_PUB_MODEL_ID_UNKNOWN 0xFFFF
+/// Unknown model publication address value
+#define BTMESH_CONF_PUB_ADDRESS_UNKNOWN 0xFFFF
+/// Unknown model publication application key value
+#define BTMESH_CONF_PUB_APPKEY_INDEX_UNKNOWN 0xFFFF
+/// Unknown model publication friendship credentials flag value
+#define BTMESH_CONF_PUB_CREDENTIALS_UNKNOWN 0xFF
+/// Unknown model publication TTL value
+#define BTMESH_CONF_PUB_TTL_UNKNOWN 0xFF
+/// Unknown model publication period value
+#define BTMESH_CONF_PUB_PERIOD_MS_UNKNOWN 0xFFFFFFFF
+/// Unknown model publication retransmit count value
+#define BTMESH_CONF_PUB_RETRANSMIT_CNT_UNKNOWN 0xFF
+/// Unknown model publication retransmit interval value
+#define BTMESH_CONF_PUB_RETRANSMIT_INT_MS_UNKNOWN 0xFFFF
+
+/// Unknown node beacon value
+#define BTMESH_CONF_BEACON_UNKNOWN 0xFF
+/// Unknown node default TTL value
+#define BTMESH_CONF_DEFAULT_TTL_UNKNOWN 0xFF
+/// Unknown node proxy value
+#define BTMESH_CONF_PROXY_VALUE_UNKNOWN 0xFF
+/// Unknown node relay value
+#define BTMESH_CONF_RELAY_VALUE_UNKNOWN 0xFF
+/// Unknown node relay retransmit count
+#define BTMESH_CONF_RELAY_RETRANSMIT_CNT_UNKNOWN 0xFF
+/// Unknown node relay retransmit interval
+#define BTMESH_CONF_RELAY_RETRANSMIT_INT_MS_UNKNOWN 0xFFFF
+/// Unknown node network transmit count
+#define BTMESH_CONF_NW_TRANSMIT_CNT_UNKNOWN 0xFF
+/// Unknown node network transmit interval
+#define BTMESH_CONF_NW_TRANSMIT_INT_MS_UNKNOWN 0xFFFF
+/// Unknown node friend value
+#define BTMESH_CONF_FRIEND_VALUE_UNKNOWN 0xFF
+
+/// Maximum theoretical number of application keys
+#define BTMESH_CONF_MAX_APPKEY_COUNT (1 << 12)
+/// Maximum theoretical number of network keys
+#define BTMESH_CONF_MAX_NETKEY_COUNT (1 << 12)
+/// @brief Maximum theoretical number of subscription addresses
+/// BT Mesh model can subscribe to group addresses (14bit) and to virtual
+/// addresses (14bit) and to unicast address of the node element which the
+/// model resides on.
+#define BTMESH_CONF_MAX_SUB_ADDR_COUNT ((1 << 14) + (1 << 14) + 1)
+
+/// Variable argument unsigned initializer macro
+#define BTMESH_CONF_VARG_U32(u32) (btmesh_conf_varg_t){ .u32 = u32 }
+/// Variable argument pointer initializer macro
+#define BTMESH_CONF_VARG_PTR(ptr) (btmesh_conf_varg_t){ .ptr = ptr }
+/// Variable argument NULL pointer initializer macro
+#define BTMESH_CONF_VARG_NULL (btmesh_conf_varg_t){ .ptr = NULL }
+
+/// Variable argument union type to provide application specific information
+typedef union {
+ uint32_t u32; ///< Unsigned 32bit variable
+ void *ptr; ///< Pointer variable
+} btmesh_conf_varg_t;
+
+/** @} (end addtogroup btmesh_conf_types) */
+/** @} (end addtogroup btmesh_conf) */
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif /* BTMESH_CONF_TYPES_H */
diff --git a/app/btmesh/common_host/btmesh_conf/config/btmesh_conf_config.h b/app/btmesh/common_host/btmesh_conf/config/btmesh_conf_config.h
new file mode 100644
index 00000000000..daae8542812
--- /dev/null
+++ b/app/btmesh/common_host/btmesh_conf/config/btmesh_conf_config.h
@@ -0,0 +1,129 @@
+/***************************************************************************/ /**
+ * @file
+ * @brief BT Mesh Configurator Component - Default Configuration
+ *******************************************************************************
+ * # License
+ * Copyright 2021 Silicon Laboratories Inc. www.silabs.com
+ *******************************************************************************
+ *
+ * SPDX-License-Identifier: Zlib
+ *
+ * The licensor of this software is Silicon Laboratories Inc.
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ ******************************************************************************/
+
+#ifndef BTMESH_CONF_CONFIG_H
+#define BTMESH_CONF_CONFIG_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+// <<< Use Configuration Wizard in Context Menu >>>
+
+// Log message fragment buffer size
+// Default: 256
+// <8-1024:1>
+// Log message fragment buffers are allocated on stack and used to format parts of log messages.
+#define SL_BTMESH_CONF_MAX_LOG_MSG_FRAGMENT_SIZE_CFG_VAL 256
+
+// Configuration executor count
+// Default: 1
+// <1-256:1>
+// Specifies the maximum number of parallel active configuration jobs.
+// The executor count shall be less than SL_BTMESH_CONFIG_MAX_SEND_SEGS and SL_BTMESH_CONFIG_APP_TXQ_SIZE in sl_btmesh_config.h of NCP embedded code.
+#define SL_BTMESH_CONF_EXECUTOR_COUNT_CFG_VAL 1
+
+// Configuration client request timeout (ms)
+// Default: 5000
+// <1-4294967295:1>
+// Default timeout in milliseconds of configuration client requests.
+// The BT Mesh Stack emits a configuration client event with SL_STATUS_TIMEOUT result if no reponse is received from configuration server.
+#define SL_BTMESH_CONF_REQUEST_TIMEOUT_MS_CFG_VAL 5000
+
+// Configuration client LPN request timeout (ms)
+// Default: 15000
+// <1-4294967295:1>
+// Default timeout in milliseconds of configuration client requests when communicating with an LPN node.
+// The BT Mesh Stack emits a configuration client event with SL_STATUS_TIMEOUT result if no reponse is received from configuration server.
+#define SL_BTMESH_CONF_LPN_REQUEST_TIMEOUT_MS_CFG_VAL 15000
+
+// Wait for event timeout (ms)
+// Default: 30000
+// <1-4294967295:1>
+// Timeout in milliseconds to wait for configuration client events.
+// It shall be greater than SL_BTMESH_CONF_REQUEST_TIMEOUT_MS_CFG_VAL and SL_BTMESH_CONF_LPN_REQUEST_TIMEOUT_MS_CFG_VAL.
+// The BT Mesh Stack triggers configuration events if the configuration client request timeout occurs.
+// Wait for event timeout handles the extremely rare case of missing events. (e.g. too many events through NCP protocol)
+#define SL_BTMESH_CONF_EVENT_WAIT_TIMEOUT_MS_CFG_VAL 30000
+
+// Maximum number of request retry due to communication issues
+// Default: 3
+// <0-1000:1>
+// The maximum number of configuration request retry due to communication issues (BT Mesh Stack configuration request timeout).
+#define SL_BTMESH_CONF_COMMUNICATION_RETRY_MAX_CFG_VAL 3
+
+// Retry interval in case of busy configuration request (ms)
+// Default: 1000
+// <1-4294967295:1>
+// Retry interval in milliseconds in case of busy configuration client requests. Config requests can be busy due to unavailable resources in BT Mesh Stack.
+#define SL_BTMESH_CONF_REQUEST_BUSY_RETRY_INTERVAL_MS_CFG_VAL 1000
+
+// Maximum number of configuration request retry due to busy BT Mesh Stack
+// Default: 10
+// <0-10000:1>
+// The maximum number of configuration request retry because of rejected configuration request due to busy BT Mesh Stack.
+// The BT Mesh Stack might reject configuration client requests temporarily due to lack of resources.
+// For example maximum number of parallel segmented message transmissions is reached.
+#define SL_BTMESH_CONF_REQUEST_BUSY_RETRY_MAX_CFG_VAL 10
+
+// Configuration Job Auto Destroy
+// Default: 1
+// The default configuration job is deallocated automatically after the job status notification.
+// If auto destroy feature is turned off (0) then the job deallocation shall be performed manually (btmesh_conf_job_destroy) after job execution ends.
+#define SL_BTMESH_CONF_JOB_AUTO_DESTROY_CFG_VAL 1
+
+// Configuration Job Auto Destroy on Submit failure
+// Default: 1
+//