2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.plugwise.internal.handler;
15 import static org.openhab.binding.plugwise.internal.PlugwiseBindingConstants.CHANNEL_LAST_SEEN;
16 import static org.openhab.core.thing.ThingStatus.*;
18 import java.math.BigDecimal;
19 import java.time.Duration;
20 import java.time.LocalDateTime;
21 import java.util.List;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.plugwise.internal.PlugwiseBindingConstants;
27 import org.openhab.binding.plugwise.internal.PlugwiseDeviceTask;
28 import org.openhab.binding.plugwise.internal.PlugwiseMessagePriority;
29 import org.openhab.binding.plugwise.internal.PlugwiseUtils;
30 import org.openhab.binding.plugwise.internal.listener.PlugwiseMessageListener;
31 import org.openhab.binding.plugwise.internal.protocol.InformationRequestMessage;
32 import org.openhab.binding.plugwise.internal.protocol.InformationResponseMessage;
33 import org.openhab.binding.plugwise.internal.protocol.Message;
34 import org.openhab.binding.plugwise.internal.protocol.PingRequestMessage;
35 import org.openhab.binding.plugwise.internal.protocol.field.DeviceType;
36 import org.openhab.binding.plugwise.internal.protocol.field.MACAddress;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.Channel;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingStatusInfo;
44 import org.openhab.core.thing.binding.BaseThingHandler;
45 import org.openhab.core.types.Command;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
50 * The {@link AbstractPlugwiseThingHandler} handles common Plugwise device channel updates and commands.
52 * @author Wouter Born - Initial contribution
55 public abstract class AbstractPlugwiseThingHandler extends BaseThingHandler implements PlugwiseMessageListener {
57 private static final Duration DEFAULT_UPDATE_INTERVAL = Duration.ofMinutes(1);
58 private static final Duration MESSAGE_TIMEOUT = Duration.ofSeconds(15);
59 private static final int MAX_UNANSWERED_PINGS = 2;
61 private final PlugwiseDeviceTask onlineStateUpdateTask = new PlugwiseDeviceTask("Online state update", scheduler) {
63 public Duration getConfiguredInterval() {
64 return MESSAGE_TIMEOUT;
68 public void runTask() {
73 public boolean shouldBeScheduled() {
74 return shouldOnlineTaskBeScheduled();
84 private final Logger logger = LoggerFactory.getLogger(AbstractPlugwiseThingHandler.class);
86 private LocalDateTime lastSeen = LocalDateTime.MIN;
87 private @Nullable PlugwiseStickHandler stickHandler;
88 private @Nullable LocalDateTime lastConfigurationUpdateSend;
89 private int unansweredPings;
91 public AbstractPlugwiseThingHandler(Thing thing) {
95 protected void addMessageListener() {
96 if (stickHandler != null) {
97 stickHandler.addMessageListener(this, getMACAddress());
102 public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
103 updateBridgeStatus();
107 public void dispose() {
108 removeMessageListener();
109 onlineStateUpdateTask.stop();
112 protected Duration durationSinceLastSeen() {
113 return Duration.between(lastSeen, LocalDateTime.now());
116 protected Duration getChannelUpdateInterval(String channelId) {
117 Channel channel = thing.getChannel(channelId);
118 if (channel == null) {
119 return DEFAULT_UPDATE_INTERVAL;
121 BigDecimal interval = (BigDecimal) channel.getConfiguration()
122 .get(PlugwiseBindingConstants.CONFIG_PROPERTY_UPDATE_INTERVAL);
123 return interval != null ? Duration.ofSeconds(interval.intValue()) : DEFAULT_UPDATE_INTERVAL;
126 protected DeviceType getDeviceType() {
127 return PlugwiseUtils.getDeviceType(thing.getThingTypeUID());
130 protected abstract MACAddress getMACAddress();
132 protected ThingStatusDetail getThingStatusDetail() {
133 return isConfigurationPending() ? ThingStatusDetail.CONFIGURATION_PENDING : ThingStatusDetail.NONE;
137 public void handleCommand(ChannelUID channelUID, Command command) {
138 logger.debug("Handling command '{}' for {} ({}) channel '{}'", command, getDeviceType(), getMACAddress(),
143 public void initialize() {
144 updateBridgeStatus();
145 updateTask(onlineStateUpdateTask);
147 // Add the message listener after dispose/initialize due to configuration update
148 if (isInitialized()) {
149 addMessageListener();
152 // Send configuration update commands after configuration update
153 if (thing.getStatus() == ONLINE) {
154 sendConfigurationUpdateCommands();
158 protected boolean isConfigurationPending() {
162 protected void ping() {
163 sendMessage(new PingRequestMessage(getMACAddress()));
166 protected boolean recentlySendConfigurationUpdate() {
167 return lastConfigurationUpdateSend != null
168 && LocalDateTime.now().minus(Duration.ofMillis(500)).isBefore(lastConfigurationUpdateSend);
171 protected void removeMessageListener() {
172 if (stickHandler != null) {
173 stickHandler.removeMessageListener(this);
177 protected abstract boolean shouldOnlineTaskBeScheduled();
179 protected void sendCommandMessage(Message message) {
180 if (stickHandler != null) {
181 stickHandler.sendMessage(message, PlugwiseMessagePriority.COMMAND);
185 protected void sendConfigurationUpdateCommands() {
186 lastConfigurationUpdateSend = LocalDateTime.now();
187 if (getThingStatusDetail() != thing.getStatusInfo().getStatusDetail()) {
188 updateStatus(thing.getStatus(), getThingStatusDetail());
192 protected void sendFastUpdateMessage(Message message) {
193 if (stickHandler != null) {
194 stickHandler.sendMessage(message, PlugwiseMessagePriority.FAST_UPDATE);
198 protected void sendMessage(Message message) {
199 if (stickHandler != null) {
200 stickHandler.sendMessage(message, PlugwiseMessagePriority.UPDATE_AND_DISCOVERY);
204 protected void stopTasks(List<PlugwiseDeviceTask> tasks) {
205 for (PlugwiseDeviceTask task : tasks) {
211 * Updates the thing state based on that of the Stick
213 protected void updateBridgeStatus() {
214 Bridge bridge = getBridge();
215 ThingStatus bridgeStatus = bridge != null ? bridge.getStatus() : null;
216 if (bridge == null) {
217 removeMessageListener();
218 updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
219 } else if (bridgeStatus == ONLINE && thing.getStatus() != ONLINE) {
220 stickHandler = (PlugwiseStickHandler) bridge.getHandler();
221 addMessageListener();
222 updateStatus(OFFLINE, getThingStatusDetail());
223 } else if (bridgeStatus == OFFLINE) {
224 removeMessageListener();
225 updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
226 } else if (bridgeStatus == UNKNOWN) {
227 removeMessageListener();
228 updateStatus(UNKNOWN);
232 protected void updateInformation() {
233 sendMessage(new InformationRequestMessage(getMACAddress()));
236 protected void updateLastSeen() {
238 lastSeen = LocalDateTime.now();
239 if (isLinked(CHANNEL_LAST_SEEN)) {
240 updateState(CHANNEL_LAST_SEEN, PlugwiseUtils.newDateTimeType(lastSeen));
242 if (thing.getStatus() == OFFLINE) {
243 updateStatus(ONLINE, getThingStatusDetail());
247 protected void updateOnlineState() {
248 ThingStatus status = thing.getStatus();
249 if (status == ONLINE && unansweredPings < MAX_UNANSWERED_PINGS
250 && MESSAGE_TIMEOUT.minus(durationSinceLastSeen()).isNegative()) {
253 } else if (status == ONLINE && unansweredPings >= MAX_UNANSWERED_PINGS) {
254 updateStatus(OFFLINE, getThingStatusDetail());
256 } else if (status == OFFLINE) {
261 protected void updateProperties(InformationResponseMessage message) {
262 Map<String, String> properties = editProperties();
263 boolean update = PlugwiseUtils.updateProperties(properties, message);
266 updateProperties(properties);
271 protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
272 ThingStatus oldStatus = thing.getStatus();
273 super.updateStatus(status, statusDetail, description);
275 updateTask(onlineStateUpdateTask);
276 if (oldStatus != ONLINE && status == ONLINE && isConfigurationPending()) {
277 sendConfigurationUpdateCommands();
281 protected void updateStatusOnDetailChange() {
282 if (thing.getStatusInfo().getStatusDetail() != getThingStatusDetail()) {
283 updateStatus(thing.getStatus(), getThingStatusDetail());
287 protected void updateTask(PlugwiseDeviceTask task) {
288 if (task.shouldBeScheduled()) {
289 if (!task.isScheduled() || task.getConfiguredInterval() != task.getInterval()) {
290 if (task.isScheduled()) {
293 task.update(getDeviceType(), getMACAddress());
296 } else if (!task.shouldBeScheduled() && task.isScheduled()) {
301 protected void updateTasks(List<PlugwiseDeviceTask> tasks) {
302 for (PlugwiseDeviceTask task : tasks) {