继上一篇Vendor Model浅析,大家如今知道,不管是SIG Model还是Vendor Model,其组成的结构都是一样的,即Model ID + Opcode + Message;因此,上篇的文章为本篇章创建红旭Model提供了理论的基础,既然要创建红旭Model,那么我们先看看红旭的开发板又有哪些功能,废话不多说:上图!!!
由前言可知,红旭的开发板共有4个用户按键、4个LED灯、1个串口、一个USB和NFC接口;然而,此次红旭Model只用到按键、LED以及串口,其具体的功能定义如下:
小编在什么是Element和Model中就讲过,一个节点可以同时存在Server和Client模型,即控制模型(Control Model);因此,按键的具体功能定义如下:
按键1
向组地址0xC000发布Generic Onoff Set消息
按键2
向组地址0xC001发布Generic Onoff Set消息
按键3
向组地址0xC002发布 “Wireless” 的Vendor消息
按键4
向虚拟地址发布 “HX Wireless-tech” 的Vendor消息
至少,按键1和按键2的功能定义,读者们都很好理解;但是,按键3和按键4则是单包与分包发送的区别,其也将用于下一篇的自主分包与MESH协议分包重组的区别讲解时使用;
对于红旭开发板的4个LED定义为如下几个功能:
LED1
订阅组地址0xC000,接收并处理按键1发送过来的Generic Onoff Set消息
LED2
订阅组地址0xC001,接收并处理按键2发送过来的Generic Onoff Set消息
LED3
响应Provisioner的Generic Onoff Set/Get消息
LED4
响应Provisioner的Generic Onoff Set/Get消息
串口或者RTT主要将对端节点发送过来的Vendor消息 (自定义的数据,如字符串、长数据包等等) 打印出来
上述的内容,讲的是开发板相关外设的功能定义;接下来,小编将讲解如何创建红旭模型;如按键中所述,模型分SERVER、CLIENT和CONTROL模型三种:
这是用的相对较多的一种模型,大部分情况下均属于被动的状态,换句话就是说被动响应;小编在前言中就讲解,不管是哪种类型的模型,其组成部分均是由Model ID + Opcode + Message三部分组成;
由Vendor Model浅析可知:
Vendor Model ID = Company ID + Vendor-Assigned Model ID
为了更好理解上述的内容,小编这里以红旭的服务端模型ID为例:
其中,Company ID为0x0059即Nordic,而小编给红旭定义的Vendor-Assigned Server Model ID为0x0000,因此红旭的服务端模型为0x00590000;
/* 标识符 */
/* Bluetooth SIG分配给Nordic的Company identifier */
/* HX服务端模型的Vendor-assigned Model ID */
简单地来说,有什么类型的操作码就决定了你这个模型有什么样的功能;而此时小编想要创建的红旭模型就是一个能传自定义数据的透传模型,即你一次可以传输最大384字节的数据,也可以传只有一个操作码的数据,任君喜欢;
xxxxxxxxxx
/* 操作码的定义 */
/* 操作码的定义 */
/* 应答收到的透传自定义数据 */
/* 接收上一次发送的透传自定义数据 */
可能有读者会疑问为什么自定义的操作码还要或上0xC0?,不知道读者们对Vendor Model浅析的内容还有没有印象,即Vendor Model操作码的最高的2个有效位必须是11;
HX_MODEL_SERVER_OPCODE_STATUS
该操作码主要用于应答红旭客户端的HX_MODEL_CLIENT_OPCODE_TX操作码,该操作码携带的消息为OK!
其中Parameters 4F 4B 21 00就是携带的消息OK!
HX_MODEL_SERVER_OPCODE_RX
该操作码则用于应答红旭客户端的HX_MODEL_CLIENT_OPCODE_GET操作码,该操作码携带的消息内容为上一次接收到的透传数据
从上面的捉包图我们可以看到,小编发了Parameters 12 34 56 78 90 12 34 56 78 90共10个字节的消息,加上操作码和TransMIC共17个字节;小编很早之前在BLE Mesh各层帧包格式详解中就提及到最大不分包的Access PDU为15字节,故上述的17字节就一定要分包,同时我们也知道分包时满载荷最大为12字节;因此,17字节会为12+5两包来发送;那事实是否也是这样呢?请看下图:
从上面的两幅图我们可以看到,第一包确实是发的12字节,第二包也如上面理论分析的5字节,因此上述的理论完全正确;
更多的分包重组内容请参考自主分包与MESH协议分包重组的区别文章中的描述;
虽然,在红旭服务端模型中发送的是上述的两个操作码及其携带的消息,但是其要处理红旭客户端模型发送过来的数据,仍然需要将客户端操作码对应的回调函数映射了,即:
xxxxxxxxxx
/*****************************************************************************
* 操作码与其对应的回调函数的映射
*****************************************************************************/
const access_opcode_handler_t m_server_opcode_handlers[] =
{
{ACCESS_OPCODE_VENDOR(HX_MODEL_CLIENT_OPCODE_TX, SERVER_COMPANY_IDENTIFIER), hx_model_opcode_tx_cb},
{ACCESS_OPCODE_VENDOR(HX_MODEL_CLIENT_OPCODE_GET, SERVER_COMPANY_IDENTIFIER), hx_model_opcode_get_cb},
{ACCESS_OPCODE_VENDOR(HX_MODEL_CLIENT_OPCODE_TX_UNRELIABLE, SERVER_COMPANY_IDENTIFIER), hx_model_opcode_tx_unreliable_cb}
};
这里也就是很多读者叨叨念的想要传送出去的自定义数据内容,针对 HX_MODEL_SERVER_OPCODE_RX和HX_MODEL_SERVER_OPCODE_STATUS两个操作码,其携带消息的处理函数如下:
HX_MODEL_SERVER_OPCODE_STATUS
x/* HX_MODEL_CLIENT_OPCODE_TX消息的应答 */
static void tx_ack(const hx_model_server_t *p_server,
const access_message_rx_t *p_message)
{
/* 应答的内容 */
uint8_t acknowledgment[] = "OK!";
access_message_tx_t reply;
reply.opcode.opcode = HX_MODEL_OPCODE_STATUS;
reply.opcode.company_id = SERVER_COMPANY_IDENTIFIER;
reply.p_buffer = acknowledgment;
reply.length = sizeof(acknowledgment) / sizeof(acknowledgment[0]);
reply.force_segmented = false;
reply.transmic_size = NRF_MESH_TRANSMIC_SIZE_DEFAULT;
reply.access_token = nrf_mesh_unique_token_get();
(void)access_model_reply(p_server->model_handle, p_message, &reply);
}
HX_MODEL_SERVER_OPCODE_RX
xxxxxxxxxx
/* HX_MODEL_CLIENT_OPCODE_GET消息的应答 */
static void get_reply(const hx_model_server_t *p_server,
const access_message_rx_t *p_message)
{
access_message_tx_t reply;
reply.opcode.opcode = HX_MODEL_OPCODE_RX;
reply.opcode.company_id = SERVER_COMPANY_IDENTIFIER;
if (data_length == 0)
{
reply.p_buffer = NULL;
reply.length = 0;
}
else
{
reply.p_buffer = message_from_opcode_tx;
reply.length = data_length;
}
/* 这里不强制分包,协议栈会自动处理是否分包 */
reply.force_segmented = false;
reply.transmic_size = NRF_MESH_TRANSMIC_SIZE_DEFAULT;
reply.access_token = nrf_mesh_unique_token_get();
(void)access_model_reply(p_server->model_handle, p_message, &reply);
}
如服务端模型所述,小编将红旭的Client Model的模型ID定义为0x00590001;
xxxxxxxxxx
/* 标识符 */
/* Bluetooth SIG分配给Nordic的Company identifier */
/* HX客户端模型的Vendor-assigned Model ID */
相较于红旭服务端模型一味地被动接收,客户端模型则用于自定义透传数据的主动发送,其相对应的操作码定义如下:
xxxxxxxxxx
/* 操作码的定义 */
/* 透传自定义数据,带应答 */
/* 透传自定义数据,不带应答 */
/* 查询上一次透传的自定义数据 */
那么,上述的操作码具体的功能是什么呢?
HX_MODEL_CLIENT_OPCODE_TX
该操作码主要用来透传用户自定义的数据,当红旭的服务端模型收到该操作码携带的消息,先处理消息的内容,其次给红旭的客户端模型应答HX_MODEL_SERVER_OPCODE_STATUS
其中Parameter携带的内容就是Wireless-tech的意思
HX_MODEL_CLIENT_OPCODE_TX_UNRELIABLE
该操作码的功能与上述的HX_MODEL_CLIENT_OPCODE_TX是一致的,只不过该操作码所携带的消息被服务端模型接收到之后,不需要进行应答
HX_MODEL_CLIENT_OPCODE_GET
当服务端模型收到该操作码时,将上一次接收到的自定义透传数据返回给客户端模型;然则需要注意的是:该操作码不携带任何消息
同样的,为了能有效地处理服务端模型应答的消息,客户端模型也是需要对客户端模型的操作码与回调处理函数进行映射的,详情如下所示:
xxxxxxxxxx
/*****************************************************************************
* 操作码与其对应的回调函数的映射
*****************************************************************************/
const access_opcode_handler_t m_client_opcode_handlers[] =
{
{ACCESS_OPCODE_VENDOR(HX_MODEL_SERVER_OPCODE_STATUS, CLIENT_COMPANY_IDENTIFIER), hx_model_opcode_status_cb},
{ACCESS_OPCODE_VENDOR(HX_MODEL_SERVER_OPCODE_RX, CLIENT_COMPANY_IDENTIFIER), hx_model_opcode_rx_cb}
};
对于客户端模型而言,上述的3个操作码分别对应的处理函数如下:
HX_MODEL_CLIENT_OPCODE_TX
xxxxxxxxxx
/**
* 发送自定义的透传数据给红旭的服务端模型(带应答)
*
* @param[in,out] p_client HX客户端模型的结构指针
* @param[in] p_data 指向透传数据的指针
* @param[in] length 透传数据的总长度
*
* @retval NRF_SUCCESS 透传数据发送成功
* @retval NRF_ERROR_NULL 指针为空
* @retval NRF_ERROR_NO_MEM 内存空间不足
* @retval NRF_ERROR_NOT_FOUND 模型句柄无效
* @retval NRF_ERROR_INVALID_ADDR 元素索引值无效
* @retval NRF_ERROR_INVALID_STATE 透传数据正在传输中,此时状态无效
* @retval NRF_ERROR_INVALID_PARAM 对应的模型没有绑定appkey或者没有配置发布地址或者操作码格式不正确
*
*/
uint32_t hx_model_client_opcode_tx(hx_model_client_t *p_client, uint8_t *p_data, uint16_t length)
{
if (p_client == NULL || p_client->p_callbacks->status_handler == NULL)
{
return NRF_ERROR_NULL;
}
else if (p_client->state.reliable_transfer_active)
{
return NRF_ERROR_INVALID_STATE;
}
uint32_t status = tx_reliable_message(p_client,
HX_MODEL_OPCODE_TX,
p_data,
length);
if (status == NRF_SUCCESS)
{
p_client->state.reliable_transfer_active = true;
}
return status;
}
HX_MODEL_CLIENT_OPCODE_TX_UNRELIABLE
xxxxxxxxxx
/**
* 发送自定义的透传数据给红旭的服务端模型(不带应答)
*
* @param[in,out] p_client HX客户端模型的结构指针
* @param[in] p_data 指向透传数据的指针
* @param[in] length 透传数据的总长度
* @param[in] repeats 发送该消息时的次数,因为没有应答所以需要多发几次
*
* @retval NRF_SUCCESS 透传数据发送成功
* @retval NRF_ERROR_NULL 指针为空
* @retval NRF_ERROR_NO_MEM 内存空间不足
* @retval NRF_ERROR_NOT_FOUND 模型句柄无效
* @retval NRF_ERROR_INVALID_ADDR 元素索引值无效
* @retval NRF_ERROR_INVALID_PARAM 对应的模型没有绑定appkey或者没有配置发布地址或者操作码格式不正确
*
*/
uint32_t hx_model_client_opcode_tx_unreliable(hx_model_client_t *p_client, uint8_t *p_data, uint16_t length, uint8_t repeats)
{
access_message_tx_t message;
message.opcode.opcode = HX_MODEL_OPCODE_TX_UNRELIABLE;
message.opcode.company_id = CLIENT_COMPANY_IDENTIFIER;
message.p_buffer = p_data;
message.length = length;
/* 不强制分包,协议栈会自动处理是不是分包 */
message.force_segmented = false;
message.transmic_size = NRF_MESH_TRANSMIC_SIZE_DEFAULT;
uint32_t status = NRF_SUCCESS;
for (uint8_t i = 0; i < repeats; ++i)
{
message.access_token = nrf_mesh_unique_token_get();
status = access_model_publish(p_client->model_handle, &message);
if (status != NRF_SUCCESS)
{
break;
}
}
return status;
}
HX_MODEL_CLIENT_OPCODE_GET
xxxxxxxxxx
/**
* 获取红旭服务端模型收到的透传数据
*
* @note 红旭服务端模型将回复其上一次收到的透传数据
*
* @param[in,out] p_client HX客户端模型的结构指针
*
* @retval NRF_SUCCESS GET消息被发送成功
* @retval NRF_ERROR_NULL 指针为空
* @retval NRF_ERROR_NO_MEM 内存空间不足
* @retval NRF_ERROR_NOT_FOUND 模型句柄无效
* @retval NRF_ERROR_INVALID_ADDR 元素索引值无效
* @retval NRF_ERROR_INVALID_STATE 透传数据正在传输中,此时状态无效
* @retval NRF_ERROR_INVALID_PARAM 对应的模型没有绑定appkey或者没有配置发布地址或者操作码格式不正确
*
*/
uint32_t hx_model_client_opcode_get(hx_model_client_t *p_client)
{
if (p_client == NULL || p_client->p_callbacks->rx_handler == NULL)
{
return NRF_ERROR_NULL;
}
else if (p_client->state.reliable_transfer_active)
{
return NRF_ERROR_INVALID_STATE;
}
uint32_t status = get_reliable_message(p_client,
HX_MODEL_OPCODE_GET);
if (status == NRF_SUCCESS)
{
p_client->state.reliable_transfer_active = true;
}
return status;
}
xxxxxxxxxx
## 控制端模型
小编在前面就已经提及到,**控制端模型 = 服务端模型 + 客户端模型**,所以控制端模型是共用**服务和客户端模型**的操作码和消息处理函数;
### Model ID
这个同上述的[服务端模型.Model ID](#server_model_id)和[客户端模型.Model ID](#client_model_id)基本一致,小编这里定义的红旭控制端模型ID为0x00590002,具体如下所示:
```c
/* 标识符 */
#define SERVER_COMPANY_IDENTIFIER 0x0059 /* Bluetooth SIG分配给Nordic的Company identifier */
#define HX_MODEL_CONTROL_ID 0x0002 /* HX控制端模型的Vendor-assigned Model ID */
同上述的服务端模型.Message和客户端模型.Message
小编在上述中已经讲解了服务端以及客户端模型的各个组成部分,对于天资聪颖的读者来说,结合前面几篇的内容,我相信这一类读者应该知道如何去创建自己的模型;而对于跟小编一样天资愚钝的读者,则需要更加努力地去看接下来的内容了;上面我们明白了创建一个模型需要什么,但是从无到有是怎么样的一个过程呢?仍然是老规矩:上图!!!
至于模型ID、操作码、回调函数的内容,上述的服务端模型以及客户端模型已经描述地很清楚了,那么小编这里着重要讲得是初始化自定义模型、应用处理函数以及应用层调用并处理自定义的模型
上述的内容已经分别讲解了Model ID、Opcode、Message;那么,又是如何将这三个参数串联起来呢?这里以红旭的服务端模型为例:
xxxxxxxxxx
/**
* 初始化HX服务端模型并分配订阅列表.
*
*
* @param[in] p_server HX服务端模型的结构体指针
* @param[in] element_index 在节点的哪个元素中增加该服务端模型
*
* @retval NRF_SUCCESS 模型初始化成功
* @retval NRF_ERROR_NULL 传进形参的实参是空指针
* @retval NRF_ERROR_NO_MEM 新添模型时,内存不足
* @retval NRF_ERROR_FORBIDDEN 每个元素不允许多个模型实例存在
* @retval NRF_ERROR_NOT_FOUND 无效的元素索引值
*/
uint32_t hx_model_server_init(hx_model_server_t *p_server, uint16_t element_index)
{
if (p_server == NULL ||
p_server->p_callbacks->tx_handler == NULL ||
p_server->p_callbacks->get_handler == NULL)
{
return NRF_ERROR_NULL;
}
access_model_add_params_t init_params;
init_params.element_index = element_index;
init_params.model_id.model_id = HX_MODEL_SERVER_ID;
init_params.model_id.company_id = SERVER_COMPANY_IDENTIFIER;
init_params.p_opcode_handlers = &m_server_opcode_handlers[0];
init_params.opcode_count = sizeof(m_server_opcode_handlers) / sizeof(m_server_opcode_handlers[0]);
init_params.p_args = p_server;
/* 因为我们用的非二值信号量的Vendor模型,所以暂不支持Publication功能 */
init_params.publish_timeout_cb = NULL;
uint32_t status = access_model_add(&init_params, &p_server->model_handle);
if (status == NRF_SUCCESS)
{
status = access_model_subscription_list_alloc(p_server->model_handle);
}
return status;
}
由上述的函数可知,将我们之前定义的Model ID、Opcode以及其对应的回调函数均覆盖了;最终通过Nordic提供的access_model_add API函数,就将这些内容串联起来了,至于Message的内容则放在应用处理函数中处理;当access_model_add API函数调用之后的返回值为0,那么此时自定义的模型就意味着创建成功;一旦已经添加成功的操作码被接收到,那么MESH协议栈就会将自定义的操作码回调函数上抛到应用层,如HX_MODEL_CLIENT_OPCODE_TX操作码被接收到,那么其对应的hx_model_opcode_tx_cb回调函数就会被触发; 为了实现订阅地址的功能,还需要通过access_model_subscription_list_alloc API函数通知MESH协议栈,让其为此模型分配一个订阅列表;
应用处理函数的意思就是说,当自定义的透传数据已经随实参传入相对应的操作码回调函数,此时应用层处理这些数据的函数,这里小编以HX_MODEL_CLIENT_OPCODE_TX操作码的回调函数hx_model_opcode_tx_cb被触发为例;
xxxxxxxxxx
/* HX_MODEL_OPCODE_TX操作码回调处理函数 */
static void hx_model_opcode_tx_cb(access_model_handle_t handle,
const access_message_rx_t *p_message,
void *p_args)
{
hx_model_control_t* p_control = p_args;
hx_model_server_t *p_server = &p_control->server_model;
hx_model_server_t *p_server = p_args;
NRF_MESH_ASSERT(p_server->p_callbacks->tx_handler!= NULL);
data_length = p_message->length;
memcpy(message_from_opcode_tx,p_message->p_data, data_length);
/* 根据操作码携带的消息,执行相对应的操作 */
p_server->p_callbacks->tx_handler(p_server, p_message->p_data, data_length);
tx_ack(p_server, p_message);
}
其中 p_server->p_callbacks->tx_handler(p_server, p_message->p_data, data_length) 就是应用处理函数,而tx_handler函数则需要用户在应用层自定义,其函数原形如下:
xxxxxxxxxx
typedef struct
{
/* 红旭客户端的操作码TX,所携带的消息的处理函数 */
hx_model_opcode_tx_handler tx_handler;
/* 红旭客户端的操作码GET,所携带的消息的处理函数 */
hx_model_opcode_get_handler get_handler;
} hx_server_model_callbacks_t;
/* HX模型的功能定义 */
struct hx_model_server
{
access_model_handle_t model_handle;
/* 红旭客户端的操作码,所携带的消息的处理函数 */
hx_server_model_callbacks_t* p_callbacks;
};
/* 定义处理HX_MODEL_OPCODE_TX或者HX_MODEL_OPCODE_TX_UNRELIABLE的消息的函数 */
typedef void (*hx_model_opcode_tx_handler)(const hx_model_server_t * p_self, \
const uint8_t* p_data,uint8_t length);
其中形参p_data就是获取得到的自定义透传数据,而形参length则是获取的数据的长度;然而,具体怎么处理这些数据则因人而异
当相对应的操作码的回调函数被触发,应用层又应该如何获取客户端发送过来的数据,以便做进一步地处理;小编这里以HX_MODEL_CLIENT_OPCODE_TX操作码以及其回调函数hx_model_opcode_tx_cb为例;老规距,上图!!!
上图可知:
小编在功能定义中定义了相关的功能,主要就是定义了红旭开发板的按键和LED,其中LED的功能比较容易,只需要接收相应的消息并点亮或者关闭LED灯即可,主要的功能实现在于按键的处理;这里,小编一一对上述的按键功能进行分解:
按键1 & 按键2
既然是向组地址0xC000/0xC001发布Generic Onoff Set消息,那么显然需要使用客户端模型,而对端设备则需要服务端模型来接收并处理这个消息;因此,需要两个控制端模型或者两个客户端模型和两个服务端模型;这是因为一个模型不能同时设置两个Publish Address
按键3 & 按键4
同按键1 & 按键2,只不过是基于红旭的模型传送自定义的数据
这里小编将通过两种方式来实现上述的功能:
如果采用这种方式的话,就意味着需要新增服务端模型*2和客户端模型*2,但是这个时候必须将两个完全相同的模型放在不同的元素下,如下图所示:
小编在上述篇幅中已经分别讲解了Client和Server模型的操作码、回调函数、应用层处理函数等等,那么如果将这些定义好的模型能增加至元素下呢?如何处理操作码所携带的消息呢?这里小编以新增两个Client模型为例:
根据自己的模型个数,修改相对应的宏ACCESS_MODEL_COUNT
xxxxxxxxxx
/**
* The number of models in the application.
*
* @note To fit the configuration and health models, this value must equal at least
* the number of models needed by the application plus two.
*/
小编是基于Light Switch工程的基础上新增两个模型,由于该工程已经有3个模型了,因此这里ACCESS_MODEL_COUNT宏设置为5
根据自己的模型数量以及类型,修改相对应的宏ACCESS_ELEMENT_COUNT
xxxxxxxxxx
/**
* The number of elements in the application.
*
* @warning If the application is to support _multiple instances_ of the _same_ model, these instances
* cannot be in the same element and a separate element is needed for each new instance of the same model.
*/
因为小编新增的是两个类型完全相同的模型,需要将这两个模型放在不同的元素下,所以这里设置为2
完成上述的2个步骤之后,现在开始定义应用层处理函数
xxxxxxxxxx
/*****************************************************************************
* 红旭CLIENT模型的回调处理函数
*****************************************************************************/
static void hx_model_client_rx_cb(const hx_model_client_t *p_self,
hx_model_client_status_t status,
const uint8_t *p_data,
uint16_t length,
uint16_t src)
{
uint8_t* message = (uint8_t*)malloc(sizeof(uint8_t)*length);
memcpy(message,p_data,length);
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Received Data: %s, Data Length: %d, Source Address: %x, Status: %d\n",
message, length, src, status);
free(message);
}
static void hx_model_client_status_cb(const hx_model_client_t *p_self,
hx_model_client_status_t status,
const uint8_t *p_data,
uint16_t length,
uint16_t src)
{
uint8_t* message = (uint8_t*)malloc(sizeof(uint8_t)*length);
memcpy(message,p_data,length);
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Response from HX SERVER MODEL: %s, length is %d, status is %d, Source Address: %4x\n",
message, length, status, src);
free(message);
}
hx_client_model_callbacks_t client_callbacks = {
.status_handler = hx_model_client_status_cb,\
.rx_handler = hx_model_client_rx_cb
};
新添两个Client模型至MESH协议栈
xxxxxxxxxx
for(uint8_t i=0;i<CLIENT_MODEL_INSTANCE_COUNT;i++)
{
m_hx_model_client[i].p_callbacks = &client_callbacks;
uint32_t status = hx_model_client_init(&m_hx_model_client[i],i+1);
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "HX Model Initializing and adding client model[%d] is %d\n",i, status);
}
其中,CLIENT_MODEL_INSTANCE_COUNT宏为2,因为我们需要将两个完全相同的模型添加至不同的元素下;如果返回的状态值为0即说明模型增加成功,否则失败;至于,操作码所映射的回调函数也成功映射了,这就意味着当接收到对应的操作码时,其映射的回调函数就会被协议栈向应用层上抛;最终的效果就同上述描绘的图;
流程跟上述的Client-Server模型是完全一样的,只不过其不需要占用过多的模型,只需要两个控制端模型即可,而上述的Client-Server模型要实现相同的功能,则需要4个模型;该模型新添至Mesh协议栈的操作如下所示:
xxxxxxxxxx
for(uint8_t i = 0; i <CONTROL_MODEL_INSTANCE_COUNT; i++)
{
m_hx_model_control[i].server_model.p_callbacks = &server_callbacks;
m_hx_model_control[i].client_model.p_callbacks = &client_callbacks;
uint32_t status = hx_model_control_init(&m_hx_model_control[i],i);
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "HX Model Initializing and adding control model[%d] is %d\n",i,status);
}
其中,CONTROL_MODEL_INSTANCE_COUNT表示我要新增多少个控制模型,并将其附于不同的元素下;这里还有一点需要注意的是:每个模型都要填充应用层的回调处理函数,如:
xxxxxxxxxx
m_hx_model_control[i].server_model.p_callbacks = &server_callbacks;
m_hx_model_control[i].client_model.p_callbacks = &client_callbacks;
最终成功添加的效果如下图所示:
更完整的源码请参考《创建红旭Model》文件夹
小编这里以控制模型为例,演示一个控制模型同时支持服务和客户端模型的事例:
手机nRF Mesh App向入网之后的节点发送HX_MODEL_CLIENT_OPCODE_TX命令
由上述的动图可知,对端设备已经收到nRF Mesh App发送过去的数据,并返回了 “OK!” 给手机
入网之后的节点,通过按键1向目标地址为0xC000发送HX_MODEL_CLIENT_OPCODE_TX_UNRELIABLE命令
从上图我们可以看到,订阅了0xC000的节点接收到了 “hx123456789” 的自定义数据,这也就是说HX_MODEL_CLIENT_OPCODE_TX_UNRELIABLE命令被成功发送出去并被订阅0xC000的节点所接收
从上述的实验结果,我们可以看出控制模型已经成功地被添加并正常运行;注意:以上的操作均是在同一个模型下执行的。