diff --git a/gallery/src/demos/demo-more-info-light.ts b/gallery/src/demos/demo-more-info-light.ts
index d469da221208..399873aa9cf8 100644
--- a/gallery/src/demos/demo-more-info-light.ts
+++ b/gallery/src/demos/demo-more-info-light.ts
@@ -9,13 +9,10 @@ import {
} from "lit-element";
import "../../../src/components/ha-card";
import {
- SUPPORT_BRIGHTNESS,
- SUPPORT_COLOR,
- SUPPORT_COLOR_TEMP,
+ LightColorModes,
SUPPORT_EFFECT,
SUPPORT_FLASH,
SUPPORT_TRANSITION,
- SUPPORT_WHITE_VALUE,
} from "../../../src/data/light";
import "../../../src/dialogs/more-info/more-info-content";
import { getEntity } from "../../../src/fake_data/entity";
@@ -32,7 +29,8 @@ const ENTITIES = [
getEntity("light", "kitchen_light", "on", {
friendly_name: "Brightness Light",
brightness: 200,
- supported_features: SUPPORT_BRIGHTNESS,
+ supported_color_modes: [LightColorModes.BRIGHTNESS],
+ color_mode: LightColorModes.BRIGHTNESS,
}),
getEntity("light", "color_temperature_light", "on", {
friendly_name: "White Color Temperature Light",
@@ -40,20 +38,96 @@ const ENTITIES = [
color_temp: 75,
min_mireds: 30,
max_mireds: 150,
- supported_features: SUPPORT_BRIGHTNESS + SUPPORT_COLOR_TEMP,
+ supported_color_modes: [
+ LightColorModes.BRIGHTNESS,
+ LightColorModes.COLOR_TEMP,
+ ],
+ color_mode: LightColorModes.COLOR_TEMP,
}),
- getEntity("light", "color_effectslight", "on", {
- friendly_name: "Color Effets Light",
+ getEntity("light", "color_hs_light", "on", {
+ friendly_name: "Color HS Light",
brightness: 255,
hs_color: [30, 100],
- white_value: 36,
- supported_features:
- SUPPORT_BRIGHTNESS +
- SUPPORT_EFFECT +
- SUPPORT_FLASH +
- SUPPORT_COLOR +
- SUPPORT_TRANSITION +
- SUPPORT_WHITE_VALUE,
+ rgb_color: [30, 100, 255],
+ min_mireds: 30,
+ max_mireds: 150,
+ supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
+ supported_color_modes: [
+ LightColorModes.BRIGHTNESS,
+ LightColorModes.COLOR_TEMP,
+ LightColorModes.HS,
+ ],
+ color_mode: LightColorModes.HS,
+ effect_list: ["random", "colorloop"],
+ }),
+ getEntity("light", "color_rgb_ct_light", "on", {
+ friendly_name: "Color RGB + CT Light",
+ brightness: 255,
+ color_temp: 75,
+ min_mireds: 30,
+ max_mireds: 150,
+ supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
+ supported_color_modes: [
+ LightColorModes.BRIGHTNESS,
+ LightColorModes.COLOR_TEMP,
+ LightColorModes.RGB,
+ ],
+ color_mode: LightColorModes.COLOR_TEMP,
+ effect_list: ["random", "colorloop"],
+ }),
+ getEntity("light", "color_RGB_light", "on", {
+ friendly_name: "Color Effets Light",
+ brightness: 255,
+ rgb_color: [30, 100, 255],
+ supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
+ supported_color_modes: [LightColorModes.BRIGHTNESS, LightColorModes.RGB],
+ color_mode: LightColorModes.RGB,
+ effect_list: ["random", "colorloop"],
+ }),
+ getEntity("light", "color_rgbw_light", "on", {
+ friendly_name: "Color RGBW Light",
+ brightness: 255,
+ rgbw_color: [30, 100, 255, 125],
+ min_mireds: 30,
+ max_mireds: 150,
+ supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
+ supported_color_modes: [
+ LightColorModes.BRIGHTNESS,
+ LightColorModes.COLOR_TEMP,
+ LightColorModes.RGBW,
+ ],
+ color_mode: LightColorModes.RGBW,
+ effect_list: ["random", "colorloop"],
+ }),
+ getEntity("light", "color_rgbww_light", "on", {
+ friendly_name: "Color RGBWW Light",
+ brightness: 255,
+ rgbww_color: [30, 100, 255, 125, 10],
+ min_mireds: 30,
+ max_mireds: 150,
+ supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
+ supported_color_modes: [
+ LightColorModes.BRIGHTNESS,
+ LightColorModes.COLOR_TEMP,
+ LightColorModes.RGBWW,
+ ],
+ color_mode: LightColorModes.RGBWW,
+ effect_list: ["random", "colorloop"],
+ }),
+ getEntity("light", "color_xy_light", "on", {
+ friendly_name: "Color XY Light",
+ brightness: 255,
+ xy_color: [30, 100],
+ rgb_color: [30, 100, 255],
+ min_mireds: 30,
+ max_mireds: 150,
+ supported_features: SUPPORT_EFFECT + SUPPORT_FLASH + SUPPORT_TRANSITION,
+ supported_color_modes: [
+ LightColorModes.BRIGHTNESS,
+ LightColorModes.COLOR_TEMP,
+ LightColorModes.XY,
+ ],
+ color_mode: LightColorModes.XY,
effect_list: ["random", "colorloop"],
}),
];
diff --git a/src/common/color/convert-color.ts b/src/common/color/convert-color.ts
index 29f95b42838a..a0716a68e432 100644
--- a/src/common/color/convert-color.ts
+++ b/src/common/color/convert-color.ts
@@ -102,3 +102,18 @@ export const lab2hex = (lab: [number, number, number]): string => {
const rgb = lab2rgb(lab);
return rgb2hex(rgb);
};
+
+export const rgb2hsv = (
+ rgb: [number, number, number]
+): [number, number, number] => {
+ const [r, g, b] = rgb;
+ const v = Math.max(r, g, b);
+ const c = v - Math.min(r, g, b);
+ const h =
+ c && (v === r ? (g - b) / c : v === g ? 2 + (b - r) / c : 4 + (r - g) / c);
+ return [60 * (h < 0 ? h + 6 : h), v && c / v, v];
+};
+
+export const rgb2hs = (rgb: [number, number, number]): [number, number] => {
+ return rgb2hsv(rgb).slice(0, 2) as [number, number];
+};
diff --git a/src/components/entity/state-badge.ts b/src/components/entity/state-badge.ts
index 0b67adcc6adc..2fa735f5b69a 100644
--- a/src/components/entity/state-badge.ts
+++ b/src/components/entity/state-badge.ts
@@ -15,6 +15,7 @@ import { computeActiveState } from "../../common/entity/compute_active_state";
import { computeStateDomain } from "../../common/entity/compute_state_domain";
import { stateIcon } from "../../common/entity/state_icon";
import { iconColorCSS } from "../../common/style/icon_color_css";
+import { getLightRgbColor, LightEntity } from "../../data/light";
import type { HomeAssistant } from "../../types";
import "../ha-icon";
@@ -99,11 +100,13 @@ export class StateBadge extends LitElement {
hostStyle.backgroundImage = `url(${imageUrl})`;
this._showIcon = false;
} else if (stateObj.state === "on") {
- if (stateObj.attributes.hs_color && this.stateColor !== false) {
- const hue = stateObj.attributes.hs_color[0];
- const sat = stateObj.attributes.hs_color[1];
- if (sat > 10) {
- iconStyle.color = `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
+ if (
+ computeStateDomain(stateObj) === "light" &&
+ this.stateColor !== false
+ ) {
+ const rgb = getLightRgbColor(stateObj as LightEntity);
+ if (rgb) {
+ iconStyle.color = `rgb(${rgb.slice(0, 3).join(",")})`;
}
}
if (stateObj.attributes.brightness && this.stateColor !== false) {
diff --git a/src/components/ha-button-toggle-group.ts b/src/components/ha-button-toggle-group.ts
index 33c65cdf82c5..8facec61bf8d 100644
--- a/src/components/ha-button-toggle-group.ts
+++ b/src/components/ha-button-toggle-group.ts
@@ -9,6 +9,7 @@ import {
property,
TemplateResult,
} from "lit-element";
+import { styleMap } from "lit-html/directives/style-map";
import { fireEvent } from "../common/dom/fire_event";
import type { ToggleButton } from "../types";
import "./ha-svg-icon";
@@ -19,6 +20,8 @@ export class HaButtonToggleGroup extends LitElement {
@property() public active?: string;
+ @property({ type: Boolean }) public fullWidth = false;
+
protected render(): TemplateResult {
return html`
@@ -33,6 +36,11 @@ export class HaButtonToggleGroup extends LitElement {
`
: html`
@@ -282,12 +291,13 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
processUserSelect(ev) {
const canvasXY = this.convertToCanvasCoordinates(ev.clientX, ev.clientY);
const hs = this.getColor(canvasXY.x, canvasXY.y);
- this.onColorSelect(hs);
+ const rgb = this.getRgbColor(canvasXY.x, canvasXY.y);
+ this.onColorSelect(hs, rgb);
}
// apply color to marker position and canvas
- onColorSelect(hs) {
- this.setMarkerOnColor(hs); // marker always follows mounse 'raw' hs value (= mouse position)
+ onColorSelect(hs, rgb) {
+ this.setMarkerOnColor(hs); // marker always follows mouse 'raw' hs value (= mouse position)
if (!this.ignoreSegments) {
// apply segments if needed
hs = this.applySegmentFilter(hs);
@@ -301,11 +311,11 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
// eventually after throttle limit has passed
clearTimeout(this.ensureFinalSelect);
this.ensureFinalSelect = setTimeout(() => {
- this.fireColorSelected(hs); // do it for the final time
+ this.fireColorSelected(hs, rgb); // do it for the final time
}, this.throttle);
return;
}
- this.fireColorSelected(hs); // do it
+ this.fireColorSelected(hs, rgb); // do it
this.colorSelectIsThrottled = true;
setTimeout(() => {
this.colorSelectIsThrottled = false;
@@ -313,9 +323,9 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
}
// set color values and fire colorselected event
- fireColorSelected(hs) {
+ fireColorSelected(hs, rgb) {
this.hsColor = hs;
- this.fire("colorselected", { hs: { h: hs.h, s: hs.s } });
+ this.fire("colorselected", { hs, rgb });
}
/*
@@ -363,6 +373,11 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
this.applyColorToCanvas(hs);
}
+ applyRgbColor(rgb) {
+ const [h, s] = rgb2hs(rgb);
+ this.applyHsColor({ h, s });
+ }
+
/*
* input processing helpers
*/
@@ -395,6 +410,15 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
return { h: hue, s: sat };
}
+ getRgbColor(x, y) {
+ // get current pixel
+ const imageData = this.backgroundLayer
+ .getContext("2d")
+ .getImageData(x + 250, y + 250, 1, 1);
+ const pixel = imageData.data;
+ return { r: pixel[0], g: pixel[1], b: pixel[2] };
+ }
+
applySegmentFilter(hs) {
// apply hue segment steps
if (this.hueSegments) {
@@ -468,7 +492,7 @@ class HaColorPicker extends EventsMixin(PolymerElement) {
.getPropertyValue("--wheel-bordercolor")
.trim();
const wheelShadow = wheelStyle.getPropertyValue("--wheel-shadow").trim();
- // extract shadow properties from CCS variable
+ // extract shadow properties from CSS variable
// the shadow should be defined as: "10px 5px 5px 0px COLOR"
if (wheelShadow !== "none") {
const values = wheelShadow.split("px ");
diff --git a/src/data/light.ts b/src/data/light.ts
index ff0070e7d01b..27f335709ea0 100644
--- a/src/data/light.ts
+++ b/src/data/light.ts
@@ -3,26 +3,82 @@ import {
HassEntityBase,
} from "home-assistant-js-websocket";
+export enum LightColorModes {
+ UNKNOWN = "unknown",
+ ONOFF = "onoff",
+ BRIGHTNESS = "brightness",
+ COLOR_TEMP = "color_temp",
+ HS = "hs",
+ XY = "xy",
+ RGB = "rgb",
+ RGBW = "rgbw",
+ RGBWW = "rgbww",
+}
+
+const modesSupportingColor = [
+ LightColorModes.HS,
+ LightColorModes.XY,
+ LightColorModes.RGB,
+ LightColorModes.RGBW,
+ LightColorModes.RGBWW,
+];
+
+const modesSupportingDimming = [
+ ...modesSupportingColor,
+ LightColorModes.COLOR_TEMP,
+ LightColorModes.BRIGHTNESS,
+];
+
+export const SUPPORT_EFFECT = 4;
+export const SUPPORT_FLASH = 8;
+export const SUPPORT_TRANSITION = 32;
+
+export const lightSupportsColorMode = (
+ entity: LightEntity,
+ mode: LightColorModes
+) => {
+ return entity.attributes.supported_color_modes?.includes(mode);
+};
+
+export const lightIsInColorMode = (entity: LightEntity) => {
+ return modesSupportingColor.includes(entity.attributes.color_mode);
+};
+
+export const lightSupportsColor = (entity: LightEntity) => {
+ return entity.attributes.supported_color_modes?.some((mode) =>
+ modesSupportingColor.includes(mode)
+ );
+};
+
+export const lightSupportsDimming = (entity: LightEntity) => {
+ return entity.attributes.supported_color_modes?.some((mode) =>
+ modesSupportingDimming.includes(mode)
+ );
+};
+
+export const getLightRgbColor = (entity: LightEntity): number[] | undefined =>
+ entity.attributes.color_mode === LightColorModes.RGBWW
+ ? entity.attributes.rgbww_color
+ : entity.attributes.color_mode === LightColorModes.RGBW
+ ? entity.attributes.rgbw_color
+ : entity.attributes.rgb_color;
+
interface LightEntityAttributes extends HassEntityAttributeBase {
min_mireds: number;
max_mireds: number;
friendly_name: string;
brightness: number;
- hs_color: number[];
+ hs_color: [number, number];
+ rgb_color: [number, number, number];
+ rgbw_color: [number, number, number, number];
+ rgbww_color: [number, number, number, number, number];
color_temp: number;
- white_value: number;
effect?: string;
effect_list: string[] | null;
+ supported_color_modes: LightColorModes[];
+ color_mode: LightColorModes;
}
export interface LightEntity extends HassEntityBase {
attributes: LightEntityAttributes;
}
-
-export const SUPPORT_BRIGHTNESS = 1;
-export const SUPPORT_COLOR_TEMP = 2;
-export const SUPPORT_EFFECT = 4;
-export const SUPPORT_FLASH = 8;
-export const SUPPORT_COLOR = 16;
-export const SUPPORT_TRANSITION = 32;
-export const SUPPORT_WHITE_VALUE = 128;
diff --git a/src/dialogs/more-info/controls/more-info-light.ts b/src/dialogs/more-info/controls/more-info-light.ts
index b0f686e412f0..7bb67a8b7788 100644
--- a/src/dialogs/more-info/controls/more-info-light.ts
+++ b/src/dialogs/more-info/controls/more-info-light.ts
@@ -11,7 +11,6 @@ import {
PropertyValues,
TemplateResult,
} from "lit-element";
-import { classMap } from "lit-html/directives/class-map";
import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-attributes";
import "../../../components/ha-color-picker";
@@ -19,20 +18,22 @@ import "../../../components/ha-icon-button";
import "../../../components/ha-labeled-slider";
import "../../../components/ha-paper-dropdown-menu";
import {
+ getLightRgbColor,
+ LightColorModes,
LightEntity,
- SUPPORT_BRIGHTNESS,
- SUPPORT_COLOR,
- SUPPORT_COLOR_TEMP,
+ lightIsInColorMode,
+ lightSupportsColor,
+ lightSupportsColorMode,
+ lightSupportsDimming,
SUPPORT_EFFECT,
- SUPPORT_WHITE_VALUE,
} from "../../../data/light";
import type { HomeAssistant } from "../../../types";
+import "../../../components/ha-button-toggle-group";
-interface HueSatColor {
- h: number;
- s: number;
-}
-
+const toggleButtons = [
+ { label: "Color", value: "color" },
+ { label: "Temperature", value: LightColorModes.COLOR_TEMP },
+];
@customElement("more-info-light")
class MoreInfoLight extends LitElement {
@property({ attribute: false }) public hass!: HomeAssistant;
@@ -41,28 +42,51 @@ class MoreInfoLight extends LitElement {
@internalProperty() private _brightnessSliderValue = 0;
- @internalProperty() private _ctSliderValue = 0;
+ @internalProperty() private _ctSliderValue?: number;
+
+ @internalProperty() private _cwSliderValue?: number;
+
+ @internalProperty() private _wwSliderValue?: number;
- @internalProperty() private _wvSliderValue = 0;
+ @internalProperty() private _wvSliderValue?: number;
+
+ @internalProperty() private _colorBrightnessSliderValue?: number;
+
+ @internalProperty() private _brightnessAdjusted?: number;
@internalProperty() private _hueSegments = 24;
@internalProperty() private _saturationSegments = 8;
- @internalProperty() private _colorPickerColor?: HueSatColor;
+ @internalProperty() private _colorPickerColor?: [number, number, number];
+
+ @internalProperty() private _mode?: "color" | LightColorModes.COLOR_TEMP;
protected render(): TemplateResult {
if (!this.hass || !this.stateObj) {
return html``;
}
+ const supportsTemp = lightSupportsColorMode(
+ this.stateObj,
+ LightColorModes.COLOR_TEMP
+ );
+
+ const supportsRgbww = lightSupportsColorMode(
+ this.stateObj,
+ LightColorModes.RGBWW
+ );
+
+ const supportsRgbw =
+ !supportsRgbww &&
+ lightSupportsColorMode(this.stateObj, LightColorModes.RGBW);
+
+ const supportsColor =
+ supportsRgbww || supportsRgbw || lightSupportsColor(this.stateObj);
+
return html`
-
- ${supportsFeature(this.stateObj!, SUPPORT_BRIGHTNESS)
+
+ ${lightSupportsDimming(this.stateObj)
? html`
` : ""}
+ ${supportsTemp && supportsColor
+ ? html``
+ : ""}
+ ${supportsTemp &&
+ (!supportsColor || this._mode === LightColorModes.COLOR_TEMP)
? html`
+
`
: ""}
- ${supportsFeature(this.stateObj, SUPPORT_WHITE_VALUE)
- ? html`
-
- `
- : ""}
- ${supportsFeature(this.stateObj, SUPPORT_COLOR)
+ ${supportsColor && (!supportsTemp || this._mode === "color")
? html`
+
+ ${
+ supportsRgbw || supportsRgbww
+ ? html``
+ : ""
+ }
+ ${
+ supportsRgbw
+ ? html`
+
+ `
+ : ""
+ }
+ ${
+ supportsRgbww
+ ? html`
+
+
+ `
+ : ""
+ }
+
`
: ""}
${supportsFeature(this.stateObj, SUPPORT_EFFECT) &&
@@ -151,34 +235,85 @@ class MoreInfoLight extends LitElement {
: ""}
`;
}
- protected updated(changedProps: PropertyValues): void {
+ protected updated(changedProps: PropertyValues
) {
+ if (!changedProps.has("stateObj")) {
+ return;
+ }
const stateObj = this.stateObj! as LightEntity;
- if (changedProps.has("stateObj")) {
- if (stateObj.state === "on") {
- this._brightnessSliderValue = Math.round(
- (stateObj.attributes.brightness * 100) / 255
- );
- this._ctSliderValue = stateObj.attributes.color_temp;
- this._wvSliderValue = stateObj.attributes.white_value;
-
- if (stateObj.attributes.hs_color) {
- this._colorPickerColor = {
- h: stateObj.attributes.hs_color[0],
- s: stateObj.attributes.hs_color[1] / 100,
- };
+ const oldStateObj = changedProps.get("stateObj") as LightEntity | undefined;
+
+ if (stateObj.state === "on") {
+ // Don't change tab when the color mode changes
+ if (
+ oldStateObj?.entity_id !== stateObj.entity_id ||
+ oldStateObj?.state !== stateObj.state
+ ) {
+ this._mode = lightIsInColorMode(this.stateObj!)
+ ? "color"
+ : LightColorModes.COLOR_TEMP;
+ }
+
+ let brightnessAdjust = 100;
+ if (
+ stateObj.attributes.color_mode === LightColorModes.RGB &&
+ !lightSupportsColorMode(stateObj, LightColorModes.RGBWW) &&
+ !lightSupportsColorMode(stateObj, LightColorModes.RGBW)
+ ) {
+ const maxVal = Math.max(...stateObj.attributes.rgb_color);
+ if (maxVal < 255) {
+ this._brightnessAdjusted = maxVal;
+ brightnessAdjust = (this._brightnessAdjusted / 255) * 100;
}
} else {
- this._brightnessSliderValue = 0;
+ this._brightnessAdjusted = undefined;
}
+ this._brightnessSliderValue = Math.round(
+ (stateObj.attributes.brightness * brightnessAdjust) / 255
+ );
+ this._ctSliderValue = stateObj.attributes.color_temp;
+ this._wvSliderValue =
+ stateObj.attributes.color_mode === LightColorModes.RGBW
+ ? Math.round((stateObj.attributes.rgbw_color[3] * 100) / 255)
+ : undefined;
+ this._cwSliderValue =
+ stateObj.attributes.color_mode === LightColorModes.RGBWW
+ ? Math.round((stateObj.attributes.rgbww_color[3] * 100) / 255)
+ : undefined;
+ this._wwSliderValue =
+ stateObj.attributes.color_mode === LightColorModes.RGBWW
+ ? Math.round((stateObj.attributes.rgbww_color[4] * 100) / 255)
+ : undefined;
+ this._colorBrightnessSliderValue =
+ stateObj.attributes.color_mode === LightColorModes.RGBWW
+ ? Math.round(
+ (Math.max(...stateObj.attributes.rgbww_color.slice(0, 3)) * 100) /
+ 255
+ )
+ : stateObj.attributes.color_mode === LightColorModes.RGBW
+ ? Math.round(
+ (Math.max(...stateObj.attributes.rgbw_color.slice(0, 3)) * 100) /
+ 255
+ )
+ : undefined;
+
+ this._colorPickerColor = getLightRgbColor(stateObj)?.slice(0, 3) as
+ | [number, number, number]
+ | undefined;
+ } else {
+ this._brightnessSliderValue = 0;
}
}
+ private _modeChanged(ev: CustomEvent) {
+ this._mode = ev.detail.value;
+ }
+
private _effectChanged(ev: CustomEvent) {
const newVal = ev.detail.item.itemName;
@@ -193,12 +328,29 @@ class MoreInfoLight extends LitElement {
}
private _brightnessSliderChanged(ev: CustomEvent) {
- const bri = parseInt((ev.target as any).value, 10);
+ const bri = Number((ev.target as any).value);
if (isNaN(bri)) {
return;
}
+ if (this._brightnessAdjusted) {
+ const rgb =
+ this.stateObj!.attributes.rgb_color ||
+ ([0, 0, 0] as [number, number, number]);
+
+ this.hass.callService("light", "turn_on", {
+ entity_id: this.stateObj!.entity_id,
+ brightness_pct: bri,
+ rgb_color: this._adjustColorBrightness(
+ rgb,
+ this._brightnessAdjusted,
+ true
+ ),
+ });
+ return;
+ }
+
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
brightness_pct: bri,
@@ -206,7 +358,7 @@ class MoreInfoLight extends LitElement {
}
private _ctSliderChanged(ev: CustomEvent) {
- const ct = parseInt((ev.target as any).value, 10);
+ const ct = Number((ev.target as any).value);
if (isNaN(ct)) {
return;
@@ -219,18 +371,64 @@ class MoreInfoLight extends LitElement {
}
private _wvSliderChanged(ev: CustomEvent) {
- const wv = parseInt((ev.target as any).value, 10);
+ const target = ev.target as any;
+ let wv = Number(target.value);
+ const name = target.name;
if (isNaN(wv)) {
return;
}
+ wv = (wv * 255) / 100;
+
+ const rgb = getLightRgbColor(this.stateObj!);
+
+ if (name === "wv") {
+ const rgbw_color = rgb || [0, 0, 0, 0];
+ rgbw_color[3] = wv;
+ this.hass.callService("light", "turn_on", {
+ entity_id: this.stateObj!.entity_id,
+ rgbw_color,
+ });
+ return;
+ }
+
+ const rgbww_color = rgb || [0, 0, 0, 0, 0];
+ while (rgbww_color.length < 5) {
+ rgbww_color.push(0);
+ }
+ rgbww_color[name === "cw" ? 3 : 4] = wv;
this.hass.callService("light", "turn_on", {
entity_id: this.stateObj!.entity_id,
- white_value: wv,
+ rgbww_color,
});
}
+ private _colorBrightnessSliderChanged(ev: CustomEvent) {
+ const target = ev.target as any;
+ const value = Number(target.value);
+
+ const rgb = (getLightRgbColor(this.stateObj!)?.slice(0, 3) || [
+ 255,
+ 255,
+ 255,
+ ]) as [number, number, number];
+
+ this._setRgbColor(
+ this._adjustColorBrightness(
+ // first normalize the value
+ this._colorBrightnessSliderValue
+ ? this._adjustColorBrightness(
+ rgb,
+ this._colorBrightnessSliderValue,
+ true
+ )
+ : rgb,
+ value
+ )
+ );
+ }
+
private _segmentClick() {
if (this._hueSegments === 24 && this._saturationSegments === 8) {
this._hueSegments = 0;
@@ -241,15 +439,90 @@ class MoreInfoLight extends LitElement {
}
}
+ private _adjustColorBrightness(
+ rgbColor: [number, number, number],
+ value?: number,
+ invert = false
+ ) {
+ if (value !== undefined && value !== 255) {
+ let ratio = value / 255;
+ if (invert) {
+ ratio = 1 / ratio;
+ }
+ rgbColor[0] *= ratio;
+ rgbColor[1] *= ratio;
+ rgbColor[2] *= ratio;
+ }
+ return rgbColor;
+ }
+
+ private _setRgbColor(rgbColor: [number, number, number]) {
+ if (lightSupportsColorMode(this.stateObj!, LightColorModes.RGBWW)) {
+ const rgbww_color: [number, number, number, number, number] = this
+ .stateObj!.attributes.rgbww_color
+ ? [...this.stateObj!.attributes.rgbww_color]
+ : [0, 0, 0, 0, 0];
+ this.hass.callService("light", "turn_on", {
+ entity_id: this.stateObj!.entity_id,
+ rgbww_color: rgbColor.concat(rgbww_color.slice(3)),
+ });
+ } else if (lightSupportsColorMode(this.stateObj!, LightColorModes.RGBW)) {
+ const rgbw_color: [number, number, number, number] = this.stateObj!
+ .attributes.rgbw_color
+ ? [...this.stateObj!.attributes.rgbw_color]
+ : [0, 0, 0, 0];
+ this.hass.callService("light", "turn_on", {
+ entity_id: this.stateObj!.entity_id,
+ rgbw_color: rgbColor.concat(rgbw_color.slice(3)),
+ });
+ }
+ }
+
/**
* Called when a new color has been picked.
* should be throttled with the 'throttle=' attribute of the color picker
*/
private _colorPicked(ev: CustomEvent) {
- this.hass.callService("light", "turn_on", {
- entity_id: this.stateObj!.entity_id,
- hs_color: [ev.detail.hs.h, ev.detail.hs.s * 100],
- });
+ if (
+ lightSupportsColorMode(this.stateObj!, LightColorModes.RGBWW) ||
+ lightSupportsColorMode(this.stateObj!, LightColorModes.RGBW)
+ ) {
+ this._setRgbColor(
+ this._colorBrightnessSliderValue
+ ? this._adjustColorBrightness(
+ [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b],
+ this._colorBrightnessSliderValue
+ )
+ : [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b]
+ );
+ } else if (lightSupportsColorMode(this.stateObj!, LightColorModes.RGB)) {
+ const rgb_color = [ev.detail.rgb.r, ev.detail.rgb.g, ev.detail.rgb.b] as [
+ number,
+ number,
+ number
+ ];
+ if (this._brightnessAdjusted) {
+ this.hass.callService("light", "turn_on", {
+ entity_id: this.stateObj!.entity_id,
+ brightness_pct: this._brightnessSliderValue,
+ rgb_color: this._adjustColorBrightness(
+ rgb_color,
+ this._brightnessAdjusted,
+ true
+ ),
+ });
+ } else {
+ this.hass.callService("light", "turn_on", {
+ entity_id: this.stateObj!.entity_id,
+ rgb_color,
+ });
+ }
+ } else {
+ this.hass.callService("light", "turn_on", {
+ entity_id: this.stateObj!.entity_id,
+ hs_color: [ev.detail.hs.h, ev.detail.hs.s * 100],
+ });
+ }
}
static get styles(): CSSResult {
@@ -275,11 +548,18 @@ class MoreInfoLight extends LitElement {
);
/* The color temp minimum value shouldn't be rendered differently. It's not "off". */
--paper-slider-knob-start-border-color: var(--primary-color);
+ margin-bottom: 4px;
}
.segmentationContainer {
position: relative;
max-height: 500px;
+ display: flex;
+ justify-content: center;
+ }
+
+ ha-button-toggle-group {
+ margin: 8px 0px;
}
ha-color-picker {
@@ -293,12 +573,19 @@ class MoreInfoLight extends LitElement {
.segmentationButton {
position: absolute;
top: 5%;
+ left: 0;
color: var(--secondary-text-color);
}
paper-item {
cursor: pointer;
}
+
+ hr {
+ border-color: var(--divider-color);
+ border-bottom: none;
+ margin: 8px 0;
+ }
`;
}
}
diff --git a/src/panels/lovelace/cards/hui-button-card.ts b/src/panels/lovelace/cards/hui-button-card.ts
index 2a0fc4dcc651..2c535a8bba1a 100644
--- a/src/panels/lovelace/cards/hui-button-card.ts
+++ b/src/panels/lovelace/cards/hui-button-card.ts
@@ -28,7 +28,7 @@ import { stateIcon } from "../../../common/entity/state_icon";
import { isValidEntityId } from "../../../common/entity/valid_entity_id";
import { iconColorCSS } from "../../../common/style/icon_color_css";
import "../../../components/ha-card";
-import { LightEntity } from "../../../data/light";
+import { getLightRgbColor, LightEntity } from "../../../data/light";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
@@ -301,14 +301,14 @@ export class HuiButtonCard extends LitElement implements LovelaceCard {
}
private _computeColor(stateObj: HassEntity | LightEntity): string {
- if (!stateObj.attributes.hs_color || !this._config?.state_color) {
- return "";
- }
- const [hue, sat] = stateObj.attributes.hs_color;
- if (sat <= 10) {
+ if (
+ !this._config?.state_color ||
+ computeStateDomain(stateObj) !== "light"
+ ) {
return "";
}
- return `hsl(${hue}, 100%, ${100 - sat / 2}%)`;
+ const rgb = getLightRgbColor(stateObj as LightEntity);
+ return rgb ? `rgb(${rgb.slice(0, 3).join(",")})` : "";
}
private _handleAction(ev: ActionHandlerEvent) {
diff --git a/src/panels/lovelace/cards/hui-light-card.ts b/src/panels/lovelace/cards/hui-light-card.ts
index 521cc5c7b8ff..c92d6dbbcab9 100644
--- a/src/panels/lovelace/cards/hui-light-card.ts
+++ b/src/panels/lovelace/cards/hui-light-card.ts
@@ -18,11 +18,14 @@ import { fireEvent } from "../../../common/dom/fire_event";
import { computeStateDisplay } from "../../../common/entity/compute_state_display";
import { computeStateName } from "../../../common/entity/compute_state_name";
import { stateIcon } from "../../../common/entity/state_icon";
-import { supportsFeature } from "../../../common/entity/supports-feature";
import "../../../components/ha-card";
import "../../../components/ha-icon-button";
import { UNAVAILABLE, UNAVAILABLE_STATES } from "../../../data/entity";
-import { LightEntity, SUPPORT_BRIGHTNESS } from "../../../data/light";
+import {
+ getLightRgbColor,
+ LightEntity,
+ lightSupportsDimming,
+} from "../../../data/light";
import { ActionHandlerEvent } from "../../../data/lovelace";
import { HomeAssistant } from "../../../types";
import { actionHandler } from "../common/directives/action-handler-directive";
@@ -121,17 +124,14 @@ export class HuiLightCard extends LitElement implements LovelaceCard {
@value-changing=${this._dragEvent}
@value-changed=${this._setBrightness}
style=${styleMap({
- visibility: supportsFeature(stateObj, SUPPORT_BRIGHTNESS)
+ visibility: lightSupportsDimming(stateObj)
? "visible"
: "hidden",
})}
>