diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index 9c869586255..38f9f91ccb0 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -35,6 +35,7 @@ GENERIC_FEATURES = \ HAPTIC \ KEY_LOCK \ KEY_OVERRIDE \ + LAMPARRAY \ LEADER \ MAGIC \ MOUSEKEY \ diff --git a/lib/python/qmk/cli/generate/config_h.py b/lib/python/qmk/cli/generate/config_h.py index 00fb1d9585f..776e68a8d28 100755 --- a/lib/python/qmk/cli/generate/config_h.py +++ b/lib/python/qmk/cli/generate/config_h.py @@ -85,6 +85,27 @@ def generate_matrix_masked(kb_info_json, config_h_lines): config_h_lines.append(generate_define('MATRIX_MASKED')) +def generate_estimated_dimensions(kb_info_json, config_h_lines): + """Try and guess physical keyboard dimensions from the declared layouts + """ + if 'layouts' in kb_info_json: + width = 0 + height = 0 + for layout_data in kb_info_json['layouts'].values(): + for key in layout_data['layout']: + x = key.get('x', 0) + y = key.get('y', 0) + w = key.get('w', 1) + h = key.get('h', 1) + + width = max(width, x + w) + height = max(height, y + h) + + # sizes are in micrometers - assume 1u = 19.05mm + config_h_lines.append(generate_define('ESTIMATED_KEYBOARD_WIDTH', width * 19050)) + config_h_lines.append(generate_define('ESTIMATED_KEYBOARD_HEIGHT', height * 19050)) + + def generate_config_items(kb_info_json, config_h_lines): """Iterate through the info_config map to generate basic config values. """ @@ -202,6 +223,8 @@ def generate_config_h(cli): generate_matrix_masked(kb_info_json, config_h_lines) + generate_estimated_dimensions(kb_info_json, config_h_lines) + if 'matrix_pins' in kb_info_json: config_h_lines.append(matrix_pins(kb_info_json['matrix_pins'])) diff --git a/quantum/keyboard.c b/quantum/keyboard.c index b5fa1a6e2e9..6dfafe43371 100644 --- a/quantum/keyboard.c +++ b/quantum/keyboard.c @@ -135,6 +135,9 @@ along with this program. If not, see . #ifdef WPM_ENABLE # include "wpm.h" #endif +#ifdef LAMPARRAY_ENABLE +# include "lamparray.h" +#endif static uint32_t last_input_modification_time = 0; uint32_t last_input_activity_time(void) { @@ -407,6 +410,9 @@ void keyboard_init(void) { #ifdef SPLIT_KEYBOARD split_pre_init(); #endif +#ifdef LAMPARRAY_ENABLE + lamparray_init(); +#endif #ifdef ENCODER_ENABLE encoder_init(); #endif @@ -629,6 +635,10 @@ void quantum_task(void) { #ifdef SECURE_ENABLE secure_task(); #endif + +#ifdef LAMPARRAY_ENABLE + lamparray_task(); +#endif } /** \brief Main task that is repeatedly called as fast as possible. */ diff --git a/quantum/lamparray/lamparray.c b/quantum/lamparray/lamparray.c new file mode 100644 index 00000000000..09f412d8ca7 --- /dev/null +++ b/quantum/lamparray/lamparray.c @@ -0,0 +1,219 @@ +// Copyright 2024 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include // for memcpy +#include "lamparray.h" +#include "lamparray_surface.h" +#include "keycodes.h" +#include "keymap_introspection.h" +#include "action_layer.h" + +// Defaults are generated from info.json layout content +#ifndef LAMPARRAY_WIDTH +# define LAMPARRAY_WIDTH ESTIMATED_KEYBOARD_WIDTH +#endif +#ifndef LAMPARRAY_HEIGHT +# define LAMPARRAY_HEIGHT ESTIMATED_KEYBOARD_HEIGHT +#endif +#ifndef LAMPARRAY_DEPTH +# define LAMPARRAY_DEPTH 30000 +#endif +#ifndef LAMPARRAY_KIND +# define LAMPARRAY_KIND LAMPARRAY_KIND_KEYBOARD +#endif + +#ifdef RGB_MATRIX_ENABLE +# include "rgb_matrix.h" +# define LAMPARRAY_RED_LEVELS 255 +# define LAMPARRAY_GREEN_LEVELS 255 +# define LAMPARRAY_BLUE_LEVELS 255 +# define LAMPARRAY_INTENSITY_LEVELS 1 +# define LAMPARRAY_LAMP_COUNT RGB_MATRIX_LED_COUNT +# define LAMPARRAY_UPDATE_INTERVAL (RGB_MATRIX_LED_FLUSH_LIMIT * 1000) +#endif + +//**************************************************************************** +// utils + +/** + * \brief Query a HID usage for a given location + * + * This can be requested while the user is changing layers. This is mitigated somewhat by assuming the default layer changes infrequently. + * This is currently accepted as a limitation as there is no method to invalidate the hosts view of the data. + */ +uint8_t lamparray_binding_at_keymap_location(uint8_t row, uint8_t col) { + uint16_t keycode = keycode_at_keymap_location(get_highest_layer(default_layer_state), row, col); + (void)keycode; +#if LAMPARRAY_KIND == LAMPARRAY_KIND_KEYBOARD + // Basic QMK keycodes currently map directly to Keyboard UsagePage so safe to return without added indirection + // Mousekeys are ignored due to values overlap Keyboard UsagePage + if (IS_BASIC_KEYCODE(keycode) || IS_MODIFIER_KEYCODE(keycode)) { + return keycode; + } +#elif LAMPARRAY_KIND == LAMPARRAY_KIND_MOUSE + // Usages from the Button UsagePage (0x09) in the range of Button1 (0x01) to Button5 (0x05) inclusive + if ((code) >= KC_MS_BTN1 && (code) <= KC_MS_BTN5) { + return keycode - KC_MS_BTN1 + 1; + } +#endif + return 0; +} + +//**************************************************************************** +// cache + +static uint8_t input_binding_cache[LAMPARRAY_LAMP_COUNT]; + +void lamparray_update_cache(void) { + for (uint8_t lamp_id = 0; lamp_id < LAMPARRAY_LAMP_COUNT; lamp_id++) { + input_binding_cache[lamp_id] = lamparray_get_lamp_binding_impl(lamp_id); + } +} + +uint8_t lamparray_get_lamp_binding(uint16_t lamp_id) { + return input_binding_cache[lamp_id]; +} + +//**************************************************************************** +// queue + +#ifndef LAMPARRAY_REQUEST_QUEUE_SIZE +# define LAMPARRAY_REQUEST_QUEUE_SIZE 5 +#endif + +universal_lamparray_response_t request_queue[LAMPARRAY_REQUEST_QUEUE_SIZE] = {0}; +uint8_t queue_size = 0; + +void lamparray_queue_request(universal_lamparray_response_t* report) { + // get next slot + universal_lamparray_response_t* target = &request_queue[queue_size++]; + + // copy data + memcpy(target, report, sizeof(universal_lamparray_response_t)); +} + +void lamparray_handle_queue(void) { + for (uint8_t id = 0; id < queue_size; id++) { + universal_lamparray_response_t* report = &request_queue[id]; + switch (report->report_id) { + case LAMPARRAY_REPORT_ID_RANGE_UPDATE: + lamparray_set_range(&report->range_update); + break; + case LAMPARRAY_REPORT_ID_MULTI_UPDATE: + lamparray_set_items(&report->multi_update); + break; + case LAMPARRAY_REPORT_ID_CONTROL: + lamparray_set_control_response(report->autonomous); + break; + } + } + queue_size = 0; +} + +//**************************************************************************** +// impl + +static uint16_t cur_lamp_id = 0; +static bool is_autonomous = true; + +void lamparray_get_attributes(lamparray_attributes_t* data) { + data->lamp_count = LAMPARRAY_LAMP_COUNT; + data->update_interval = LAMPARRAY_UPDATE_INTERVAL; + data->kind = LAMPARRAY_KIND; + data->bounds.width = LAMPARRAY_WIDTH; + data->bounds.height = LAMPARRAY_HEIGHT; + data->bounds.depth = LAMPARRAY_DEPTH; +} + +void lamparray_get_attributes_response(lamparray_attributes_response_t* data) { + data->lamp_id = cur_lamp_id; + data->update_latency = 1000; + data->is_programmable = 1; + data->input_binding = lamparray_get_lamp_binding(cur_lamp_id); + + data->levels.red = LAMPARRAY_RED_LEVELS; + data->levels.green = LAMPARRAY_GREEN_LEVELS; + data->levels.blue = LAMPARRAY_BLUE_LEVELS; + data->levels.intensity = LAMPARRAY_INTENSITY_LEVELS; + + lamparray_get_lamp_impl(cur_lamp_id, data); + + // Automatic address pointer incrementing - 26.8.1 LampAttributesRequestReport + cur_lamp_id++; + if (cur_lamp_id >= LAMPARRAY_LAMP_COUNT) cur_lamp_id = 0; +} + +void lamparray_set_attributes_response(uint16_t lamp_id) { + cur_lamp_id = lamp_id; +} + +void lamparray_set_control_response(uint8_t autonomous) { + is_autonomous = !!autonomous; + + lamparray_surface_enable(!autonomous); +} + +void lamparray_set_range(lamparray_range_update_t* data) { + // Any Lamp*UpdateReports can be ignored - 26.10.1 AutonomousMode + if (is_autonomous) { + return; + } + + // Ensure IDs are within bounds + if ((data->start >= LAMPARRAY_LAMP_COUNT) || (data->end >= LAMPARRAY_LAMP_COUNT)) { + return; + } + + for (uint16_t index = data->start; index <= data->end; index++) { + lamparray_surface_set_item(index, data->color); + } + + // Batch update complete - 26.11 Updating Lamp State + if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) { + lamparray_surface_update_finished(); + } +} + +void lamparray_set_items(lamparray_multi_update_t* data) { + // Any Lamp*UpdateReports can be ignored - 26.10.1 AutonomousMode + if (is_autonomous) { + return; + } + + // Ensure data is within bounds + if (data->count > LAMP_MULTI_UPDATE_LAMP_COUNT) { + return; + } + + for (uint8_t i = 0; i < data->count; i++) { + // Ensure IDs are within bounds + if (data->ids[i] >= LAMPARRAY_LAMP_COUNT) { + continue; + } + lamparray_surface_set_item(data->ids[i], data->colors[i]); + } + + // Batch update complete - 26.11 Updating Lamp State + if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) { + lamparray_surface_update_finished(); + } +} + +//**************************************************************************** +// feature hooks + +void lamparray_init(void) { + lamparray_update_cache(); +} + +void lamparray_task(void) { + lamparray_handle_queue(); + + // TODO: regen cache if dynamic keymap updated? + uint16_t temp = 0; + if (!++temp) lamparray_update_cache(); +} + +// TODO: SRC += ... +#ifdef RGB_MATRIX_ENABLE +# include "lamparray_rgb_matrix.c" +#endif diff --git a/quantum/lamparray/lamparray.h b/quantum/lamparray/lamparray.h new file mode 100644 index 00000000000..a9c7c5a1f28 --- /dev/null +++ b/quantum/lamparray/lamparray.h @@ -0,0 +1,159 @@ +// Copyright 2024 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +#include +#include +#include "util.h" // PACKED + +#define LAMPARRAY_REPORT_ID_ATTRIBUTES 0x01 +#define LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST 0x02 +#define LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE 0x03 +#define LAMPARRAY_REPORT_ID_MULTI_UPDATE 0x04 +#define LAMPARRAY_REPORT_ID_RANGE_UPDATE 0x05 +#define LAMPARRAY_REPORT_ID_CONTROL 0x06 + +// 26.2.1 LampArrayKind Values +#define LAMPARRAY_KIND_UNDEFINED 0x00 +#define LAMPARRAY_KIND_KEYBOARD 0x01 +#define LAMPARRAY_KIND_MOUSE 0x02 +#define LAMPARRAY_KIND_GAMECONTROLLER 0x03 +#define LAMPARRAY_KIND_PERIPHERAL 0x04 +#define LAMPARRAY_KIND_SCENE 0x05 +#define LAMPARRAY_KIND_NOTIFICATION 0x06 +#define LAMPARRAY_KIND_CHASSIS 0x07 +#define LAMPARRAY_KIND_WEARABLE 0x08 +#define LAMPARRAY_KIND_FURNITURE 0x09 +#define LAMPARRAY_KIND_ART 0x0A + +// 26.3.1 LampPurposes Flags +#define LAMP_PURPOSE_CONTROL 0x01 +#define LAMP_PURPOSE_ACCENT 0x02 +#define LAMP_PURPOSE_BRANDING 0x04 +#define LAMP_PURPOSE_STATUS 0x08 +#define LAMP_PURPOSE_ILLUMINATION 0x10 +#define LAMP_PURPOSE_PRESENTATION 0x20 + +// 26.4.1 LampUpdate Flags +#define LAMP_UPDATE_FLAG_COMPLETE 0x01 + +typedef struct PACKED { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t intensity; +} lamp_state_t; + +typedef struct PACKED { + uint16_t lamp_count; + struct { + uint32_t width; + uint32_t height; + uint32_t depth; + } bounds; + uint32_t kind; + uint32_t update_interval; +} lamparray_attributes_t; + +typedef struct PACKED { + uint16_t lamp_id; + struct { + int32_t x; + int32_t y; + int32_t z; + } position; + int32_t update_latency; + int32_t purposes; + lamp_state_t levels; + uint8_t is_programmable; + uint8_t input_binding; +} lamparray_attributes_response_t; + +typedef struct PACKED { + uint8_t flags; + uint16_t start; + uint16_t end; + lamp_state_t color; +} lamparray_range_update_t; + +#define LAMP_MULTI_UPDATE_LAMP_COUNT 8 +typedef struct PACKED { + uint8_t count; + uint8_t flags; + uint16_t ids[LAMP_MULTI_UPDATE_LAMP_COUNT]; + lamp_state_t colors[LAMP_MULTI_UPDATE_LAMP_COUNT]; +} lamparray_multi_update_t; + +typedef struct PACKED universal_lamparray_response_t { + uint8_t report_id; + union { + struct { + uint16_t lamp_id; + }; + struct { + uint8_t autonomous; + }; + lamparray_range_update_t range_update; + lamparray_multi_update_t multi_update; + }; +} universal_lamparray_response_t; + +typedef struct PACKED lamparray_attributes_report_t { + uint8_t report_id; + lamparray_attributes_t attributes; +} lamparray_attributes_report_t; + +typedef struct PACKED lamparray_attributes_response_report_t { + uint8_t report_id; + lamparray_attributes_response_t attributes_response; +} lamparray_attributes_response_report_t; + +/** + * \brief Gets LampArrayAttributesReport data + */ +void lamparray_get_attributes(lamparray_attributes_t* data); + +/** + * \brief Sets LampAttributesRequestReport data + */ +void lamparray_set_attributes_response(uint16_t lamp_id); + +/** + * \brief Gets LampAttributesResponseReport data + */ +void lamparray_get_attributes_response(lamparray_attributes_response_t* data); + +/** + * \brief Sets LampRangeUpdateReport data + */ +void lamparray_set_range(lamparray_range_update_t* data); + +/** + * \brief Sets LampMultiUpdateReport data + */ +void lamparray_set_items(lamparray_multi_update_t* data); + +/** + * \brief Sets LampArrayControlReport data + */ +void lamparray_set_control_response(uint8_t autonomous); + +//**************************************************************************** +// utils + +uint8_t lamparray_binding_at_keymap_location(uint8_t row, uint8_t col); + +void lamparray_queue_request(universal_lamparray_response_t* report); + +//**************************************************************************** +// feature hooks + +void lamparray_init(void); + +void lamparray_task(void); + +//**************************************************************************** +// lighting framework bindings + +void lamparray_get_lamp_impl(uint16_t lamp_id, lamparray_attributes_response_t* data); +uint8_t lamparray_get_lamp_binding_impl(uint16_t lamp_id); diff --git a/quantum/lamparray/lamparray_rgb_matrix.c b/quantum/lamparray/lamparray_rgb_matrix.c new file mode 100644 index 00000000000..ce8265d78d6 --- /dev/null +++ b/quantum/lamparray/lamparray_rgb_matrix.c @@ -0,0 +1,56 @@ +// Copyright 2024 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include "lamparray.h" +#include "lamparray_surface.h" +#include "rgb_matrix.h" + +/** + * \brief Get feature specific lamp info. + * + * Scales the LED config with the assumed RGB Matrix dimensions (224x64), for simplicity, as a completely flat device. + * Assumes all keys are either on the top or bottom of the resulting rectangle. + */ +__attribute__((weak)) void lamparray_get_lamp_impl(uint16_t lamp_id, lamparray_attributes_response_t* data) { + data->position.x = (LAMPARRAY_WIDTH / 224) * g_led_config.point[lamp_id].x; + data->position.y = (LAMPARRAY_HEIGHT / 64) * g_led_config.point[lamp_id].y; + + if (g_led_config.flags[lamp_id] & LED_FLAG_UNDERGLOW) { + data->position.z = LAMPARRAY_DEPTH; + data->purposes = LAMP_PURPOSE_ACCENT; + } else if (g_led_config.flags[lamp_id] & LED_FLAG_INDICATOR) { + data->position.z = 0; + data->purposes = LAMP_PURPOSE_STATUS; + } else { + data->position.z = 0; + data->purposes = LAMP_PURPOSE_CONTROL; + } +} + +/** + * \brief Query a HID usage for a given lamp + */ +__attribute__((weak)) uint8_t lamparray_get_lamp_binding_impl(uint16_t lamp_id) { + for (uint8_t i_row = 0; i_row < MATRIX_ROWS; i_row++) { + for (uint8_t i_col = 0; i_col < MATRIX_COLS; i_col++) { + if (g_led_config.matrix_co[i_row][i_col] == lamp_id) { + return lamparray_binding_at_keymap_location(i_row, i_col); + } + } + } + return 0; +} + +// TODO: temporay binding of storage and render +#include "rgb_matrix/overlay.c" + +void lamparray_surface_enable(bool enable) { + rgb_matrix_overlay_enable(enable); +} + +void lamparray_surface_set_item(uint16_t index, lamp_state_t color) { + rgb_matrix_overlay_set(index, (rgba_t){color.red, color.green, color.blue, color.intensity ? 0xFF : 0}); +} + +void lamparray_surface_update_finished(void) { + rgb_matrix_overlay_flush(); +} diff --git a/quantum/lamparray/lamparray_surface.h b/quantum/lamparray/lamparray_surface.h new file mode 100644 index 00000000000..a558c7875c2 --- /dev/null +++ b/quantum/lamparray/lamparray_surface.h @@ -0,0 +1,18 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#pragma once + +/** + * \brief lamparray_surface_enable + */ +void lamparray_surface_enable(bool enable); + +/** + * \brief lamparray_surface_set_item + */ +void lamparray_surface_set_item(uint16_t index, lamp_state_t color); + +/** + * \brief lamparray_surface_update_finished + */ +void lamparray_surface_update_finished(void); diff --git a/quantum/quantum.h b/quantum/quantum.h index 996e93a12fc..2b82e572566 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -233,6 +233,10 @@ extern layer_state_t layer_state; # include "process_repeat_key.h" #endif +#ifdef LAMPARRAY_ENABLE +# include "lamparray.h" +#endif + void set_single_persistent_default_layer(uint8_t default_layer); #define IS_LAYER_ON(layer) layer_state_is(layer) diff --git a/quantum/rgb_matrix/overlay.c b/quantum/rgb_matrix/overlay.c new file mode 100644 index 00000000000..ed5771c79eb --- /dev/null +++ b/quantum/rgb_matrix/overlay.c @@ -0,0 +1,59 @@ +// Copyright 2023 QMK +// SPDX-License-Identifier: GPL-2.0-or-later +#include +#include + +// TODO: Pack "enabled" of single LED more efficiently than as alpha channels +typedef struct PACKED rgba_t { + uint8_t r; + uint8_t g; + uint8_t b; + uint8_t a; +} rgba_t; + +#define OVERLAY_DOUBLE_BUFFER +#ifdef OVERLAY_DOUBLE_BUFFER +rgba_t overlay_buffer_a[RGB_MATRIX_LED_COUNT] = {0}; +rgba_t overlay_buffer_b[RGB_MATRIX_LED_COUNT] = {0}; + +rgba_t* overlay_buffer_render = overlay_buffer_a; +rgba_t* overlay_buffer_write = overlay_buffer_b; +#else +rgba_t overlay_buffer[RGB_MATRIX_LED_COUNT] = {0}; + +rgba_t* overlay_buffer_render = overlay_buffer; +rgba_t* overlay_buffer_write = overlay_buffer; +#endif + +static bool is_enabled = false; + +void rgb_matrix_overlay_enable(bool enable) { + is_enabled = enable; +} + +void rgb_matrix_overlay_set(uint8_t index, rgba_t color) { + overlay_buffer_write[index] = color; +} + +void rgb_matrix_overlay_flush(void) { +#ifdef OVERLAY_DOUBLE_BUFFER + memcpy(overlay_buffer_render, overlay_buffer_write, sizeof(rgba_t) * RGB_MATRIX_LED_COUNT); + + rgba_t* buffer_tmp = overlay_buffer_render; + overlay_buffer_render = overlay_buffer_write; + overlay_buffer_write = buffer_tmp; +#endif +} + +bool rgb_matrix_indicators_user(void) { + if (!is_enabled) return true; + + for (uint8_t i = 0; i < RGB_MATRIX_LED_COUNT; i++) { + rgba_t* led = &overlay_buffer_render[i]; + + // Allow "transparent" to running effect? + if (led->a) rgb_matrix_set_color(i, led->r, led->g, led->b); + } + + return false; +} diff --git a/tmk_core/protocol/chibios/usb_main.c b/tmk_core/protocol/chibios/usb_main.c index 66f9ad03186..517bb70f851 100644 --- a/tmk_core/protocol/chibios/usb_main.c +++ b/tmk_core/protocol/chibios/usb_main.c @@ -51,6 +51,10 @@ extern keymap_config_t keymap_config; #endif +#ifdef LAMPARRAY_ENABLE +# include "lamparray.h" +#endif + /* --------------------------------------------------------- * Global interface variables and declarations * --------------------------------------------------------- @@ -217,6 +221,24 @@ static const USBEndpointConfig digitizer_ep_config = { }; #endif +#ifdef LAMPARRAY_ENABLE +/* LampArray endpoint state structure */ +static USBInEndpointState lamparray_ep_state; + +/* LampArray endpoint initialization structure (IN) - see USBEndpointConfig comment at top of file */ +static const USBEndpointConfig lamparray_ep_config = { + USB_EP_MODE_TYPE_INTR, /* Interrupt EP */ + NULL, /* SETUP packet notification callback */ + dummy_usb_cb, /* IN notification callback */ + NULL, /* OUT notification callback */ + LAMPARRAY_EPSIZE, /* IN maximum packet size */ + 0, /* OUT maximum packet size */ + &lamparray_ep_state, /* IN Endpoint state */ + NULL, /* OUT endpoint state */ + usb_lld_endpoint_fields /* USB driver specific endpoint fields */ +}; +#endif + #ifdef USB_ENDPOINTS_ARE_REORDERABLE typedef struct { size_t queue_capacity_in; @@ -509,6 +531,9 @@ static void usb_event_cb(USBDriver *usbp, usbevent_t event) { #endif #if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP) usbInitEndpointI(usbp, DIGITIZER_IN_EPNUM, &digitizer_ep_config); +#endif +#ifdef LAMPARRAY_ENABLE + usbInitEndpointI(usbp, LAMPARRAY_IN_EPNUM, &lamparray_ep_config); #endif for (int i = 0; i < NUM_USB_DRIVERS; i++) { #ifdef USB_ENDPOINTS_ARE_REORDERABLE @@ -586,6 +611,19 @@ static void set_led_transfer_cb(USBDriver *usbp) { } } +#ifdef LAMPARRAY_ENABLE +static universal_lamparray_response_t universal_lamparray_report_buf; + +static void set_lamparray_transfer_cb(USBDriver *usbp) { + // handle directly to avoid sync issues with get + if (universal_lamparray_report_buf.report_id == LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST) { + lamparray_set_attributes_response(universal_lamparray_report_buf.lamp_id); + } else { + lamparray_queue_request(&universal_lamparray_report_buf); + } +} +#endif + static bool usb_requests_hook_cb(USBDriver *usbp) { usb_control_request_t *setup = (usb_control_request_t *)usbp->setup; @@ -623,6 +661,24 @@ static bool usb_requests_hook_cb(USBDriver *usbp) { } # endif #endif /* SHARED_EP_ENABLE */ + +#ifdef LAMPARRAY_ENABLE + case LAMPARRAY_INTERFACE: + switch (setup->wValue.lbyte) { + case LAMPARRAY_REPORT_ID_ATTRIBUTES: + static lamparray_attributes_report_t ret = {.report_id = LAMPARRAY_REPORT_ID_ATTRIBUTES}; + lamparray_get_attributes(&ret.attributes); + + usbSetupTransfer(usbp, (uint8_t *)&ret, sizeof(ret), NULL); + return TRUE; + case LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE: + static lamparray_attributes_response_report_t res = {.report_id = LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE}; + lamparray_get_attributes_response(&res.attributes_response); + + usbSetupTransfer(usbp, (uint8_t *)&res, sizeof(res), NULL); + return TRUE; + } +#endif default: universal_report_blank.report_id = setup->wValue.lbyte; usbSetupTransfer(usbp, (uint8_t *)&universal_report_blank, setup->wLength, NULL); @@ -653,6 +709,18 @@ static bool usb_requests_hook_cb(USBDriver *usbp) { #endif usbSetupTransfer(usbp, set_report_buf, sizeof(set_report_buf), set_led_transfer_cb); return true; +#ifdef LAMPARRAY_ENABLE + case LAMPARRAY_INTERFACE: + switch (setup->wValue.lbyte) { + case LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST: + case LAMPARRAY_REPORT_ID_RANGE_UPDATE: + case LAMPARRAY_REPORT_ID_MULTI_UPDATE: + case LAMPARRAY_REPORT_ID_CONTROL: + usbSetupTransfer(usbp, (uint8_t *)&universal_lamparray_report_buf, sizeof(universal_lamparray_report_buf), set_lamparray_transfer_cb); + return true; + } + break; +#endif } break; diff --git a/tmk_core/protocol/lufa/lufa.c b/tmk_core/protocol/lufa/lufa.c index 553f69b1e46..538ed1172da 100644 --- a/tmk_core/protocol/lufa/lufa.c +++ b/tmk_core/protocol/lufa/lufa.c @@ -67,6 +67,10 @@ # include "raw_hid.h" #endif +#ifdef LAMPARRAY_ENABLE +# include "lamparray.h" +#endif + uint8_t keyboard_idle = 0; /* 0: Boot Protocol, 1: Report Protocol(default) */ uint8_t keyboard_protocol = 1; @@ -409,6 +413,11 @@ void EVENT_USB_Device_ConfigurationChanged(void) { ConfigSuccess &= Endpoint_ConfigureEndpoint((DIGITIZER_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, DIGITIZER_EPSIZE, 1); #endif +#ifdef LAMPARRAY_ENABLE + /* Setup LampArray endpoint */ + ConfigSuccess &= Endpoint_ConfigureEndpoint((LAMPARRAY_IN_EPNUM | ENDPOINT_DIR_IN), EP_TYPE_INTERRUPT, LAMPARRAY_EPSIZE, 1); +#endif + usb_device_state_set_configuration(USB_DeviceState == DEVICE_STATE_Configured, USB_Device_ConfigurationNumber); } @@ -446,6 +455,26 @@ void EVENT_USB_Device_ControlRequest(void) { ReportData = (uint8_t *)&keyboard_report_sent; ReportSize = sizeof(keyboard_report_sent); break; +#ifdef LAMPARRAY_ENABLE + case LAMPARRAY_INTERFACE: + switch ((USB_ControlRequest.wValue & 0xFF)) { + case LAMPARRAY_REPORT_ID_ATTRIBUTES: + static lamparray_attributes_report_t ret = {.report_id = LAMPARRAY_REPORT_ID_ATTRIBUTES}; + lamparray_get_attributes(&ret.attributes); + + ReportData = (uint8_t *)&ret; + ReportSize = sizeof(ret); + break; + case LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE: + static lamparray_attributes_response_report_t res = {.report_id = LAMPARRAY_REPORT_ID_ATTRIBUTES_RESPONSE}; + lamparray_get_attributes_response(&res.attributes_response); + + ReportData = (uint8_t *)&res; + ReportSize = sizeof(res); + break; + } + break; +#endif } /* Write the report data to the control endpoint */ @@ -481,6 +510,26 @@ void EVENT_USB_Device_ControlRequest(void) { Endpoint_ClearOUT(); Endpoint_ClearStatusStage(); break; +#ifdef LAMPARRAY_ENABLE + case LAMPARRAY_INTERFACE: + Endpoint_ClearSETUP(); + + while (!(Endpoint_IsINReady())) + ; + + static universal_lamparray_response_t universal_lamparray_report_buf; + + Endpoint_Read_Control_Stream_LE(&universal_lamparray_report_buf, USB_ControlRequest.wLength); + Endpoint_ClearIN(); + + // handle directly to avoid sync issues with get + if (universal_lamparray_report_buf.report_id == LAMPARRAY_REPORT_ID_ATTRIBUTES_REQUEST) { + lamparray_set_attributes_response(universal_lamparray_report_buf.lamp_id); + } else { + lamparray_queue_request(&universal_lamparray_report_buf); + } + break; +#endif } } diff --git a/tmk_core/protocol/usb_descriptor.c b/tmk_core/protocol/usb_descriptor.c index eb214c0492d..0f08435e603 100644 --- a/tmk_core/protocol/usb_descriptor.c +++ b/tmk_core/protocol/usb_descriptor.c @@ -432,6 +432,173 @@ const USB_Descriptor_HIDReport_Datatype_t PROGMEM ConsoleReport[] = { }; #endif +#ifdef LAMPARRAY_ENABLE +const USB_Descriptor_HIDReport_Datatype_t PROGMEM LampArrayReport[] = { + HID_RI_USAGE_PAGE(8, 0x59), // Usage Page (Lighting and Illumination) + HID_RI_USAGE(8, 0x01), // Usage (Lamp Array) + HID_RI_COLLECTION(8, 0x01), // Collection (Application) + HID_RI_REPORT_ID(8, 1), // Report ID (1) + HID_RI_USAGE(8, 0x02), // Usage (Lamp Array Attributes Report) + HID_RI_COLLECTION(8, 0x02), // Collection (Logical) + HID_RI_USAGE(8, 0x03), // Usage (Lamp Count) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535) + HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16) + HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1) + HID_RI_FEATURE(8, 0x03), // Feature (Cnst,Var,Abs) + HID_RI_USAGE(8, 0x04), // Usage (Bounding Box Width In Micrometers) + HID_RI_USAGE(8, 0x05), // Usage (Bounding Box Height In Micrometers) + HID_RI_USAGE(8, 0x06), // Usage (Bounding Box Depth In Micrometers) + HID_RI_USAGE(8, 0x07), // Usage (Lamp Array Kind) + HID_RI_USAGE(8, 0x08), // Usage (Min Update Interval In Microseconds) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(32, 0x7FFFFFFF), // Logical Maximum (2147483647) + HID_RI_REPORT_SIZE(8, 0x20), // Report Size (32) + HID_RI_REPORT_COUNT(8, 0x05), // Report Count (5) + HID_RI_FEATURE(8, 0x03), // Feature (Cnst,Var,Abs) + HID_RI_END_COLLECTION(0), // End Collection + HID_RI_REPORT_ID(8, 2), // Report ID (2) + HID_RI_USAGE(8, 0x20), // Usage (Lamp Attributes Request Report) + HID_RI_COLLECTION(8, 0x02), // Collection (Logical) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535) + HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16) + HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_END_COLLECTION(0), // End Collection + HID_RI_REPORT_ID(8, 3), // Report ID (3) + HID_RI_USAGE(8, 0x22), // Usage (Lamp Attributes Response Report) + HID_RI_COLLECTION(8, 0x02), // Collection (Logical) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535) + HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16) + HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_USAGE(8, 0x23), // Usage (Position X In Micrometers) + HID_RI_USAGE(8, 0x24), // Usage (Position Y In Micrometers) + HID_RI_USAGE(8, 0x25), // Usage (Position Z In Micrometers) + HID_RI_USAGE(8, 0x27), // Usage (Update Latency In Microseconds) + HID_RI_USAGE(8, 0x26), // Usage (Lamp Purposes) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(32, 0x7FFFFFFF), // Logical Maximum (2147483647) + HID_RI_REPORT_SIZE(8, 0x20), // Report Size (32) + HID_RI_REPORT_COUNT(8, 0x05), // Report Count (5) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_USAGE(8, 0x28), // Usage (Red Level Count) + HID_RI_USAGE(8, 0x29), // Usage (Green Level Count) + HID_RI_USAGE(8, 0x2a), // Usage (Blue Level Count) + HID_RI_USAGE(8, 0x2b), // Usage (Intensity Level Count) + HID_RI_USAGE(8, 0x2c), // Usage (Is Programmable) + HID_RI_USAGE(8, 0x2d), // Usage (Input Binding) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), // Logical Maximum (255) + HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8) + HID_RI_REPORT_COUNT(8, 0x06), // Report Count (6) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_END_COLLECTION(0), // End Collection + HID_RI_REPORT_ID(8, 4), // Report ID (4) + HID_RI_USAGE(8, 0x50), // Usage (Lamp Multi Update Report) + HID_RI_COLLECTION(8, 0x02), // Collection (Logical) + HID_RI_USAGE(8, 0x03), // Usage (Lamp Count) + HID_RI_USAGE(8, 0x55), // Usage (Lamp Update Flags) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(8, 0x08), // Logical Maximum (8) + HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8) + HID_RI_REPORT_COUNT(8, 0x02), // Report Count (2) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_USAGE(8, 0x21), // Usage (Lamp Id) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535) + HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16) + HID_RI_REPORT_COUNT(8, 0x08), // Report Count (8) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), // Logical Maximum (255) + HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8) + HID_RI_REPORT_COUNT(8, 0x20), // Report Count (32) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_END_COLLECTION(0), // End Collection + HID_RI_REPORT_ID(8, 5), // Report ID (5) + HID_RI_USAGE(8, 0x60), // Usage (Lamp Range Update Report) + HID_RI_COLLECTION(8, 0x02), // Collection (Logical) + HID_RI_USAGE(8, 0x55), // Usage (Lamp Update Flags) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(8, 0x08), // Logical Maximum (8) + HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8) + HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_USAGE(8, 0x61), // Usage (Lamp Id Start) + HID_RI_USAGE(8, 0x62), // Usage (Lamp Id End) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(32, (uint32_t)0xFFFF), // Logical Maximum (65535) + HID_RI_REPORT_SIZE(8, 0x10), // Report Size (16) + HID_RI_REPORT_COUNT(8, 0x02), // Report Count (2) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_USAGE(8, 0x51), // Usage (Red Update Channel) + HID_RI_USAGE(8, 0x52), // Usage (Green Update Channel) + HID_RI_USAGE(8, 0x53), // Usage (Blue Update Channel) + HID_RI_USAGE(8, 0x54), // Usage (Intensity Update Channel) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(16, 0x00FF), // Logical Maximum (255) + HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8) + HID_RI_REPORT_COUNT(8, 0x04), // Report Count (4) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_END_COLLECTION(0), // End Collection + HID_RI_REPORT_ID(8, 6), // Report ID (6) + HID_RI_USAGE(8, 0x70), // Usage (Lamp Array Control Report) + HID_RI_COLLECTION(8, 0x02), // Collection (Logical) + HID_RI_USAGE(8, 0x71), // Usage (Autonomous Mode) + HID_RI_LOGICAL_MINIMUM(8, 0x00), // Logical Minimum (0) + HID_RI_LOGICAL_MAXIMUM(8, 0x01), // Logical Maximum (1) + HID_RI_REPORT_SIZE(8, 0x08), // Report Size (8) + HID_RI_REPORT_COUNT(8, 0x01), // Report Count (1) + HID_RI_FEATURE(8, 0x02), // Feature (Data,Var,Abs) + HID_RI_END_COLLECTION(0), // End Collection + HID_RI_END_COLLECTION(0) // End Collection +}; +#endif + /* * Device descriptor */ @@ -1050,6 +1217,46 @@ const USB_Descriptor_Configuration_t PROGMEM ConfigurationDescriptor = { .PollingIntervalMS = USB_POLLING_INTERVAL_MS }, #endif + +#ifdef LAMPARRAY_ENABLE + /* + * LampArray + */ + .LampArray_Interface = { + .Header = { + .Size = sizeof(USB_Descriptor_Interface_t), + .Type = DTYPE_Interface + }, + .InterfaceNumber = LAMPARRAY_INTERFACE, + .AlternateSetting = 0x00, + .TotalEndpoints = 1, + .Class = HID_CSCP_HIDClass, + .SubClass = HID_CSCP_NonBootSubclass, + .Protocol = HID_CSCP_NonBootProtocol, + .InterfaceStrIndex = NO_DESCRIPTOR + }, + .LampArray_HID = { + .Header = { + .Size = sizeof(USB_HID_Descriptor_HID_t), + .Type = HID_DTYPE_HID + }, + .HIDSpec = VERSION_BCD(1, 1, 1), + .CountryCode = 0x00, + .TotalReportDescriptors = 1, + .HIDReportType = HID_DTYPE_Report, + .HIDReportLength = sizeof(LampArrayReport) + }, + .LampArray_INEndpoint = { + .Header = { + .Size = sizeof(USB_Descriptor_Endpoint_t), + .Type = DTYPE_Endpoint + }, + .EndpointAddress = (ENDPOINT_DIR_IN | LAMPARRAY_IN_EPNUM), + .Attributes = (EP_TYPE_INTERRUPT | ENDPOINT_ATTR_NO_SYNC | ENDPOINT_USAGE_DATA), + .EndpointSize = LAMPARRAY_EPSIZE, + .PollingIntervalMS = 0x01 + }, +#endif }; /* @@ -1198,6 +1405,13 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const break; #endif +#ifdef LAMPARRAY_ENABLE + case LAMPARRAY_INTERFACE: + Address = &ConfigurationDescriptor.LampArray_HID; + Size = sizeof(USB_HID_Descriptor_HID_t); + + break; +#endif } break; @@ -1254,6 +1468,13 @@ uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const Size = sizeof(DigitizerReport); break; #endif +#ifdef LAMPARRAY_ENABLE + case LAMPARRAY_INTERFACE: + Address = &LampArrayReport; + Size = sizeof(LampArrayReport); + + break; +#endif } break; diff --git a/tmk_core/protocol/usb_descriptor.h b/tmk_core/protocol/usb_descriptor.h index 1268bdae733..69354d54b79 100644 --- a/tmk_core/protocol/usb_descriptor.h +++ b/tmk_core/protocol/usb_descriptor.h @@ -145,6 +145,13 @@ typedef struct { USB_HID_Descriptor_HID_t Digitizer_HID; USB_Descriptor_Endpoint_t Digitizer_INEndpoint; #endif + +#ifdef LAMPARRAY_ENABLE + // HID LampArray Interface + USB_Descriptor_Interface_t LampArray_Interface; + USB_HID_Descriptor_HID_t LampArray_HID; + USB_Descriptor_Endpoint_t LampArray_INEndpoint; +#endif } USB_Descriptor_Configuration_t; /* @@ -194,6 +201,11 @@ enum usb_interfaces { #if defined(DIGITIZER_ENABLE) && !defined(DIGITIZER_SHARED_EP) DIGITIZER_INTERFACE, #endif + +#ifdef LAMPARRAY_ENABLE + LAMPARRAY_INTERFACE, +#endif + TOTAL_INTERFACES }; @@ -281,6 +293,10 @@ enum usb_endpoints { # define DIGITIZER_IN_EPNUM SHARED_IN_EPNUM # endif #endif + +#ifdef LAMPARRAY_ENABLE + LAMPARRAY_IN_EPNUM = NEXT_EPNUM, +#endif }; #ifdef PROTOCOL_LUFA @@ -307,5 +323,6 @@ enum usb_endpoints { #define CDC_EPSIZE 16 #define JOYSTICK_EPSIZE 8 #define DIGITIZER_EPSIZE 8 +#define LAMPARRAY_EPSIZE 8 uint16_t get_usb_descriptor(const uint16_t wValue, const uint16_t wIndex, const uint16_t wLength, const void** const DescriptorAddress);