Skip to content

Commit

Permalink
[amazonechocontrol] fix color handling for smarthome devices (#272)
Browse files Browse the repository at this point in the history
Signed-off-by: Jan N. Klug <[email protected]>
# Conflicts:
#	bundles/org.smarthomej.binding.amazonechocontrol/README.md
  • Loading branch information
J-N-K committed Dec 31, 2021
1 parent 155e79b commit f110aec
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.smarthomej.binding.amazonechocontrol.internal.handler.EchoHandler;
import org.smarthomej.binding.amazonechocontrol.internal.handler.FlashBriefingProfileHandler;
import org.smarthomej.binding.amazonechocontrol.internal.handler.SmartHomeDeviceHandler;
import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider;

import com.google.gson.Gson;

Expand All @@ -60,13 +61,15 @@ public class AmazonEchoControlHandlerFactory extends BaseThingHandlerFactory {
private final BindingServlet bindingServlet;
private final Gson gson;
private final HttpClient httpClient;
private final SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider;

@Activate
public AmazonEchoControlHandlerFactory(@Reference HttpService httpService, @Reference StorageService storageService)
throws Exception {
public AmazonEchoControlHandlerFactory(@Reference HttpService httpService, @Reference StorageService storageService,
@Reference SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider) throws Exception {
this.storageService = storageService;
this.httpService = httpService;
this.gson = new Gson();
this.dynamicCommandDescriptionProvider = dynamicCommandDescriptionProvider;
this.httpClient = new HttpClient(new SslContextFactory.Client());
this.bindingServlet = new BindingServlet(httpService);

Expand Down Expand Up @@ -109,7 +112,7 @@ protected void deactivate(ComponentContext componentContext) {
} else if (SUPPORTED_ECHO_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new EchoHandler(thing, gson);
} else if (SUPPORTED_SMART_HOME_THING_TYPES_UIDS.contains(thingTypeUID)) {
return new SmartHomeDeviceHandler(thing, gson);
return new SmartHomeDeviceHandler(thing, gson, dynamicCommandDescriptionProvider);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.CommandOption;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
Expand All @@ -55,6 +56,7 @@
import org.smarthomej.binding.amazonechocontrol.internal.smarthome.ChannelInfo;
import org.smarthomej.binding.amazonechocontrol.internal.smarthome.Constants;
import org.smarthomej.binding.amazonechocontrol.internal.smarthome.InterfaceHandler;
import org.smarthomej.commons.SimpleDynamicCommandDescriptionProvider;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
Expand All @@ -67,16 +69,20 @@
@NonNullByDefault
public class SmartHomeDeviceHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SmartHomeDeviceHandler.class);
private final SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider;

private @Nullable SmartHomeBaseDevice smartHomeBaseDevice;
private final Gson gson;
private final Map<String, InterfaceHandler> interfaceHandlers = new HashMap<>();
private final Map<String, JsonArray> lastStates = new HashMap<>();

private @Nullable SmartHomeBaseDevice smartHomeBaseDevice;
private String deviceId = "";

public SmartHomeDeviceHandler(Thing thing, Gson gson) {
public SmartHomeDeviceHandler(Thing thing, Gson gson,
SimpleDynamicCommandDescriptionProvider dynamicCommandDescriptionProvider) {
super(thing);
this.gson = gson;
this.dynamicCommandDescriptionProvider = dynamicCommandDescriptionProvider;
}

public synchronized void setDeviceAndUpdateThingState(AccountHandler accountHandler,
Expand Down Expand Up @@ -123,6 +129,12 @@ public synchronized void setDeviceAndUpdateThingState(AccountHandler accountHand
if (addChannelToDevice(thingBuilder, callback, channelInfo)) {
changed = true;
}

List<CommandOption> commandOptions = handler.getCommandDescription(channelInfo);
if (commandOptions != null) {
dynamicCommandDescriptionProvider.setCommandOptions(
new ChannelUID(thing.getUID(), channelInfo.channelId), commandOptions);
}
}
}
}
Expand Down Expand Up @@ -168,6 +180,8 @@ public void dispose() {
if (accountHandler != null) {
accountHandler.removeSmartHomeDeviceHandler(this);
}

dynamicCommandDescriptionProvider.removeCommandDescriptionForThing(thing.getUID());
}

public String getId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.Command;
import org.openhab.core.types.CommandOption;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -51,6 +53,12 @@ public AbstractInterfaceHandler(SmartHomeDeviceHandler smartHomeDeviceHandler, L
this.interfaces = interfaces;
}

@Override
public @Nullable List<CommandOption> getCommandDescription(ChannelInfo channelInfo) {
// return null if not used
return null;
}

protected abstract Set<ChannelInfo> findChannelInfos(JsonSmartHomeCapability capability, String property);

public abstract void updateChannels(String interfaceName, List<JsonObject> stateList, UpdateChannelResult result);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* Copyright (c) 2021 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.smarthome;

import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.HSBType;

/**
* The {@link AlexaColor} defines the Alexa color names
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
public class AlexaColor {
public static final List<AlexaColor> ALEXA_COLORS = List.of( //
new AlexaColor("white", new HSBType("0,0,100")), //
new AlexaColor("red", new HSBType("0,100,100")), //
new AlexaColor("crimson", new HSBType("348,90,100")), //
new AlexaColor("salmon", new HSBType("16,52,100")), //
new AlexaColor("orange", new HSBType("38,100,100")), //
new AlexaColor("gold", new HSBType("49,100,100")), //
new AlexaColor("yellow", new HSBType("60,100,100")), //
new AlexaColor("green", new HSBType("120,100,100")), //
new AlexaColor("turquoise", new HSBType("173,72,100")), //
new AlexaColor("cyan", new HSBType("180,100,100")), //
new AlexaColor("sky_blue", new HSBType("197,42,100")), //
new AlexaColor("blue", new HSBType("240,100,100")), //
new AlexaColor("purple", new HSBType("276,86,100")), //
new AlexaColor("magenta", new HSBType("300,100,100")), //
new AlexaColor("pink", new HSBType("348,25,100")), //
new AlexaColor("lavender", new HSBType("255,50,100")));

public final String colorName;
final HSBType value;
private final double[] lab;

public AlexaColor(String colorName, HSBType value) {
this.colorName = colorName;
this.value = value;
this.lab = getLabFromHSB(value);
}

/**
* get the closest Alexa color
*
* @param value a given HSB color
* @return the name of the closest pre-defined Alexa color
*/
public static String getClosestColorName(HSBType value) {
double[] lab = getLabFromHSB(value);
String colorName = "";
double smallestDistance = Double.MAX_VALUE;
for (AlexaColor color : ALEXA_COLORS) {
double distance = color.getEuclideanDistance(lab);
if (distance < smallestDistance) {
colorName = color.colorName;
smallestDistance = distance;
}
}
return colorName;
}

private double getEuclideanDistance(double[] value) {
double deltaL = value[0] - lab[0];
double deltaA = value[1] - lab[1];
double deltaB = value[2] - lab[2];

return Math.sqrt(deltaL * deltaL + deltaA * deltaA + deltaB * deltaB);
}

private static double[] getLabFromHSB(HSBType value) {
double r = value.getRed().doubleValue() / 100.0;
double g = value.getGreen().doubleValue() / 100.0;
double b = value.getBlue().doubleValue() / 100.0;

// D65, 10 degree
double xn = 94.811;
double yn = 100.0;
double zn = 107.304;

double x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b;
double y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b;
double z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b;

double ls = 116.0 * labRoot(y / yn) - 16.0;
double as = 500.0 * (labRoot(x / xn) - labRoot(y / yn));
double bs = 200.0 * (labRoot(y / yn) - labRoot(z / zn));

return new double[] { ls, as, bs };
}

private static double labRoot(double value) {
if (value < 216.0 / 24389.0) {
return (1.0 / 116.0) * ((24389.0 / 27.0) * value + 16.0);
} else {
return Math.pow(value, 1.0 / 3.0);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
package org.smarthomej.binding.amazonechocontrol.internal.smarthome;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -25,6 +27,7 @@
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.CommandOption;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -75,9 +78,9 @@ public void updateChannels(String interfaceName, List<JsonObject> stateList, Upd
JsonObject value = state.get("value").getAsJsonObject();
// For groups take the maximum
if (colorValue == null) {
colorValue = new HSBType(new DecimalType(value.get("hue").getAsInt()),
new PercentType(value.get("saturation").getAsInt() * 100),
new PercentType(value.get("brightness").getAsInt() * 100));
colorValue = new HSBType(new DecimalType(value.get("hue").getAsDouble()),
new PercentType(BigDecimal.valueOf(value.get("saturation").getAsDouble() * 100.0)),
new PercentType(BigDecimal.valueOf(value.get("brightness").getAsDouble() * 100.0)));
}
}
}
Expand Down Expand Up @@ -129,4 +132,10 @@ public boolean handleCommand(Connection connection, JsonSmartHomeDevice shd, Str
}
return false;
}

@Override
public List<CommandOption> getCommandDescription(ChannelInfo channelInfo) {
return AlexaColor.ALEXA_COLORS.stream().map(color -> new CommandOption(color.colorName, color.colorName))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
import java.util.List;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.types.Command;
import org.openhab.core.types.CommandOption;
import org.smarthomej.binding.amazonechocontrol.internal.connection.Connection;
import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeCapability;
import org.smarthomej.binding.amazonechocontrol.internal.jsons.JsonSmartHomeDevice;
Expand All @@ -43,6 +45,9 @@ boolean handleCommand(Connection connection, JsonSmartHomeDevice shd, String ent
List<JsonSmartHomeCapability> capabilities, String channelId, Command command)
throws IOException, InterruptedException;

@Nullable
List<CommandOption> getCommandDescription(ChannelInfo channelInfo);

class UpdateChannelResult {
public boolean needSingleUpdate;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2021 Contributors to the SmartHome/J 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.smarthomej.binding.amazonechocontrol.internal.smarthome;

import java.util.stream.Stream;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.junit.jupiter.MockitoExtension;
import org.openhab.core.test.java.JavaTest;

/**
* The {@link AlexaColorTest} is a
*
* @author Jan N. Klug - Initial contribution
*/
@NonNullByDefault
@ExtendWith(MockitoExtension.class)
public class AlexaColorTest extends JavaTest {

@ParameterizedTest
@MethodSource("getColors")
public void distanceTest(AlexaColor color) {
Assertions.assertEquals(color.colorName, AlexaColor.getClosestColorName(color.value));
}

@SuppressWarnings("unused")
private static Stream<AlexaColor> getColors() {
return AlexaColor.ALEXA_COLORS.stream();
}
}

0 comments on commit f110aec

Please sign in to comment.