1 /* main.c - Application main entry point */
2
3 /*
4 * Copyright (c) 2017 Intel Corporation
5 *
6 * SPDX-License-Identifier: Apache-2.0
7 */
8
9 #include <zephyr/sys/printk.h>
10
11 #include <zephyr/settings/settings.h>
12 #include <zephyr/devicetree.h>
13 #include <zephyr/device.h>
14 #include <zephyr/drivers/hwinfo.h>
15 #include <zephyr/sys/byteorder.h>
16
17 #include <zephyr/bluetooth/bluetooth.h>
18 #include <zephyr/bluetooth/mesh.h>
19
20 #include "board.h"
21
22 #define OP_ONOFF_GET BT_MESH_MODEL_OP_2(0x82, 0x01)
23 #define OP_ONOFF_SET BT_MESH_MODEL_OP_2(0x82, 0x02)
24 #define OP_ONOFF_SET_UNACK BT_MESH_MODEL_OP_2(0x82, 0x03)
25 #define OP_ONOFF_STATUS BT_MESH_MODEL_OP_2(0x82, 0x04)
26
attention_on(const struct bt_mesh_model * mod)27 static void attention_on(const struct bt_mesh_model *mod)
28 {
29 board_led_set(true);
30 }
31
attention_off(const struct bt_mesh_model * mod)32 static void attention_off(const struct bt_mesh_model *mod)
33 {
34 board_led_set(false);
35 }
36
37 static const struct bt_mesh_health_srv_cb health_cb = {
38 .attn_on = attention_on,
39 .attn_off = attention_off,
40 };
41
42 static struct bt_mesh_health_srv health_srv = {
43 .cb = &health_cb,
44 };
45
46 BT_MESH_HEALTH_PUB_DEFINE(health_pub, 0);
47
48 static const char *const onoff_str[] = { "off", "on" };
49
50 static struct {
51 bool val;
52 uint8_t tid;
53 uint16_t src;
54 uint32_t transition_time;
55 struct k_work_delayable work;
56 } onoff;
57
58 /* OnOff messages' transition time and remaining time fields are encoded as an
59 * 8 bit value with a 6 bit step field and a 2 bit resolution field.
60 * The resolution field maps to:
61 * 0: 100 ms
62 * 1: 1 s
63 * 2: 10 s
64 * 3: 20 min
65 */
66 static const uint32_t time_res[] = {
67 100,
68 MSEC_PER_SEC,
69 10 * MSEC_PER_SEC,
70 10 * 60 * MSEC_PER_SEC,
71 };
72
model_time_decode(uint8_t val)73 static inline int32_t model_time_decode(uint8_t val)
74 {
75 uint8_t resolution = (val >> 6) & BIT_MASK(2);
76 uint8_t steps = val & BIT_MASK(6);
77
78 if (steps == 0x3f) {
79 return SYS_FOREVER_MS;
80 }
81
82 return steps * time_res[resolution];
83 }
84
model_time_encode(int32_t ms)85 static inline uint8_t model_time_encode(int32_t ms)
86 {
87 if (ms == SYS_FOREVER_MS) {
88 return 0x3f;
89 }
90
91 for (int i = 0; i < ARRAY_SIZE(time_res); i++) {
92 if (ms >= BIT_MASK(6) * time_res[i]) {
93 continue;
94 }
95
96 uint8_t steps = DIV_ROUND_UP(ms, time_res[i]);
97
98 return steps | (i << 6);
99 }
100
101 return 0x3f;
102 }
103
onoff_status_send(const struct bt_mesh_model * model,struct bt_mesh_msg_ctx * ctx)104 static int onoff_status_send(const struct bt_mesh_model *model,
105 struct bt_mesh_msg_ctx *ctx)
106 {
107 uint32_t remaining;
108
109 BT_MESH_MODEL_BUF_DEFINE(buf, OP_ONOFF_STATUS, 3);
110 bt_mesh_model_msg_init(&buf, OP_ONOFF_STATUS);
111
112 remaining = k_ticks_to_ms_floor32(
113 k_work_delayable_remaining_get(&onoff.work)) +
114 onoff.transition_time;
115
116 /* Check using remaining time instead of "work pending" to make the
117 * onoff status send the right value on instant transitions. As the
118 * work item is executed in a lower priority than the mesh message
119 * handler, the work will be pending even on instant transitions.
120 */
121 if (remaining) {
122 net_buf_simple_add_u8(&buf, !onoff.val);
123 net_buf_simple_add_u8(&buf, onoff.val);
124 net_buf_simple_add_u8(&buf, model_time_encode(remaining));
125 } else {
126 net_buf_simple_add_u8(&buf, onoff.val);
127 }
128
129 return bt_mesh_model_send(model, ctx, &buf, NULL, NULL);
130 }
131
onoff_timeout(struct k_work * work)132 static void onoff_timeout(struct k_work *work)
133 {
134 if (onoff.transition_time) {
135 /* Start transition.
136 *
137 * The LED should be on as long as the transition is in
138 * progress, regardless of the target value, according to the
139 * Bluetooth Mesh Model specification, section 3.1.1.
140 */
141 board_led_set(true);
142
143 k_work_reschedule(&onoff.work, K_MSEC(onoff.transition_time));
144 onoff.transition_time = 0;
145 return;
146 }
147
148 board_led_set(onoff.val);
149 }
150
151 /* Generic OnOff Server message handlers */
152
gen_onoff_get(const struct bt_mesh_model * model,struct bt_mesh_msg_ctx * ctx,struct net_buf_simple * buf)153 static int gen_onoff_get(const struct bt_mesh_model *model,
154 struct bt_mesh_msg_ctx *ctx,
155 struct net_buf_simple *buf)
156 {
157 onoff_status_send(model, ctx);
158 return 0;
159 }
160
gen_onoff_set_unack(const struct bt_mesh_model * model,struct bt_mesh_msg_ctx * ctx,struct net_buf_simple * buf)161 static int gen_onoff_set_unack(const struct bt_mesh_model *model,
162 struct bt_mesh_msg_ctx *ctx,
163 struct net_buf_simple *buf)
164 {
165 uint8_t val = net_buf_simple_pull_u8(buf);
166 uint8_t tid = net_buf_simple_pull_u8(buf);
167 int32_t trans = 0;
168 int32_t delay = 0;
169
170 if (buf->len) {
171 trans = model_time_decode(net_buf_simple_pull_u8(buf));
172 delay = net_buf_simple_pull_u8(buf) * 5;
173 }
174
175 /* Only perform change if the message wasn't a duplicate and the
176 * value is different.
177 */
178 if (tid == onoff.tid && ctx->addr == onoff.src) {
179 /* Duplicate */
180 return 0;
181 }
182
183 if (val == onoff.val) {
184 /* No change */
185 return 0;
186 }
187
188 printk("set: %s delay: %d ms time: %d ms\n", onoff_str[val], delay,
189 trans);
190
191 onoff.tid = tid;
192 onoff.src = ctx->addr;
193 onoff.val = val;
194 onoff.transition_time = trans;
195
196 /* Schedule the next action to happen on the delay, and keep
197 * transition time stored, so it can be applied in the timeout.
198 */
199 k_work_reschedule(&onoff.work, K_MSEC(delay));
200
201 return 0;
202 }
203
gen_onoff_set(const struct bt_mesh_model * model,struct bt_mesh_msg_ctx * ctx,struct net_buf_simple * buf)204 static int gen_onoff_set(const struct bt_mesh_model *model,
205 struct bt_mesh_msg_ctx *ctx,
206 struct net_buf_simple *buf)
207 {
208 (void)gen_onoff_set_unack(model, ctx, buf);
209 onoff_status_send(model, ctx);
210
211 return 0;
212 }
213
214 static const struct bt_mesh_model_op gen_onoff_srv_op[] = {
215 { OP_ONOFF_GET, BT_MESH_LEN_EXACT(0), gen_onoff_get },
216 { OP_ONOFF_SET, BT_MESH_LEN_MIN(2), gen_onoff_set },
217 { OP_ONOFF_SET_UNACK, BT_MESH_LEN_MIN(2), gen_onoff_set_unack },
218 BT_MESH_MODEL_OP_END,
219 };
220
221 /* Generic OnOff Client */
222
gen_onoff_status(const struct bt_mesh_model * model,struct bt_mesh_msg_ctx * ctx,struct net_buf_simple * buf)223 static int gen_onoff_status(const struct bt_mesh_model *model,
224 struct bt_mesh_msg_ctx *ctx,
225 struct net_buf_simple *buf)
226 {
227 uint8_t present = net_buf_simple_pull_u8(buf);
228
229 if (buf->len) {
230 uint8_t target = net_buf_simple_pull_u8(buf);
231 int32_t remaining_time =
232 model_time_decode(net_buf_simple_pull_u8(buf));
233
234 printk("OnOff status: %s -> %s: (%d ms)\n", onoff_str[present],
235 onoff_str[target], remaining_time);
236 return 0;
237 }
238
239 printk("OnOff status: %s\n", onoff_str[present]);
240
241 return 0;
242 }
243
244 static const struct bt_mesh_model_op gen_onoff_cli_op[] = {
245 {OP_ONOFF_STATUS, BT_MESH_LEN_MIN(1), gen_onoff_status},
246 BT_MESH_MODEL_OP_END,
247 };
248
249 /* This application only needs one element to contain its models */
250 static const struct bt_mesh_model models[] = {
251 BT_MESH_MODEL_CFG_SRV,
252 BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
253 BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_srv_op, NULL,
254 NULL),
255 BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_CLI, gen_onoff_cli_op, NULL,
256 NULL),
257 };
258
259 static const struct bt_mesh_elem elements[] = {
260 BT_MESH_ELEM(0, models, BT_MESH_MODEL_NONE),
261 };
262
263 static const struct bt_mesh_comp comp = {
264 .cid = BT_COMP_ID_LF,
265 .elem = elements,
266 .elem_count = ARRAY_SIZE(elements),
267 };
268
269 /* Provisioning */
270
output_number(bt_mesh_output_action_t action,uint32_t number)271 static int output_number(bt_mesh_output_action_t action, uint32_t number)
272 {
273 printk("OOB Number: %u\n", number);
274
275 board_output_number(action, number);
276
277 return 0;
278 }
279
prov_complete(uint16_t net_idx,uint16_t addr)280 static void prov_complete(uint16_t net_idx, uint16_t addr)
281 {
282 board_prov_complete();
283 }
284
prov_reset(void)285 static void prov_reset(void)
286 {
287 bt_mesh_prov_enable(BT_MESH_PROV_ADV | BT_MESH_PROV_GATT);
288 }
289
290 static uint8_t dev_uuid[16];
291
292 static const struct bt_mesh_prov prov = {
293 .uuid = dev_uuid,
294 .output_size = 4,
295 .output_actions = BT_MESH_DISPLAY_NUMBER,
296 .output_number = output_number,
297 .complete = prov_complete,
298 .reset = prov_reset,
299 };
300
301 /** Send an OnOff Set message from the Generic OnOff Client to all nodes. */
gen_onoff_send(bool val)302 static int gen_onoff_send(bool val)
303 {
304 struct bt_mesh_msg_ctx ctx = {
305 .app_idx = models[3].keys[0], /* Use the bound key */
306 .addr = BT_MESH_ADDR_ALL_NODES,
307 .send_ttl = BT_MESH_TTL_DEFAULT,
308 };
309 static uint8_t tid;
310
311 if (ctx.app_idx == BT_MESH_KEY_UNUSED) {
312 printk("The Generic OnOff Client must be bound to a key before "
313 "sending.\n");
314 return -ENOENT;
315 }
316
317 BT_MESH_MODEL_BUF_DEFINE(buf, OP_ONOFF_SET_UNACK, 2);
318 bt_mesh_model_msg_init(&buf, OP_ONOFF_SET_UNACK);
319 net_buf_simple_add_u8(&buf, val);
320 net_buf_simple_add_u8(&buf, tid++);
321
322 printk("Sending OnOff Set: %s\n", onoff_str[val]);
323
324 return bt_mesh_model_send(&models[3], &ctx, &buf, NULL, NULL);
325 }
326
button_pressed(struct k_work * work)327 static void button_pressed(struct k_work *work)
328 {
329 if (bt_mesh_is_provisioned()) {
330 (void)gen_onoff_send(!onoff.val);
331 return;
332 }
333
334 /* Self-provision with an arbitrary address.
335 *
336 * NOTE: This should never be done in a production environment.
337 * Addresses should be assigned by a provisioner, and keys should
338 * be generated from true random numbers. It is done in this
339 * sample to allow testing without a provisioner.
340 */
341 static uint8_t net_key[16];
342 static uint8_t dev_key[16];
343 static uint8_t app_key[16];
344 uint16_t addr;
345 int err;
346
347 if (IS_ENABLED(CONFIG_HWINFO)) {
348 addr = sys_get_le16(&dev_uuid[0]) & BIT_MASK(15);
349 } else {
350 addr = k_uptime_get_32() & BIT_MASK(15);
351 }
352
353 printk("Self-provisioning with address 0x%04x\n", addr);
354 err = bt_mesh_provision(net_key, 0, 0, 0, addr, dev_key);
355 if (err) {
356 printk("Provisioning failed (err: %d)\n", err);
357 return;
358 }
359
360 /* Add an application key to both Generic OnOff models: */
361 err = bt_mesh_app_key_add(0, 0, app_key);
362 if (err) {
363 printk("App key add failed (err: %d)\n", err);
364 return;
365 }
366
367 /* Models must be bound to an app key to send and receive messages with
368 * it:
369 */
370 models[2].keys[0] = 0;
371 models[3].keys[0] = 0;
372
373 printk("Provisioned and configured!\n");
374 }
375
bt_ready(int err)376 static void bt_ready(int err)
377 {
378 if (err) {
379 printk("Bluetooth init failed (err %d)\n", err);
380 return;
381 }
382
383 printk("Bluetooth initialized\n");
384
385 err = bt_mesh_init(&prov, &comp);
386 if (err) {
387 printk("Initializing mesh failed (err %d)\n", err);
388 return;
389 }
390
391 if (IS_ENABLED(CONFIG_SETTINGS)) {
392 settings_load();
393 }
394
395 /* This will be a no-op if settings_load() loaded provisioning info */
396 bt_mesh_prov_enable(BT_MESH_PROV_ADV | BT_MESH_PROV_GATT);
397
398 printk("Mesh initialized\n");
399 }
400
main(void)401 int main(void)
402 {
403 static struct k_work button_work;
404 int err = -1;
405
406 printk("Initializing...\n");
407
408 if (IS_ENABLED(CONFIG_HWINFO)) {
409 err = hwinfo_get_device_id(dev_uuid, sizeof(dev_uuid));
410 }
411
412 if (err < 0) {
413 dev_uuid[0] = 0xdd;
414 dev_uuid[1] = 0xdd;
415 }
416
417 k_work_init(&button_work, button_pressed);
418
419 err = board_init(&button_work);
420 if (err) {
421 printk("Board init failed (err: %d)\n", err);
422 return 0;
423 }
424
425 k_work_init_delayable(&onoff.work, onoff_timeout);
426
427 /* Initialize the Bluetooth Subsystem */
428 err = bt_enable(bt_ready);
429 if (err) {
430 printk("Bluetooth init failed (err %d)\n", err);
431 }
432 return 0;
433 }
434