feat: Add RGBW LED support and hardware communication protocol

- Add RGBW LED type support alongside existing RGB LEDs
- Implement 4-channel RGBW data transmission (R,G,B,W bytes)
- Add RGBW visual preview with half-color, half-white gradient display
- Fix RGB color calibration bug in publisher (was not being applied)
- Create comprehensive hardware communication protocol documentation
- Support mixed RGB/RGBW LED strips on same display
- Add W channel color temperature adjustment in white balance page
- Hardware acts as simple UDP-to-WS2812 bridge without type distinction
This commit is contained in:
2025-07-05 02:46:31 +08:00
parent 5de105960b
commit 99cbaf3b9f
10 changed files with 392 additions and 20 deletions

View File

@ -3,6 +3,7 @@ import { Component, createMemo, For, JSX, splitProps } from 'solid-js';
import { DisplayInfo } from '../../models/display-info.model';
import { ledStripStore } from '../../stores/led-strip.store';
import { Borders } from '../../constants/border';
import { LedType } from '../../models/led-strip-config';
type LedCountControlItemProps = {
displayId: number;
@ -45,7 +46,7 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
const target = e.target as HTMLInputElement;
const newValue = parseInt(target.value);
const currentLen = config()?.len || 0;
if (!isNaN(newValue) && newValue >= 0 && newValue <= 1000) {
const deltaLen = newValue - currentLen;
if (deltaLen !== 0) {
@ -65,6 +66,19 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
}
};
const handleLedTypeChange = (e: Event) => {
const target = e.target as HTMLSelectElement;
const newType = target.value as LedType;
invoke('patch_led_strip_type', {
displayId: props.displayId,
border: props.border,
ledType: newType,
}).catch((e) => {
console.error(e);
});
};
return (
<div class="card bg-base-100 border border-base-300/50 p-2">
<div class="flex flex-col gap-1">
@ -107,6 +121,18 @@ const LedCountControlItem: Component<LedCountControlItemProps> = (props) => {
+
</button>
</div>
<div class="mt-1">
<select
class="select select-xs w-full text-xs"
value={config()?.led_type || LedType.RGB}
onChange={handleLedTypeChange}
title="LED类型"
>
<option value={LedType.RGB}>RGB</option>
<option value={LedType.RGBW}>RGBW</option>
</select>
</div>
</div>
</div>
);

View File

@ -74,6 +74,8 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
return;
}
// Frontend always uses RGB data (3 bytes per LED) for preview
// The backend sends RGB data to frontend regardless of LED type
const offset = mapper.start * 3;
console.log('🎨 LED: Updating colors', {
@ -124,7 +126,19 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
},
});
const onWheel = (e: WheelEvent) => {
if (localProps.config) {
invoke('patch_led_strip_len', {
displayId: localProps.config.display_id,
border: localProps.config.border,
deltaLen: e.deltaY > 0 ? 1 : -1,
})
.then(() => {})
.catch((e) => {
console.error(e);
});
}
};
return (
<section
@ -140,7 +154,7 @@ export const LedStripPart: Component<LedStripPartProps> = (props) => {
stripConfiguration.selectedStripPart?.displayId ===
localProps.config?.display_id,
}}
onWheel={onWheel}
>
<For each={colors()}>{(item) => <Pixel color={item} />}</For>
</section>

View File

@ -266,10 +266,19 @@ export const WhiteBalance = () => {
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-base-content/70"> (W)</span>
<div class="badge badge-outline badge-sm"></div>
<span class="label-text font-semibold text-amber-500"> (W)</span>
<Value value={ledStripStore.colorCalibration.w} />
</label>
<ColorSlider class="from-yellow-50 to-cyan-50" disabled />
<ColorSlider
class="from-amber-100 to-amber-50"
value={ledStripStore.colorCalibration.w}
onInput={(ev) =>
updateColorCalibration(
'w',
(ev.target as HTMLInputElement).valueAsNumber ?? 1,
)
}
/>
</div>
</div>
@ -400,6 +409,23 @@ export const WhiteBalance = () => {
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-amber-500"> (W)</span>
<Value value={ledStripStore.colorCalibration.w} />
</label>
<ColorSlider
class="from-amber-100 to-amber-50"
value={ledStripStore.colorCalibration.w}
onInput={(ev) =>
updateColorCalibration(
'w',
(ev.target as HTMLInputElement).valueAsNumber ?? 1,
)
}
/>
</div>
<div class="form-control">
<label class="label">
<span class="label-text font-semibold text-base-content/70"> (W)</span>

View File

@ -1,5 +1,10 @@
import { Borders } from '../constants/border';
export enum LedType {
RGB = 'RGB',
RGBW = 'RGBW',
}
export type LedStripPixelMapper = {
start: number;
end: number;
@ -10,6 +15,7 @@ export class ColorCalibration {
r: number = 1;
g: number = 1;
b: number = 1;
w: number = 1;
}
export type LedStripConfigContainer = {
@ -23,5 +29,6 @@ export class LedStripConfig {
public readonly display_id: number,
public readonly border: Borders,
public len: number,
public led_type: LedType = LedType.RGB,
) {}
}