[lifx] Improve firmware version support (#10384)

* [lifx] Improve firmware version support

When the firmware of a light is upgraded the supported features can change.
With these changes the binding uses the features based on the light firmware version.
Also corrects some of the temperature ranges based on the LIFX products description.

Signed-off-by: Wouter Born <github@maindrain.net>
This commit is contained in:
Wouter Born 2021-03-24 23:39:34 +01:00 committed by GitHub
parent 5477fa5fb1
commit 1d34872c61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 410 additions and 114 deletions

View File

@ -15,6 +15,7 @@ package org.openhab.binding.lifx.internal;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.lifx.internal.LifxProduct.Features;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler;
import org.openhab.binding.lifx.internal.handler.LifxLightHandler.CurrentLightState;
@ -30,14 +31,14 @@ public class LifxLightContext {
private final LifxLightConfig configuration;
private final CurrentLightState currentLightState;
private final LifxLightState pendingLightState;
private final LifxProduct product;
private final Features features;
private final ScheduledExecutorService scheduler;
public LifxLightContext(String logId, LifxProduct product, LifxLightConfig configuration,
public LifxLightContext(String logId, Features features, LifxLightConfig configuration,
CurrentLightState currentLightState, LifxLightState pendingLightState, ScheduledExecutorService scheduler) {
this.logId = logId;
this.configuration = configuration;
this.product = product;
this.features = features;
this.currentLightState = currentLightState;
this.pendingLightState = pendingLightState;
this.scheduler = scheduler;
@ -51,8 +52,8 @@ public class LifxLightContext {
return configuration;
}
public LifxProduct getProduct() {
return product;
public Features getFeatures() {
return features;
}
public CurrentLightState getCurrentLightState() {

View File

@ -23,6 +23,7 @@ import java.util.concurrent.locks.ReentrantLock;
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.GetLightInfraredRequest;
import org.openhab.binding.lifx.internal.dto.GetRequest;
@ -56,7 +57,7 @@ public class LifxLightCurrentStateUpdater {
private final Logger logger = LoggerFactory.getLogger(LifxLightCurrentStateUpdater.class);
private final String logId;
private final LifxProduct product;
private final Features features;
private final CurrentLightState currentLightState;
private final ScheduledExecutorService scheduler;
private final LifxLightCommunicationHandler communicationHandler;
@ -70,7 +71,7 @@ public class LifxLightCurrentStateUpdater {
public LifxLightCurrentStateUpdater(LifxLightContext context, LifxLightCommunicationHandler communicationHandler) {
this.logId = context.getLogId();
this.product = context.getProduct();
this.features = context.getFeatures();
this.currentLightState = context.getCurrentLightState();
this.scheduler = context.getScheduler();
this.communicationHandler = communicationHandler;
@ -132,13 +133,13 @@ public class LifxLightCurrentStateUpdater {
private void sendLightStateRequests() {
communicationHandler.sendPacket(new GetRequest());
if (product.hasFeature(INFRARED)) {
if (features.hasFeature(INFRARED)) {
communicationHandler.sendPacket(new GetLightInfraredRequest());
}
if (product.hasFeature(MULTIZONE)) {
if (features.hasFeature(MULTIZONE)) {
communicationHandler.sendPacket(new GetColorZonesRequest());
}
if (product.hasFeature(TILE_EFFECT)) {
if (features.hasFeature(TILE_EFFECT)) {
communicationHandler.sendPacket(new GetTileEffectRequest());
}
if (updateSignalStrength) {

View File

@ -298,9 +298,11 @@ public class LifxLightDiscovery extends AbstractDiscoveryService {
light.label = ((StateLabelResponse) packet).getLabel().trim();
} else if (packet instanceof StateVersionResponse) {
try {
light.product = LifxProduct
LifxProduct product = LifxProduct
.getProductFromProductID(((StateVersionResponse) packet).getProduct());
light.product = product;
light.productVersion = ((StateVersionResponse) packet).getVersion();
light.supportedProduct = product.isLight();
} catch (IllegalArgumentException e) {
logger.debug("Discovered an unsupported light ({}): {}", light.macAddress.getAsLabel(),
e.getMessage());
@ -309,7 +311,7 @@ public class LifxLightDiscovery extends AbstractDiscoveryService {
}
}
if (light != null && light.isDataComplete()) {
if (light != null && light.supportedProduct && light.isDataComplete()) {
try {
thingDiscovered(createDiscoveryResult(light));
} catch (IllegalArgumentException e) {

View File

@ -29,6 +29,7 @@ import java.util.concurrent.locks.ReentrantLock;
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.AcknowledgementResponse;
import org.openhab.binding.lifx.internal.dto.ApplicationRequest;
import org.openhab.binding.lifx.internal.dto.Effect;
@ -74,7 +75,7 @@ public class LifxLightStateChanger implements LifxLightStateListener {
private final Logger logger = LoggerFactory.getLogger(LifxLightStateChanger.class);
private final String logId;
private final LifxProduct product;
private final Features features;
private final Duration fadeTime;
private final LifxLightState pendingLightState;
private final ScheduledExecutorService scheduler;
@ -104,7 +105,7 @@ public class LifxLightStateChanger implements LifxLightStateListener {
public LifxLightStateChanger(LifxLightContext context, LifxLightCommunicationHandler communicationHandler) {
this.logId = context.getLogId();
this.product = context.getProduct();
this.features = context.getFeatures();
this.fadeTime = context.getConfiguration().getFadeTime();
this.pendingLightState = context.getPendingLightState();
this.scheduler = context.getScheduler();
@ -371,7 +372,7 @@ public class LifxLightStateChanger implements LifxLightStateListener {
}
private void getZonesIfZonesAreSet() {
if (product.hasFeature(MULTIZONE)) {
if (features.hasFeature(MULTIZONE)) {
List<PendingPacket> pending = pendingPacketsMap.get(SetColorZonesRequest.TYPE);
if (pending == null || pending.isEmpty()) {
GetColorZonesRequest zoneColorPacket = new GetColorZonesRequest();

View File

@ -16,8 +16,9 @@ import static org.openhab.binding.lifx.internal.LifxProduct.Feature.*;
import static org.openhab.binding.lifx.internal.LifxProduct.TemperatureRange.*;
import static org.openhab.binding.lifx.internal.LifxProduct.Vendor.LIFX;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
@ -33,75 +34,99 @@ import org.openhab.core.thing.ThingTypeUID;
@NonNullByDefault
public enum LifxProduct {
PRODUCT_1(1, "LIFX Original 1000", TR_2500_9000, COLOR),
PRODUCT_3(3, "LIFX Color 650", TR_2500_9000, COLOR),
PRODUCT_10(10, "LIFX White 800 (Low Voltage)", TR_2700_6500),
PRODUCT_11(11, "LIFX White 800 (High Voltage)", TR_2700_6500),
PRODUCT_15(15, "LIFX Color 1000", TR_2500_9000, COLOR),
PRODUCT_18(18, "LIFX White 900 BR30 (Low Voltage)", TR_2700_6500),
PRODUCT_19(19, "LIFX White 900 BR30 (High Voltage)", TR_2700_6500),
PRODUCT_20(20, "LIFX Color 1000 BR30", TR_2500_9000, COLOR),
PRODUCT_22(22, "LIFX Color 1000", TR_2500_9000, COLOR),
PRODUCT_27(27, "LIFX A19", TR_2500_9000, COLOR),
PRODUCT_28(28, "LIFX BR30", TR_2500_9000, COLOR),
PRODUCT_29(29, "LIFX A19 Night Vision", TR_2500_9000, COLOR, INFRARED),
PRODUCT_30(30, "LIFX BR30 Night Vision", TR_2500_9000, COLOR, INFRARED),
PRODUCT_31(31, "LIFX Z", TR_2500_9000, COLOR, MULTIZONE),
PRODUCT_32(32, "LIFX Z", TR_2500_9000, COLOR, MULTIZONE),
PRODUCT_36(36, "LIFX Downlight", TR_2500_9000, COLOR),
PRODUCT_37(37, "LIFX Downlight", TR_2500_9000, COLOR),
PRODUCT_38(38, "LIFX Beam", TR_2500_9000, COLOR, MULTIZONE),
PRODUCT_39(39, "LIFX Downlight White to Warm", TR_1500_9000),
PRODUCT_40(40, "LIFX Downlight", TR_2500_9000, COLOR),
PRODUCT_43(43, "LIFX A19", TR_2500_9000, COLOR),
PRODUCT_44(44, "LIFX BR30", TR_2500_9000, COLOR),
PRODUCT_45(45, "LIFX A19 Night Vision", TR_2500_9000, COLOR, INFRARED),
PRODUCT_46(46, "LIFX BR30 Night Vision", TR_2500_9000, COLOR, INFRARED),
PRODUCT_49(49, "LIFX Mini Color", TR_2500_9000, COLOR),
PRODUCT_50(50, "LIFX Mini White to Warm", TR_1500_4000),
PRODUCT_51(51, "LIFX Mini White", TR_2700_2700),
PRODUCT_52(52, "LIFX GU10", TR_2500_9000, COLOR),
PRODUCT_53(53, "LIFX GU10", TR_2500_9000, COLOR),
PRODUCT_55(55, "LIFX Tile", TR_2500_9000, CHAIN, COLOR, MATRIX, TILE_EFFECT),
PRODUCT_57(57, "LIFX Candle", TR_1500_9000, COLOR, MATRIX),
PRODUCT_59(59, "LIFX Mini Color", TR_2500_9000, COLOR),
PRODUCT_60(60, "LIFX Mini White to Warm", TR_1500_4000),
PRODUCT_61(61, "LIFX Mini White", TR_2700_2700),
PRODUCT_62(62, "LIFX A19", TR_2500_9000, COLOR),
PRODUCT_63(63, "LIFX BR30", TR_2500_9000, COLOR),
PRODUCT_64(64, "LIFX A19 Night Vision", TR_2500_9000, COLOR, INFRARED),
PRODUCT_65(65, "LIFX BR30 Night Vision", TR_2500_9000, COLOR, INFRARED),
PRODUCT_66(66, "LIFX Mini White", TR_2700_2700),
PRODUCT_68(68, "LIFX Candle", TR_1500_9000, MATRIX),
PRODUCT_81(81, "LIFX Candle White to Warm", TR_2200_6500),
PRODUCT_82(82, "LIFX Filament Clear", TR_2100_2100),
PRODUCT_85(85, "LIFX Filament Amber", TR_2000_2000),
PRODUCT_87(87, "LIFX Mini White", TR_2700_2700),
PRODUCT_88(88, "LIFX Mini White", TR_2700_2700),
PRODUCT_90(90, "LIFX Clean", TR_2500_9000, HEV),
PRODUCT_91(91, "LIFX Color", TR_2500_9000, COLOR),
PRODUCT_92(92, "LIFX Color", TR_2500_9000, COLOR),
PRODUCT_94(94, "LIFX BR30", TR_2500_9000, COLOR),
PRODUCT_96(96, "LIFX Candle White to Warm", TR_2200_6500),
PRODUCT_97(97, "LIFX A19", TR_2500_9000, COLOR),
PRODUCT_98(98, "LIFX BR30", TR_2500_9000, COLOR),
PRODUCT_99(99, "LIFX Clean", TR_2500_9000, HEV),
PRODUCT_100(100, "LIFX Filament Clear", TR_2100_2100),
PRODUCT_101(101, "LIFX Filament Amber", TR_2000_2000),
PRODUCT_109(109, "LIFX A19 Night Vision", TR_2500_9000, COLOR, INFRARED),
PRODUCT_110(110, "LIFX BR30 Night Vision", TR_2500_9000, COLOR, INFRARED),
PRODUCT_111(111, "LIFX A19 Night Vision", TR_2500_9000, COLOR, INFRARED);
PRODUCT_1(1, "LIFX Original 1000", new Features(TR_2500_9000, COLOR)),
PRODUCT_3(3, "LIFX Color 650", new Features(TR_2500_9000, COLOR)),
PRODUCT_10(10, "LIFX White 800 (Low Voltage)", new Features(TR_2700_6500)),
PRODUCT_11(11, "LIFX White 800 (High Voltage)", new Features(TR_2700_6500)),
PRODUCT_15(15, "LIFX Color 1000", new Features(TR_2500_9000, COLOR)),
PRODUCT_18(18, "LIFX White 900 BR30 (Low Voltage)", new Features(TR_2700_6500)),
PRODUCT_19(19, "LIFX White 900 BR30 (High Voltage)", new Features(TR_2700_6500)),
PRODUCT_20(20, "LIFX Color 1000 BR30", new Features(TR_2500_9000, COLOR)),
PRODUCT_22(22, "LIFX Color 1000", new Features(TR_2500_9000, COLOR)),
PRODUCT_27(27, "LIFX A19", new Features(TR_2500_9000, COLOR), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_28(28, "LIFX BR30", new Features(TR_2500_9000, COLOR), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_29(29, "LIFX A19 Night Vision", new Features(TR_2500_9000, COLOR, INFRARED), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_30(30, "LIFX BR30 Night Vision", new Features(TR_2500_9000, COLOR, INFRARED), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_31(31, "LIFX Z", new Features(TR_2500_9000, COLOR, MULTIZONE)),
PRODUCT_32(32, "LIFX Z", new Features(TR_2500_9000, COLOR, MULTIZONE), //
new Upgrade(2, 77, new Features(EXTENDED_MULTIZONE)), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_36(36, "LIFX Downlight", new Features(TR_2500_9000, COLOR), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_37(37, "LIFX Downlight", new Features(TR_2500_9000, COLOR), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_38(38, "LIFX Beam", new Features(TR_2500_9000, COLOR, MULTIZONE), //
new Upgrade(2, 77, new Features(EXTENDED_MULTIZONE)), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_39(39, "LIFX Downlight White to Warm", new Features(TR_2500_9000), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_40(40, "LIFX Downlight", new Features(TR_2500_9000, COLOR), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_43(43, "LIFX A19", new Features(TR_2500_9000, COLOR), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_44(44, "LIFX BR30", new Features(TR_2500_9000, COLOR), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_45(45, "LIFX A19 Night Vision", new Features(TR_2500_9000, COLOR, INFRARED), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_46(46, "LIFX BR30 Night Vision", new Features(TR_2500_9000, COLOR, INFRARED), //
new Upgrade(2, 80, new Features(TR_1500_9000))),
PRODUCT_49(49, "LIFX Mini Color", new Features(TR_1500_9000, COLOR)),
PRODUCT_50(50, "LIFX Mini White to Warm", new Features(TR_1500_6500), //
new Upgrade(3, 70, new Features(TR_1500_9000))),
PRODUCT_51(51, "LIFX Mini White", new Features(TR_2700_2700)),
PRODUCT_52(52, "LIFX GU10", new Features(TR_1500_9000, COLOR)),
PRODUCT_53(53, "LIFX GU10", new Features(TR_1500_9000, COLOR)),
PRODUCT_55(55, "LIFX Tile", new Features(TR_2500_9000, CHAIN, COLOR, MATRIX, TILE_EFFECT)),
PRODUCT_57(57, "LIFX Candle", new Features(TR_1500_9000, COLOR, MATRIX)),
PRODUCT_59(59, "LIFX Mini Color", new Features(TR_1500_9000, COLOR)),
PRODUCT_60(60, "LIFX Mini White to Warm", new Features(TR_1500_6500), //
new Upgrade(3, 70, new Features(TR_1500_9000))),
PRODUCT_61(61, "LIFX Mini White", new Features(TR_2700_2700)),
PRODUCT_62(62, "LIFX A19", new Features(TR_1500_9000, COLOR)),
PRODUCT_63(63, "LIFX BR30", new Features(TR_1500_9000, COLOR)),
PRODUCT_64(64, "LIFX A19 Night Vision", new Features(TR_1500_9000, COLOR, INFRARED)),
PRODUCT_65(65, "LIFX BR30 Night Vision", new Features(TR_1500_9000, COLOR, INFRARED)),
PRODUCT_66(66, "LIFX Mini White", new Features(TR_2700_2700)),
PRODUCT_68(68, "LIFX Candle", new Features(TR_1500_9000, MATRIX)),
PRODUCT_70(70, "LIFX Switch", new Features(BUTTONS, RELAYS)),
PRODUCT_71(71, "LIFX Switch", new Features(BUTTONS, RELAYS)),
PRODUCT_81(81, "LIFX Candle White to Warm", new Features(TR_2200_6500)),
PRODUCT_82(82, "LIFX Filament Clear", new Features(TR_2100_2100)),
PRODUCT_85(85, "LIFX Filament Amber", new Features(TR_2000_2000)),
PRODUCT_87(87, "LIFX Mini White", new Features(TR_2700_2700)),
PRODUCT_88(88, "LIFX Mini White", new Features(TR_2700_2700)),
PRODUCT_89(89, "LIFX Switch", new Features(BUTTONS, RELAYS)),
PRODUCT_90(90, "LIFX Clean", new Features(TR_1500_9000, HEV)),
PRODUCT_91(91, "LIFX Color", new Features(TR_1500_9000, COLOR)),
PRODUCT_92(92, "LIFX Color", new Features(TR_1500_9000, COLOR)),
PRODUCT_94(94, "LIFX BR30", new Features(TR_1500_9000, COLOR)),
PRODUCT_96(96, "LIFX Candle White to Warm", new Features(TR_2200_6500)),
PRODUCT_97(97, "LIFX A19", new Features(TR_1500_9000, COLOR)),
PRODUCT_98(98, "LIFX BR30", new Features(TR_1500_9000, COLOR)),
PRODUCT_99(99, "LIFX Clean", new Features(TR_1500_9000, HEV)),
PRODUCT_100(100, "LIFX Filament Clear", new Features(TR_2100_2100)),
PRODUCT_101(101, "LIFX Filament Amber", new Features(TR_2000_2000)),
PRODUCT_109(109, "LIFX A19 Night Vision", new Features(TR_1500_9000, COLOR, INFRARED)),
PRODUCT_110(110, "LIFX BR30 Night Vision", new Features(TR_1500_9000, COLOR, INFRARED)),
PRODUCT_111(111, "LIFX A19 Night Vision", new Features(TR_1500_9000, COLOR, INFRARED));
/**
* Enumerates the product features.
*/
public enum Feature {
BUTTONS,
CHAIN,
COLOR,
EXTENDED_MULTIZONE,
HEV,
INFRARED,
MATRIX,
MULTIZONE,
RELAYS,
TILE_EFFECT
}
@ -132,11 +157,22 @@ public enum LifxProduct {
* Enumerates the color temperature ranges of lights.
*/
public enum TemperatureRange {
/**
* When the temperature range is not defined for {@link Upgrade}s it is inherited from the most
* recent firmware version.
*/
NONE(0, 0),
/**
* 1500-4000K
*/
TR_1500_4000(1500, 4000),
/**
* 1500-6500K
*/
TR_1500_6500(1500, 6500),
/**
* 1500-9000K
*/
@ -208,30 +244,70 @@ public enum LifxProduct {
}
}
public static class Features {
private TemperatureRange temperatureRange;
private Set<Feature> features;
private Features(Feature... features) {
this(NONE, features);
}
private Features(Features other) {
this(other.temperatureRange, other.features);
}
private Features(TemperatureRange temperatureRange, Feature... features) {
this.temperatureRange = temperatureRange;
this.features = Set.of(features);
}
private Features(TemperatureRange temperatureRange, Set<Feature> features) {
this.temperatureRange = temperatureRange;
this.features = Set.copyOf(features);
}
public TemperatureRange getTemperatureRange() {
return temperatureRange;
}
public boolean hasFeature(Feature feature) {
return features.contains(feature);
}
public void update(Features other) {
temperatureRange = other.temperatureRange;
features = other.features;
}
}
static class Upgrade {
final long major;
final long minor;
final Features features;
private Upgrade(long major, long minor, Features features) {
this.major = major;
this.minor = minor;
this.features = features;
}
}
private final Vendor vendor;
private final long id;
private final String name;
private final TemperatureRange temperatureRange;
private final EnumSet<Feature> features = EnumSet.noneOf(Feature.class);
private final Features features;
private final List<Upgrade> upgrades;
private LifxProduct(long id, String name, TemperatureRange temperatureRange) {
this(LIFX, id, name, temperatureRange);
private LifxProduct(long id, String name, Features features, Upgrade... upgrades) {
this(LIFX, id, name, features, upgrades);
}
private LifxProduct(long id, String name, TemperatureRange temperatureRange, Feature... features) {
this(LIFX, id, name, temperatureRange, features);
}
private LifxProduct(Vendor vendor, long id, String name, TemperatureRange temperatureRange) {
this(vendor, id, name, temperatureRange, new Feature[0]);
}
private LifxProduct(Vendor vendor, long id, String name, TemperatureRange temperatureRange, Feature... features) {
private LifxProduct(Vendor vendor, long id, String name, Features features, Upgrade... upgrades) {
this.vendor = vendor;
this.id = id;
this.name = name;
this.temperatureRange = temperatureRange;
this.features.addAll(Arrays.asList(features));
this.features = features;
this.upgrades = List.of(upgrades);
}
@Override
@ -251,8 +327,46 @@ public enum LifxProduct {
return name;
}
public TemperatureRange getTemperatureRange() {
return temperatureRange;
/**
* Returns the features of the initial product firmware version.
*
* @return the initial features
*/
public Features getFeatures() {
return new Features(features);
}
/**
* Returns the features for a specific product firmware version.
*
* @param version the firmware version
* @return the composite features of all firmware upgrades for the given major and minor firmware version
*/
public Features getFeatures(String version) {
if (upgrades.isEmpty() || !version.contains(".")) {
return new Features(features);
}
String[] majorMinorVersion = version.split("\\.");
long major = Long.valueOf(majorMinorVersion[0]);
long minor = Long.valueOf(majorMinorVersion[1]);
TemperatureRange temperatureRange = features.temperatureRange;
Set<Feature> features = new HashSet<>(this.features.features);
for (Upgrade upgrade : upgrades) {
if (upgrade.major < major || (upgrade.major == major && upgrade.minor <= minor)) {
Features upgradeFeatures = upgrade.features;
if (upgradeFeatures.temperatureRange != NONE) {
temperatureRange = upgradeFeatures.temperatureRange;
}
features.addAll(upgradeFeatures.features);
} else {
break;
}
}
return new Features(temperatureRange, features);
}
public ThingTypeUID getThingTypeUID() {
@ -271,8 +385,8 @@ public enum LifxProduct {
}
}
public boolean hasFeature(Feature feature) {
return features.contains(feature);
private boolean hasFeature(Feature feature) {
return features.hasFeature(feature);
}
/**
@ -308,4 +422,12 @@ public enum LifxProduct {
throw new IllegalArgumentException(id + " is not a valid product ID");
}
List<Upgrade> getUpgrades() {
return upgrades;
}
public boolean isLight() {
return !features.hasFeature(Feature.BUTTONS) && !features.hasFeature(Feature.RELAYS);
}
}

View File

@ -24,6 +24,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jdt.annotation.NonNullByDefault;
@ -39,6 +40,7 @@ import org.openhab.binding.lifx.internal.LifxLightPropertiesUpdater;
import org.openhab.binding.lifx.internal.LifxLightState;
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.GetLightInfraredRequest;
import org.openhab.binding.lifx.internal.dto.GetLightPowerRequest;
@ -90,7 +92,7 @@ public class LifxLightHandler extends BaseThingHandler {
private static final Duration MAX_STATE_CHANGE_DURATION = Duration.ofSeconds(4);
private final LifxChannelFactory channelFactory;
private @NonNullByDefault({}) LifxProduct product;
private @NonNullByDefault({}) Features features;
private @Nullable PercentType powerOnBrightness;
private @Nullable HSBType powerOnColor;
@ -175,7 +177,7 @@ public class LifxLightHandler extends BaseThingHandler {
updateStateIfChanged(CHANNEL_COLOR, hsb);
updateStateIfChanged(CHANNEL_BRIGHTNESS, hsb.getBrightness());
updateStateIfChanged(CHANNEL_TEMPERATURE,
kelvinToPercentType(updateColor.getKelvin(), product.getTemperatureRange()));
kelvinToPercentType(updateColor.getKelvin(), features.getTemperatureRange()));
updateZoneChannels(powerState, colors);
}
@ -210,7 +212,7 @@ public class LifxLightHandler extends BaseThingHandler {
}
private void updateZoneChannels(@Nullable PowerState powerState, HSBK[] colors) {
if (!product.hasFeature(MULTIZONE) || colors.length == 0) {
if (!features.hasFeature(MULTIZONE) || colors.length == 0) {
return;
}
@ -225,7 +227,7 @@ public class LifxLightHandler extends BaseThingHandler {
HSBK updateColor = nullSafeUpdateColor(powerState, color);
updateStateIfChanged(CHANNEL_COLOR_ZONE + i, updateColor.getHSB());
updateStateIfChanged(CHANNEL_TEMPERATURE_ZONE + i,
kelvinToPercentType(updateColor.getKelvin(), product.getTemperatureRange()));
kelvinToPercentType(updateColor.getKelvin(), features.getTemperatureRange()));
}
}
}
@ -243,9 +245,12 @@ public class LifxLightHandler extends BaseThingHandler {
LifxLightConfig configuration = getConfigAs(LifxLightConfig.class);
logId = getLogId(configuration.getMACAddress(), configuration.getHost());
product = getProduct();
logger.debug("{} : Initializing handler for product {}", logId, product.getName());
if (logger.isDebugEnabled()) {
logger.debug("{} : Initializing handler for product {}", logId, getProduct().getName());
}
features = getFeatures();
powerOnBrightness = getPowerOnBrightness();
powerOnColor = getPowerOnColor();
@ -262,7 +267,7 @@ public class LifxLightHandler extends BaseThingHandler {
currentLightState = new CurrentLightState();
pendingLightState = new LifxLightState();
LifxLightContext context = new LifxLightContext(logId, product, configuration, currentLightState,
LifxLightContext context = new LifxLightContext(logId, features, configuration, currentLightState,
pendingLightState, scheduler);
communicationHandler = new LifxLightCommunicationHandler(context);
@ -338,7 +343,7 @@ public class LifxLightHandler extends BaseThingHandler {
private @Nullable PercentType getPowerOnBrightness() {
Channel channel = null;
if (product.hasFeature(COLOR)) {
if (features.hasFeature(COLOR)) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_COLOR);
channel = getThing().getChannel(channelUID.getId());
} else {
@ -358,7 +363,7 @@ public class LifxLightHandler extends BaseThingHandler {
private @Nullable HSBType getPowerOnColor() {
Channel channel = null;
if (product.hasFeature(COLOR)) {
if (features.hasFeature(COLOR)) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_COLOR);
channel = getThing().getChannel(channelUID.getId());
}
@ -391,7 +396,7 @@ public class LifxLightHandler extends BaseThingHandler {
private @Nullable Double getEffectSpeed(String parameter) {
Channel channel = null;
if (product.hasFeature(TILE_EFFECT)) {
if (features.hasFeature(TILE_EFFECT)) {
ChannelUID channelUID = new ChannelUID(getThing().getUID(), LifxBindingConstants.CHANNEL_EFFECT);
channel = getThing().getChannel(channelUID.getId());
}
@ -405,8 +410,21 @@ public class LifxLightHandler extends BaseThingHandler {
return speed == null ? null : Double.valueOf(speed.toString());
}
private Features getFeatures() {
LifxProduct product = getProduct();
String propertyValue = getThing().getProperties().get(LifxBindingConstants.PROPERTY_HOST_VERSION);
if (propertyValue == null) {
logger.debug("{} : Using features of initial firmware version", logId);
return product.getFeatures();
}
logger.debug("{} : Using features of firmware version {}", logId, propertyValue);
return product.getFeatures(propertyValue);
}
private LifxProduct getProduct() {
Object propertyValue = getThing().getProperties().get(LifxBindingConstants.PROPERTY_PRODUCT_ID);
String propertyValue = getThing().getProperties().get(LifxBindingConstants.PROPERTY_PRODUCT_ID);
if (propertyValue == null) {
return LifxProduct.getLikelyProduct(getThing().getThingTypeUID());
}
@ -414,7 +432,7 @@ public class LifxLightHandler extends BaseThingHandler {
// Without first conversion to double, on a very first thing creation from discovery inbox,
// the product type is incorrectly parsed, as framework passed it as a floating point number
// (e.g. 50.0 instead of 50)
Double d = Double.valueOf((String) propertyValue);
Double d = Double.valueOf(propertyValue);
long productID = d.longValue();
return LifxProduct.getProductFromProductID(productID);
} catch (IllegalArgumentException e) {
@ -485,7 +503,7 @@ public class LifxLightHandler extends BaseThingHandler {
sendPacket(new GetWifiInfoRequest());
break;
case CHANNEL_EFFECT:
if (product.hasFeature(TILE_EFFECT)) {
if (features.hasFeature(TILE_EFFECT)) {
sendPacket(new GetTileEffectRequest());
}
break;
@ -538,7 +556,7 @@ public class LifxLightHandler extends BaseThingHandler {
}
break;
case CHANNEL_EFFECT:
if (command instanceof StringType && product.hasFeature(TILE_EFFECT)) {
if (command instanceof StringType && features.hasFeature(TILE_EFFECT)) {
handleTileEffectCommand((StringType) command);
} else {
supportedCommand = false;
@ -597,14 +615,14 @@ public class LifxLightHandler extends BaseThingHandler {
private void handleTemperatureCommand(PercentType temperature) {
HSBK newColor = getLightStateForCommand().getColor();
newColor.setSaturation(PercentType.ZERO);
newColor.setKelvin(percentTypeToKelvin(temperature, product.getTemperatureRange()));
newColor.setKelvin(percentTypeToKelvin(temperature, features.getTemperatureRange()));
getLightStateForCommand().setColor(newColor);
}
private void handleTemperatureCommand(PercentType temperature, int zoneIndex) {
HSBK newColor = getLightStateForCommand().getColor(zoneIndex);
newColor.setSaturation(PercentType.ZERO);
newColor.setKelvin(percentTypeToKelvin(temperature, product.getTemperatureRange()));
newColor.setKelvin(percentTypeToKelvin(temperature, features.getTemperatureRange()));
getLightStateForCommand().setColor(newColor, zoneIndex);
}
@ -633,7 +651,7 @@ public class LifxLightHandler extends BaseThingHandler {
PercentType localPowerOnTemperature = powerOnTemperature;
if (localPowerOnTemperature != null && onOff == OnOffType.ON) {
getLightStateForCommand()
.setTemperature(percentTypeToKelvin(localPowerOnTemperature, product.getTemperatureRange()));
.setTemperature(percentTypeToKelvin(localPowerOnTemperature, features.getTemperatureRange()));
}
PercentType powerOnBrightness = this.powerOnBrightness;
@ -658,14 +676,14 @@ public class LifxLightHandler extends BaseThingHandler {
private void handleIncreaseDecreaseTemperatureCommand(IncreaseDecreaseType increaseDecrease) {
PercentType baseTemperature = kelvinToPercentType(getLightStateForCommand().getColor().getKelvin(),
product.getTemperatureRange());
features.getTemperatureRange());
PercentType newTemperature = increaseDecreasePercentType(increaseDecrease, baseTemperature);
handleTemperatureCommand(newTemperature);
}
private void handleIncreaseDecreaseTemperatureCommand(IncreaseDecreaseType increaseDecrease, int zoneIndex) {
PercentType baseTemperature = kelvinToPercentType(getLightStateForCommand().getColor(zoneIndex).getKelvin(),
product.getTemperatureRange());
features.getTemperatureRange());
PercentType newTemperature = increaseDecreasePercentType(increaseDecrease, baseTemperature);
handleTemperatureCommand(newTemperature, zoneIndex);
}
@ -691,7 +709,18 @@ public class LifxLightHandler extends BaseThingHandler {
flameSpeedInMSecs.longValue());
getLightStateForCommand().setTileEffect(effect);
} catch (IllegalArgumentException e) {
logger.debug("Wrong effect type received as command: {}", type);
logger.debug("{} : Wrong effect type received as command: {}", logId, type);
}
}
@Override
protected void updateProperties(Map<String, String> properties) {
String oldHostVersion = getThing().getProperties().get(LifxBindingConstants.PROPERTY_HOST_VERSION);
super.updateProperties(properties);
String newHostVersion = getThing().getProperties().get(LifxBindingConstants.PROPERTY_HOST_VERSION);
if (!Objects.equals(oldHostVersion, newHostVersion)) {
features.update(getFeatures());
}
}

View File

@ -0,0 +1,140 @@
/**
* 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;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.openhab.binding.lifx.internal.LifxProduct.Feature.*;
import static org.openhab.binding.lifx.internal.LifxProduct.TemperatureRange.*;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.lifx.internal.LifxProduct.Features;
import org.openhab.binding.lifx.internal.LifxProduct.Upgrade;
/**
* Tests {@link LifxProduct}.
*
* @author Wouter Born - Initial contribution
*/
@NonNullByDefault
public class LifxProductTest {
@Test
public void productIDsAreUnique() {
Set<Long> productIDs = new HashSet<>();
for (LifxProduct product : LifxProduct.values()) {
assertThat(productIDs, not(hasItem(product.getID())));
productIDs.add(product.getID());
}
}
@Test
public void productNamesMatchProductIDs() {
for (LifxProduct product : LifxProduct.values()) {
assertThat(product.name(), is("PRODUCT_" + product.getID()));
}
}
@Test
public void lightsHaveDefinedTemperatureRange() {
for (LifxProduct product : LifxProduct.values()) {
if (product.isLight()) {
String reason = String.format("The %s light does not define a temperature range", product.name());
assertThat(reason, product.getFeatures().getTemperatureRange(), is(not(NONE)));
}
}
}
@Test
public void upgradesSortedByMajorMinor() {
for (LifxProduct product : LifxProduct.values()) {
long major = 0;
long minor = 0;
for (Upgrade upgrade : product.getUpgrades()) {
String reason = String.format("Upgrades for %s are not sorted by major minor (%s.%s >= %s.%s)",
product.name(), major, minor, upgrade.major, upgrade.minor);
assertThat(reason, major < upgrade.major || (major == upgrade.major && minor < upgrade.minor),
is(true));
major = upgrade.major;
minor = upgrade.minor;
}
}
}
@Test
public void getFeaturesForProductWithoutUpgrades() {
LifxProduct product = LifxProduct.PRODUCT_1;
assertThat(product.getUpgrades(), hasSize(0));
Features features = product.getFeatures();
assertThat(features.getTemperatureRange(), is(TR_2500_9000));
assertThat(features.hasFeature(COLOR), is(true));
features = product.getFeatures("1.23");
assertThat(features.getTemperatureRange(), is(TR_2500_9000));
assertThat(features.hasFeature(COLOR), is(true));
}
@Test
public void getFeaturesForProductWithUpgrades() {
LifxProduct product = LifxProduct.PRODUCT_32;
assertThat(product.getUpgrades(), hasSize(2));
Features features = product.getFeatures();
assertThat(features.getTemperatureRange(), is(TR_2500_9000));
assertThat(features.hasFeature(COLOR), is(true));
assertThat(features.hasFeature(EXTENDED_MULTIZONE), is(false));
assertThat(features.hasFeature(INFRARED), is(false));
assertThat(features.hasFeature(MULTIZONE), is(true));
features = product.getFeatures("2.70");
assertThat(features.getTemperatureRange(), is(TR_2500_9000));
assertThat(features.hasFeature(COLOR), is(true));
assertThat(features.hasFeature(EXTENDED_MULTIZONE), is(false));
assertThat(features.hasFeature(INFRARED), is(false));
assertThat(features.hasFeature(MULTIZONE), is(true));
features = product.getFeatures("2.77");
assertThat(features.getTemperatureRange(), is(TR_2500_9000));
assertThat(features.hasFeature(COLOR), is(true));
assertThat(features.hasFeature(EXTENDED_MULTIZONE), is(true));
assertThat(features.hasFeature(INFRARED), is(false));
assertThat(features.hasFeature(MULTIZONE), is(true));
features = product.getFeatures("2.79");
assertThat(features.getTemperatureRange(), is(TR_2500_9000));
assertThat(features.hasFeature(COLOR), is(true));
assertThat(features.hasFeature(EXTENDED_MULTIZONE), is(true));
assertThat(features.hasFeature(INFRARED), is(false));
assertThat(features.hasFeature(MULTIZONE), is(true));
features = product.getFeatures("2.80");
assertThat(features.getTemperatureRange(), is(TR_1500_9000));
assertThat(features.hasFeature(COLOR), is(true));
assertThat(features.hasFeature(EXTENDED_MULTIZONE), is(true));
assertThat(features.hasFeature(INFRARED), is(false));
assertThat(features.hasFeature(MULTIZONE), is(true));
features = product.getFeatures("2.81");
assertThat(features.getTemperatureRange(), is(TR_1500_9000));
assertThat(features.hasFeature(COLOR), is(true));
assertThat(features.hasFeature(EXTENDED_MULTIZONE), is(true));
assertThat(features.hasFeature(INFRARED), is(false));
assertThat(features.hasFeature(MULTIZONE), is(true));
}
}