feature(split): add support for sensors from peripheral
This commit adds a new GATT characteristics on the peripheral side and wires it up to read sensor values. The central side subscribes to this new characteristics and replays sensor values on its side. Co-authored-by: Peter Johanson <peter@peterjohanson.com>
This commit is contained in:
parent
c957348e61
commit
9d44229800
7 changed files with 196 additions and 28 deletions
|
@ -24,7 +24,9 @@ struct zmk_sensor_config {
|
|||
uint16_t triggers_per_rotation;
|
||||
};
|
||||
|
||||
// This struct is also used for data transfer for splits, so any changes to the size, layout, etc
|
||||
// is a breaking change for the split GATT service protocol.
|
||||
struct zmk_sensor_channel_data {
|
||||
enum sensor_channel channel;
|
||||
struct sensor_value value;
|
||||
};
|
||||
enum sensor_channel channel;
|
||||
} __packed;
|
||||
|
|
|
@ -6,8 +6,18 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <zmk/events/sensor_event.h>
|
||||
#include <zmk/sensors.h>
|
||||
|
||||
#define ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN 9
|
||||
|
||||
struct sensor_event {
|
||||
uint8_t sensor_index;
|
||||
|
||||
uint8_t channel_data_size;
|
||||
struct zmk_sensor_channel_data channel_data[ZMK_SENSOR_EVENT_MAX_CHANNELS];
|
||||
} __packed;
|
||||
|
||||
struct zmk_split_run_behavior_data {
|
||||
uint8_t position;
|
||||
uint8_t state;
|
||||
|
@ -21,4 +31,7 @@ struct zmk_split_run_behavior_payload {
|
|||
} __packed;
|
||||
|
||||
int zmk_split_bt_position_pressed(uint8_t position);
|
||||
int zmk_split_bt_position_released(uint8_t position);
|
||||
int zmk_split_bt_position_released(uint8_t position);
|
||||
int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
const struct zmk_sensor_channel_data channel_data[],
|
||||
size_t channel_data_size);
|
||||
|
|
|
@ -16,3 +16,4 @@
|
|||
#define ZMK_SPLIT_BT_SERVICE_UUID ZMK_BT_SPLIT_UUID(0x00000000)
|
||||
#define ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID ZMK_BT_SPLIT_UUID(0x00000001)
|
||||
#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)
|
||||
|
|
|
@ -29,7 +29,7 @@ struct sensors_item_cfg {
|
|||
{ \
|
||||
.dev = DEVICE_DT_GET_OR_NULL(node), \
|
||||
.trigger = {.type = SENSOR_TRIG_DATA_READY, .chan = SENSOR_CHAN_ROTATION}, \
|
||||
.config = &configs[idx] \
|
||||
.config = &configs[idx], .sensor_index = idx \
|
||||
}
|
||||
#define SENSOR_ITEM(idx, _i) _SENSOR_ITEM(idx, ZMK_KEYMAP_SENSORS_BY_IDX(idx))
|
||||
|
||||
|
@ -112,7 +112,7 @@ static void zmk_sensors_trigger_handler(const struct device *dev,
|
|||
int sensor_index = test_item - sensors;
|
||||
|
||||
if (sensor_index < 0 || sensor_index >= ARRAY_SIZE(sensors)) {
|
||||
LOG_ERR("Invalid sensor item triggered our callback");
|
||||
LOG_ERR("Invalid sensor item triggered our callback (%d)", sensor_index);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -127,8 +127,6 @@ static void zmk_sensors_trigger_handler(const struct device *dev,
|
|||
static void zmk_sensors_init_item(uint8_t i) {
|
||||
LOG_DBG("Init sensor at index %d", i);
|
||||
|
||||
sensors[i].sensor_index = i;
|
||||
|
||||
if (!sensors[i].dev) {
|
||||
LOG_DBG("No local device for %d", i);
|
||||
return;
|
||||
|
|
|
@ -21,10 +21,12 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
|||
#include <zmk/stdlib.h>
|
||||
#include <zmk/ble.h>
|
||||
#include <zmk/behavior.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zmk/split/bluetooth/uuid.h>
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
|
||||
static int start_scanning(void);
|
||||
|
||||
|
@ -41,6 +43,7 @@ struct peripheral_slot {
|
|||
struct bt_conn *conn;
|
||||
struct bt_gatt_discover_params discover_params;
|
||||
struct bt_gatt_subscribe_params subscribe_params;
|
||||
struct bt_gatt_subscribe_params sensor_subscribe_params;
|
||||
struct bt_gatt_discover_params sub_discover_params;
|
||||
uint16_t run_behavior_handle;
|
||||
uint8_t position_state[POSITION_STATE_DATA_LEN];
|
||||
|
@ -165,6 +168,52 @@ int confirm_peripheral_slot_conn(struct bt_conn *conn) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
K_MSGQ_DEFINE(peripheral_sensor_event_msgq, sizeof(struct zmk_sensor_event),
|
||||
CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4);
|
||||
|
||||
void peripheral_sensor_event_work_callback(struct k_work *work) {
|
||||
struct zmk_sensor_event ev;
|
||||
while (k_msgq_get(&peripheral_sensor_event_msgq, &ev, K_NO_WAIT) == 0) {
|
||||
LOG_DBG("Trigger sensor change for %d", ev.sensor_index);
|
||||
ZMK_EVENT_RAISE(new_zmk_sensor_event(ev));
|
||||
}
|
||||
}
|
||||
|
||||
K_WORK_DEFINE(peripheral_sensor_event_work, peripheral_sensor_event_work_callback);
|
||||
|
||||
static uint8_t split_central_sensor_notify_func(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params,
|
||||
const void *data, uint16_t length) {
|
||||
if (!data) {
|
||||
LOG_DBG("[UNSUBSCRIBED]");
|
||||
params->value_handle = 0U;
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
LOG_DBG("[SENSOR NOTIFICATION] data %p length %u", data, length);
|
||||
|
||||
if (length < offsetof(struct sensor_event, channel_data)) {
|
||||
LOG_WRN("Ignoring sensor notify with insufficient data length (%d)", length);
|
||||
return BT_GATT_ITER_STOP;
|
||||
}
|
||||
|
||||
struct sensor_event sensor_event;
|
||||
memcpy(&sensor_event, data, MIN(length, sizeof(sensor_event)));
|
||||
struct zmk_sensor_event ev = {
|
||||
.sensor_index = sensor_event.sensor_index,
|
||||
.channel_data_size = MIN(sensor_event.channel_data_size, ZMK_SENSOR_EVENT_MAX_CHANNELS),
|
||||
.timestamp = k_uptime_get()};
|
||||
|
||||
memcpy(ev.channel_data, sensor_event.channel_data,
|
||||
sizeof(struct zmk_sensor_channel_data) * sensor_event.channel_data_size);
|
||||
k_msgq_put(&peripheral_sensor_event_msgq, &ev, K_NO_WAIT);
|
||||
k_work_submit(&peripheral_sensor_event_work);
|
||||
|
||||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
|
||||
static uint8_t split_central_notify_func(struct bt_conn *conn,
|
||||
struct bt_gatt_subscribe_params *params, const void *data,
|
||||
uint16_t length) {
|
||||
|
@ -209,14 +258,8 @@ static uint8_t split_central_notify_func(struct bt_conn *conn,
|
|||
return BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
|
||||
static void split_central_subscribe(struct bt_conn *conn) {
|
||||
struct peripheral_slot *slot = peripheral_slot_for_conn(conn);
|
||||
if (slot == NULL) {
|
||||
LOG_ERR("No peripheral state found for connection");
|
||||
return;
|
||||
}
|
||||
|
||||
int err = bt_gatt_subscribe(conn, &slot->subscribe_params);
|
||||
static int split_central_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *params) {
|
||||
int err = bt_gatt_subscribe(conn, params);
|
||||
switch (err) {
|
||||
case -EALREADY:
|
||||
LOG_DBG("[ALREADY SUBSCRIBED]");
|
||||
|
@ -228,6 +271,8 @@ static void split_central_subscribe(struct bt_conn *conn) {
|
|||
LOG_ERR("Subscribe failed (err %d)", err);
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn,
|
||||
|
@ -250,9 +295,9 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn,
|
|||
}
|
||||
|
||||
LOG_DBG("[ATTRIBUTE] handle %u", attr->handle);
|
||||
const struct bt_uuid *chrc_uuid = ((struct bt_gatt_chrc *)attr->user_data)->uuid;
|
||||
|
||||
if (bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid,
|
||||
BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID)) == 0) {
|
||||
if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID)) == 0) {
|
||||
LOG_DBG("Found position state characteristic");
|
||||
slot->discover_params.uuid = NULL;
|
||||
slot->discover_params.start_handle = attr->handle + 2;
|
||||
|
@ -263,14 +308,33 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn,
|
|||
slot->subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);
|
||||
slot->subscribe_params.notify = split_central_notify_func;
|
||||
slot->subscribe_params.value = BT_GATT_CCC_NOTIFY;
|
||||
split_central_subscribe(conn);
|
||||
} else if (bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid,
|
||||
BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID)) == 0) {
|
||||
split_central_subscribe(conn, &slot->subscribe_params);
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
} else if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_SENSOR_STATE_UUID)) ==
|
||||
0) {
|
||||
slot->discover_params.uuid = NULL;
|
||||
slot->discover_params.start_handle = attr->handle + 2;
|
||||
slot->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC;
|
||||
|
||||
slot->sensor_subscribe_params.disc_params = &slot->sub_discover_params;
|
||||
slot->sensor_subscribe_params.end_handle = slot->discover_params.end_handle;
|
||||
slot->sensor_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr);
|
||||
slot->sensor_subscribe_params.notify = split_central_sensor_notify_func;
|
||||
slot->sensor_subscribe_params.value = BT_GATT_CCC_NOTIFY;
|
||||
split_central_subscribe(conn, &slot->sensor_subscribe_params);
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
} else if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID)) ==
|
||||
0) {
|
||||
LOG_DBG("Found run behavior handle");
|
||||
slot->discover_params.uuid = NULL;
|
||||
slot->discover_params.start_handle = attr->handle + 2;
|
||||
slot->run_behavior_handle = bt_gatt_attr_value_handle(attr);
|
||||
}
|
||||
|
||||
bool subscribed = (slot->run_behavior_handle && slot->subscribe_params.value_handle);
|
||||
bool subscribed = slot->run_behavior_handle && slot->subscribe_params.value_handle;
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
subscribed = subscribed && slot->sensor_subscribe_params.value_handle;
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
|
||||
return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <zephyr/drivers/sensor.h>
|
||||
#include <zephyr/types.h>
|
||||
#include <zephyr/sys/util.h>
|
||||
#include <zephyr/init.h>
|
||||
|
@ -20,6 +21,22 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
|||
#include <zmk/matrix.h>
|
||||
#include <zmk/split/bluetooth/uuid.h>
|
||||
#include <zmk/split/bluetooth/service.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
#include <zmk/sensors.h>
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
static struct sensor_event last_sensor_event;
|
||||
|
||||
static ssize_t split_svc_sensor_state(struct bt_conn *conn, const struct bt_gatt_attr *attrs,
|
||||
void *buf, uint16_t len, uint16_t offset) {
|
||||
return bt_gatt_attr_read(conn, attrs, buf, len, offset, &last_sensor_event,
|
||||
sizeof(last_sensor_event));
|
||||
}
|
||||
|
||||
static void split_svc_sensor_state_ccc(const struct bt_gatt_attr *attr, uint16_t value) {
|
||||
LOG_DBG("value %d", value);
|
||||
}
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
|
||||
#define POS_STATE_LEN 16
|
||||
|
||||
|
@ -98,7 +115,14 @@ BT_GATT_SERVICE_DEFINE(
|
|||
BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL,
|
||||
split_svc_run_behavior, &behavior_run_payload),
|
||||
BT_GATT_DESCRIPTOR(BT_UUID_NUM_OF_DIGITALS, BT_GATT_PERM_READ, split_svc_num_of_positions, NULL,
|
||||
&num_of_positions), );
|
||||
&num_of_positions),
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_SENSOR_STATE_UUID),
|
||||
BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT,
|
||||
split_svc_sensor_state, NULL, &last_sensor_event),
|
||||
BT_GATT_CCC(split_svc_sensor_state_ccc, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT),
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
);
|
||||
|
||||
K_THREAD_STACK_DEFINE(service_q_stack, CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE);
|
||||
|
||||
|
@ -151,6 +175,58 @@ int zmk_split_bt_position_released(uint8_t position) {
|
|||
return send_position_state();
|
||||
}
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
K_MSGQ_DEFINE(sensor_state_msgq, sizeof(struct sensor_event),
|
||||
CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE, 4);
|
||||
|
||||
void send_sensor_state_callback(struct k_work *work) {
|
||||
while (k_msgq_get(&sensor_state_msgq, &last_sensor_event, K_NO_WAIT) == 0) {
|
||||
int err = bt_gatt_notify(NULL, &split_svc.attrs[8], &last_sensor_event,
|
||||
sizeof(last_sensor_event));
|
||||
if (err) {
|
||||
LOG_DBG("Error notifying %d", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
K_WORK_DEFINE(service_sensor_notify_work, send_sensor_state_callback);
|
||||
|
||||
int send_sensor_state(struct sensor_event ev) {
|
||||
int err = k_msgq_put(&sensor_state_msgq, &ev, K_MSEC(100));
|
||||
if (err) {
|
||||
// retry...
|
||||
switch (err) {
|
||||
case -EAGAIN: {
|
||||
LOG_WRN("Sensor state message queue full, popping first message and queueing again");
|
||||
struct sensor_event discarded_state;
|
||||
k_msgq_get(&sensor_state_msgq, &discarded_state, K_NO_WAIT);
|
||||
return send_sensor_state(ev);
|
||||
}
|
||||
default:
|
||||
LOG_WRN("Failed to queue sensor state to send (%d)", err);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
k_work_submit_to_queue(&service_work_q, &service_sensor_notify_work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int zmk_split_bt_sensor_triggered(uint8_t sensor_index,
|
||||
const struct zmk_sensor_channel_data channel_data[],
|
||||
size_t channel_data_size) {
|
||||
if (channel_data_size > ZMK_SENSOR_EVENT_MAX_CHANNELS) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
struct sensor_event ev =
|
||||
(struct sensor_event){.sensor_index = sensor_index, .channel_data_size = channel_data_size};
|
||||
memcpy(ev.channel_data, channel_data,
|
||||
channel_data_size * sizeof(struct zmk_sensor_channel_data));
|
||||
return send_sensor_state(ev);
|
||||
}
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
|
||||
int service_init(const struct device *_arg) {
|
||||
static const struct k_work_queue_config queue_config = {
|
||||
.name = "Split Peripheral Notification Queue"};
|
||||
|
|
|
@ -13,21 +13,35 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
|||
|
||||
#include <zmk/event_manager.h>
|
||||
#include <zmk/events/position_state_changed.h>
|
||||
#include <zmk/events/sensor_event.h>
|
||||
#include <zmk/hid.h>
|
||||
#include <zmk/sensors.h>
|
||||
#include <zmk/endpoints.h>
|
||||
|
||||
int split_listener(const zmk_event_t *eh) {
|
||||
LOG_DBG("");
|
||||
const struct zmk_position_state_changed *ev = as_zmk_position_state_changed(eh);
|
||||
if (ev != NULL) {
|
||||
if (ev->state) {
|
||||
return zmk_split_bt_position_pressed(ev->position);
|
||||
const struct zmk_position_state_changed *pos_ev;
|
||||
if ((pos_ev = as_zmk_position_state_changed(eh)) != NULL) {
|
||||
if (pos_ev->state) {
|
||||
return zmk_split_bt_position_pressed(pos_ev->position);
|
||||
} else {
|
||||
return zmk_split_bt_position_released(ev->position);
|
||||
return zmk_split_bt_position_released(pos_ev->position);
|
||||
}
|
||||
}
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
const struct zmk_sensor_event *sensor_ev;
|
||||
if ((sensor_ev = as_zmk_sensor_event(eh)) != NULL) {
|
||||
return zmk_split_bt_sensor_triggered(sensor_ev->sensor_index, sensor_ev->channel_data,
|
||||
sensor_ev->channel_data_size);
|
||||
}
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
return ZMK_EV_EVENT_BUBBLE;
|
||||
}
|
||||
|
||||
ZMK_LISTENER(split_listener, split_listener);
|
||||
ZMK_SUBSCRIPTION(split_listener, zmk_position_state_changed);
|
||||
ZMK_SUBSCRIPTION(split_listener, zmk_position_state_changed);
|
||||
|
||||
#if ZMK_KEYMAP_HAS_SENSORS
|
||||
ZMK_SUBSCRIPTION(split_listener, zmk_sensor_event);
|
||||
#endif /* ZMK_KEYMAP_HAS_SENSORS */
|
||||
|
|
Loading…
Add table
Reference in a new issue