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:
@ -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>
|
||||
);
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
) {}
|
||||
}
|
||||
|
Reference in New Issue
Block a user