nordic-mesh中应用的代码实现

Nordic-Mesh遵循SIG-Mesh-Profile中的mesh定义,实现了element、model等概念。

一个应用中包含一个或多个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过程。

/* 接口初始化 */
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)中了。

配置server端的的过程如下:

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的操作码处理函数中进行相关的操作。函数注释及定义如下:

/**
* 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)中实现的。

操作码-处理函数

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); }

nordic-mesh中应用的代码实现的更多相关文章

  1. nordic mesh中的消息缓存实现

    nordic mesh中的消息缓存实现 代码文件msg_cache.h.msg_cache.c. 接口定义 头文件中定义了四个接口,供mesh协议栈调用,四个接口如下所示,接口的实现代码在msg_ca ...

  2. nordic mesh 任务调度实现

    nordic mesh 任务调度实现 nordic mesh的任务调度室基于定时器实现的,有两个链表结构维护任务. 需要注意的是,任务调度的部分接口只能在"bearer event" ...

  3. 基于事件驱动机制,在Service Mesh中进行消息传递的探讨

    翻译 | 宋松 原文 | https://www.infoq.com/articles/service-mesh-event-driven-messaging 关键点 当前流行的Service Mes ...

  4. linux内核分析作业4:使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用

    系统调用:库函数封装了系统调用,通过库函数和系统调用打交道 用户态:低级别执行状态,代码的掌控范围会受到限制. 内核态:高执行级别,代码可移植性特权指令,访问任意物理地址 为什么划分级别:如果全部特权 ...

  5. Gradle中的buildScript代码块

    在编写Gradle脚本的时候,在build.gradle文件中经常看到这样的代码: build.gradle 1 2 3 4 5 6 7 8 9 buildScript { repositories ...

  6. Oracle中经典分页代码!

    在Oracle中因为没有top关键字,所以在sqlserver中的分页代码并不适用于Oracle,那么在Oracle中如何来实现分页呢? --查询所有数据 STUNO STUNAME STUAGE S ...

  7. python学习之——计算给出代码中注释、代码、空行的行数

    题目:计算给出代码中注释.代码.空行的行数 来源:网络 思路:注释行以 ‘#’开头,空行以 ‘\n’ 开头,以此作为判断 def count_linenum(fname): fobj = open(f ...

  8. netbeans中实体类代码的bug

    用了netbeans中实体类代码后,忽然报错: com.sun.tools.javac.code.Symbol$CompletionFailure: 找不到sun.util.logging.Platf ...

  9. 在Flex (Flash)中嵌入HTML 代码或页面—Flex IFrame

    在flex组件中嵌入html代码,可以利用flex iframe.这个在很多时候会用到的,有时候flex必须得这样做,如果你不这样做还真不行-- flex而且可以和html进行JavaScript交互 ...

随机推荐

  1. 【JavaScript-基础-文件上传】

    Upload 最原始方式 form表单提交 // html <form method="get" action="/test/upload"> &l ...

  2. Oracle 数据库备份和恢复配置

    可能的失败及其解决方法 失败类型 我们坑你遇到的失败或错误分为两大类:物理和逻辑.物理错误一般是硬件错误或使用数据库的应用程序中的软件错误,而逻辑错误一般在终端用户级别(数据库用户和管理员). 按从轻 ...

  3. Oracle数据库对表基本的操作--增删查改

    --向student表中加入入学时间属性,其数据类型为日期型alter table student add scome date; --删除student表中的入学时间属性alter table st ...

  4. Javascript--将十进制数字转换成罗马数字显示

    下午在FCC(FreeCodeCamp)中文网上做到一道练习题:将给定的数字转换成罗马数字.折磨了一个多小时,终于能把基本功能给实现了.过程如下: 关于罗马数字 罗马数字的详细介绍可见百度,或者罗马数 ...

  5. Hadoop启动dataNode失败,却没有任何报错

    问题描述: centos7,伪分布模式下,启动datanode后,通过JPS查看发现没有相关进程,在日志文件里也没有任何提示.通过百度,网上一堆说什么vesion 的ID不一致,不能解决我的问题. 经 ...

  6. Java调用WeChat's API总结

    微信公众号结合着内置浏览器,有着普通浏览器无法完成的服务,前者可以获取浏览页面的微信用户的信息,从而根据信息为用户提供基于微信的更多服务:而后者仅仅能够浏览页面,通过用户的输入信息与用户互动. 本人根 ...

  7. 韦东山linux学习之ubuntu 9.10 软件源 问题

    跟着开发板视频学习,安装了ubuntu9.10,然而由于现在官方已经不再提供软件更新的服务,软件我一直安装不上,搞了两天终于解决了. 一.安装VMware,配置等等就不详细说了,安装好系统后,网能连上 ...

  8. 【Hutool】Hutool工具类之日期时间工具——DateUtil

    一.用于取代Date对象的DateTime对象 再也不用Date SimpleDateFormat Calendar之间倒腾来倒腾去了!日期创建-获取-操作一步到位! 如果JDK版本更新到了8及以上, ...

  9. js点击后将文字复制到剪贴板,将图片复制到画图

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"><HTML> <HEAD& ...

  10. 学号20155311 2016-2017-2 《Java程序设计》第4周学习总结

    教材学习内容总结 6.1 何谓继承 何谓继承 面向对象中,子类继承父类,避免重复的行为定义,不过并非为了避免重复定义行为就使用继承,滥用继承而导致程序维护上的问题时有所闻.如何正确判断使用继承的时机, ...