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;
+}