/* Bluetooth Mesh */ /* * Copyright (c) 2017 Intel Corporation * Additional Copyright (c) 2018 Espressif Systems (Shanghai) PTE LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLE_MESH_DEBUG_MODEL) #include "btc_ble_mesh_health_model.h" #include "access.h" #include "foundation.h" #include "mesh_common.h" #include "health_srv.h" #define HEALTH_TEST_STANDARD 0x00 #define HEALTH_NO_FAULT 0x00 /* Health Server context of the primary element */ struct bt_mesh_health_srv *health_srv; /** * When an Element receives a Health Fault Get, or a Health Fault Test, or * a Health Fault Test Unacknowledged, or a Health Fault Clear, or a Health * Fault Clear Unacknowledged message that is not successfully processed * (i.e. the Company ID field that does not identify any Health Fault state * present in the node), it shall ignore the message. * The Health Fault state is identified by Company ID and may be present in * the node for more than one Company ID. */ static u8_t health_get_curr_fault_count(struct bt_mesh_model *model) { struct bt_mesh_health_srv *srv = model->user_data; u8_t count = 0U; size_t i = 0U; for (i = 0U; i < ARRAY_SIZE(srv->test.curr_faults); i++) { if (srv->test.curr_faults[i] != HEALTH_NO_FAULT) { count++; } } return count; } static void health_get_fault_value(struct bt_mesh_model *model, struct net_buf_simple *msg, bool current) { struct bt_mesh_health_srv *srv = model->user_data; size_t array_size = 0U; size_t i = 0U; array_size = current ? ARRAY_SIZE(srv->test.curr_faults) : ARRAY_SIZE(srv->test.reg_faults); for (i = 0U; i < array_size; i++) { if (net_buf_simple_tailroom(msg) == 0) { return; } u8_t fault = current ? srv->test.curr_faults[i] : srv->test.reg_faults[i]; if (fault != HEALTH_NO_FAULT) { net_buf_simple_add_u8(msg, fault); } } } static bool health_is_test_id_exist(struct bt_mesh_model *model, u8_t test_id) { struct bt_mesh_health_srv *srv = model->user_data; int i; for (i = 0; i < srv->test.id_count; i++) { if (srv->test.test_ids[i] == test_id) { return true; } } return false; } static int health_send_fault_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx) { struct bt_mesh_health_srv *srv = model->user_data; struct net_buf_simple *msg = NULL; int err = 0; msg = bt_mesh_alloc_buf(4 + ARRAY_SIZE(srv->test.reg_faults) + 4); if (!msg) { BT_ERR("%s, Failed to allocate memory", __func__); return -ENOMEM; } bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_STATUS); net_buf_simple_add_u8(msg, srv->test.prev_test_id); net_buf_simple_add_le16(msg, srv->test.company_id); if (ctx->recv_op != OP_HEALTH_FAULT_CLEAR) { /** * For Health Fault Clear, the FaultArray field in Health Fault Status * shall be empty. */ health_get_fault_value(model, msg, false); } err = bt_mesh_model_send(model, ctx, msg, NULL, NULL); if (err) { BT_ERR("%s, Failed to send Health Fault Status response", __func__); } bt_mesh_free_buf(msg); return err; } static void health_fault_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { struct bt_mesh_health_srv *srv = model->user_data; u16_t company_id = 0U; if (!srv) { BT_ERR("%s, No Health Server context provided", __func__); return; } company_id = net_buf_simple_pull_le16(buf); if (company_id != srv->test.company_id) { BT_ERR("%s, Unknown Company ID 0x%04x", __func__, company_id); return; } BT_DBG("company_id 0x%04x", company_id); health_send_fault_status(model, ctx); } static void health_fault_clear(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { struct bt_mesh_health_srv *srv = model->user_data; u16_t company_id = 0U; if (!srv) { BT_ERR("%s, No Health Server context provided", __func__); return; } company_id = net_buf_simple_pull_le16(buf); if (company_id != srv->test.company_id) { BT_ERR("%s, Unknown Company ID 0x%04x", __func__, company_id); return; } BT_DBG("company_id 0x%04x", company_id); memset(srv->test.reg_faults, HEALTH_NO_FAULT, ARRAY_SIZE(srv->test.reg_faults)); if (srv->cb.fault_clear) { srv->cb.fault_clear(model, company_id); } if (ctx->recv_op == OP_HEALTH_FAULT_CLEAR) { health_send_fault_status(model, ctx); } } static void health_fault_test(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { struct bt_mesh_health_srv *srv = model->user_data; u16_t company_id = 0U; u8_t test_id = 0U; BT_DBG("%s", __func__); if (!srv) { BT_ERR("%s, No Health Server context provided", __func__); return; } test_id = net_buf_simple_pull_u8(buf); if (health_is_test_id_exist(model, test_id) == false) { BT_ERR("%s, Unknown Test ID 0x%02x", __func__, test_id); return; } company_id = net_buf_simple_pull_le16(buf); if (company_id != srv->test.company_id) { BT_ERR("%s, Unknown Company ID 0x%04x", __func__, company_id); return; } BT_DBG("test 0x%02x company 0x%04x", test_id, company_id); srv->test.prev_test_id = test_id; if (srv->cb.fault_test) { srv->cb.fault_test(model, test_id, company_id); } if (ctx->recv_op == OP_HEALTH_FAULT_TEST) { health_send_fault_status(model, ctx); } } static void send_attention_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx) { BLE_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_STATUS, 1); struct bt_mesh_health_srv *srv = model->user_data; u8_t time = 0U; if (!srv) { BT_ERR("%s, No Health Server context provided", __func__); return; } time = k_delayed_work_remaining_get(&srv->attn_timer) / 1000; BT_DBG("%u second%s", time, (time == 1U) ? "" : "s"); bt_mesh_model_msg_init(&msg, OP_ATTENTION_STATUS); net_buf_simple_add_u8(&msg, time); if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { BT_ERR("%s, Unable to send Health Attention Status", __func__); } } static void attention_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { BT_DBG("%s", __func__); send_attention_status(model, ctx); } static void health_set_attention(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { u8_t time = 0U; time = net_buf_simple_pull_u8(buf); BT_DBG("%u second%s", time, (time == 1U) ? "" : "s"); bt_mesh_attention(model, time); } static void attention_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { BT_DBG("%s", __func__); health_set_attention(model, ctx, buf); if (ctx->recv_op == OP_ATTENTION_SET) { send_attention_status(model, ctx); } } static void send_health_period_status(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx) { BLE_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_PERIOD_STATUS, 1); bt_mesh_model_msg_init(&msg, OP_HEALTH_PERIOD_STATUS); net_buf_simple_add_u8(&msg, model->pub->period_div); if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { BT_ERR("%s, Unable to send Health Period Status", __func__); } } static void health_period_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { BT_DBG("%s", __func__); send_health_period_status(model, ctx); } static void health_set_period(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { u8_t period = 0U; period = net_buf_simple_pull_u8(buf); if (period > 15) { BT_WARN("%s, Prohibited period value %u", __func__, period); return; } BT_DBG("period %u", period); model->pub->period_div = period; } static void health_period_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf) { BT_DBG("%s", __func__); health_set_period(model, ctx, buf); if (ctx->recv_op == OP_HEALTH_PERIOD_SET) { send_health_period_status(model, ctx); } } const struct bt_mesh_model_op bt_mesh_health_srv_op[] = { { OP_HEALTH_FAULT_GET, 2, health_fault_get }, { OP_HEALTH_FAULT_CLEAR, 2, health_fault_clear }, { OP_HEALTH_FAULT_CLEAR_UNREL, 2, health_fault_clear }, { OP_HEALTH_FAULT_TEST, 3, health_fault_test }, { OP_HEALTH_FAULT_TEST_UNREL, 3, health_fault_test }, { OP_HEALTH_PERIOD_GET, 0, health_period_get }, { OP_HEALTH_PERIOD_SET, 1, health_period_set }, { OP_HEALTH_PERIOD_SET_UNREL, 1, health_period_set }, { OP_ATTENTION_GET, 0, attention_get }, { OP_ATTENTION_SET, 1, attention_set }, { OP_ATTENTION_SET_UNREL, 1, attention_set }, BLE_MESH_MODEL_OP_END, }; static size_t health_get_current(struct bt_mesh_model *model, struct net_buf_simple *msg) { struct bt_mesh_health_srv *srv = model->user_data; if (!srv) { BT_ERR("%s, No Health Server context provided", __func__); return 0; } if (msg->size < 4) { BT_ERR("%s, Too small health publication msg size %d", __func__, msg->size); return 0; } bt_mesh_model_msg_init(msg, OP_HEALTH_CURRENT_STATUS); net_buf_simple_add_u8(msg, srv->test.prev_test_id); net_buf_simple_add_le16(msg, srv->test.company_id); health_get_fault_value(model, msg, true); return health_get_curr_fault_count(model); } static int health_pub_update(struct bt_mesh_model *model) { struct bt_mesh_model_pub *pub = model->pub; size_t count = 0U; BT_DBG("%s", __func__); if (!pub || !pub->msg) { BT_ERR("%s, Invalid health publication context", __func__); return -EINVAL; } count = health_get_current(model, pub->msg); if (count) { pub->fast_period = 1U; } else { pub->fast_period = 0U; } return 0; } int bt_mesh_fault_update(struct bt_mesh_elem *elem) { struct bt_mesh_model *model = NULL; model = bt_mesh_model_find(elem, BLE_MESH_MODEL_ID_HEALTH_SRV); if (!model) { BT_ERR("%s, Health Server does not exist", __func__); return -EINVAL; } if (!model->pub) { BT_ERR("%s, Health Server has no publication support", __func__); return -EINVAL; } /* Let periodic publishing, if enabled, take care of sending the * Health Current Status. */ if (bt_mesh_model_pub_period_get(model)) { return 0; } health_pub_update(model); return bt_mesh_model_publish(model); } static void attention_off(struct k_work *work) { struct bt_mesh_health_srv *srv = CONTAINER_OF(work, struct bt_mesh_health_srv, attn_timer.work); BT_DBG("%s", __func__); if (!srv) { BT_ERR("%s, No Health Server context provided", __func__); return; } if (srv->cb.attn_off) { srv->cb.attn_off(srv->model); } srv->attn_timer_start = false; } int bt_mesh_health_srv_init(struct bt_mesh_model *model, bool primary) { struct bt_mesh_health_srv *srv = model->user_data; /* Health Server Model shall be supported by a primary element and may be * supported by any secondary elements. */ if (!srv) { if (!primary) { /* If Health Server is in the secondary element with NULL user_data. */ return 0; } BT_ERR("%s, No Health Server context provided", __func__); return -EINVAL; } if (srv->test.id_count == 0 || !srv->test.test_ids) { BT_ERR("%s, No Health Test ID provided", __func__); return -EINVAL; } if (!model->pub) { BT_ERR("%s, Health Server has no publication support", __func__); return -EINVAL; } model->pub->update = health_pub_update; k_delayed_work_init(&srv->attn_timer, attention_off); srv->model = model; srv->attn_timer_start = false; memset(srv->test.curr_faults, HEALTH_NO_FAULT, ARRAY_SIZE(srv->test.curr_faults)); memset(srv->test.reg_faults, HEALTH_NO_FAULT, ARRAY_SIZE(srv->test.reg_faults)); if (primary) { health_srv = srv; } return 0; } int bt_mesh_health_srv_deinit(struct bt_mesh_model *model, bool primary) { struct bt_mesh_health_srv *srv = model->user_data; if (!srv) { if (!primary) { /* If Health Server is in the secondary element with NULL user_data. */ return 0; } BT_ERR("%s, No Health Server context provided", __func__); return -EINVAL; } if (srv->test.id_count == 0 || !srv->test.test_ids) { BT_ERR("%s, No Health Test ID provided", __func__); return -EINVAL; } if (!model->pub) { BT_ERR("%s, Health Server has no publication support", __func__); return -EINVAL; } model->pub->addr = BLE_MESH_ADDR_UNASSIGNED; model->pub->update = NULL; k_delayed_work_free(&srv->attn_timer); if (primary) { health_srv = NULL; } return 0; } void bt_mesh_attention(struct bt_mesh_model *model, u8_t time) { struct bt_mesh_health_srv *srv = NULL; if (!model) { srv = health_srv; if (!srv) { BT_WARN("%s, No Health Server context provided", __func__); return; } model = srv->model; } else { srv = model->user_data; if (!srv) { BT_WARN("%s, No Health Server context provided", __func__); return; } } if (time) { if (srv->cb.attn_on) { srv->cb.attn_on(model, time); } k_delayed_work_submit(&srv->attn_timer, time * 1000U); srv->attn_timer_start = true; } else { k_delayed_work_cancel(&srv->attn_timer); if (srv->attn_timer_start == true) { if (srv->cb.attn_off) { srv->cb.attn_off(model); } srv->attn_timer_start = false; } } }