From 804aaee370eb89d21895418c5b33742748877073 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Gr=C3=A4ff?= Date: Wed, 12 Dec 2018 11:08:36 +0100 Subject: [PATCH] MQTT: Add MQTT receive tests for all channel types (#6664) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TypeParser commit a few days ago allowed to send command strings like INCREASE/DECREASE. Unfortunately it broke a few channels. * This commit adds tests for all channel types. * The RGB color channel was broken (even before the TypeParser commit): Fixed. Added features: * Add location, datetime and image channel types including tests and readme sections. Refactor: * The internal.values.* classes are a lot slimmer. Value itself is not an interface but an abstract class now. * Move the xml thing/channel configurations to ESH-INF/config. * Rename two methods for clarity, so that value.getValue().getValue() is not a think anymore. Signed-off-by: David Gräff --- .../HomeAssistantMQTTImplementationTests.java | 18 +- .../generic/HomieImplementationTests.java | 12 +- .../internal/generic/ChannelStateTests.java | 182 +++++++++-- .../ChannelStateTransformationTests.java | 2 +- .../handler/GenericThingHandlerTests.java | 10 +- .../handler/HomieThingHandlerTests.java | 10 +- .../generic/internal/values/ValueTests.java | 90 +++--- .../ESH-INF/config/number-channel-config.xml | 21 +- .../ESH-INF/config/string-channel-config.xml | 46 +++ .../ESH-INF/config/switch-channel-config.xml | 53 +++ .../ESH-INF/thing/channels.xml | 305 ++---------------- .../README.md | 40 ++- .../internal/MqttBindingConstants.java | 4 +- .../homeassistant/AbstractComponent.java | 2 +- .../convention/homie300/Property.java | 11 +- .../internal/generic/ChannelConfig.java | 1 - .../generic/ChannelConfigBuilder.java | 5 + .../internal/generic/ChannelState.java | 87 +++-- .../generic/ChannelStateTransformation.java | 2 +- .../handler/AbstractMQTTThingHandler.java | 4 +- .../internal/handler/GenericThingHandler.java | 2 +- .../generic/internal/values/ColorValue.java | 115 +++---- .../internal/values/DateTimeValue.java | 54 ++++ .../generic/internal/values/ImageValue.java | 41 +++ .../internal/values/LocationValue.java | 43 +++ .../generic/internal/values/NumberValue.java | 136 ++------ .../generic/internal/values/OnOffValue.java | 64 +--- .../internal/values/OpenCloseValue.java | 65 +--- .../internal/values/PercentageValue.java | 96 ++++++ .../generic/internal/values/TextValue.java | 42 +-- .../mqtt/generic/internal/values/Value.java | 122 +++++-- .../generic/internal/values/ValueFactory.java | 13 +- 32 files changed, 933 insertions(+), 765 deletions(-) create mode 100644 extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/string-channel-config.xml create mode 100644 extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/switch-channel-config.xml create mode 100644 extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/DateTimeValue.java create mode 100644 extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ImageValue.java create mode 100644 extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/LocationValue.java create mode 100644 extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/PercentageValue.java diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomeAssistantMQTTImplementationTests.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomeAssistantMQTTImplementationTests.java index f43c2b23d90..852ef562d62 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomeAssistantMQTTImplementationTests.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomeAssistantMQTTImplementationTests.java @@ -36,12 +36,12 @@ import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.ComponentSwitch; import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents; import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered; +import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.HaID; import org.eclipse.smarthome.binding.mqtt.generic.internal.generic.ChannelStateUpdateListener; import org.eclipse.smarthome.binding.mqtt.generic.internal.generic.MqttChannelTypeProvider; -import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.HaID; import org.eclipse.smarthome.binding.mqtt.generic.internal.handler.ThingChannelConstants; -import org.eclipse.smarthome.binding.mqtt.generic.internal.values.OnOffValue; import org.eclipse.smarthome.core.library.types.OnOffType; +import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.UnDefType; import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection; import org.eclipse.smarthome.io.transport.mqtt.MqttConnectionObserver; @@ -126,7 +126,7 @@ public void setUp() throws InterruptedException, ExecutionException, TimeoutExce public void tearDown() throws InterruptedException, ExecutionException, TimeoutException { if (connection != null) { connection.removeConnectionObserver(failIfChange); - connection.stop().get(500, TimeUnit.MILLISECONDS); + connection.stop().get(1000, TimeUnit.MILLISECONDS); } } @@ -190,9 +190,9 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo verify(channelTypeProvider, times(1)).setChannelType(any(), any()); // We expect a switch component with an OnOff channel with the initial value UNDEF: - OnOffValue value = (OnOffValue) haComponents.get(haID.getChannelGroupID()).channelTypes() - .get(ComponentSwitch.switchChannelID).channelState.getValue(); - assertThat(value.getValue(), is(UnDefType.UNDEF)); + State value = haComponents.get(haID.getChannelGroupID()).channelTypes() + .get(ComponentSwitch.switchChannelID).channelState.getCache().getChannelState(); + assertThat(value, is(UnDefType.UNDEF)); haComponents.values().stream().map(e -> e.start(connection, scheduler, 100)) .reduce(CompletableFuture.completedFuture(null), (a, v) -> a.thenCompose(b -> v)).exceptionally(e -> { @@ -204,9 +204,9 @@ public void parseHATree() throws InterruptedException, ExecutionException, Timeo verify(channelStateUpdateListener, times(1)).updateChannelState(any(), any()); // Value should be ON now. - value = (OnOffValue) haComponents.get(haID.getChannelGroupID()).channelTypes() - .get(ComponentSwitch.switchChannelID).channelState.getValue(); - assertThat(value.getValue(), is(OnOffType.ON)); + value = haComponents.get(haID.getChannelGroupID()).channelTypes() + .get(ComponentSwitch.switchChannelID).channelState.getCache().getChannelState(); + assertThat(value, is(OnOffType.ON)); } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomieImplementationTests.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomieImplementationTests.java index 88c176c031c..b9ee5d88a87 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomieImplementationTests.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomieImplementationTests.java @@ -176,7 +176,7 @@ public void retrieveAllTopics() throws InterruptedException, ExecutionException, CountDownLatch c = new CountDownLatch(registeredTopics); connection.subscribe(deviceTopic + "/#", (topic, payload) -> c.countDown()).get(200, TimeUnit.MILLISECONDS); assertTrue("Connection " + connection.getClientId() + " not retrieving all topics", - c.await(200, TimeUnit.MILLISECONDS)); + c.await(1000, TimeUnit.MILLISECONDS)); } @Test @@ -217,7 +217,7 @@ public void retrieveAttributes() throws InterruptedException, ExecutionException verify(channelState).processMessage(any(), any()); verify(callback).updateChannelState(any(), any()); - assertThat(property.getChannelState().getValue().getValue(), is(new DecimalType(10))); + assertThat(property.getChannelState().getCache().getChannelState(), is(new DecimalType(10))); property.stop().get(); assertThat(connection.hasSubscribers(), is(false)); @@ -307,17 +307,17 @@ public void parseHomieTree() throws InterruptedException, ExecutionException, Ti // The device->node->property tree is ready. Now subscribe to property values. device.startChannels(connection, scheduler, 50, handler).get(); assertThat(propertyBell.getChannelState().isStateful(), is(false)); - assertThat(propertyBell.getChannelState().getValue().getValue(), is(UnDefType.UNDEF)); - assertThat(property.getChannelState().getValue().getValue(), is(new DecimalType(10))); + assertThat(propertyBell.getChannelState().getCache().getChannelState(), is(UnDefType.UNDEF)); + assertThat(property.getChannelState().getCache().getChannelState(), is(new DecimalType(10))); property = node.properties.get("testRetain"); WaitForTopicValue watcher = new WaitForTopicValue(embeddedConnection, propertyTestTopic + "/set"); // Watch the topic. Publish a retain=false value to MQTT - property.getChannelState().setValue(OnOffType.OFF).get(); + property.getChannelState().publishValue(OnOffType.OFF).get(); assertThat(watcher.waitForTopicValue(50), is("false")); // Publish a retain=false value to MQTT. - property.getChannelState().setValue(OnOffType.ON).get(); + property.getChannelState().publishValue(OnOffType.ON).get(); // This test is flaky if the MQTT broker does not get a time to "forget" this non-retained value Thread.sleep(50); // No value is expected to be retained on this MQTT topic diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTests.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTests.java index 684535c999f..fcd0ad0236b 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTests.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTests.java @@ -12,12 +12,16 @@ */ package org.eclipse.smarthome.binding.mqtt.generic.internal.generic; -import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; import static org.mockito.MockitoAnnotations.initMocks; +import java.math.BigDecimal; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; @@ -28,11 +32,18 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homeassistant.DiscoverComponents.ComponentDiscovered; +import org.eclipse.smarthome.binding.mqtt.generic.internal.values.ColorValue; +import org.eclipse.smarthome.binding.mqtt.generic.internal.values.DateTimeValue; +import org.eclipse.smarthome.binding.mqtt.generic.internal.values.ImageValue; +import org.eclipse.smarthome.binding.mqtt.generic.internal.values.LocationValue; +import org.eclipse.smarthome.binding.mqtt.generic.internal.values.NumberValue; +import org.eclipse.smarthome.binding.mqtt.generic.internal.values.PercentageValue; import org.eclipse.smarthome.binding.mqtt.generic.internal.values.TextValue; +import org.eclipse.smarthome.core.library.types.HSBType; +import org.eclipse.smarthome.core.library.types.RawType; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection; -import org.eclipse.smarthome.test.java.JavaOSGiTest; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -44,7 +55,7 @@ * * @author David Graeff - Initial contribution */ -public class ChannelStateTests extends JavaOSGiTest { +public class ChannelStateTests { @Mock MqttBrokerConnection connection; @@ -62,6 +73,8 @@ public class ChannelStateTests extends JavaOSGiTest { ScheduledExecutorService scheduler; + ChannelConfig config = ChannelConfigBuilder.create("state", "command").build(); + @Before public void setUp() { initMocks(this); @@ -84,9 +97,7 @@ public void tearDown() { @Test public void noInteractionTimeoutTest() throws InterruptedException, ExecutionException, TimeoutException { - - ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state", "command").build(), channelUID, - textValue, channelStateUpdateListener)); + ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener)); c.start(connection, scheduler, 50).get(100, TimeUnit.MILLISECONDS); verify(connection).subscribe(eq("state"), eq(c)); c.stop().get(); @@ -94,30 +105,29 @@ public void noInteractionTimeoutTest() throws InterruptedException, ExecutionExc } @Test - public void publishTest() throws InterruptedException, ExecutionException, TimeoutException { - ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state", "command").build(), channelUID, - textValue, channelStateUpdateListener)); + public void publishFormatTest() throws InterruptedException, ExecutionException, TimeoutException { + ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener)); c.start(connection, scheduler, 0).get(50, TimeUnit.MILLISECONDS); verify(connection).subscribe(eq("state"), eq(c)); - c.setValue(new StringType("UPDATE")).get(); + c.publishValue(new StringType("UPDATE")).get(); verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE".getBytes())), anyInt(), eq(false)); c.config.formatBeforePublish = "prefix%s"; - c.setValue(new StringType("UPDATE")).get(); + c.publishValue(new StringType("UPDATE")).get(); verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "prefixUPDATE".getBytes())), anyInt(), eq(false)); c.config.formatBeforePublish = "%1$s-%1$s"; - c.setValue(new StringType("UPDATE")).get(); + c.publishValue(new StringType("UPDATE")).get(); verify(connection).publish(eq("command"), argThat(p -> Arrays.equals(p, "UPDATE-UPDATE".getBytes())), anyInt(), eq(false)); c.config.formatBeforePublish = "%s"; c.config.retained = true; - c.setValue(new StringType("UPDATE")).get(); + c.publishValue(new StringType("UPDATE")).get(); verify(connection).publish(eq("command"), any(), anyInt(), eq(true)); c.stop().get(); @@ -125,28 +135,154 @@ public void publishTest() throws InterruptedException, ExecutionException, Timeo } @Test - public void receiveTest() throws InterruptedException, ExecutionException, TimeoutException { - ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state", "command").build(), channelUID, - textValue, channelStateUpdateListener)); + public void receiveWildcardTest() throws InterruptedException, ExecutionException, TimeoutException { + ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(), + channelUID, textValue, channelStateUpdateListener)); CompletableFuture<@Nullable Void> future = c.start(connection, scheduler, 100); - c.processMessage("state", "A TEST".getBytes()); + c.processMessage("state/bla/topic", "A TEST".getBytes()); future.get(300, TimeUnit.MILLISECONDS); - assertThat(textValue.getValue().toString(), is("A TEST")); + assertThat(textValue.getChannelState().toString(), is("A TEST")); verify(channelStateUpdateListener).updateChannelState(eq(channelUID), any()); } @Test - public void receiveWildcardTest() throws InterruptedException, ExecutionException, TimeoutException { - ChannelState c = spy(new ChannelState(ChannelConfigBuilder.create("state/+/topic", "command").build(), - channelUID, textValue, channelStateUpdateListener)); + public void receiveStringTest() throws InterruptedException, ExecutionException, TimeoutException { + ChannelState c = spy(new ChannelState(config, channelUID, textValue, channelStateUpdateListener)); CompletableFuture<@Nullable Void> future = c.start(connection, scheduler, 100); - c.processMessage("state/bla/topic", "A TEST".getBytes()); + c.processMessage("state", "A TEST".getBytes()); future.get(300, TimeUnit.MILLISECONDS); - assertThat(textValue.getValue().toString(), is("A TEST")); + assertThat(textValue.getChannelState().toString(), is("A TEST")); verify(channelStateUpdateListener).updateChannelState(eq(channelUID), any()); } + + @Test + public void receiveDecimalTest() throws InterruptedException, ExecutionException, TimeoutException { + NumberValue value = new NumberValue(null, null, new BigDecimal(10)); + ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener)); + c.start(connection, mock(ScheduledExecutorService.class), 100); + + c.processMessage("state", "15".getBytes()); + assertThat(value.getChannelState().toString(), is("15")); + + c.processMessage("state", "INCREASE".getBytes()); + assertThat(value.getChannelState().toString(), is("25")); + + c.processMessage("state", "DECREASE".getBytes()); + assertThat(value.getChannelState().toString(), is("15")); + + verify(channelStateUpdateListener, times(3)).updateChannelState(eq(channelUID), any()); + } + + @Test + public void receiveDecimalFractionalTest() throws InterruptedException, ExecutionException, TimeoutException { + NumberValue value = new NumberValue(null, null, new BigDecimal(10.5)); + ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener)); + c.start(connection, mock(ScheduledExecutorService.class), 100); + + c.processMessage("state", "5.5".getBytes()); + assertThat(value.getChannelState().toString(), is("5.5")); + + c.processMessage("state", "INCREASE".getBytes()); + assertThat(value.getChannelState().toString(), is("16.0")); + } + + @Test + public void receivePercentageTest() throws InterruptedException, ExecutionException, TimeoutException { + PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10)); + ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener)); + c.start(connection, mock(ScheduledExecutorService.class), 100); + + c.processMessage("state", "-100".getBytes()); // 0% + assertThat(value.getChannelState().toString(), is("0")); + + c.processMessage("state", "100".getBytes()); // 100% + assertThat(value.getChannelState().toString(), is("100")); + + c.processMessage("state", "0".getBytes()); // 50% + assertThat(value.getChannelState().toString(), is("50")); + + c.processMessage("state", "INCREASE".getBytes()); + assertThat(value.getChannelState().toString(), is("60")); + } + + @Test + public void receiveRGBColorTest() throws InterruptedException, ExecutionException, TimeoutException { + ColorValue value = new ColorValue(true, "FON", "FOFF"); + ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener)); + c.start(connection, mock(ScheduledExecutorService.class), 100); + + c.processMessage("state", "ON".getBytes()); + assertThat(value.getChannelState().toString(), is("0,0,10")); + assertThat(value.getMQTTpublishValue().toString(), is("25,25,25")); + + c.processMessage("state", "FOFF".getBytes()); + assertThat(value.getChannelState().toString(), is("0,0,0")); + assertThat(value.getMQTTpublishValue().toString(), is("0,0,0")); + + HSBType t = HSBType.fromRGB(12, 18, 231); + + c.processMessage("state", "12,18,231".getBytes()); + assertThat(value.getChannelState(), is(t)); // HSB + // rgb -> hsv -> rgb is quite lossy + assertThat(value.getMQTTpublishValue().toString(), is("13,20,225")); + } + + @Test + public void receiveHSBColorTest() throws InterruptedException, ExecutionException, TimeoutException { + ColorValue value = new ColorValue(false, "FON", "FOFF"); + ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener)); + c.start(connection, mock(ScheduledExecutorService.class), 100); + + c.processMessage("state", "ON".getBytes()); // Minimum brightness is 10 + assertThat(value.getChannelState().toString(), is("0,0,10")); + assertThat(value.getMQTTpublishValue().toString(), is("0,0,10")); + + c.processMessage("state", "FOFF".getBytes()); + assertThat(value.getChannelState().toString(), is("0,0,0")); + assertThat(value.getMQTTpublishValue().toString(), is("0,0,0")); + + c.processMessage("state", "12,18,100".getBytes()); + assertThat(value.getChannelState().toString(), is("12,18,100")); + assertThat(value.getMQTTpublishValue().toString(), is("12,18,100")); + } + + @Test + public void receiveLocationTest() throws InterruptedException, ExecutionException, TimeoutException { + LocationValue value = new LocationValue(); + ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener)); + c.start(connection, mock(ScheduledExecutorService.class), 100); + + c.processMessage("state", "46.833974, 7.108433".getBytes()); + assertThat(value.getChannelState().toString(), is("46.833974,7.108433")); + assertThat(value.getMQTTpublishValue().toString(), is("46.833974,7.108433")); + } + + @Test + public void receiveDateTimeTest() throws InterruptedException, ExecutionException, TimeoutException { + DateTimeValue value = new DateTimeValue(); + ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener)); + c.start(connection, mock(ScheduledExecutorService.class), 100); + + ZonedDateTime zd = ZonedDateTime.now(); + String datetime = zd.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + c.processMessage("state", datetime.getBytes()); + assertThat(value.getChannelState().toString(), is(datetime + "+0100")); + assertThat(value.getMQTTpublishValue().toString(), is(datetime)); + } + + @Test + public void receiveImageTest() throws InterruptedException, ExecutionException, TimeoutException { + ImageValue value = new ImageValue(); + ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener)); + c.start(connection, mock(ScheduledExecutorService.class), 100); + + byte payload[] = new byte[] { (byte) 0xFF, (byte) 0xD8, 0x01, 0x02, (byte) 0xFF, (byte) 0xD9 }; + c.processMessage("state", payload); + assertThat(value.getChannelState(), is(instanceOf(RawType.class))); + assertThat(((RawType) value.getChannelState()).getMimeType(), is("image/jpeg")); + } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformationTests.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformationTests.java index 5d13f7d101f..a5fd8ce6775 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformationTests.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformationTests.java @@ -126,6 +126,6 @@ public void processMessageWithJSONPath() throws Exception { channelConfig.processMessage(channelConfig.getStateTopic(), payload); verify(callback).stateUpdated(eq(textChannelUID), argThat(arg -> "23.2".equals(arg.toString()))); - assertThat(channelConfig.getValue().getValue().toString(), is("23.2")); + assertThat(channelConfig.getCache().getChannelState().toString(), is("23.2")); } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandlerTests.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandlerTests.java index 80e4f9b6b52..40a4d88ac7c 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandlerTests.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandlerTests.java @@ -132,11 +132,11 @@ public void handleCommandRefresh() { thingHandler.initialize(); TextValue value = spy(new TextValue()); - doReturn(value).when(channelConfig).getValue(); + doReturn(value).when(channelConfig).getCache(); thingHandler.connection = connection; thingHandler.handleCommand(textChannelUID, RefreshType.REFRESH); - verify(value).getValue(); + verify(value).getChannelState(); } @Test @@ -152,7 +152,7 @@ public void handleCommandUpdateString() { StringType updateValue = new StringType("UPDATE"); thingHandler.handleCommand(textChannelUID, updateValue); verify(value).update(eq(updateValue)); - assertThat(channelConfig.getValue().getValue().toString(), is("UPDATE")); + assertThat(channelConfig.getCache().getChannelState().toString(), is("UPDATE")); } @Test @@ -169,7 +169,7 @@ public void handleCommandUpdateBoolean() { thingHandler.handleCommand(textChannelUID, updateValue); verify(value).update(eq(updateValue)); - assertThat(channelConfig.getValue().getValue(), is(OnOffType.ON)); + assertThat(channelConfig.getCache().getChannelState(), is(OnOffType.ON)); } @Test @@ -187,6 +187,6 @@ public void processMessage() { verify(callback).statusUpdated(eq(thing), argThat(arg -> arg.getStatus().equals(ThingStatus.ONLINE))); verify(callback).stateUpdated(eq(textChannelUID), argThat(arg -> "UPDATE".equals(arg.toString()))); - assertThat(textValue.getValue().toString(), is("UPDATE")); + assertThat(textValue.getChannelState().toString(), is("UPDATE")); } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/HomieThingHandlerTests.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/HomieThingHandlerTests.java index 3265e147c7d..f252a5402e1 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/HomieThingHandlerTests.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/HomieThingHandlerTests.java @@ -230,7 +230,7 @@ public void handleCommandRefresh() { thingHandler.handleCommand(property.channelUID, RefreshType.REFRESH); verify(callback).stateUpdated(argThat(arg -> property.channelUID.equals(arg)), - argThat(arg -> property.getChannelState().getValue().getValue().equals(arg))); + argThat(arg -> property.getChannelState().getCache().getChannelState().equals(arg))); } @SuppressWarnings("null") @@ -260,21 +260,21 @@ public void handleCommandUpdate() { StringType updateValue = new StringType("UPDATE"); thingHandler.handleCommand(property.channelUID, updateValue); - assertThat(property.getChannelState().getValue().getValue().toString(), is("UPDATE")); + assertThat(property.getChannelState().getCache().getChannelState().toString(), is("UPDATE")); verify(connection, times(1)).publish(any(), any(), anyInt(), anyBoolean()); // Check non writable property property.attributes.settable = false; property.attributesReceived(); // Assign old value - Value value = property.getChannelState().getValue(); + Value value = property.getChannelState().getCache(); Command command = TypeParser.parseCommand(value.getSupportedCommandTypes(), "OLDVALUE"); - property.getChannelState().getValue().update(command); + property.getChannelState().getCache().update(command); // Try to update with new value updateValue = new StringType("SOMETHINGNEW"); thingHandler.handleCommand(property.channelUID, updateValue); // Expect old value and no MQTT publish - assertThat(property.getChannelState().getValue().getValue().toString(), is("OLDVALUE")); + assertThat(property.getChannelState().getCache().getChannelState().toString(), is("OLDVALUE")); verify(connection, times(1)).publish(any(), any(), anyInt(), anyBoolean()); } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueTests.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueTests.java index e9f0599e449..c0b9cbda02a 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueTests.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueTests.java @@ -58,16 +58,16 @@ public void colorUpdate() { v.update(p(v, "255, 255, 255")); v.update(p(v, "OFF")); - assertThat(((HSBType) v.getValue()).getBrightness().intValue(), is(0)); + assertThat(((HSBType) v.getChannelState()).getBrightness().intValue(), is(0)); // Minimum brightness setting after brightness 0 is 10 for ON command v.update(p(v, "ON")); - assertThat(((HSBType) v.getValue()).getBrightness().intValue(), is(10)); + assertThat(((HSBType) v.getChannelState()).getBrightness().intValue(), is(10)); v.update(p(v, "0")); - assertThat(((HSBType) v.getValue()).getBrightness().intValue(), is(0)); + assertThat(((HSBType) v.getChannelState()).getBrightness().intValue(), is(0)); // Minimum brightness setting after brightness 0 is 10 for ON command v.update(p(v, "1")); - assertThat(((HSBType) v.getValue()).getBrightness().intValue(), is(10)); + assertThat(((HSBType) v.getChannelState()).getBrightness().intValue(), is(10)); } @Test(expected = IllegalArgumentException.class) @@ -78,13 +78,13 @@ public void illegalColorUpdate() { @Test(expected = IllegalArgumentException.class) public void illegalNumberCommand() { - NumberValue v = new NumberValue(null, null, null, null, false); + NumberValue v = new NumberValue(null, null, null); v.update(OnOffType.OFF); } @Test(expected = IllegalArgumentException.class) public void illegalPercentCommand() { - NumberValue v = new NumberValue(null, null, null, null, false); + PercentageValue v = new PercentageValue(null, null, null); v.update(OnOffType.OFF); } @@ -96,7 +96,7 @@ public void illegalOnOffCommand() { @Test(expected = IllegalArgumentException.class) public void illegalPercentUpdate() { - NumberValue v = new NumberValue(null, null, null, null, true); + PercentageValue v = new PercentageValue(null, null, null); v.update(new DecimalType(101.0)); } @@ -104,69 +104,81 @@ public void illegalPercentUpdate() { public void onoffUpdate() { OnOffValue v = new OnOffValue("fancyON", "fancyOff"); // Test with command - assertThat(v.update(OnOffType.OFF), is("fancyOff")); - assertThat(v.getValue(), is(OnOffType.OFF)); - assertThat(v.update(OnOffType.ON), is("fancyON")); - assertThat(v.getValue(), is(OnOffType.ON)); + v.update(OnOffType.OFF); + assertThat(v.getMQTTpublishValue(), is("fancyOff")); + assertThat(v.getChannelState(), is(OnOffType.OFF)); + v.update(OnOffType.ON); + assertThat(v.getMQTTpublishValue(), is("fancyON")); + assertThat(v.getChannelState(), is(OnOffType.ON)); // Test with string, representing the command - assertThat(v.update(new StringType("OFF")), is("fancyOff")); - assertThat(v.getValue(), is(OnOffType.OFF)); - assertThat(v.update(new StringType("ON")), is("fancyON")); - assertThat(v.getValue(), is(OnOffType.ON)); + v.update(new StringType("OFF")); + assertThat(v.getMQTTpublishValue(), is("fancyOff")); + assertThat(v.getChannelState(), is(OnOffType.OFF)); + v.update(new StringType("ON")); + assertThat(v.getMQTTpublishValue(), is("fancyON")); + assertThat(v.getChannelState(), is(OnOffType.ON)); // Test with custom string, setup in the constructor - assertThat(v.update(new StringType("fancyOff")), is("fancyOff")); - assertThat(v.getValue(), is(OnOffType.OFF)); - assertThat(v.update(new StringType("fancyON")), is("fancyON")); - assertThat(v.getValue(), is(OnOffType.ON)); + v.update(new StringType("fancyOff")); + assertThat(v.getMQTTpublishValue(), is("fancyOff")); + assertThat(v.getChannelState(), is(OnOffType.OFF)); + v.update(new StringType("fancyON")); + assertThat(v.getMQTTpublishValue(), is("fancyON")); + assertThat(v.getChannelState(), is(OnOffType.ON)); } @Test public void openCloseUpdate() { OpenCloseValue v = new OpenCloseValue("fancyON", "fancyOff"); // Test with command - assertThat(v.update(OpenClosedType.CLOSED), is("fancyOff")); - assertThat(v.getValue(), is(OpenClosedType.CLOSED)); - assertThat(v.update(OpenClosedType.OPEN), is("fancyON")); - assertThat(v.getValue(), is(OpenClosedType.OPEN)); + v.update(OpenClosedType.CLOSED); + assertThat(v.getMQTTpublishValue(), is("fancyOff")); + assertThat(v.getChannelState(), is(OpenClosedType.CLOSED)); + v.update(OpenClosedType.OPEN); + assertThat(v.getMQTTpublishValue(), is("fancyON")); + assertThat(v.getChannelState(), is(OpenClosedType.OPEN)); // Test with string, representing the command - assertThat(v.update(new StringType("CLOSED")), is("fancyOff")); - assertThat(v.getValue(), is(OpenClosedType.CLOSED)); - assertThat(v.update(new StringType("OPEN")), is("fancyON")); - assertThat(v.getValue(), is(OpenClosedType.OPEN)); + v.update(new StringType("CLOSED")); + assertThat(v.getMQTTpublishValue(), is("fancyOff")); + assertThat(v.getChannelState(), is(OpenClosedType.CLOSED)); + v.update(new StringType("OPEN")); + assertThat(v.getMQTTpublishValue(), is("fancyON")); + assertThat(v.getChannelState(), is(OpenClosedType.OPEN)); // Test with custom string, setup in the constructor - assertThat(v.update(new StringType("fancyOff")), is("fancyOff")); - assertThat(v.getValue(), is(OpenClosedType.CLOSED)); - assertThat(v.update(new StringType("fancyON")), is("fancyON")); - assertThat(v.getValue(), is(OpenClosedType.OPEN)); + v.update(new StringType("fancyOff")); + assertThat(v.getMQTTpublishValue(), is("fancyOff")); + assertThat(v.getChannelState(), is(OpenClosedType.CLOSED)); + v.update(new StringType("fancyON")); + assertThat(v.getMQTTpublishValue(), is("fancyON")); + assertThat(v.getChannelState(), is(OpenClosedType.OPEN)); } @Test public void percentCalc() { - NumberValue v = new NumberValue(true, new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0), true); + PercentageValue v = new PercentageValue(new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0)); v.update(new DecimalType(110.0)); - assertThat((PercentType) v.getValue(), is(new PercentType(100))); + assertThat((PercentType) v.getChannelState(), is(new PercentType(100))); v.update(new DecimalType(10.0)); - assertThat((PercentType) v.getValue(), is(new PercentType(0))); + assertThat((PercentType) v.getChannelState(), is(new PercentType(0))); } @Test public void decimalCalc() { - NumberValue v = new NumberValue(true, new BigDecimal(0.1), new BigDecimal(1.0), new BigDecimal(0.1), true); + PercentageValue v = new PercentageValue(new BigDecimal(0.1), new BigDecimal(1.0), new BigDecimal(0.1)); v.update(new DecimalType(1.0)); - assertThat((PercentType) v.getValue(), is(new PercentType(100))); + assertThat((PercentType) v.getChannelState(), is(new PercentType(100))); v.update(new DecimalType(0.1)); - assertThat((PercentType) v.getValue(), is(new PercentType(0))); + assertThat((PercentType) v.getChannelState(), is(new PercentType(0))); v.update(new DecimalType(0.2)); - assertEquals(((PercentType) v.getValue()).floatValue(), 11.11f, 0.01f); + assertEquals(((PercentType) v.getChannelState()).floatValue(), 11.11f, 0.01f); } @Test(expected = IllegalArgumentException.class) public void percentCalcInvalid() { - NumberValue v = new NumberValue(true, new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0), true); + PercentageValue v = new PercentageValue(new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0)); v.update(new DecimalType(9.0)); } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/number-channel-config.xml b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/number-channel-config.xml index 08105edaf92..591122c0ee0 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/number-channel-config.xml +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/number-channel-config.xml @@ -4,7 +4,7 @@ xmlns:config-description="http://eclipse.org/smarthome/schemas/config-description/v1.0.0" xsi:schemaLocation="http://eclipse.org/smarthome/schemas/config-description/v1.0.0 http://eclipse.org/smarthome/schemas/config-description-1.0.0.xsd"> - + An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less then and will publish values non-retained. @@ -30,16 +30,19 @@ false true + + + + This configuration represents the minimum of the allowed range. For a percentage channel that equals zero percent. + + + + This configuration represents the maximum of the allowed range. For a percentage channel that equals one-hundred percent. + - A number channel can receive Increase/Decrease commands and computes the target number by adding or subtracting this delta value. - 10.0 - true - - - - If enabled, the value will be published to the MQTT broker including the fractional part of the number and a dot as the decimal marker. - false + A number/dimmer channel can receive Increase/Decrease commands and computes the target number by adding or subtracting this delta value. + 1.0 true diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/string-channel-config.xml b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/string-channel-config.xml new file mode 100644 index 00000000000..05b942f88f2 --- /dev/null +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/string-channel-config.xml @@ -0,0 +1,46 @@ + + + + + + + An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. + + + + An MQTT topic that this thing will send a command to. If not set, this will be a read-only channel. + + + + Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. Any supported transformation service can be used. + true + + + + Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". + true + %s + + + + The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. + false + true + + + + If the received MQTT value should not only update the state of linked items, but command them, enable this option. + false + true + + + + + If your MQTT topic is limited to a set of one or more specific commands or specific states, define those states here. Separate multiple states with commas. An example for a light bulb state set: ON,DIMMED,OFF + true + + + diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/switch-channel-config.xml b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/switch-channel-config.xml new file mode 100644 index 00000000000..778f32f07fc --- /dev/null +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/switch-channel-config.xml @@ -0,0 +1,53 @@ + + + + + + + An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. + + + + An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. + + + + Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. Any supported transformation service can be used. + true + + + + Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". + true + %s + + + + The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. + false + true + + + + If the received MQTT value should not only update the state of linked items, but command them, enable this option. + false + true + + + + + A number (like 1, 10) or a string (like "enabled") that is recognised as on/open state. You can use this parameter for a second keyword, next to ON (OPEN respectively on a Contact). + 1 + true + + + + A number (like 0, -10) or a string (like "disabled") that is recognised as off/closed state. You can use this parameter for a second keyword, next to OFF (CLOSED respectively on a Contact). + 0 + true + + + diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/thing/channels.xml b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/thing/channels.xml index 16d067ed63a..75aa2706e8a 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/thing/channels.xml +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/thing/channels.xml @@ -7,314 +7,67 @@ String - - - - An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. - - - - An MQTT topic that this thing will send a command to. If not set, this will be a read-only channel. - - - - Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. Any supported transformation service can be used. - true - - - - Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". - true - %s - - - - The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. - false - true - - - - If the received MQTT value should not only update the state of linked items, but command them, enable this option. - false - true - - - - - If your MQTT topic is limited to a set of one or more specific commands or specific states, define those states here. Separate multiple states with commas. An example for a light bulb state set: ON,DIMMED,OFF - true - - + + + DateTime + + Current date and/or time + + + + + Image + + An image to display. Send a binary bmp, jpg, png or any other supported format to this channel. + + + + + + Location + + GPS coordinates as Latitude,Longitude,Altitude + + + Number - + Dimmer - - - - An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. - - - - An MQTT topic that this thing will send a command to. If not set, this will be a read-only channel. - - - - Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. Any supported transformation service can be used. - true - - - - Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". - true - %s - - - - The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. - false - true - - - - If the received MQTT value should not only update the state of linked items, but command them, enable this option. - false - true - - - - - The received number needs to be converted to a percentage. This configuration represents the minimum of the allowed range that equals zero percent. - 0.0 - - - - The received number needs to be converted to a percentage. This configuration represents the maximum of the allowed range that equals one-hundred percent. - 100.0 - - - - A dimmer channel can receive Increase/Decrease commands and computes the target percentage by adding or subtracting this delta percentage. - 10.0 - true - - - - If enabled, the value will be published to the MQTT broker including the fractional part of the number and a dot as the decimal marker. - false - true - - + Switch - - - - An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel. - - - - An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch. - - - - Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. Any supported transformation service can be used. - true - - - - Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". - true - %s - - - - The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. - false - true - - - - If the received MQTT value should not only update the state of linked items, but command them, enable this option. - false - true - - - - - A number (like 1, 10) or a string (like enabled) that is recognised as on state. "ON" (case insensitive) will always be recognised. You can use this parameter for a second keyword. - 1 - true - - - - A number (like 0, -10) or a string (like disabled) that is recognised as off state. "OFF" (case insensitive) will always be recognised. You can use this parameter for a second keyword. - 0 - true - - + Contact - - - - An MQTT topic that this thing will subscribe to, to receive the state. - - - - Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. Any supported transformation service can be used. - true - - - - Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". - true - %s - - - - The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. - false - true - - - - If the received MQTT value should not only update the state of linked items, but command them, enable this option. - false - true - - - - - A number (like 1, 10) or a string (like "positionUp") that is recognised as open state. "OPEN" (case insensitive) will always be recognised. You can use this parameter for a second keyword. - 1 - true - - - - A number (like 0, -10) or a string (like "positionDown") that is recognised as close state. "CLOSED" (case insensitive) will always be recognised. You can use this parameter for a second keyword. - 0 - true - - + Color - - - - An MQTT topic that this thing will subscribe to, to receive the state. - - - - An MQTT topic that this thing will send a command to. If not set, this will be a read-only channel. - - - - Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.color.rgb" for a json {device: {color: { rgb: "12,43,112" }}}. Any supported transformation service can be used. - true - - - - Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". - true - %s - - - - The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. - false - true - - - - If the received MQTT value should not only update the state of linked items, but command them, enable this option. - false - true - - - - - A number (like 1, 10) or a string (like ON) that is recognised as on state. - ON - true - - - - A number (like 0, -10) or a string (like OFF) that is recognised as off state. - OFF - true - - + Color - - - - An MQTT topic that this thing will subscribe to, to receive the state. - - - - An MQTT topic that this thing will send a command to. If not set, this will be a read-only channel. - - - - Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.color.hsb" for a json {device: {color: { hsb: "12,43,112" }}}. Any supported transformation service can be used. - true - - - - Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f". - true - %s - - - - The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time. - false - true - - - - If the received MQTT value should not only update the state of linked items, but command them, enable this option. - false - true - - - - - A number (like 1, 10) or a string (like ON) that is recognised as on state. - ON - true - - - - A number (like 0, -10) or a string (like OFF) that is recognised as off state. - OFF - true - - + diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/README.md b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/README.md index 63bc7ebfeaa..524ed73fc0f 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/README.md +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/README.md @@ -49,6 +49,9 @@ You can manually add the following channels: * **switch**: This channel represents a on/off state of a given topic and can send an on/off value to a given topic. * **colorRGB**: This channel handles color values in RGB format. * **colorHSB**: This channel handles color values in HSB format. +* **location**: This channel handles a location. +* **image**: This channel handles binary images in common java supported formats (bmp,jpg,png). +* **datetime**: This channel handles date/time values. ## Thing and Channel configuration @@ -68,13 +71,13 @@ All things require a configured broker. You can connect this channel to a String item. ### Channel Type "number" - -* __min__: An optional minimum value -* __max__: An optional maximum value + +* __min__: An optional minimum value. +* __max__: An optional maximum value. * __step__: For decrease, increase commands the step needs to be known -* __isDecimal__: If set to true the value is send as a decimal value, otherwise it is send as integer. -If any of the parameters is a float/double (has a decimal point value), then a float value is send to the MQTT topic otherwise an int value is send. +A decimal value (like 0.2) is send to the MQTT topic if the number has a fractional part. +If you always require an integer, please use the formatter. You can connect this channel to a Number item. @@ -86,6 +89,8 @@ You can connect this channel to a Number item. The value is internally stored as a percentage for a value between **min** and **max**. +The channel will publish a value between 0 and 100. + You can connect this channel to a Rollershutter or Dimmer item. ### Channel Type "contact", "switch" @@ -109,6 +114,31 @@ e.g. "112,54,123" for an RGB channel (0-255 per component) and "360,100,100" for The channel expects values on the corresponding MQTT topic to be in this format as well. +### Channel Type "location" + +You can connect this channel to a Location item. + +The channel will publish the location as comma separated list to the MQTT broker, +e.g. "112,54,123" for latitude, longitude, altitude. The altitude is optional. + +The channel expects values on the corresponding MQTT topic to be in this format as well. + +### Channel Type "image" + +You can connect this channel to an Image item. This is a read-only channel. + +The channel expects values on the corresponding MQTT topic to contain the binary +data of a bmp, jpg, png or any other format that the installed java runtime supports. + +### Channel Type "datetime" + +You can connect this channel to a DateTime item. + +The channel will publish the date/time in the format "yyyy-MM-dd'T'HH:mm" +for example 2018-01-01T12:14:00. If you require another format, please use the formatter. + +The channel expects values on the corresponding MQTT topic to be in this format as well. + ## Limitations * This binding does not support Homie Node Instances. diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/MqttBindingConstants.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/MqttBindingConstants.java index fa4f2438a79..ac2045c0701 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/MqttBindingConstants.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/MqttBindingConstants.java @@ -35,11 +35,13 @@ public class MqttBindingConstants { public static final String COLOR_RGB = "colorRGB"; public static final String COLOR_HSB = "colorHSB"; public static final String CONTACT = "contact"; - public static final String DATETIME = "dateTime"; public static final String DIMMER = "dimmer"; public static final String NUMBER = "number"; public static final String STRING = "string"; public static final String SWITCH = "switch"; + public static final String IMAGE = "image"; + public static final String LOCATION = "location"; + public static final String DATETIME = "datetime"; public static final String CONFIG_HA_CHANNEL = "mqtt:ha_channel"; public static final String CONFIG_HOMIE_CHANNEL = "mqtt:homie_channel"; diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homeassistant/AbstractComponent.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homeassistant/AbstractComponent.java index 3ee94740970..dc684f7fc8d 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homeassistant/AbstractComponent.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homeassistant/AbstractComponent.java @@ -174,7 +174,7 @@ public ChannelGroupType type() { * to the MQTT broker got lost. */ public void resetState() { - channels.values().forEach(c -> c.channelState.getValue().resetState()); + channels.values().forEach(c -> c.channelState.getCache().resetState()); } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homie300/Property.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homie300/Property.java index 4626b4b8f77..cae7c0a2205 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homie300/Property.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homie300/Property.java @@ -135,7 +135,7 @@ private ChannelType createChannelType(PropertyAttributes attributes, ChannelStat return ChannelTypeBuilder.state(channelTypeUID, attributes.name, channelState.getItemType()) .withConfigDescriptionURI(URI.create(MqttBindingConstants.CONFIG_HOMIE_CHANNEL)) .withStateDescription( - channelState.getValue().createStateDescription(attributes.unit, !attributes.settable)) + channelState.getCache().createStateDescription(attributes.unit, !attributes.settable)) .build(); } else { if (attributes.datatype.equals(DataTypeEnum.enum_)) { @@ -159,6 +159,7 @@ public void createChannelFromAttribute() { final String stateTopic = topic; Value value; + Boolean isDecimal = null; switch (attributes.datatype) { case boolean_: value = new OnOffValue("true", "false"); @@ -172,7 +173,7 @@ public void createChannelFromAttribute() { break; case float_: case integer_: - boolean isDecimal = attributes.datatype == DataTypeEnum.float_; + isDecimal = attributes.datatype == DataTypeEnum.float_; String s[] = attributes.format.split("\\:"); BigDecimal min = s.length == 2 ? convertFromString(s[0]) : null; BigDecimal max = s.length == 2 ? convertFromString(s[1]) : null; @@ -183,7 +184,7 @@ public void createChannelFromAttribute() { step = new BigDecimal(1); } - value = new NumberValue(isDecimal, min, max, step, false); + value = new NumberValue(min, max, step); break; case string_: case unknown: @@ -195,6 +196,10 @@ public void createChannelFromAttribute() { ChannelConfigBuilder b = ChannelConfigBuilder.create().makeTrigger(!attributes.retained) .withStateTopic(stateTopic); + if (isDecimal != null && !isDecimal) { + b = b.withFormatter("%d"); // Apply formatter to only publish integers + } + if (attributes.settable) { b = b.withCommandTopic(commandTopic); } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfig.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfig.java index 76655d2d2b3..2f36b488b52 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfig.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfig.java @@ -47,7 +47,6 @@ public class ChannelConfig { public @Nullable BigDecimal min; public @Nullable BigDecimal max; public @Nullable BigDecimal step; - public boolean isDecimal = false; public @Nullable String on; public @Nullable String off; } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfigBuilder.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfigBuilder.java index 0053ef1ebad..a1ed58ba30e 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfigBuilder.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfigBuilder.java @@ -40,6 +40,11 @@ public ChannelConfig build() { return config; } + public ChannelConfigBuilder withFormatter(String formatter) { + config.formatBeforePublish = formatter; + return this; + } + public ChannelConfigBuilder withStateTopic(@Nullable String topic) { if (topic != null) { config.stateTopic = topic; diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelState.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelState.java index 793e6cda061..b691c109678 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelState.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelState.java @@ -28,7 +28,6 @@ import org.eclipse.smarthome.binding.mqtt.generic.internal.values.Value; import org.eclipse.smarthome.core.thing.ChannelUID; import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.TypeParser; import org.eclipse.smarthome.io.transport.mqtt.MqttBrokerConnection; import org.eclipse.smarthome.io.transport.mqtt.MqttMessageSubscriber; @@ -51,7 +50,7 @@ public class ChannelState implements MqttMessageSubscriber { protected final ChannelConfig config; /** Channel value **/ - protected final Value value; + protected final Value cachedValue; // Runtime variables @@ -67,15 +66,16 @@ public class ChannelState implements MqttMessageSubscriber { * * @param config The channel configuration * @param channelUID The channelUID is used for the {@link ChannelStateUpdateListener} to notify about value changes - * @param value The channel state value. + * @param cachedValue MQTT only notifies us once about a value, during the subscribe. The channel state therefore + * needs a cache for the current value. * @param channelStateUpdateListener A channel state update listener */ - public ChannelState(ChannelConfig config, ChannelUID channelUID, Value value, + public ChannelState(ChannelConfig config, ChannelUID channelUID, Value cachedValue, @Nullable ChannelStateUpdateListener channelStateUpdateListener) { this.config = config; this.channelStateUpdateListener = channelStateUpdateListener; this.channelUID = channelUID; - this.value = value; + this.cachedValue = cachedValue; this.readOnly = StringUtils.isBlank(config.commandTopic); } @@ -101,10 +101,15 @@ public void clearTransformations() { } /** - * Returns the value state object of this message subscriber. + * Returns the cached value state object of this message subscriber. + *

+ * MQTT only notifies us once about a value, during the subscribe. + * The channel state therefore needs a cache for the current value. + * If MQTT has not yet published a value, the cache might still be in UNDEF state. + *

*/ - public Value getValue() { - return value; + public Value getCache() { + return cachedValue; } /** @@ -118,9 +123,7 @@ public ChannelUID channelUID() { * Incoming message from the MqttBrokerConnection * * @param topic The topic. Is the same as the field stateTopic. - * @param payload The byte payload. Must be UTF8 encoded text. - * Some clients may decide to encode their own binary number or struct types. - * We do not and cannot support those here though. + * @param payload The byte payload. Must be UTF8 encoded text or binary data. */ @Override public void processMessage(String topic, byte[] payload) { @@ -130,33 +133,49 @@ public void processMessage(String topic, byte[] payload) { return; } - // Apply transformations + if (cachedValue.isBinary()) { + cachedValue.update(payload); + channelStateUpdateListener.updateChannelState(channelUID, cachedValue.getChannelState()); + receivedOrTimeout(); + return; + } + + // String value: Apply transformations String strvalue = new String(payload, StandardCharsets.UTF_8); for (ChannelStateTransformation t : transformations) { strvalue = t.processValue(strvalue); } + // Is trigger?: Special handling if (config.trigger) { channelStateUpdateListener.triggerChannel(channelUID, strvalue); - } else { - try { - Command command = TypeParser.parseCommand(value.getSupportedCommandTypes(), strvalue); - if (command == null) { - throw new IllegalArgumentException("TypeParser failed"); - } - value.update(command); - final State updatedState = value.getValue(); - if (config.postCommand) { - channelStateUpdateListener.postChannelState(channelUID, (Command) updatedState); - } else { - channelStateUpdateListener.updateChannelState(channelUID, updatedState); - } - } catch (IllegalArgumentException e) { - logger.warn("Incoming payload '{}' not supported by type '{}': {}", strvalue, - value.getClass().getSimpleName(), e.getMessage()); - } + receivedOrTimeout(); + return; } + Command command = TypeParser.parseCommand(cachedValue.getSupportedCommandTypes(), strvalue); + if (command == null) { + logger.warn("Incoming payload '{}' not supported by type '{}'", strvalue, + cachedValue.getClass().getSimpleName()); + receivedOrTimeout(); + return; + } + + // Map the string to an ESH command, update the cached value and post the command to the framework + try { + cachedValue.update(command); + } catch (IllegalArgumentException | IllegalStateException e) { + logger.warn("Command '{}' not supported by type '{}': {}", strvalue, cachedValue.getClass().getSimpleName(), + e.getMessage()); + receivedOrTimeout(); + return; + } + + if (config.postCommand) { + channelStateUpdateListener.postChannelState(channelUID, (Command) cachedValue.getChannelState()); + } else { + channelStateUpdateListener.updateChannelState(channelUID, cachedValue.getChannelState()); + } receivedOrTimeout(); } @@ -178,7 +197,7 @@ public String getCommandTopic() { * Returns the channelType ID which also happens to be an item-type */ public String getItemType() { - return value.getItemType(); + return cachedValue.getItemType(); } /** @@ -208,7 +227,7 @@ private void internalStop() { this.connection = null; this.channelStateUpdateListener = null; hasSubscribed = false; - value.resetState(); + cachedValue.resetState(); } private void receivedOrTimeout() { @@ -279,8 +298,10 @@ public boolean hasSubscribed() { * @param command The command to send * @return A future that completes with true if the publishing worked and false and/or exceptionally otherwise. */ - public CompletableFuture<@Nullable Void> setValue(Command command) { - String mqttCommandValue = getValue().update(command); + public CompletableFuture<@Nullable Void> publishValue(Command command) { + cachedValue.update(command); + + String mqttCommandValue = cachedValue.getMQTTpublishValue(); final MqttBrokerConnection connection = this.connection; diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformation.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformation.java index 74fb66e8223..2c61c3c92f5 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformation.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformation.java @@ -45,7 +45,7 @@ public class ChannelStateTransformation { * temperature: 23.2 }}}. * @param channelUID The channel UID, used in processMessage() to inform the respective channel * of an update. - * @param value A value object + * @param cachedValue A value object * @param provider The transformation service provider */ public ChannelStateTransformation(String pattern, TransformationServiceProvider provider) { diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/AbstractMQTTThingHandler.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/AbstractMQTTThingHandler.java index c2b9208b6d0..49156f51310 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/AbstractMQTTThingHandler.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/AbstractMQTTThingHandler.java @@ -112,11 +112,11 @@ public void handleCommand(ChannelUID channelUID, Command command) { } if (command instanceof RefreshType || data.isReadOnly()) { - updateState(channelUID.getId(), data.getValue().getValue()); + updateState(channelUID.getId(), data.getCache().getChannelState()); return; } - final CompletableFuture<@Nullable Void> future = data.setValue(command); + final CompletableFuture<@Nullable Void> future = data.publishValue(command); future.exceptionally(e -> { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); return null; diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandler.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandler.java index ed79ecfa529..eda7371282e 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandler.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandler.java @@ -94,7 +94,7 @@ public GenericThingHandler(Thing thing, MqttChannelStateDescriptionProvider stat @Override protected void stop() { - channelStateByChannelUID.values().forEach(c -> c.getValue().resetState()); + channelStateByChannelUID.values().forEach(c -> c.getCache().resetState()); } @Override diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ColorValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ColorValue.java index 9e94628eaaa..24c706ad0f7 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ColorValue.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ColorValue.java @@ -12,8 +12,7 @@ */ package org.eclipse.smarthome.binding.mqtt.generic.internal.values; -import java.util.Collections; -import java.util.List; +import java.math.BigDecimal; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -22,32 +21,28 @@ import org.eclipse.smarthome.core.library.CoreItemFactory; import org.eclipse.smarthome.core.library.types.HSBType; import org.eclipse.smarthome.core.library.types.OnOffType; -import org.eclipse.smarthome.core.library.types.OpenClosedType; import org.eclipse.smarthome.core.library.types.PercentType; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.State; -import org.eclipse.smarthome.core.types.StateDescription; import org.eclipse.smarthome.core.types.UnDefType; /** * Implements a color value. * - * Accepts user updates from a HSBType, OnOffType or OpenClosedType. - * Accepts MQTT state updates as comma separated HSB ("h,s,b"), RGB ("r,g,b") and on, off strings. - * On, Off strings can be customized but "1","ON","0","OFF" are always recognized. + *

+ * Accepts user updates from a HSBType, OnOffType and StringType. + *

+ * Accepts MQTT state updates as OnOffType and a + * StringType with comma separated HSB ("h,s,b"), RGB ("r,g,b") and on, off strings. + * On, Off strings can be customized. * * @author David Graeff - Initial contribution */ @NonNullByDefault -public class ColorValue implements Value { - private State state = UnDefType.UNDEF; - private HSBType colorValue; +public class ColorValue extends Value { private final boolean isRGB; private final String onValue; private final String offValue; - private List> commandTypes = Stream.of(OnOffType.class, HSBType.class, StringType.class) - .collect(Collectors.toList()); /** * Creates a non initialized color value. @@ -56,32 +51,12 @@ public class ColorValue implements Value { * @param offValue The OFF value string. This will be compared to MQTT messages. */ public ColorValue(boolean isRGB, @Nullable String onValue, @Nullable String offValue) { + super(CoreItemFactory.COLOR, Stream.of(OnOffType.class, StringType.class).collect(Collectors.toList())); this.isRGB = isRGB; - colorValue = new HSBType(); this.onValue = onValue == null ? "ON" : onValue; this.offValue = offValue == null ? "OFF" : offValue; } - /** - * Create color type from string. - * - * @param colorTextValue Expects hue,saturation,brightness as comma separated string. hue is in the range [0,360], - * saturation and brightness are in [0,100]. - * @param onValue The ON value string. This will be compared to MQTT messages. - * @param offValue The OFF value string. This will be compared to MQTT messages. - */ - public ColorValue(boolean isRGB, String colorTextValue, @Nullable String onValue, @Nullable String offValue) { - this.isRGB = isRGB; - colorValue = new HSBType(colorTextValue); - this.onValue = onValue == null ? "ON" : onValue; - this.offValue = offValue == null ? "OFF" : offValue; - } - - @Override - public State getValue() { - return state; - } - /** * Updates the color value. * @@ -90,66 +65,54 @@ public State getValue() { * [0,255]. */ @Override - public String update(Command command) throws IllegalArgumentException { - if (command instanceof OnOffType) { + public void update(Command command) throws IllegalArgumentException { + HSBType oldvalue = (state == UnDefType.UNDEF) ? new HSBType() : (HSBType) state; + if (command instanceof HSBType) { + state = (HSBType) command; + } else if (command instanceof OnOffType) { OnOffType boolValue = ((OnOffType) command); - PercentType minOn = new PercentType(Math.max(colorValue.getBrightness().intValue(), 10)); - colorValue = new HSBType(colorValue.getHue(), colorValue.getSaturation(), - boolValue == OnOffType.ON ? minOn : new PercentType(0)); - } else if (command instanceof OpenClosedType) { - OnOffType boolValue = ((OpenClosedType) command) == OpenClosedType.OPEN ? OnOffType.ON : OnOffType.OFF; - PercentType minOn = new PercentType(Math.max(colorValue.getBrightness().intValue(), 10)); - colorValue = new HSBType(colorValue.getHue(), colorValue.getSaturation(), + PercentType minOn = new PercentType(Math.max(oldvalue.getBrightness().intValue(), 10)); + state = new HSBType(oldvalue.getHue(), oldvalue.getSaturation(), boolValue == OnOffType.ON ? minOn : new PercentType(0)); - } else if (command instanceof HSBType) { - colorValue = (HSBType) command; - } else if (command instanceof StringType) { + } else { final String updatedValue = command.toString(); if (onValue.equals(updatedValue)) { - PercentType minOn = new PercentType(Math.max(colorValue.getBrightness().intValue(), 10)); - colorValue = new HSBType(colorValue.getHue(), colorValue.getSaturation(), minOn); + PercentType minOn = new PercentType(Math.max(oldvalue.getBrightness().intValue(), 10)); + state = new HSBType(oldvalue.getHue(), oldvalue.getSaturation(), minOn); } else if (offValue.equals(updatedValue)) { - colorValue = new HSBType(colorValue.getHue(), colorValue.getSaturation(), new PercentType(0)); + state = new HSBType(oldvalue.getHue(), oldvalue.getSaturation(), new PercentType(0)); } else if (isRGB) { String[] split = updatedValue.split(","); if (split.length != 3) { throw new IllegalArgumentException(updatedValue + " is not a valid RGB syntax"); } - colorValue = HSBType.fromRGB(Integer.parseInt(split[0]), Integer.parseInt(split[1]), + state = HSBType.fromRGB(Integer.parseInt(split[0]), Integer.parseInt(split[1]), Integer.parseInt(split[2])); } else { - colorValue = new HSBType(updatedValue); - + state = new HSBType(updatedValue); } - } else { - throw new IllegalArgumentException("Didn't recognise the color value " + command.toString()); - } - state = colorValue; - if (isRGB) { - return colorValue.getRed() + "," + colorValue.getGreen() + "," + colorValue.getBlue(); - } else { - return colorValue.toString(); } } - @Override - public String getItemType() { - return CoreItemFactory.COLOR; - } + private static BigDecimal factor = new BigDecimal(2.5); @Override - public StateDescription createStateDescription(String unit, boolean readOnly) { - return new StateDescription(null, null, null, "%s " + unit.replace("%", "%%"), readOnly, - Collections.emptyList()); - } - - @Override - public List> getSupportedCommandTypes() { - return commandTypes; - } + public String getMQTTpublishValue() { + if (state == UnDefType.UNDEF) { + return ""; + } - @Override - public void resetState() { - state = UnDefType.UNDEF; + if (isRGB) { + PercentType[] rgb = ((HSBType) state).toRGB(); + StringBuilder b = new StringBuilder(); + b.append(rgb[0].toBigDecimal().multiply(factor).intValue()); + b.append(','); + b.append(rgb[1].toBigDecimal().multiply(factor).intValue()); + b.append(','); + b.append(rgb[2].toBigDecimal().multiply(factor).intValue()); + return b.toString(); + } else { + return state.toString(); + } } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/DateTimeValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/DateTimeValue.java new file mode 100644 index 00000000000..a68d2a5763a --- /dev/null +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/DateTimeValue.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.binding.mqtt.generic.internal.values; + +import java.time.format.DateTimeFormatter; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.CoreItemFactory; +import org.eclipse.smarthome.core.library.types.DateTimeType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.UnDefType; + +/** + * Implements a datetime value. + * + * @author David Graeff - Initial contribution + */ +@NonNullByDefault +public class DateTimeValue extends Value { + public DateTimeValue() { + super(CoreItemFactory.DATETIME, Stream.of(DateTimeType.class, StringType.class).collect(Collectors.toList())); + } + + @Override + public void update(Command command) throws IllegalArgumentException { + if (command instanceof DateTimeType) { + state = ((DateTimeType) command); + } else { + state = DateTimeType.valueOf(command.toString()); + } + } + + @Override + public String getMQTTpublishValue() { + if (state == UnDefType.UNDEF) { + return ""; + } + + return ((DateTimeType) state).getZonedDateTime().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } +} diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ImageValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ImageValue.java new file mode 100644 index 00000000000..b7409fa3094 --- /dev/null +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ImageValue.java @@ -0,0 +1,41 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.binding.mqtt.generic.internal.values; + +import java.util.Collections; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.CoreItemFactory; +import org.eclipse.smarthome.core.types.Command; + +/** + * Implements an image value. + * + * @author David Graeff - Initial contribution + */ +@NonNullByDefault +public class ImageValue extends Value { + public ImageValue() { + super(CoreItemFactory.IMAGE, Collections.emptyList()); + } + + @Override + public void update(Command command) throws IllegalArgumentException { + throw new IllegalArgumentException("Binary type. Command not allowed"); + } + + @Override + public boolean isBinary() { + return true; + } +} diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/LocationValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/LocationValue.java new file mode 100644 index 00000000000..20050186346 --- /dev/null +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/LocationValue.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.binding.mqtt.generic.internal.values; + +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.library.CoreItemFactory; +import org.eclipse.smarthome.core.library.types.PointType; +import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.types.Command; + +/** + * Implements a location value. + * + * @author David Graeff - Initial contribution + */ +@NonNullByDefault +public class LocationValue extends Value { + public LocationValue() { + super(CoreItemFactory.LOCATION, Stream.of(PointType.class, StringType.class).collect(Collectors.toList())); + } + + @Override + public void update(Command command) throws IllegalArgumentException { + if (command instanceof PointType) { + state = ((PointType) command); + } else { + state = PointType.valueOf(command.toString()); + } + } +} diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/NumberValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/NumberValue.java index a9aab6fb1a1..a5314b5f843 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/NumberValue.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/NumberValue.java @@ -14,7 +14,6 @@ import java.math.BigDecimal; import java.util.Collections; -import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -23,137 +22,52 @@ import org.eclipse.smarthome.core.library.CoreItemFactory; import org.eclipse.smarthome.core.library.types.DecimalType; import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; -import org.eclipse.smarthome.core.library.types.PercentType; -import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.library.types.UpDownType; import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.StateDescription; import org.eclipse.smarthome.core.types.UnDefType; /** - * Implements a percentage value. Minimum and maximum are definable. + * Implements a number value. + * + *

+ * Accepts user updates and MQTT state updates from a DecimalType, IncreaseDecreaseType and UpDownType. + *

* * @author David Graeff - Initial contribution */ @NonNullByDefault -public class NumberValue implements Value { - private State state = UnDefType.UNDEF; - private final double min; - private final double max; - private final double step; - private final Boolean isDecimal; - private final boolean isPercent; - private List> commandTypes = Stream - .of(DecimalType.class, IncreaseDecreaseType.class, UpDownType.class).collect(Collectors.toList()); - - private DecimalType numberValue; - - public NumberValue(@Nullable Boolean isDecimal, @Nullable BigDecimal min, @Nullable BigDecimal max, - @Nullable BigDecimal step, boolean isPercent) { - this.isDecimal = isDecimal == null ? false : isDecimal; - this.min = min == null ? 0.0 : min.doubleValue(); - this.max = max == null ? 100.0 : max.doubleValue(); - if (isPercent && this.min >= this.max) { - throw new IllegalArgumentException("Min need to be smaller than max!"); - } - this.step = step == null ? 1.0 : step.doubleValue(); - this.isPercent = isPercent; - numberValue = new DecimalType(); - } +public class NumberValue extends Value { + private final @Nullable BigDecimal min; + private final @Nullable BigDecimal max; + private final BigDecimal step; - @Override - public State getValue() { - return state; + public NumberValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Nullable BigDecimal step) { + super(CoreItemFactory.NUMBER, Stream.of(DecimalType.class, IncreaseDecreaseType.class, UpDownType.class) + .collect(Collectors.toList())); + this.min = min; + this.max = max; + this.step = step == null ? new BigDecimal(1.0) : step; } @Override - public String update(Command command) throws IllegalArgumentException { - if (isPercent) { - if (command instanceof DecimalType) { - double v = ((DecimalType) command).doubleValue(); - v = (v - min) * 100.0 / (max - min); - numberValue = new PercentType(new BigDecimal(v)); - } else if (command instanceof IncreaseDecreaseType) { - if (((IncreaseDecreaseType) command) == IncreaseDecreaseType.INCREASE) { - final double v = numberValue.doubleValue() + step; - numberValue = new PercentType(new BigDecimal(v <= max ? v : max)); - } else { - double v = numberValue.doubleValue() - step; - numberValue = new PercentType(new BigDecimal(v >= min ? v : min)); - } - } else if (command instanceof UpDownType) { - if (((UpDownType) command) == UpDownType.UP) { - final double v = numberValue.doubleValue() + step; - numberValue = new PercentType(new BigDecimal(v <= max ? v : max)); - } else { - final double v = numberValue.doubleValue() - step; - numberValue = new PercentType(new BigDecimal(v >= min ? v : min)); - } + public void update(Command command) throws IllegalArgumentException { + DecimalType oldvalue = (state == UnDefType.UNDEF) ? new DecimalType() : (DecimalType) state; + if (command instanceof DecimalType) { + state = (DecimalType) command; + } else if (command instanceof IncreaseDecreaseType || command instanceof UpDownType) { + if (command == IncreaseDecreaseType.INCREASE || command == UpDownType.UP) { + state = new DecimalType(oldvalue.toBigDecimal().add(step)); } else { - throw new IllegalArgumentException( - "Type " + command.getClass().getName() + " not supported for PercentValue"); - } - - if (isDecimal) { - state = numberValue; - return numberValue.toString(); - } else { - state = numberValue; - return String.valueOf(numberValue.intValue()); + state = new DecimalType(oldvalue.toBigDecimal().subtract(step)); } } else { - if (command instanceof DecimalType) { - numberValue = (DecimalType) command; - } else if (command instanceof IncreaseDecreaseType) { - double v; - if (((IncreaseDecreaseType) command) == IncreaseDecreaseType.INCREASE) { - v = numberValue.doubleValue() + step; - } else { - v = numberValue.doubleValue() - step; - } - numberValue = new DecimalType(v); - } else if (command instanceof UpDownType) { - double v; - if (((UpDownType) command) == UpDownType.UP) { - v = numberValue.doubleValue() + step; - } else { - v = numberValue.doubleValue() - step; - } - numberValue = new DecimalType(v); - } else { - throw new IllegalArgumentException( - "Type " + command.getClass().getName() + " not supported for NumberValue"); - } - - if (isDecimal) { - state = numberValue; - return numberValue.toString(); - } else { - state = numberValue; - return String.valueOf(numberValue.intValue()); - } + state = DecimalType.valueOf(command.toString()); } } - @Override - public String getItemType() { - return isPercent ? CoreItemFactory.DIMMER : CoreItemFactory.NUMBER; - } - @Override public StateDescription createStateDescription(String unit, boolean readOnly) { - return new StateDescription(new BigDecimal(min), new BigDecimal(max), new BigDecimal(step), - "%s " + unit.replace("%", "%%"), readOnly, Collections.emptyList()); - } - - @Override - public List> getSupportedCommandTypes() { - return commandTypes; - } - - @Override - public void resetState() { - state = UnDefType.UNDEF; + return new StateDescription(min, max, step, "%s " + unit.replace("%", "%%"), readOnly, Collections.emptyList()); } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/OnOffValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/OnOffValue.java index 72c8293d5a5..c5cee9b5a7f 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/OnOffValue.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/OnOffValue.java @@ -12,8 +12,6 @@ */ package org.eclipse.smarthome.binding.mqtt.generic.internal.values; -import java.util.Collections; -import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -23,9 +21,6 @@ import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.State; -import org.eclipse.smarthome.core.types.StateDescription; -import org.eclipse.smarthome.core.types.UnDefType; /** * Implements an on/off boolean value. @@ -33,21 +28,17 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class OnOffValue implements Value { - private State state = UnDefType.UNDEF; - private OnOffType onOffValue; +public class OnOffValue extends Value { private final String onString; private final String offString; - private List> commandTypes = Stream.of(OnOffType.class, StringType.class) - .collect(Collectors.toList()); /** * Creates a switch On/Off type, that accepts "ON", "1" for on and "OFF","0" for off. */ public OnOffValue() { + super(CoreItemFactory.SWITCH, Stream.of(OnOffType.class, StringType.class).collect(Collectors.toList())); this.onString = OnOffType.ON.name(); this.offString = OnOffType.OFF.name(); - this.onOffValue = OnOffType.OFF; } /** @@ -57,58 +48,29 @@ public OnOffValue() { * @param offValue The OFF value string. This will be compared to MQTT messages. */ public OnOffValue(@Nullable String onValue, @Nullable String offValue) { + super(CoreItemFactory.SWITCH, Stream.of(OnOffType.class, StringType.class).collect(Collectors.toList())); this.onString = onValue == null ? OnOffType.ON.name() : onValue; this.offString = offValue == null ? OnOffType.OFF.name() : offValue; - this.onOffValue = OnOffType.OFF; } @Override - public State getValue() { - return state; - } - - @Override - public String update(Command command) throws IllegalArgumentException { + public void update(Command command) throws IllegalArgumentException { if (command instanceof OnOffType) { - onOffValue = ((OnOffType) command); - } else if (command instanceof StringType) { + state = (OnOffType) command; + } else { final String updatedValue = command.toString(); - final String upperCase = updatedValue.toUpperCase(); - if (onString.equals(updatedValue) || OnOffType.ON.name().equals(upperCase)) { - onOffValue = OnOffType.ON; - } else if (offString.equals(updatedValue) || OnOffType.OFF.name().equals(upperCase)) { - onOffValue = OnOffType.OFF; + if (onString.equals(updatedValue)) { + state = OnOffType.ON; + } else if (offString.equals(updatedValue)) { + state = OnOffType.OFF; } else { - throw new IllegalArgumentException("Didn't recognise the on/off value " + updatedValue); + state = OnOffType.valueOf(updatedValue); } - - } else { - throw new IllegalArgumentException( - "Type " + command.getClass().getName() + " not supported for OnOffValue"); } - - state = onOffValue; - return (onOffValue == OnOffType.ON) ? onString : offString; - } - - @Override - public String getItemType() { - return CoreItemFactory.SWITCH; - } - - @Override - public StateDescription createStateDescription(String unit, boolean readOnly) { - return new StateDescription(null, null, null, "%s " + unit.replace("%", "%%"), readOnly, - Collections.emptyList()); - } - - @Override - public List> getSupportedCommandTypes() { - return commandTypes; } @Override - public void resetState() { - state = UnDefType.UNDEF; + public String getMQTTpublishValue() { + return (state == OnOffType.ON) ? onString : offString; } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/OpenCloseValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/OpenCloseValue.java index 13a1404d309..38ad67e3f12 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/OpenCloseValue.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/OpenCloseValue.java @@ -12,8 +12,6 @@ */ package org.eclipse.smarthome.binding.mqtt.generic.internal.values; -import java.util.Collections; -import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -23,9 +21,6 @@ import org.eclipse.smarthome.core.library.types.OpenClosedType; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.State; -import org.eclipse.smarthome.core.types.StateDescription; -import org.eclipse.smarthome.core.types.UnDefType; /** * Implements an open/close boolean value. @@ -33,21 +28,17 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class OpenCloseValue implements Value { - private State state = UnDefType.UNDEF; - private OpenClosedType boolValue; +public class OpenCloseValue extends Value { private final String openString; private final String closeString; - private List> commandTypes = Stream.of(OpenClosedType.class, StringType.class) - .collect(Collectors.toList()); /** * Creates a contact Open/Close type. */ public OpenCloseValue() { + super(CoreItemFactory.CONTACT, Stream.of(OpenClosedType.class, StringType.class).collect(Collectors.toList())); this.openString = OpenClosedType.OPEN.name(); this.closeString = OpenClosedType.CLOSED.name(); - this.boolValue = OpenClosedType.CLOSED; } /** @@ -57,57 +48,29 @@ public OpenCloseValue() { * @param closeValue The OFF value string. This will be compared to MQTT messages. */ public OpenCloseValue(@Nullable String openValue, @Nullable String closeValue) { + super(CoreItemFactory.CONTACT, Stream.of(OpenClosedType.class, StringType.class).collect(Collectors.toList())); this.openString = openValue == null ? OpenClosedType.OPEN.name() : openValue; this.closeString = closeValue == null ? OpenClosedType.CLOSED.name() : closeValue; - this.boolValue = OpenClosedType.CLOSED; } @Override - public State getValue() { - return state; - } - - @Override - public String update(Command command) throws IllegalArgumentException { + public void update(Command command) throws IllegalArgumentException { if (command instanceof OpenClosedType) { - boolValue = ((OpenClosedType) command); - } else if (command instanceof StringType) { - final String updatedValue = ((StringType) command).toString(); - final String upperCase = updatedValue.toUpperCase(); - if (openString.equals(updatedValue) || OpenClosedType.OPEN.name().equals(upperCase)) { - boolValue = OpenClosedType.OPEN; - } else if (closeString.equals(updatedValue) || OpenClosedType.CLOSED.name().equals(upperCase)) { - boolValue = OpenClosedType.CLOSED; + state = (OpenClosedType) command; + } else { + final String updatedValue = command.toString(); + if (openString.equals(updatedValue)) { + state = OpenClosedType.OPEN; + } else if (closeString.equals(updatedValue)) { + state = OpenClosedType.CLOSED; } else { - throw new IllegalArgumentException("Didn't recognise the open/closed value " + updatedValue); + state = OpenClosedType.valueOf(updatedValue); } - } else { - throw new IllegalArgumentException( - "Type " + command.getClass().getName() + " not supported for OpenCloseValue"); } - - state = boolValue; - return (boolValue == OpenClosedType.OPEN) ? openString : closeString; - } - - @Override - public String getItemType() { - return CoreItemFactory.CONTACT; - } - - @Override - public StateDescription createStateDescription(String unit, boolean readOnly) { - return new StateDescription(null, null, null, "%s " + unit.replace("%", "%%"), readOnly, - Collections.emptyList()); - } - - @Override - public List> getSupportedCommandTypes() { - return commandTypes; } @Override - public void resetState() { - state = UnDefType.UNDEF; + public String getMQTTpublishValue() { + return (state == OpenClosedType.OPEN) ? openString : closeString; } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/PercentageValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/PercentageValue.java new file mode 100644 index 00000000000..eefcc7296fa --- /dev/null +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/PercentageValue.java @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.binding.mqtt.generic.internal.values; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.CoreItemFactory; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.IncreaseDecreaseType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.UpDownType; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.UnDefType; + +/** + * Implements a percentage value. Minimum and maximum are definable. + * + *

+ * Accepts user updates from a DecimalType, IncreaseDecreaseType and UpDownType. + * If this is a percent value, PercentType + *

+ * Accepts MQTT state updates as DecimalType, IncreaseDecreaseType and UpDownType + * StringType with comma separated HSB ("h,s,b"), RGB ("r,g,b") and on, off strings. + * On, Off strings can be customized. + * + * @author David Graeff - Initial contribution + */ +@NonNullByDefault +public class PercentageValue extends Value { + private final double min; + private final double max; + private final double step; + + public PercentageValue(@Nullable BigDecimal min, @Nullable BigDecimal max, @Nullable BigDecimal step) { + super(CoreItemFactory.DIMMER, Stream.of(DecimalType.class, IncreaseDecreaseType.class, UpDownType.class) + .collect(Collectors.toList())); + this.min = min == null ? 0.0 : min.doubleValue(); + this.max = max == null ? 100.0 : max.doubleValue(); + if (this.min >= this.max) { + throw new IllegalArgumentException("Min need to be smaller than max!"); + } + this.step = step == null ? 1.0 : step.doubleValue(); + } + + @Override + public void update(Command command) throws IllegalArgumentException { + PercentType oldvalue = (state == UnDefType.UNDEF) ? new PercentType() : (PercentType) state; + if (command instanceof PercentType) { + state = (PercentType) command; + } else if (command instanceof DecimalType) { + double v = ((DecimalType) command).doubleValue(); + v = (v - min) * 100.0 / (max - min); + state = new PercentType(new BigDecimal(v)); + } else if (command instanceof IncreaseDecreaseType) { + if (((IncreaseDecreaseType) command) == IncreaseDecreaseType.INCREASE) { + final double v = oldvalue.doubleValue() + step; + state = new PercentType(new BigDecimal(v <= max ? v : max)); + } else { + double v = oldvalue.doubleValue() - step; + state = new PercentType(new BigDecimal(v >= min ? v : min)); + } + } else if (command instanceof UpDownType) { + if (((UpDownType) command) == UpDownType.UP) { + final double v = oldvalue.doubleValue() + step; + state = new PercentType(new BigDecimal(v <= max ? v : max)); + } else { + final double v = oldvalue.doubleValue() - step; + state = new PercentType(new BigDecimal(v >= min ? v : min)); + } + } else { + state = PercentType.valueOf(command.toString()); + } + } + + @Override + public StateDescription createStateDescription(String unit, boolean readOnly) { + return new StateDescription(new BigDecimal(min), new BigDecimal(max), new BigDecimal(step), + "%s " + unit.replace("%", "%%"), readOnly, Collections.emptyList()); + } +} diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/TextValue.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/TextValue.java index 83a46e5f639..49b144739d2 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/TextValue.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/TextValue.java @@ -25,10 +25,8 @@ import org.eclipse.smarthome.core.library.CoreItemFactory; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.types.Command; -import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.StateDescription; import org.eclipse.smarthome.core.types.StateOption; -import org.eclipse.smarthome.core.types.UnDefType; /** * Implements a text/string value. Allows to restrict the incoming value to a set of states. @@ -36,9 +34,7 @@ * @author David Graeff - Initial contribution */ @NonNullByDefault -public class TextValue implements Value { - private State state = UnDefType.UNDEF; - private StringType strValue; +public class TextValue extends Value { private final @Nullable Set states; /** @@ -48,7 +44,7 @@ public class TextValue implements Value { * will be allowed. */ public TextValue(String[] states) { - strValue = new StringType(); + super(CoreItemFactory.STRING, Collections.singletonList(StringType.class)); Set s = Stream.of(states).filter(e -> StringUtils.isNotBlank(e)).collect(Collectors.toSet()); if (s.size() > 0) { this.states = s; @@ -58,30 +54,18 @@ public TextValue(String[] states) { } public TextValue() { - strValue = new StringType(); + super(CoreItemFactory.STRING, Collections.singletonList(StringType.class)); this.states = null; } @Override - public State getValue() { - return state; - } - - @Override - public String update(Command command) throws IllegalArgumentException { + public void update(Command command) throws IllegalArgumentException { final Set states = this.states; - String value = command.toString(); - if (states != null && !states.contains(value)) { - throw new IllegalArgumentException("Value " + value + " not within range"); + String valueStr = command.toString(); + if (states != null && !states.contains(valueStr)) { + throw new IllegalArgumentException("Value " + valueStr + " not within range"); } - strValue = new StringType(value); - state = strValue; - return value; - } - - @Override - public String getItemType() { - return CoreItemFactory.STRING; + state = new StringType(valueStr); } /** @@ -102,14 +86,4 @@ public StateDescription createStateDescription(String unit, boolean readOnly) { } return new StateDescription(null, null, null, "%s " + unit.replace("%", "%%"), readOnly, stateOptions); } - - @Override - public void resetState() { - state = UnDefType.UNDEF; - } - - @Override - public List> getSupportedCommandTypes() { - return Collections.singletonList(StringType.class); - } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/Value.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/Value.java index 86e0d2f9172..f80c264e216 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/Value.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/Value.java @@ -12,32 +12,49 @@ */ package org.eclipse.smarthome.binding.mqtt.generic.internal.values; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URLConnection; +import java.util.Collections; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.smarthome.binding.mqtt.generic.internal.handler.GenericThingHandler; import org.eclipse.smarthome.core.library.CoreItemFactory; +import org.eclipse.smarthome.core.library.types.DecimalType; +import org.eclipse.smarthome.core.library.types.PercentType; +import org.eclipse.smarthome.core.library.types.RawType; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.StateDescription; import org.eclipse.smarthome.core.types.UnDefType; /** - * MQTT topics are not inherently typed. Users are able to map topic values to framework types, for example - * for numbers {@link NumberValue}, boolean values {@link OnOffValue}, number values - * with limits {@link NumberValue} and string values {@link TextValue}. + * MQTT topics are not inherently typed. * - * This interface allows the handler class {@link GenericThingHandler} to treat all MQTT topics the same. - * {@link #getValue()} is used to retrieve the topic state and a call to {@link #update(Command)} sets the value. + *

+ * With this class users are able to map MQTT topic values to framework types, + * for example for numbers {@link NumberValue}, boolean values {@link OnOffValue}, percentage values + * {@link PercentageValue}, string values {@link TextValue} and more. + *

+ * + *

+ * This abstract class allows the handler class {@link GenericThingHandler} to treat all MQTT topics the same. + * {@link #getCache()} is used to retrieve the topic state and a call to {@link #update(Command)} sets the value. + *

* * @author David Graeff - Initial contribution */ @NonNullByDefault -public interface Value { - /** - * Returns the item-type (one of {@link CoreItemFactory}). - */ - String getItemType(); +public abstract class Value { + protected State state = UnDefType.UNDEF; + protected final List> commandTypes; + private final String itemType; + + protected Value(String itemType, List> commandTypes) { + this.itemType = itemType; + this.commandTypes = commandTypes; + } /** * Return a list of supported command types. The order of the list is important. @@ -45,29 +62,96 @@ public interface Value { * The framework will try to parse an incoming string into one of those command types, * starting with the first and continue until it succeeds. *

+ *

+ * Your {@link #update(Command)} method must accept all command types of this list. + * You may accept more command types. This allows you to restrict the parsing of incoming + * MQTT values to the listed types, but handle more user commands. + *

+ * A prominent example is the {@link NumberValue}, which does not return {@link PercentType}, + * because that would interfere with {@link DecimalType} while parsing the MQTT value. + * It does however accept a {@link PercentType} for {@link #update(Command)}, because a + * linked Item could send that type of command. */ - List> getSupportedCommandTypes(); + public final List> getSupportedCommandTypes() { + return commandTypes; + } + + /** + * Returns the item-type (one of {@link CoreItemFactory}). + */ + public final String getItemType() { + return itemType; + } /** * Returns the current value state. */ - State getValue(); + public final State getChannelState() { + return state; + } + + public String getMQTTpublishValue() { + return state.toString(); + } + + /** + * Returns true if this is a binary type. + */ + public boolean isBinary() { + return false; + } + + /** + * If the MQTT connection is not yet initialised or no values have + * been received yet, the default value is {@link UnDefType#UNDEF}. To restore to the + * default value after a connection got lost etc, this method will be called. + */ + public final void resetState() { + state = UnDefType.UNDEF; + } /** * Updates the internal value state with the given command. * * @param command The command to update the internal value. - * @return An updated value state. The same as if {@link #getValue()} is called. * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type. */ - String update(Command command) throws IllegalArgumentException; + public abstract void update(Command command) throws IllegalArgumentException; /** - * If the MQTT connection is not yet initialised or no values have - * been received yet, the default value is {@link UnDefType#UNDEF}. To restore to the - * default value after a connection got lost etc, this method will be called. + * Updates the internal value state with the given binary payload. + * + * @param data The binary payload to update the internal value. + * @exception IllegalArgumentException Thrown if for example a text is assigned to a number type. */ - void resetState(); + public void update(byte data[]) throws IllegalArgumentException { + String mimeType = null; + + // URLConnection.guessContentTypeFromStream(input) is not sufficient to detect all JPEG files + if (data.length >= 2 && data[0] == (byte) 0xFF && data[1] == (byte) 0xD8 && data[data.length - 2] == (byte) 0xFF + && data[data.length - 1] == (byte) 0xD9) { + mimeType = "image/jpeg"; + } else { + try (final ByteArrayInputStream input = new ByteArrayInputStream(data)) { + try { + mimeType = URLConnection.guessContentTypeFromStream(input); + } catch (final IOException ignored) { + } + } catch (final IOException ignored) { + } + } + state = new RawType(data, mimeType == null ? RawType.DEFAULT_MIME_TYPE : mimeType); + } - StateDescription createStateDescription(String unit, boolean readOnly); + /** + * Return the state description for this value state. + * + * @param unit An optional unit string. Might be an empty string. + * @param readOnly True if this is a read-only value. + * @return A state description + */ + public StateDescription createStateDescription(String unit, boolean readOnly) { + return new StateDescription(null, null, null, "%s " + unit.replace("%", "%%"), readOnly, + Collections.emptyList()); + } } diff --git a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueFactory.java b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueFactory.java index fc269575a7d..6211b700f3d 100644 --- a/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueFactory.java +++ b/extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueFactory.java @@ -37,11 +37,20 @@ public static Value createValueState(ChannelConfig config, String channelTypeID) value = StringUtils.isBlank(config.allowedStates) ? new TextValue() : new TextValue(config.allowedStates.split(",")); break; + case MqttBindingConstants.DATETIME: + value = new DateTimeValue(); + break; + case MqttBindingConstants.IMAGE: + value = new ImageValue(); + break; + case MqttBindingConstants.LOCATION: + value = new LocationValue(); + break; case MqttBindingConstants.NUMBER: - value = new NumberValue(config.isDecimal, config.min, config.max, config.step, false); + value = new NumberValue(config.min, config.max, config.step); break; case MqttBindingConstants.DIMMER: - value = new NumberValue(config.isDecimal, config.min, config.max, config.step, true); + value = new PercentageValue(config.min, config.max, config.step); break; case MqttBindingConstants.COLOR_RGB: value = new ColorValue(true, config.on, config.off);