From 8c477648ce60413e323da77ad7db0c473a134f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20H=C3=A4nsel?= Date: Thu, 23 Feb 2023 04:18:07 +0100 Subject: [PATCH] Fix color handling for LIDL light bar (Koenkk/zigbee2mqtt#13421) --- devices/lidl.js | 122 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/devices/lidl.js b/devices/lidl.js index 86b95cbf6456a..ff7f8ad7a1249 100644 --- a/devices/lidl.js +++ b/devices/lidl.js @@ -7,6 +7,12 @@ const ea = exposes.access; const tuya = require('../lib/tuya'); const globalStore = require('../lib/store'); const ota = require('../lib/ota'); +const utils = require('../lib/utils'); +const constants = require('../lib/constants'); +const libColor = require('../lib/color'); + +const COLOR_MODE_COLOR_TEMP = constants.colorMode[2]; +const COLOR_MODE_HS = constants.colorMode[0]; const tuyaLocal = { dataPoints: { @@ -356,6 +362,118 @@ const tzLocal = { await tuya.sendDataPointRaw(entity, (109+day-1), results); }, }, + tuya_led_control: { + key: ['brightness', 'color', 'color_temp', 'transition'], + options: [exposes.options.color_sync()], + convertSet: async (entity, _key, _value, meta) => { + const newState = {}; + + + // The color mode encodes whether the light is using its white LEDs or its color LEDs + let colorMode = meta.state.color_mode || COLOR_MODE_COLOR_TEMP; + + // Color mode switching is done by setting color temperature (switch to white LEDs) or setting color (switch + // to color LEDs) + if ('color_temp' in meta.message) { + colorMode = COLOR_MODE_COLOR_TEMP; + } + if ('color' in meta.message) { + colorMode = COLOR_MODE_HS; + } + + if (colorMode != meta.state.color_mode) { + newState.color_mode = colorMode; + + // We need to send a command to switch from white LEDs to color LEDs. We don't need to send one to + // switch back because the color LEDs are automatically turned off by the moveToColorTemp and + // moveToLevel commands. + if (colorMode == COLOR_MODE_HS) { + await entity.command('lightingColorCtrl', 'tuyaRgbMode', {enable: 1}, {}, {disableDefaultResponse: true}); + } + } + + + // A transition time of 0 would be treated as about 1 second, probably some kind of fallback/default + // transition time, so for "no transition" we use 1 (tenth of a second). + let transtime = 1; + if ('transition' in meta.message) { + transtime = meta.message.transition * 10; + } + + + if (colorMode == COLOR_MODE_COLOR_TEMP) { + if ('brightness' in meta.message) { + const zclData = {level: Number(meta.message.brightness), transtime: transtime}; + await entity.command('genLevelCtrl', 'moveToLevel', zclData, utils.getOptions(meta.mapped, entity)); + newState.brightness = meta.message.brightness; + } + + if ('color_temp' in meta.message) { + const zclData = {colortemp: meta.message.color_temp, transtime: transtime}; + await entity.command('lightingColorCtrl', 'moveToColorTemp', zclData, utils.getOptions(meta.mapped, entity)); + newState.color_temp = meta.message.color_temp; + } + } + + if (colorMode == COLOR_MODE_HS) { + if ('brightness' in meta.message || 'color' in meta.message) { + // We ignore the brightness of the color and instead use the overall brightness setting of the lamp + // for the brightness because I think that's the expected behavior and also because the color + // conversion below always returns 100 as brightness ("value") even for very dark colors, except + // when the color is completely black/zero. + + // Load current state or defaults + const newSettings = { + brightness: meta.state.brightness || 254, // full brightness + hue: (meta.state.color || {}).h || 0, // red + saturation: (meta.state.color || {}).s || 100, // full saturation + }; + + // Apply changes + if ('brightness' in meta.message) { + newSettings.brightness = meta.message.brightness; + newState.brightness = meta.message.brightness; + } + if ('color' in meta.message) { + // The Z2M UI sends `{ hex:'#xxxxxx' }`. + // Home Assistant sends `{ h: xxx, s: xxx }`. + // We convert the former into the latter. + const c = libColor.Color.fromConverterArg(meta.message.color); + if (c.isRGB()) { + // https://github.com/Koenkk/zigbee2mqtt/issues/13421#issuecomment-1426044963 + c.hsv = c.rgb.gammaCorrected().toXY().toHSV(); + } + const color = c.hsv; + + newSettings.hue = color.hue; + newSettings.saturation = color.saturation; + + newState.color = { + h: color.hue, + s: color.saturation, + }; + } + + // Convert to device specific format and send + const zclData = { + brightness: utils.mapNumberRange(newSettings.brightness, 0, 254, 0, 1000), + hue: newSettings.hue, + saturation: utils.mapNumberRange(newSettings.saturation, 0, 100, 0, 1000), + }; + // This command doesn't support a transition time + await entity.command('lightingColorCtrl', 'tuyaMoveToHueAndSaturationBrightness2', zclData, + utils.getOptions(meta.mapped, entity)); + } + } + + return {state: newState}; + }, + convertGet: async (entity, key, meta) => { + await entity.read('lightingColorCtrl', [ + 'currentHue', 'currentSaturation', 'tuyaBrightness', 'tuyaRgbMode', 'colorTemperature', + ]); + }, + }, }; module.exports = [ @@ -739,7 +857,9 @@ module.exports = [ model: '14149505L/14149506L', vendor: 'Lidl', description: 'Livarno Lux light bar RGB+CCT (black/white)', - extend: tuya.extend.light_onoff_brightness_colortemp_color({noConfigure: true}), + toZigbee: [tz.on_off, tzLocal.tuya_led_control], + fromZigbee: [fz.on_off, fz.tuya_led_controller, fz.brightness, fz.ignore_basic_report], + exposes: [e.light_brightness_colortemp_colorhs([153, 500]).removeFeature('color_temp_startup')], configure: async (device, coordinatorEndpoint, logger) => { device.getEndpoint(1).saveClusterAttributeKeyValue('lightingColorCtrl', {colorCapabilities: 29}); },