Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ build
.vscode/settings.json
.jj
mcux_include.json
compile_commands.json
.devices
Makefile
createTags.sh
tags
4 changes: 4 additions & 0 deletions device/src/keyboard/oled/oled.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

#if DEVICE_HAS_OLED

uint8_t OledOverrideMode = 0;

#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include "keyboard/oled/oled.h"
Expand Down Expand Up @@ -349,6 +351,8 @@ void InitOled(void) {

#else // DEVICE_HAS_OLED

uint8_t OledOverrideMode = 0;

void Oled_ActivateScreen(widget_t* screen, bool forceRedraw){};
void Oled_RequestRedraw(){};
void Oled_ShiftScreen(){};
Expand Down
2 changes: 2 additions & 0 deletions device/src/keyboard/oled/oled.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

// Variables:

extern uint8_t OledOverrideMode;

extern const struct gpio_dt_spec oledEn;
extern const struct gpio_dt_spec oledResetDt;
extern const struct gpio_dt_spec oledCsDt;
Expand Down
26 changes: 26 additions & 0 deletions device/src/keyboard/oled/screens/canvas_screen.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,32 @@ void CanvasScreen_Draw(uint16_t x, uint16_t y, uint8_t* data, uint16_t len) {
Oled_RequestRedraw();
}

void CanvasScreen_DrawPacked(uint16_t x, uint16_t y, uint8_t* data, uint16_t pixelCount) {
ScreenManager_ActivateScreen(ScreenId_Canvas);
widget_t canvas = (widget_t) {
.x = x,
.y = y,
.w = MIN(DISPLAY_WIDTH-x, pixelCount),
.h = 1,
};
// Mark the buffer region as dirty
Framebuffer_Clear(&canvas, OledBuffer);

for (uint16_t i = 0; i < pixelCount; i++) {
if (x >= DISPLAY_WIDTH) {
printk("Usb attempting to draw outside of display range!\n");
break;
}
// 2 pixels per byte: high nibble is first pixel, low nibble is second
uint8_t packedByte = data[i / 2];
uint8_t pixelValue = (i % 2 == 0) ? (packedByte >> 4) : (packedByte & 0x0F);
// Scale 4-bit value to 8-bit for Framebuffer_SetPixel
Framebuffer_SetPixel(OledBuffer, x, y, pixelValue << 4);
x++;
}
Oled_RequestRedraw();
}

void CanvasScreen_Init() {
CanvasScreen = &CanvasWidget;
}
1 change: 1 addition & 0 deletions device/src/keyboard/oled/screens/canvas_screen.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@

void CanvasScreen_Init();
void CanvasScreen_Draw(uint16_t x, uint16_t y, uint8_t* data, uint16_t len);
void CanvasScreen_DrawPacked(uint16_t x, uint16_t y, uint8_t* data, uint16_t pixelCount);

#endif
10 changes: 8 additions & 2 deletions device/src/keyboard/oled/screens/screen_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ void ScreenManager_ActivateScreen(screen_id_t screen)
screenPtr = MainScreen;
break;
case ScreenId_Canvas:
EventScheduler_Reschedule(Timer_GetCurrentTime() + CANVAS_TIMEOUT, EventSchedulerEvent_SwitchScreen, "ScreenManager - switch to main screen");
if (!OledOverrideMode) {
EventScheduler_Reschedule(Timer_GetCurrentTime() + CANVAS_TIMEOUT, EventSchedulerEvent_SwitchScreen, "ScreenManager - switch to main screen");
}
screenPtr = CanvasScreen;
break;
case ScreenId_Notification:
Expand All @@ -70,7 +72,11 @@ void ScreenManager_ActivateScreen(screen_id_t screen)

void ScreenManager_SwitchScreenEvent()
{
ScreenManager_ActivateScreen(ScreenId_Main);
if (OledOverrideMode) {
ScreenManager_ActivateScreen(ScreenId_Canvas);
} else {
ScreenManager_ActivateScreen(ScreenId_Main);
}
}

void ScreenManager_Init()
Expand Down
101 changes: 101 additions & 0 deletions doc-dev/other/usb_led_controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# USB LED Controls

This document describes USB commands for controlling LEDs and displays on UHK keyboards.

For easy testing, we include an `UHKID` argument that can be used to target a specific UHK in a multi-UHK environment. Feel free to set it to empty string

```bash
UHKID=--serial-number=1777777765
```

## LED Override Mask (0x0a)

Get/set override flags for LEDs. The mask controls which LED subsystems are in override mode.

**Get current mask:**

```bash
./send-command.ts $UHKID 0x12 0x0a
```

**Set mask:**
```bash
./send-command.ts $UHKID 0x13 0x0a 255 255 128 17
```

Format: `0x13 0x0a <uhk60_flags> <oled_mode> <per_key_bitmap...>`

- Byte 1: UHK60 LED override flags (bitfield: mod, fn, mouse, capsLock, agent, adaptive, segmentDisplay)
- Byte 2: OLED override mode
- Bytes 3-34: Per-key RGB override bitmap (256 bits for key IDs)

## UHK60 LED Panel (0x20)

Set UHK60 indicator LEDs and 14-segment display.

```bash
./send-command.ts $UHKID 0x20 0 1 0 1 0 1 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 1 0 0 0 0
```

Format: `0x20 <6 LED bytes> <42 segment bytes>`

- Bytes 1-6: LED brightness (mod, fn, mouse, capsLock, agent, adaptive)
- Bytes 7-48: 14-segment display (3 chars × 14 segments)

Reset override:
```bash
./send-command.ts $UHKID 0x13 0x0a 0 0 0 0
```

## Per-Key RGB (0x21)

Set individual key colors.

```bash
# First enable per-key override in mask
./send-command.ts $UHKID 0x13 0x0a 0 0 255 255 255 255 255 255 255

# Set key 5 to red, key 10 to green
./send-command.ts $UHKID 0x21 2 5 255 0 0 10 0 255 0
```

Format: `0x21 <count> [<keyId> <r> <g> <b>]...`

- Byte 1: Number of keys to set
- Then for each key: keyId (slot×64 + inSlotIdx), R, G, B values

Reset override:
```bash
./send-command.ts $UHKID 0x13 0x0a 000 000 000 000 000 000 000 000 000
```

## OLED Display (0x15)

Draw pixels on the UHK80 OLED (256×64, 4-bit grayscale).

**Enable OLED override:**
```bash
./send-command.ts $UHKID 0x13 0x0a 0 255 0
```

**Show endianity:**
```bash
./send-command.ts $UHKID 0x15 128 32 8 0xFF 0x0F 0xF0 0xFF 255
```

**Draw a heart:**
```bash
./send-command.ts $UHKID 0x15 \
8 13 6 0x0f 0xf0 0xff \
8 14 7 0xf0 0x0f 0x00 0xf0 \
8 15 6 0x0f 0x00 0x0f \
8 16 6 0x00 0xf0 0xf0 \
8 17 6 0x00 0x0f 0x00 \
0xff
```

Format: `0x15 [<x> <y> <pixelCount> <packed_pixels>...]... 0xff`

- Each segment: x, y, pixel count, then packed pixel data
- Pixels are 4-bit grayscale (0x0=black, 0xf=white), 2 per byte (high nibble first)
- Terminate with x=0xff
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Karel Tucek <kareltucek@centrum.cz>
Date: Sun, 19 Jan 2026 16:00:00 +0100
Subject: [PATCH] zephyr_module: handle None revision in offline projects

When running in a directory without a .git (e.g., a jj workspace),
the revision can be None. Check for this before appending "-off"
suffix to avoid TypeError.

---
scripts/zephyr_module.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/zephyr_module.py b/scripts/zephyr_module.py
index 000000000..000000001 100755
--- a/scripts/zephyr_module.py
+++ b/scripts/zephyr_module.py
@@ -585,7 +585,7 @@ def process_meta(zephyr_base, west_projs, modules,
manifest_project, manifest_dirty = _create_meta_project(
projects[0].posixpath)
manifest_off = manifest_project.get("remote") is None
- if manifest_off:
+ if manifest_off and manifest_project.get("revision") is not None:
manifest_project["revision"] += "-off"

if manifest_project:
--
2.44.0

60 changes: 55 additions & 5 deletions right/src/led_display.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ void LedDisplay_SetText(uint8_t length, const char* text)
for (uint8_t ledId=0; ledId<ledCountPerChar; ledId++) {
uint8_t ledIdx = segmentLedIds[charId][ledId];
bool isLedOn = charBits & (1 << ledId);

// Apply segment override if enabled
if (Uhk60LedOverride.segmentDisplay) {
uint8_t segmentIdx = charId * ledCountPerChar + ledId;
isLedOn = Uhk60LedState.segments[segmentIdx] == LED_VALUE_ON;
}

LedDriverValues[LedDriverId_Left][ledIdx] = isLedOn ? DisplayBrightness : 0;
}
}
Expand All @@ -161,12 +168,31 @@ void LedDisplay_SetText(uint8_t length, const char* text)
void LedDisplay_SetLayer(layer_id_t layerId)
{
#ifndef __ZEPHYR__
// layerLedIds is defined for just three values atm
for (uint8_t i=1; i<4; i++) {
LedDriverValues[LedDriverId_Left][layerLedIds[i-1]] = layerId == i ? DisplayBrightness : 0;
// Layer LED indices in Uhk60LedState.leds: 0=Mod, 1=Fn, 2=Mouse
bool layerStates[3] = {false, false, false};

// Determine firmware-intended states
if (layerId >= 1 && layerId <= 3) {
layerStates[layerId - 1] = true;
}
if (LayerId_Fn2 <= layerId && layerId <= LayerId_Fn5) {
LedDriverValues[LedDriverId_Left][layerLedIds[LayerId_Fn-1]] = DisplayBrightness;
layerStates[LayerId_Fn - 1] = true;
}

// Apply overrides if set
if (Uhk60LedOverride.mod) {
layerStates[0] = Uhk60LedState.leds[0] == LED_VALUE_ON;
}
if (Uhk60LedOverride.fn) {
layerStates[1] = Uhk60LedState.leds[1] == LED_VALUE_ON;
}
if (Uhk60LedOverride.mouse) {
layerStates[2] = Uhk60LedState.leds[2] == LED_VALUE_ON;
}

// Set the actual LED values
for (uint8_t i = 0; i < 3; i++) {
LedDriverValues[LedDriverId_Left][layerLedIds[i]] = layerStates[i] ? DisplayBrightness : 0;
}
#endif
}
Expand All @@ -184,7 +210,31 @@ void LedDisplay_SetIcon(led_display_icon_t icon, bool isEnabled)
{
ledIconStates[icon] = isEnabled;
#ifndef __ZEPHYR__
LedDriverValues[LedDriverId_Left][iconLedIds[icon]] = isEnabled ? DisplayBrightness : 0;
bool effectiveState = isEnabled;

// Check if this icon is overridden and use user-set value instead
// Icon LED indices in Uhk60LedState.leds: 3=capsLock, 4=agent, 5=adaptive
switch (icon) {
case LedDisplayIcon_CapsLock:
if (Uhk60LedOverride.capsLock) {
effectiveState = Uhk60LedState.leds[3] == LED_VALUE_ON;
}
break;
case LedDisplayIcon_Agent:
if (Uhk60LedOverride.agent) {
effectiveState = Uhk60LedState.leds[4] == LED_VALUE_ON;
}
break;
case LedDisplayIcon_Adaptive:
if (Uhk60LedOverride.adaptive) {
effectiveState = Uhk60LedState.leds[5] == LED_VALUE_ON;
}
break;
default:
break;
}

LedDriverValues[LedDriverId_Left][iconLedIds[icon]] = effectiveState ? DisplayBrightness : 0;
#endif
}

Expand Down
4 changes: 3 additions & 1 deletion right/src/led_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
bool KeyBacklightSleepModeActive = false;
bool DisplaySleepModeActive = false;

led_override_uhk60_t Uhk60LedOverride = {0};
uhk60_led_state_t Uhk60LedState = {0};

uint8_t DisplayBrightness = 0xff;
uint8_t KeyBacklightBrightness = 0xff;

Expand Down Expand Up @@ -140,4 +143,3 @@ void LedManager_UpdateSleepModes() {
EventVector_Set(EventVector_LedManagerFullUpdateNeeded);
}
}

31 changes: 31 additions & 0 deletions right/src/led_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,39 @@
#include "attributes.h"

// Macros:

#define UHK60_LED_COUNT 6
#define UHK60_SEGMENT_DISPLAY_CHARS 3
#define UHK60_SEGMENT_COUNT_PER_CHAR 14
#define UHK60_SEGMENT_DISPLAY_SIZE (UHK60_SEGMENT_DISPLAY_CHARS * UHK60_SEGMENT_COUNT_PER_CHAR)

#define LED_VALUE_OFF 0
#define LED_VALUE_ON 1
#define LED_VALUE_UNCHANGED 2

// Typedefs:

typedef struct {
uint8_t mod : 1;
uint8_t fn : 1;
uint8_t mouse : 1;
uint8_t capsLock : 1;
uint8_t agent : 1;
uint8_t adaptive : 1;
uint8_t segmentDisplay : 1;
uint8_t reserved : 1;
} ATTR_PACKED led_override_uhk60_t;

typedef struct {
uint8_t leds[UHK60_LED_COUNT]; // 6 bytes: mod, fn, mouse, capsLock, agent, adaptive
uint8_t segments[UHK60_SEGMENT_DISPLAY_SIZE]; // 42 bytes: 3 chars * 14 segments
} ATTR_PACKED uhk60_led_state_t;

// Variables:

extern led_override_uhk60_t Uhk60LedOverride;
extern uhk60_led_state_t Uhk60LedState;

extern uint8_t DisplayBrightness;
extern uint8_t KeyBacklightBrightness;

Expand Down
2 changes: 2 additions & 0 deletions right/src/usb_commands/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ target_sources(${PROJECT_NAME} PRIVATE
$<$<BOOL:${IS_MCUX_SDK}>:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_i2c_baud_rate.c>
$<$<BOOL:${IS_MCUX_SDK}>:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_led_pwm_brightness.c>
$<$<BOOL:${IS_MCUX_SDK}>:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_test_led.c>
$<$<BOOL:${IS_MCUX_SDK}>:${CMAKE_CURRENT_SOURCE_DIR}/usb_command_set_uhk60_led_state.c>
usb_command_set_per_key_rgb.c
usb_command_set_variable.c
usb_command_switch_keymap.c
usb_command_write_config.c
Expand Down
Loading