Enable Thread joiner on matter light bulb example

Hello,

I recently bought a nrf52840 dev kit + dongle to test the functionalities of Matter.

To commission devices "On network" without the use of Bluetooth, i wanted to use the Thread commissioner/joiner mechanism, based on a pre-shared key or the EUI+64 of the device.

I started from the matter light bulb example, and added the necessary flags in prj.conf.

# Enable CHIP
CONFIG_CHIP=y
CONFIG_CHIP_PROJECT_CONFIG="src/chip_project_config.h"
# 32773 == 0x8005 (example lighting-app)
CONFIG_CHIP_DEVICE_PRODUCT_ID=32773
CONFIG_STD_CPP17=y

# Enable Matter pairing automatically on application start.
CONFIG_CHIP_ENABLE_PAIRING_AUTOSTART=y

# Enable Matter extended announcement and increase duration to 1 hour.
CONFIG_CHIP_BLE_EXT_ADVERTISING=y
CONFIG_CHIP_BLE_ADVERTISING_DURATION=60

# Add support for LEDs and buttons on Nordic development kits
CONFIG_DK_LIBRARY=y
CONFIG_PWM=y

# Bluetooth Low Energy configuration
CONFIG_BT_DEVICE_NAME="MatterLight"

# Other settings
CONFIG_THREAD_NAME=y
CONFIG_MPU_STACK_GUARD=y
CONFIG_RESET_ON_FATAL_ERROR=n
CONFIG_CHIP_LIB_SHELL=y
CONFIG_NCS_SAMPLE_MATTER_TEST_SHELL=y

# Reduce application size
CONFIG_USE_SEGGER_RTT=n

# Enable Factory Data feature
CONFIG_CHIP_FACTORY_DATA=y
CONFIG_CHIP_FACTORY_DATA_BUILD=y

# extra: mbedTLS
CONFIG_MBEDTLS_BUILTIN=y
CONFIG_MBEDTLS_AES_C=y
CONFIG_MBEDTLS_LEGACY_CRYPTO_C=y
CONFIG_NRF_SECURITY=y
CONFIG_MBEDTLS=y
CONFIG_MBEDTLS_CMAC_C=y
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED=y
CONFIG_MBEDTLS_ECJPAKE_C=y
# Enable elliptic curve cryptography
# CONFIG_MBEDTLS_ECP=y

# Enable specific elliptic curves (example: CURVE25519 and CURVE448)
CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y
CONFIG_MBEDTLS_ECP_DP_CURVE448_ENABLED=y

# extra: Thread

# Enable OpenThread support
CONFIG_OPENTHREAD=y
CONFIG_OPENTHREAD_THREAD_VERSION_1_3=y

# start the joiner
CONFIG_OPENTHREAD_JOINER=y
CONFIG_CHIP_OPENTHREAD_JOINER_ENABLED=y

# Enable Thread networking
CONFIG_NET_L2_OPENTHREAD=y
CONFIG_OPENTHREAD_FTD=y  # Full Thread Device (use MTD for Minimal Thread Device)

# Enable IPv6 support (required for Thread)
CONFIG_NETWORKING=y
CONFIG_NET_IPV6=y
CONFIG_NET_IPV6_MLD=y
CONFIG_NET_IPV6_NBR_CACHE=y
CONFIG_NET_IPV6_ND=y

# Network shell
CONFIG_SHELL=y
CONFIG_OPENTHREAD_SHELL=y
CONFIG_SHELL_ARGC_MAX=26
CONFIG_SHELL_CMD_BUFF_SIZE=416

in my src/main.cpp code i adjusted the program to print the EUI64 in the console and start the thread joiner.

#include "app_task.h"

#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <openthread/thread.h>
#include <openthread/joiner.h>
#include <openthread/platform/radio.h> // For otPlatRadioGetIeeeEui64

#define THREAD_JOINER_TIMEOUT 900 // 15 minutes in seconds

// Global flag to indicate Thread Joiner completion
static volatile bool is_thread_joiner_complete = false;

static const struct gpio_dt_spec button1 = GPIO_DT_SPEC_GET(DT_ALIAS(sw1), gpios);
static struct gpio_callback button1_cb_data;


LOG_MODULE_REGISTER(app, CONFIG_CHIP_APP_LOG_LEVEL);

// Callback for Thread Joiner completion
void JoinerCallback(otError result, void *context)
{
    if (result == OT_ERROR_NONE)
    {
        LOG_INF("Thread Joiner completed successfully");
        is_thread_joiner_complete = true;
    }
    else
    {
        LOG_ERR("Thread Joiner failed: %d", result);
    }
}

void StartThreadJoiner()
{
    otInstance *instance = openthread_get_default_instance();
    if (instance == NULL)
    {
        LOG_ERR("Failed to get OpenThread instance");
        return;
    }

	// Retrieve the EUI64
	uint8_t eui64[OT_EXT_ADDRESS_SIZE];
	otPlatRadioGetIeeeEui64(instance, eui64);

	// Print the EUI64 to the console
	LOG_INF("Device EUI64: %02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",
			eui64[0], eui64[1], eui64[2], eui64[3],
			eui64[4], eui64[5], eui64[6], eui64[7]);

	// Start the Thread Joiner
	const char *pskd = NULL; // No PSKd since you're using EUI64
    otError error = otJoinerStart(instance, pskd, NULL, NULL, NULL, NULL, NULL, JoinerCallback, NULL);

    if (error == OT_ERROR_NONE)
    {
        LOG_INF("Thread Joiner started successfully");
    }
    else
    {
        LOG_ERR("Failed to start Thread Joiner: %d", error);
    }
}

void button1_pressed(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
    LOG_INF("Button 1 (sw1 alias) pressed, starting Thread Joiner...");
    StartThreadJoiner();
}

void init_buttons()
{
    if (!device_is_ready(button1.port)) {
        LOG_ERR("Button 1 GPIO port not ready");
        return;
    }

    gpio_pin_configure_dt(&button1, GPIO_INPUT);
    gpio_pin_interrupt_configure_dt(&button1, GPIO_INT_EDGE_TO_ACTIVE);
    gpio_init_callback(&button1_cb_data, button1_pressed, BIT(button1.pin));
    gpio_add_callback(button1.port, &button1_cb_data);

    LOG_INF("Button 1 configured successfully");
}


int main()
{
    // Initialize Button 1 for Thread Joiner
    init_buttons();	

	LOG_INF("Waiting for Thread Joiner to complete...");
    while (!is_thread_joiner_complete)
    {
        k_sleep(K_SECONDS(1)); // Polling to check if Joiner has completed
    }

	LOG_INF("Starting Matter application...");
    CHIP_ERROR err = AppTask::Instance().StartApp();


	LOG_ERR("Exited with code %" CHIP_ERROR_FORMAT, err.Format());
	return err == CHIP_NO_ERROR ? EXIT_SUCCESS : EXIT_FAILURE;
}

there is an issue with the link to the Openthread libraries though, because i get the error:

C:/ncs/workspace/light_bulb/src/main.cpp:62: undefined reference to `otJoinerStart'

Has anyone tried this before?

Do i need to add extra lines in the config file or is it not possible to use both Opentread and Matter due to conflicts?

Thanks in advance for your help!

Related