zephyr/subsys/bluetooth/mesh/health_srv.c
Michal Narajowski fab8e29ee2 Bluetooth: Mesh: Add model publish support to Health Client
Mesh Profile Specification errata 11310
Section 4.4.4.1
"This model shall support model publication, as defined in Section
4.2.2 of the Mesh Profile specification and model subscription,
as defined in section 4.2.3 of the Mesh Profile specification."

Signed-off-by: Michal Narajowski <michal.narajowski@codecoup.pl>
2022-08-01 18:04:42 +01:00

478 lines
11 KiB
C

/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/zephyr.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <zephyr/types.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/mesh.h>
#define BT_DBG_ENABLED IS_ENABLED(CONFIG_BT_MESH_DEBUG_MODEL)
#define LOG_MODULE_NAME bt_mesh_health_srv
#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
/* Health Server context of the primary element */
struct bt_mesh_health_srv *health_srv;
static void health_get_registered(struct bt_mesh_model *mod,
uint16_t company_id,
struct net_buf_simple *msg)
{
struct bt_mesh_health_srv *srv = mod->user_data;
uint8_t *test_id;
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);
if (srv->cb && srv->cb->fault_get_reg) {
uint8_t fault_count = net_buf_simple_tailroom(msg) - 4;
int err;
err = srv->cb->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 *srv = mod->user_data;
const struct bt_mesh_comp *comp;
uint8_t *test_id, *company_ptr;
uint16_t company_id;
uint8_t fault_count;
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));
comp = bt_mesh_comp_get();
if (srv->cb && srv->cb->fault_get_cur) {
fault_count = net_buf_simple_tailroom(msg);
int err;
err = srv->cb->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(comp->cid, company_ptr);
*test_id = HEALTH_TEST_STANDARD;
fault_count = 0U;
} else {
sys_put_le16(company_id, company_ptr);
net_buf_simple_add(msg, fault_count);
}
} else {
BT_WARN("No callback for getting faults");
sys_put_le16(comp->cid, company_ptr);
*test_id = HEALTH_TEST_STANDARD;
fault_count = 0U;
}
return fault_count;
}
static int health_fault_get(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX);
uint16_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, &sdu);
if (bt_mesh_model_send(model, ctx, &sdu, NULL, NULL)) {
BT_ERR("Unable to send Health Current Status response");
}
return 0;
}
static int 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 *srv = model->user_data;
uint16_t company_id;
company_id = net_buf_simple_pull_le16(buf);
BT_DBG("company_id 0x%04x", company_id);
if (srv->cb && srv->cb->fault_clear) {
return srv->cb->fault_clear(model, company_id);
}
return 0;
}
static int health_fault_clear(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX);
struct bt_mesh_health_srv *srv = model->user_data;
uint16_t company_id;
company_id = net_buf_simple_pull_le16(buf);
BT_DBG("company_id 0x%04x", company_id);
if (srv->cb && srv->cb->fault_clear) {
int err;
err = srv->cb->fault_clear(model, company_id);
if (err) {
return err;
}
}
health_get_registered(model, company_id, &sdu);
if (bt_mesh_model_send(model, ctx, &sdu, NULL, NULL)) {
BT_ERR("Unable to send Health Current Status response");
}
return 0;
}
static int 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 *srv = model->user_data;
uint16_t company_id;
uint8_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);
if (srv->cb && srv->cb->fault_test) {
return srv->cb->fault_test(model, test_id, company_id);
}
return 0;
}
static int health_fault_test(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX);
struct bt_mesh_health_srv *srv = model->user_data;
uint16_t company_id;
uint8_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->cb && srv->cb->fault_test) {
int err;
err = srv->cb->fault_test(model, test_id, company_id);
if (err) {
BT_WARN("Running fault test failed with err %d", err);
return err;
}
}
health_get_registered(model, company_id, &sdu);
if (bt_mesh_model_send(model, ctx, &sdu, NULL, NULL)) {
BT_ERR("Unable to send Health Current Status response");
}
return 0;
}
static int send_attention_status(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx)
{
/* Needed size: opcode (2 bytes) + msg + MIC */
BT_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_STATUS, 1);
struct bt_mesh_health_srv *srv = model->user_data;
uint8_t time;
time = k_ticks_to_ms_floor32(
k_work_delayable_remaining_get(&srv->attn_timer)) / 1000U;
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("Unable to send Attention Status");
}
return 0;
}
static int attention_get(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
BT_DBG("");
return send_attention_status(model, ctx);
}
static int attention_set_unrel(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
uint8_t time;
time = net_buf_simple_pull_u8(buf);
BT_DBG("%u second%s", time, (time == 1U) ? "" : "s");
bt_mesh_attention(model, time);
return 0;
}
static int attention_set(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
int err;
BT_DBG("");
err = attention_set_unrel(model, ctx, buf);
if (err) {
return err;
}
return send_attention_status(model, ctx);
}
static int send_health_period_status(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx)
{
/* Needed size: opcode (2 bytes) + msg + MIC */
BT_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("Unable to send Health Period Status");
}
return 0;
}
static int health_period_get(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
BT_DBG("");
return send_health_period_status(model, ctx);
}
static int health_period_set_unrel(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
uint8_t period;
period = net_buf_simple_pull_u8(buf);
if (period > 15) {
BT_WARN("Prohibited period value %u", period);
return -EINVAL;
}
BT_DBG("period %u", period);
model->pub->period_div = period;
return 0;
}
static int health_period_set(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf)
{
int err;
BT_DBG("");
err = health_period_set_unrel(model, ctx, buf);
if (err) {
return err;
}
return send_health_period_status(model, ctx);
}
const struct bt_mesh_model_op bt_mesh_health_srv_op[] = {
{ OP_HEALTH_FAULT_GET, BT_MESH_LEN_EXACT(2), health_fault_get },
{ OP_HEALTH_FAULT_CLEAR, BT_MESH_LEN_EXACT(2), health_fault_clear },
{ OP_HEALTH_FAULT_CLEAR_UNREL, BT_MESH_LEN_EXACT(2), health_fault_clear_unrel },
{ OP_HEALTH_FAULT_TEST, BT_MESH_LEN_EXACT(3), health_fault_test },
{ OP_HEALTH_FAULT_TEST_UNREL, BT_MESH_LEN_EXACT(3), health_fault_test_unrel },
{ OP_HEALTH_PERIOD_GET, BT_MESH_LEN_EXACT(0), health_period_get },
{ OP_HEALTH_PERIOD_SET, BT_MESH_LEN_EXACT(1), health_period_set },
{ OP_HEALTH_PERIOD_SET_UNREL, BT_MESH_LEN_EXACT(1), health_period_set_unrel },
{ OP_ATTENTION_GET, BT_MESH_LEN_EXACT(0), attention_get },
{ OP_ATTENTION_SET, BT_MESH_LEN_EXACT(1), attention_set },
{ OP_ATTENTION_SET_UNREL, BT_MESH_LEN_EXACT(1), attention_set_unrel },
BT_MESH_MODEL_OP_END,
};
static int health_pub_update(struct bt_mesh_model *mod)
{
struct bt_mesh_model_pub *pub = mod->pub;
size_t count;
BT_DBG("");
count = health_get_current(mod, 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)
{
return bt_mesh_health_srv_fault_update(elem);
}
int bt_mesh_health_srv_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;
}
/* Let periodic publishing, if enabled, take care of sending the
* Health Current Status.
*/
if (bt_mesh_model_pub_period_get(mod) > 0) {
return 0;
}
health_pub_update(mod);
return bt_mesh_model_publish(mod);
}
static void attention_off(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct bt_mesh_health_srv *srv = CONTAINER_OF(dwork,
struct bt_mesh_health_srv,
attn_timer);
BT_DBG("");
if (srv->cb && srv->cb->attn_off) {
srv->cb->attn_off(srv->model);
}
}
static int health_srv_init(struct bt_mesh_model *model)
{
struct bt_mesh_health_srv *srv = model->user_data;
if (!srv) {
BT_ERR("No Health Server context provided");
return -EINVAL;
}
if (!model->pub) {
BT_ERR("Health Server has no publication support");
return -EINVAL;
}
model->pub->update = health_pub_update;
k_work_init_delayable(&srv->attn_timer, attention_off);
srv->model = model;
if (bt_mesh_model_in_primary(model)) {
health_srv = srv;
}
return 0;
}
const struct bt_mesh_model_cb bt_mesh_health_srv_cb = {
.init = health_srv_init,
};
void bt_mesh_attention(struct bt_mesh_model *model, uint8_t time)
{
struct bt_mesh_health_srv *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 > 0) && srv->cb && srv->cb->attn_on) {
srv->cb->attn_on(model);
}
k_work_reschedule(&srv->attn_timer, K_SECONDS(time));
}