From 5591a68b6594e1f32849051cc305a1b937a1d98a Mon Sep 17 00:00:00 2001 From: Joel Challis Date: Thu, 19 Feb 2026 11:03:26 +0000 Subject: [PATCH] Allow custom data sync for community modules (#25955) * Allow custom data sync for community modules * Stub out community_config.h codegen * Fix SPLIT_TRANSACTION_RPC logic --- builddefs/build_keyboard.mk | 17 +++++- data/constants/module_hooks/1.1.2.hjson | 3 + docs/features/community_modules.md | 8 +++ .../qmk/cli/generate/community_modules.py | 55 +++++++++++++++++++ modules/qmk/split_data_sync/config.h | 5 ++ modules/qmk/split_data_sync/qmk_module.json | 5 ++ modules/qmk/split_data_sync/split_data_sync.c | 40 ++++++++++++++ quantum/split_common/transaction_id_define.h | 13 ++++- quantum/split_common/transactions.c | 12 ++-- quantum/split_common/transport.h | 9 +-- 10 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 data/constants/module_hooks/1.1.2.hjson create mode 100644 modules/qmk/split_data_sync/config.h create mode 100644 modules/qmk/split_data_sync/qmk_module.json create mode 100644 modules/qmk/split_data_sync/split_data_sync.c diff --git a/builddefs/build_keyboard.mk b/builddefs/build_keyboard.mk index aa527f79b5..acfad61e7d 100644 --- a/builddefs/build_keyboard.mk +++ b/builddefs/build_keyboard.mk @@ -187,6 +187,11 @@ include $(COMMUNITY_RULES_MK) ifneq ($(COMMUNITY_MODULES),) +$(INTERMEDIATE_OUTPUT)/src/community_config.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-community-config-h -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_config.h $(KEYMAP_JSON)) + @$(BUILD_CMD) + $(INTERMEDIATE_OUTPUT)/src/community_modules.h: $(KEYMAP_JSON) $(DD_CONFIG_FILES) @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) $(eval CMD=$(QMK_BIN) generate-community-modules-h -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(KEYMAP_JSON)) @@ -217,9 +222,15 @@ $(INTERMEDIATE_OUTPUT)/src/rgb_matrix_community_modules.inc: $(KEYMAP_JSON) $(DD $(eval CMD=$(QMK_BIN) generate-rgb-matrix-community-modules-inc -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/rgb_matrix_community_modules.inc $(KEYMAP_JSON)) @$(BUILD_CMD) +$(INTERMEDIATE_OUTPUT)/src/split_transaction_id_community_modules.inc: $(KEYMAP_JSON) $(DD_CONFIG_FILES) + @$(SILENT) || printf "$(MSG_GENERATING) $@" | $(AWK_CMD) + $(eval CMD=$(QMK_BIN) generate-split-transaction-id-community-modules-inc -kb $(KEYBOARD) --quiet --output $(INTERMEDIATE_OUTPUT)/src/split_transaction_id_community_modules.inc $(KEYMAP_JSON)) + @$(BUILD_CMD) + +COMMUNITY_CONFIG_H = $(INTERMEDIATE_OUTPUT)/src/community_config.h SRC += $(INTERMEDIATE_OUTPUT)/src/community_modules.c -generated-files: $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(INTERMEDIATE_OUTPUT)/src/community_modules.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h $(INTERMEDIATE_OUTPUT)/src/led_matrix_community_modules.inc $(INTERMEDIATE_OUTPUT)/src/rgb_matrix_community_modules.inc +generated-files: $(INTERMEDIATE_OUTPUT)/src/community_config.h $(INTERMEDIATE_OUTPUT)/src/community_modules.h $(INTERMEDIATE_OUTPUT)/src/community_modules.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.c $(INTERMEDIATE_OUTPUT)/src/community_modules_introspection.h $(INTERMEDIATE_OUTPUT)/src/led_matrix_community_modules.inc $(INTERMEDIATE_OUTPUT)/src/rgb_matrix_community_modules.inc $(INTERMEDIATE_OUTPUT)/src/split_transaction_id_community_modules.inc endif @@ -320,6 +331,10 @@ define config_h_community_module_appender endef $(foreach module,$(COMMUNITY_MODULE_PATHS),$(eval $(call config_h_community_module_appender,$(module)))) +ifneq ($(COMMUNITY_CONFIG_H),) + CONFIG_H += $(COMMUNITY_CONFIG_H) +endif + ifneq ("$(wildcard $(KEYBOARD_PATH_5)/config.h)","") CONFIG_H += $(KEYBOARD_PATH_5)/config.h endif diff --git a/data/constants/module_hooks/1.1.2.hjson b/data/constants/module_hooks/1.1.2.hjson new file mode 100644 index 0000000000..3d4d100f83 --- /dev/null +++ b/data/constants/module_hooks/1.1.2.hjson @@ -0,0 +1,3 @@ +{ + // This version exists to signify addition of split data sync support. +} diff --git a/docs/features/community_modules.md b/docs/features/community_modules.md index eff07c939a..c8adf58674 100644 --- a/docs/features/community_modules.md +++ b/docs/features/community_modules.md @@ -95,6 +95,10 @@ The use of `features` matches the definition normally provided within `keyboard. The `keycodes` array allows a module to provide new keycodes (as well as corresponding aliases) to a keymap. +### `config.h` + +This file will be automatically added to the build as if it were present in the keyboard or keymap. + ### `rules.mk` / `post_rules.mk` These two files follows standard QMK build system logic, allowing for `Makefile`-style customisation as if it were present in the keyboard or keymap. @@ -131,6 +135,10 @@ This file defines LED matrix effects in the same form as used with `led_matrix_k This file defines RGB matrix effects in the same form as used with `rgb_matrix_kb.inc` and `rgb_matrix_user.inc` (see [Custom RGB Matrix Effects](rgb_matrix#custom-rgb-matrix-effects)). Effect mode names are prepended with `RGB_MATRIX_COMMUNITY_MODULE_`. +### Custom split keyboard data sync + +Defines follow the convention, `SPLIT_TRANSACTION_IDS_MODULE_` (see [Custom LED Matrix Effects](split_keyboard#custom-data-sync)). + ### Compatible APIs Community Modules may provide specializations for the following APIs: diff --git a/lib/python/qmk/cli/generate/community_modules.py b/lib/python/qmk/cli/generate/community_modules.py index a5ab61f9bd..22bb7c0a13 100644 --- a/lib/python/qmk/cli/generate/community_modules.py +++ b/lib/python/qmk/cli/generate/community_modules.py @@ -194,6 +194,38 @@ def generate_community_modules_rules_mk(cli): cli.log.info('Wrote rules.mk to %s.', cli.args.output) +@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') +@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") +@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_config.h for.') +@cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') +@cli.subcommand('Creates a community_config.h from a keymap.json file.') +def generate_community_config_h(cli): + """Creates a community_config.h from a keymap.json file + """ + if cli.args.output and cli.args.output.name == '-': + cli.args.output = None + + lines = [ + GPL2_HEADER_C_LIKE, + GENERATED_HEADER_C_LIKE, + '#pragma once', + '', + ] + + modules = get_modules(cli.args.keyboard, cli.args.filename) + if len(modules) > 0: + lines.append('// Split transactions') + for module in modules: + lines.extend([ + f'#ifdef SPLIT_TRANSACTION_IDS_MODULE_{Path(module).name.upper()}', + '# define SPLIT_TRANSACTION_RPC', + '#endif', + ]) + lines.append('') + + dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) + + @cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') @cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") @cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate community_modules.h for.') @@ -339,3 +371,26 @@ def generate_rgb_matrix_community_modules_inc(cli): """Creates an rgb_matrix_community_modules.inc from a keymap.json file """ _generate_include_per_module(cli, 'rgb_matrix_module.inc') + + +@cli.argument('-o', '--output', arg_only=True, type=qmk.path.normpath, help='File to write to') +@cli.argument('-q', '--quiet', arg_only=True, action='store_true', help="Quiet mode, only output error messages") +@cli.argument('-kb', '--keyboard', arg_only=True, type=keyboard_folder, completer=keyboard_completer, help='Keyboard to generate split_transaction_id_community_modules.inc for.') +@cli.argument('filename', nargs='?', type=qmk.path.FileType('r'), arg_only=True, completer=FilesCompleter('.json'), help='Configurator JSON file') +@cli.subcommand('Creates an split_transaction_id_community_modules.inc from a keymap.json file.') +def generate_split_transaction_id_community_modules_inc(cli): + """Creates an split_transaction_id_community_modules.inc from a keymap.json file + """ + if cli.args.output and cli.args.output.name == '-': + cli.args.output = None + + lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE] + + for module in get_modules(cli.args.keyboard, cli.args.filename): + lines.extend([ + f'#ifdef SPLIT_TRANSACTION_IDS_MODULE_{Path(module).name.upper()}', + f' SPLIT_TRANSACTION_IDS_MODULE_{Path(module).name.upper()},', + '#endif', + ]) + + dump_lines(cli.args.output, lines, cli.args.quiet, remove_repeated_newlines=True) diff --git a/modules/qmk/split_data_sync/config.h b/modules/qmk/split_data_sync/config.h new file mode 100644 index 0000000000..22a8505489 --- /dev/null +++ b/modules/qmk/split_data_sync/config.h @@ -0,0 +1,5 @@ +// Copyright 2026 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#define SPLIT_TRANSACTION_IDS_MODULE_SPLIT_DATA_SYNC EXAMPLE_MODULE_SYNC_A diff --git a/modules/qmk/split_data_sync/qmk_module.json b/modules/qmk/split_data_sync/qmk_module.json new file mode 100644 index 0000000000..286cf29248 --- /dev/null +++ b/modules/qmk/split_data_sync/qmk_module.json @@ -0,0 +1,5 @@ +{ + "module_name": "Example split data sync", + "maintainer": "QMK Maintainers", + "license": "GPL-2.0-or-later" +} diff --git a/modules/qmk/split_data_sync/split_data_sync.c b/modules/qmk/split_data_sync/split_data_sync.c new file mode 100644 index 0000000000..cf0a93c350 --- /dev/null +++ b/modules/qmk/split_data_sync/split_data_sync.c @@ -0,0 +1,40 @@ +// Copyright 2026 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include "debug.h" +#include "timer.h" +#include "transactions.h" + +typedef struct _master_to_slave_t { + int m2s_data; +} master_to_slave_t; + +typedef struct _slave_to_master_t { + int s2m_data; +} slave_to_master_t; + +static void module_sync_slave_handler(uint8_t in_buflen, const void *in_data, uint8_t out_buflen, void *out_data) { + const master_to_slave_t *m2s = (const master_to_slave_t *)in_data; + slave_to_master_t *s2m = (slave_to_master_t *)out_data; + s2m->s2m_data = m2s->m2s_data + 5; // whatever comes in, add 5 so it can be sent back +} + +void keyboard_post_init_split_data_sync(void) { + transaction_register_rpc(EXAMPLE_MODULE_SYNC_A, module_sync_slave_handler); +} + +void housekeeping_task_split_data_sync(void) { + if (is_keyboard_master()) { + // Interact with slave every 500ms + static uint32_t last_sync = 0; + if (timer_elapsed32(last_sync) > 500) { + master_to_slave_t m2s = {6}; + slave_to_master_t s2m = {0}; + if (transaction_rpc_exec(EXAMPLE_MODULE_SYNC_A, sizeof(m2s), &m2s, sizeof(s2m), &s2m)) { + last_sync = timer_read32(); + dprintf("Slave value: %d\n", s2m.s2m_data); // this will now be 11, as the slave adds 5 + } else { + dprint("Slave sync failed!\n"); + } + } + } +} diff --git a/quantum/split_common/transaction_id_define.h b/quantum/split_common/transaction_id_define.h index 694737868a..c383b26adb 100644 --- a/quantum/split_common/transaction_id_define.h +++ b/quantum/split_common/transaction_id_define.h @@ -18,6 +18,10 @@ #include "compiler_support.h" +#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +# define SPLIT_TRANSACTION_RPC +#endif + enum serial_transaction_id { #ifdef USE_I2C I2C_EXECUTE_CALLBACK, @@ -99,12 +103,12 @@ enum serial_transaction_id { PUT_ACTIVITY, #endif // SPLIT_ACTIVITY_ENABLE -#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#if defined(SPLIT_TRANSACTION_RPC) PUT_RPC_INFO, PUT_RPC_REQ_DATA, EXECUTE_RPC, GET_RPC_RESP_DATA, -#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#endif // defined(SPLIT_TRANSACTION_RPC) // keyboard-specific #ifdef SPLIT_TRANSACTION_IDS_KB @@ -116,6 +120,11 @@ enum serial_transaction_id { SPLIT_TRANSACTION_IDS_USER, #endif // SPLIT_TRANSACTION_IDS_USER +// community module specific +#ifdef COMMUNITY_MODULES_ENABLE +# include "split_transaction_id_community_modules.inc" +#endif // COMMUNITY_MODULES_ENABLE + #if defined(OS_DETECTION_ENABLE) && defined(SPLIT_DETECTED_OS_ENABLE) PUT_DETECTED_OS, #endif // defined(OS_DETECTION_ENABLE) && defined(SPLIT_DETECTED_OS_ENABLE) diff --git a/quantum/split_common/transactions.c b/quantum/split_common/transactions.c index 64a81ef31f..ffc3e45c2c 100644 --- a/quantum/split_common/transactions.c +++ b/quantum/split_common/transactions.c @@ -85,11 +85,11 @@ #define transport_read(id, data, length) transport_execute_transaction(id, NULL, 0, data, length) #define transport_exec(id) transport_execute_transaction(id, NULL, 0, NULL, 0) -#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#if defined(SPLIT_TRANSACTION_RPC) // Forward-declare the RPC callback handlers void slave_rpc_info_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *initiator2target_buffer, uint8_t target2initiator_buffer_size, void *target2initiator_buffer); -#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#endif // defined(SPLIT_TRANSACTION_RPC) //////////////////////////////////////////////////// // Helpers @@ -943,12 +943,12 @@ split_transaction_desc_t split_transaction_table[NUM_TOTAL_TRANSACTIONS] = { TRANSACTIONS_DETECTED_OS_REGISTRATIONS // clang-format on -#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#if defined(SPLIT_TRANSACTION_RPC) [PUT_RPC_INFO] = trans_initiator2target_initializer_cb(rpc_info, slave_rpc_info_callback), [PUT_RPC_REQ_DATA] = trans_initiator2target_initializer(rpc_m2s_buffer), [EXECUTE_RPC] = trans_initiator2target_initializer_cb(rpc_info.payload.transaction_id, slave_rpc_exec_callback), [GET_RPC_RESP_DATA] = trans_target2initiator_initializer(rpc_s2m_buffer), -#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#endif // defined(SPLIT_TRANSACTION_RPC) }; bool transactions_master(matrix_row_t master_matrix[], matrix_row_t slave_matrix[]) { @@ -996,7 +996,7 @@ void transactions_slave(matrix_row_t master_matrix[], matrix_row_t slave_matrix[ TRANSACTIONS_DETECTED_OS_SLAVE(); } -#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#if defined(SPLIT_TRANSACTION_RPC) void transaction_register_rpc(int8_t transaction_id, slave_callback_t callback) { // Prevent invoking RPC on QMK core sync data @@ -1073,4 +1073,4 @@ void slave_rpc_exec_callback(uint8_t initiator2target_buffer_size, const void *i } } -#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#endif // defined(SPLIT_TRANSACTION_RPC) diff --git a/quantum/split_common/transport.h b/quantum/split_common/transport.h index fbd87ca312..e6caca4fa8 100644 --- a/quantum/split_common/transport.h +++ b/quantum/split_common/transport.h @@ -22,6 +22,7 @@ #include "progmem.h" #include "action_layer.h" #include "matrix.h" +#include "transaction_id_define.h" #ifndef RPC_M2S_BUFFER_SIZE # define RPC_M2S_BUFFER_SIZE 32 @@ -132,7 +133,7 @@ typedef struct _split_slave_activity_sync_t { } split_slave_activity_sync_t; #endif // defined(SPLIT_ACTIVITY_ENABLE) -#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#if defined(SPLIT_TRANSACTION_RPC) typedef struct _rpc_sync_info_t { uint8_t checksum; struct { @@ -141,7 +142,7 @@ typedef struct _rpc_sync_info_t { uint8_t s2m_length; } payload; } rpc_sync_info_t; -#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#endif // defined(SPLIT_TRANSACTION_RPC) #if defined(OS_DETECTION_ENABLE) && defined(SPLIT_DETECTED_OS_ENABLE) # include "os_detection.h" @@ -222,11 +223,11 @@ typedef struct _split_shared_memory_t { split_slave_activity_sync_t activity_sync; #endif // defined(SPLIT_ACTIVITY_ENABLE) -#if defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#if defined(SPLIT_TRANSACTION_RPC) rpc_sync_info_t rpc_info; uint8_t rpc_m2s_buffer[RPC_M2S_BUFFER_SIZE]; uint8_t rpc_s2m_buffer[RPC_S2M_BUFFER_SIZE]; -#endif // defined(SPLIT_TRANSACTION_IDS_KB) || defined(SPLIT_TRANSACTION_IDS_USER) +#endif // defined(SPLIT_TRANSACTION_RPC) #if defined(OS_DETECTION_ENABLE) && defined(SPLIT_DETECTED_OS_ENABLE) os_variant_t detected_os;