From 5e408c59f53c15cf752259ce06f157b9d4f13e0f Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Wed, 27 Mar 2024 21:57:37 +0000 Subject: [PATCH] feat(rpc): Tons more RPC work. * Add full notification support, including mapping local events to RPC notifications by a given subsystem. * New 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. --- app/CMakeLists.txt | 5 - app/include/linker/zmk-rpc-event-mappers.ld | 9 + app/include/zmk/studio/core.h | 23 +++ app/include/zmk/studio/rpc.h | 64 ++++++- app/proto/zmk/behaviors.options | 3 - app/proto/zmk/behaviors.options.in | 3 + app/proto/zmk/behaviors.proto | 55 ++++-- app/proto/zmk/core.proto | 24 ++- app/proto/zmk/meta.proto | 10 +- app/proto/zmk/studio-msgs.cddl | 54 ------ app/proto/zmk/studio.proto | 4 +- app/src/studio/CMakeLists.txt | 6 + app/src/studio/Kconfig | 16 +- app/src/studio/behavior_subsystem.c | 182 ++++++++++++++++++++ app/src/studio/core.c | 20 +++ app/src/studio/core_subsystem.c | 34 +++- app/src/studio/gatt_rpc_interface.c | 42 +++-- app/src/studio/rpc.c | 22 ++- app/src/studio/uart_rpc_interface.c | 52 +++--- app/west.yml | 4 + 20 files changed, 494 insertions(+), 138 deletions(-) create mode 100644 app/include/linker/zmk-rpc-event-mappers.ld create mode 100644 app/include/zmk/studio/core.h delete mode 100644 app/proto/zmk/behaviors.options create mode 100644 app/proto/zmk/behaviors.options.in delete mode 100644 app/proto/zmk/studio-msgs.cddl create mode 100644 app/src/studio/behavior_subsystem.c create mode 100644 app/src/studio/core.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 4c3e6280f311..b702154c8f52 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -15,11 +15,6 @@ endif() zephyr_linker_sources(SECTIONS include/linker/zmk-behaviors.ld) zephyr_linker_sources(RODATA include/linker/zmk-events.ld) -if(CONFIG_ZMK_STUDIO) - zephyr_linker_sources(DATA_SECTIONS include/linker/zmk-rpc-subsystems.ld) - zephyr_linker_sources(SECTIONS include/linker/zmk-rpc-subsystem-handlers.ld) -endif() - zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/behavior.h) zephyr_syscall_header(${APPLICATION_SOURCE_DIR}/include/drivers/ext_power.h) diff --git a/app/include/linker/zmk-rpc-event-mappers.ld b/app/include/linker/zmk-rpc-event-mappers.ld new file mode 100644 index 000000000000..bc5a0eea1f60 --- /dev/null +++ b/app/include/linker/zmk-rpc-event-mappers.ld @@ -0,0 +1,9 @@ +/* + * Copyright (c) 2024 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include + +ITERABLE_SECTION_ROM(zmk_rpc_event_mapper, 4) diff --git a/app/include/zmk/studio/core.h b/app/include/zmk/studio/core.h new file mode 100644 index 000000000000..398f1f180c5c --- /dev/null +++ b/app/include/zmk/studio/core.h @@ -0,0 +1,23 @@ + +#pragma once + +#include + +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(); \ No newline at end of file diff --git a/app/include/zmk/studio/rpc.h b/app/include/zmk/studio/rpc.h index bd10f5b7ee0f..46b611bfcd57 100644 --- a/app/include/zmk/studio/rpc.h +++ b/app/include/zmk/studio/rpc.h @@ -5,6 +5,15 @@ #include +#include +#include + +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, @@ -23,8 +32,11 @@ 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_SUBSYSTEM(prefix) \ zmk_Response subsystem_func_##prefix(const struct zmk_rpc_subsystem *subsys, \ const zmk_Request *req) { \ @@ -35,6 +47,11 @@ struct zmk_rpc_subsystem_handler { 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); \ } \ } \ @@ -46,14 +63,28 @@ struct zmk_rpc_subsystem_handler { .subsystem_choice = zmk_Request_##prefix##_tag, \ }; -#define ZMK_RPC_SUBSYSTEM_HANDLER(prefix, request_id) \ +#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##_Response_##request_id##_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, \ @@ -75,4 +106,33 @@ struct zmk_rpc_subsystem_handler { }, \ }) +#define ZMK_RPC_NOTIFICATION_PUBLISHER(name, _cb) \ + static int name##_listener_cb(const zmk_event_t *eh) { \ + struct zmk_studio_rpc_notification *notif = as_zmk_studio_rpc_notification(eh); \ + __ASSERT(notif, "Only notification events should be received"); \ + \ + zmk_Response resp = zmk_Response_init_zero; \ + resp.which_type = zmk_Response_notification_tag; \ + resp.type.notification = notif->notification; \ + _cb(&resp); \ + \ + return ZMK_EV_EVENT_BUBBLE; \ + }; \ + ZMK_LISTENER(name##_listener, name##_listener_cb); \ + ZMK_SUBSCRIPTION(name##_listener, zmk_studio_rpc_notification); + +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, \ + }; + zmk_Response zmk_rpc_handle_request(const zmk_Request *req); diff --git a/app/proto/zmk/behaviors.options b/app/proto/zmk/behaviors.options deleted file mode 100644 index 4e76e7790d68..000000000000 --- a/app/proto/zmk/behaviors.options +++ /dev/null @@ -1,3 +0,0 @@ -zmk.behaviors.ListAllBehaviorsResponse.behaviors max_count:20 -zmk.behaviors.GetBehaviorDetailsResponse.friendly_name max_size:20 -zmk.behaviors.behavior_binding_parameter_details.name max_size:20 \ No newline at end of file diff --git a/app/proto/zmk/behaviors.options.in b/app/proto/zmk/behaviors.options.in new file mode 100644 index 000000000000..1fd4a9d3b4ba --- /dev/null +++ b/app/proto/zmk/behaviors.options.in @@ -0,0 +1,3 @@ +zmk.behaviors.GetBehaviorDetailsResponse.friendly_name max_size:20 +zmk.behaviors.BehaviorBindingParameterStandard.name max_size:20 +zmk.behaviors.BehaviorParameterValueDescription.name max_size:32 \ No newline at end of file diff --git a/app/proto/zmk/behaviors.proto b/app/proto/zmk/behaviors.proto index 9f874f1fe8de..2f12bd2fe43a 100644 --- a/app/proto/zmk/behaviors.proto +++ b/app/proto/zmk/behaviors.proto @@ -27,33 +27,54 @@ message ListAllBehaviorsResponse { message GetBehaviorDetailsResponse { uint32 id = 1; string friendly_name = 2; - behavior_binding_parameter_details param1 = 3; - behavior_binding_parameter_details param2 = 4; + BehaviorBindingParameters parameters = 3; } -message behavior_binding_parameter_details { - behavior_binding_parameter_domain domain = 1; - string name = 2; -} - -message behavior_binding_parameter_domain { - oneof type { - behavior_binding_parameter_domain_standard standard = 1; - behavior_binding_parameter_domain_custom custom = 2; +message BehaviorBindingParameters { + oneof parameter_types { + BehaviorBindingParametersStandard standard = 1; + BehaviorBindingParametersCustom custom = 2; } } -message behavior_binding_parameter_domain_custom { - repeated behavior_binding_parameter_value_description values = 1; +message BehaviorBindingParametersStandard { + BehaviorBindingParameterStandard param1 = 1; + BehaviorBindingParameterStandard param2 = 2; + } -message behavior_binding_parameter_value_description { - int32 value = 1; +message BehaviorBindingParameterStandard { + BehaviorBindingParameterStandardDomain domain = 1; string name = 2; } -enum behavior_binding_parameter_domain_standard { +enum BehaviorBindingParameterStandardDomain { NIL = 0; HID_USAGE = 1; LAYER_INDEX = 2; -} \ No newline at end of file + 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; + } +} diff --git a/app/proto/zmk/core.proto b/app/proto/zmk/core.proto index 930c3109fc66..390156961a7a 100644 --- a/app/proto/zmk/core.proto +++ b/app/proto/zmk/core.proto @@ -2,20 +2,28 @@ 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_status = 1; - bool unlock_request = 2; - bool lock_request = 3; + bool get_lock_state = 1; + bool request_unlock = 2; + bool lock = 3; } } -message GetLockStatusResponse { - bool locked = 1; -} - message Response { oneof response_type { - GetLockStatusResponse get_lock_status = 1; + LockState get_lock_state = 1; + } +} + +message Notification { + oneof notification_type { + LockState lock_state_changed = 1; } } \ No newline at end of file diff --git a/app/proto/zmk/meta.proto b/app/proto/zmk/meta.proto index 7a7ae92efbb5..c3dc0bfafbcf 100644 --- a/app/proto/zmk/meta.proto +++ b/app/proto/zmk/meta.proto @@ -4,13 +4,15 @@ package zmk.meta; enum ErrorConditions { GENERIC = 0; - RPC_NOT_FOUND = 1; - MSG_DECODE_FAILED = 2; - MSG_ENCODE_FAILED = 3; + UNLOCK_REQUIRED = 1; + RPC_NOT_FOUND = 2; + MSG_DECODE_FAILED = 3; + MSG_ENCODE_FAILED = 4; } message Response { oneof response_type { - ErrorConditions simple_error = 1; + bool no_response = 1; + ErrorConditions simple_error = 2; } } \ No newline at end of file diff --git a/app/proto/zmk/studio-msgs.cddl b/app/proto/zmk/studio-msgs.cddl deleted file mode 100644 index 00a8c76af5c7..000000000000 --- a/app/proto/zmk/studio-msgs.cddl +++ /dev/null @@ -1,54 +0,0 @@ -; Requests - -request = [request_id: uint, core_subsystem / keymap_subsystem / behavior_subsystem] - -core_subsystem = [0, core_request] -keymap_subsystem = [1, keymap_request] -behavior_subsystem = [2, behavior_request] - -core_request = get_lock_state / unlock_request / lock_request - -get_lock_state = [0] -unlock_request = [1] -lock_request = [2] - -keymap_request = get_layers_summary - -get_layers_summary = [0] - -behavior_request = - list_all_behaviors / - get_behavior_details - -list_all_behaviors = [0] -get_behavior_details = [1, get_behavior_details_payload] - -get_behavior_details_payload = [behavior_id: uint] - -; Responses - -response = notification / request_response - -notification = [0, notification_payload] -request_response = [1, request_id: uint, response_payload] - -notification_payload = uint - -response_payload = core_response / keymap_response / behavior_response - -core_response = [0, get_lock_state_response / unlock_response / lock_response] -keymap_response = [1, get_layers_summary_response] -behavior_response = [2, list_all_behaviors_response / get_behavior_details_response] - -get_lock_state_response = [0, locked: bool] -unlock_response = [1] -lock_response = [2] - -get_layers_summary_response = [ 0, keymap_layer_summary ] -list_all_behaviors_response = [ 0, behaviors: [ 1*20 behavior_summary ] ] -get_behavior_details_response = [ 1, behavior_detail ] - -keymap_layer_summary = [ name: tstr, enabled: bool ] -behavior_summary = [ id: uint ] - -behavior_detail = [ id: uint, friendly_name: tstr ] diff --git a/app/proto/zmk/studio.proto b/app/proto/zmk/studio.proto index 95e7442086e7..fd9e98a36bcf 100644 --- a/app/proto/zmk/studio.proto +++ b/app/proto/zmk/studio.proto @@ -35,5 +35,7 @@ message RequestResponse { } message Notification { - uint32 notification_type = 1; + oneof subsystem { + zmk.core.Notification core = 2; + } } diff --git a/app/src/studio/CMakeLists.txt b/app/src/studio/CMakeLists.txt index 13b9cd50bb33..ca932887027b 100644 --- a/app/src/studio/CMakeLists.txt +++ b/app/src/studio/CMakeLists.txt @@ -1,6 +1,12 @@ +zephyr_linker_sources(DATA_SECTIONS ../../include/linker/zmk-rpc-subsystems.ld) +zephyr_linker_sources(SECTIONS ../../include/linker/zmk-rpc-subsystem-handlers.ld) +zephyr_linker_sources(SECTIONS ../../include/linker/zmk-rpc-event-mappers.ld) + target_sources(app PRIVATE common.c) target_sources(app PRIVATE rpc.c) +target_sources(app PRIVATE core.c) +target_sources(app PRIVATE behavior_subsystem.c) target_sources(app PRIVATE core_subsystem.c) target_sources_ifdef(CONFIG_ZMK_STUDIO_INTERFACE_UART app PRIVATE uart_rpc_interface.c) target_sources_ifdef(CONFIG_ZMK_STUDIO_INTERFACE_BLE app PRIVATE gatt_rpc_interface.c) \ No newline at end of file diff --git a/app/src/studio/Kconfig b/app/src/studio/Kconfig index b97825830110..0a4013ca0e95 100644 --- a/app/src/studio/Kconfig +++ b/app/src/studio/Kconfig @@ -8,9 +8,8 @@ menuconfig ZMK_STUDIO if ZMK_STUDIO -config ZMK_STUDIO_MAX_MSG_SIZE - int "Max RPC message size" - default 64 + +menu "Remote Procedure Calls (RPC)" menu "Interfaces" @@ -23,9 +22,20 @@ config ZMK_STUDIO_INTERFACE_UART config ZMK_STUDIO_INTERFACE_BLE bool "BLE (GATT)" select NET_BUF + select BT_USER_DATA_LEN_UPDATE depends on ZMK_BLE default y endmenu +menu "Message Encoding" + +config ZMK_STUDIO_MSG_MAX_SIZE + int "Max message size" + default 512 + +endmenu + +endmenu + endif diff --git a/app/src/studio/behavior_subsystem.c b/app/src/studio/behavior_subsystem.c new file mode 100644 index 000000000000..74da1eade192 --- /dev/null +++ b/app/src/studio/behavior_subsystem.c @@ -0,0 +1,182 @@ + + +#include +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include + +ZMK_RPC_SUBSYSTEM(behaviors) + +#define BEHAVIOR_RESPONSE(type, ...) ZMK_RPC_RESPONSE(behaviors, type, __VA_ARGS__) + +static bool encode_behavior_summaries(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + uint32_t i = 0; + STRUCT_SECTION_FOREACH(zmk_behavior_ref, beh) { + if (!pb_encode_tag_for_field(stream, field)) + return false; + + if (!pb_encode_varint(stream, i++)) { + LOG_ERR("Failed to encode behavior ID"); + return false; + } + } + + return true; +} + +zmk_Response list_all_behaviors(const zmk_Request *req) { + zmk_behaviors_ListAllBehaviorsResponse beh_resp = + zmk_behaviors_ListAllBehaviorsResponse_init_zero; + beh_resp.behaviors.funcs.encode = encode_behavior_summaries; + + return BEHAVIOR_RESPONSE(list_all_behaviors, beh_resp); +} + +struct encode_custom_sets_state { + const struct behavior_parameter_metadata_custom *metadata; + uint8_t i; +}; + +static bool encode_value_description(pb_ostream_t *stream, const pb_field_t *field, + void *const *arg) { + struct encode_custom_sets_state *state = (struct encode_custom_sets_state *)*arg; + + const struct behavior_parameter_metadata_custom_set *set = &state->metadata->sets[state->i]; + + for (int val_i = 0; val_i < set->values_len; val_i++) { + const struct behavior_parameter_value_metadata *val = &set->values[val_i]; + + if (!((val->position == 0 && + field->tag == zmk_behaviors_BehaviorBindingParametersCustomSet_param1_tag) || + (val->position == 1 && + field->tag == zmk_behaviors_BehaviorBindingParametersCustomSet_param2_tag))) { + continue; + } + + if (!pb_encode_tag_for_field(stream, field)) + return false; + + zmk_behaviors_BehaviorParameterValueDescription desc = + zmk_behaviors_BehaviorParameterValueDescription_init_zero; + strncpy(desc.name, val->friendly_name, ARRAY_SIZE(desc.name)); + + switch (val->type) { + case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_VALUE: + desc.which_value_type = zmk_behaviors_BehaviorParameterValueDescription_constant_tag; + desc.value_type.constant = val->value; + break; + case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_RANGE: + desc.which_value_type = zmk_behaviors_BehaviorParameterValueDescription_range_tag; + desc.value_type.range.min = val->range.min; + desc.value_type.range.max = val->range.max; + break; + case BEHAVIOR_PARAMETER_VALUE_METADATA_TYPE_STANDARD: + LOG_DBG("GOT A STANDARD ONE for %d", val->standard); + desc.which_value_type = zmk_behaviors_BehaviorParameterValueDescription_standard_tag; + desc.value_type.standard = val->standard; + break; + default: + LOG_ERR("Unknown value description type %d", val->type); + return false; + } + + if (!pb_encode_submessage(stream, &zmk_behaviors_BehaviorParameterValueDescription_msg, + &desc)) { + LOG_WRN("Failed to encode submessage!"); + return false; + } + } + + return true; +} + +static bool encode_custom_sets(pb_ostream_t *stream, const pb_field_t *field, void *const *arg) { + struct encode_custom_sets_state *state = (struct encode_custom_sets_state *)*arg; + bool ret = true; + + for (int i = 0; i < state->metadata->sets_len; i++) { + if (!pb_encode_tag_for_field(stream, field)) + return false; + + state->i = i; + zmk_behaviors_BehaviorBindingParametersCustomSet msg = + zmk_behaviors_BehaviorBindingParametersCustomSet_init_zero; + msg.param1.funcs.encode = encode_value_description; + msg.param1.arg = state; + msg.param2.funcs.encode = encode_value_description; + msg.param2.arg = state; + ret = pb_encode_submessage(stream, &zmk_behaviors_BehaviorBindingParametersCustomSet_msg, + &msg); + if (!ret) { + LOG_WRN("Failed to encode submessage for set %d", i); + break; + } + } + + return ret; +} + +static struct encode_custom_sets_state state = {}; + +zmk_Response get_behavior_details(const zmk_Request *req) { + uint32_t behavior_id = req->subsystem.behaviors.request_type.get_behavior_details.behavior_id; + + uint16_t zbm_count; + STRUCT_SECTION_COUNT(zmk_behavior_ref, &zbm_count); + if (behavior_id < 0 || behavior_id >= zbm_count) { + LOG_ERR("Have %d zbm", zbm_count); + // TODO Error type: + zmk_Response resp = zmk_Response_init_zero; + return resp; + } + + struct zmk_behavior_ref *zbm; + STRUCT_SECTION_GET(zmk_behavior_ref, behavior_id, &zbm); + + zmk_behaviors_GetBehaviorDetailsResponse resp = + (zmk_behaviors_GetBehaviorDetailsResponse){.id = behavior_id}; + strcpy(resp.friendly_name, zbm->friendly_name); + + struct behavior_parameter_metadata desc; + behavior_get_parameter_domains(zbm->device, &desc); + + switch (desc.type) { + case BEHAVIOR_PARAMETER_METADATA_STANDARD: + if (desc.standard.param1 > 0) { + LOG_DBG("Had a param1"); + resp.has_parameters = true; + resp.parameters.which_parameter_types = + zmk_behaviors_BehaviorBindingParameters_standard_tag; + resp.parameters.parameter_types.standard.has_param1 = true; + resp.parameters.parameter_types.standard.param1.domain = desc.standard.param1; + // resp.parameters.parameter_types.standard.param1.name = "Name"; + } + + if (desc.standard.param2 > 0) { + resp.has_parameters = true; + resp.parameters.which_parameter_types = + zmk_behaviors_BehaviorBindingParameters_standard_tag; + resp.parameters.parameter_types.standard.has_param2 = true; + resp.parameters.parameter_types.standard.param2.domain = desc.standard.param2; + } + break; + case BEHAVIOR_PARAMETER_METADATA_CUSTOM: + resp.has_parameters = true; + state.metadata = desc.custom; + + resp.parameters.which_parameter_types = zmk_behaviors_BehaviorBindingParameters_custom_tag; + resp.parameters.parameter_types.custom.param_sets.funcs.encode = encode_custom_sets; + resp.parameters.parameter_types.custom.param_sets.arg = &state; + break; + default: + break; + } + + return BEHAVIOR_RESPONSE(get_behavior_details, resp); +} + +ZMK_RPC_SUBSYSTEM_HANDLER(behaviors, list_all_behaviors, false); +ZMK_RPC_SUBSYSTEM_HANDLER(behaviors, get_behavior_details, true); diff --git a/app/src/studio/core.c b/app/src/studio/core.c new file mode 100644 index 000000000000..5a527ce7be90 --- /dev/null +++ b/app/src/studio/core.c @@ -0,0 +1,20 @@ + +#include + +ZMK_EVENT_IMPL(zmk_studio_core_lock_state_changed); + +static enum zmk_studio_core_lock_state state = ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED; + +enum zmk_studio_core_lock_state zmk_studio_core_get_lock_state(void) { return state; } + +static void set_state(enum zmk_studio_core_lock_state new_state) { + state = new_state; + + raise_zmk_studio_core_lock_state_changed( + (struct zmk_studio_core_lock_state_changed){.state = state}); +} + +void zmk_studio_core_unlock() { set_state(ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED); } +void zmk_studio_core_lock() { set_state(ZMK_STUDIO_CORE_LOCK_STATE_LOCKED); } +void zmk_studio_core_initiate_unlock() { set_state(ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKING); } +void zmk_studio_core_complete_unlock() { set_state(ZMK_STUDIO_CORE_LOCK_STATE_UNLOCKED); } \ No newline at end of file diff --git a/app/src/studio/core_subsystem.c b/app/src/studio/core_subsystem.c index 846b2c99b977..e5d66afb1c04 100644 --- a/app/src/studio/core_subsystem.c +++ b/app/src/studio/core_subsystem.c @@ -2,17 +2,41 @@ #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +#include #include ZMK_RPC_SUBSYSTEM(core) #define CORE_RESPONSE(type, ...) ZMK_RPC_RESPONSE(core, type, __VA_ARGS__) -zmk_Response get_lock_status(const zmk_Request *req) { - zmk_core_GetLockStatusResponse resp = zmk_core_GetLockStatusResponse_init_zero; - resp.locked = true; +zmk_Response get_lock_state(const zmk_Request *req) { + zmk_core_LockState resp = zmk_studio_core_get_lock_state(); - return CORE_RESPONSE(get_lock_status, resp); + return CORE_RESPONSE(get_lock_state, resp); } -ZMK_RPC_SUBSYSTEM_HANDLER(core, get_lock_status); +zmk_Response request_unlock(const zmk_Request *req) { + // TODO: Actually request it, instead of unilaterally unlocking! + + zmk_studio_core_unlock(); + + return ZMK_RPC_NO_RESPONSE(); +} + +ZMK_RPC_SUBSYSTEM_HANDLER(core, get_lock_state, false); +ZMK_RPC_SUBSYSTEM_HANDLER(core, request_unlock, false); + +static int core_event_mapper(const zmk_event_t *eh, zmk_Notification *n) { + struct zmk_studio_core_lock_state_changed *lock_ev = as_zmk_studio_core_lock_state_changed(eh); + + if (!lock_ev) { + return -ENOTSUP; + } + + LOG_DBG("Mapped a lock state event properly"); + + *n = ZMK_RPC_NOTIFICATION(core, lock_state_changed, lock_ev->state); + return 0; +} + +ZMK_RPC_EVENT_MAPPER(core, core_event_mapper, zmk_studio_core_lock_state_changed); diff --git a/app/src/studio/gatt_rpc_interface.c b/app/src/studio/gatt_rpc_interface.c index 89e33376786c..7482ec27ee07 100644 --- a/app/src/studio/gatt_rpc_interface.c +++ b/app/src/studio/gatt_rpc_interface.c @@ -11,6 +11,7 @@ #include #include +#include #include #include "uuid.h" @@ -23,7 +24,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); -NET_BUF_SIMPLE_DEFINE_STATIC(rx_buf, CONFIG_ZMK_STUDIO_MAX_MSG_SIZE); +NET_BUF_SIMPLE_DEFINE_STATIC(rx_buf, CONFIG_ZMK_STUDIO_MSG_MAX_SIZE); static enum studio_framing_state rpc_framing_state; @@ -80,8 +81,12 @@ BT_GATT_SERVICE_DEFINE( write_rpc_req, NULL), BT_GATT_CCC(rpc_ccc_cfg_changed, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT)); +static inline void send_response_to_all(const zmk_Response *response) { + send_response(NULL, response); +} + static void send_response(struct bt_conn *conn, const zmk_Response *response) { - uint8_t resp_data[CONFIG_ZMK_STUDIO_MAX_MSG_SIZE]; + uint8_t resp_data[CONFIG_ZMK_STUDIO_MSG_MAX_SIZE]; pb_ostream_t stream = pb_ostream_from_buffer(resp_data, ARRAY_SIZE(resp_data)); @@ -95,7 +100,7 @@ static void send_response(struct bt_conn *conn, const zmk_Response *response) { size_t out_size = stream.bytes_written; - uint8_t resp_frame[CONFIG_ZMK_STUDIO_MAX_MSG_SIZE * 2]; + uint8_t resp_frame[CONFIG_ZMK_STUDIO_MSG_MAX_SIZE * 2]; int frame_encoded_size = studio_framing_encode_frame(resp_data, out_size, resp_frame, sizeof(resp_frame)); @@ -105,14 +110,27 @@ static void send_response(struct bt_conn *conn, const zmk_Response *response) { return; } - struct bt_gatt_notify_params notify_params = { - .attr = &rpc_interface.attrs[1], - .data = &resp_frame, - .len = frame_encoded_size, - }; + int notify_size = frame_encoded_size; + 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; + } + + int offset = 0; + while (offset < frame_encoded_size) { + struct bt_gatt_notify_params notify_params = { + .attr = &rpc_interface.attrs[1], + .data = &resp_frame[offset], + .len = notify_size, + }; - int err = bt_gatt_notify_cb(conn, ¬ify_params); - if (err < 0) { - LOG_WRN("Failed to notify the response %d", err); + offset += notify_size; + + int err = bt_gatt_notify_cb(conn, ¬ify_params); + if (err < 0) { + LOG_WRN("Failed to notify the response %d", err); + } } -} \ No newline at end of file +} + +// ZMK_RPC_NOTIFICATION_PUBLISHER(gatt_rpc_interface, send_response_to_all); diff --git a/app/src/studio/rpc.c b/app/src/studio/rpc.c index 18288831e62d..a29c073a87d1 100644 --- a/app/src/studio/rpc.c +++ b/app/src/studio/rpc.c @@ -5,8 +5,12 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +#include +#include #include +ZMK_EVENT_IMPL(zmk_studio_rpc_notification); + zmk_Response zmk_rpc_handle_request(const zmk_Request *req) { LOG_DBG("Request for subsystem %d", req->which_subsystem); STRUCT_SECTION_FOREACH(zmk_rpc_subsystem, sub) { @@ -61,4 +65,20 @@ static int zmk_rpc_init(void) { return 0; } -SYS_INIT(zmk_rpc_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); \ No newline at end of file +SYS_INIT(zmk_rpc_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); + +static int studio_rpc_listener_cb(const zmk_event_t *eh) { + zmk_Notification n = zmk_Notification_init_zero; + STRUCT_SECTION_FOREACH(zmk_rpc_event_mapper, mapper) { + int ret = mapper->func(eh, &n); + if (ret >= 0) { + raise_zmk_studio_rpc_notification( + (struct zmk_studio_rpc_notification){.notification = n}); + break; + } + } + + return ZMK_EV_EVENT_BUBBLE; +} + +ZMK_LISTENER(studio_rpc, studio_rpc_listener_cb); \ No newline at end of file diff --git a/app/src/studio/uart_rpc_interface.c b/app/src/studio/uart_rpc_interface.c index e876a03fe4f2..21bc3bebf403 100644 --- a/app/src/studio/uart_rpc_interface.c +++ b/app/src/studio/uart_rpc_interface.c @@ -26,42 +26,46 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static const struct device *const uart_dev = DEVICE_DT_GET(UART_DEVICE_NODE); -NET_BUF_SIMPLE_DEFINE_STATIC(rx_buf, CONFIG_ZMK_STUDIO_MAX_MSG_SIZE); +NET_BUF_SIMPLE_DEFINE_STATIC(rx_buf, CONFIG_ZMK_STUDIO_MSG_MAX_SIZE); /* queue to store up to 10 messages (aligned to 4-byte boundary) */ K_MSGQ_DEFINE(req_msgq, sizeof(zmk_Request), 10, 4); static enum studio_framing_state rpc_framing_state; -void rpc_cb(struct k_work *work) { - zmk_Request req; - while (k_msgq_get(&req_msgq, &req, K_NO_WAIT) >= 0) { - zmk_Response resp = zmk_rpc_handle_request(&req); - - uint8_t payload[CONFIG_ZMK_STUDIO_MAX_MSG_SIZE]; +static void send_response(const zmk_Response *resp) { + uint8_t payload[CONFIG_ZMK_STUDIO_MSG_MAX_SIZE]; - pb_ostream_t stream = pb_ostream_from_buffer(payload, ARRAY_SIZE(payload)); + pb_ostream_t stream = pb_ostream_from_buffer(payload, ARRAY_SIZE(payload)); - /* Now we are ready to encode the message! */ - bool status = pb_encode(&stream, &zmk_Response_msg, &resp); + /* Now we are ready to encode the message! */ + bool status = pb_encode(&stream, &zmk_Response_msg, resp); - size_t how_much = stream.bytes_written; + size_t how_much = stream.bytes_written; - LOG_HEXDUMP_DBG(payload, how_much, "Encoded payload"); + LOG_HEXDUMP_DBG(payload, how_much, "Encoded payload"); - uart_poll_out(uart_dev, FRAMING_SOF); - for (int i = 0; i < how_much; i++) { - switch (payload[i]) { - case FRAMING_SOF: - case FRAMING_ESC: - case FRAMING_EOF: - uart_poll_out(uart_dev, FRAMING_ESC); - break; - } - uart_poll_out(uart_dev, payload[i]); + uart_poll_out(uart_dev, FRAMING_SOF); + for (int i = 0; i < how_much; i++) { + switch (payload[i]) { + case FRAMING_SOF: + case FRAMING_ESC: + case FRAMING_EOF: + uart_poll_out(uart_dev, FRAMING_ESC); + break; } + uart_poll_out(uart_dev, payload[i]); + } - uart_poll_out(uart_dev, FRAMING_EOF); + uart_poll_out(uart_dev, FRAMING_EOF); +} + +static void rpc_cb(struct k_work *work) { + zmk_Request req; + while (k_msgq_get(&req_msgq, &req, K_NO_WAIT) >= 0) { + zmk_Response resp = zmk_rpc_handle_request(&req); + + send_response(&resp); } } @@ -130,3 +134,5 @@ static int uart_rpc_interface_init(void) { } SYS_INIT(uart_rpc_interface_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); + +ZMK_RPC_NOTIFICATION_PUBLISHER(uart_rpc_interface, send_response); diff --git a/app/west.yml b/app/west.yml index 1b50247786b4..7bb89a77f718 100644 --- a/app/west.yml +++ b/app/west.yml @@ -29,5 +29,9 @@ manifest: - openthread - edtt - trusted-firmware-m + - name: nanopb + revision: 65cbefb4695bc7af1cb733ced99618afb3586b20 + path: modules/lib/nanopb + remote: zephyrproject-rtos self: west-commands: scripts/west-commands.yml