| /* |
| * Copyright (c) 2018 Intel Corporation |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr.h> |
| #include <string.h> |
| #include <sys/printk.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/mesh.h> |
| #include <bluetooth/hci.h> |
| |
| #include <drivers/sensor.h> |
| |
| #include "mesh.h" |
| #include "board.h" |
| |
| #define MOD_LF 0x0000 |
| #define OP_HELLO 0xbb |
| #define OP_HEARTBEAT 0xbc |
| #define OP_BADUSER 0xbd |
| #define OP_VND_HELLO BT_MESH_MODEL_OP_3(OP_HELLO, BT_COMP_ID_LF) |
| #define OP_VND_HEARTBEAT BT_MESH_MODEL_OP_3(OP_HEARTBEAT, BT_COMP_ID_LF) |
| #define OP_VND_BADUSER BT_MESH_MODEL_OP_3(OP_BADUSER, BT_COMP_ID_LF) |
| |
| #define IV_INDEX 0 |
| #define DEFAULT_TTL 31 |
| #define GROUP_ADDR 0xc123 |
| #define NET_IDX 0x000 |
| #define APP_IDX 0x000 |
| #define FLAGS 0 |
| |
| /* Maximum characters in "hello" message */ |
| #define HELLO_MAX 8 |
| |
| #define MAX_SENS_STATUS_LEN 8 |
| |
| #define SENS_PROP_ID_PRESENT_DEVICE_TEMP 0x0054 |
| |
| enum { |
| SENSOR_HDR_A = 0, |
| SENSOR_HDR_B = 1, |
| }; |
| |
| struct sensor_hdr_a { |
| uint16_t prop_id:11; |
| uint16_t length:4; |
| uint16_t format:1; |
| } __packed; |
| |
| struct sensor_hdr_b { |
| uint8_t length:7; |
| uint8_t format:1; |
| uint16_t prop_id; |
| } __packed; |
| |
| static struct k_work hello_work; |
| static struct k_work baduser_work; |
| static struct k_work mesh_start_work; |
| |
| /* Definitions of models user data (Start) */ |
| static struct led_onoff_state led_onoff_state[] = { |
| /* Use LED 0 for this model */ |
| { .dev_id = 0 }, |
| }; |
| |
| static void heartbeat(const struct bt_mesh_hb_sub *sub, uint8_t hops, |
| uint16_t feat) |
| { |
| board_show_text("Heartbeat Received", false, K_SECONDS(2)); |
| } |
| |
| BT_MESH_HB_CB_DEFINE(hb_cb) = { |
| .recv = heartbeat, |
| }; |
| |
| static struct bt_mesh_cfg_cli cfg_cli = { |
| }; |
| |
| static void attention_on(struct bt_mesh_model *model) |
| { |
| board_show_text("Attention!", false, K_SECONDS(2)); |
| } |
| |
| static void attention_off(struct bt_mesh_model *model) |
| { |
| board_refresh_display(); |
| } |
| |
| static const struct bt_mesh_health_srv_cb health_srv_cb = { |
| .attn_on = attention_on, |
| .attn_off = attention_off, |
| }; |
| |
| static struct bt_mesh_health_srv health_srv = { |
| .cb = &health_srv_cb, |
| }; |
| |
| /* Generic OnOff Server message handlers */ |
| static int gen_onoff_get(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| NET_BUF_SIMPLE_DEFINE(msg, 2 + 1 + 4); |
| struct led_onoff_state *state = model->user_data; |
| |
| printk("addr 0x%04x onoff 0x%02x\n", |
| bt_mesh_model_elem(model)->addr, state->current); |
| bt_mesh_model_msg_init(&msg, BT_MESH_MODEL_OP_GEN_ONOFF_STATUS); |
| net_buf_simple_add_u8(&msg, state->current); |
| |
| if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { |
| printk("Unable to send On Off Status response\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int gen_onoff_set_unack(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| struct net_buf_simple *msg = model->pub->msg; |
| struct led_onoff_state *state = model->user_data; |
| int err; |
| uint8_t tid, onoff; |
| int64_t now; |
| |
| onoff = net_buf_simple_pull_u8(buf); |
| tid = net_buf_simple_pull_u8(buf); |
| |
| if (onoff > STATE_ON) { |
| printk("Wrong state received\n"); |
| |
| return 0; |
| } |
| |
| now = k_uptime_get(); |
| if (state->last_tid == tid && state->last_tx_addr == ctx->addr && |
| (now - state->last_msg_timestamp <= (6 * MSEC_PER_SEC))) { |
| printk("Already received message\n"); |
| } |
| |
| state->current = onoff; |
| state->last_tid = tid; |
| state->last_tx_addr = ctx->addr; |
| state->last_msg_timestamp = now; |
| |
| printk("addr 0x%02x state 0x%02x\n", |
| bt_mesh_model_elem(model)->addr, state->current); |
| |
| if (set_led_state(state->dev_id, onoff)) { |
| printk("Failed to set led state\n"); |
| |
| return 0; |
| } |
| |
| /* |
| * If a server has a publish address, it is required to |
| * publish status on a state change |
| * |
| * See Mesh Profile Specification 3.7.6.1.2 |
| * |
| * Only publish if there is an assigned address |
| */ |
| |
| if (state->previous != state->current && |
| model->pub->addr != BT_MESH_ADDR_UNASSIGNED) { |
| printk("publish last 0x%02x cur 0x%02x\n", |
| state->previous, state->current); |
| state->previous = state->current; |
| bt_mesh_model_msg_init(msg, |
| BT_MESH_MODEL_OP_GEN_ONOFF_STATUS); |
| net_buf_simple_add_u8(msg, state->current); |
| err = bt_mesh_model_publish(model); |
| if (err) { |
| printk("bt_mesh_model_publish err %d\n", err); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int gen_onoff_set(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| (void)gen_onoff_set_unack(model, ctx, buf); |
| (void)gen_onoff_get(model, ctx, buf); |
| |
| return 0; |
| } |
| |
| static int sensor_desc_get(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| /* TODO */ |
| return 0; |
| } |
| |
| static void sens_temperature_celsius_fill(struct net_buf_simple *msg) |
| { |
| struct sensor_hdr_a hdr; |
| /* TODO Get only temperature from sensor */ |
| struct sensor_value val[2]; |
| int16_t temp_degrees; |
| |
| hdr.format = SENSOR_HDR_A; |
| hdr.length = sizeof(temp_degrees); |
| hdr.prop_id = SENS_PROP_ID_PRESENT_DEVICE_TEMP; |
| |
| get_hdc1010_val(val); |
| temp_degrees = sensor_value_to_double(&val[0]) * 100; |
| |
| net_buf_simple_add_mem(msg, &hdr, sizeof(hdr)); |
| net_buf_simple_add_le16(msg, temp_degrees); |
| } |
| |
| static void sens_unknown_fill(uint16_t id, struct net_buf_simple *msg) |
| { |
| struct sensor_hdr_b hdr; |
| |
| /* |
| * When the message is a response to a Sensor Get message that |
| * identifies a sensor property that does not exist on the element, the |
| * Length field shall represent the value of zero and the Raw Value for |
| * that property shall be omitted. (Mesh model spec 1.0, 4.2.14). |
| * |
| * The length zero is represented using the format B and the special |
| * value 0x7F. |
| */ |
| hdr.format = SENSOR_HDR_B; |
| hdr.length = 0x7FU; |
| hdr.prop_id = id; |
| |
| net_buf_simple_add_mem(msg, &hdr, sizeof(hdr)); |
| } |
| |
| static void sensor_create_status(uint16_t id, struct net_buf_simple *msg) |
| { |
| bt_mesh_model_msg_init(msg, BT_MESH_MODEL_OP_SENS_STATUS); |
| |
| switch (id) { |
| case SENS_PROP_ID_PRESENT_DEVICE_TEMP: |
| sens_temperature_celsius_fill(msg); |
| break; |
| default: |
| sens_unknown_fill(id, msg); |
| break; |
| } |
| } |
| |
| static int sensor_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| NET_BUF_SIMPLE_DEFINE(msg, 1 + MAX_SENS_STATUS_LEN + 4); |
| uint16_t sensor_id; |
| |
| sensor_id = net_buf_simple_pull_le16(buf); |
| sensor_create_status(sensor_id, &msg); |
| |
| if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) { |
| printk("Unable to send Sensor get status response\n"); |
| } |
| |
| return 0; |
| } |
| |
| static int sensor_col_get(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| /* TODO */ |
| return 0; |
| } |
| |
| static int sensor_series_get(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| /* TODO */ |
| return 0; |
| } |
| |
| /* Definitions of models publication context (Start) */ |
| BT_MESH_HEALTH_PUB_DEFINE(health_pub, 0); |
| BT_MESH_MODEL_PUB_DEFINE(gen_onoff_srv_pub_root, NULL, 2 + 3); |
| |
| /* Mapping of message handlers for Generic OnOff Server (0x1000) */ |
| static const struct bt_mesh_model_op gen_onoff_srv_op[] = { |
| { BT_MESH_MODEL_OP_GEN_ONOFF_GET, BT_MESH_LEN_EXACT(0), gen_onoff_get }, |
| { BT_MESH_MODEL_OP_GEN_ONOFF_SET, BT_MESH_LEN_MIN(2), gen_onoff_set }, |
| { BT_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, BT_MESH_LEN_MIN(2), gen_onoff_set_unack }, |
| BT_MESH_MODEL_OP_END, |
| }; |
| |
| /* Mapping of message handlers for Sensor Server (0x1100) */ |
| static const struct bt_mesh_model_op sensor_srv_op[] = { |
| { BT_MESH_MODEL_OP_SENS_DESC_GET, BT_MESH_LEN_EXACT(0), sensor_desc_get }, |
| { BT_MESH_MODEL_OP_SENS_GET, BT_MESH_LEN_EXACT(2), sensor_get }, |
| { BT_MESH_MODEL_OP_SENS_COL_GET, BT_MESH_LEN_EXACT(2), sensor_col_get }, |
| { BT_MESH_MODEL_OP_SENS_SERIES_GET, BT_MESH_LEN_EXACT(2), sensor_series_get }, |
| }; |
| |
| static struct bt_mesh_model root_models[] = { |
| BT_MESH_MODEL_CFG_SRV, |
| BT_MESH_MODEL_CFG_CLI(&cfg_cli), |
| BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub), |
| BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, |
| gen_onoff_srv_op, &gen_onoff_srv_pub_root, |
| &led_onoff_state[0]), |
| BT_MESH_MODEL(BT_MESH_MODEL_ID_SENSOR_SRV, |
| sensor_srv_op, NULL, NULL), |
| }; |
| |
| static int vnd_hello(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| char str[32]; |
| size_t len; |
| |
| printk("Hello message from 0x%04x\n", ctx->addr); |
| |
| if (ctx->addr == bt_mesh_model_elem(model)->addr) { |
| printk("Ignoring message from self\n"); |
| return 0; |
| } |
| |
| len = MIN(buf->len, HELLO_MAX); |
| memcpy(str, buf->data, len); |
| str[len] = '\0'; |
| |
| board_add_hello(ctx->addr, str); |
| |
| strcat(str, " says hi!"); |
| board_show_text(str, false, K_SECONDS(3)); |
| |
| board_blink_leds(); |
| |
| return 0; |
| } |
| |
| static int vnd_baduser(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| char str[32]; |
| size_t len; |
| |
| printk("\"Bad user\" message from 0x%04x\n", ctx->addr); |
| |
| if (ctx->addr == bt_mesh_model_elem(model)->addr) { |
| printk("Ignoring message from self\n"); |
| return 0; |
| } |
| |
| len = MIN(buf->len, HELLO_MAX); |
| memcpy(str, buf->data, len); |
| str[len] = '\0'; |
| |
| strcat(str, " is misbehaving!"); |
| board_show_text(str, false, K_SECONDS(3)); |
| |
| board_blink_leds(); |
| |
| return 0; |
| } |
| |
| static int vnd_heartbeat(struct bt_mesh_model *model, |
| struct bt_mesh_msg_ctx *ctx, |
| struct net_buf_simple *buf) |
| { |
| uint8_t init_ttl, hops; |
| |
| /* Ignore messages from self */ |
| if (ctx->addr == bt_mesh_model_elem(model)->addr) { |
| return 0; |
| } |
| |
| init_ttl = net_buf_simple_pull_u8(buf); |
| hops = init_ttl - ctx->recv_ttl + 1; |
| |
| printk("Heartbeat from 0x%04x over %u hop%s\n", ctx->addr, |
| hops, hops == 1U ? "" : "s"); |
| |
| board_add_heartbeat(ctx->addr, hops); |
| |
| return 0; |
| } |
| |
| static const struct bt_mesh_model_op vnd_ops[] = { |
| { OP_VND_HELLO, BT_MESH_LEN_MIN(1), vnd_hello }, |
| { OP_VND_HEARTBEAT, BT_MESH_LEN_MIN(1), vnd_heartbeat }, |
| { OP_VND_BADUSER, BT_MESH_LEN_MIN(1), vnd_baduser }, |
| BT_MESH_MODEL_OP_END, |
| }; |
| |
| static int pub_update(struct bt_mesh_model *mod) |
| { |
| struct net_buf_simple *msg = mod->pub->msg; |
| |
| printk("Preparing to send heartbeat\n"); |
| |
| bt_mesh_model_msg_init(msg, OP_VND_HEARTBEAT); |
| net_buf_simple_add_u8(msg, DEFAULT_TTL); |
| |
| return 0; |
| } |
| |
| BT_MESH_MODEL_PUB_DEFINE(vnd_pub, pub_update, 3 + 1); |
| |
| static struct bt_mesh_model vnd_models[] = { |
| BT_MESH_MODEL_VND(BT_COMP_ID_LF, MOD_LF, vnd_ops, &vnd_pub, NULL), |
| }; |
| |
| static struct bt_mesh_elem elements[] = { |
| BT_MESH_ELEM(0, root_models, vnd_models), |
| }; |
| |
| static const struct bt_mesh_comp comp = { |
| .cid = BT_COMP_ID_LF, |
| .elem = elements, |
| .elem_count = ARRAY_SIZE(elements), |
| }; |
| |
| static size_t first_name_len(const char *name) |
| { |
| size_t len; |
| |
| for (len = 0; *name; name++, len++) { |
| switch (*name) { |
| case ' ': |
| case ',': |
| case '\n': |
| return len; |
| } |
| } |
| |
| return len; |
| } |
| |
| static void send_hello(struct k_work *work) |
| { |
| NET_BUF_SIMPLE_DEFINE(msg, 3 + HELLO_MAX + 4); |
| struct bt_mesh_msg_ctx ctx = { |
| .app_idx = APP_IDX, |
| .addr = GROUP_ADDR, |
| .send_ttl = DEFAULT_TTL, |
| }; |
| const char *name = bt_get_name(); |
| |
| bt_mesh_model_msg_init(&msg, OP_VND_HELLO); |
| net_buf_simple_add_mem(&msg, name, |
| MIN(HELLO_MAX, first_name_len(name))); |
| |
| if (bt_mesh_model_send(&vnd_models[0], &ctx, &msg, NULL, NULL) == 0) { |
| board_show_text("Saying \"hi!\" to everyone", false, |
| K_SECONDS(2)); |
| } else { |
| board_show_text("Sending Failed!", false, K_SECONDS(2)); |
| } |
| } |
| |
| void mesh_send_hello(void) |
| { |
| k_work_submit(&hello_work); |
| } |
| |
| static void send_baduser(struct k_work *work) |
| { |
| NET_BUF_SIMPLE_DEFINE(msg, 3 + HELLO_MAX + 4); |
| struct bt_mesh_msg_ctx ctx = { |
| .app_idx = APP_IDX, |
| .addr = GROUP_ADDR, |
| .send_ttl = DEFAULT_TTL, |
| }; |
| const char *name = bt_get_name(); |
| |
| bt_mesh_model_msg_init(&msg, OP_VND_BADUSER); |
| net_buf_simple_add_mem(&msg, name, |
| MIN(HELLO_MAX, first_name_len(name))); |
| |
| if (bt_mesh_model_send(&vnd_models[0], &ctx, &msg, NULL, NULL) == 0) { |
| board_show_text("Bad user!", false, K_SECONDS(2)); |
| } else { |
| board_show_text("Sending Failed!", false, K_SECONDS(2)); |
| } |
| } |
| |
| void mesh_send_baduser(void) |
| { |
| k_work_submit(&baduser_work); |
| } |
| |
| static int provision_and_configure(void) |
| { |
| static const uint8_t net_key[16] = { |
| 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, |
| 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, |
| }; |
| static const uint8_t app_key[16] = { |
| 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, |
| 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, |
| }; |
| struct bt_mesh_cfg_mod_pub pub = { |
| .addr = GROUP_ADDR, |
| .app_idx = APP_IDX, |
| .ttl = DEFAULT_TTL, |
| .period = BT_MESH_PUB_PERIOD_SEC(10), |
| }; |
| uint8_t dev_key[16]; |
| uint16_t addr; |
| int err; |
| |
| err = bt_rand(dev_key, sizeof(dev_key)); |
| if (err) { |
| return err; |
| } |
| |
| do { |
| err = bt_rand(&addr, sizeof(addr)); |
| if (err) { |
| return err; |
| } |
| } while (!addr); |
| |
| /* Make sure it's a unicast address (highest bit unset) */ |
| addr &= ~0x8000; |
| |
| err = bt_mesh_provision(net_key, NET_IDX, FLAGS, IV_INDEX, addr, |
| dev_key); |
| if (err) { |
| return err; |
| } |
| |
| printk("Configuring...\n"); |
| |
| /* Add Application Key */ |
| bt_mesh_cfg_app_key_add(NET_IDX, addr, NET_IDX, APP_IDX, app_key, NULL); |
| |
| /* Bind to vendor model */ |
| bt_mesh_cfg_mod_app_bind_vnd(NET_IDX, addr, addr, APP_IDX, |
| MOD_LF, BT_COMP_ID_LF, NULL); |
| |
| bt_mesh_cfg_mod_app_bind(NET_IDX, addr, addr, APP_IDX, |
| BT_MESH_MODEL_ID_GEN_ONOFF_SRV, NULL); |
| |
| bt_mesh_cfg_mod_app_bind(NET_IDX, addr, addr, APP_IDX, |
| BT_MESH_MODEL_ID_SENSOR_SRV, NULL); |
| |
| /* Bind to Health model */ |
| bt_mesh_cfg_mod_app_bind(NET_IDX, addr, addr, APP_IDX, |
| BT_MESH_MODEL_ID_HEALTH_SRV, NULL); |
| |
| /* Add model subscription */ |
| bt_mesh_cfg_mod_sub_add_vnd(NET_IDX, addr, addr, GROUP_ADDR, |
| MOD_LF, BT_COMP_ID_LF, NULL); |
| |
| bt_mesh_cfg_mod_pub_set_vnd(NET_IDX, addr, addr, MOD_LF, BT_COMP_ID_LF, |
| &pub, NULL); |
| |
| printk("Configuration complete\n"); |
| |
| return addr; |
| } |
| |
| static void start_mesh(struct k_work *work) |
| { |
| int err; |
| |
| err = provision_and_configure(); |
| if (err < 0) { |
| board_show_text("Starting Mesh Failed", false, |
| K_SECONDS(2)); |
| } else { |
| char buf[32]; |
| |
| snprintk(buf, sizeof(buf), |
| "Mesh Started\nAddr: 0x%04x", err); |
| board_show_text(buf, false, K_SECONDS(4)); |
| } |
| } |
| |
| void mesh_start(void) |
| { |
| k_work_submit(&mesh_start_work); |
| } |
| |
| bool mesh_is_initialized(void) |
| { |
| return elements[0].addr != BT_MESH_ADDR_UNASSIGNED; |
| } |
| |
| uint16_t mesh_get_addr(void) |
| { |
| return elements[0].addr; |
| } |
| |
| int mesh_init(void) |
| { |
| static const uint8_t dev_uuid[16] = { 0xc0, 0xff, 0xee }; |
| static const struct bt_mesh_prov prov = { |
| .uuid = dev_uuid, |
| }; |
| |
| k_work_init(&hello_work, send_hello); |
| k_work_init(&baduser_work, send_baduser); |
| k_work_init(&mesh_start_work, start_mesh); |
| |
| return bt_mesh_init(&prov, &comp); |
| } |