Skip to content

Commit

Permalink
Initial PoC for Dynamic lighting support
Browse files Browse the repository at this point in the history
  • Loading branch information
zvecr committed Oct 19, 2023
1 parent ab952c3 commit 74f9260
Show file tree
Hide file tree
Showing 10 changed files with 788 additions and 0 deletions.
1 change: 1 addition & 0 deletions builddefs/generic_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ GENERIC_FEATURES = \
HAPTIC \
KEY_LOCK \
KEY_OVERRIDE \
LAMPARRAY \
LEADER \
PROGRAMMABLE_BUTTON \
REPEAT_KEY \
Expand Down
26 changes: 26 additions & 0 deletions lib/python/qmk/cli/generate/config_h.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,30 @@ def generate_matrix_size(kb_info_json, config_h_lines):
config_h_lines.append(generate_define('MATRIX_ROWS', kb_info_json['matrix_size']['rows']))


def generate_estimated_dimensions(kb_info_json, config_h_lines):
"""Try and guess physical keyboard dimensions from the declared layouts
"""
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)

# assume +0.5u on each dimension
width += 1
height += 1

# 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.
"""
Expand Down Expand Up @@ -196,6 +220,8 @@ def generate_config_h(cli):

generate_matrix_size(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']))

Expand Down
191 changes: 191 additions & 0 deletions quantum/lamparray.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright 2023 QMK
// SPDX-License-Identifier: GPL-2.0-or-later
#include "lamparray.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

// TODO: Implement backing API
void lamparray_backing_enable(bool enable);
void lamparray_backing_set_item(uint16_t index, lamp_state_t color);
void lamparray_backing_update_finished(void);

/**
* \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 less frequently.
* This is currently accepted as a limitation as there is no method to invalidate the hosts view of the data.
*/
static inline uint16_t 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);
#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;
}
#endif
return 0;
}

#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)

/**
* \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_info_data(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) * (64 - g_led_config.point[lamp_id].y);
data->position.z = (g_led_config.flags[lamp_id] & LED_FLAG_UNDERGLOW) ? LAMPARRAY_DEPTH : 0;
data->purposes = (g_led_config.flags[lamp_id] & LED_FLAG_UNDERGLOW) ? LAMP_PURPOSE_ACCENT : LAMP_PURPOSE_CONTROL;
}

/**
* \brief Query a HID usage for a given lamp
*/
__attribute__((weak)) uint8_t lamparray_get_lamp_binding(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 binding_at_keymap_location(i_row, i_col);
}
}
}
return 0;
}

// TODO: temporay binding of storage and render
# include "rgb_matrix/overlay.c"

void lamparray_backing_enable(bool enable) {
rgb_matrix_overlay_enable(enable);
}

void lamparray_backing_set_item(uint16_t index, lamp_state_t color) {
rgb_matrix_overlay_set(index, (rgba_t){color.red, color.green, color.blue, color.intensity ? 0 : 0xFF});
}

void lamparray_backing_update_finished(void) {
rgb_matrix_overlay_flush();
}
#endif

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_lamp_info(uint16_t lamp_id, lamparray_attributes_response_t* data) {
data->lamp_id = lamp_id;
data->update_latency = 1000;
data->is_programmable = 1;
data->input_binding = lamparray_get_lamp_binding(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_info_data(lamp_id, data);
}

void lamparray_get_attributes_response(lamparray_attributes_response_t* data) {
lamparray_get_lamp_info(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_backing_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_backing_set_item(index, data->color);
}

// Batch update complete - 26.11 Updating Lamp State
if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) {
lamparray_backing_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_backing_set_item(data->ids[i], data->colors[i]);
}

// Batch update complete - 26.11 Updating Lamp State
if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) {
lamparray_backing_update_finished();
}
}
106 changes: 106 additions & 0 deletions quantum/lamparray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2023 QMK
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once

#include "util.h" // PACKED

// 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;

/**
* \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);
Loading

0 comments on commit 74f9260

Please sign in to comment.