better RGB light stuff, simpler on the frontend.

This commit is contained in:
legop3
2026-06-05 14:02:05 -04:00
parent 0fe060cd22
commit 41b59058bc
9 changed files with 180 additions and 99 deletions
@@ -50,14 +50,90 @@ function normalizeTriggerEntry(entry, index) {
};
}
function clampByte(value) {
const numeric = Number(value);
if (!Number.isFinite(numeric)) return 0;
return Math.max(0, Math.min(255, Math.round(numeric)));
}
function byteToHex(value) {
return clampByte(value).toString(16).padStart(2, '0');
}
function rgbToHex(rgb) {
if (!Array.isArray(rgb) || rgb.length < 3) return null;
return `#${byteToHex(rgb[0])}${byteToHex(rgb[1])}${byteToHex(rgb[2])}`;
}
function hsToRgb(hsColor) {
if (!Array.isArray(hsColor) || hsColor.length < 2) return null;
const rawHue = Number(hsColor[0]);
const rawSaturation = Number(hsColor[1]);
if (!Number.isFinite(rawHue) || !Number.isFinite(rawSaturation)) return null;
// Home Assistant's hs_color is hue in degrees plus saturation as 0-100.
// Brightness is not part of this attribute, so the UI color preview uses full
// value. When HA also provides rgb_color, rgb_color wins because it carries
// the already-resolved device color.
const hue = ((rawHue % 360) + 360) % 360;
const saturation = Math.max(0, Math.min(100, rawSaturation)) / 100;
const value = 1;
const chroma = value * saturation;
const x = chroma * (1 - Math.abs(((hue / 60) % 2) - 1));
const m = value - chroma;
let r = 0;
let g = 0;
let b = 0;
if (hue < 60) {
r = chroma;
g = x;
} else if (hue < 120) {
r = x;
g = chroma;
} else if (hue < 180) {
g = chroma;
b = x;
} else if (hue < 240) {
g = x;
b = chroma;
} else if (hue < 300) {
r = x;
b = chroma;
} else {
r = chroma;
b = x;
}
return [
clampByte((r + m) * 255),
clampByte((g + m) * 255),
clampByte((b + m) * 255),
];
}
function colorHexFromAttributes(attributes = {}) {
const rgbColor = Array.isArray(attributes.rgb_color) ? attributes.rgb_color : null;
if (rgbColor) {
return rgbToHex(rgbColor);
}
const hsColor = Array.isArray(attributes.hs_color) ? attributes.hs_color : null;
if (hsColor) {
return rgbToHex(hsToRgb(hsColor));
}
return null;
}
function buildState(meta, raw) {
if (!meta) return null;
const name = meta.name || raw?.attributes?.friendly_name || meta.id;
const attributes = raw?.attributes || {};
const supportedColorModes = Array.isArray(raw?.attributes?.supported_color_modes)
? raw.attributes.supported_color_modes.map((mode) => String(mode))
: [];
const rgbColor = Array.isArray(raw?.attributes?.rgb_color) ? raw.attributes.rgb_color : null;
const hsColor = Array.isArray(raw?.attributes?.hs_color) ? raw.attributes.hs_color : null;
const rgbColor = Array.isArray(attributes.rgb_color) ? attributes.rgb_color : null;
const hsColor = Array.isArray(attributes.hs_color) ? attributes.hs_color : null;
const colorHex = colorHexFromAttributes(attributes);
const supportsColor =
meta.type === 'light' &&
(rgbColor || hsColor || supportedColorModes.some((mode) => mode === 'hs' || mode === 'rgb' || mode === 'xy'));
@@ -73,6 +149,7 @@ function buildState(meta, raw) {
lastUpdated: null,
supportedColorModes,
colorMode: null,
colorHex: null,
rgbColor: null,
hsColor: null,
supportsColor,
@@ -91,7 +168,8 @@ function buildState(meta, raw) {
lastChanged: raw.last_changed || null,
lastUpdated: raw.last_updated || null,
supportedColorModes,
colorMode: raw?.attributes?.color_mode || null,
colorMode: attributes.color_mode || null,
colorHex,
rgbColor,
hsColor,
supportsColor,