Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions bundles/org.openhab.binding.dirigera/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -327,22 +327,24 @@ Channel `brightness` can receive

Light with color temperature support.

| Channel | Type | Read/Write | Description |
|-----------------------|-----------------------|------------|------------------------------------------------------|
| `power` | Switch | RW | Power state of light |
| `brightness` | Dimmer | RW | Control brightness of light |
| `color-temperature` | Dimmer | RW | Color temperature from cold (0 %) to warm (100 %) |
| Channel | Type | Read/Write | Description | Advanced |
|---------------------------|-----------------------|------------|------------------------------------------------------|----------|
| `power` | Switch | RW | Power state of light | |
| `brightness` | Dimmer | RW | Control brightness of light | |
| `color-temperature` | Dimmer | RW | Color temperature from cold (0 %) to warm (100 %) | |
| `color-temperature-abs` | Number:Temperature | RW | Color temperature of a bulb in Kelvin | X |

## Color Lights

Light with color support.

| Channel | Type | Read/Write | Description |
|-----------------------|-----------------------|------------|------------------------------------------------------|
| `power` | Switch | RW | Power state of light |
| `brightness` | Dimmer | RW | Brightness of light in percent |
| `color-temperature` | Dimmer | RW | Color temperature from cold (0 %) to warm (100 %) |
| `color` | Color | RW | Color of light with hue, saturation and brightness |
| Channel | Type | Read/Write | Description | Advanced |
|---------------------------|-----------------------|------------|------------------------------------------------------|----------|
| `power` | Switch | RW | Power state of light | |
| `brightness` | Dimmer | RW | Brightness of light in percent | |
| `color-temperature` | Dimmer | RW | Color temperature from cold (0 %) to warm (100 %) | |
| `color-temperature-abs` | Number:Temperature | RW | Color temperature of a bulb in Kelvin | |
| `color` | Color | RW | Color of light with hue, saturation and brightness | X |

Channel `color` can receive

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ public class Constants {
// Light channels
public static final String CHANNEL_LIGHT_BRIGHTNESS = "brightness";
public static final String CHANNEL_LIGHT_TEMPERATURE = "color-temperature";
public static final String CHANNEL_LIGHT_TEMPERATURE_ABS = "color-temperature-abs";

public static final String CHANNEL_LIGHT_COLOR = "color";
public static final String CHANNEL_LIGHT_PRESET = "light-preset";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
@Component(configurationPid = "binding.dirigera", service = ThingHandlerFactory.class)
public class DirigeraHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(DirigeraHandlerFactory.class);
private final DirigeraStateDescriptionProvider stateProvider;
private final DirigeraDiscoveryService discoveryService;
private final DirigeraCommandProvider commandProvider;
private final LocationProvider locationProvider;
Expand All @@ -78,10 +79,12 @@ public class DirigeraHandlerFactory extends BaseThingHandlerFactory {
@Activate
public DirigeraHandlerFactory(@Reference StorageService storageService,
final @Reference DirigeraDiscoveryService discovery, final @Reference LocationProvider locationProvider,
final @Reference DirigeraCommandProvider commandProvider) {
final @Reference DirigeraCommandProvider commandProvider,
final @Reference DirigeraStateDescriptionProvider stateProvider) {
this.locationProvider = locationProvider;
this.commandProvider = commandProvider;
this.discoveryService = discovery;
this.stateProvider = stateProvider;

this.insecureClient = new HttpClient(new SslContextFactory.Client(true));
insecureClient.setUserAgentField(null);
Expand Down Expand Up @@ -118,9 +121,9 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return new DirigeraHandler((Bridge) thing, insecureClient, bindingStorage, discoveryService,
locationProvider, commandProvider, bundleContext);
} else if (THING_TYPE_COLOR_LIGHT.equals(thingTypeUID)) {
return new ColorLightHandler(thing, COLOR_LIGHT_MAP);
return new ColorLightHandler(thing, COLOR_LIGHT_MAP, stateProvider);
} else if (THING_TYPE_TEMPERATURE_LIGHT.equals(thingTypeUID)) {
return new TemperatureLightHandler(thing, TEMPERATURE_LIGHT_MAP);
return new TemperatureLightHandler(thing, TEMPERATURE_LIGHT_MAP, stateProvider);
} else if (THING_TYPE_DIMMABLE_LIGHT.equals(thingTypeUID)) {
return new DimmableLightHandler(thing, TEMPERATURE_LIGHT_MAP);
} else if (THING_TYPE_SWITCH_LIGHT.equals(thingTypeUID)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2010-2025 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.dirigera.internal;

import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.events.EventPublisher;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.events.ThingEventFactory;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.openhab.core.types.StateDescription;
import org.openhab.core.types.StateDescriptionFragment;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* The {@link Clip2StateDescriptionProvider} provides dynamic state descriptions of alert, effect, scene, and colour
* temperature channels whose capabilities are dynamically determined at runtime.
*
* @author Andrew Fiddian-Green - Initial contribution
*
*/
@NonNullByDefault
@Component(service = { DynamicStateDescriptionProvider.class, DirigeraStateDescriptionProvider.class })
public class DirigeraStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
private Map<ChannelUID, StateDescriptionFragment> stateDescriptionMap = new HashMap<>();

@Activate
public DirigeraStateDescriptionProvider(final @Reference EventPublisher eventPublisher,
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry,
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.eventPublisher = eventPublisher;
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}

@Override
public @Nullable StateDescription getStateDescription(Channel channel,
@Nullable StateDescription originalStateDescription, @Nullable Locale locale) {
StateDescription original = null;
StateDescriptionFragment fragment = stateDescriptionMap.get(channel.getUID());
if (fragment != null) {
original = fragment.toStateDescription();
StateDescription modified = super.getStateDescription(channel, original, locale);
if (modified == null) {
modified = original;
}
return modified;
}
return super.getStateDescription(channel, original, locale);
}

public void setStateDescription(ChannelUID channelUid, StateDescriptionFragment stateDescriptionFragment) {
StateDescription stateDescription = stateDescriptionFragment.toStateDescription();
if (stateDescription != null) {
StateDescriptionFragment old = stateDescriptionMap.get(channelUid);
stateDescriptionMap.put(channelUid, stateDescriptionFragment);
Set<String> linkedItems = null;
ItemChannelLinkRegistry compareRegistry = itemChannelLinkRegistry;
if (compareRegistry != null) {
linkedItems = compareRegistry.getLinkedItemNames(channelUid);
}
postEvent(ThingEventFactory.createChannelDescriptionChangedEvent(channelUid,
linkedItems != null ? linkedItems : Set.of(), stateDescriptionFragment, old));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,18 @@

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.json.JSONObject;
import org.openhab.binding.dirigera.internal.DirigeraStateDescriptionProvider;
import org.openhab.binding.dirigera.internal.interfaces.Model;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.HSBType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.types.Command;
import org.openhab.core.util.ColorUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -42,8 +46,8 @@ public class ColorLightHandler extends TemperatureLightHandler {
private HSBType hsbStateReflection = new HSBType(); // proxy to reflect state to end user
private HSBType hsbDevice = new HSBType(); // strictly holding values which were received via update

public ColorLightHandler(Thing thing, Map<String, String> mapping) {
super(thing, mapping);
public ColorLightHandler(Thing thing, Map<String, String> mapping, DirigeraStateDescriptionProvider stateProvider) {
super(thing, mapping, stateProvider);
super.setChildHandler(this);
}

Expand Down Expand Up @@ -92,22 +96,26 @@ public void handleCommand(ChannelUID channelUID, Command command) {
}
}
}
if (CHANNEL_LIGHT_TEMPERATURE.equals(channel)) {
if (CHANNEL_LIGHT_TEMPERATURE.equals(channel) || CHANNEL_LIGHT_TEMPERATURE_ABS.equals(channel)) {
int kelvin = -1;
HSBType colorTemp = null;
if (command instanceof PercentType percent) {
// there are color lights which cannot handle temperature as stored in capabilities
// in this case calculate color which is fitting to temperature
// otherwise TemperatureLightHandler will do the command
if (!receiveCapabilities.contains(Model.COLOR_TEMPERATURE_CAPABILITY)) {
int kelvin = super.getKelvin(percent.intValue());
HSBType colorTemp = getHSBTemperature(kelvin);
HSBType colorTempAdaption = new HSBType(colorTemp.getHue(), colorTemp.getSaturation(),
hsbDevice.getBrightness());
if (customDebug) {
logger.info("DIRIGERA COLOR_LIGHT {} handle temperature as color {}", thing.getLabel(),
colorTempAdaption);
}
colorCommand(colorTempAdaption);
kelvin = super.getKelvin(percent.intValue());
colorTemp = kelvin2Hsb(kelvin);
} else if (command instanceof QuantityType number) {
kelvin = number.intValue();
colorTemp = kelvin2Hsb(kelvin);
}
// there are color lights which cannot handle tempera HSB {}t ,kelvin,colorTempure as stored in capabilities
// in this case calculate color which is fitting to temperature
if (colorTemp != null && !receiveCapabilities.contains(Model.COLOR_TEMPERATURE_CAPABILITY)) {
HSBType colorTempAdaption = new HSBType(colorTemp.getHue(), colorTemp.getSaturation(),
hsbDevice.getBrightness());
if (customDebug) {
logger.info("DIRIGERA COLOR_LIGHT {} handle temperature as color {}", thing.getLabel(),
colorTempAdaption);
}
colorCommand(colorTempAdaption);
}
}
}
Expand All @@ -134,8 +142,9 @@ private void colorCommand(HSBType hsb) {
}

private boolean closeTo(HSBType hsb) {
return (Math.abs(hsb.getHue().intValue() - hsbDevice.getHue().intValue()) < 3
&& Math.abs(hsb.getSaturation().intValue() - hsbDevice.getSaturation().intValue()) < 3);
int hueDistance = Math.abs(hsb.getHue().intValue() - hsbDevice.getHue().intValue());
int saturationDistance = Math.abs(hsb.getSaturation().intValue() - hsbDevice.getSaturation().intValue());
return ((hueDistance < 3 || hueDistance > 367) && saturationDistance < 3);
}

private void brightnessCommand(HSBType hsb) {
Expand Down Expand Up @@ -197,10 +206,34 @@ public void handleUpdate(JSONObject update) {
}
if (deliverHSB) {
updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_COLOR), hsbStateReflection);
if (!receiveCapabilities.contains(Model.COLOR_TEMPERATURE_CAPABILITY)) {
// if color light doesn't support native light temperature converted values are taken
int kelvin = hsb2Kelvin(new HSBType(hsbStateReflection.getHue(), hsbStateReflection.getSaturation(),
PercentType.HUNDRED));
updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE),
new PercentType(getPercent(kelvin)));
updateState(new ChannelUID(thing.getUID(), CHANNEL_LIGHT_TEMPERATURE_ABS),
QuantityType.valueOf(kelvin, Units.KELVIN));
}
}
}
}

public int hsb2Kelvin(HSBType hsb) {
double[] xy = ColorUtil.hsbToXY(hsb);
long kelvin = Math.round(ColorUtil.xyToKelvin(new double[] { xy[0], xy[1] }));

kelvin = Math.min(kelvin, colorTemperatureMin);
kelvin = Math.max(kelvin, colorTemperatureMax);
return (int) kelvin;
}

public HSBType kelvin2Hsb(int kelvin) {
int kelvinBoundaries = Math.min(kelvin, colorTemperatureMin);
kelvinBoundaries = Math.max(kelvin, colorTemperatureMax);
return ColorUtil.xyToHsb(ColorUtil.kelvinToXY(kelvinBoundaries));
}

/**
* Simulate color-temperature if color light doesn't have the "canReceive" "colorTemperature" capability
* https://www.npmjs.com/package/color-temperature?activeTab=code
Expand All @@ -213,7 +246,6 @@ public static HSBType getHSBTemperature(long kelvin) {
double red;
double green;
double blue;

/* Calculate red */
if (temperature <= 66.0) {
red = 255;
Expand Down Expand Up @@ -247,7 +279,6 @@ public static HSBType getHSBTemperature(long kelvin) {
green = 255;
}
}

/* Calculate blue */
if (temperature >= 66.0) {
blue = 255;
Expand Down
Loading