Send 54-byte packets so no LED is split across packets

The keyboard applies each packet as it arrives. With 56 colour bytes per
packet, LEDs 18, 74 and 93 straddled two packets and briefly showed a mix of
old and new colour. 54 is a multiple of 3, and 7 x 54 covers exactly LEDs
0..125, so the frame no longer pads past the last LED either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-28 23:19:39 +01:00
1 parent 9ff5b99fa0
commit 822787ae0f
2 files changed
+11 -8

No files matched your search

+5 -4
View File
@@ -9,14 +9,15 @@ product ID or have a K87 layout, so the keyboard is ignored without this plugin.
Copy `Womier_K87_EVision.js` to `%USERPROFILE%\Documents\WhirlwindFX\Plugins\` and restart SignalRGB. Copy `Womier_K87_EVision.js` to `%USERPROFILE%\Documents\WhirlwindFX\Plugins\` and restart SignalRGB.
The device settings have a "Key test" option (column or row sweep) to check the LED map, The device settings have a "Key test" option (column or row sweep) to check the LED map,
"Low frame-rate mode" in case the keyboard drops frames at full speed, and a "Frame rate limit" "Low frame-rate mode" (normally not needed) and a "Frame rate limit" (Unlimited / 30 / 15 / 10 fps).
(Unlimited / 30 / 15 / 10 fps). Known issue: single keys occasionally flash a random colour for a moment.
## Protocol ## Protocol
- HID interface 1, usage page `0xFF1C`, usage `0x92` (collection 4), 64-byte output reports. - HID interface 1, usage page `0xFF1C`, usage `0x92` (collection 4), 64-byte output reports.
- Colour frame: packets of `[0x04, sumLo, sumHi, 0x12, len(56), offLo, offHi, 0x00, ...56 RGB bytes]`, - Colour frame: packets of `[0x04, sumLo, sumHi, 0x12, len, offLo, offHi, 0x00, ...len RGB bytes]`,
where the checksum is the 16-bit sum of bytes 3..63. 7 packets cover LED slots 0..127. where the checksum is the 16-bit sum of bytes 3..63. The plugin uses len 54: 7 packets cover LEDs 0..125.
- The keyboard shows each packet as it arrives, so `len` must be a multiple of 3. With the stock
plugin's 56, LEDs 18, 74 and 93 (W, O, Menu) are split across packets and flash random colours.
- The keyboard falls back to its own effect when frames stop, so colours must be streamed. - The keyboard falls back to its own effect when frames stop, so colours must be streamed.
## LED map ## LED map
+6 -4
View File
@@ -24,7 +24,7 @@ export function ControllableParameters() {
{property:"shutdownColor", group:"lighting", label:"Shutdown Color", description: "This color is applied to the device when the System, or SignalRGB is shutting down", min:"0", max:"360", type:"color", default:"#000000"}, {property:"shutdownColor", group:"lighting", label:"Shutdown Color", description: "This color is applied to the device when the System, or SignalRGB is shutting down", min:"0", max:"360", type:"color", default:"#000000"},
{property:"LightingMode", group:"lighting", label:"Lighting Mode", description: "Determines where the device's RGB comes from. Canvas will pull from the active Effect, while Forced will override it to a specific color", type:"combobox", values:["Canvas", "Forced"], default:"Canvas"}, {property:"LightingMode", group:"lighting", label:"Lighting Mode", description: "Determines where the device's RGB comes from. Canvas will pull from the active Effect, while Forced will override it to a specific color", type:"combobox", values:["Canvas", "Forced"], default:"Canvas"},
{property:"forcedColor", group:"lighting", label:"Forced Color", description: "The color used when 'Forced' Lighting Mode is enabled", min:"0", max:"360", type:"color", default:"#009bde"}, {property:"forcedColor", group:"lighting", label:"Forced Color", description: "The color used when 'Forced' Lighting Mode is enabled", min:"0", max:"360", type:"color", default:"#009bde"},
{property:"lowFPS", group:"lighting", label:"Low frame-rate mode", description: "Sends smaller packets with pauses in between, can improve stability", type:"boolean", default:false}, {property:"lowFPS", group:"lighting", label:"Low frame-rate mode", description: "Sends smaller packets with pauses in between; normally not needed", type:"boolean", default:false},
{property:"frameLimit", group:"lighting", label:"Frame rate limit", description: "Maximum frames per second sent to the keyboard", type:"combobox", values:["Unlimited", "30", "15", "10"], default:"Unlimited"}, {property:"frameLimit", group:"lighting", label:"Frame rate limit", description: "Maximum frames per second sent to the keyboard", type:"combobox", values:["Unlimited", "30", "15", "10"], default:"Unlimited"},
{property:"keyTest", group:"lighting", label:"Key test", description: "Sweeps a white line across the keys to check the LED map", type:"combobox", values:["Off", "Column sweep", "Row sweep"], default:"Off"}, {property:"keyTest", group:"lighting", label:"Key test", description: "Sweeps a white line across the keys to check the LED map", type:"combobox", values:["Off", "Column sweep", "Row sweep"], default:"Off"},
]; ];
@@ -57,8 +57,8 @@ const vLedPositions = [
[0, 5], [1, 5], [2, 5], [6, 5], [10, 5], [11, 5], [12, 5], [13, 5], [14, 5], [15, 5], [16, 5], [0, 5], [1, 5], [2, 5], [6, 5], [10, 5], [11, 5], [12, 5], [13, 5], [14, 5], [15, 5], [16, 5],
]; ];
// Highest LED index is 125; 128 slots = 384 bytes = 7 full packets of 56 // Highest LED index is 125; 126 slots = 378 bytes = exactly 7 packets of 54
const LED_SLOTS = 128; const LED_SLOTS = 126;
export function LedNames() { return vLedNames; } export function LedNames() { return vLedNames; }
export function LedPositions() { return vLedPositions; } export function LedPositions() { return vLedPositions; }
@@ -118,7 +118,9 @@ function sendColors(overrideColor) {
} }
function writeRGBPackage(RGBData) { function writeRGBPackage(RGBData) {
const bytesToSend = lowFPS ? 48 : 56; // Must be a multiple of 3: the keyboard shows each packet as it arrives, so an LED split
// across two packets briefly flashes a mix of its old and new colour
const bytesToSend = lowFPS ? 48 : 54;
for (let offset = 0; offset < RGBData.length; offset += bytesToSend) { for (let offset = 0; offset < RGBData.length; offset += bytesToSend) {
const data = RGBData.slice(offset, offset + bytesToSend); const data = RGBData.slice(offset, offset + bytesToSend);