1. 准备
1.1 SDK文件
nRF5_SDK_15.2.0_9412b96.zip 和 (nrf5SDKforMeshv310src.zip,官方下载链接
1.2 集成开发环境工具
SEGGER Embedded Studio 4.1.2,下载链接
1.3 工具
J-link和天猫精灵
2. 平台设置
登录并注册AliGenie 的IOT接入平台,网址.点击控制台, 选择添加新产品,设置产品信息,随便选一个品牌吧。
3. 协议对接
这个是天猫精灵官方提供的蓝牙mesh软件基础规范,网址
我基于nrf5SDKforMeshv310src\examples\light_switch\server例子,修改了几个地方,一个是三元组信息,设备地址,广播包,Confirmation匹配,和绑定Appkey。
建议将prov_provisionee.c将PROV_DEBUG_MODE置1,不仅可以让我们理解配网的一个完成过程(包括公钥交换,Confirmation确认等),也可以方便查找配网过程中的问题
3.1 三元组信息
三元组信息包括ProductID,DeviceSecret,MAC地址。点击,进入控制台->点击相对应的产品->3.设备调试->点击查看,就可以得到三元组信息了,随便选择一组三元组信息。根据三元组信息计算AuthValue:
uint8_t mIAuthValue[32] = {0};
void sha256test()
{
sha256_context_t sha256_ctx;
uint32_t err_code = NRF_SUCCESS;
err_code = sha256_init(&sha256_ctx);
if (err_code != NRF_SUCCESS)
{
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Error in sha256_init (%d)", err_code);
}
//00002b57:ProductId的16进制
//f8a76392b60f:MAC地址
//8ad28fbcdfcad149d1f89d8a3a01fab7:DeviceSecret
sha256_update(&sha256_ctx, "00002b57,f8a76392b60f,8ad28fbcdfcad149d1f89d8a3a01fab7", strlen("00002b57,f8a76392b60f,8ad28fbcdfcad149d1f89d8a3a01fab7"));
sha256_final(&sha256_ctx, mIAuthValue,false);
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "sha256test\n");
}
3.2 修改设备蓝牙地址
设备蓝牙地址也就是上图中的MAC地址,注意地址数据的大小端模式,可以使用蓝牙软件(比如nRF connect)看看地址是否准确。
main.c
static void initialize(void)
{
__LOG_INIT(LOG_SRC_APP | LOG_SRC_ACCESS | LOG_SRC_BEARER | LOG_SRC_API |LOG_SRC_PROV, LOG_LEVEL_INFO, LOG_CALLBACK_DEFAULT);
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "----- BLE Mesh Light Switch Server Demo -----\n");
ERROR_CHECK(app_timer_init());
hal_leds_init();
#if BUTTON_BOARD
ERROR_CHECK(hal_buttons_init(button_event_handler));
#endif
ble_stack_init();
ble_gap_addr_t my_addr;
uint32_t error_code;
static ble_gap_addr_t m_central_addr;
uint8_t addr[6] = {0x0f,0xb6,0x92,0x63,0xa7,0xf8};//f8a76392b60f
m_central_addr.addr_type = BLE_GAP_ADDR_TYPE_RANDOM_STATIC;
m_central_addr.addr[0] = (uint8_t)addr[0];
m_central_addr.addr[1] = (uint8_t)(addr[1]);
m_central_addr.addr[2] = (uint8_t)(addr[2]);
m_central_addr.addr[3] = (uint8_t)(addr[3]);
m_central_addr.addr[4] = (uint8_t)addr[4];
m_central_addr.addr[5] = (uint8_t)(addr[5]); // 2MSB must be set 11
sd_ble_gap_addr_set(&m_central_addr);
#if MESH_FEATURE_GATT_ENABLED
gap_params_init();
conn_params_init();
#endif
mesh_init();
}
3.3 广播包
main.c
static void mesh_init(void)
{
const uint8_t tianmao_uuid[16] ={
0xA8,0x01,//公司ID
0x71,//蓝牙广播版本号
0x57,0x2b,0x00,0x00,//ProjectID
0x0f,0xb6,0x92,0x63,0xa7,0xf8,//Mac地址
0x02,//UUID版本号
0x00,0x00};
mesh_stack_init_params_t init_params =
{
.core.irq_priority = NRF_MESH_IRQ_PRIORITY_LOWEST,
.core.lfclksrc = DEV_BOARD_LF_CLK_CFG,
.core.p_uuid = &tianmao_uuid[0],
.models.models_init_cb = models_init_cb,
.models.config_server_cb = config_server_evt_cb
};
ERROR_CHECK(mesh_stack_init(&init_params, &m_device_provisioned));
}
3.4 Confirmation匹配
Confirmation在天猫精灵和Mesh设备进行双向认证,所以要修改两个地方,如下:
prov_utils.c:
extern uint8_t mIAuthValue[32];
void prov_utils_authentication_values_derive(const nrf_mesh_prov_ctx_t * p_ctx,
uint8_t * p_confirmation_salt,
uint8_t * p_confirmation,
uint8_t * p_random)
{
create_confirmation_salt(p_ctx, p_confirmation_salt);
uint8_t confirmation_key[NRF_MESH_KEY_SIZE];
/* ConfirmationKey = k1(ECDHSecret, ConfirmationSalt, "prck") */
enc_k1(p_ctx->shared_secret,
NRF_MESH_ECDH_SHARED_SECRET_SIZE,
p_confirmation_salt,
CONFIRMATION_KEY_INFO, CONFIRMATION_KEY_INFO_LENGTH,
confirmation_key);
rand_hw_rng_get(p_random, PROV_RANDOM_LEN);
uint8_t random_and_auth[PROV_RANDOM_LEN + PROV_AUTH_LEN];
memcpy(&random_and_auth[0], p_random, PROV_RANDOM_LEN);
//memcpy(&random_and_auth[PROV_RANDOM_LEN], p_ctx->auth_value, PROV_AUTH_LEN);
memcpy(&random_and_auth[PROV_RANDOM_LEN], mIAuthValue, PROV_AUTH_LEN);
/* Confirmation value = AES-CMAC(ConfirmationKey, LocalRandom || AuthValue) */
enc_aes_cmac(confirmation_key,
random_and_auth,
PROV_RANDOM_LEN + PROV_AUTH_LEN,
p_confirmation);
}
bool prov_utils_confirmation_check(const nrf_mesh_prov_ctx_t * p_ctx)
{
uint8_t confirmation_key[NRF_MESH_KEY_SIZE];
/* ConfirmationKey = k1(ECDHSecret, ConfirmationSalt, "prck") */
enc_k1(p_ctx->shared_secret,
NRF_MESH_ECDH_SHARED_SECRET_SIZE,
p_ctx->confirmation_salt,
CONFIRMATION_KEY_INFO, CONFIRMATION_KEY_INFO_LENGTH,
confirmation_key);
uint8_t random_and_auth[PROV_RANDOM_LEN + PROV_AUTH_LEN];
memcpy(&random_and_auth[0], p_ctx->peer_random, PROV_RANDOM_LEN);
//memcpy(&random_and_auth[PROV_RANDOM_LEN], p_ctx->auth_value, PROV_AUTH_LEN);
memcpy(&random_and_auth[PROV_RANDOM_LEN], mIAuthValue, PROV_AUTH_LEN);
/* Confirmation value = AES-CMAC(ConfirmationKey, LocalRandom || AuthValue) */
uint8_t confirmation[PROV_CONFIRMATION_LEN];
enc_aes_cmac(confirmation_key,
random_and_auth,
PROV_RANDOM_LEN + PROV_AUTH_LEN,
confirmation);
return memcmp(confirmation, p_ctx->peer_confirmation, sizeof(confirmation)) == 0;
}
3.5 Appkey和地址绑定
配网完成之后,天猫精灵会直接下发三个Appkey(我测试三个Appkey都是一样)。配网完成之后我直接用这个Appkey绑定到health Model和Generic OnOff Server Model。
void mI_bind_appkey(){
uint32_t status =0;
mesh_key_index_t appkey_index =0;
access_model_handle_t model_handle =2;
dsm_handle_t appkey_handle = dsm_appkey_index_to_appkey_handle(appkey_index);
status = access_model_application_bind(model_handle, appkey_handle);
if (status == NRF_SUCCESS)
{
__LOG(LOG_SRC_PROV, LOG_LEVEL_INFO, "bind appkey is ok\nn");
}
}
给Generic OnOff Server Model设置组地址
void Add_subscription_address(uint16_t address){
uint32_t status =0;
access_model_handle_t model_handle =2;
/* Check that the subscription address is valid before continuing: */
nrf_mesh_address_type_t address_type = nrf_mesh_address_type_get(address);
if ((address_type != NRF_MESH_ADDRESS_TYPE_GROUP) || (address == NRF_MESH_ALL_NODES_ADDR))
{
return;
}
/* Add the address to the DSM as a subscription address: */
dsm_handle_t subscription_address_handle;
status = dsm_address_subscription_add(address, &subscription_address_handle);
if (status != NRF_SUCCESS){
return;
}
/* Add the subscription to the model: */
status = access_model_subscription_add(model_handle, subscription_address_handle);
__LOG(LOG_SRC_PROV, LOG_LEVEL_INFO, "Provisionee: subscription_address_handle:%d,\n",subscription_address_handle);
if (status != NRF_SUCCESS)
{
NRF_MESH_ASSERT(dsm_address_subscription_remove(subscription_address_handle) == NRF_SUCCESS);
}
else
{
__LOG(LOG_SRC_PROV, LOG_LEVEL_INFO, "main: subscription finish\n");
access_flash_config_store();
}
}
配网完成的回调:
static void provisioning_complete_cb(void)
{
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Successfully provisioned\n");
#if MESH_FEATURE_GATT_ENABLED
/* Restores the application parameters after switching from the Provisioning
* service to the Proxy */
gap_params_init();
conn_params_init();
#endif
dsm_local_unicast_address_t node_address;
dsm_local_unicast_addresses_get(&node_address);
__LOG(LOG_SRC_APP, LOG_LEVEL_INFO, "Node Address: 0x%04x \n", node_address.address_start);
mI_bind_appkey();
Add_subscription_address(0xc001);
hal_led_blink_stop();
hal_led_mask_set(LEDS_MASK, LED_MASK_STATE_OFF);
hal_led_blink_ms(LEDS_MASK, LED_BLINK_INTERVAL_MS, LED_BLINK_CNT_PROV);
app_onoff_status_publish(&m_onoff_server_0);
}
4. 参考资料