This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Mesh node self provisioning

Hi all,

I want to self provision my node. I am using nrf52832 dev board, mesh SDK v 3.1.0, light switch client example. You may wonder why self provisioning, when there is nrf mesh provisioning mobile app, but it is the necessary intermediate step in the project where I combine the provisioner and client role in one node. The parts of code for self provisioning:

static uint8_t m_netkey; 
static uint8_t m_appkey;
static uint8_t m_self_devkey;

static dsm_handle_t m_netkey_handle;
static dsm_handle_t m_appkey_handle;
static dsm_handle_t m_self_devkey_handle;

static dsm_handle_t group_handle;
static generic_onoff_client_t m_client;

void self_provision()
{
    dsm_local_unicast_address_t local_address = {START_ADDRESS, ACCESS_ELEMENT_COUNT};
    ERROR_CHECK(dsm_local_unicast_addresses_set(&local_address));
    
    //hardcoded keys
    m_netkey = NETKEY;     
    m_appkey = APPKEY;
    m_devkey = DEVKEY;
    
    ERROR_CHECK(dsm_subnet_add(0, netkey, &m_netkey_handle));
    ERROR_CHECK(dsm_appkey_add(0, m_netkey_handle ,m_appkey, &m_appkey_handle));
    ERROR_CHECK(dsm_devkey_add(START_ADDRESS, m_netkey_handle, m_self_devkey, &m_self_devkey_handle));
    
    ERROR_CHECK(dsm_address_publish_add(FIRST_GROUP_ADDRESS, &group_handle));
}

void app_default_models_bind_setup(void)
{
    ERROR_CHECK(config_server_bind(m_self_devkey_handle));
    
    ERROR_CHECK(access_model_application_bind(m_client.model_handle, m_appkey_handle));
    ERROR_CHECK(access_model_publish_application_set(m_client.model_handle, m_appkey_handle));
    
    ERROR_CHECK(access_model_publish_address_set((m_client.model_handle, group_handle));
}

void models_init_cb(void)
{
    ERROR_CHECK(config_client_init(app_config_client_event_cb));
    ERROR_CHECK(generic_onoff_client_init(&m_client, FIRST_ELEMENT_INDEX));
}

To test if I correctly self provision the CLIENT node, I used the nrf mesh app to provision the SERVER node (lifgt switch server example), took the appkey and the netkey assigned by the mesh app, hardcoded them into the code for light switch client, configured the subscription address on the server node (using nrf mesh app) and watched, if the server reacts on the button presses on my client board. But I just watched the transfer timeout (transfer timeout - You can can object I use acknowledged messages in group messaging. I am aware of the fact, it is just temporary solution). 

My question is: Is there something missing in the code snippets above relating to be capable to self provision the node? I assume it has to do something with config client or config server model. Every help appreciated :) 

Patrik

Parents
  • Hi Patrik, 

    A good source of reference is to have a look at our Mesh SDK v1.0.1's light switch example. In that example we have the provisioner and the client combined. And we actually have hardcoded provisioning in that example. 

    It looks pretty good in your code, I don't think there is any thing missing. I would suggest to debug on access layer , if you look at the code you can find where we printout "TX:" and "RX:" in access.c (Inside mesh_msg_handle() and packet_tx(). 

    Could you try to printout the message  data and meta data on the client when you press the button (check TX) and on the server when there is any message arrives (check RX) 

    I would also suggest to double check the netkey and app key received on the server after you provision the node using the phone, just to double check if they are identical to what you have on the client. 

  • Thank you for the reply, Hung. I printed the TX and RX info in both of the functions mesh_msg_handle() and packet_tx(). I found out, that the self provisioned client is sending the message on the air. To confirm I am really sending messages on the air, in the access.c  in mesh_evt_cb() I added printing functions:

    static void mesh_evt_cb(const nrf_mesh_evt_t * p_evt)
    {
        switch (p_evt->type)
        {
            case NRF_MESH_EVT_MESSAGE_RECEIVED:
                SEGGER_RTT_printf(0, "%s(): Received message from address 0x%04x\n", __func__, p_evt->params.message.src.value);
                mesh_msg_handle(&p_evt->params.message);
                break;
             case NRF_MESH_EVT_TX_COMPLETE:
                SEGGER_RTT_printf(0, "%s(): Message has been sent on the air with token number %u\n", __func__, p_evt->params.tx_complete.token);
                break;
            default:
                /* Ignore */
                break;
        }
    
    }

     

    On the self provisioned client side: checked the NRF_MESH_EVT_TX_COMPLETE event, because this event happens when the packet is sent on the air. I could also watch in packet_tx() sending to destination address, the source address and the whole message:

    static uint32_t packet_tx(access_model_handle_t handle,
                              const access_message_tx_t * p_tx_message,
                              const access_message_rx_t * p_rx_message,
                              const uint8_t *p_access_payload,
                              uint16_t access_payload_len)
    {
        ....
        ....
        ....
        status = nrf_mesh_packet_send(&tx_params, NULL);
        if (status == NRF_SUCCESS)
        {
            __LOG(LOG_SRC_ACCESS, LOG_LEVEL_DBG1, "TX: [aop: 0x%04x] \n", p_tx_message->opcode.opcode);
            __LOG_XB(LOG_SRC_ACCESS, LOG_LEVEL_DBG1, "TX: Msg", p_tx_message->p_buffer, p_tx_message->length);
            
            SEGGER_RTT_printf(0, "TX: Sending message to address 0x%04x\n", tx_params.dst.value);
            SEGGER_RTT_printf(0, "TX: The source address of message: 0x%04x\n", tx_params.src);
        }
    }
    

    On the server side, which I provisioned and configured by nrf mesh app: checked NRF_MESH_EVT_MESSAGE_RECEIVED event, which is triggered when incomming message.

    In my case the event NRF_MESH_EVT_TX_COMPLETE occurs every time on the client side, but the event NRF_MESH_EVT_MESSAGE_RECEIVED is not occuring on the server side. I am working with group address, publication group address on the client side and subscription address on the server side set the same, checked many times and confirmed, that on both side set the same netkey, appkey, iv index. Not sure if necessary, but in my case on both sides set the same appkey index and netkey index.

    To simulate my case, please try the light switch client example from mesh v1.0.1 on one board and the light switch server example from mesh version 3.1.0 on the second board. The server example I provisioned with nrf mesh app. In the client example I commented the following lines (to prevent provisioning of external nodes): 

    static void button_event_handler(uint32_t button_number)
    {
        __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Button %u pressed\n", button_number);
    //    if (m_configured_devices == 0)
    //    {
    //        __LOG(LOG_SRC_APP, LOG_LEVEL_WARN, "No devices provisioned\n");
    //        return;
    //    }
    //    else if (m_configured_devices <= button_number && button_number != BUTTON_NUMBER_GROUP)
    //    {
    //        __LOG(LOG_SRC_APP, LOG_LEVEL_WARN, "Device %u not provisioned yet.\n", button_number);
    //        return;
    //    }
    
        uint32_t status = NRF_SUCCESS;
        switch (button_number)
        {
            case 0:
            case 1:
            case 2:
                /* Invert LED. */
                status = simple_on_off_client_set(&m_clients[button_number],
                                                  !hal_led_pin_get(BSP_LED_0 + button_number));
                break;
            case 3:
                /* Group message: invert all LEDs. */
                status = simple_on_off_client_set_unreliable(&m_clients[GROUP_CLIENT_INDEX],
                                                             !hal_led_pin_get(BSP_LED_0 + button_number), 3);
                break;
            default:
                break;
        }
        
        ....
        ....
        ....
    }
    
    static void access_setup(void)
    {
        ....
        ....
        ....
        
    //    provisioner_init();
    //    if (m_configured_devices < m_provisioned_devices)
    //    {
    //        provisioner_configure(UNPROV_START_ADDRESS + m_configured_devices);
    //    }
    //    else if (m_provisioned_devices < SERVER_COUNT)
    //    {
    //        provisioner_wait_for_unprov(UNPROV_START_ADDRESS + m_provisioned_devices);
    //    }
    
    }

    I know that the server example contains the generic on off client model and the server example contains the simple on off server model, two different models, but up to the access layer the message should be processed even if not the same appkeys but the publication group address on the client side and subscription address on the server side set the same, network key set the same on both sides, iv index the same on both sides, not sure for appkey index and network index but in my case set the same on both sides.  -- > in this case NRF_MESH_EVT_MESSAGE_RECEIVED  event should occur on the server side when sending messages from client node. I tried it, but this event is not triggered. Dont know why ... Please, try to simulate that case :) 

  • Ouu, yes all the keys are arrays. It is wrong above in the code snippet, but in my code it is ok. I made the mistake by editing to devzone. Double checked the rest of the code above and it is identical with my code. 

  • netkey set to zero, but the result is the same

  • Not Netkey but IV Index you need to set net_state_iv_index_set()

    Could you try adding a breakpoint in mesh_stack_provisioning_data_store() when you provisioning your client (using a provisioner) to see if there is any difference in the provisioning data compare to what you do on your code ? 

  • checked the iv index, network key, netkey index, iv update, key refresh, subscription and publish address. the same by provisioning the node by external provisioner (nrf mesh app) and by self provisioning. still not working. controlling if receiving messages at the other side at the access layer. can it be problem with some other encryption key? any other idea? 

  • Hi Patrik, 

    I will try to do some tests here. But be aware that appkeys are used in the transport layer to decrypt message before the access layer. You can find in the access.c all messages are already decrypted. 

    Also, hardcoded provisioning is not suggested and is not fully following the spec. 

Reply Children
  • Resolved !!!

    It was pain to debug. It was also necessary to set the identical encryption key, privacy key, network id - all members of the structure nrf_mesh_network_secmat_t. Not to acces this structure directly, using device manager function dsm_net_secmat_from_keyindex_get()

  • But there also must be some function, which calculates all those values from the netkey. Dont know so far.

  • Hi Patrik, 

    This code worked for me. There is no model configured but I can see I can receive the message to address 0xC001 on access layer. This is a very minimum code, you would need to add device key, bind the appkey to model etc. 


    uint32_t my_mesh_stack_provisioning_data_store()
    {
        dsm_handle_t netkey_handle, devkey_handle;
        dsm_local_unicast_address_t local_address;
        uint8_t netkey[16]={0x79,0xF5,0xF1,0x6A,0x43,0x22,0x17,0xDF,0x1D,0xC1,0x59,0x10,0x13,0x0A,0x31,0x6F};
        uint8_t appkey[16]={0x5B,0xD8,0xDE,0xB4,0x49,0xAB,0x15,0x1B,0x72,0x7F,0xD4,0x56,0xF5,0x49,0x81,0x9B};
        local_address.address_start = 0x99;
        local_address.count = ACCESS_ELEMENT_COUNT;
        uint32_t status;
        dsm_handle_t app_handle;
        /* Store received provisioning data in the DSM */
        status = dsm_local_unicast_addresses_set(&local_address);
        if (status != NRF_SUCCESS)
        {
            return status;
        }
        status = dsm_subnet_add(0, netkey, &netkey_handle);
        if (status != NRF_SUCCESS)
        {
            return status;
        }
        NRF_MESH_ERROR_CHECK(net_state_iv_index_set(0,0));
    
        dsm_handle_t network_handle = dsm_net_key_index_to_subnet_handle(0);
        status = dsm_appkey_add(0, network_handle, appkey, &app_handle);
        dsm_handle_t subscription_address_handle;
        status = dsm_address_subscription_add(0xC001, &subscription_address_handle);
        return status;
    }

    Note that to print out the "RX:" log in access.c you need to enable logging at level DBG1. 

    (Posted this just to find out that you figured it out 20 minutes earlier :) . Good news Patrik. ) 

  • Hi Hung, I am trying to do a similar thing as this post, but I need to ask, where does teh device key come from?

  • Hi Nishant, 

    The device key is generated using prov_utils_derive_keys(). My understanding is that  it's calculated on both side the provisioner and provisionee (should have the same result) using that function. So that key is not exchanged between them. 

Related