| /* Bluetooth Mesh */ |
| |
| /* |
| * Copyright (c) 2017 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <stdbool.h> |
| #include <zephyr/types.h> |
| #include <misc/byteorder.h> |
| #include <misc/util.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/mesh.h> |
| |
| #define BT_DBG_ENABLED IS_ENABLED(CONFIG_BLUETOOTH_MESH_DEBUG_MODEL) |
| #include "common/log.h" |
| |
| #include "mesh.h" |
| #include "adv.h" |
| #include "net.h" |
| #include "transport.h" |
| #include "access.h" |
| #include "foundation.h" |
| |
| #define HEALTH_TEST_STANDARD 0x00 |
| |
| /* Increasing this requires also increasing the system workqueue */ |
| #define MAX_FAULTS 32 |
| #define HEALTH_STATUS_SIZE_MAX (1 + 3 + MAX_FAULTS + 4) |
| |
| #if BT_MESH_TX_SDU_MAX < HEALTH_STATUS_SIZE_MAX |
| #define HEALTH_STATUS_SIZE BT_MESH_TX_SDU_MAX |
| #else |
| #define HEALTH_STATUS_SIZE HEALTH_STATUS_SIZE_MAX |
| #endif |
| |
| /* Health Server context of the primary element */ |
| struct bt_mesh_health *health_srv; |
| |
| static void health_get_registered(struct bt_mesh_model *mod, |
| u16_t company_id, |
| struct net_buf_simple *msg) |
| { |
| struct bt_mesh_health *srv = mod->user_data; |
| u8_t fault_count; |
| u8_t *test_id; |
| int err; |
| |
| BT_DBG("Company ID 0x%04x", company_id); |
| |
| bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_STATUS); |
| |
| test_id = net_buf_simple_add(msg, 1); |
| net_buf_simple_add_le16(msg, company_id); |
| fault_count = net_buf_simple_tailroom(msg) - 4; |
| |
| if (srv->fault_get_reg) { |
| err = srv->fault_get_reg(mod, company_id, test_id, |
| net_buf_simple_tail(msg), |
| &fault_count); |
| if (err) { |
| BT_ERR("Failed to get faults (err %d)", err); |
| *test_id = HEALTH_TEST_STANDARD; |
| } else { |
| net_buf_simple_add(msg, fault_count); |
| } |
| } else { |
| BT_WARN("No callback for getting faults"); |
| *test_id = HEALTH_TEST_STANDARD; |
| } |
| } |
| |
| static size_t health_get_current(struct bt_mesh_model *mod, |
| struct net_buf_simple *msg) |
| { |
| struct bt_mesh_health *srv = mod->user_data; |
| u8_t *test_id, *company_ptr; |
| u16_t company_id; |
| u8_t fault_count; |
| int err; |
| |
| bt_mesh_model_msg_init(msg, OP_HEALTH_CURRENT_STATUS); |
| |
| test_id = net_buf_simple_add(msg, 1); |
| company_ptr = net_buf_simple_add(msg, sizeof(company_id)); |
| |
| fault_count = net_buf_simple_tailroom(msg) - 4; |
| |
| if (srv->fault_get_cur) { |
| err = srv->fault_get_cur(mod, test_id, &company_id, |
| net_buf_simple_tail(msg), |
| &fault_count); |
| if (err) { |
| BT_ERR("Failed to get faults (err %d)", err); |
| sys_put_le16(0, company_ptr); |
| *test_id = HEALTH_TEST_STANDARD; |
| fault_count = 0; |
| } else { |
| sys_put_le16(company_id, company_ptr); |
| } |
| } else { |
| BT_WARN("No callback for getting faults"); |
| net_buf_simple_push_u8(msg, HEALTH_TEST_STANDARD); |
| net_buf_simple_push_le16(msg, 0); |
| fault_count = 0; |
| } |
| |
| return fault_count; |
| } |
| |
| static void health_fault_get(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct net_buf_simple *msg = NET_BUF_SIMPLE(HEALTH_STATUS_SIZE); |
| u16_t company_id; |
| |
| company_id = net_buf_simple_pull_le16(buf); |
| |
| BT_DBG("company_id 0x%04x", company_id); |
| |
| health_get_registered(model, company_id, msg); |
| |
| if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) { |
| BT_ERR("Unable to send Health Current Status response"); |
| } |
| } |
| |
| static void health_fault_clear_unrel(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_health *srv = model->user_data; |
| u16_t company_id; |
| |
| company_id = net_buf_simple_pull_le16(buf); |
| |
| BT_DBG("company_id 0x%04x", company_id); |
| |
| if (srv->fault_clear) { |
| srv->fault_clear(model, company_id); |
| } |
| } |
| |
| static void health_fault_clear(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct net_buf_simple *msg = NET_BUF_SIMPLE(HEALTH_STATUS_SIZE); |
| struct bt_mesh_health *srv = model->user_data; |
| u16_t company_id; |
| |
| company_id = net_buf_simple_pull_le16(buf); |
| |
| BT_DBG("company_id 0x%04x", company_id); |
| |
| if (srv->fault_clear) { |
| srv->fault_clear(model, company_id); |
| } |
| |
| health_get_registered(model, company_id, msg); |
| |
| if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) { |
| BT_ERR("Unable to send Health Current Status response"); |
| } |
| } |
| |
| static void health_fault_test_unrel(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_health *srv = model->user_data; |
| const struct bt_mesh_comp *comp; |
| u16_t company_id; |
| u8_t test_id; |
| |
| test_id = net_buf_simple_pull_u8(buf); |
| company_id = net_buf_simple_pull_le16(buf); |
| |
| BT_DBG("test 0x%02x company 0x%04x", test_id, company_id); |
| |
| comp = bt_mesh_comp_get(); |
| if (comp->cid != company_id) { |
| BT_WARN("CID 0x%04x doesn't match composition CID 0x%04x", |
| company_id, comp->cid); |
| return; |
| } |
| |
| if (srv->fault_test) { |
| srv->fault_test(model, test_id, company_id); |
| } |
| } |
| |
| static void health_fault_test(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct net_buf_simple *msg = NET_BUF_SIMPLE(HEALTH_STATUS_SIZE); |
| struct bt_mesh_health *srv = model->user_data; |
| u16_t company_id; |
| u8_t test_id; |
| |
| BT_DBG(""); |
| |
| test_id = net_buf_simple_pull_u8(buf); |
| company_id = net_buf_simple_pull_le16(buf); |
| |
| BT_DBG("test 0x%02x company 0x%04x", test_id, company_id); |
| |
| if (srv->fault_test) { |
| srv->fault_test(model, test_id, company_id); |
| } |
| |
| health_get_registered(model, company_id, msg); |
| |
| if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) { |
| BT_ERR("Unable to send Health Current Status response"); |
| } |
| } |
| |
| static void send_attention_status(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx) |
| { |
| /* Needed size: opcode (2 bytes) + msg + MIC */ |
| struct net_buf_simple *msg = NET_BUF_SIMPLE(2 + 1 + 4); |
| struct bt_mesh_health *srv = model->user_data; |
| u8_t time; |
| |
| time = k_delayed_work_remaining_get(&srv->attention.timer) / 1000; |
| BT_DBG("%u second%s", time, (time == 1) ? "" : "s"); |
| |
| bt_mesh_model_msg_init(msg, OP_ATTENTION_STATUS); |
| |
| net_buf_simple_add_u8(msg, time); |
| |
| bt_mesh_model_send(model, ctx, msg, NULL, NULL); |
| } |
| |
| static void attention_get(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| BT_DBG(""); |
| |
| send_attention_status(model, ctx); |
| } |
| |
| static void attention_set_unrel(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| u8_t time; |
| |
| time = net_buf_simple_pull_u8(buf); |
| |
| BT_DBG("%u second%s", time, (time == 1) ? "" : "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(""); |
| |
| attention_set_unrel(model, ctx, buf); |
| |
| send_attention_status(model, ctx); |
| } |
| |
| static void send_health_period_status(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx) |
| { |
| /* Needed size: opcode (2 bytes) + msg + MIC */ |
| struct net_buf_simple *msg = NET_BUF_SIMPLE(2 + 1 + 4); |
| struct bt_mesh_health *srv = model->user_data; |
| |
| bt_mesh_model_msg_init(msg, OP_HEALTH_PERIOD_STATUS); |
| |
| net_buf_simple_add_u8(msg, srv->period); |
| |
| bt_mesh_model_send(model, ctx, msg, NULL, NULL); |
| } |
| |
| static void health_period_get(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| BT_DBG(""); |
| |
| send_health_period_status(model, ctx); |
| } |
| |
| static void health_period_set_unrel(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct bt_mesh_health *srv = model->user_data; |
| u8_t period; |
| |
| period = net_buf_simple_pull_u8(buf); |
| if (period > 15) { |
| BT_WARN("Prohibited period value %u", period); |
| return; |
| } |
| |
| BT_DBG("period %u", period); |
| |
| srv->period = period; |
| } |
| |
| static void health_period_set(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| BT_DBG(""); |
| |
| health_period_set_unrel(model, ctx, buf); |
| |
| send_health_period_status(model, ctx); |
| } |
| |
| const struct bt_mesh_model_op bt_mesh_health_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_unrel }, |
| { OP_HEALTH_FAULT_TEST, 3, health_fault_test }, |
| { OP_HEALTH_FAULT_TEST_UNREL, 3, health_fault_test_unrel }, |
| { 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_unrel }, |
| { OP_ATTENTION_GET, 0, attention_get }, |
| { OP_ATTENTION_SET, 1, attention_set }, |
| { OP_ATTENTION_SET_UNREL, 1, attention_set_unrel }, |
| BT_MESH_MODEL_OP_END, |
| }; |
| |
| static void health_pub(struct bt_mesh_model *mod) |
| { |
| struct net_buf_simple *msg = NET_BUF_SIMPLE(HEALTH_STATUS_SIZE); |
| struct bt_mesh_health *srv = mod->user_data; |
| size_t count; |
| int err; |
| |
| BT_DBG(""); |
| |
| count = health_get_current(mod, msg); |
| if (count) { |
| mod->pub->period_div = srv->period; |
| } else { |
| mod->pub->period_div = 0; |
| } |
| |
| err = bt_mesh_model_publish(mod, msg); |
| if (err) { |
| BT_ERR("Publishing failed (err %d)", err); |
| } |
| } |
| |
| struct bt_mesh_model_pub bt_mesh_health_pub = { |
| .func = health_pub, |
| }; |
| |
| int bt_mesh_fault_update(struct bt_mesh_elem *elem) |
| { |
| struct bt_mesh_model *mod; |
| |
| mod = bt_mesh_model_find(elem, BT_MESH_MODEL_ID_HEALTH_SRV); |
| if (!mod) { |
| return -EINVAL; |
| } |
| |
| k_delayed_work_submit(&mod->pub->timer, K_NO_WAIT); |
| return 0; |
| } |
| |
| static void attention_off(struct k_work *work) |
| { |
| struct bt_mesh_health *srv = CONTAINER_OF(work, struct bt_mesh_health, |
| attention.timer.work); |
| BT_DBG(""); |
| |
| if (srv->attention.off) { |
| srv->attention.off(srv->model); |
| } |
| } |
| |
| int bt_mesh_health_init(struct bt_mesh_model *model, bool primary) |
| { |
| struct bt_mesh_health *srv = model->user_data; |
| |
| if (!srv) { |
| if (!primary) { |
| return 0; |
| } |
| |
| BT_ERR("No Health Server context provided"); |
| return -EINVAL; |
| } |
| |
| k_delayed_work_init(&srv->attention.timer, attention_off); |
| |
| srv->model = model; |
| |
| if (primary) { |
| health_srv = srv; |
| } |
| |
| return 0; |
| } |
| |
| void bt_mesh_attention(struct bt_mesh_model *model, u8_t time) |
| { |
| struct bt_mesh_health *srv; |
| |
| if (!model) { |
| srv = health_srv; |
| if (!srv) { |
| BT_WARN("No Health Server available"); |
| return; |
| } |
| |
| model = srv->model; |
| } else { |
| srv = model->user_data; |
| } |
| |
| if (time) { |
| if (srv->attention.on) { |
| srv->attention.on(model); |
| } |
| |
| k_delayed_work_submit(&srv->attention.timer, time * 1000); |
| } else { |
| k_delayed_work_cancel(&srv->attention.timer); |
| |
| if (srv->attention.off) { |
| srv->attention.off(model); |
| } |
| } |
| } |