nordic-mesh中應用的代碼實現

nordic-mesh中應用的代碼實現

Nordic-Mesh遵循SIG-Mesh-Profile中的mesh定義,實現了element、model等概念。node

一個應用中包含一個或多個element,element是能夠尋址的實體;每一個element中包含多個model,model定義了應用的功能。數據庫

每一個設備在provision階段,其中的每一個element都會得到一個unicast-address;在config階段,設置每一個model的APP-Key等內容,該過程經過configure_model實現。每一個model的發佈地址只有一個,訂閱地址能夠有多個。數組

Provision階段

provision過程就是先掃描un_provision幀,而後根據UUID選擇進行provision的過程,provision就是給未配網節點設置unicast-address、Netkey、IV_Index的過程。網絡

在nordic的示例中,將provisioner相關的接口封裝到了provisioner_helper.c(h)中,由如下四個接口函數控制provision過程。app

/* 接口初始化 */
void prov_helper_init(mesh_provisioner_init_params_t * p_prov_init_info);

/* 開始掃描beacon幀 */
void prov_helper_scan_start(void);

/* 根據UUID的過濾字段進行provision, UUID中包含過濾字段的設備會被provision */
void prov_helper_provision_next_device(uint8_t retry_cnt, uint16_t address,
                                       prov_helper_uuid_filter_t * p_uuid_filter);
/* 給provisioner節點自己配置NetKey、Unicast-address */                                     
void prov_helper_provision_self(void);

咱們從prov_helper_provision_self()函數的實現中,理解provision的過程,配置本節點與配置其餘節點本質是一致的,只是一個直接修改本地狀態,一個經過網絡傳輸在接收端經過操做碼處理函數修改狀態。函數

void prov_helper_provision_self(void)
{
    /* Add addresses */
    /* Set and add local addresses and keys, if flash recovery fails. */
    dsm_local_unicast_address_t local_address = {PROVISIONER_ADDRESS, ACCESS_ELEMENT_COUNT};
    ERROR_CHECK(dsm_local_unicast_addresses_set(&local_address));

    /* Generate keys, 隨機產生各類KEY*/
    rand_hw_rng_get(m_provisioner.p_nw_data->netkey, NRF_MESH_KEY_SIZE);
    rand_hw_rng_get(m_provisioner.p_nw_data->appkey, NRF_MESH_KEY_SIZE);
    rand_hw_rng_get(m_provisioner.p_nw_data->self_devkey, NRF_MESH_KEY_SIZE);

    /* Add default Netkey and App Key */
    ERROR_CHECK(dsm_subnet_add(0, m_provisioner.p_nw_data->netkey, &m_provisioner.p_dev_data->m_netkey_handle));
    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "netkey_handle: %d\n", m_provisioner.p_dev_data->m_netkey_handle);
    ERROR_CHECK(dsm_appkey_add(0, m_provisioner.p_dev_data->m_netkey_handle, m_provisioner.p_nw_data->appkey, &m_provisioner.p_dev_data->m_appkey_handle));

    /* Add device key for the own config server */
    ERROR_CHECK(dsm_devkey_add(PROVISIONER_ADDRESS, m_provisioner.p_dev_data->m_netkey_handle, m_provisioner.p_nw_data->self_devkey, &m_provisioner.p_dev_data->m_self_devkey_handle));

}

Configure階段

provision以後節點就得到了unicast地址與Netkey,只須要再給節點配置Appkey以及發佈訂閱地址就能夠正常實現功能了,這個過程在nordic_mesh的示例也封裝在了node_setup.c(h)中了。ui

配置server端的的過程以下:code

static const config_steps_t server1_server2_config_steps[] =
{
    // 獲取composition_data
    // 這裏麪包含了待配置節點的基本信息,好比有多少了model、多少個element等等
    NODE_SETUP_CONFIG_COMPOSITION_GET,

    // 添加appkey,並綁定到health-server。
    // appkey_add過程,至關於把key保存在本地數據庫中,並返回handle
    // appkey_bind過程,至關於把key傳輸給health_server
    NODE_SETUP_CONFIG_APPKEY_ADD,
    NODE_SETUP_CONFIG_APPKEY_BIND_HEALTH,
    
    // appkey綁定到light_server
    NODE_SETUP_CONFIG_APPKEY_BIND_ONOFF_SERVER,
    
    // 配置health_server的發佈地址
    NODE_SETUP_CONFIG_PUBLICATION_HEALTH,
    
    // 配置light_server的發佈地址
    NODE_SETUP_CONFIG_PUBLICATION_ONOFF_SERVER1_2,
    
    // 配置light_server的訂閱地址
    // 將節點分組,就是給節點的訂閱地址加一個組地址
    NODE_SETUP_CONFIG_SUBSCRIPTION_ONOFF_SERVER,
    
    NODE_SETUP_DONE
};

AppKey的綁定經過函數config_client_model_app_bind()實現,該函數把須要設置的內容發送到對應的地址,在接收端根據Config_server的操做碼處理函數中進行相關的操做。函數註釋及定義以下:orm

/**
 * Sends a application bind request.
 *
 * @note Response: @ref CONFIG_OPCODE_MODEL_APP_STATUS
 *
 * @param[in] element_address Element address of the model.
 * @param[in] appkey_index    Application key index to bind/unbind.
 * @param[in] model_id        Model ID of the model.
 *
 * @retval NRF_SUCCESS             Successfully sent request.
 * @retval NRF_ERROR_BUSY          The client is in a transaction. Try again later.
 * @retval NRF_ERROR_NO_MEM        Not enough memory available for sending request.
 * @retval NRF_ERROR_INVALID_STATE Client not initialized.
 */
uint32_t config_client_model_app_bind(uint16_t element_address, uint16_t appkey_index, access_model_id_t model_id)
{
    return app_bind_unbind_send(element_address, appkey_index, model_id, CONFIG_OPCODE_MODEL_APP_BIND);
}
有一點須要注意,在nordic_mesh的實現中,無論是key的設置仍是address的設置,都是經過handle進行的,這個handle實際上就是數組的index。所以須要首先將地址或key添加到本地數據庫,這個歌添加過程會得到一個handle,而後經過handle設置對應內容。

Model管理

在nordic的實現中,element、model的管理是在access.c(h)中實現的,消息發佈是在access_publish.c(h) access_reliable.c(h)中實現的,地址、netkey、appkey的管理是在device_state_manager.c(h)中實現的。server

操做碼-處理函數

mesh中的功能都是經過model來定義的,SIG_Mesh_Profile文檔中定義了四種基本的model,分別是config_server、config_client、health_server、health_client。其中config_server、health_server是默認存在的,且存在於主element中(element_pool[0]即爲主element)。

model的功能是經過Opcode-Handler來定義了,一個model中的opcode與響應的處理函數決定了這個model的功能。nordic定義了一個基本的開關燈的model,其支持以下的操做碼,並定義了每一個操做碼消息的內容(即操做碼的參數)。咱們能夠在此基礎上添加新的操做碼來實現更加複雜的功能,從簡單的開關到RGB燈多路控制,再到參數存儲、定時任務等。

/** Simple OnOff opcodes. */
typedef enum
{
    SIMPLE_ON_OFF_OPCODE_SET = 0xC1,            /**< Simple OnOff Acknowledged Set. */
    SIMPLE_ON_OFF_OPCODE_GET = 0xC2,            /**< Simple OnOff Get. */
    SIMPLE_ON_OFF_OPCODE_SET_UNRELIABLE = 0xC3, /**< Simple OnOff Set Unreliable. */
    SIMPLE_ON_OFF_OPCODE_STATUS = 0xC4          /**< Simple OnOff Status. */
} simple_on_off_opcode_t;

/* 開關設置消息的參數 */
typedef struct __attribute((packed))
{
    uint8_t on_off; /**< State to set. */
    uint8_t tid;    /**< Transaction number. */
} simple_on_off_msg_set_t;

/** Message format for th Simple OnOff Set Unreliable message. */
typedef struct __attribute((packed))
{
    uint8_t on_off; /**< State to set. */
    uint8_t tid;    /**< Transaction number. */
} simple_on_off_msg_set_unreliable_t;

/** Message format for the Simple OnOff Status message. */
typedef struct __attribute((packed))
{
    uint8_t present_on_off; /**< Current state. */
} simple_on_off_msg_status_t;

在server、client分別定義對應每一個Opcode的處理函數,就能夠實現每一個操做碼實現什麼操做。對於開關model,其操做碼與處理函數對應以下:

/* server 端 Opcode-Handler */
static const access_opcode_handler_t m_opcode_handlers[] =
{
    {ACCESS_OPCODE_VENDOR(SIMPLE_ON_OFF_OPCODE_SET,            SIMPLE_ON_OFF_COMPANY_ID), handle_set_cb},
    {ACCESS_OPCODE_VENDOR(SIMPLE_ON_OFF_OPCODE_GET,            SIMPLE_ON_OFF_COMPANY_ID), handle_get_cb},
    {ACCESS_OPCODE_VENDOR(SIMPLE_ON_OFF_OPCODE_SET_UNRELIABLE, SIMPLE_ON_OFF_COMPANY_ID), handle_set_unreliable_cb}
};

/* Client 端  Opcode-Handler */
static const access_opcode_handler_t m_opcode_handlers[] =
{
    {{SIMPLE_ON_OFF_OPCODE_STATUS, SIMPLE_ON_OFF_COMPANY_ID}, handle_status_cb}
};

Model添加

model是經過一個數組結構m_model_pool[]來管理,在access.c中定義了一個m_model_pool的全局變量用來管理全部的model。

須要在應用中實現某個Model的話,首先須要將其加入到Model池,這是經過函數access_model_add()實現的,下面代碼段是該函數的註釋及定義。model初始化參數做爲函數參數傳入,model_handle經過地址方式返回新添加model在model_pool中的index。在函數實現中,首先在model_pool數組中找到未被佔用的位置,而後以該位置做爲model_handle。

/**
 * Allocates, initializes and adds a model to the element at the given element index.
 *
 * @param[in]  p_model_params            Pointer to model initialization parameter structure.
 * @param[out] p_model_handle            Pointer to store allocated model handle.
 *
 * @retval     NRF_SUCCESS               Successfully added model to the given element.
 * @retval     NRF_ERROR_NO_MEM          @ref ACCESS_MODEL_COUNT number of models already allocated.
 * @retval     NRF_ERROR_NULL            One or more of the function parameters was NULL.
 * @retval     NRF_ERROR_FORBIDDEN       Multiple model instances per element is not allowed.
 * @retval     NRF_ERROR_NOT_FOUND       Invalid access element index.
 * @retval     NRF_ERROR_INVALID_LENGTH  Number of opcodes was zero.
 * @retval     NRF_ERROR_INVALID_PARAM   One or more of the opcodes had an invalid format.
 * @see        access_opcode_t for documentation of the valid format.
 */
uint32_t access_model_add(const access_model_add_params_t * p_model_params,
                          access_model_handle_t * p_model_handle)
{
    /*
        參數有效性校驗     
    */
    {
        *p_model_handle = find_available_model();
        if (ACCESS_HANDLE_INVALID == *p_model_handle)
        {
            return NRF_ERROR_NO_MEM;
        }

        m_model_pool[*p_model_handle].model_info.publish_address_handle = DSM_HANDLE_INVALID;
        m_model_pool[*p_model_handle].model_info.publish_appkey_handle = DSM_HANDLE_INVALID;
        m_model_pool[*p_model_handle].model_info.element_index = p_model_params->element_index;
        m_model_pool[*p_model_handle].model_info.model_id.model_id = p_model_params->model_id.model_id;
        m_model_pool[*p_model_handle].model_info.model_id.company_id = p_model_params->model_id.company_id;
        m_model_pool[*p_model_handle].model_info.publish_ttl = m_default_ttl;
        increment_model_count(p_model_params->element_index, p_model_params->model_id.company_id);
        ACCESS_INTERNAL_STATE_OUTDATED_SET(m_model_pool[*p_model_handle].internal_state);
    }

    m_model_pool[*p_model_handle].p_args = p_model_params->p_args;
    m_model_pool[*p_model_handle].p_opcode_handlers = p_model_params->p_opcode_handlers;
    m_model_pool[*p_model_handle].opcode_count = p_model_params->opcode_count;

    m_model_pool[*p_model_handle].publication_state.publish_timeout_cb = p_model_params->publish_timeout_cb;
    m_model_pool[*p_model_handle].publication_state.model_handle = *p_model_handle;
    ACCESS_INTERNAL_STATE_ALLOCATED_SET(m_model_pool[*p_model_handle].internal_state);

    return NRF_SUCCESS;
}

Model配置

Model添加後,須要配置過Appkey及發佈訂閱地址才能夠正常工做,配置的過程在config_client端實現,參照前面Config過程。Config_client_model所在的節點,能夠直接配置。好比,咱們在config-client_model所在的節點上,實現light_client_model的過程以下:

/*
     * 初始化 light-client-model,就是一個access_model_add()的過程
     */ 
    uint32_t simple_on_off_client_init(simple_on_off_client_t * p_client, uint16_t element_index)
    {
        if (p_client == NULL ||
            p_client->status_cb == NULL)
        {
            return NRF_ERROR_NULL;
        }

        access_model_add_params_t init_params;
        init_params.model_id.model_id = SIMPLE_ON_OFF_CLIENT_MODEL_ID;
        init_params.model_id.company_id = SIMPLE_ON_OFF_COMPANY_ID;
        init_params.element_index = element_index;
        init_params.p_opcode_handlers = &m_opcode_handlers[0];
        init_params.opcode_count = sizeof(m_opcode_handlers) / sizeof(m_opcode_handlers[0]);
        init_params.p_args = p_client;
        init_params.publish_timeout_cb = handle_publish_timeout;
        return access_model_add(&init_params, &p_client->model_handle);
    }

    /*
     * 初始化 四個light_client
     */
    __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Initializing and adding light-client models\n");

    for (uint32_t i = 0; i < CLIENT_MODEL_INSTANCE_COUNT; ++i)
    {
        m_clients[i].status_cb = client_status_cb;
        m_clients[i].timeout_cb = client_publish_timeout_cb;
        uint32_t ret=simple_on_off_client_init(&m_clients[i], i + 1);
        
        ERROR_CHECK(access_model_subscription_list_alloc(m_clients[i].model_handle));
    }

    /*
     * 綁定appkey,及設置發佈訂閱地址
     */
    for(int i =0 ; i<4; i++){
        ERROR_CHECK(access_model_application_bind(m_clients[i].model_handle, m_dev_handles.m_appkey_handle));
        ERROR_CHECK(access_model_publish_application_set(m_clients[i].model_handle, m_dev_handles.m_appkey_handle));
    
        dsm_handle_t address_handle;
        uint32_t status = dsm_address_publish_add(0x100+i, &address_handle); 

        __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "dsm_address_publish_add status: %d \n", status);
        
        status = access_model_publish_address_set(m_clients[i].model_handle, address_handle);
        __LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "model_pulish_set status: %d \n", status);
        
    }
相關文章
相關標籤/搜索