Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(split): sync central & peripherals last activity timing #2459

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ target_sources_ifdef(CONFIG_ZMK_BATTERY_REPORTING app PRIVATE src/battery.c)
target_sources_ifdef(CONFIG_ZMK_HID_INDICATORS app PRIVATE src/events/hid_indicators_changed.c)

target_sources_ifdef(CONFIG_ZMK_SPLIT app PRIVATE src/events/split_peripheral_status_changed.c)
target_sources_ifdef(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC app PRIVATE src/events/sync_activity_event.c)
target_sources_ifdef(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT app PRIVATE src/events/sync_activity_event.c)
add_subdirectory(src/split)

target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c)
Expand Down
16 changes: 16 additions & 0 deletions app/include/zmk/events/sync_activity_event.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#pragma once

#include <zmk/hid_indicators_types.h>
#include <zmk/event_manager.h>

struct zmk_sync_activity_event {
int32_t central_inactive_duration;
};

ZMK_EVENT_DECLARE(zmk_sync_activity_event);
10 changes: 9 additions & 1 deletion app/include/zmk/split/bluetooth/central.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@ int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators);

int zmk_split_get_peripheral_battery_level(uint8_t source, uint8_t *level);

#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)

#if IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) || \
IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)

int zmk_split_bt_queue_sync_activity(int32_t inactive_duration);

#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) ||
// IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)
1 change: 1 addition & 0 deletions app/include/zmk/split/bluetooth/uuid.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@
#define ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID ZMK_BT_SPLIT_UUID(0x00000002)
#define ZMK_SPLIT_BT_CHAR_SENSOR_STATE_UUID ZMK_BT_SPLIT_UUID(0x00000003)
#define ZMK_SPLIT_BT_UPDATE_HID_INDICATORS_UUID ZMK_BT_SPLIT_UUID(0x00000004)
#define ZMK_SPLIT_BT_CHAR_SYNC_ACTIVITY_UUID ZMK_BT_SPLIT_UUID(0x00000005)
68 changes: 68 additions & 0 deletions app/src/activity.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/events/activity_state_changed.h>
#include <zmk/events/position_state_changed.h>
#include <zmk/events/sensor_event.h>
#include <zmk/events/sync_activity_event.h>

#if IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) || \
IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)
#include <zmk/split/bluetooth/central.h>
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) ||
// IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)

#include <zmk/pm.h>

Expand All @@ -38,6 +45,20 @@ static enum zmk_activity_state activity_state;

static uint32_t activity_last_uptime;

#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)

#if IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC)
static uint32_t last_periodic_sync_time;
#define PERIODIC_SYNC_INTERVAL_MS CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC_INTERVAL_MS
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC)

#if IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)
static uint32_t last_event_sync_time;
#define EVENT_SYNC_MIN_INTERVAL_MS CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_EVENT_MIN_INTERVAL
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)

#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL)

#define MAX_IDLE_MS CONFIG_ZMK_IDLE_TIMEOUT

#if IS_ENABLED(CONFIG_ZMK_SLEEP)
Expand All @@ -62,6 +83,16 @@ enum zmk_activity_state zmk_activity_get_state(void) { return activity_state; }
int activity_event_listener(const zmk_event_t *eh) {
activity_last_uptime = k_uptime_get();

#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) && \
IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)
if (activity_last_uptime - last_event_sync_time > EVENT_SYNC_MIN_INTERVAL_MS) {
LOG_DBG("Refresh %d", activity_last_uptime - last_event_sync_time);
last_event_sync_time = activity_last_uptime;
zmk_split_bt_queue_sync_activity(0);
}
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) &&
// IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)

return set_state(ZMK_ACTIVITY_ACTIVE);
}

Expand All @@ -85,6 +116,15 @@ void activity_work_handler(struct k_work *work) {
if (inactive_time > MAX_IDLE_MS) {
set_state(ZMK_ACTIVITY_IDLE);
}

#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) && \
IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC)
if (current - last_periodic_sync_time > PERIODIC_SYNC_INTERVAL_MS) {
last_periodic_sync_time = current;
zmk_split_bt_queue_sync_activity(inactive_time);
}
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) &&
// IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC)
}

K_WORK_DEFINE(activity_work, activity_work_handler);
Expand All @@ -104,4 +144,32 @@ ZMK_LISTENER(activity, activity_event_listener);
ZMK_SUBSCRIPTION(activity, zmk_position_state_changed);
ZMK_SUBSCRIPTION(activity, zmk_sensor_event);

#if !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) && \
(IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) || \
IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT))

int sync_activity_event_listener(const zmk_event_t *eh) {
int32_t current = k_uptime_get();

struct zmk_sync_activity_event *ev = as_zmk_sync_activity_event(eh);
if (ev == NULL) {
LOG_ERR("Invalid event type");
return -ENOTSUP;
}

activity_last_uptime = current - ev->central_inactive_duration;

if (activity_state == ZMK_ACTIVITY_IDLE && ev->central_inactive_duration < MAX_IDLE_MS) {
LOG_DBG("Syncing state to active to match central device.");
return set_state(ZMK_ACTIVITY_ACTIVE);
}
return 0;
}

ZMK_LISTENER(sync_activity, sync_activity_event_listener);
ZMK_SUBSCRIPTION(sync_activity, zmk_sync_activity_event);
#endif // !IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) &&
// (IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) ||
// IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT))

SYS_INIT(activity_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
10 changes: 10 additions & 0 deletions app/src/events/sync_activity_event.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/kernel.h>
#include <zmk/events/sync_activity_event.h>

ZMK_EVENT_IMPL(zmk_sync_activity_event);
22 changes: 22 additions & 0 deletions app/src/split/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ config ZMK_SPLIT_PERIPHERAL_HID_INDICATORS
help
Enable propagating the HID (LED) Indicator state to the split peripheral(s).

config ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC
bool "Sync last activity timing across all devices periodically"
default n
help
Sync central device last activity timing to the split peripheral(s) with a periodic interval.
Does not help to wake up peripheral devices that have gone to deep sleep.

config ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC_INTERVAL_MS
int "Last activity time periodic sync interval in milliseconds"
default 30000

config ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT
bool "Sync last activity timing across all devices upon central activity event"
default n
help
Sync central device last activity timing to the split peripheral(s) when an event (key press/sensor)
is detected. Does not help to wake up peripheral devices that have gone to deep sleep.

config ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_EVENT_MIN_INTERVAL
int "Sync timings on events (key/sensors presses) as well, 0 to disable. Represents minimum interval in milliseconds"
default 1000

#ZMK_SPLIT
endif

Expand Down
72 changes: 72 additions & 0 deletions app/src/split/bluetooth/central.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ static int start_scanning(void);

#define POSITION_STATE_DATA_LEN 16

#if IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) || \
IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)
#define SYNC_LAST_ACTIVITY_TIMING 1
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) ||
// IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)

enum peripheral_slot_state {
PERIPHERAL_SLOT_STATE_OPEN,
PERIPHERAL_SLOT_STATE_CONNECTING,
Expand All @@ -49,6 +55,9 @@ struct peripheral_slot {
struct bt_gatt_subscribe_params sensor_subscribe_params;
struct bt_gatt_discover_params sub_discover_params;
uint16_t run_behavior_handle;
#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
uint16_t sync_activity_handle;
#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
struct bt_gatt_subscribe_params batt_lvl_subscribe_params;
struct bt_gatt_read_params batt_lvl_read_params;
Expand All @@ -66,6 +75,11 @@ static bool is_scanning = false;

static const struct bt_uuid_128 split_service_uuid = BT_UUID_INIT_128(ZMK_SPLIT_BT_SERVICE_UUID);

#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
static int32_t activity_inactive_duration;
static void split_central_sync_activity_with_delay();
#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)

K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed),
CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4);

Expand Down Expand Up @@ -144,6 +158,9 @@ int release_peripheral_slot(int index) {
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
slot->update_hid_indicators = 0;
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
slot->sync_activity_handle = 0;
#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)

return 0;
}
Expand Down Expand Up @@ -465,6 +482,13 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn,
slot->batt_lvl_read_params.single.offset = 0;
bt_gatt_read(conn, &slot->batt_lvl_read_params);
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */
#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
} else if (!bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_SYNC_ACTIVITY_UUID))) {
LOG_DBG("Found sync activity handle");
slot->discover_params.uuid = NULL;
slot->discover_params.start_handle = attr->handle + 2;
slot->sync_activity_handle = bt_gatt_attr_value_handle(attr);
#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
}

bool subscribed = slot->run_behavior_handle && slot->subscribe_params.value_handle;
Expand All @@ -476,6 +500,9 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn,
#if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
subscribed = subscribed && slot->update_hid_indicators;
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)
#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
subscribed = subscribed && slot->sync_activity_handle;
#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING)
subscribed = subscribed && slot->batt_lvl_subscribe_params.value_handle;
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */
Expand Down Expand Up @@ -713,6 +740,12 @@ static void split_central_connected(struct bt_conn *conn, uint8_t conn_err) {

confirm_peripheral_slot_conn(conn);
split_central_process_connection(conn);

#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
// Bluetooth discovery is done only after connection, so a delay is
/// added here to compensate for that before syncing the activity time
split_central_sync_activity_with_delay();
#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
}

static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) {
Expand Down Expand Up @@ -866,6 +899,45 @@ int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators) {

#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)

#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)

static void split_central_sync_activity_callback(struct k_work *work) {
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) {
if (peripherals[i].state != PERIPHERAL_SLOT_STATE_CONNECTED ||
peripherals[i].sync_activity_handle == 0) {
continue;
}

int err = bt_gatt_write_without_response(
peripherals[i].conn, peripherals[i].sync_activity_handle, &activity_inactive_duration,
sizeof(activity_inactive_duration), true);

if (err) {
LOG_ERR("Failed to sync activity state (err %d)", err);
}
}
}

static K_WORK_DEFINE(split_central_sync_activity, split_central_sync_activity_callback);

void split_central_sync_activity_delay_timer_callback(struct k_timer *_timer) {
k_timer_stop(_timer);
k_work_submit_to_queue(&split_central_split_run_q, &split_central_sync_activity);
}
K_TIMER_DEFINE(split_central_sync_activity_delay_timer,
split_central_sync_activity_delay_timer_callback, NULL);

static void split_central_sync_activity_with_delay() {
k_timer_start(&split_central_sync_activity_delay_timer, K_SECONDS(1), K_SECONDS(1));
}

int zmk_split_bt_queue_sync_activity(int32_t inactive_duration) {
activity_inactive_duration = inactive_duration;
return k_work_submit_to_queue(&split_central_split_run_q, &split_central_sync_activity);
}

#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)

static int finish_init() {
return IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) ? 0 : start_scanning();
}
Expand Down
38 changes: 38 additions & 0 deletions app/src/split/bluetooth/service.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/events/hid_indicators_changed.h>
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)

#if IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) || \
IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)
#include <zmk/events/sync_activity_event.h>
#define SYNC_LAST_ACTIVITY_TIMING 1
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_PERIODIC) ||
// IS_ENABLED(CONFIG_ZMK_SPLIT_SYNC_LAST_ACTIVITY_TIMING_ON_EVENT)

#include <zmk/events/sensor_event.h>
#include <zmk/sensors.h>

Expand Down Expand Up @@ -138,6 +145,30 @@ static ssize_t split_svc_update_indicators(struct bt_conn *conn, const struct bt

#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)

#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
static int32_t central_inactive_duration;

static void split_svc_sync_activity_callback(struct k_work *work) {
raise_zmk_sync_activity_event(
(struct zmk_sync_activity_event){.central_inactive_duration = central_inactive_duration});
}

static K_WORK_DEFINE(split_svc_sync_activity_work, split_svc_sync_activity_callback);

static ssize_t split_svc_sync_activity(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags) {
if (offset + len > sizeof(int32_t)) {
return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET);
}

memcpy((uint8_t *)&central_inactive_duration + offset, buf, len);
k_work_submit(&split_svc_sync_activity_work);

return len;
}
#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)

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),
Expand All @@ -160,6 +191,13 @@ BT_GATT_SERVICE_DEFINE(
BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL,
split_svc_update_indicators, NULL),
#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS)

#if IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)
BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_SYNC_ACTIVITY_UUID),
BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL,
split_svc_sync_activity, NULL),
#endif // IS_ENABLED(SYNC_LAST_ACTIVITY_TIMING)

);

K_THREAD_STACK_DEFINE(service_q_stack, CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE);
Expand Down