diff --git a/apps/mesh_badge/CMakeLists.txt b/apps/mesh_badge/CMakeLists.txt new file mode 100644 index 000000000..c5861dd7c --- /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 000000000..f69589687 --- /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 " 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 000000000..206ba5acf --- /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 000000000..6a50f81b1 --- /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 000000000..7842cd40d --- /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 000000000..23fe4140e --- /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 +#include + +#include + +#include + +#include +#include + +#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 000000000..aa4b8a777 --- /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 +#include +#include + +#include +#include +#include + +#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 000000000..951771ba8 --- /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 000000000..ca5809f4c --- /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 +#include +#include +#include +#include +#include + +#include + +#include + +#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; +}