summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPete Johanson <peter@peterjohanson.com>2020-07-20 23:14:48 -0400
committerGitHub <noreply@github.com>2020-07-20 23:14:48 -0400
commit25509adef7fe36e1fde794851922ae07216046ab (patch)
treeb00dbd6bb2237640e1fca2d77cd027170c8a1c93
parentf6110a632d0ddbc0a9ebd7bfd4997366f95facfd (diff)
parentc4d3c03eb069c814321ab49a809dc584f1819b34 (diff)
Merge pull request #56 from petejohanson/core/split-ble-infrastructure
Initial split BLE infrastructure.
-rw-r--r--app/CMakeLists.txt3
-rw-r--r--app/Kconfig70
-rw-r--r--app/boards/arm/nice_nano/nice_nano_defconfig7
-rw-r--r--app/boards/shields/kyria/Kconfig.defconfig19
-rw-r--r--app/boards/shields/kyria/kyria_left.conf2
-rw-r--r--app/boards/shields/kyria/kyria_right.conf2
-rw-r--r--app/boards/shields/lily58/Kconfig.defconfig11
-rw-r--r--app/boards/shields/lily58/lily58_left.conf2
-rw-r--r--app/boards/shields/lily58/lily58_right.conf2
-rw-r--r--app/include/zmk/split/bluetooth/service.h4
-rw-r--r--app/include/zmk/split/bluetooth/uuid.h12
-rw-r--r--app/src/ble.c33
-rw-r--r--app/src/main.c5
-rw-r--r--app/src/split/bluetooth/central.c301
-rw-r--r--app/src/split/bluetooth/service.c50
-rw-r--r--app/src/split_listener.c36
16 files changed, 533 insertions, 26 deletions
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index 5be628b..1cee6b3 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -42,6 +42,9 @@ target_sources(app PRIVATE src/behaviors/behavior_mod_tap.c)
target_sources(app PRIVATE src/behaviors/behavior_momentary_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_transparent.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c)
+target_sources_ifdef(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL app PRIVATE src/split_listener.c)
+target_sources_ifdef(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL app PRIVATE src/split/bluetooth/service.c)
+target_sources_ifdef(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL app PRIVATE src/split/bluetooth/central.c)
target_sources_ifdef(CONFIG_ZMK_KSCAN_MOCK_DRIVER app PRIVATE src/kscan_mock.c)
target_sources_ifdef(CONFIG_ZMK_KSCAN_COMPOSITE_DRIVER app PRIVATE src/kscan_composite.c)
target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c)
diff --git a/app/Kconfig b/app/Kconfig
index e6dc3bd..997409e 100644
--- a/app/Kconfig
+++ b/app/Kconfig
@@ -40,6 +40,8 @@ menuconfig ZMK_BLE
select BT_PERIPHERAL
select BT_GATT_DIS
select BT_GATT_BAS
+ select SETTINGS
+ select BT_SETTINGS
if ZMK_BLE
@@ -47,6 +49,9 @@ config ZMK_BLE_INIT_PRIORITY
int "Init Priority"
default 50
+config SYSTEM_WORKQUEUE_STACK_SIZE
+ default 2048
+
# HID GATT notifications sent this way are *not* picked up by Linux, and possibly others.
config BT_GATT_NOTIFY_MULTIPLE
default n
@@ -58,20 +63,65 @@ config ZMK_BLE_PASSKEY_ENTRY
bool "Experimental: Requiring typing passkey from host to pair BLE connection"
default n
-# Incresed stack due to settings API usage
-# CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
-#
-# CONFIG_BT_SETTINGS=y
-# CONFIG_FLASH=y
-# CONFIG_FLASH_PAGE_LAYOUT=y
-# CONFIG_FLASH_MAP=y
-# CONFIG_NVS=y
-# CONFIG_SETTINGS=y
+endif
+
+endmenu
+
+
+menu "Split Support"
+
+config ZMK_SPLIT
+ bool "Split keyboard support"
+ default n
+
+if ZMK_SPLIT
+
+config ZMK_SPLIT_BLE
+ bool "Split keyboard support via BLE transport"
+ depends on ZMK_BLE
+ default y
+
+if ZMK_SPLIT_BLE
+
+choice ZMK_SPLIT_BLE_ROLE
+ bool "BLE Role For Split Communication"
+ default ZMK_SPLIT_BLE_ROLE_CENTRAL
+
+config ZMK_SPLIT_BLE_ROLE_CENTRAL
+ bool "Central"
+ select BT_CENTRAL
+ select BT_GATT_CLIENT
+
+if ZMK_SPLIT_BLE_ROLE_CENTRAL
+
+config BT_MAX_CONN
+ default 5
+
+endif
+
+config ZMK_SPLIT_BLE_ROLE_PERIPHERAL
+ bool "Peripheral"
+
+if ZMK_SPLIT_BLE_ROLE_PERIPHERAL
+
+config ZMK_USB
+ default n
+
+
+config BT_MAX_CONN
+ default 5
endif
+endchoice
+
+endif
+
+endif
+
endmenu
+
config ZMK_KSCAN_MOCK_DRIVER
bool "Enable mock kscan driver to simulate key presses"
default n
@@ -89,7 +139,7 @@ config ZMK_ACTION_MOD_TAP
endmenu
config HEAP_MEM_POOL_SIZE
- default 200
+ default 1024
module = ZMK
module-str = zmk
diff --git a/app/boards/arm/nice_nano/nice_nano_defconfig b/app/boards/arm/nice_nano/nice_nano_defconfig
index f9be0ee..393d61f 100644
--- a/app/boards/arm/nice_nano/nice_nano_defconfig
+++ b/app/boards/arm/nice_nano/nice_nano_defconfig
@@ -11,3 +11,10 @@ CONFIG_ARM_MPU=y
CONFIG_GPIO=y
CONFIG_USE_DT_CODE_PARTITION=y
+
+CONFIG_MPU_ALLOW_FLASH_WRITE=y
+CONFIG_NVS=y
+CONFIG_SETTINGS_NVS=y
+CONFIG_FLASH=y
+CONFIG_FLASH_PAGE_LAYOUT=y
+CONFIG_FLASH_MAP=y \ No newline at end of file
diff --git a/app/boards/shields/kyria/Kconfig.defconfig b/app/boards/shields/kyria/Kconfig.defconfig
index 25af537..bc0a7b8 100644
--- a/app/boards/shields/kyria/Kconfig.defconfig
+++ b/app/boards/shields/kyria/Kconfig.defconfig
@@ -1,7 +1,22 @@
-if SHIELD_KYRIA_LEFT || SHIELD_KYRIA_RIGHT
+if SHIELD_KYRIA_LEFT
+
+config ZMK_KEYBOARD_NAME
+ default "Kyria Left"
+
+endif
+
+
+if SHIELD_KYRIA_RIGHT
config ZMK_KEYBOARD_NAME
- default "Kyria"
+ default "Kyria Right"
+
+endif
+
+if SHIELD_KYRIA_LEFT || SHIELD_KYRIA_RIGHT
+
+config ZMK_SPLIT
+ default y
endif
diff --git a/app/boards/shields/kyria/kyria_left.conf b/app/boards/shields/kyria/kyria_left.conf
new file mode 100644
index 0000000..e51dee4
--- /dev/null
+++ b/app/boards/shields/kyria/kyria_left.conf
@@ -0,0 +1,2 @@
+CONFIG_ZMK_SPLIT=y
+CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL=y \ No newline at end of file
diff --git a/app/boards/shields/kyria/kyria_right.conf b/app/boards/shields/kyria/kyria_right.conf
new file mode 100644
index 0000000..a835adc
--- /dev/null
+++ b/app/boards/shields/kyria/kyria_right.conf
@@ -0,0 +1,2 @@
+CONFIG_ZMK_SPLIT=y
+CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL=y \ No newline at end of file
diff --git a/app/boards/shields/lily58/Kconfig.defconfig b/app/boards/shields/lily58/Kconfig.defconfig
index 9ec5834..8f278b0 100644
--- a/app/boards/shields/lily58/Kconfig.defconfig
+++ b/app/boards/shields/lily58/Kconfig.defconfig
@@ -1,7 +1,14 @@
-if SHIELD_LILY58_LEFT || SHIELD_LILY58_RIGHT
+if SHIELD_LILY58_LEFT
config ZMK_KEYBOARD_NAME
- default "Lily58"
+ default "Lily58 Left"
+
+endif
+
+if SHIELD_LILY58_RIGHT
+
+config ZMK_KEYBOARD_NAME
+ default "Lily58 Right"
endif
diff --git a/app/boards/shields/lily58/lily58_left.conf b/app/boards/shields/lily58/lily58_left.conf
new file mode 100644
index 0000000..e51dee4
--- /dev/null
+++ b/app/boards/shields/lily58/lily58_left.conf
@@ -0,0 +1,2 @@
+CONFIG_ZMK_SPLIT=y
+CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL=y \ No newline at end of file
diff --git a/app/boards/shields/lily58/lily58_right.conf b/app/boards/shields/lily58/lily58_right.conf
new file mode 100644
index 0000000..990cf7c
--- /dev/null
+++ b/app/boards/shields/lily58/lily58_right.conf
@@ -0,0 +1,2 @@
+CONFIG_ZMK_SPLIT=y
+CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL=y
diff --git a/app/include/zmk/split/bluetooth/service.h b/app/include/zmk/split/bluetooth/service.h
new file mode 100644
index 0000000..954e0cd
--- /dev/null
+++ b/app/include/zmk/split/bluetooth/service.h
@@ -0,0 +1,4 @@
+#pragma once
+
+int zmk_split_bt_position_pressed(u8_t position);
+int zmk_split_bt_position_released(u8_t position); \ No newline at end of file
diff --git a/app/include/zmk/split/bluetooth/uuid.h b/app/include/zmk/split/bluetooth/uuid.h
new file mode 100644
index 0000000..59f2f71
--- /dev/null
+++ b/app/include/zmk/split/bluetooth/uuid.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <bluetooth/uuid.h>
+
+#ifndef BT_UUID_NUM_OF_DIGITALS
+#define BT_UUID_NUM_OF_DIGITALS BT_UUID_DECLARE_16(0x2909)
+#endif
+
+#define ZMK_BT_SPLIT_UUID(num) BT_UUID_128_ENCODE(num, 0x0096, 0x7107, 0xc967, 0xc5cfb1c2482a)
+#define ZMK_SPLIT_BT_SERVICE_UUID ZMK_BT_SPLIT_UUID(0x00000000)
+#define ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID ZMK_BT_SPLIT_UUID(0x00000001)
+
diff --git a/app/src/ble.c b/app/src/ble.c
index c0e81a9..af3c25d 100644
--- a/app/src/ble.c
+++ b/app/src/ble.c
@@ -11,7 +11,13 @@
#include <bluetooth/uuid.h>
#include <bluetooth/gatt.h>
+
+#include <logging/log.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
#include <zmk/keys.h>
+#include <zmk/split/bluetooth/uuid.h>
static struct bt_conn *auth_passkey_entry_conn;
static u8_t passkey_entries[6] = {0, 0, 0, 0, 0, 0};
@@ -31,6 +37,8 @@ static void connected(struct bt_conn *conn, u8_t err)
printk("Connected %s\n", addr);
+ bt_conn_le_param_update(conn, BT_LE_CONN_PARAM(0x0006, 0x000c, 5, 400));
+
if (bt_conn_set_security(conn, BT_SECURITY_L2))
{
printk("Failed to set security\n");
@@ -121,13 +129,21 @@ static struct bt_conn_auth_cb zmk_ble_auth_cb_display = {
static const struct bt_data zmk_ble_ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
- BT_DATA_BYTES(BT_DATA_UUID16_ALL,
+ BT_DATA_BYTES(BT_DATA_UUID16_SOME,
+#if !IS_ENABLED(CONFIG_ZMK_SPLIT_BLE)
0x12, 0x18, /* HID Service */
- 0x0f, 0x18), /* Battery Service */
+#endif
+ 0x0f, 0x18 /* Battery Service */
+ ),
+#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE)
+ BT_DATA_BYTES(BT_DATA_UUID128_ALL,
+ ZMK_SPLIT_BT_SERVICE_UUID)
+#endif
};
static void zmk_ble_ready(int err)
{
+ LOG_DBG("ready? %d", err);
if (err)
{
printk("Bluetooth init failed (err %d)\n", err);
@@ -144,11 +160,7 @@ static void zmk_ble_ready(int err)
static int zmk_ble_init(struct device *_arg)
{
- if (IS_ENABLED(CONFIG_SETTINGS))
- {
- settings_load();
- }
- int err = bt_enable(zmk_ble_ready);
+ int err = bt_enable(NULL);
if (err)
{
@@ -156,9 +168,16 @@ static int zmk_ble_init(struct device *_arg)
return err;
}
+ if (IS_ENABLED(CONFIG_BT_SETTINGS))
+ {
+ settings_load();
+ }
+
bt_conn_cb_register(&conn_callbacks);
bt_conn_auth_cb_register(&zmk_ble_auth_cb_display);
+ zmk_ble_ready(0);
+
return 0;
}
diff --git a/app/src/main.c b/app/src/main.c
index 92ecc8b..e755304 100644
--- a/app/src/main.c
+++ b/app/src/main.c
@@ -26,9 +26,4 @@ void main(void)
{
return;
}
-
-
-#ifdef CONFIG_SETTINGS
- settings_load();
-#endif
}
diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c
new file mode 100644
index 0000000..b6d7222
--- /dev/null
+++ b/app/src/split/bluetooth/central.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2020 Peter Johanson
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#include <zephyr/types.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/conn.h>
+#include <bluetooth/uuid.h>
+#include <bluetooth/gatt.h>
+#include <sys/byteorder.h>
+
+#include <logging/log.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#include <zmk/split/bluetooth/uuid.h>
+#include <zmk/event-manager.h>
+#include <zmk/events/position-state-changed.h>
+#include <init.h>
+
+static int start_scan(void);
+
+#define POSITION_STATE_DATA_LEN 16
+
+static struct bt_conn *default_conn;
+
+static struct bt_uuid_128 uuid = BT_UUID_INIT_128(ZMK_SPLIT_BT_SERVICE_UUID);
+static struct bt_gatt_discover_params discover_params;
+static struct bt_gatt_subscribe_params subscribe_params;
+
+static u8_t split_central_notify_func(struct bt_conn *conn,
+ struct bt_gatt_subscribe_params *params,
+ const void *data, u16_t length)
+{
+ static u8_t position_state[POSITION_STATE_DATA_LEN];
+
+ u8_t changed_positions[POSITION_STATE_DATA_LEN];
+
+ if (!data) {
+ LOG_DBG("[UNSUBSCRIBED]");
+ params->value_handle = 0U;
+ return BT_GATT_ITER_STOP;
+ }
+
+ LOG_DBG("[NOTIFICATION] data %p length %u", data, length);
+
+ for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) {
+ changed_positions[i] = ((u8_t *)data)[i] ^ position_state[i];
+ position_state[i] = ((u8_t *)data)[i];
+ }
+
+ for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) {
+ for (int j = 0; j < 8; j++) {
+ if (changed_positions[i] & BIT(j)) {
+ u32_t position = (i * 8) + j;
+ bool pressed = position_state[i] & BIT(j);
+ struct position_state_changed *pos_ev = new_position_state_changed();
+ pos_ev->position = position;
+ pos_ev->state = pressed;
+
+ LOG_DBG("Trigger key position state change for %d", position);
+ ZMK_EVENT_RAISE(pos_ev);
+ }
+ }
+ }
+
+
+ return BT_GATT_ITER_CONTINUE;
+}
+
+static u8_t split_central_discovery_func(struct bt_conn *conn,
+ const struct bt_gatt_attr *attr,
+ struct bt_gatt_discover_params *params)
+{
+ int err;
+
+ if (!attr) {
+ LOG_DBG("Discover complete");
+ (void)memset(params, 0, sizeof(*params));
+ return BT_GATT_ITER_STOP;
+ }
+
+ LOG_DBG("[ATTRIBUTE] handle %u", attr->handle);
+
+ if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SERVICE_UUID))) {
+ memcpy(&uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID), sizeof(uuid));
+ discover_params.uuid = &uuid.uuid;
+ discover_params.start_handle = attr->handle + 1;
+ discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
+
+ err = bt_gatt_discover(conn, &discover_params);
+ if (err) {
+ LOG_ERR("Discover failed (err %d)", err);
+ }
+ } else if (!bt_uuid_cmp(discover_params.uuid,
+ BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID))) {
+ memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid));
+ discover_params.uuid = &uuid.uuid;
+ discover_params.start_handle = attr->handle + 2;
+ discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR;
+ subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);
+
+ err = bt_gatt_discover(conn, &discover_params);
+ if (err) {
+ LOG_ERR("Discover failed (err %d)", err);
+ }
+ } else {
+ subscribe_params.notify = split_central_notify_func;
+ subscribe_params.value = BT_GATT_CCC_NOTIFY;
+ subscribe_params.ccc_handle = attr->handle;
+
+ err = bt_gatt_subscribe(conn, &subscribe_params);
+ if (err && err != -EALREADY) {
+ LOG_ERR("Subscribe failed (err %d)", err);
+ } else {
+ LOG_DBG("[SUBSCRIBED]");
+ }
+
+ return BT_GATT_ITER_STOP;
+ }
+
+ return BT_GATT_ITER_STOP;
+}
+
+static void split_central_process_connection(struct bt_conn *conn) {
+ int err;
+
+ LOG_DBG("Current security for connection: %d", bt_conn_get_security(conn));
+
+ err = bt_conn_set_security(conn, BT_SECURITY_L2);
+ if (err) {
+ LOG_ERR("Failed to set security (reason %d)", err);
+ return;
+ }
+
+ if (conn == default_conn) {
+ discover_params.uuid = &uuid.uuid;
+ discover_params.func = split_central_discovery_func;
+ discover_params.start_handle = 0x0001;
+ discover_params.end_handle = 0xffff;
+ discover_params.type = BT_GATT_DISCOVER_PRIMARY;
+
+ err = bt_gatt_discover(default_conn, &discover_params);
+ if (err) {
+ LOG_ERR("Discover failed(err %d)", err);
+ return;
+ }
+ }
+}
+
+static bool split_central_eir_found(struct bt_data *data, void *user_data)
+{
+ bt_addr_le_t *addr = user_data;
+ int i;
+
+ LOG_DBG("[AD]: %u data_len %u", data->type, data->data_len);
+
+ switch (data->type) {
+ case BT_DATA_UUID128_SOME:
+ case BT_DATA_UUID128_ALL:
+ if (data->data_len % 16 != 0U) {
+ LOG_ERR("AD malformed");
+ return true;
+ }
+
+ for (i = 0; i < data->data_len; i += 16) {
+ struct bt_le_conn_param *param;
+ struct bt_uuid uuid;
+ int err;
+
+ if (!bt_uuid_create(&uuid, &data->data[i], 16)) {
+ LOG_ERR("Unable to load UUID");
+ continue;
+ }
+
+ if (!bt_uuid_cmp(&uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SERVICE_UUID))) {
+ char uuid_str[BT_UUID_STR_LEN];
+ char service_uuid_str[BT_UUID_STR_LEN];
+
+ bt_uuid_to_str(&uuid, uuid_str, sizeof(uuid_str));
+ bt_uuid_to_str(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SERVICE_UUID), service_uuid_str, sizeof(service_uuid_str));
+ LOG_DBG("UUID %s does not match split UUID: %s", log_strdup(uuid_str), log_strdup(service_uuid_str));
+ continue;
+ }
+
+ LOG_DBG("Found the split service");
+
+ err = bt_le_scan_stop();
+ if (err) {
+ LOG_ERR("Stop LE scan failed (err %d)", err);
+ continue;
+ }
+
+ default_conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr);
+ if (default_conn) {
+ LOG_DBG("Found existing connection");
+ split_central_process_connection(default_conn);
+ } else {
+ param = BT_LE_CONN_PARAM(0x0006, 0x000c, 5, 400);
+ err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
+ param, &default_conn);
+ if (err) {
+ LOG_ERR("Create conn failed (err %d)", err);
+ start_scan();
+ }
+ }
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void split_central_device_found(const bt_addr_le_t *addr, s8_t rssi, u8_t type,
+ struct net_buf_simple *ad)
+{
+ char dev[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(addr, dev, sizeof(dev));
+ LOG_DBG("[DEVICE]: %s, AD evt type %u, AD data len %u, RSSI %i",
+ log_strdup(dev), type, ad->len, rssi);
+
+ /* We're only interested in connectable events */
+ if (type == BT_GAP_ADV_TYPE_ADV_IND ||
+ type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND) {
+ bt_data_parse(ad, split_central_eir_found, (void *)addr);
+ }
+}
+
+static int start_scan(void)
+{
+ int err;
+
+ err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, split_central_device_found);
+ if (err) {
+ LOG_ERR("Scanning failed to start (err %d)", err);
+ return err;
+ }
+
+ LOG_DBG("Scanning successfully started");
+ return 0;
+}
+
+static void split_central_connected(struct bt_conn *conn, u8_t conn_err)
+{
+ char addr[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
+
+ if (conn_err) {
+ LOG_ERR("Failed to connect to %s (%u)", addr, conn_err);
+
+ bt_conn_unref(default_conn);
+ default_conn = NULL;
+
+ start_scan();
+ return;
+ }
+
+ LOG_DBG("Connected: %s", log_strdup(addr));
+
+ split_central_process_connection(conn);
+}
+
+static void split_central_disconnected(struct bt_conn *conn, u8_t reason)
+{
+ char addr[BT_ADDR_LE_STR_LEN];
+
+ bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
+
+ LOG_DBG("Disconnected: %s (reason %d)", log_strdup(addr), reason);
+
+ if (default_conn != conn) {
+ return;
+ }
+
+ bt_conn_unref(default_conn);
+ default_conn = NULL;
+
+ start_scan();
+}
+
+static struct bt_conn_cb conn_callbacks = {
+ .connected = split_central_connected,
+ .disconnected = split_central_disconnected,
+};
+
+int zmk_split_bt_central_init(struct device *_arg)
+{
+ bt_conn_cb_register(&conn_callbacks);
+
+ return start_scan();
+}
+
+SYS_INIT(zmk_split_bt_central_init,
+ APPLICATION,
+ CONFIG_ZMK_BLE_INIT_PRIORITY); \ No newline at end of file
diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c
new file mode 100644
index 0000000..e1d232a
--- /dev/null
+++ b/app/src/split/bluetooth/service.c
@@ -0,0 +1,50 @@
+
+#include <zephyr/types.h>
+#include <sys/util.h>
+#include <bluetooth/gatt.h>
+#include <bluetooth/uuid.h>
+
+#include <zmk/matrix.h>
+#include <zmk/split/bluetooth/uuid.h>
+#include <zmk/split/bluetooth/service.h>
+
+static u8_t num_of_positions = ZMK_KEYMAP_LEN;
+static u8_t position_state[16];
+
+static ssize_t split_svc_pos_state(struct bt_conn *conn, const struct bt_gatt_attr *attrs, void *buf, u16_t len, u16_t offset)
+{
+ return bt_gatt_attr_read(conn, attrs, buf, len, offset, &position_state, sizeof(position_state));
+}
+
+static ssize_t split_svc_num_of_positions(struct bt_conn *conn, const struct bt_gatt_attr *attrs, void *buf, u16_t len, u16_t offset)
+{
+ return bt_gatt_attr_read(conn, attrs, buf, len, offset, attrs->user_data, sizeof(u8_t));
+}
+
+static void split_svc_pos_state_ccc(const struct bt_gatt_attr *attr, u16_t value)
+{
+}
+
+
+BT_GATT_SERVICE_DEFINE(split_svc,
+ BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SERVICE_UUID)),
+ BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID), BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY,
+ BT_GATT_PERM_READ_ENCRYPT,
+ split_svc_pos_state, NULL, &position_state),
+ BT_GATT_CCC(split_svc_pos_state_ccc,
+ BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
+ BT_GATT_DESCRIPTOR(BT_UUID_NUM_OF_DIGITALS, BT_GATT_PERM_READ,
+ split_svc_num_of_positions, NULL, &num_of_positions),
+);
+
+int zmk_split_bt_position_pressed(u8_t position)
+{
+ WRITE_BIT(position_state[position / 8], position % 8, true);
+ return bt_gatt_notify(NULL, &split_svc.attrs[1], &position_state, sizeof(position_state));
+}
+
+int zmk_split_bt_position_released(u8_t position)
+{
+ WRITE_BIT(position_state[position / 8], position % 8, false);
+ return bt_gatt_notify(NULL, &split_svc.attrs[1], &position_state, sizeof(position_state));
+} \ No newline at end of file
diff --git a/app/src/split_listener.c b/app/src/split_listener.c
new file mode 100644
index 0000000..65f835a
--- /dev/null
+++ b/app/src/split_listener.c
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2020 Peter Johanson <peter@peterjohanson.com>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#define DT_DRV_COMPAT zmk_split_listener
+
+#include <device.h>
+#include <power/reboot.h>
+#include <logging/log.h>
+
+#include <zmk/split/bluetooth/service.h>
+
+LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
+
+#include <zmk/event-manager.h>
+#include <zmk/events/position-state-changed.h>
+#include <zmk/hid.h>
+#include <zmk/endpoints.h>
+
+int split_listener(const struct zmk_event_header *eh)
+{
+ if (is_position_state_changed(eh)) {
+ const struct position_state_changed *ev = cast_position_state_changed(eh);
+ if (ev->state) {
+ zmk_split_bt_position_pressed(ev->position);
+ } else {
+ zmk_split_bt_position_released(ev->position);
+ }
+ }
+ return 0;
+}
+
+ZMK_LISTENER(split_listener, split_listener);
+ZMK_SUBSCRIPTION(split_listener, position_state_changed); \ No newline at end of file