Răsfoiți Sursa

Initial PoC for Dynamic lighting support

zvecr 2 ani în urmă
părinte
comite
90f974344b

+ 1 - 0
builddefs/generic_features.mk

@@ -35,6 +35,7 @@ GENERIC_FEATURES = \
     HAPTIC \
     KEY_LOCK \
     KEY_OVERRIDE \
+    LAMPARRAY \
     LEADER \
     MAGIC \
     MOUSEKEY \

+ 23 - 0
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']))
 

+ 10 - 0
quantum/keyboard.c

@@ -135,6 +135,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #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. */

+ 219 - 0
quantum/lamparray/lamparray.c

@@ -0,0 +1,219 @@
+// Copyright 2024 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <string.h> // 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

+ 159 - 0
quantum/lamparray/lamparray.h

@@ -0,0 +1,159 @@
+// Copyright 2024 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#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);

+ 56 - 0
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();
+}

+ 18 - 0
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);

+ 4 - 0
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)

+ 59 - 0
quantum/rgb_matrix/overlay.c

@@ -0,0 +1,59 @@
+// Copyright 2023 QMK
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <stdbool.h>
+#include <string.h>
+
+// 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;
+}

+ 68 - 0
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;
 

+ 49 - 0
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
                 }
             }
 

+ 221 - 0
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;

+ 17 - 0
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);