[lifx] Support HEV clean cycle (#11262)

* Implement HEV packets
* Add colorhevlight thing type with a hevcycle channel
* Update documentation

Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
Wouter Born 2021-09-19 11:45:06 +02:00 committed by GitHub
parent 0a7a9ae281
commit 344011c2ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1143 additions and 218 deletions

View File

@ -9,46 +9,48 @@ All LIFX lights are directly connected to the WLAN and the binding communicates
The following table lists the thing types of the supported LIFX devices:
| Device Type | Thing Type |
|------------------------------|--------------|
| Original 1000 | colorlight |
| Color 650 | colorlight |
| Color 1000 | colorlight |
| Color 1000 BR30 | colorlight |
| LIFX A19 | colorlight |
| LIFX BR30 | colorlight |
| LIFX Candle | colorlight |
| LIFX Clean | colorlight |
| LIFX Downlight | colorlight |
| LIFX GU10 | colorlight |
| LIFX Mini Color | colorlight |
| | |
| LIFX+ A19 | colorirlight |
| LIFX+ BR30 | colorirlight |
| | |
| LIFX Beam | colormzlight |
| LIFX Z | colormzlight |
| | |
| LIFX Tile | tilelight |
| | |
| White 800 (Low Voltage) | whitelight |
| White 800 (High Voltage) | whitelight |
| White 900 BR30 (Low Voltage) | whitelight |
| LIFX Candle Warm to White | whitelight |
| LIFX Filament | whitelight |
| LIFX Mini Day and Dusk | whitelight |
| LIFX Mini White | whitelight |
| Device Type | Thing Type |
|------------------------------|---------------|
| Original 1000 | colorlight |
| Color 650 | colorlight |
| Color 1000 | colorlight |
| Color 1000 BR30 | colorlight |
| LIFX A19 | colorlight |
| LIFX BR30 | colorlight |
| LIFX Candle | colorlight |
| LIFX Downlight | colorlight |
| LIFX GU10 | colorlight |
| LIFX Mini Color | colorlight |
| | |
| LIFX Clean | colorhevlight |
| | |
| LIFX+ A19 | colorirlight |
| LIFX+ BR30 | colorirlight |
| | |
| LIFX Beam | colormzlight |
| LIFX Z | colormzlight |
| | |
| LIFX Tile | tilelight |
| | |
| White 800 (Low Voltage) | whitelight |
| White 800 (High Voltage) | whitelight |
| White 900 BR30 (Low Voltage) | whitelight |
| LIFX Candle Warm to White | whitelight |
| LIFX Filament | whitelight |
| LIFX Mini Day and Dusk | whitelight |
| LIFX Mini White | whitelight |
The thing type determines the capability of a device and with that the possible ways of interacting with it.
The following matrix lists the capabilities (channels) for each type:
| Thing Type | On/Off | Brightness | Color | Color Zone | Color Temperature | Color Temperature Zone | Infrared | Tile Effects |
|--------------|:------:|:----------:|:-----:|:----------:|:-----------------:|:----------------------:|:--------:|:------------:|
| colorlight | X | | X | | X | | | |
| colorirlight | X | | X | | X | | X | |
| colormzlight | X | | X | X | X | X | | |
| tilelight | X | X | X | | X | | | X |
| whitelight | X | X | | | X | | | |
| Thing Type | On/Off | Brightness | Color | Color Zone | Color Temperature | Color Temperature Zone | HEV Cycle | Infrared | Tile Effects |
|---------------|:------:|:----------:|:-----:|:----------:|:-----------------:|:----------------------:|:---------:|:--------:|:------------:|
| colorlight | X | | X | | X | | | | |
| colorhevlight | X | | X | | X | | X | | |
| colorirlight | X | | X | | X | | | X | |
| colormzlight | X | | X | X | X | X | | | |
| tilelight | X | X | X | | X | | | | X |
| whitelight | X | X | | | X | | | | |
## Discovery
@ -83,39 +85,44 @@ Thing lifx:colorirlight:porch [ host="10.120.130.4", fadetime=0 ]
All devices support some of the following channels:
| Channel Type ID | Item Type | Description | Thing Types |
|-----------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------|
| brightness | Dimmer | This channel supports adjusting the brightness value. | whitelight |
| color | Color | This channel supports full color control with hue, saturation and brightness values. | colorlight, colorirlight, colormzlight, tile |
| colorzone | Color | This channel supports full zone color control with hue, saturation and brightness values. | colormzlight |
| effect | String | This channel represents a type of light effect (e.g. for tile light: off, morph, flame) | tilelight |
| infrared | Dimmer | This channel supports adjusting the infrared value. *Note:* IR capable lights only activate their infrared LEDs when the brightness drops below a certain level. | colorirlight |
| signalstrength | Number | This channel represents signal strength with values 0, 1, 2, 3 or 4; 0 being worst strength and 4 being best strength. | colorlight, colorirlight, colormzlight, whitelight, tile |
| temperature | Dimmer | This channel supports adjusting the color temperature from cold (0%) to warm (100%). | colorlight, colorirlight, colormzlight, whitelight, tile |
| temperaturezone | Dimmer | This channel supports adjusting the zone color temperature from cold (0%) to warm (100%). | colormzlight |
| Channel Type ID | Item Type | Description | Thing Types |
|-----------------|-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| brightness | Dimmer | This channel supports adjusting the brightness value. | whitelight |
| color | Color | This channel supports full color control with hue, saturation and brightness values. | colorlight, colorhevlight, colorirlight, colormzlight, tilelight |
| colorzone | Color | This channel supports full zone color control with hue, saturation and brightness values. | colormzlight |
| effect | String | This channel represents a type of light effect (e.g. for tile light: off, morph, flame) | tilelight |
| hevcycle | Switch | This channel supports starting and stopping the HEV clean cycle. | colorhevlight |
| infrared | Dimmer | This channel supports adjusting the infrared value. *Note:* IR capable lights only activate their infrared LEDs when the brightness drops below a certain level. | colorirlight |
| signalstrength | Number | This channel represents signal strength with values 0, 1, 2, 3 or 4; 0 being worst strength and 4 being best strength. | colorlight, colorhevlight, colorirlight, colormzlight, tilelight, whitelight |
| temperature | Dimmer | This channel supports adjusting the color temperature from cold (0%) to warm (100%). | colorlight, colorhevlight, colorirlight, colormzlight, tilelight, whitelight |
| temperaturezone | Dimmer | This channel supports adjusting the zone color temperature from cold (0%) to warm (100%). | colormzlight |
The *color* and *brightness* channels have a "Power on brightness" configuration option that is used to determine the brightness when a light is switched on.
The *color* and *brightness* channels have a "Power On Brightness" configuration option that is used to determine the brightness when a light is switched on.
When it is left empty, the brightness of a light remains unchanged when a light is switched on or off.
The *color* channels have a "Power on color" configuration option that is used to determine the hue, saturation, brightness levels when a light is switched on.
The *color* channels have a "Power On Color" configuration option that is used to determine the hue, saturation, brightness levels when a light is switched on.
When it is left empty, the color of a light remains unchanged when a light is switched on or off.
Configuration options contains 3 comma separated values, where first value is hue (0-360), second saturation (0-100) and third brightness (0-100).
If both "Power on brightness" and "Power on color" configuration options are defined, "Power on brightness" option overrides the brightness level defined on the "Power on color" configuration option.
If both "Power on brightness" and "Power On Color" configuration options are defined, "Power on brightness" option overrides the brightness level defined on the "Power on color" configuration option.
The *temperature* channels have a "Power on temperature" configuration option that is used to determine the color temperature when a light is switched on. When it is left empty, the color temperature of a light remains unchanged when a light is switched on or off.
The *temperature* channels have a "Power On Temperature" configuration option that is used to determine the color temperature when a light is switched on. When it is left empty, the color temperature of a light remains unchanged when a light is switched on or off.
MultiZone lights (*colormzlight*) have serveral channels (e.g. *colorzone0*, *temperaturezone0*, etc.) that allow for controlling specific zones of the light.
MultiZone lights (*colormzlight*) have several channels (e.g. *colorzone0*, *temperaturezone0*, etc.) that allow for controlling specific zones of the light.
Changing the *color* and *temperature* channels will update the states of all zones.
The *color* and *temperature* channels of MultiZone lights always return the same state as *colorzone0*, *temperaturezone0*.
The *hevcycle* channels have an optional "HEV Cycle Duration" configuration option that can be used to override the cycle duration configured in the light.
LIFX Tile (*tilelight*) supports special tile effects: morph and flame.
These effects are predefined to their appearance using LIFX application.
Each effect has a separate speed configurable.
Each effect has a separate speed configuration option.
## Full Example
In this example **living** is a Color 1000 light that has a *colorlight* thing type which supports *color* and *temperature* channels.
The **desk** light is a LIFX Clean that has a *colorhevlight* thing type which supports *color*, *temperature* and *hevcycle* channels.
The **porch** light is a LIFX+ BR30 that has a *colorirlight* thing type which supports *color*, *temperature* and *infrared* channels.
The **ceiling** light is a LIFX Z with 2 strips (16 zones) that has a *colormzlight* thing type which supports *color*, *colorzone*, *temperature* and *temperaturezone* channels.
@ -136,6 +143,11 @@ Thing lifx:colorlight:living2 [ deviceId="D073D5A2A2A2" ] {
Type color : color [ powerOnBrightness=50 ]
}
Thing lifx:colorhevlight:desk [ deviceId="D073D5A3A3A3" ] {
Channels:
Type hevcycle : hevcycle [ hevCycleDuration=3600 ]
}
Thing lifx:colorirlight:porch [ deviceId="D073D5B2B2B2", host="10.120.130.4", fadetime=0 ] {
Channels:
Type color : color [ powerOnBrightness=75 ]
@ -169,6 +181,11 @@ Switch Living2_Switch { channel="lifx:colorlight:living2:color" }
Dimmer Living2_Dimmer { channel="lifx:colorlight:living2:color" }
Dimmer Living2_Temperature { channel="lifx:colorlight:living2:temperature" }
// Desk
Color Desk_Color { channel="lifx:colorhevlight:desk:color" }
Dimmer Desk_Temperature { channel="lifx:colorhevlight:desk:temperature" }
Switch Desk_HEV_Cycle { channel="lifx:colorhevlight:desk:hevcycle" }
// Porch
Color Porch_Color { channel="lifx:colorirlight:porch:color" }
Dimmer Porch_Infrared { channel="lifx:colorirlight:porch:infrared" }
@ -208,6 +225,14 @@ sitemap demo label="Main Menu"
Slider item=Living2_Temperature
}
Frame label="Desk" {
Switch item=Desk_Color
Slider item=Desk_Color
Colorpicker item=Desk_Color
Slider item=Desk_Temperature
Switch item=Desk_HEV_Cycle
}
Frame label="Porch" {
Switch item=Porch_Color
Slider item=Porch_Color

View File

@ -13,8 +13,6 @@
package org.openhab.binding.lifx.internal;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lifx.internal.fields.HSBK;
@ -55,6 +53,7 @@ public class LifxBindingConstants {
public static final String CHANNEL_COLOR = "color";
public static final String CHANNEL_COLOR_ZONE = "colorzone";
public static final String CHANNEL_EFFECT = "effect";
public static final String CHANNEL_HEV_CYCLE = "hevcycle";
public static final String CHANNEL_INFRARED = "infrared";
public static final String CHANNEL_SIGNAL_STRENGTH = "signalstrength";
public static final String CHANNEL_TEMPERATURE = "temperature";
@ -80,11 +79,12 @@ public class LifxBindingConstants {
public static final String CONFIG_PROPERTY_FADETIME = "fadetime";
// Config property for channel configuration
public static final String CONFIG_PROPERTY_HEV_CYCLE_DURATION = "hevCycleDuration";
public static final String CONFIG_PROPERTY_EFFECT_FLAME_SPEED = "effectFlameSpeed";
public static final String CONFIG_PROPERTY_EFFECT_MORPH_SPEED = "effectMorphSpeed";
public static final String CONFIG_PROPERTY_POWER_ON_BRIGHTNESS = "powerOnBrightness";
public static final String CONFIG_PROPERTY_POWER_ON_COLOR = "powerOnColor";
public static final String CONFIG_PROPERTY_POWER_ON_TEMPERATURE = "powerOnTemperature";
public static final String CONFIG_PROPERTY_EFFECT_MORPH_SPEED = "effectMorphSpeed";
public static final String CONFIG_PROPERTY_EFFECT_FLAME_SPEED = "effectFlameSpeed";
// Property keys
public static final String PROPERTY_HOST = "host";
@ -100,12 +100,13 @@ public class LifxBindingConstants {
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_COLORLIGHT = new ThingTypeUID(BINDING_ID, "colorlight");
public static final ThingTypeUID THING_TYPE_COLORHEVLIGHT = new ThingTypeUID(BINDING_ID, "colorhevlight");
public static final ThingTypeUID THING_TYPE_COLORIRLIGHT = new ThingTypeUID(BINDING_ID, "colorirlight");
public static final ThingTypeUID THING_TYPE_COLORMZLIGHT = new ThingTypeUID(BINDING_ID, "colormzlight");
public static final ThingTypeUID THING_TYPE_WHITELIGHT = new ThingTypeUID(BINDING_ID, "whitelight");
public static final ThingTypeUID THING_TYPE_TILELIGHT = new ThingTypeUID(BINDING_ID, "tilelight");
public static final ThingTypeUID THING_TYPE_WHITELIGHT = new ThingTypeUID(BINDING_ID, "whitelight");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream.of(THING_TYPE_COLORLIGHT,
THING_TYPE_COLORIRLIGHT, THING_TYPE_COLORMZLIGHT, THING_TYPE_WHITELIGHT, THING_TYPE_TILELIGHT)
.collect(Collectors.toSet());
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_COLORLIGHT,
THING_TYPE_COLORHEVLIGHT, THING_TYPE_COLORIRLIGHT, THING_TYPE_COLORMZLIGHT, THING_TYPE_TILELIGHT,
THING_TYPE_WHITELIGHT);
}

View File

@ -25,11 +25,14 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.LifxProduct.Features;
import org.openhab.binding.lifx.internal.dto.GetColorZonesRequest;
import org.openhab.binding.lifx.internal.dto.GetHevCycleRequest;
import org.openhab.binding.lifx.internal.dto.GetLightInfraredRequest;
import org.openhab.binding.lifx.internal.dto.GetRequest;
import org.openhab.binding.lifx.internal.dto.GetTileEffectRequest;
import org.openhab.binding.lifx.internal.dto.GetWifiInfoRequest;
import org.openhab.binding.lifx.internal.dto.HevCycleState;
import org.openhab.binding.lifx.internal.dto.Packet;
import org.openhab.binding.lifx.internal.dto.StateHevCycleResponse;
import org.openhab.binding.lifx.internal.dto.StateLightInfraredResponse;
import org.openhab.binding.lifx.internal.dto.StateLightPowerResponse;
import org.openhab.binding.lifx.internal.dto.StateMultiZoneResponse;
@ -133,6 +136,9 @@ public class LifxLightCurrentStateUpdater {
private void sendLightStateRequests() {
communicationHandler.sendPacket(new GetRequest());
if (features.hasFeature(HEV)) {
communicationHandler.sendPacket(new GetHevCycleRequest());
}
if (features.hasFeature(INFRARED)) {
communicationHandler.sendPacket(new GetLightInfraredRequest());
}
@ -157,14 +163,16 @@ public class LifxLightCurrentStateUpdater {
handlePowerStatus((StatePowerResponse) packet);
} else if (packet instanceof StateLightPowerResponse) {
handleLightPowerStatus((StateLightPowerResponse) packet);
} else if (packet instanceof StateHevCycleResponse) {
handleHevCycleStatus((StateHevCycleResponse) packet);
} else if (packet instanceof StateLightInfraredResponse) {
handleInfraredStatus((StateLightInfraredResponse) packet);
} else if (packet instanceof StateMultiZoneResponse) {
handleMultiZoneStatus((StateMultiZoneResponse) packet);
} else if (packet instanceof StateWifiInfoResponse) {
handleWifiInfoStatus((StateWifiInfoResponse) packet);
} else if (packet instanceof StateTileEffectResponse) {
handleTileEffectStatus((StateTileEffectResponse) packet);
} else if (packet instanceof StateWifiInfoResponse) {
handleWifiInfoStatus((StateWifiInfoResponse) packet);
}
currentLightState.setOnline();
@ -192,6 +200,11 @@ public class LifxLightCurrentStateUpdater {
currentLightState.setPowerState(packet.getState());
}
private void handleHevCycleStatus(StateHevCycleResponse packet) {
HevCycleState hevCycleState = new HevCycleState(!packet.getRemaining().isZero(), packet.getDuration());
currentLightState.setHevCycleState(hevCycleState);
}
private void handleInfraredStatus(StateLightInfraredResponse packet) {
PercentType infrared = infraredToPercentType(packet.getInfrared());
currentLightState.setInfrared(infrared);
@ -209,11 +222,11 @@ public class LifxLightCurrentStateUpdater {
currentLightState.setColors(colors);
}
private void handleWifiInfoStatus(StateWifiInfoResponse packet) {
currentLightState.setSignalStrength(packet.getSignalStrength());
}
private void handleTileEffectStatus(StateTileEffectResponse packet) {
currentLightState.setTileEffect(packet.getEffect());
}
private void handleWifiInfoStatus(StateWifiInfoResponse packet) {
currentLightState.setSignalStrength(packet.getSignalStrength());
}
}

View File

@ -23,6 +23,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.dto.Effect;
import org.openhab.binding.lifx.internal.dto.HevCycleState;
import org.openhab.binding.lifx.internal.dto.PowerState;
import org.openhab.binding.lifx.internal.dto.SignalStrength;
import org.openhab.binding.lifx.internal.fields.HSBK;
@ -40,6 +41,7 @@ import org.openhab.core.library.types.PercentType;
public class LifxLightState {
private HSBK[] colors = new HSBK[] { new HSBK(DEFAULT_COLOR) };
private @Nullable HevCycleState hevCycleState;
private @Nullable PercentType infrared;
private @Nullable PowerState powerState;
private @Nullable SignalStrength signalStrength;
@ -51,6 +53,7 @@ public class LifxLightState {
public void copy(LifxLightState other) {
this.powerState = other.getPowerState();
this.colors = other.getColors();
this.hevCycleState = other.getHevCycleState();
this.infrared = other.getInfrared();
this.signalStrength = other.getSignalStrength();
this.tileEffect = other.getTileEffect();
@ -76,6 +79,10 @@ public class LifxLightState {
return colorsCopy;
}
public @Nullable HevCycleState getHevCycleState() {
return hevCycleState;
}
public @Nullable PercentType getInfrared() {
return infrared;
}
@ -158,6 +165,13 @@ public class LifxLightState {
setColor(newColor, zoneIndex);
}
public void setHevCycleState(HevCycleState newHevCycleState) {
HevCycleState oldHevCycleState = this.hevCycleState;
this.hevCycleState = newHevCycleState;
updateLastChange();
listeners.forEach(listener -> listener.handleHevCycleStateChange(oldHevCycleState, newHevCycleState));
}
public void setInfrared(PercentType newInfrared) {
PercentType oldInfrared = this.infrared;
this.infrared = newInfrared;

View File

@ -34,13 +34,16 @@ import org.openhab.binding.lifx.internal.dto.AcknowledgementResponse;
import org.openhab.binding.lifx.internal.dto.ApplicationRequest;
import org.openhab.binding.lifx.internal.dto.Effect;
import org.openhab.binding.lifx.internal.dto.GetColorZonesRequest;
import org.openhab.binding.lifx.internal.dto.GetHevCycleRequest;
import org.openhab.binding.lifx.internal.dto.GetLightInfraredRequest;
import org.openhab.binding.lifx.internal.dto.GetLightPowerRequest;
import org.openhab.binding.lifx.internal.dto.GetRequest;
import org.openhab.binding.lifx.internal.dto.HevCycleState;
import org.openhab.binding.lifx.internal.dto.Packet;
import org.openhab.binding.lifx.internal.dto.PowerState;
import org.openhab.binding.lifx.internal.dto.SetColorRequest;
import org.openhab.binding.lifx.internal.dto.SetColorZonesRequest;
import org.openhab.binding.lifx.internal.dto.SetHevCycleRequest;
import org.openhab.binding.lifx.internal.dto.SetLightInfraredRequest;
import org.openhab.binding.lifx.internal.dto.SetLightPowerRequest;
import org.openhab.binding.lifx.internal.dto.SetPowerRequest;
@ -308,6 +311,17 @@ public class LifxLightStateChanger implements LifxLightStateListener {
}
}
@Override
public void handleHevCycleStateChange(@Nullable HevCycleState oldHevCycleState, HevCycleState newHevCycleState) {
// The change should be ignored when both the old and new state are disabled regardless of the cycle duration
if (!newHevCycleState.equals(oldHevCycleState)
&& (oldHevCycleState == null || oldHevCycleState.isEnable() || newHevCycleState.isEnable())) {
SetHevCycleRequest packet = new SetHevCycleRequest(newHevCycleState.isEnable(),
newHevCycleState.getDuration());
replacePacketsInMap(packet);
}
}
@Override
public void handleInfraredChange(@Nullable PercentType oldInfrared, PercentType newInfrared) {
PercentType infrared = pendingLightState.getInfrared();
@ -325,7 +339,7 @@ public class LifxLightStateChanger implements LifxLightStateListener {
@Override
public void handleTileEffectChange(@Nullable Effect oldEffect, Effect newEffect) {
if (oldEffect == null || !oldEffect.equals(newEffect)) {
if (!newEffect.equals(oldEffect)) {
SetTileEffectRequest packet = new SetTileEffectRequest(newEffect);
replacePacketsInMap(packet);
}
@ -360,6 +374,13 @@ public class LifxLightStateChanger implements LifxLightStateListener {
getZonesIfZonesAreSet();
} else if (sentPacket instanceof SetColorZonesRequest) {
getZonesIfZonesAreSet();
} else if (sentPacket instanceof SetHevCycleRequest) {
scheduler.schedule(() -> {
GetHevCycleRequest hevCyclePacket = new GetHevCycleRequest();
communicationHandler.sendPacket(hevCyclePacket);
GetLightPowerRequest powerPacket = new GetLightPowerRequest();
communicationHandler.sendPacket(powerPacket);
}, 600, TimeUnit.MILLISECONDS);
} else if (sentPacket instanceof SetLightInfraredRequest) {
GetLightInfraredRequest infraredPacket = new GetLightInfraredRequest();
communicationHandler.sendPacket(infraredPacket);

View File

@ -371,12 +371,14 @@ public enum LifxProduct {
public ThingTypeUID getThingTypeUID() {
if (hasFeature(COLOR)) {
if (hasFeature(TILE_EFFECT)) {
return LifxBindingConstants.THING_TYPE_TILELIGHT;
if (hasFeature(HEV)) {
return LifxBindingConstants.THING_TYPE_COLORHEVLIGHT;
} else if (hasFeature(INFRARED)) {
return LifxBindingConstants.THING_TYPE_COLORIRLIGHT;
} else if (hasFeature(MULTIZONE)) {
return LifxBindingConstants.THING_TYPE_COLORMZLIGHT;
} else if (hasFeature(TILE_EFFECT)) {
return LifxBindingConstants.THING_TYPE_TILELIGHT;
} else {
return LifxBindingConstants.THING_TYPE_COLORLIGHT;
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.nio.ByteBuffer;
/**
* @author Wouter Born - Initial contribution
*/
public class GetHevCycleConfigurationRequest extends Packet {
public static final int TYPE = 0x91;
public GetHevCycleConfigurationRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateHevCycleConfigurationResponse.TYPE };
}
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.nio.ByteBuffer;
/**
* @author Wouter Born - Initial contribution
*/
public class GetHevCycleRequest extends Packet {
public static final int TYPE = 0x8E;
public GetHevCycleRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateHevCycleResponse.TYPE };
}
}

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.nio.ByteBuffer;
/**
* @author Wouter Born - Initial contribution
*/
public class GetLastHevCycleResultRequest extends Packet {
public static final int TYPE = 0x94;
public GetLastHevCycleResultRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 0;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
// do nothing
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(0);
}
@Override
public int[] expectedResponses() {
return new int[] { StateLastHevCycleResultResponse.TYPE };
}
}

View File

@ -0,0 +1,86 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.time.Duration;
/**
* The pending or current HEV cycle state.
*
* @author Wouter Born - Initial contribution
*/
public class HevCycleState {
public static final HevCycleState OFF = new HevCycleState(false);
public static final HevCycleState ON = new HevCycleState(true);
private boolean enable;
private Duration duration;
public HevCycleState(boolean enable) {
this.enable = enable;
this.duration = Duration.ZERO;
}
public HevCycleState(boolean enable, Duration duration) {
this.enable = enable;
this.duration = duration;
}
public boolean isEnable() {
return enable;
}
public Duration getDuration() {
return duration;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((duration == null) ? 0 : duration.hashCode());
result = prime * result + (enable ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
HevCycleState other = (HevCycleState) obj;
if (duration == null) {
if (other.duration != null) {
return false;
}
} else if (!duration.equals(other.duration)) {
return false;
}
if (enable != other.enable) {
return false;
}
return true;
}
@Override
public String toString() {
return "HevCycleState [enable=" + enable + ", duration=" + duration + "]";
}
}

View File

@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.util.Arrays;
import java.util.Optional;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public enum LightLastHevCycleResult {
SUCCESS(0),
BUSY(1),
INTERRUPTED_BY_RESET(2),
INTERRUPTED_BY_HOMEKIT(3),
INTERRUPTED_BY_LAN(4),
INTERRUPTED_BY_CLOUD(5),
NONE(255);
private final int type;
LightLastHevCycleResult(int type) {
this.type = type;
}
public static LightLastHevCycleResult fromValue(int type) {
Optional<LightLastHevCycleResult> result = Arrays.stream(values()).filter((value) -> value.type == type)
.findFirst();
if (!result.isPresent()) {
throw new IllegalArgumentException("Invalid LightLastHevCycleResult type: " + type);
}
return result.get();
}
}

View File

@ -52,10 +52,13 @@ public class PacketFactory {
register(GetColorZonesRequest.class);
register(GetEchoRequest.class);
register(GetGroupRequest.class);
register(GetHevCycleConfigurationRequest.class);
register(GetHevCycleRequest.class);
register(GetHostFirmwareRequest.class);
register(GetHostInfoRequest.class);
register(GetInfoRequest.class);
register(GetLabelRequest.class);
register(GetLastHevCycleResultRequest.class);
register(GetLightInfraredRequest.class);
register(GetLightPowerRequest.class);
register(GetLocationRequest.class);
@ -71,16 +74,21 @@ public class PacketFactory {
register(SetColorRequest.class);
register(SetColorZonesRequest.class);
register(SetDimAbsoluteRequest.class);
register(SetHevCycleRequest.class);
register(SetHevCycleConfigurationRequest.class);
register(SetLabelRequest.class);
register(SetLightInfraredRequest.class);
register(SetLightPowerRequest.class);
register(SetPowerRequest.class);
register(SetTagsRequest.class);
register(StateGroupResponse.class);
register(StateHevCycleConfigurationResponse.class);
register(StateHevCycleResponse.class);
register(StateHostFirmwareResponse.class);
register(StateHostInfoResponse.class);
register(StateInfoResponse.class);
register(StateLabelResponse.class);
register(StateLastHevCycleResultResponse.class);
register(StateLightInfraredResponse.class);
register(StateLightPowerResponse.class);
register(StateLocationResponse.class);

View File

@ -0,0 +1,81 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.nio.ByteBuffer;
import java.time.Duration;
import org.openhab.binding.lifx.internal.fields.BoolIntField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Wouter Born - Initial contribution
*/
public class SetHevCycleConfigurationRequest extends Packet {
public static final int TYPE = 0x92;
public static final Field<Boolean> FIELD_INDICATION = new BoolIntField();
public static final Field<Long> FIELD_DURATION = new UInt32Field().little();
private boolean indication;
private long duration;
public boolean isIndication() {
return indication;
}
public Duration getDuration() {
return Duration.ofSeconds(duration);
}
public SetHevCycleConfigurationRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public SetHevCycleConfigurationRequest(boolean indication, Duration duration) {
this();
this.indication = indication;
this.duration = duration.toSeconds();
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 5;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
indication = FIELD_INDICATION.value(bytes);
duration = FIELD_DURATION.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_INDICATION.bytes(indication))
.put(FIELD_DURATION.bytes(duration));
}
@Override
public int[] expectedResponses() {
return new int[] { StateHevCycleConfigurationResponse.TYPE };
}
}

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.nio.ByteBuffer;
import java.time.Duration;
import org.openhab.binding.lifx.internal.fields.BoolIntField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Wouter Born - Initial contribution
*/
public class SetHevCycleRequest extends Packet {
public static final int TYPE = 0x8F;
public static final Field<Boolean> FIELD_ENABLE = new BoolIntField();
public static final Field<Long> FIELD_DURATION = new UInt32Field().little();
private boolean enable;
private long duration;
public boolean isEnable() {
return enable;
}
public Duration getDuration() {
return Duration.ofSeconds(duration);
}
public SetHevCycleRequest() {
setTagged(false);
setAddressable(true);
setResponseRequired(true);
}
public SetHevCycleRequest(boolean enable) {
this();
this.enable = enable;
}
public SetHevCycleRequest(boolean enable, Duration duration) {
this();
this.enable = enable;
this.duration = duration.toSeconds();
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 5;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
enable = FIELD_ENABLE.value(bytes);
duration = FIELD_DURATION.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_ENABLE.bytes(enable)).put(FIELD_DURATION.bytes(duration));
}
@Override
public int[] expectedResponses() {
return new int[] { StateHevCycleResponse.TYPE };
}
}

View File

@ -0,0 +1,69 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.nio.ByteBuffer;
import java.time.Duration;
import org.openhab.binding.lifx.internal.fields.BoolIntField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Wouter Born - Initial contribution
*/
public class StateHevCycleConfigurationResponse extends Packet {
public static final int TYPE = 0x93;
public static final Field<Boolean> FIELD_INDICATION = new BoolIntField();
public static final Field<Long> FIELD_DURATION = new UInt32Field().little();
private boolean indication;
private long duration;
public boolean isIndication() {
return indication;
}
public Duration getDuration() {
return Duration.ofSeconds(duration);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 5;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
indication = FIELD_INDICATION.value(bytes);
duration = FIELD_DURATION.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_INDICATION.bytes(indication))
.put(FIELD_DURATION.bytes(duration));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@ -0,0 +1,76 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.nio.ByteBuffer;
import java.time.Duration;
import org.openhab.binding.lifx.internal.fields.BoolIntField;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt32Field;
/**
* @author Wouter Born - Initial contribution
*/
public class StateHevCycleResponse extends Packet {
public static final int TYPE = 0x90;
public static final Field<Long> FIELD_DURATION = new UInt32Field().little();
public static final Field<Long> FIELD_REMAINING = new UInt32Field().little();
public static final Field<Boolean> FIELD_LAST_POWER = new BoolIntField();
private long duration;
private long remaining;
private boolean lastPower;
public Duration getDuration() {
return Duration.ofSeconds(duration);
}
public Duration getRemaining() {
return Duration.ofSeconds(remaining);
}
public boolean isLastPower() {
return lastPower;
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 9;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
duration = FIELD_DURATION.value(bytes);
remaining = FIELD_REMAINING.value(bytes);
lastPower = FIELD_LAST_POWER.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_DURATION.bytes(duration))
.put(FIELD_REMAINING.bytes(remaining)).put(FIELD_LAST_POWER.bytes(lastPower));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.dto;
import java.nio.ByteBuffer;
import org.openhab.binding.lifx.internal.fields.Field;
import org.openhab.binding.lifx.internal.fields.UInt8Field;
/**
* @author Wouter Born - Initial contribution
*/
public class StateLastHevCycleResultResponse extends Packet {
public static final int TYPE = 0x95;
public static final Field<Integer> FIELD_RESULT = new UInt8Field().little();
private int result;
public LightLastHevCycleResult getResult() {
return LightLastHevCycleResult.fromValue(result);
}
@Override
public int packetType() {
return TYPE;
}
@Override
protected int packetLength() {
return 1;
}
@Override
protected void parsePacket(ByteBuffer bytes) {
result = FIELD_RESULT.value(bytes);
}
@Override
protected ByteBuffer packetBytes() {
return ByteBuffer.allocate(packetLength()).put(FIELD_RESULT.bytes(result));
}
@Override
public int[] expectedResponses() {
return new int[] {};
}
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2021 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.lifx.internal.fields;
import java.nio.ByteBuffer;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class BoolIntField extends Field<Boolean> {
public BoolIntField() {
super(1);
}
@Override
public int defaultLength() {
return 1;
}
@Override
public Boolean value(ByteBuffer bytes) {
return bytes.get() == 1;
}
@Override
public ByteBuffer bytesInternal(Boolean value) {
return ByteBuffer.allocate(1).put((byte) (value ? 1 : 0));
}
}

View File

@ -25,6 +25,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -42,11 +43,13 @@ import org.openhab.binding.lifx.internal.LifxLightStateChanger;
import org.openhab.binding.lifx.internal.LifxProduct;
import org.openhab.binding.lifx.internal.LifxProduct.Features;
import org.openhab.binding.lifx.internal.dto.Effect;
import org.openhab.binding.lifx.internal.dto.GetHevCycleRequest;
import org.openhab.binding.lifx.internal.dto.GetLightInfraredRequest;
import org.openhab.binding.lifx.internal.dto.GetLightPowerRequest;
import org.openhab.binding.lifx.internal.dto.GetRequest;
import org.openhab.binding.lifx.internal.dto.GetTileEffectRequest;
import org.openhab.binding.lifx.internal.dto.GetWifiInfoRequest;
import org.openhab.binding.lifx.internal.dto.HevCycleState;
import org.openhab.binding.lifx.internal.dto.Packet;
import org.openhab.binding.lifx.internal.dto.PowerState;
import org.openhab.binding.lifx.internal.dto.SignalStrength;
@ -94,6 +97,7 @@ public class LifxLightHandler extends BaseThingHandler {
private final LifxChannelFactory channelFactory;
private @NonNullByDefault({}) Features features;
private Duration hevCycleDuration = Duration.ZERO;
private @Nullable PercentType powerOnBrightness;
private @Nullable HSBType powerOnColor;
private @Nullable PercentType powerOnTemperature;
@ -191,6 +195,14 @@ public class LifxLightHandler extends BaseThingHandler {
return updateColor;
}
@Override
public void setHevCycleState(HevCycleState hevCycleState) {
if (!isStateChangePending() || hevCycleState.equals(pendingLightState.getHevCycleState())) {
updateStateIfChanged(CHANNEL_HEV_CYCLE, OnOffType.from(hevCycleState.isEnable()));
}
super.setHevCycleState(hevCycleState);
}
@Override
public void setInfrared(PercentType infrared) {
if (!isStateChangePending() || infrared.equals(pendingLightState.getInfrared())) {
@ -263,6 +275,8 @@ public class LifxLightHandler extends BaseThingHandler {
if (speed != null) {
effectFlameSpeed = speed;
}
hevCycleDuration = getHevCycleDuration();
channelStates.clear();
currentLightState = new CurrentLightState();
pendingLightState = new LifxLightState();
@ -410,6 +424,19 @@ public class LifxLightHandler extends BaseThingHandler {
return speed == null ? null : Double.valueOf(speed.toString());
}
private Duration getHevCycleDuration() {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_HEV_CYCLE);
Channel channel = getThing().getChannel(channelUID.getId());
if (channel == null) {
return Duration.ZERO;
}
Configuration configuration = channel.getConfiguration();
Object duration = configuration.get(LifxBindingConstants.CONFIG_PROPERTY_HEV_CYCLE_DURATION);
return duration == null ? Duration.ZERO : Duration.ofSeconds(Integer.valueOf(duration.toString()));
}
private Features getFeatures() {
LifxProduct product = getProduct();
@ -486,118 +513,126 @@ public class LifxLightHandler extends BaseThingHandler {
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
channelStates.remove(channelUID.getId());
switch (channelUID.getId()) {
case CHANNEL_COLOR:
case CHANNEL_BRIGHTNESS:
sendPacket(new GetLightPowerRequest());
sendPacket(new GetRequest());
break;
case CHANNEL_TEMPERATURE:
sendPacket(new GetRequest());
break;
case CHANNEL_INFRARED:
sendPacket(new GetLightInfraredRequest());
break;
case CHANNEL_SIGNAL_STRENGTH:
sendPacket(new GetWifiInfoRequest());
break;
case CHANNEL_EFFECT:
if (features.hasFeature(TILE_EFFECT)) {
sendPacket(new GetTileEffectRequest());
}
break;
default:
break;
}
handleRefreshCommand(channelUID);
} else {
boolean supportedCommand = true;
switch (channelUID.getId()) {
case CHANNEL_COLOR:
if (command instanceof HSBType) {
handleHSBCommand((HSBType) command);
} else if (command instanceof PercentType) {
handlePercentCommand((PercentType) command);
} else if (command instanceof OnOffType) {
handleOnOffCommand((OnOffType) command);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
} else {
supportedCommand = false;
}
break;
case CHANNEL_BRIGHTNESS:
if (command instanceof PercentType) {
handlePercentCommand((PercentType) command);
} else if (command instanceof OnOffType) {
handleOnOffCommand((OnOffType) command);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
} else {
supportedCommand = false;
}
break;
case CHANNEL_TEMPERATURE:
if (command instanceof PercentType) {
handleTemperatureCommand((PercentType) command);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command);
} else {
supportedCommand = false;
}
break;
case CHANNEL_INFRARED:
if (command instanceof PercentType) {
handleInfraredCommand((PercentType) command);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseInfraredCommand((IncreaseDecreaseType) command);
} else {
supportedCommand = false;
}
break;
case CHANNEL_EFFECT:
if (command instanceof StringType && features.hasFeature(TILE_EFFECT)) {
handleTileEffectCommand((StringType) command);
} else {
supportedCommand = false;
}
break;
default:
try {
if (channelUID.getId().startsWith(CHANNEL_COLOR_ZONE)) {
int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_COLOR_ZONE, ""));
if (command instanceof HSBType) {
handleHSBCommand((HSBType) command, zoneIndex);
} else if (command instanceof PercentType) {
handlePercentCommand((PercentType) command, zoneIndex);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseCommand((IncreaseDecreaseType) command, zoneIndex);
} else {
supportedCommand = false;
}
} else if (channelUID.getId().startsWith(CHANNEL_TEMPERATURE_ZONE)) {
int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_TEMPERATURE_ZONE, ""));
if (command instanceof PercentType) {
handleTemperatureCommand((PercentType) command, zoneIndex);
} else if (command instanceof IncreaseDecreaseType) {
handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command, zoneIndex);
} else {
supportedCommand = false;
}
} else {
supportedCommand = false;
}
} catch (NumberFormatException e) {
logger.error("Failed to parse zone index for a command of a light ({}) : {}", logId,
e.getMessage());
supportedCommand = false;
}
break;
Runnable channelCommandRunnable = getChannelCommandRunnable(channelUID, command);
if (channelCommandRunnable == null) {
return;
}
if (supportedCommand && !(command instanceof OnOffType) && !CHANNEL_INFRARED.equals(channelUID.getId())) {
getLightStateForCommand().setPowerState(PowerState.ON);
String channelId = channelUID.getId();
boolean isHevCycleChannelCommand = CHANNEL_HEV_CYCLE.equals(channelId);
boolean isInfraredChannelCommand = CHANNEL_INFRARED.equals(channelId);
boolean waitForHevCycleDisabled = false;
if (getFeatures().hasFeature(HEV) && !isHevCycleChannelCommand) {
LifxLightState lightState = getLightStateForCommand();
HevCycleState currentHevCycleState = lightState.getHevCycleState();
if (currentHevCycleState == null || currentHevCycleState.isEnable()) {
lightState.setHevCycleState(HevCycleState.OFF);
lightState.setPowerState(PowerState.OFF);
waitForHevCycleDisabled = true;
}
}
Runnable compositeCommandsRunnable = () -> {
channelCommandRunnable.run();
if (!(command instanceof OnOffType) && !isHevCycleChannelCommand && !isInfraredChannelCommand) {
getLightStateForCommand().setPowerState(PowerState.ON);
}
};
if (waitForHevCycleDisabled) {
scheduler.schedule(compositeCommandsRunnable, 200, TimeUnit.MILLISECONDS);
} else {
compositeCommandsRunnable.run();
}
}
}
private @Nullable Runnable getChannelCommandRunnable(ChannelUID channelUID, Command command) {
switch (channelUID.getId()) {
case CHANNEL_BRIGHTNESS:
if (command instanceof PercentType) {
return () -> handlePercentCommand((PercentType) command);
} else if (command instanceof OnOffType) {
return () -> handleOnOffCommand((OnOffType) command);
} else if (command instanceof IncreaseDecreaseType) {
return () -> handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
} else {
return null;
}
case CHANNEL_COLOR:
if (command instanceof HSBType) {
return () -> handleHSBCommand((HSBType) command);
} else if (command instanceof PercentType) {
return () -> handlePercentCommand((PercentType) command);
} else if (command instanceof OnOffType) {
return () -> handleOnOffCommand((OnOffType) command);
} else if (command instanceof IncreaseDecreaseType) {
return () -> handleIncreaseDecreaseCommand((IncreaseDecreaseType) command);
} else {
return null;
}
case CHANNEL_EFFECT:
if (command instanceof StringType && features.hasFeature(TILE_EFFECT)) {
return () -> handleTileEffectCommand((StringType) command);
} else {
return null;
}
case CHANNEL_HEV_CYCLE:
if (command instanceof OnOffType) {
return () -> handleHevCycleCommand((OnOffType) command);
} else {
return null;
}
case CHANNEL_INFRARED:
if (command instanceof PercentType) {
return () -> handleInfraredCommand((PercentType) command);
} else if (command instanceof IncreaseDecreaseType) {
return () -> handleIncreaseDecreaseInfraredCommand((IncreaseDecreaseType) command);
} else {
return null;
}
case CHANNEL_TEMPERATURE:
if (command instanceof PercentType) {
return () -> handleTemperatureCommand((PercentType) command);
} else if (command instanceof IncreaseDecreaseType) {
return () -> handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command);
} else {
return null;
}
default:
try {
if (channelUID.getId().startsWith(CHANNEL_COLOR_ZONE)) {
int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_COLOR_ZONE, ""));
if (command instanceof HSBType) {
return () -> handleHSBCommand((HSBType) command, zoneIndex);
} else if (command instanceof PercentType) {
return () -> handlePercentCommand((PercentType) command, zoneIndex);
} else if (command instanceof IncreaseDecreaseType) {
return () -> handleIncreaseDecreaseCommand((IncreaseDecreaseType) command, zoneIndex);
} else {
return null;
}
} else if (channelUID.getId().startsWith(CHANNEL_TEMPERATURE_ZONE)) {
int zoneIndex = Integer.parseInt(channelUID.getId().replace(CHANNEL_TEMPERATURE_ZONE, ""));
if (command instanceof PercentType) {
return () -> handleTemperatureCommand((PercentType) command, zoneIndex);
} else if (command instanceof IncreaseDecreaseType) {
return () -> handleIncreaseDecreaseTemperatureCommand((IncreaseDecreaseType) command,
zoneIndex);
} else {
return null;
}
} else {
return null;
}
} catch (NumberFormatException e) {
logger.error("Failed to parse zone index for a command of a light ({}) : {}", logId,
e.getMessage());
return null;
}
}
}
@ -612,6 +647,36 @@ public class LifxLightHandler extends BaseThingHandler {
return pendingLightState.getDurationSinceLastChange().minus(MAX_STATE_CHANGE_DURATION).isNegative();
}
private void handleRefreshCommand(ChannelUID channelUID) {
channelStates.remove(channelUID.getId());
switch (channelUID.getId()) {
case CHANNEL_COLOR:
case CHANNEL_BRIGHTNESS:
sendPacket(new GetLightPowerRequest());
sendPacket(new GetRequest());
break;
case CHANNEL_EFFECT:
if (features.hasFeature(TILE_EFFECT)) {
sendPacket(new GetTileEffectRequest());
}
break;
case CHANNEL_HEV_CYCLE:
sendPacket(new GetHevCycleRequest());
break;
case CHANNEL_INFRARED:
sendPacket(new GetLightInfraredRequest());
break;
case CHANNEL_SIGNAL_STRENGTH:
sendPacket(new GetWifiInfoRequest());
break;
case CHANNEL_TEMPERATURE:
sendPacket(new GetRequest());
break;
default:
break;
}
}
private void handleTemperatureCommand(PercentType temperature) {
HSBK newColor = getLightStateForCommand().getColor();
newColor.setSaturation(PercentType.ZERO);
@ -688,6 +753,11 @@ public class LifxLightHandler extends BaseThingHandler {
handleTemperatureCommand(newTemperature, zoneIndex);
}
private void handleHevCycleCommand(OnOffType onOff) {
HevCycleState hevCycleState = new HevCycleState(onOff == OnOffType.ON, hevCycleDuration);
getLightStateForCommand().setHevCycleState(hevCycleState);
}
private void handleInfraredCommand(PercentType infrared) {
getLightStateForCommand().setInfrared(infrared);
}

View File

@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.lifx.internal.LifxLightState;
import org.openhab.binding.lifx.internal.dto.Effect;
import org.openhab.binding.lifx.internal.dto.HevCycleState;
import org.openhab.binding.lifx.internal.dto.PowerState;
import org.openhab.binding.lifx.internal.dto.SignalStrength;
import org.openhab.binding.lifx.internal.fields.HSBK;
@ -45,6 +46,14 @@ public interface LifxLightStateListener {
*/
void handlePowerStateChange(@Nullable PowerState oldPowerState, PowerState newPowerState);
/**
* Called when the HEV cycle state property changes.
*
* @param oldHevCycleState the old HEV cycle state value
* @param newHevCycleState the new HEV cycle state value
*/
void handleHevCycleStateChange(@Nullable HevCycleState oldHevCycleState, HevCycleState newHevCycleState);
/**
* Called when the infrared property changes.
*

View File

@ -42,6 +42,26 @@
</parameter>
</config-description>
<config-description uri="channel-type:lifx:effect">
<parameter name="effectMorphSpeed" type="decimal" min="0" max="120" step="0.1" required="false" unit="s">
<label>Morph Effect Speed</label>
<description>Speed of the morph effect in seconds.</description>
<default>3</default>
</parameter>
<parameter name="effectFlameSpeed" type="decimal" min="0" max="120" step="0.1" required="false" unit="s">
<label>Flame Effect Speed</label>
<description>Speed of the flame effect in seconds.</description>
<default>4</default>
</parameter>
</config-description>
<config-description uri="channel-type:lifx:hevcycle">
<parameter name="hevCycleDuration" type="integer" min="0" max="86400" step="1" required="false" unit="m">
<label>HEV Cycle Duration</label>
<description>HEV cycle duration in seconds. Use empty value for the cycle duration configured in the light.</description>
</parameter>
</config-description>
<config-description uri="channel-type:lifx:temperature">
<parameter name="powerOnTemperature" type="integer" min="0" max="100" required="false" unit="%">
<label>Power On Temperature</label>
@ -50,17 +70,4 @@
</parameter>
</config-description>
<config-description uri="channel-type:lifx:effect">
<parameter name="effectMorphSpeed" type="decimal" min="0" max="120" step="0.1" required="false" unit="s">
<label>Morph effect speed</label>
<description>Speed of the morph effect in seconds.</description>
<default>3</default>
</parameter>
<parameter name="effectFlameSpeed" type="decimal" min="0" max="120" step="0.1" required="false" unit="s">
<label>Flame effect speed</label>
<description>Speed of the flame effect in seconds.</description>
<default>4</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@ -4,16 +4,18 @@ binding.lifx.description = This is the binding for LIFX lights.
# thing types
thing-type.lifx.colorlight.label = LIFX Color Light
thing-type.lifx.colorhevlight.label = LIFX Color HEV Light
thing-type.lifx.colorirlight.label = LIFX Color IR Light
thing-type.lifx.colormzlight.label = LIFX Color MultiZone Light
thing-type.lifx.tilelight.label = LIFX Tile Light
thing-type.lifx.whitelight.label = LIFX White Light
# thing type configuration
thing-type.config.lifx.light.deviceId.label = LIFX device ID
thing-type.config.lifx.light.deviceId.label = LIFX Device ID
thing-type.config.lifx.light.deviceId.description = Identifies the light, e.g. "D073D5A1A1A1"
thing-type.config.lifx.light.host.label = Host
thing-type.config.lifx.light.host.description = Hostname or IP address of the light. Use empty value for automatic discovery.
thing-type.config.lifx.light.fadetime.label = Fade time
thing-type.config.lifx.light.fadetime.label = Fade Time
thing-type.config.lifx.light.fadetime.description = The time to fade to the new color value (in ms).
# channel types
@ -21,30 +23,34 @@ channel-type.lifx.brightness.label = Brightness
channel-type.lifx.brightness.description = Sets the brightness of the light
channel-type.lifx.color.label = Color
channel-type.lifx.color.description = Selects the color of the light
channel-type.lifx.colorzone.label = Color zone {0}
channel-type.lifx.colorzone.label = Color Zone {0}
channel-type.lifx.colorzone.description = Selects the zone {0} color of the light
channel-type.lifx.infrared.label = Infrared
channel-type.lifx.infrared.description = Sets the infrared of the light
channel-type.lifx.temperature.label = Temperature
channel-type.lifx.temperature.description = Sets the temperature of the light
channel-type.lifx.temperaturezone.label = Temperature zone {0}
channel-type.lifx.temperaturezone.description = Sets the zone {0} temperature of the light
channel-type.lifx.effect.label = Effect
channel-type.lifx.effect.description = Sets the effect of the light
channel-type.lifx.effect.state.option.off = Off
channel-type.lifx.effect.state.option.morph = Morph
channel-type.lifx.effect.state.option.flame = Flame
channel-type.lifx.hevcycle.label = HEV Cycle
channel-type.lifx.hevcycle.description = Controls the HEV clean cycle of the light
channel-type.lifx.infrared.label = Infrared
channel-type.lifx.infrared.description = Sets the infrared of the light
channel-type.lifx.temperature.label = Temperature
channel-type.lifx.temperature.description = Sets the temperature of the light
channel-type.lifx.temperaturezone.label = Temperature Zone {0}
channel-type.lifx.temperaturezone.description = Sets the zone {0} temperature of the light
# channel type configuration
channel-type.config.lifx.brightness.powerOnBrightness.label = Power on brightness
channel-type.config.lifx.brightness.powerOnBrightness.label = Power On Brightness
channel-type.config.lifx.brightness.powerOnBrightness.description = Brightness level used when switching on the light. Use empty value to leave brightness as is.
channel-type.config.lifx.color.powerOnBrightness.label = Power on brightness
channel-type.config.lifx.color.powerOnBrightness.label = Power On Brightness
channel-type.config.lifx.color.powerOnBrightness.description = Brightness level used when switching on the light. Use empty value to leave brightness as is.
channel-type.config.lifx.color.powerOnColor.label = Power on color
channel-type.config.lifx.color.powerOnColor.label = Power On Color
channel-type.config.lifx.color.powerOnColor.description = Color used when switching on the light. Use empty value to leave color as is.
channel-type.config.lifx.temperature.powerOnTemperature.label = Power on color temperature
channel-type.config.lifx.temperature.powerOnTemperature.description = Color temperature used when switching on the light. Use empty value to leave color temperature as is.
channel-type.config.lifx.effect.effectMorphSpeed.label = Morph effect speed
channel-type.config.lifx.effect.effectMorphSpeed.label = Morph Effect Speed
channel-type.config.lifx.effect.effectMorphSpeed.description = Speed of the morph effect in seconds.
channel-type.config.lifx.effect.effectFlameSpeed.label = Flame effect speed
channel-type.config.lifx.effect.effectFlameSpeed.label = Flame Effect Speed
channel-type.config.lifx.effect.effectFlameSpeed.description = Speed of the flame effect in seconds.
channel-type.config.lifx.hevcycle.hevCycleDuration.label = HEV Cycle Duration
channel-type.config.lifx.hevcycle.hevCycleDuration.description = HEV cycle duration in seconds. Use empty value for the cycle duration configured in the light.
channel-type.config.lifx.temperature.powerOnTemperature.label = Power On Color Temperature
channel-type.config.lifx.temperature.powerOnTemperature.description = Color temperature used when switching on the light. Use empty value to leave color temperature as is.

View File

@ -4,15 +4,17 @@ binding.lifx.description = Dit is de binding voor LIFX lampen.
# thing types
thing-type.lifx.colorlight.label = LIFX Kleuren Lamp
thing-type.lifx.colorhevlight.label = LIFX Kleuren HEV Lamp
thing-type.lifx.colorirlight.label = LIFX Kleuren IR Lamp
thing-type.lifx.colormzlight.label = LIFX Kleuren MultiZone Lamp
thing-type.lifx.tilelight.label = LIFX Tegel Lamp
thing-type.lifx.whitelight.label = LIFX Wittinten Lamp
# thing type configuration
thing-type.config.lifx.light.deviceId.label = LIFX apparaat ID
thing-type.config.lifx.light.deviceId.label = LIFX Apparaat ID
thing-type.config.lifx.light.deviceId.description = Identificeert de lamp, bv. "D073D5A1A1A1"
thing-type.config.lifx.light.host.label = Host
thing-type.config.lifx.light.host.description = Hostnaam of IP adres van de lamp. Gebruik lege waarde voor automatische detectie.
thing-type.config.lifx.light.host.description = Hostnaam of IP adres van de lamp. Gebruik een lege waarde voor automatische detectie.
thing-type.config.lifx.light.fadetime.label = Vervagingsduur
thing-type.config.lifx.light.fadetime.description = De tijdsduur van het vervagen naar een nieuwe kleur (in ms).
@ -21,24 +23,34 @@ channel-type.lifx.brightness.label = Helderheid
channel-type.lifx.brightness.description = Bepaalt de helderheid van de lamp
channel-type.lifx.color.label = Kleur
channel-type.lifx.color.description = Bepaalt de kleur van de lamp
channel-type.lifx.colorzone.label = Kleur zone {0}
channel-type.lifx.colorzone.label = Kleur Zone {0}
channel-type.lifx.colorzone.description = Bepaalt de kleur van lampzone {0}
channel-type.lifx.infrared.label = Infrarood
channel-type.lifx.infrared.description = Bepaalt het infraroodniveau van de lamp
channel-type.lifx.temperature.label = Temperatuur
channel-type.lifx.temperature.description = Bepaalt de kleurtemperatuur van de lamp
channel-type.lifx.temperaturezone.label = Temperatuur zone {0}
channel-type.lifx.temperaturezone.description = Bepaalt de kleurtemperatuur van lampzone {0}
channel-type.lifx.effect.label = Effect
channel-type.lifx.effect.description = Bepaalt het lichteffect
channel-type.lifx.effect.state.option.off = Uit
channel-type.lifx.effect.state.option.morph = Morph
channel-type.lifx.effect.state.option.flame = Flamme
channel-type.lifx.effect.state.option.flame = Vlam
channel-type.lifx.hevcycle.label = HEV Cyclus
channel-type.lifx.hevcycle.description = Bedient de HEV schoonmaakcylcus van de lamp
channel-type.lifx.infrared.label = Infrarood
channel-type.lifx.infrared.description = Bepaalt het infraroodniveau van de lamp
channel-type.lifx.temperature.label = Temperatuur
channel-type.lifx.temperature.description = Bepaalt de kleurtemperatuur van de lamp
channel-type.lifx.temperaturezone.label = Temperatuur Zone {0}
channel-type.lifx.temperaturezone.description = Bepaalt de kleurtemperatuur van lampzone {0}
# channel type configuration
channel-type.config.lifx.brightness.powerOnBrightness.label = Inschakelhelderheid
channel-type.config.lifx.brightness.powerOnBrightness.description = Het helderheidsniveau bij inschakeling van de lamp. Gebruik een lege waarde om de helderheid ongewijzigd te laten.
channel-type.config.lifx.effect.effectMorphSpeed.label = Morph-effect snelheid
channel-type.config.lifx.color.powerOnBrightness.label = Inschakelhelderheid
channel-type.config.lifx.color.powerOnBrightness.description = Het helderheidsniveau bij inschakeling van de lamp. Gebruik een lege waarde om de helderheid ongewijzigd te laten.
channel-type.config.lifx.color.powerOnColor.label = Inschakelkleur
channel-type.config.lifx.color.powerOnColor.description = De kleur die gebruikt wordt bij het inschakelen van de lamp. Gebruik een lege waarde om de kleur ongewijzigd te laten.
channel-type.config.lifx.effect.effectMorphSpeed.label = Morph-effect Snelheid
channel-type.config.lifx.effect.effectMorphSpeed.description = De snelheid van het Morph-effect in seconden.
channel-type.config.lifx.effect.effectFlameSpeed.label = Vlam-effect snelheid
channel-type.config.lifx.effect.effectFlameSpeed.label = Vlam-effect Snelheid
channel-type.config.lifx.effect.effectFlameSpeed.description = De snelheid van het Vlam-effect in seconden.
channel-type.config.lifx.hevcycle.hevCycleDuration.label = HEV Cycluslooptijd
channel-type.config.lifx.hevcycle.hevCycleDuration.description = HEV cycluslooptijd in seconden. Gebruik een lege waarde om de in de lamp geconfigureerde cycluslooptijd te gebruiken.
channel-type.config.lifx.temperature.powerOnTemperature.label = Inschakelkleurtemperatuur
channel-type.config.lifx.temperature.powerOnTemperature.description = De kleurtemperatuur die gebruikt wordt bij het inschakelen van de lamp. Gebruik een lege waarde om de kleurtemperatuur ongewijzigd te laten.

View File

@ -36,6 +36,13 @@
</tags>
</channel-type>
<channel-type id="hevcycle">
<item-type>Switch</item-type>
<label>HEV Cycle</label>
<description>Controls the HEV clean cycle of the light</description>
<config-description-ref uri="channel-type:lifx:hevcycle"/>
</channel-type>
<channel-type id="infrared">
<item-type>Dimmer</item-type>
<label>Infrared</label>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="lifx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="colorhevlight">
<label>LIFX Color HEV Light</label>
<channels>
<channel id="color" typeId="color"/>
<channel id="temperature" typeId="temperature"/>
<channel id="hevcycle" typeId="hevcycle"/>
<channel id="signalstrength" typeId="system.signal-strength"/>
</channels>
<representation-property>macAddress</representation-property>
<config-description-ref uri="thing-type:lifx:light"/>
</thing-type>
</thing:thing-descriptions>