一 创建新model的步骤
本指南介绍了如何创建新model的基础知识。您可以实现自己的自定义模型,该模型将使您的设备能够提供自定义状态和行为,而这些状态和行为未包含在已定义的标准模型中。
Bluetooth mesh model APIs,可以参考:mesh models api
自定义model分为以下几个步骤:
- Step 1: 定义各个操作码处理函数
- Step 2: 将模型分配并绑定到元素
- Step 3: 设置发布和订阅
- Step 3.1: 设置发布
- Step 3.2: 设置订阅
1 定义各个操作码处理函数
通过创建一个access_opcode_handler_t数组,为传入消息定义一个处理程序表。此数组中的每个元素都用作一个查找表条目,用于处理发送给模型的传入消息的操作码。
typedef uint16_t access_model_handle_t;
typedef void (*access_opcode_handler_cb_t)(access_model_handle_t handle,
const access_message_rx_t * p_message,
void * p_args);
typedef struct
{
uint16_t opcode;
uint16_t company_id;
} access_opcode_t;
typedef struct
{
access_opcode_t opcode;
access_opcode_handler_cb_t handler;
} access_opcode_handler_t;
2 将模型分配并绑定到元素
所有模型都必须绑定到一个元素。 元素代表设备中的可寻址单元,例如灯具中的灯泡。 因此,配网者(provisioner)为每个元素分配了一个单独的单播地址(unicast address)。
使用access_model_add()API将模型分配、初始化,并将其绑定到给定元素索引处。通过分配给输出参数p_model_handle的句柄值来标识模型实例。 调用访问层API函数时,请使用此句柄。
一个模型可以扩展一个或多个其他模型.这些父模型实例可以绑定到不同的元素,从而使完整的模型跨越多个元素。这些模型称为扩展模型。有关更多信息和示例,请参阅Mesh Model Specification (MshMDLv1.0.1) 的Bluetooth Mesh模型规范。
3 设置发布和订阅
mesh应用程序是使用客户端-服务器体系结构指定的,其中客户端和服务器模型使用发布和订阅机制相互通信。因此,在应用程序中使用模型时,必须设置发布和订阅。
3.1 设置发布
发布,允许从模型发送消息。
每个模型都有一个发布地址。消息的发布可以是定期的或一次性的,发布的消息可以发送到单播,组或虚拟地址。
发布-相关(publication-related)状态,通常由由配网者(provisioner)通过配置模型来控制。发布非常有用,例如允许传感器节点定期报告读取的数据。可以使用access_model_publish() API函数来发布消息,该函数将根据模型的发布设置(时间间隔,目标)发布消息。
客户端模型也使用发布,将消息发送到服务器模型。然而,在许多情况下,应用程序希望控制从客户端模型发布的消息的目的地,而不是依赖于外部配网者(provisioner)(在许多情况下,包含客户端的应用程序是provisioner).为此,提供了API函数access_model_publish_address_set().
3.2 设置订阅
订阅使模型可以侦听来自特定地址的传入消息。 例如,可以使用此功能来侦听从传感器节点发布的定期消息。
要允许模型订阅地址,您必须首先使用access_model_subscription_list_alloc() API 函数分配订阅列表。
在使用客户端模型中,你主动发送了一条消息,并且希望对方能够应答,不需要订阅这类应答消息。订阅仅用在订阅节点(node)主动发送的消息。
二 Simple OnOff model
简单的OnOff模型。
蓝牙mesh模型规范指定了通用OnOff模型,该模型将在实际应用中与蓝牙mesh一起使用。此特定于供应商(vendor-specific)的模型是通用OnOff模型的简化版本。 这是创建新模型的介绍性示例,但是您也可以在应用程序中使用它。
请参阅以下各节,以获取有关如何实施特定于供应商的简单OnOff模型的信息,该模型可以打开或关闭某些东西,例如灯泡,加热器或洗衣机。
为简便起见,本页上未讨论一些重要功能,例如错误处理。 编写应用程序时,请检查所有API函数返回的错误代码,以避免应用程序中的错误。
实现过程
- Properties and features
- Supported opcodes
- Identifiers
- Implementing the model
- Implementing the server model
- Implementing the client model
您可以在models / vendor / simple_on_off目录中检查完整的模型实现及其布局。
如果要查看如何将此模型集成到完整的应用程序中,请查看灯光开关示例和examples/light_switch目录。
1 Properties and features
特性和特征。
蓝牙mesh应用程序是使用客户端-服务器体系结构指定的,其中客户端和服务器模型使用发布和订阅机制相互通信。因此,该模型的预期功能将使用两个部分来实现:
- 服务器模型,用于维护OnOff状态;
- 客户端模型,用于操控服务器上的OnOff状态。
当服务器模型从客户端模型接收到GET或(可靠)SET消息时,它将发送OnOff状态的当前值作为响应。这样可以使客户端了解服务器状态的最新信息。
有关设置发布和订阅的更多详细信息,请参阅第一章。
1.1 Supported opcodes
支持的操作码。
下表显示了此模型支持的操作码。
Name | Definition | Opcode | Description | Parameter | Parameter size |
---|---|---|---|---|---|
SET | SIMPLE_ON_OFF_OPCODE_SET | 0xc1 | Sets the current on/off state | New state | 1 byte |
GET | SIMPLE_ON_OFF_OPCODE_GET | 0xc2 | Gets the current on/off state | N/A | No parameter |
SET UNRELIABLE | SIMPLE_ON_OFF_OPCODE_SET_UNRELIABLE | 0xc3 | Sets the current on/off state | New state | 1 byte |
Status | SIMPLE_ON_OFF_OPCODE_STATUS | 0xc4 | Contains the current state | Current state | 1 byte |
对于特定于供应商的型号,广播发送的操作码是三个字节。完整的操作码是特定于供应商的操作码和公司标识符的组合。 有关更多信息,请参见access_opcode_t文档。
1.2 Identifiers
身份标识。
Description | Value |
---|---|
Company identifier | 0x0059 |
Server identifier | 0x0000 |
Client identifier | 0x0001 |
该表中使用的公司标识符是Nordic Semiconductor分配的Bluetooth公司ID。 在实际的应用程序中,请使用您自己公司分配的ID。
2 Implementing the model
实现模型。
如前所述,模型包含两个实体,它们共同实现了完整的行为:
- 服务模型,它通常具有状态并公开消息,以便客户模型控制这些状态的值并触发行为。
- 它发送消息以控制和观察服务器模型上的状态。
实现服务器和客户端模型,以使Simple OnOff模型能够正常工作。
2.1 Implementing the server model
实现服务器模型。
以下消息序列图说明了简单OnOff服务器的行为。
当OnOff服务器收到SET和GET消息时:
- 它调用应用程序提供的回调函数。
- 它通过回调函数的参数共享或请求数据。
以下步骤说明了如何实现一个服务模型
-
定义一个模型上下文结构,其中包含指向回调函数的指针。该上下文结构将传递给所有消息处理程序。
- 以下代码段显示了服务器模型(simple_on_off_server_t)和关联的回调所需的上下文结构:
typedef struct __simple_on_off_server simple_on_off_server_t; typedef bool (*simple_on_off_get_cb_t)(const simple_on_off_server_t * p_self); typedef bool (*simple_on_off_set_cb_t)(const simple_on_off_server_t * p_self, bool on_off); struct __simple_on_off_server { access_model_handle_t model_handle; simple_on_off_get_cb_t get_cb; simple_on_off_set_cb_t set_cb; };
-
定义操作码并创建必要的操作码处理程序函数,以处理传入服务器模型的消息。
- 服务器中需要三个操作码处理程序来处理SIMPLE_ON_OFF_OPCODE_GET,SIMPLE_ON_OFF_OPCODE_SET和SIMPLE_ON_OFF_OPCODE_SET_UNRELIABLE消息。这些操作码处理程序中的每一个都从上下文结构中调用相应的用户回调函数。该上下文结构通过p_args参数传递给操作码处理程序。
- 所有模型的所有操作码处理程序都必须使用相同的函数原型:
typedef void (*access_opcode_handler_cb_t)(access_model_handle_t handle, const access_message_rx_t * p_message, void * p_args);
- 以下代码片段显示了为Simple OnOff服务器模型定义的操作码处理程序:
static void handle_set_cb(access_model_handle_t handle, const access_message_rx_t * p_message, void * p_args) { simple_on_off_server_t * p_server = p_args; NRF_MESH_ASSERT(p_server->set_cb != NULL); bool value = (((simple_on_off_msg_set_t*) p_message->p_data)->on_off) > 0; value = p_server->set_cb(p_server, value); reply_status(p_server, p_message, value); (void) simple_on_off_server_status_publish(p_server, value); /* We don't care about status */ } static void handle_get_cb(access_model_handle_t handle, const access_message_rx_t * p_message, void * p_args) { simple_on_off_server_t * p_server = p_args; NRF_MESH_ASSERT(p_server->get_cb != NULL); reply_status(p_server, p_message, p_server->get_cb(p_server)); } static void handle_set_unreliable_cb(access_model_handle_t handle, const access_message_rx_t * p_message, void * p_args) { simple_on_off_server_t * p_server = p_args; NRF_MESH_ASSERT(p_server->set_cb != NULL); bool value = (((simple_on_off_msg_set_unreliable_t*) p_message->p_data)->on_off) > 0; value = p_server->set_cb(p_server, value); (void) simple_on_off_server_status_publish(p_server, value); }
-
实现reply_status()函数
- 如Bluetooth Mesh Profile Specification (MshPRFv1.0.1)的3.7.5.2节中所定义,每个接收元素都通过响应该消息来确认收到的已确认消息。该响应通常是状态消息。状态消息通常包含当前的状态值,该值由SET消息设置。由于这个原因,模型使用set_cb() 回调从用户应用程序中获取当的OnOff状态值,并使用Reply_status()函数发送该值。如果配网者(provisioner)设置了服务器的发布地址,则服务器模型还将使用simple_on_off_server_status_publish()函数来响应收到的任何消息来发布其状态。
- reply_status()函数使用access_model_reply() API在SIMPLE_ON_OFF_OPCODE_STATUS消息中发送当前状态的值作为对客户端的答复。access_model_reply()需要某些参数才能正确发送消息,这就是为什么将其包装在Reply_status()中的原因。
- 以下代码段显示了reply_status()函数的实现:
static void reply_status(const simple_on_off_server_t * p_server, const access_message_rx_t * p_message, bool present_on_off) { simple_on_off_msg_status_t status; status.present_on_off = present_on_off ? 1 : 0; access_message_tx_t reply; reply.opcode.opcode = SIMPLE_ON_OFF_OPCODE_STATUS; reply.opcode.company_id = ACCESS_COMPANY_ID_NORDIC; reply.p_buffer = (const uint8_t *) &status; reply.length = sizeof(status); 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); }
-
实现simple_on_off_server_status_publish()函数
- simple_on_off_server_status_publish()函数与reply_status()函数非常相似,不同之处在于它使用access_model_publish()API发布响应消息。如果配网者(provisioner)未配置客户端模型的发布地址,则access_model_publish()将不会发布给定消息。
-
确保将指定的操作码和公司ID链接到指定的操作码处理程序查找表中的相应处理程序功能。
- 向访问层(access layer)注册模型时,此查找表作为输入参数提供.该表中的每个条目都是access_opcode_handler_t类型,由操作码,供应商ID和操作码处理程序功能指针组成。
- 对于服务器模型,查找表定义如下:
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} };
-
将模型放在初始化函数中。
- 初始化函数必须分配模型并将其添加到访问层:
uint32_t simple_on_off_server_init(simple_on_off_server_t * p_server, uint16_t element_index) { if (p_server == NULL || p_server->get_cb == NULL || p_server->set_cb == NULL) { return NRF_ERROR_NULL; } access_model_add_params_t init_params; init_params.element_index = element_index; init_params.model_id.model_id = SIMPLE_ON_OFF_SERVER_MODEL_ID; init_params.model_id.company_id = SIMPLE_ON_OFF_COMPANY_ID; 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_server; init_params.publish_timeout_cb = handle_publish_timeout; return access_model_add(&init_params, &p_server->model_handle); }
现在,您具有简单的OnOff服务器模型的基本框架,可以对其进行扩展或调整以生成更复杂的服务器模型。 有关此模型的完整代码,请参见models/vendor/simple_on_off /。
2.2 Implementing the client model
实现客户端模型。
客户端模型用于与相应的服务器模型进行交互。它发送SET和GET消息并处理传入的状态回复。客户端模型使用发布机制发送消息。它使用分配的发布地址作为外发消息的目的地。
就像在服务器实现中一样,客户端需要上下文结构来保留有关回调及其模型句柄的信息。另外,布尔变量用于跟踪事务当前是否处于活动状态,并防止运行多个同时事务。
在蓝牙mesh网络中,消息可能无法按顺序传递,也可能根本无法传递。因此,客户端一次只能与其对应的服务器执行一次传输。
客户端模型使用回调函数将有关服务器状态的信息提供给用户应用程序。如果服务器在给定的时间内没有回复,它将使用错误代码SIMPLE_ON_OFF_STATUS_ERROR_NO_REPLY通知用户应用程序。
以下代码段显示了此模型所需的状态码(simple_on_off_status_t),上下文结构(simple_on_off_client_t)和相关的回调:
typedef enum
{
SIMPLE_ON_OFF_STATUS_ON,
SIMPLE_ON_OFF_STATUS_OFF,
SIMPLE_ON_OFF_STATUS_ERROR_NO_REPLY,
SIMPLE_ON_OFF_STATUS_CANCELLED
} simple_on_off_status_t;
typedef struct __simple_on_off_client simple_on_off_client_t;
typedef void (*simple_on_off_status_cb_t)(const simple_on_off_client_t * p_self, simple_on_off_status_t status, uint16_t src);
typedef void (*simple_on_off_timeout_cb_t)(access_model_handle_t handle, void * p_self);
struct __simple_on_off_client
{
access_model_handle_t model_handle;
simple_on_off_status_cb_t status_cb;
simple_on_off_timeout_cb_t timeout_cb;
struct
{
bool reliable_transfer_active;
simple_on_off_msg_set_t data;
} state;
};
实现客户端模型
-
通过选择以下选项之一来定义客户端模型将发送的消息类型:
- 不可靠(没有应答)的消息;
- 使用access_model_publish()API发送不可靠的消息。
- 可靠(有应答)的消息。
- 使用access_model_reliable_publish() API发送可靠的消息。该API通过重新传输消息来保证消息的传递,直到从目标节点接收到答复或传输超时为止。
- 当传输完成时,无论有无响应,都会调用状态回调函数来通知用户应用程序。
- 以下代码段显示了客户端模型的trusted_status_cb()回调和send_reliable_message()函数:
static void reliable_status_cb(access_model_handle_t model_handle, void * p_args, access_reliable_status_t status) { simple_on_off_client_t * p_client = p_args; NRF_MESH_ASSERT(p_client->status_cb != NULL); p_client->state.reliable_transfer_active = false; switch (status) { case ACCESS_RELIABLE_TRANSFER_SUCCESS: /* Ignore */ break; case ACCESS_RELIABLE_TRANSFER_TIMEOUT: p_client->status_cb(p_client, SIMPLE_ON_OFF_STATUS_ERROR_NO_REPLY, NRF_MESH_ADDR_UNASSIGNED); break; case ACCESS_RELIABLE_TRANSFER_CANCELLED: p_client->status_cb(p_client, SIMPLE_ON_OFF_STATUS_CANCELLED, NRF_MESH_ADDR_UNASSIGNED); break; default: /* Should not be possible. */ NRF_MESH_ASSERT(false); break; } } static uint32_t send_reliable_message(const simple_on_off_client_t * p_client, simple_on_off_opcode_t opcode, const uint8_t * p_data, uint16_t length) { access_reliable_t reliable; reliable.model_handle = p_client->model_handle; reliable.message.p_buffer = p_data; reliable.message.length = length; reliable.message.opcode.opcode = opcode; reliable.message.opcode.company_id = SIMPLE_ON_OFF_COMPANY_ID; reliable.message.force_segmented = false; reliable.message.transmic_size = NRF_MESH_TRANSMIC_SIZE_DEFAULT; reliable.message.access_token = nrf_mesh_unique_token_get(); reliable.reply_opcode.opcode = SIMPLE_ON_OFF_OPCODE_STATUS; reliable.reply_opcode.company_id = SIMPLE_ON_OFF_COMPANY_ID; reliable.timeout = SIMPLE_ON_OFF_CLIENT_ACKED_TRANSACTION_TIMEOUT; reliable.status_cb = reliable_status_cb; return access_model_reliable_publish(&reliable); }
- 使用access_model_reliable_publish() API发送可靠的消息。该API通过重新传输消息来保证消息的传递,直到从目标节点接收到答复或传输超时为止。
- 不可靠(没有应答)的消息;
-
为用户应用程序创建API函数,以发送GET和SET消息。以下代码段定义了这些功能:
uint32_t simple_on_off_client_set(simple_on_off_client_t * p_client, bool on_off)
{
if (p_client == NULL || p_client->status_cb == NULL)
{
return NRF_ERROR_NULL;
}
else if (p_client->state.reliable_transfer_active)
{
return NRF_ERROR_INVALID_STATE;
}
p_client->state.data.on_off = on_off ? 1 : 0;
p_client->state.data.tid = m_tid++;
uint32_t status = send_reliable_message(p_client,
SIMPLE_ON_OFF_OPCODE_SET,
(const uint8_t *)&p_client->state.data,
sizeof(simple_on_off_msg_set_t));
if (status == NRF_SUCCESS)
{
p_client->state.reliable_transfer_active = true;
}
return status;
}
uint32_t simple_on_off_client_set_unreliable(simple_on_off_client_t * p_client, bool on_off, uint8_t repeats)
{
simple_on_off_msg_set_unreliable_t set_unreliable;
set_unreliable.on_off = on_off ? 1 : 0;
set_unreliable.tid = m_tid++;
access_message_tx_t message;
message.opcode.opcode = SIMPLE_ON_OFF_OPCODE_SET_UNRELIABLE;
message.opcode.company_id = SIMPLE_ON_OFF_COMPANY_ID;
message.p_buffer = (const uint8_t*) &set_unreliable;
message.length = sizeof(set_unreliable);
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;
}
uint32_t simple_on_off_client_get(simple_on_off_client_t * p_client)
{
if (p_client == NULL || p_client->status_cb == NULL)
{
return NRF_ERROR_NULL;
}
else if (p_client->state.reliable_transfer_active)
{
return NRF_ERROR_INVALID_STATE;
}
uint32_t status = send_reliable_message(p_client,
SIMPLE_ON_OFF_OPCODE_GET,
NULL,
0);
if (status == NRF_SUCCESS)
{
p_client->state.reliable_transfer_active = true;
}
return status;
}
-
为SIMPLE_ON_OFF_OPCODE_STATUS操作码添加操作码处理程序以处理回复消息。
- 所有传入消息,即使它们是对本节点发送的消息的答复,也都需要处理操作码处理程序。
- 以下代码段显示了操作码处理程序的实现,并为客户端模型定义了操作码处理程序查找表:
static void handle_status_cb(access_model_handle_t handle, const access_message_rx_t * p_message, void * p_args) { simple_on_off_client_t * p_client = p_args; NRF_MESH_ASSERT(p_client->status_cb != NULL); simple_on_off_msg_status_t * p_status = (simple_on_off_msg_status_t *) p_message->p_data; simple_on_off_status_t on_off_status = (p_status->present_on_off ? SIMPLE_ON_OFF_STATUS_ON : SIMPLE_ON_OFF_STATUS_OFF); p_client->status_cb(p_client, on_off_status, p_message->meta_data.src.value); } static const access_opcode_handler_t m_opcode_handlers[] = { {{SIMPLE_ON_OFF_OPCODE_STATUS, SIMPLE_ON_OFF_COMPANY_ID}, handle_status_cb} };
-
提供回调以支持定期发布。
- 为了支持发布功能,模型必须提供publish_timeout_cb。 如果配网者(provisioner)配置了定期发布,则该回调将由发布机制调用。
- 以下代码段显示了定期发布回调的实现。 在此实现中,客户端模型的定期发布超时回调将调用用户指定的回调。
static void handle_publish_timeout(access_model_handle_t handle, void * p_args) { simple_on_off_client_t * p_client = p_args; if (p_client->timeout_cb != NULL) { p_client->timeout_cb(handle, p_args); } }
-
初始化客户端模型。
- 初始化与服务器模型的初始化相同
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); }
客户端模型现已实现。 现在,您可以使用它通过与服务器节点进行通信来打开或关闭某些内容。