import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
}
protected void calculateThingStatus() {
- final boolean availabilityTopicsSeen;
+ final Optional<Boolean> availabilityTopicsSeen;
if (availabilityStates.isEmpty()) {
- availabilityTopicsSeen = true;
+ availabilityTopicsSeen = Optional.empty();
} else {
- availabilityTopicsSeen = availabilityStates.values().stream().allMatch(
- c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class)));
+ availabilityTopicsSeen = Optional.of(availabilityStates.values().stream().allMatch(
+ c -> c != null && OnOffType.ON.equals(c.getCache().getChannelState().as(OnOffType.class))));
}
updateThingStatus(messageReceived.get(), availabilityTopicsSeen);
}
- protected abstract void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen);
+ protected abstract void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen);
}
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import java.util.stream.Stream;
}
@Override
- protected void updateThingStatus(boolean messageReceived, boolean availibilityTopicsSeen) {
- if (messageReceived || availibilityTopicsSeen) {
+ protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availibilityTopicsSeen) {
+ if (availibilityTopicsSeen.orElse(true)) {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE);
*/
package org.openhab.binding.mqtt.homeassistant.internal;
+import java.util.concurrent.ScheduledExecutorService;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.mqtt.generic.AvailabilityTracker;
*/
public static @Nullable AbstractComponent<?> createComponent(ThingUID thingUID, HaID haID,
String channelConfigurationJSON, ChannelStateUpdateListener updateListener, AvailabilityTracker tracker,
- Gson gson, TransformationServiceProvider transformationServiceProvider) {
+ ScheduledExecutorService scheduler, Gson gson,
+ TransformationServiceProvider transformationServiceProvider) {
ComponentConfiguration componentConfiguration = new ComponentConfiguration(thingUID, haID,
- channelConfigurationJSON, gson, updateListener, tracker)
+ channelConfigurationJSON, gson, updateListener, tracker, scheduler)
.transformationProvider(transformationServiceProvider);
try {
switch (haID.component) {
private final ChannelStateUpdateListener updateListener;
private final AvailabilityTracker tracker;
private final Gson gson;
+ private final ScheduledExecutorService scheduler;
private @Nullable TransformationServiceProvider transformationServiceProvider;
protected ComponentConfiguration(ThingUID thingUID, HaID haID, String configJSON, Gson gson,
- ChannelStateUpdateListener updateListener, AvailabilityTracker tracker) {
+ ChannelStateUpdateListener updateListener, AvailabilityTracker tracker,
+ ScheduledExecutorService scheduler) {
this.thingUID = thingUID;
this.haID = haID;
this.configJSON = configJSON;
this.gson = gson;
this.updateListener = updateListener;
this.tracker = tracker;
+ this.scheduler = scheduler;
}
public ComponentConfiguration transformationProvider(
return tracker;
}
+ public ScheduledExecutorService getScheduler() {
+ return scheduler;
+ }
+
public <C extends BaseChannelConfiguration> C getConfig(Class<C> clazz) {
return BaseChannelConfiguration.fromString(configJSON, gson, clazz);
}
String command_topic = channelConfiguration.command_topic;
if (command_topic != null) {
buildChannel(switchDisarmChannelID, new TextValue(new String[] { channelConfiguration.payload_disarm }),
- channelConfiguration.name, componentConfiguration.getUpdateListener())//
- .commandTopic(command_topic, channelConfiguration.retain)//
- .build();
+ channelConfiguration.name, componentConfiguration.getUpdateListener())
+ .commandTopic(command_topic, channelConfiguration.retain).build();
buildChannel(switchArmHomeChannelID, new TextValue(new String[] { channelConfiguration.payload_arm_home }),
- channelConfiguration.name, componentConfiguration.getUpdateListener())//
- .commandTopic(command_topic, channelConfiguration.retain)//
- .build();
+ channelConfiguration.name, componentConfiguration.getUpdateListener())
+ .commandTopic(command_topic, channelConfiguration.retain).build();
buildChannel(switchArmAwayChannelID, new TextValue(new String[] { channelConfiguration.payload_arm_away }),
- channelConfiguration.name, componentConfiguration.getUpdateListener())//
- .commandTopic(command_topic, channelConfiguration.retain)//
- .build();
+ channelConfiguration.name, componentConfiguration.getUpdateListener())
+ .commandTopic(command_topic, channelConfiguration.retain).build();
}
}
}
*/
package org.openhab.binding.mqtt.homeassistant.internal;
+import java.util.List;
+
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.values.OnOffValue;
+import org.openhab.binding.mqtt.generic.values.Value;
+import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
+import org.openhab.binding.mqtt.homeassistant.internal.listener.OffDelayUpdateStateListener;
/**
* A MQTT BinarySensor, following the https://www.home-assistant.io/components/binary_sensor.mqtt/ specification.
protected @Nullable String device_class;
protected boolean force_update = false;
- protected int expire_after = 0;
+ protected @Nullable Integer expire_after;
+ protected @Nullable Integer off_delay;
protected String state_topic = "";
protected String payload_on = "ON";
protected String payload_off = "OFF";
+
+ protected @Nullable String json_attributes_topic;
+ protected @Nullable String json_attributes_template;
+ protected @Nullable List<String> json_attributes;
}
public ComponentBinarySensor(CFactory.ComponentConfiguration componentConfiguration) {
super(componentConfiguration, ChannelConfiguration.class);
- if (channelConfiguration.force_update) {
- throw new UnsupportedOperationException("Component:Sensor does not support forced updates");
+ OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off);
+
+ buildChannel(sensorChannelID, value, "value", getListener(componentConfiguration, value))
+ .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template).build();
+ }
+
+ private ChannelStateUpdateListener getListener(CFactory.ComponentConfiguration componentConfiguration,
+ Value value) {
+ ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
+
+ if (channelConfiguration.expire_after != null) {
+ updateListener = new ExpireUpdateStateListener(updateListener, channelConfiguration.expire_after, value,
+ componentConfiguration.getTracker(), componentConfiguration.getScheduler());
+ }
+ if (channelConfiguration.off_delay != null) {
+ updateListener = new OffDelayUpdateStateListener(updateListener, channelConfiguration.off_delay, value,
+ componentConfiguration.getScheduler());
}
- buildChannel(sensorChannelID, new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off),
- channelConfiguration.name, componentConfiguration.getUpdateListener())//
- .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
- .build();
+ return updateListener;
}
}
ImageValue value = new ImageValue();
- buildChannel(cameraChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())//
- .stateTopic(channelConfiguration.topic)//
- .build();
+ buildChannel(cameraChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
+ .stateTopic(channelConfiguration.topic).build();
}
}
RollershutterValue value = new RollershutterValue(channelConfiguration.payload_open,
channelConfiguration.payload_close, channelConfiguration.payload_stop);
- buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())//
- .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
- .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)//
- .build();
+ buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
+ .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
+ .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
}
}
super(componentConfiguration, ChannelConfiguration.class);
OnOffValue value = new OnOffValue(channelConfiguration.payload_on, channelConfiguration.payload_off);
- buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())//
- .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
- .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)//
- .build();
+ buildChannel(switchChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())
+ .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
+ .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
}
}
channelConfiguration.payload_off, 100);
// Create three MQTT subscriptions and use this class object as update listener
- switchChannel = buildChannel(switchChannelID, value, channelConfiguration.name, this)//
- // Some lights use the value_template field for the template, most use state_value_template
+ switchChannel = buildChannel(switchChannelID, value, channelConfiguration.name, this)
.stateTopic(channelConfiguration.state_topic, channelConfiguration.state_value_template,
- channelConfiguration.value_template)//
- .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)//
- .build(false);
-
- colorChannel = buildChannel(colorChannelID, value, channelConfiguration.name, this)//
- .stateTopic(channelConfiguration.rgb_state_topic, channelConfiguration.rgb_value_template)//
- .commandTopic(channelConfiguration.rgb_command_topic, channelConfiguration.retain)//
- .build(false);
-
- brightnessChannel = buildChannel(brightnessChannelID, value, channelConfiguration.name, this)//
- .stateTopic(channelConfiguration.brightness_state_topic, channelConfiguration.brightness_value_template)//
- .commandTopic(channelConfiguration.brightness_command_topic, channelConfiguration.retain)//
- .build(false);
+ channelConfiguration.value_template)
+ .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build(false);
+
+ colorChannel = buildChannel(colorChannelID, value, channelConfiguration.name, this)
+ .stateTopic(channelConfiguration.rgb_state_topic, channelConfiguration.rgb_value_template)
+ .commandTopic(channelConfiguration.rgb_command_topic, channelConfiguration.retain).build(false);
+
+ brightnessChannel = buildChannel(brightnessChannelID, value, channelConfiguration.name, this)
+ .stateTopic(channelConfiguration.brightness_state_topic, channelConfiguration.brightness_value_template)
+ .commandTopic(channelConfiguration.brightness_command_topic, channelConfiguration.retain).build(false);
channels.put(colorChannelID, colorChannel);
}
buildChannel(switchChannelID,
new OnOffValue(channelConfiguration.payload_lock, channelConfiguration.payload_unlock),
- channelConfiguration.name, componentConfiguration.getUpdateListener())//
- .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
- .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain)//
- .build();
+ channelConfiguration.name, componentConfiguration.getUpdateListener())
+ .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
+ .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain).build();
}
}
*/
package org.openhab.binding.mqtt.homeassistant.internal;
+import java.util.List;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
import org.openhab.binding.mqtt.generic.values.NumberValue;
import org.openhab.binding.mqtt.generic.values.TextValue;
import org.openhab.binding.mqtt.generic.values.Value;
+import org.openhab.binding.mqtt.homeassistant.internal.listener.ExpireUpdateStateListener;
/**
* A MQTT sensor, following the https://www.home-assistant.io/components/sensor.mqtt/ specification.
protected @Nullable String unit_of_measurement;
protected @Nullable String device_class;
protected boolean force_update = false;
- protected int expire_after = 0;
+ protected @Nullable Integer expire_after;
protected String state_topic = "";
+
+ protected @Nullable String json_attributes_topic;
+ protected @Nullable String json_attributes_template;
+ protected @Nullable List<String> json_attributes;
}
public ComponentSensor(CFactory.ComponentConfiguration componentConfiguration) {
super(componentConfiguration, ChannelConfiguration.class);
- if (channelConfiguration.force_update) {
- throw new UnsupportedOperationException("Component:Sensor does not support forced updates");
- }
-
Value value;
String uom = channelConfiguration.unit_of_measurement;
boolean trigger = triggerIcons.matcher(icon).matches();
- buildChannel(sensorChannelID, value, channelConfiguration.name, componentConfiguration.getUpdateListener())//
+ buildChannel(sensorChannelID, value, channelConfiguration.name, getListener(componentConfiguration, value))
.stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
.trigger(trigger).build();
}
+
+ private ChannelStateUpdateListener getListener(CFactory.ComponentConfiguration componentConfiguration,
+ Value value) {
+ ChannelStateUpdateListener updateListener = componentConfiguration.getUpdateListener();
+
+ if (channelConfiguration.expire_after != null) {
+ updateListener = new ExpireUpdateStateListener(updateListener, channelConfiguration.expire_after, value,
+ componentConfiguration.getTracker(), componentConfiguration.getScheduler());
+ }
+ return updateListener;
+ }
}
OnOffValue value = new OnOffValue(state_on, state_off, channelConfiguration.payload_on,
channelConfiguration.payload_off);
- buildChannel(switchChannelID, value, "state", componentConfiguration.getUpdateListener())//
- .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)//
- .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain, channelConfiguration.qos)//
+ buildChannel(switchChannelID, value, "state", componentConfiguration.getUpdateListener())
+ .stateTopic(channelConfiguration.state_topic, channelConfiguration.value_template)
+ .commandTopic(channelConfiguration.command_topic, channelConfiguration.retain, channelConfiguration.qos)
.build();
}
}
AbstractComponent<?> component = null;
if (config.length() > 0) {
- component = CFactory.createComponent(thingUID, haID, config, updateListener, tracker, gson,
+ component = CFactory.createComponent(thingUID, haID, config, updateListener, tracker, scheduler, gson,
transformationServiceProvider);
}
if (component != null) {
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
if (channelConfigurationJSON == null) {
logger.warn("Provided channel does not have a 'config' configuration key!");
} else {
- component = CFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, gson,
- transformationServiceProvider);
+ component = CFactory.createComponent(thingUID, haID, channelConfigurationJSON, this, this, scheduler,
+ gson, transformationServiceProvider);
}
if (component != null) {
}
@Override
- protected void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen) {
- if (!messageReceived || availabilityTopicsSeen) {
+ protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
+ if (availabilityTopicsSeen.orElse(messageReceived)) {
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE);
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mqtt.homeassistant.internal.listener;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+
+/**
+ * A proxy class for {@link ChannelStateUpdateListener} forwarding everything to the real listener.
+ * <p>
+ * This class is used to be able handle special cases like timeouts.
+ *
+ * @author Jochen Klein - Initial contribution
+ */
+@NonNullByDefault
+public abstract class ChannelStateUpdateListenerProxy implements ChannelStateUpdateListener {
+
+ private final ChannelStateUpdateListener original;
+
+ public ChannelStateUpdateListenerProxy(ChannelStateUpdateListener original) {
+ this.original = original;
+ }
+
+ @Override
+ public void updateChannelState(@NonNull ChannelUID channelUID, @NonNull State value) {
+ original.updateChannelState(channelUID, value);
+ }
+
+ @Override
+ public void postChannelCommand(@NonNull ChannelUID channelUID, @NonNull Command value) {
+ original.postChannelCommand(channelUID, value);
+ }
+
+ @Override
+ public void triggerChannel(@NonNull ChannelUID channelUID, @NonNull String eventPayload) {
+ original.triggerChannel(channelUID, eventPayload);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mqtt.homeassistant.internal.listener;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mqtt.generic.AvailabilityTracker;
+import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
+import org.openhab.binding.mqtt.generic.values.Value;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.State;
+
+/**
+ * A listener to reset the channel value after a timeout.
+ *
+ * @author Jochen Klein - Initial contribution
+ */
+@NonNullByDefault
+public class ExpireUpdateStateListener extends ChannelStateUpdateListenerProxy {
+
+ private final int expireAfter;
+ private final Value value;
+ private final AvailabilityTracker tracker;
+ private final ScheduledExecutorService scheduler;
+
+ private AtomicReference<@Nullable ScheduledFuture<?>> expire = new AtomicReference<>();
+
+ public ExpireUpdateStateListener(ChannelStateUpdateListener original, int expireAfter, Value value,
+ AvailabilityTracker tracker, ScheduledExecutorService scheduler) {
+ super(original);
+ this.expireAfter = expireAfter;
+ this.value = value;
+ this.tracker = tracker;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public void updateChannelState(final ChannelUID channelUID, State state) {
+ super.updateChannelState(channelUID, state);
+
+ ScheduledFuture<?> oldExpire = expire.getAndSet(scheduler.schedule(() -> {
+ value.resetState();
+ tracker.resetMessageReceived();
+ ExpireUpdateStateListener.super.updateChannelState(channelUID, value.getChannelState());
+ }, expireAfter, TimeUnit.SECONDS));
+
+ if (oldExpire != null) {
+ oldExpire.cancel(false);
+ }
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.mqtt.homeassistant.internal.listener;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
+import org.openhab.binding.mqtt.generic.values.Value;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.types.State;
+
+/**
+ * A listener to set the binary sensor value to 'off' after a timeout.
+ *
+ * @author Jochen Klein - Initial contribution
+ */
+@NonNullByDefault
+public class OffDelayUpdateStateListener extends ChannelStateUpdateListenerProxy {
+
+ private final int offDelay;
+ private final Value value;
+ private final ScheduledExecutorService scheduler;
+
+ private AtomicReference<@Nullable ScheduledFuture<?>> delay = new AtomicReference<>();
+
+ public OffDelayUpdateStateListener(ChannelStateUpdateListener original, int offDelay, Value value,
+ ScheduledExecutorService scheduler) {
+ super(original);
+ this.offDelay = offDelay;
+ this.value = value;
+ this.scheduler = scheduler;
+ }
+
+ @Override
+ public void updateChannelState(final ChannelUID channelUID, State state) {
+ super.updateChannelState(channelUID, state);
+
+ ScheduledFuture<?> newDelay = null;
+
+ if (OnOffType.ON == state) {
+ newDelay = scheduler.schedule(() -> {
+ value.update(OnOffType.OFF);
+ OffDelayUpdateStateListener.super.updateChannelState(channelUID, value.getChannelState());
+ }, offDelay, TimeUnit.SECONDS);
+ }
+
+ ScheduledFuture<?> oldDelay = delay.getAndSet(newDelay);
+ if (oldDelay != null) {
+ oldDelay.cancel(false);
+ }
+ }
+}
package org.openhab.binding.mqtt.homie.internal.handler;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledFuture;
import java.util.function.Consumer;
}
@Override
- protected void updateThingStatus(boolean messageReceived, boolean availabilityTopicsSeen) {
+ protected void updateThingStatus(boolean messageReceived, Optional<Boolean> availabilityTopicsSeen) {
// not used here
}
}