You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by na...@apache.org on 2020/09/17 08:34:23 UTC

[mynewt-nimble] 01/02: apps/mesh_badge: Copy files from Zephyr

This is an automated email from the ASF dual-hosted git repository.

naraj pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-nimble.git

commit fad96b60570878ea8fba0699a0d07965771aa6ed
Author: MichaƂ Narajowski <mi...@codecoup.pl>
AuthorDate: Fri Oct 26 14:00:37 2018 +0200

    apps/mesh_badge: Copy files from Zephyr
---
 apps/mesh_badge/CMakeLists.txt   |   5 +
 apps/mesh_badge/README.rst       |  50 +++++++
 apps/mesh_badge/prj.conf         |  66 +++++++++
 apps/mesh_badge/sample.yaml      |   8 ++
 apps/mesh_badge/src/board.h      |  11 ++
 apps/mesh_badge/src/main.c       | 235 ++++++++++++++++++++++++++++++
 apps/mesh_badge/src/mesh.c       | 249 ++++++++++++++++++++++++++++++++
 apps/mesh_badge/src/mesh.h       |  12 ++
 apps/mesh_badge/src/reel_board.c | 303 +++++++++++++++++++++++++++++++++++++++
 9 files changed, 939 insertions(+)

diff --git a/apps/mesh_badge/CMakeLists.txt b/apps/mesh_badge/CMakeLists.txt
new file mode 100644
index 0000000..c5861dd
--- /dev/null
+++ b/apps/mesh_badge/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.8.2)
+include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
+project(NONE)
+
+target_sources(app PRIVATE src/main.c src/mesh.c src/${BOARD}.c)
diff --git a/apps/mesh_badge/README.rst b/apps/mesh_badge/README.rst
new file mode 100644
index 0000000..f695896
--- /dev/null
+++ b/apps/mesh_badge/README.rst
@@ -0,0 +1,50 @@
+.. _mesh_badge:
+
+Mesh Badge
+##########
+
+Overview
+********
+
+This sample app for the reel board showcases Bluetooth Mesh
+
+The app starts off as a regular Bluetooth GATT peripheral application.
+Install the the "nRF Connect" app on your phone (available both for
+Android and iOS) to access the service that the app exposes. The service
+can also be accessed with any Bluetooth LE GATT client from your PC,
+however these instructions focus on the necessary steps for phones.
+
+Steps to set up
+***************
+
+#. On your phone, use the nRF Connect app to Scan for devices and look
+   for "reel board"
+#. Connect to the device. You'll see a single service - select it
+#. Request to write to the characteristic by pressing on the upward pointing
+   arrow symbol
+#. Select "Text" to enter text instead of hex
+#. Enter your name (or any other arbitrary text). Multiple words
+   separated by spaces are possible. The font used on the reel display
+   allows three rows of up to 12 characters
+   wide text. You can force line breaks with a comma.
+#. Press "Send" - this will trigger pairing since this is a protected
+   characteristic. The passkey for the pairing will be shown on the board's
+   display. Enter the passkey in your phone.
+#. Once pairing is complete the board will show the text you sent. If
+   you're not happy with it you can try writing something else.
+#. When you're happy with the text, disconnect from the board (exit the app or
+   go back to the device scan page)
+#. Once disconnected the board switches over to Bluetooth Mesh mode, and you
+   can't connect to it anymore over GATT.
+
+If you configure multiple boards like this they can communicate with
+each other over mesh: by pressing the user button on the board the first
+word (name) of the stored text will be sent to all other boards in
+the network and cause the other boards to display "<name> says hi!".
+
+To reset a board to its initial state (disable mesh, erase the stored
+text, and make it connectable over GATT):
+
+#. Keep the user button pressed when powering on (or press the reset button
+   when powered)
+#. Wait until "Reseting Device" is shown
diff --git a/apps/mesh_badge/prj.conf b/apps/mesh_badge/prj.conf
new file mode 100644
index 0000000..206ba5a
--- /dev/null
+++ b/apps/mesh_badge/prj.conf
@@ -0,0 +1,66 @@
+CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
+CONFIG_BT_RX_STACK_SIZE=4096
+
+CONFIG_BT_DEVICE_NAME_DYNAMIC=y
+CONFIG_BT_DEVICE_NAME_GATT_WRITABLE=y
+CONFIG_BT_SMP=y
+CONFIG_BT_PERIPHERAL=y
+CONFIG_BT_PRIVACY=y
+CONFIG_BT_DEVICE_NAME="reel board"
+CONFIG_BT_DEVICE_NAME_MAX=32
+
+CONFIG_BT_DEBUG_LOG=y
+CONFIG_BT_MESH_DEBUG=y
+#CONFIG_BT_DEBUG_HCI_CORE=y
+#CONFIG_BT_MESH_DEBUG_ADV=y
+
+CONFIG_BT_OBSERVER=y
+CONFIG_BT_BROADCASTER=y
+CONFIG_BT_RX_BUF_COUNT=30
+CONFIG_BT_L2CAP_RX_MTU=69
+CONFIG_BT_L2CAP_TX_MTU=69
+CONFIG_BT_L2CAP_TX_BUF_COUNT=8
+
+CONFIG_BT_MESH=y
+CONFIG_BT_MESH_RELAY=y
+#CONFIG_BT_MESH_GATT_PROXY=y
+CONFIG_BT_MESH_PB_ADV=n
+CONFIG_BT_MESH_PB_GATT=n
+CONFIG_BT_MESH_ADV_BUF_COUNT=30
+CONFIG_BT_MESH_LABEL_COUNT=0
+CONFIG_BT_MESH_CFG_CLI=y
+CONFIG_BT_MESH_TX_SEG_MAX=6
+CONFIG_BT_MESH_TX_SEG_MSG_COUNT=3
+CONFIG_BT_MESH_RX_SEG_MSG_COUNT=3
+CONFIG_BT_MESH_CRPL=128
+CONFIG_BT_MESH_RPL_STORE_TIMEOUT=120
+
+CONFIG_PRINTK=y
+CONFIG_SERIAL=y
+CONFIG_CONSOLE=y
+CONFIG_STDOUT_CONSOLE=y
+
+CONFIG_I2C=y
+CONFIG_GPIO=y
+CONFIG_SENSOR=y
+
+CONFIG_APDS9960=y
+
+CONFIG_HDC1008=y
+
+CONFIG_SPI=y
+CONFIG_SPI_ASYNC=y
+
+CONFIG_HEAP_MEM_POOL_SIZE=16384
+CONFIG_DISPLAY=y
+CONFIG_SSD1673=y
+
+CONFIG_CHARACTER_FRAMEBUFFER=y
+
+CONFIG_BT_SETTINGS=y
+CONFIG_FLASH=y
+CONFIG_FLASH_PAGE_LAYOUT=y
+CONFIG_FLASH_MAP=y
+CONFIG_FCB=y
+CONFIG_SETTINGS=y
+CONFIG_SETTINGS_FCB=y
diff --git a/apps/mesh_badge/sample.yaml b/apps/mesh_badge/sample.yaml
new file mode 100644
index 0000000..6a50f81
--- /dev/null
+++ b/apps/mesh_badge/sample.yaml
@@ -0,0 +1,8 @@
+sample:
+  description: reel board mesh badge sample
+  name: mesh badge
+tests:
+  test:
+    platform_whitelist: reel_board
+    tags: samples sensor
+    harness: sensor
diff --git a/apps/mesh_badge/src/board.h b/apps/mesh_badge/src/board.h
new file mode 100644
index 0000000..7842cd4
--- /dev/null
+++ b/apps/mesh_badge/src/board.h
@@ -0,0 +1,11 @@
+/*
+ * Copyright (c) 2018 Phytec Messtechnik GmbH
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+void board_refresh_display(void);
+void board_show_text(const char *text, bool center, s32_t duration);
+void board_blink_leds(void);
+int board_init(void);
diff --git a/apps/mesh_badge/src/main.c b/apps/mesh_badge/src/main.c
new file mode 100644
index 0000000..23fe414
--- /dev/null
+++ b/apps/mesh_badge/src/main.c
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr.h>
+#include <misc/printk.h>
+
+#include <string.h>
+
+#include <settings/settings.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/gatt.h>
+
+#include "mesh.h"
+#include "board.h"
+
+static const struct bt_data ad[] = {
+	BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
+};
+
+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 ssize_t read_name(struct bt_conn *conn, const struct bt_gatt_attr *attr,
+			 void *buf, u16_t len, u16_t offset)
+{
+	const char *value = bt_get_name();
+
+	return bt_gatt_attr_read(conn, attr, buf, len, offset, value,
+				 strlen(value));
+}
+
+static ssize_t write_name(struct bt_conn *conn, const struct bt_gatt_attr *attr,
+			  const void *buf, u16_t len, u16_t offset,
+			  u8_t flags)
+{
+	char name[CONFIG_BT_DEVICE_NAME_MAX];
+	int err;
+
+	if (offset) {
+		return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
+	}
+
+	if (len >= CONFIG_BT_DEVICE_NAME_MAX) {
+		return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
+	}
+
+	memcpy(name, buf, len);
+	name[len] = '\0';
+
+	err = bt_set_name(name);
+	if (err) {
+		return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES);
+	}
+
+	mesh_set_name(name, first_name_len(name));
+	board_refresh_display();
+
+	return len;
+}
+
+static struct bt_uuid_128 name_uuid = BT_UUID_INIT_128(
+	0xf0, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,
+	0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
+
+static struct bt_uuid_128 name_enc_uuid = BT_UUID_INIT_128(
+	0xf1, 0xde, 0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12,
+	0x78, 0x56, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12);
+
+#define CPF_FORMAT_UTF8 0x19
+
+static const struct bt_gatt_cpf name_cpf = {
+	.format = CPF_FORMAT_UTF8,
+};
+
+/* Vendor Primary Service Declaration */
+static struct bt_gatt_attr name_attrs[] = {
+	/* Vendor Primary Service Declaration */
+	BT_GATT_PRIMARY_SERVICE(&name_uuid),
+	BT_GATT_CHARACTERISTIC(&name_enc_uuid.uuid,
+			       BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
+			       BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT,
+			       read_name, write_name, NULL),
+	BT_GATT_CUD("Badge Name", BT_GATT_PERM_READ),
+	BT_GATT_CPF(&name_cpf),
+};
+
+static struct bt_gatt_service name_svc = BT_GATT_SERVICE(name_attrs);
+
+static void passkey_display(struct bt_conn *conn, unsigned int passkey)
+{
+	char buf[20];
+
+	snprintk(buf, sizeof(buf), "Passkey:\n%06u", passkey);
+
+	printk("%s\n", buf);
+	board_show_text(buf, false, K_FOREVER);
+}
+
+static void passkey_cancel(struct bt_conn *conn)
+{
+	printk("Cancel\n");
+}
+
+static void pairing_complete(struct bt_conn *conn, bool bonded)
+{
+	printk("Pairing Complete\n");
+	board_show_text("Pairing Complete", false, K_SECONDS(2));
+}
+
+static void pairing_failed(struct bt_conn *conn)
+{
+	printk("Pairing Failed\n");
+	board_show_text("Pairing Failed", false, K_SECONDS(2));
+}
+
+const struct bt_conn_auth_cb auth_cb = {
+	.passkey_display = passkey_display,
+	.cancel = passkey_cancel,
+	.pairing_complete = pairing_complete,
+	.pairing_failed = pairing_failed,
+};
+
+static void connected(struct bt_conn *conn, u8_t err)
+{
+	printk("Connected (err 0x%02x)\n", err);
+
+	if (err) {
+		board_show_text("Connection failed", false, K_SECONDS(2));
+	} else {
+		board_show_text("Connected", false, K_FOREVER);
+	}
+}
+
+static void disconnected(struct bt_conn *conn, u8_t reason)
+{
+	printk("Disconnected (reason 0x%02x)\n", reason);
+
+	if (strcmp(CONFIG_BT_DEVICE_NAME, bt_get_name()) &&
+	    !mesh_is_initialized()) {
+		/* Mesh will take over advertising control */
+		bt_le_adv_stop();
+		mesh_start();
+	} else {
+		board_show_text("Disconnected", false, K_SECONDS(2));
+	}
+}
+
+static struct bt_conn_cb conn_cb = {
+	.connected = connected,
+	.disconnected = disconnected,
+};
+
+static void bt_ready(int err)
+{
+	if (err) {
+		printk("Bluetooth init failed (err %d)\n", err);
+		return;
+	}
+
+	printk("Bluetooth initialized\n");
+
+	err = mesh_init();
+	if (err) {
+		printk("Initializing mesh failed (err %d)\n", err);
+		return;
+	}
+
+	printk("Mesh initialized\n");
+
+	bt_conn_cb_register(&conn_cb);
+	bt_conn_auth_cb_register(&auth_cb);
+
+	bt_gatt_service_register(&name_svc);
+
+	if (IS_ENABLED(CONFIG_SETTINGS)) {
+		settings_load();
+	}
+
+	if (!mesh_is_initialized()) {
+		/* Start advertising */
+		err = bt_le_adv_start(BT_LE_ADV_CONN_NAME,
+				      ad, ARRAY_SIZE(ad), NULL, 0);
+		if (err) {
+			printk("Advertising failed to start (err %d)\n", err);
+			return;
+		}
+	} else {
+		const char *name = bt_get_name();
+
+		printk("Already provisioned\n");
+
+		mesh_set_name(name, first_name_len(name));
+	}
+
+	board_refresh_display();
+
+	printk("Board started\n");
+}
+
+void main(void)
+{
+	int err;
+
+	err = board_init();
+	if (err) {
+		printk("board init failed (err %d)\n", err);
+		return;
+	}
+
+	printk("Starting Board Demo\n");
+
+	/* Initialize the Bluetooth Subsystem */
+	err = bt_enable(bt_ready);
+	if (err) {
+		printk("Bluetooth init failed (err %d)\n", err);
+		return;
+	}
+}
diff --git a/apps/mesh_badge/src/mesh.c b/apps/mesh_badge/src/mesh.c
new file mode 100644
index 0000000..aa4b8a7
--- /dev/null
+++ b/apps/mesh_badge/src/mesh.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr.h>
+#include <string.h>
+#include <misc/printk.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/mesh.h>
+#include <bluetooth/hci.h>
+
+#include "mesh.h"
+#include "board.h"
+
+#define MOD_LF         0x0000
+#define OP_LF          0xbb
+#define OP_VENDOR_MSG  BT_MESH_MODEL_OP_3(OP_LF, BT_COMP_ID_LF)
+
+#define DEFAULT_TTL    7
+#define GROUP_ADDR     0xc123
+#define NET_IDX        0x000
+#define APP_IDX        0x000
+#define FLAGS          0
+
+static struct k_work publish_work;
+static struct k_work mesh_start_work;
+
+static void heartbeat(u8_t hops, u16_t feat)
+{
+	board_show_text("Heartbeat Received", false, K_SECONDS(2));
+}
+
+static struct bt_mesh_cfg_srv cfg_srv = {
+	.relay = BT_MESH_RELAY_ENABLED,
+	.beacon = BT_MESH_BEACON_ENABLED,
+	.default_ttl = DEFAULT_TTL,
+
+	/* 3 transmissions with 20ms interval */
+	.net_transmit = BT_MESH_TRANSMIT(2, 20),
+	.relay_retransmit = BT_MESH_TRANSMIT(3, 20),
+
+	.hb_sub.func = 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,
+};
+
+BT_MESH_HEALTH_PUB_DEFINE(health_pub, 0);
+
+static struct bt_mesh_model root_models[] = {
+	BT_MESH_MODEL_CFG_SRV(&cfg_srv),
+	BT_MESH_MODEL_CFG_CLI(&cfg_cli),
+	BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
+};
+
+static void vnd_message(struct bt_mesh_model *model,
+			struct bt_mesh_msg_ctx *ctx,
+			struct net_buf_simple *buf)
+{
+	char str[32];
+	size_t len;
+
+	printk("Vendor message from 0x%04x\n", ctx->addr);
+
+	if (ctx->addr == bt_mesh_model_elem(model)->addr) {
+		printk("Ignoring message from self\n");
+		return;
+	}
+
+	len = min(buf->len, 8);
+	memcpy(str, buf->data, len);
+	strcpy(str + len, " says hi!");
+
+	board_show_text(str, false, K_SECONDS(3));
+
+	board_blink_leds();
+}
+
+static const struct bt_mesh_model_op vnd_ops[] = {
+	{ OP_VENDOR_MSG, 1, vnd_message },
+	BT_MESH_MODEL_OP_END,
+};
+
+/* Limit the payload to 11 bytes so that it doesn't get segmented */
+BT_MESH_MODEL_PUB_DEFINE(vnd_pub, NULL, 3 + 8);
+
+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 void publish_message(struct k_work *work)
+{
+	if (bt_mesh_model_publish(&vnd_models[0]) == 0) {
+		board_show_text("Saying \"hi!\" to everyone", false,
+				K_SECONDS(2));
+	} else {
+		board_show_text("Sending Failed!", false, K_SECONDS(2));
+	}
+}
+
+void mesh_publish(void)
+{
+	k_work_submit(&publish_work);
+}
+
+static int provision_and_configure(void)
+{
+	static const u8_t net_key[16] = {
+		0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
+		0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
+	};
+	static const u8_t app_key[16] = {
+		0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+		0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+	};
+	static const u16_t iv_index;
+	struct bt_mesh_cfg_mod_pub pub = {
+		.addr = GROUP_ADDR,
+		.app_idx = APP_IDX,
+		.ttl = DEFAULT_TTL,
+	};
+	u8_t dev_key[16];
+	u16_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);
+
+	/* 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);
+}
+
+void mesh_set_name(const char *name, size_t len)
+{
+	bt_mesh_model_msg_init(vnd_pub.msg, OP_VENDOR_MSG);
+	len = min(len, net_buf_simple_tailroom(vnd_pub.msg));
+	net_buf_simple_add_mem(vnd_pub.msg, name, len);
+}
+
+bool mesh_is_initialized(void)
+{
+	return elements[0].addr != BT_MESH_ADDR_UNASSIGNED;
+}
+
+int mesh_init(void)
+{
+	static const u8_t dev_uuid[16] = { 0xc0, 0xff, 0xee };
+	static const struct bt_mesh_prov prov = {
+		.uuid = dev_uuid,
+	};
+
+	k_work_init(&publish_work, publish_message);
+	k_work_init(&mesh_start_work, start_mesh);
+
+	return bt_mesh_init(&prov, &comp);
+}
diff --git a/apps/mesh_badge/src/mesh.h b/apps/mesh_badge/src/mesh.h
new file mode 100644
index 0000000..951771b
--- /dev/null
+++ b/apps/mesh_badge/src/mesh.h
@@ -0,0 +1,12 @@
+/*
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+void mesh_publish(void);
+void mesh_set_name(const char *name, size_t len);
+
+bool mesh_is_initialized(void);
+void mesh_start(void);
+int mesh_init(void);
diff --git a/apps/mesh_badge/src/reel_board.c b/apps/mesh_badge/src/reel_board.c
new file mode 100644
index 0000000..ca5809f
--- /dev/null
+++ b/apps/mesh_badge/src/reel_board.c
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2018 Phytec Messtechnik GmbH
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr.h>
+#include <device.h>
+#include <gpio.h>
+#include <display/cfb.h>
+#include <misc/printk.h>
+#include <flash.h>
+
+#include <string.h>
+
+#include <bluetooth/bluetooth.h>
+
+#include "mesh.h"
+#include "board.h"
+
+#define EDGE (GPIO_INT_EDGE | GPIO_INT_DOUBLE_EDGE)
+
+#ifdef SW0_GPIO_FLAGS
+#define PULL_UP SW0_GPIO_FLAGS
+#else
+#define PULL_UP 0
+#endif
+
+#define LINE_MAX 12
+
+static struct device *epd_dev;
+static bool pressed;
+static struct device *gpio;
+static struct k_delayed_work epd_work;
+
+static struct {
+	struct device *dev;
+	const char *name;
+	u32_t pin;
+} leds[] = {
+	{ .name = LED0_GPIO_CONTROLLER, .pin = LED0_GPIO_PIN, },
+	{ .name = LED1_GPIO_CONTROLLER, .pin = LED1_GPIO_PIN, },
+	{ .name = LED2_GPIO_CONTROLLER, .pin = LED2_GPIO_PIN, },
+	{ .name = LED3_GPIO_CONTROLLER, .pin = LED3_GPIO_PIN, },
+};
+
+struct k_delayed_work led_timer;
+
+static size_t print_line(int row, const char *text, size_t len, bool center)
+{
+	u8_t font_height, font_width;
+	u8_t line[LINE_MAX + 1];
+	int pad;
+
+	len = min(len, LINE_MAX);
+	memcpy(line, text, len);
+	line[len] = '\0';
+
+	if (center) {
+		pad = (LINE_MAX - len) / 2;
+	} else {
+		pad = 0;
+	}
+
+	cfb_get_font_size(epd_dev, 0, &font_width, &font_height);
+
+	if (cfb_print(epd_dev, line, font_width * pad, font_height * row)) {
+		printk("Failed to print a string\n");
+	}
+
+	return len;
+}
+
+static size_t get_len(const char *text)
+{
+	const char *space = NULL;
+	size_t i;
+
+	for (i = 0; i <= LINE_MAX; i++) {
+		switch (text[i]) {
+		case '\n':
+		case '\0':
+			return i;
+		case ' ':
+			space = &text[i];
+			break;
+		default:
+			continue;
+		}
+	}
+
+	/* If we got more characters than fits a line, and a space was
+	 * encountered, fall back to the last space.
+	 */
+	if (space) {
+		return space - text;
+	}
+
+	return LINE_MAX;
+}
+
+void board_blink_leds(void)
+{
+	k_delayed_work_submit(&led_timer, K_MSEC(100));
+}
+
+void board_show_text(const char *text, bool center, s32_t duration)
+{
+	int i;
+
+	cfb_framebuffer_set_font(epd_dev, 0);
+	cfb_framebuffer_clear(epd_dev, false);
+
+	for (i = 0; i < 3; i++) {
+		size_t len;
+
+		while (*text == ' ' || *text == '\n') {
+			text++;
+		}
+
+		len = get_len(text);
+		if (!len) {
+			break;
+		}
+
+		text += print_line(i, text, len, center);
+		if (!*text) {
+			break;
+		}
+	}
+
+	cfb_framebuffer_finalize(epd_dev);
+
+	if (duration != K_FOREVER) {
+		k_delayed_work_submit(&epd_work, duration);
+	}
+}
+
+static void epd_update(struct k_work *work)
+{
+	char buf[CONFIG_BT_DEVICE_NAME_MAX];
+	int i;
+
+	strncpy(buf, bt_get_name(), sizeof(buf));
+
+	/* Convert commas to newlines */
+	for (i = 0; buf[i] != '\0'; i++) {
+		if (buf[i] == ',') {
+			buf[i] = '\n';
+		}
+	}
+
+	board_show_text(buf, true, K_FOREVER);
+}
+
+static bool button_is_pressed(void)
+{
+	u32_t val;
+
+	gpio_pin_read(gpio, SW0_GPIO_PIN, &val);
+
+	return !val;
+}
+
+static void button_interrupt(struct device *dev, struct gpio_callback *cb,
+			     u32_t pins)
+{
+	if (button_is_pressed() == pressed) {
+		return;
+	}
+
+	pressed = !pressed;
+	printk("Button %s\n", pressed ? "pressed" : "released");
+
+	/* We only care about button release for now */
+	if (pressed) {
+		return;
+	}
+
+	if (!mesh_is_initialized()) {
+		return;
+	}
+
+	if (pins & BIT(SW0_GPIO_PIN)) {
+		mesh_publish();
+	}
+}
+
+static int configure_button(void)
+{
+	static struct gpio_callback button_cb;
+
+	gpio = device_get_binding(GPIO_KEYS_BUTTON_0_GPIO_CONTROLLER);
+	if (!gpio) {
+		return -ENODEV;
+	}
+
+	gpio_pin_configure(gpio, SW0_GPIO_PIN,
+			   (GPIO_DIR_IN | GPIO_INT |  PULL_UP | EDGE));
+
+	gpio_init_callback(&button_cb, button_interrupt, BIT(SW0_GPIO_PIN));
+	gpio_add_callback(gpio, &button_cb);
+
+	gpio_pin_enable_callback(gpio, SW0_GPIO_PIN);
+
+	return 0;
+}
+
+static void led_timeout(struct k_work *work)
+{
+	static int led_cntr;
+	int i;
+
+	/* Disable all LEDs */
+	for (i = 0; i < ARRAY_SIZE(leds); i++) {
+		gpio_pin_write(leds[i].dev, leds[i].pin, 1);
+	}
+
+	/* Stop after 5 iterations */
+	if (led_cntr > (ARRAY_SIZE(leds) * 5)) {
+		led_cntr = 0;
+		return;
+	}
+
+	/* Select and enable current LED */
+	i = led_cntr++ % ARRAY_SIZE(leds);
+	gpio_pin_write(leds[i].dev, leds[i].pin, 0);
+
+	k_delayed_work_submit(&led_timer, K_MSEC(100));
+}
+
+static int configure_leds(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(leds); i++) {
+		leds[i].dev = device_get_binding(leds[i].name);
+		if (!leds[i].dev) {
+			printk("Failed to get %s device\n", leds[i].name);
+			return -ENODEV;
+		}
+
+		gpio_pin_configure(leds[i].dev, leds[i].pin, GPIO_DIR_OUT);
+		gpio_pin_write(leds[i].dev, leds[i].pin, 1);
+
+	}
+
+	k_delayed_work_init(&led_timer, led_timeout);
+	return 0;
+}
+
+static int erase_storage(void)
+{
+	struct device *dev;
+
+	dev = device_get_binding(FLASH_DEV_NAME);
+
+	return flash_erase(dev, FLASH_AREA_STORAGE_OFFSET,
+			   FLASH_AREA_STORAGE_SIZE);
+}
+
+void board_refresh_display(void)
+{
+	k_delayed_work_submit(&epd_work, K_NO_WAIT);
+}
+
+int board_init(void)
+{
+	epd_dev = device_get_binding(CONFIG_SSD1673_DEV_NAME);
+	if (epd_dev == NULL) {
+		printk("SSD1673 device not found\n");
+		return -ENODEV;
+	}
+
+	if (cfb_framebuffer_init(epd_dev)) {
+		printk("Framebuffer initialization failed\n");
+		return -EIO;
+	}
+
+	cfb_framebuffer_clear(epd_dev, true);
+
+	if (configure_button()) {
+		printk("Failed to configure button\n");
+		return -EIO;
+	}
+
+	if (configure_leds()) {
+		printk("LED init failed\n");
+		return -EIO;
+	}
+
+	k_delayed_work_init(&epd_work, epd_update);
+
+	pressed = button_is_pressed();
+	if (pressed) {
+		printk("Erasing storage\n");
+		board_show_text("Resetting Device", false, K_SECONDS(4));
+		erase_storage();
+	}
+
+	return 0;
+}