Skip to content

Commit

Permalink
feat(studio): Initial RPC infrastructure and subsystems.
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
petejohanson committed May 17, 2024
1 parent 4109982 commit 899aed7
Show file tree
Hide file tree
Showing 29 changed files with 1,453 additions and 1 deletion.
18 changes: 18 additions & 0 deletions app/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ target_sources_ifdef(CONFIG_ZMK_GPIO_KEY_WAKEUP_TRIGGER app PRIVATE src/gpio_key
target_sources(app PRIVATE src/events/activity_state_changed.c)
target_sources(app PRIVATE src/events/position_state_changed.c)
target_sources(app PRIVATE src/events/sensor_event.c)

target_sources(app PRIVATE src/events/mouse_button_state_changed.c)
target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c)
target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/events/usb_conn_state_changed.c)
Expand Down Expand Up @@ -105,4 +106,21 @@ target_sources(app PRIVATE src/main.c)
add_subdirectory(src/display/)
add_subdirectory_ifdef(CONFIG_SETTINGS src/settings/)

if (CONFIG_ZMK_STUDIO)
# For some reason this is failing if run from a different sub-file.
list(APPEND CMAKE_MODULE_PATH ${ZEPHYR_BASE}/modules/nanopb)

include(nanopb)

zephyr_nanopb_sources(app
proto/zmk/studio.proto
proto/zmk/meta.proto
proto/zmk/core.proto
proto/zmk/behaviors.proto
proto/zmk/keymap.proto
)

add_subdirectory(src/studio)
endif()

zephyr_cc_option(-Wfatal-errors)
2 changes: 2 additions & 0 deletions app/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ rsource "src/split/Kconfig"
#Basic Keyboard Setup
endmenu

rsource "src/studio/Kconfig"

menu "Display/LED Options"

rsource "src/display/Kconfig"
Expand Down
12 changes: 11 additions & 1 deletion app/boards/shields/zmk_uno/boards/nrf52840dk_nrf52840.overlay
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,14 @@ encoder: &qdec0 {

wakeup-sources = <&soft_off_direct_kscan>;
};
};

chosen {
zmk,studio-rpc-uart = &studio_uart;
};
};

&zephyr_udc0 {
studio_uart: studio_uart {
compatible = "zephyr,cdc-acm-uart";
};
};
9 changes: 9 additions & 0 deletions app/include/linker/zmk-rpc-event-mappers.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/linker/linker-defs.h>

ITERABLE_SECTION_ROM(zmk_rpc_event_mapper, 4)
9 changes: 9 additions & 0 deletions app/include/linker/zmk-rpc-subsystem-handlers.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/linker/linker-defs.h>

ITERABLE_SECTION_ROM(zmk_rpc_subsystem_handler, 4)
9 changes: 9 additions & 0 deletions app/include/linker/zmk-rpc-subsystems.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2023 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/linker/linker-defs.h>

ITERABLE_SECTION_RAM(zmk_rpc_subsystem, 4)
9 changes: 9 additions & 0 deletions app/include/linker/zmk-rpc-transport.ld
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright (c) 2024 The ZMK Contributors
*
* SPDX-License-Identifier: MIT
*/

#include <zephyr/linker/linker-defs.h>

ITERABLE_SECTION_ROM(zmk_rpc_transport, 4)
23 changes: 23 additions & 0 deletions app/include/zmk/studio/core.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

#pragma once

#include <zmk/event_manager.h>

enum zmk_studio_core_lock_state {
ZMK_STUDIO_CORE_LOCK_STATE_LOCKED = 0,
ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKING = 1,
ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED = 2,
};

struct zmk_studio_core_lock_state_changed {
enum zmk_studio_core_lock_state state;
};

ZMK_EVENT_DECLARE(zmk_studio_core_lock_state_changed);

enum zmk_studio_core_lock_state zmk_studio_core_get_lock_state(void);

void zmk_studio_core_unlock();
void zmk_studio_core_lock();
void zmk_studio_core_initiate_unlock();
void zmk_studio_core_complete_unlock();
153 changes: 153 additions & 0 deletions app/include/zmk/studio/rpc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@

#pragma once

#include <zephyr/sys/iterable_sections.h>
#include <zephyr/sys/ring_buffer.h>

#include <proto/zmk/studio.pb.h>

#include <zmk/endpoints_types.h>
#include <zmk/event_manager.h>
#include <zmk/studio/core.h>

struct zmk_studio_rpc_notification {
zmk_Notification notification;
};

ZMK_EVENT_DECLARE(zmk_studio_rpc_notification);

struct zmk_rpc_subsystem;

typedef zmk_Response(subsystem_func)(const struct zmk_rpc_subsystem *subsys,
const zmk_Request *req);

typedef zmk_Response(rpc_func)(const zmk_Request *neq);

struct zmk_rpc_subsystem {
subsystem_func *func;
uint16_t handlers_start_index;
uint16_t handlers_end_index;
uint8_t subsystem_choice;
};

struct zmk_rpc_subsystem_handler {
rpc_func *func;
uint8_t subsystem_choice;
uint8_t request_choice;
bool secured;
};

#define ZMK_RPC_NO_RESPONSE() ZMK_RPC_RESPONSE(meta, no_response, true)
#define ZMK_RPC_SIMPLE_ERR(type) \
ZMK_RPC_RESPONSE(meta, simple_error, zmk_meta_ErrorConditions_##type)

#define ZMK_RPC_SUBSYSTEM(prefix) \
zmk_Response subsystem_func_##prefix(const struct zmk_rpc_subsystem *subsys, \
const zmk_Request *req) { \
uint8_t which_req = req->subsystem.prefix.which_request_type; \
LOG_DBG("Got subsystem func for %d", subsys->subsystem_choice); \
\
for (int i = subsys->handlers_start_index; i <= subsys->handlers_end_index; i++) { \
struct zmk_rpc_subsystem_handler *sub_handler; \
STRUCT_SECTION_GET(zmk_rpc_subsystem_handler, i, &sub_handler); \
if (sub_handler->request_choice == which_req) { \
if (sub_handler->secured && \
zmk_studio_core_get_lock_state() != ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED) { \
return ZMK_RPC_RESPONSE(meta, simple_error, \
zmk_meta_ErrorConditions_UNLOCK_REQUIRED); \
} \
return sub_handler->func(req); \
} \
} \
LOG_ERR("No handler func found for %d", which_req); \
return ZMK_RPC_RESPONSE(meta, simple_error, zmk_meta_ErrorConditions_RPC_NOT_FOUND); \
} \
STRUCT_SECTION_ITERABLE(zmk_rpc_subsystem, prefix##_subsystem) = { \
.func = subsystem_func_##prefix, \
.subsystem_choice = zmk_Request_##prefix##_tag, \
};

#define ZMK_RPC_SUBSYSTEM_HANDLER(prefix, request_id, _secured) \
STRUCT_SECTION_ITERABLE(zmk_rpc_subsystem_handler, \
prefix##_subsystem_handler_##request_id) = { \
.func = request_id, \
.subsystem_choice = zmk_Request_##prefix##_tag, \
.request_choice = zmk_##prefix##_Request_##request_id##_tag, \
.secured = _secured, \
};

#define ZMK_RPC_NOTIFICATION(subsys, _type, ...) \
((zmk_Notification){ \
.which_subsystem = zmk_Notification_##subsys##_tag, \
.subsystem = \
{ \
.subsys = \
{ \
.which_notification_type = zmk_##subsys##_Notification_##_type##_tag, \
.notification_type = {._type = __VA_ARGS__}, \
}, \
}, \
})

#define ZMK_RPC_RESPONSE(subsys, _type, ...) \
((zmk_Response){ \
.which_type = zmk_Response_request_response_tag, \
.type = \
{ \
.request_response = \
{ \
.which_subsystem = zmk_RequestResponse_##subsys##_tag, \
.subsystem = \
{ \
.subsys = \
{ \
.which_response_type = \
zmk_##subsys##_Response_##_type##_tag, \
.response_type = {._type = __VA_ARGS__}, \
}, \
}, \
}, \
}, \
})

typedef int(zmk_rpc_event_mapper_cb)(const zmk_event_t *ev, zmk_Notification *n);

struct zmk_rpc_event_mapper {
zmk_rpc_event_mapper_cb *func;
};

#define ZMK_RPC_EVENT_MAPPER_ADD_LISTENER(_t) ZMK_SUBSCRIPTION(studio_rpc, _t)

#define ZMK_RPC_EVENT_MAPPER(name, _func, ...) \
FOR_EACH_NONEMPTY_TERM(ZMK_RPC_EVENT_MAPPER_ADD_LISTENER, (;), __VA_ARGS__) \
STRUCT_SECTION_ITERABLE(zmk_rpc_event_mapper, name) = { \
.func = _func, \
};

typedef int (*zmk_rpc_rx_start_stop_func)(void);

typedef void (*zmk_rpc_tx_buffer_notify_func)(struct ring_buf *buf, size_t added, bool message_done,
void *user_data);
typedef void *(*zmk_rpc_tx_user_data_func)(void);

struct zmk_rpc_transport {
enum zmk_transport transport;

zmk_rpc_tx_user_data_func tx_user_data;
zmk_rpc_tx_buffer_notify_func tx_notify;
zmk_rpc_rx_start_stop_func rx_start;
zmk_rpc_rx_start_stop_func rx_stop;
};

struct ring_buf *zmk_rpc_get_tx_buf(void);
struct ring_buf *zmk_rpc_get_rx_buf(void);
void zmk_rpc_rx_notify(void);

#define ZMK_RPC_TRANSPORT(name, _transport, _rx_start, _rx_stop, _tx_user_data, _tx_notify) \
STRUCT_SECTION_ITERABLE(zmk_rpc_transport, name) = { \
.transport = _transport, \
.rx_start = _rx_start, \
.rx_stop = _rx_stop, \
.tx_user_data = _tx_user_data, \
.tx_notify = _tx_notify, \
}
2 changes: 2 additions & 0 deletions app/proto/zmk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include/
src/
Empty file.
70 changes: 70 additions & 0 deletions app/proto/zmk/behaviors.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
syntax = "proto3";

package zmk.behaviors;

message Request {
oneof request_type {
bool list_all_behaviors = 1;
GetBehaviorDetailsRequest get_behavior_details = 2;
}
}

message GetBehaviorDetailsRequest {
uint32 behavior_id = 1;
}

message Response {
oneof response_type {
ListAllBehaviorsResponse list_all_behaviors = 1;
GetBehaviorDetailsResponse get_behavior_details = 2;
}
}

message ListAllBehaviorsResponse {
repeated uint32 behaviors = 1;
}

message GetBehaviorDetailsResponse {
uint32 id = 1;
string friendly_name = 2;
oneof parameters_type {
BehaviorBindingParametersStandard standard = 3;
BehaviorBindingParametersCustom custom = 4;
}
}

message BehaviorBindingParametersStandard {
BehaviorBindingParameterStandardDomain param1 = 1;
BehaviorBindingParameterStandardDomain param2 = 2;
}

enum BehaviorBindingParameterStandardDomain {
NIL = 0;
HID_USAGE = 1;
LAYER_INDEX = 2;
HSV_VALUE = 3;
}

message BehaviorBindingParametersCustom {
repeated BehaviorBindingParametersCustomSet param_sets = 1;
}

message BehaviorBindingParametersCustomSet {
repeated BehaviorParameterValueDescription param1 = 1;
repeated BehaviorParameterValueDescription param2 = 2;
}

message BehaviorParameterValueDescriptionRange {
uint32 min = 1;
uint32 max = 2;
}

message BehaviorParameterValueDescription {
string name = 1;

oneof value_type {
uint32 constant = 2;
BehaviorParameterValueDescriptionRange range = 3;
BehaviorBindingParameterStandardDomain standard = 4;
}
}
29 changes: 29 additions & 0 deletions app/proto/zmk/core.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
syntax = "proto3";

package zmk.core;

enum LockState {
ZMK_STUDIO_CORE_LOCK_STATE_LOCKED = 0;
ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKING = 1;
ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED = 2;
}

message Request {
oneof request_type {
bool get_lock_state = 1;
bool request_unlock = 2;
bool lock = 3;
}
}

message Response {
oneof response_type {
LockState get_lock_state = 1;
}
}

message Notification {
oneof notification_type {
LockState lock_state_changed = 1;
}
}
9 changes: 9 additions & 0 deletions app/proto/zmk/keymap.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
syntax = "proto3";

package zmk.keymap;

message Request {
oneof request_type {
bool get_layer_summaries = 1;
}
}
Loading

0 comments on commit 899aed7

Please sign in to comment.