Skip to content

Commit a08db41

Browse files
zvecrJohnAZoidberg
authored andcommitted
Initial PoC for Dynamic lighting support
1 parent e06fde4 commit a08db41

File tree

13 files changed

+919
-0
lines changed

13 files changed

+919
-0
lines changed

builddefs/generic_features.mk

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ GENERIC_FEATURES = \
3030
HAPTIC \
3131
KEY_LOCK \
3232
KEY_OVERRIDE \
33+
LAMPARRAY \
3334
LEADER \
3435
PROGRAMMABLE_BUTTON \
3536
REPEAT_KEY \

lib/python/qmk/cli/generate/config_h.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,40 @@ def generate_matrix_size(kb_info_json, config_h_lines):
6767
config_h_lines.append(generate_define('MATRIX_ROWS', kb_info_json['matrix_size']['rows']))
6868

6969

70+
def generate_matrix_masked(kb_info_json, config_h_lines):
71+
""""Enable matrix mask if required"""
72+
mask_required = False
73+
74+
if 'matrix_grid' in kb_info_json.get('dip_switch', {}):
75+
mask_required = True
76+
if 'matrix_grid' in kb_info_json.get('split', {}).get('handedness', {}):
77+
mask_required = True
78+
79+
if mask_required:
80+
config_h_lines.append(generate_define('MATRIX_MASKED'))
81+
82+
83+
def generate_estimated_dimensions(kb_info_json, config_h_lines):
84+
"""Try and guess physical keyboard dimensions from the declared layouts
85+
"""
86+
if 'layouts' in kb_info_json:
87+
width = 0
88+
height = 0
89+
for layout_data in kb_info_json['layouts'].values():
90+
for key in layout_data['layout']:
91+
x = key.get('x', 0)
92+
y = key.get('y', 0)
93+
w = key.get('w', 1)
94+
h = key.get('h', 1)
95+
96+
width = max(width, x + w)
97+
height = max(height, y + h)
98+
99+
# sizes are in micrometers - assume 1u = 19.05mm
100+
config_h_lines.append(generate_define('ESTIMATED_KEYBOARD_WIDTH', width * 19050))
101+
config_h_lines.append(generate_define('ESTIMATED_KEYBOARD_HEIGHT', height * 19050))
102+
103+
70104
def generate_config_items(kb_info_json, config_h_lines):
71105
"""Iterate through the info_config map to generate basic config values.
72106
"""
@@ -191,6 +225,10 @@ def generate_config_h(cli):
191225

192226
generate_matrix_size(kb_info_json, config_h_lines)
193227

228+
generate_matrix_masked(kb_info_json, config_h_lines)
229+
230+
generate_estimated_dimensions(kb_info_json, config_h_lines)
231+
194232
if 'matrix_pins' in kb_info_json:
195233
config_h_lines.append(matrix_pins(kb_info_json['matrix_pins']))
196234

quantum/keyboard.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
138138
#ifdef WPM_ENABLE
139139
# include "wpm.h"
140140
#endif
141+
#ifdef LAMPARRAY_ENABLE
142+
# include "lamparray.h"
143+
#endif
141144

142145
static uint32_t last_input_modification_time = 0;
143146
uint32_t last_input_activity_time(void) {
@@ -414,6 +417,9 @@ void keyboard_init(void) {
414417
#ifdef SPLIT_KEYBOARD
415418
split_pre_init();
416419
#endif
420+
#ifdef LAMPARRAY_ENABLE
421+
lamparray_init();
422+
#endif
417423
#ifdef ENCODER_ENABLE
418424
encoder_init();
419425
#endif
@@ -637,6 +643,10 @@ void quantum_task(void) {
637643
#ifdef SECURE_ENABLE
638644
secure_task();
639645
#endif
646+
647+
#ifdef LAMPARRAY_ENABLE
648+
lamparray_task();
649+
#endif
640650
}
641651

642652
/** \brief Main task that is repeatedly called as fast as possible. */

quantum/lamparray/lamparray.c

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Copyright 2024 QMK
2+
// SPDX-License-Identifier: GPL-2.0-or-later
3+
#include <string.h> // for memcpy
4+
#include "lamparray.h"
5+
#include "lamparray_surface.h"
6+
#include "keycodes.h"
7+
#include "keymap_introspection.h"
8+
#include "action_layer.h"
9+
10+
// Defaults are generated from info.json layout content
11+
#ifndef LAMPARRAY_WIDTH
12+
# define LAMPARRAY_WIDTH ESTIMATED_KEYBOARD_WIDTH
13+
#endif
14+
#ifndef LAMPARRAY_HEIGHT
15+
# define LAMPARRAY_HEIGHT ESTIMATED_KEYBOARD_HEIGHT
16+
#endif
17+
#ifndef LAMPARRAY_DEPTH
18+
# define LAMPARRAY_DEPTH 30000
19+
#endif
20+
#ifndef LAMPARRAY_KIND
21+
# define LAMPARRAY_KIND LAMPARRAY_KIND_KEYBOARD
22+
#endif
23+
24+
#ifdef RGB_MATRIX_ENABLE
25+
# include "rgb_matrix.h"
26+
# define LAMPARRAY_RED_LEVELS 255
27+
# define LAMPARRAY_GREEN_LEVELS 255
28+
# define LAMPARRAY_BLUE_LEVELS 255
29+
# define LAMPARRAY_INTENSITY_LEVELS 1
30+
# define LAMPARRAY_LAMP_COUNT RGB_MATRIX_LED_COUNT
31+
# define LAMPARRAY_UPDATE_INTERVAL (RGB_MATRIX_LED_FLUSH_LIMIT * 1000)
32+
#endif
33+
34+
//****************************************************************************
35+
// utils
36+
37+
/**
38+
* \brief Query a HID usage for a given location
39+
*
40+
* This can be requested while the user is changing layers. This is mitigated somewhat by assuming the default layer changes infrequently.
41+
* This is currently accepted as a limitation as there is no method to invalidate the hosts view of the data.
42+
*/
43+
uint8_t lamparray_binding_at_keymap_location(uint8_t row, uint8_t col) {
44+
uint16_t keycode = keycode_at_keymap_location(get_highest_layer(default_layer_state), row, col);
45+
(void)keycode;
46+
#if LAMPARRAY_KIND == LAMPARRAY_KIND_KEYBOARD
47+
// Basic QMK keycodes currently map directly to Keyboard UsagePage so safe to return without added indirection
48+
// Mousekeys are ignored due to values overlap Keyboard UsagePage
49+
if (IS_BASIC_KEYCODE(keycode) || IS_MODIFIER_KEYCODE(keycode)) {
50+
return keycode;
51+
}
52+
#elif LAMPARRAY_KIND == LAMPARRAY_KIND_MOUSE
53+
// Usages from the Button UsagePage (0x09) in the range of Button1 (0x01) to Button5 (0x05) inclusive
54+
if ((code) >= KC_MS_BTN1 && (code) <= KC_MS_BTN5) {
55+
return keycode - KC_MS_BTN1 + 1;
56+
}
57+
#endif
58+
return 0;
59+
}
60+
61+
//****************************************************************************
62+
// cache
63+
64+
static uint8_t input_binding_cache[LAMPARRAY_LAMP_COUNT];
65+
66+
void lamparray_update_cache(void) {
67+
for (uint8_t lamp_id = 0; lamp_id < LAMPARRAY_LAMP_COUNT; lamp_id++) {
68+
input_binding_cache[lamp_id] = lamparray_get_lamp_binding_impl(lamp_id);
69+
}
70+
}
71+
72+
uint8_t lamparray_get_lamp_binding(uint16_t lamp_id) {
73+
return input_binding_cache[lamp_id];
74+
}
75+
76+
//****************************************************************************
77+
// queue
78+
79+
#ifndef LAMPARRAY_REQUEST_QUEUE_SIZE
80+
# define LAMPARRAY_REQUEST_QUEUE_SIZE 5
81+
#endif
82+
83+
universal_lamparray_response_t request_queue[LAMPARRAY_REQUEST_QUEUE_SIZE] = {0};
84+
uint8_t queue_size = 0;
85+
86+
void lamparray_queue_request(universal_lamparray_response_t* report) {
87+
// get next slot
88+
universal_lamparray_response_t* target = &request_queue[queue_size++];
89+
90+
// copy data
91+
memcpy(target, report, sizeof(universal_lamparray_response_t));
92+
}
93+
94+
void lamparray_handle_queue(void) {
95+
for (uint8_t id = 0; id < queue_size; id++) {
96+
universal_lamparray_response_t* report = &request_queue[id];
97+
switch (report->report_id) {
98+
case LAMPARRAY_REPORT_ID_RANGE_UPDATE:
99+
lamparray_set_range(&report->range_update);
100+
break;
101+
case LAMPARRAY_REPORT_ID_MULTI_UPDATE:
102+
lamparray_set_items(&report->multi_update);
103+
break;
104+
case LAMPARRAY_REPORT_ID_CONTROL:
105+
lamparray_set_control_response(report->autonomous);
106+
break;
107+
}
108+
}
109+
queue_size = 0;
110+
}
111+
112+
//****************************************************************************
113+
// impl
114+
115+
static uint16_t cur_lamp_id = 0;
116+
static bool is_autonomous = true;
117+
118+
void lamparray_get_attributes(lamparray_attributes_t* data) {
119+
data->lamp_count = LAMPARRAY_LAMP_COUNT;
120+
data->update_interval = LAMPARRAY_UPDATE_INTERVAL;
121+
data->kind = LAMPARRAY_KIND;
122+
data->bounds.width = LAMPARRAY_WIDTH;
123+
data->bounds.height = LAMPARRAY_HEIGHT;
124+
data->bounds.depth = LAMPARRAY_DEPTH;
125+
}
126+
127+
void lamparray_get_attributes_response(lamparray_attributes_response_t* data) {
128+
data->lamp_id = cur_lamp_id;
129+
data->update_latency = 1000;
130+
data->is_programmable = 1;
131+
data->input_binding = lamparray_get_lamp_binding(cur_lamp_id);
132+
133+
data->levels.red = LAMPARRAY_RED_LEVELS;
134+
data->levels.green = LAMPARRAY_GREEN_LEVELS;
135+
data->levels.blue = LAMPARRAY_BLUE_LEVELS;
136+
data->levels.intensity = LAMPARRAY_INTENSITY_LEVELS;
137+
138+
lamparray_get_lamp_impl(cur_lamp_id, data);
139+
140+
// Automatic address pointer incrementing - 26.8.1 LampAttributesRequestReport
141+
cur_lamp_id++;
142+
if (cur_lamp_id >= LAMPARRAY_LAMP_COUNT) cur_lamp_id = 0;
143+
}
144+
145+
void lamparray_set_attributes_response(uint16_t lamp_id) {
146+
cur_lamp_id = lamp_id;
147+
}
148+
149+
void lamparray_set_control_response(uint8_t autonomous) {
150+
is_autonomous = !!autonomous;
151+
152+
lamparray_surface_enable(!autonomous);
153+
}
154+
155+
void lamparray_set_range(lamparray_range_update_t* data) {
156+
// Any Lamp*UpdateReports can be ignored - 26.10.1 AutonomousMode
157+
if (is_autonomous) {
158+
return;
159+
}
160+
161+
// Ensure IDs are within bounds
162+
if ((data->start >= LAMPARRAY_LAMP_COUNT) || (data->end >= LAMPARRAY_LAMP_COUNT)) {
163+
return;
164+
}
165+
166+
for (uint16_t index = data->start; index <= data->end; index++) {
167+
lamparray_surface_set_item(index, data->color);
168+
}
169+
170+
// Batch update complete - 26.11 Updating Lamp State
171+
if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) {
172+
lamparray_surface_update_finished();
173+
}
174+
}
175+
176+
void lamparray_set_items(lamparray_multi_update_t* data) {
177+
// Any Lamp*UpdateReports can be ignored - 26.10.1 AutonomousMode
178+
if (is_autonomous) {
179+
return;
180+
}
181+
182+
// Ensure data is within bounds
183+
if (data->count > LAMP_MULTI_UPDATE_LAMP_COUNT) {
184+
return;
185+
}
186+
187+
for (uint8_t i = 0; i < data->count; i++) {
188+
// Ensure IDs are within bounds
189+
if (data->ids[i] >= LAMPARRAY_LAMP_COUNT) {
190+
continue;
191+
}
192+
lamparray_surface_set_item(data->ids[i], data->colors[i]);
193+
}
194+
195+
// Batch update complete - 26.11 Updating Lamp State
196+
if (data->flags & LAMP_UPDATE_FLAG_COMPLETE) {
197+
lamparray_surface_update_finished();
198+
}
199+
}
200+
201+
//****************************************************************************
202+
// feature hooks
203+
204+
void lamparray_init(void) {
205+
lamparray_update_cache();
206+
}
207+
208+
void lamparray_task(void) {
209+
lamparray_handle_queue();
210+
211+
// TODO: regen cache if dynamic keymap updated?
212+
uint16_t temp = 0;
213+
if (!++temp) lamparray_update_cache();
214+
}
215+
216+
// TODO: SRC += ...
217+
#ifdef RGB_MATRIX_ENABLE
218+
# include "lamparray_rgb_matrix.c"
219+
#endif

0 commit comments

Comments
 (0)