New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Color Temperature #26
Comments
Hi Dan - I had a think about this one... It seems that for the lights that support colour temperature in OpenHAB, the binding exposes them as a dimmer - but I would imagine the colour temperature is in discreet steps - my milight bulbs are - but then again, they are cheap :) perhaps the tradfri bulbs are a smooth slider of colour temperature. Anyway, we'd need some way of getting the low and high points of white balance to be able to translate a specific colour temperature to a percentage value for OpenHAB. Are the ways to do this in OpenHAB? |
In case, this support will not be part of the initial release of the v3 ported skill [#54], I have implemented the color temperature support with the current version. In my implementation, the color temperature is defined as a dimmer item which is set to the value given by the API divided by 100. The increment/decrement functionalities send INCREASE/DECREASE command to that dimmer item so that the increment logic can be handled in OH since the api request is not providing an increment value. The associated item can be Color, Dimmer, or even Switch, and has to be tagged with "Lighting". Both items need to be in the same group. Here is an item definition example:
Additionally, it handles the not supported in color mode case, when a color temperature item receives an increment request while the associated color item is that mode, as defined in the API reference. This assumes that on the OH side, the color temperature item state would be updated to 0 or NULL, when a non-white colored command is received by the associated color item. |
@jsetton : Can I somehow help in testing this? I am using the zigbee binding to control some lights, and have been waiting to be able to control color temperature via alexa. |
@AfromD You can run your own version of the skill and back-end function by cloning my repository and using the installation steps listed in the README. Keep in mind that if going that route, you would need to use this workaround to create a new v2 skill in your Amazon developer console. I have tested successfully this implementation with my cabinet RGBW LED strips using the WiFi LED binding and a color temperature to RGBW conversion algorithm since the color and white controls are separated with that binding. I can share my setup if interested. |
@jsetton Thanks for your work. Right now it seems like a steep learning curve to get it going, probably won't find time for that soon. Was hoping there was some way I could have used an already set up (unpublished) instance. |
@jsetton Thanks for sharing your code. I set your skill up. Not super easy, but eventually I had it run. Unfortunately the device I need to control (a WiFi LED strip compatible with the WiFi LED binding, but only RGB - not RGBW) does not have the white channel for the temperature. The native app (MagicHome) is able to emulate all the whites through Alexa by converting the shades of whites to HSL values. Do you know if there is way to let the Alexa app emulate the white through a simple color adjustment? May it be feasible to adapt the Alexa skill code in such a way that tagging the Color device itself (instead of the dimmer) with 'ColorTemperature' creates some sort of "color temperature emulation"? NB: I am not asking you to do it, but just to let me (and other interested readers know if it is feasible without having to modify OH's bindings). |
@djdubois I actually did exactly what you are describing in emulating the Magic Home Alexa skill to a certain extent on the OH side. I don't believe this should be implemented in the skill as the color temperature integration will be different depending on the binding and lights used. To limit the complexity, I could see some portion of the logic that I provided below moved to the WiFi LED binding code but that would require some additional work. You can use the below color temperature to RGB algorithm to get the emulated white color code. On my side, I just have an additional algorithm which translates RGB to RGBW. I also included the rule functions I am using to control my LED strips. Obviously, the change color logic would be much simpler for your setup but I would still recommend using a virtual color item and a function so the non-white and white color commands can be differentiated. As I mentioned above, the color temperature item should be updated to 0 when setting to a non-white color so that the skill can determine if in color mode. As you can see, I initially had hardcoded values but wanted a more linear increment functionality to work with the skill opposed to just cycling through the color temperature values used by the Smart Home API. I also have a color temperature offset ( transform/colorTempToRGB.js (function(kelvin) {
// Convert Color Temperature to RGB using algorithm referenced in this post:
// http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
var temp = kelvin / 100;
var red, green, blue;
if (temp == 0) {
red = green = blue = 0;
} else if (temp <= 66) {
red = 255;
green = Math.round(99.4708025861 * Math.log(temp) - 161.1195681661);
blue = Math.round(temp <= 19 ? 0 : 138.5177312231 * Math.log(temp - 10) - 305.0447927307);
} else {
red = Math.round(329.698727446 * Math.pow(temp - 60, -0.1332047592));
green = Math.round(288.1221695283 * Math.pow(temp - 60, -0.0755148492));
blue = 255;
}
// Clamp color values to 8-bits
red = red > 0 ? red < 255 ? red : 255 : 0;
green = green > 0 ? green < 255 ? green : 255 : 0;
blue = blue > 0 ? blue < 255 ? blue : 255 : 0;
return red + "," + green + "," + blue;
})(input) rules/wifiled.rules // Change Cabinet Color Function
val Functions$Function2<ColorItem, Object, Void> changeCabinetColor = [
cabinetItem, receivedCommand |
val cabinetName = cabinetItem.name
val cabinetGroupName = cabinetItem.groupNames.findFirst[g | g.contains(cabinetName)]
val cabinetGroup = gCabinetLights.members.findFirst[g | g.name == cabinetGroupName] as GroupItem
val cabinetPower = cabinetGroup.members.findFirst[i | i.name == cabinetName + "Power"] as SwitchItem
val cabinetColor = cabinetGroup.members.findFirst[i | i.name == cabinetName + "Color"] as ColorItem
val cabinetWhite = cabinetGroup.members.findFirst[i | i.name == cabinetName + "White"] as DimmerItem
// Turn off power if receive OFF, 0,0,0 (Black color) or 0 (Zero level)
if (receivedCommand instanceof OnOffType && receivedCommand == OFF ||
receivedCommand instanceof HSBType && receivedCommand == HSBType::BLACK ||
receivedCommand instanceof PercentType && receivedCommand == PercentType::ZERO) {
cabinetPower.sendCommand(OFF)
} else {
// Turn on power if not already
if (cabinetPower.state != ON)
cabinetPower.sendCommand(ON)
// Set Cabinet Color/White level depending on receivedCommand
if (receivedCommand instanceof HSBType) {
// Convert RGB color to RGBW
val rgbw = transform("JS", "colorRGBToRGBW.js",
receivedCommand.red + ',' + receivedCommand.green + ',' + receivedCommand.blue)
//logInfo(cabinetName + "Color", "RGB: {},{},{} => RGBW: {}",
// (receivedCommand.red * 2.55).intValue, (receivedCommand.green * 2.55).intValue,
// (receivedCommand.blue * 2.55).intValue, rgbw)
// Seperate colors
val red = Integer::parseInt(rgbw.split(',').get(0))
val green = Integer::parseInt(rgbw.split(',').get(1))
val blue = Integer::parseInt(rgbw.split(',').get(2))
val white = Integer::parseInt(rgbw.split(',').get(3))
// Set Cabinet Color/White
cabinetColor.sendCommand(HSBType::fromRGB(red, green, blue))
cabinetWhite.sendCommand(white)
} else if (receivedCommand instanceof PercentType) {
val color = cabinetColor.state as HSBType
val brightness = Math::round((receivedCommand * color.brightness / (color.brightness + cabinetWhite.state)).floatValue)
val white = Math::round((receivedCommand * cabinetWhite.state / (color.brightness + cabinetWhite.state)).floatValue)
//logInfo(cabinetName + "Color", "Color Level: {} => {}", color.brightness, brightness)
//logInfo(cabinetName + "Color", "White Level: {} => {}", cabinetWhite.state, white)
cabinetColor.sendCommand(new HSBType(color.hue, color.saturation, new PercentType(brightness)))
cabinetWhite.sendCommand(white)
}
}
]
// Change Cabinet Color Temperature Function
val Functions$Function3<ColorItem, Number, Functions$Function2<ColorItem, Object, Void>, Void> changeCabinetColorTemperature = [
cabinetItem, colorTemperature, changeFunction |
// Convert color temperature to RGB
val rgb = transform("JS", "colorTempToRGB.js", colorTemperature.toString)
//logInfo(cabinetItem.name + "ColorTemperature", "RGB: {}", rgb)
// Seperate colors
val red = Integer::parseInt(rgb.split(',').get(0))
val green = Integer::parseInt(rgb.split(',').get(1))
val blue = Integer::parseInt(rgb.split(',').get(2))
// Set Cabinet color
changeFunction.apply(cabinetItem, HSBType::fromRGB(red, green, blue))
// Hardcoded value used by Magic Home Alexa skill (RGBW)
/* switch colorTemperature {
case 2200: changeFunction.apply(cabinetItem, "0,0,100") // Warm White
case 2700: changeFunction.apply(cabinetItem, "28,65,100") // Soft White
case 4000: changeFunction.apply(cabinetItem, "26,25,100") // White
case 5500: changeFunction.apply(cabinetItem, "260,3,100") // Daylight White
case 7000: changeFunction.apply(cabinetItem, "222,18,100") // Cool White
} */
]
// Get Color Temperature Function
val Functions$Function2<Object, Object, Number> getColorTemperature = [
colorTemperatureState, receivedCommand |
var colorTemperature = 0;
val defaultIncrement = 500;
val defaultValue = 4000;
val minValue = 1000;
val maxValue = 10000;
val curValue = if (colorTemperatureState instanceof PercentType) (colorTemperatureState as DecimalType).intValue * 100 else -1
if (receivedCommand instanceof OnOffType) {
colorTemperature = if (receivedCommand == ON) defaultValue else 0
} else if (receivedCommand instanceof PercentType) {
val setValue = (receivedCommand as DecimalType).intValue * 100
colorTemperature = if (setValue > minValue) setValue else minValue
} else if (receivedCommand instanceof IncreaseDecreaseType) {
if (curValue >= 0)
switch receivedCommand {
case INCREASE: colorTemperature = if (curValue + defaultIncrement < maxValue) curValue + defaultIncrement else maxValue
case DECREASE: colorTemperature = if (curValue - defaultIncrement > minValue) curValue - defaultIncrement else minValue
}
}
//logInfo(colorTemperatureItem.name, "Color Temperature: {}K", colorTemperature)
return colorTemperature;
]
rule "Under Cabinet Color Command"
when
Item UnderCabinet received command
then
// Reset Color Temperature
UnderCabinetColorTemperature.postUpdate(0)
// Change Color
changeCabinetColor.apply(UnderCabinet, receivedCommand)
end
rule "Under Cabinet Color Temperature Command"
when
Item UnderCabinetColorTemperature received command
then
// Determine color temperature
val colorTemperature = getColorTemperature.apply(UnderCabinetColorTemperature.state, receivedCommand)
// Update Color Temperature
UnderCabinetColorTemperature.postUpdate((colorTemperature / 100).intValue)
// Change Color Temperature
changeCabinetColorTemperature.apply(UnderCabinet, colorTemperature + 2500, changeCabinetColor)
end items/wifiled.items
|
very good solution. thx a lot |
@AnPutz Here you go. I also included the reverse logic as well in case you want to keep your virtual color item in sync when you interact directly with your LED lights. transform/colorRGBToRGBW.js (function(rgb) {
// Convert RGB to RGBW using algorithm referenced in this post:
// https://stackoverflow.com/questions/40312216/converting-rgb-to-rgbw
// Convert and split rgb colors from percemt to 8-bits value
var redInput = Math.round(rgb.split(",")[0] * 255) / 100;
var greenInput = Math.round(rgb.split(",")[1] * 255) / 100;
var blueInput = Math.round(rgb.split(",")[2] * 255) / 100;
// Get the maximum between R, G, and B
var maxInput = Math.max(redInput, Math.max(greenInput, blueInput));
// If the maximum value is 0, immediately return pure black.
if (maxInput == 0)
return "0,0,0,0";
// This section serves to figure out what the color with 100% hue is
var multiplier = 255.0 / maxInput;
var redHue = redInput * multiplier;
var greenHue = greenInput * multiplier;
var blueHue = blueInput * multiplier;
// This calculates the Whiteness (not strictly speaking Luminance) of the color
var max = Math.max(redHue, Math.max(greenHue, blueHue));
var min = Math.min(redHue, Math.min(greenHue, blueHue));
var luminance = ((max + min) / 2.0 - 127.5) * (255.0 / 127.5) / multiplier;
// Calculate the output values
var redOutput = Math.round(redInput - luminance);
var greenOutput = Math.round(greenInput - luminance);
var blueOutput = Math.round(blueInput - luminance);
var whiteOutput = Math.round(luminance / 255.0 * 100);
// Clamp output values within 8-bits
redOutput = redOutput > 0 ? redOutput < 255 ? redOutput : 255 : 0;
greenOutput = greenOutput > 0 ? greenOutput < 255 ? greenOutput : 255 : 0;
blueOutput = blueOutput > 0 ? blueOutput < 255 ? blueOutput : 255 : 0;
return redOutput + "," + greenOutput + "," + blueOutput + "," + whiteOutput;
})(input) transform/colorRGBWToRGB.js (function(rgbw) {
// Convert RGBW to RGB reversing algorithm referenced in this post:
// https://stackoverflow.com/questions/40312216/converting-rgb-to-rgbw
// Convert and split rgbw colors from percent to 8-bits value
var redInput = Math.round(rgbw.split(",")[0] * 255) / 100;
var greenInput = Math.round(rgbw.split(",")[1] * 255) / 100;
var blueInput = Math.round(rgbw.split(",")[2] * 255) / 100;
var whiteInput = Math.round(rgbw.split(",")[3] * 255) / 100;
// Calculate the output values
var redOutput = Math.round(redInput + whiteInput);
var greenOutput = Math.round(greenInput + whiteInput);
var blueOutput = Math.round(blueInput + whiteInput);
// Clamp output values within 8-bits
redOutput = redOutput > 0 ? redOutput < 255 ? redOutput : 255 : 0;
greenOutput = greenOutput > 0 ? greenOutput < 255 ? greenOutput : 255 : 0;
blueOutput = blueOutput > 0 ? blueOutput < 255 ? blueOutput : 255 : 0;
return redOutput + "," + greenOutput + "," + blueOutput;
})(input) |
One aspect I forgot to add is that I changed the item for this logic to be a number instead of a dimmer. So the get color temperature function is no longer needed and the increment values are now coming from the Alexa skill since number items don't support IncreaseDecreaseType commands. While doing some research on how other devices with color temperature capability are controlled by openHAB natively, I found that the default way is using a dimmer where 0% is coldest and 100% is warmest. This is how it is working in the Hue and LIFX bindings currently. Therefore, I implemented the logic for the color temperature support in v3 for these two types of items. In the end, I think this logic should be moved to the WiFi LED binding in a form of a color temperature channel. |
Thx a lot for your fast response, I will try it next free time slot. |
When will the v3 be released so you have an idea? I ask because I was looking for th is feature and found this request. The ks! |
Implemented in v3 (in beta testing now) |
Changes to Smart Home Skill API Reference - April 7, 2017
New directives added to control the color and tunable white light settings of devices
SetColorTemperatureRequest
SetColorTemperatureConfirmation
IncrementColorTemperatureRequest
IncrementColorTemperatureConfirmation
DecrementColorTemperatureRequest
DecrementColorTemperatureConfirmation
The text was updated successfully, but these errors were encountered: