Files
zmk/app/src/studio/gatt_rpc_transport.c
Peter Johanson feda96eb40 feat(studio): Initial RPC infrastructure and subsystems.
* UART and BLE/GATT transports for a protobuf encoded RPC
  request/response protocol.
* Custom framing protocol is used to frame a give message.
* Requests/responses are divided into major "subsystems" which
  handle requests and create response messages.
* Notification support, including mapping local events to RPC
  notifications by a given subsystem.
* Meta responses for "no response" and "unlock needed".
* Initial basic lock state support in a new core section, and allow specifying
  if a given RPC callback requires unlocked state or not.
* Add behavior subsystem with full metadata support and examples of
  using callback to serialize a repeated field without extra stack space needed.

Co-authored-by: Cem Aksoylar <caksoylar@users.noreply.github.com>
2024-08-15 11:45:18 -06:00

223 lines
6.2 KiB
C

/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <sys/types.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/kernel.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/sys/ring_buffer.h>
#include <zmk/ble.h>
#include <zmk/event_manager.h>
#include <zmk/events/ble_active_profile_changed.h>
#include <zmk/studio/rpc.h>
#include "uuid.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(zmk_studio, CONFIG_ZMK_STUDIO_LOG_LEVEL);
static bool handling_rx = false;
static atomic_t notify_size;
static void rpc_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) {
ARG_UNUSED(attr);
bool notif_enabled = (value == BT_GATT_CCC_INDICATE);
LOG_INF("RPC Notifications %s", notif_enabled ? "enabled" : "disabled");
#if CONFIG_ZMK_STUDIO_TRANSPORT_BLE_PREF_LATENCY < CONFIG_BT_PERIPHERAL_PREF_LATENCY
struct bt_conn *conn = zmk_ble_active_profile_conn();
if (conn) {
uint8_t latency = notif_enabled ? CONFIG_ZMK_STUDIO_TRANSPORT_BLE_PREF_LATENCY
: CONFIG_BT_PERIPHERAL_PREF_LATENCY;
int ret = bt_conn_le_param_update(
conn,
BT_LE_CONN_PARAM(CONFIG_BT_PERIPHERAL_PREF_MIN_INT, CONFIG_BT_PERIPHERAL_PREF_MAX_INT,
latency, CONFIG_BT_PERIPHERAL_PREF_TIMEOUT));
if (ret < 0) {
LOG_WRN("Failed to request lower latency while studio is active (%d)", ret);
}
bt_conn_unref(conn);
}
#endif
}
static ssize_t read_rpc_resp(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset) {
LOG_DBG("Read response for length %d at offset %d", len, offset);
return 0;
}
static ssize_t write_rpc_req(struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf,
uint16_t len, uint16_t offset, uint8_t flags) {
if (!handling_rx) {
return len;
}
uint32_t copied = 0;
struct ring_buf *rpc_buf = zmk_rpc_get_rx_buf();
while (copied < len) {
uint8_t *buffer;
uint32_t claim_len = ring_buf_put_claim(rpc_buf, &buffer, len - copied);
if (claim_len > 0) {
memcpy(buffer, ((uint8_t *)buf) + copied, claim_len);
copied += claim_len;
}
ring_buf_put_finish(rpc_buf, claim_len);
}
zmk_rpc_rx_notify();
return len;
}
BT_GATT_SERVICE_DEFINE(
rpc_interface, BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_128(ZMK_STUDIO_BT_SERVICE_UUID)),
BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_STUDIO_BT_RPC_CHRC_UUID),
BT_GATT_CHRC_WRITE | BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE,
BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, read_rpc_resp,
write_rpc_req, NULL),
BT_GATT_CCC(rpc_ccc_cfg_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT));
static uint16_t get_notify_size_for_conn(struct bt_conn *conn) {
uint16_t notify_size = 23; // Default MTU size unless negotiated higher
struct bt_conn_info conn_info;
if (conn && bt_conn_get_info(conn, &conn_info) >= 0) {
notify_size = conn_info.le.data_len->tx_max_len;
}
return notify_size;
}
static void refresh_notify_size(void) {
struct bt_conn *conn = zmk_ble_active_profile_conn();
uint16_t ns = get_notify_size_for_conn(conn);
if (conn) {
bt_conn_unref(conn);
}
atomic_set(&notify_size, ns);
}
static int gatt_start_rx() {
refresh_notify_size();
handling_rx = true;
return 0;
}
static int gatt_stop_rx(void) {
handling_rx = false;
return 0;
}
static struct bt_gatt_indicate_params rpc_indicate_params = {
.attr = &rpc_interface.attrs[1],
};
static void notif_rpc_tx_cb(struct k_work *work) {
struct bt_conn *conn = zmk_ble_active_profile_conn();
struct ring_buf *tx_buf = zmk_rpc_get_tx_buf();
if (!conn) {
LOG_WRN("No active connection for queued data, dropping");
ring_buf_reset(tx_buf);
return;
}
uint16_t notify_size = get_notify_size_for_conn(conn);
uint8_t notify_bytes[notify_size];
while (ring_buf_size_get(tx_buf) > 0) {
uint16_t added = 0;
while (added < notify_size && ring_buf_size_get(tx_buf) > 0) {
uint8_t *buf;
int len = ring_buf_get_claim(tx_buf, &buf, notify_size - added);
memcpy(notify_bytes + added, buf, len);
added += len;
ring_buf_get_finish(tx_buf, len);
}
rpc_indicate_params.data = notify_bytes;
rpc_indicate_params.len = added;
int notify_attempts = 5;
do {
int err = bt_gatt_indicate(conn, &rpc_indicate_params);
if (err >= 0) {
break;
}
LOG_WRN("Failed to notify the response %d", err);
k_sleep(K_MSEC(200));
} while (notify_attempts-- > 0);
}
bt_conn_unref(conn);
}
static K_WORK_DEFINE(notify_tx_work, notif_rpc_tx_cb);
struct gatt_write_state {
size_t pending_notify;
};
static void gatt_tx_notify(struct ring_buf *tx_buf, size_t added, bool msg_done, void *user_data) {
struct gatt_write_state *state = (struct gatt_write_state *)user_data;
state->pending_notify += added;
atomic_t ns = atomic_get(&notify_size);
if (msg_done || state->pending_notify > ns) {
k_work_submit(&notify_tx_work);
state->pending_notify = 0;
}
}
static struct gatt_write_state tx_state = {};
static void *gatt_tx_user_data(void) {
memset(&tx_state, sizeof(tx_state), 0);
return &tx_state;
}
ZMK_RPC_TRANSPORT(gatt, ZMK_TRANSPORT_BLE, gatt_start_rx, gatt_stop_rx, gatt_tx_user_data,
gatt_tx_notify);
static int gatt_rpc_listener(const zmk_event_t *eh) {
refresh_notify_size();
#if IS_ENABLED(CONFIG_ZMK_STUDIO_LOCK_ON_DISCONNECT)
struct bt_conn *conn = zmk_ble_active_profile_conn();
if (!conn) {
zmk_studio_core_lock();
} else {
bt_conn_unref(conn);
}
#endif
return 0;
}
ZMK_LISTENER(gatt_rpc_listener, gatt_rpc_listener);
ZMK_SUBSCRIPTION(gatt_rpc_listener, zmk_ble_active_profile_changed);