]> git.basschouten.com Git - openhab-addons.git/blob
a22582e11d13e0e1f2faeed79b5ebd9b2172a643
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.plugwise.internal.handler;
14
15 import static org.openhab.binding.plugwise.internal.PlugwiseBindingConstants.CHANNEL_LAST_SEEN;
16 import static org.openhab.core.thing.ThingStatus.*;
17
18 import java.math.BigDecimal;
19 import java.time.Duration;
20 import java.time.LocalDateTime;
21 import java.util.List;
22 import java.util.Map;
23
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;
48
49 /**
50  * The {@link AbstractPlugwiseThingHandler} handles common Plugwise device channel updates and commands.
51  *
52  * @author Wouter Born - Initial contribution
53  */
54 @NonNullByDefault
55 public abstract class AbstractPlugwiseThingHandler extends BaseThingHandler implements PlugwiseMessageListener {
56
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;
60
61     private final PlugwiseDeviceTask onlineStateUpdateTask = new PlugwiseDeviceTask("Online state update", scheduler) {
62         @Override
63         public Duration getConfiguredInterval() {
64             return MESSAGE_TIMEOUT;
65         }
66
67         @Override
68         public void runTask() {
69             updateOnlineState();
70         }
71
72         @Override
73         public boolean shouldBeScheduled() {
74             return shouldOnlineTaskBeScheduled();
75         }
76
77         @Override
78         public void start() {
79             unansweredPings = 0;
80             super.start();
81         }
82     };
83
84     private final Logger logger = LoggerFactory.getLogger(AbstractPlugwiseThingHandler.class);
85
86     private LocalDateTime lastSeen = LocalDateTime.MIN;
87     private @Nullable PlugwiseStickHandler stickHandler;
88     private @Nullable LocalDateTime lastConfigurationUpdateSend;
89     private int unansweredPings;
90
91     public AbstractPlugwiseThingHandler(Thing thing) {
92         super(thing);
93     }
94
95     protected void addMessageListener() {
96         if (stickHandler != null) {
97             stickHandler.addMessageListener(this, getMACAddress());
98         }
99     }
100
101     @Override
102     public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
103         updateBridgeStatus();
104     }
105
106     @Override
107     public void dispose() {
108         removeMessageListener();
109         onlineStateUpdateTask.stop();
110     }
111
112     protected Duration durationSinceLastSeen() {
113         return Duration.between(lastSeen, LocalDateTime.now());
114     }
115
116     protected Duration getChannelUpdateInterval(String channelId) {
117         Channel channel = thing.getChannel(channelId);
118         if (channel == null) {
119             return DEFAULT_UPDATE_INTERVAL;
120         }
121         BigDecimal interval = (BigDecimal) channel.getConfiguration()
122                 .get(PlugwiseBindingConstants.CONFIG_PROPERTY_UPDATE_INTERVAL);
123         return interval != null ? Duration.ofSeconds(interval.intValue()) : DEFAULT_UPDATE_INTERVAL;
124     }
125
126     protected DeviceType getDeviceType() {
127         return PlugwiseUtils.getDeviceType(thing.getThingTypeUID());
128     }
129
130     protected abstract MACAddress getMACAddress();
131
132     protected ThingStatusDetail getThingStatusDetail() {
133         return isConfigurationPending() ? ThingStatusDetail.CONFIGURATION_PENDING : ThingStatusDetail.NONE;
134     }
135
136     @Override
137     public void handleCommand(ChannelUID channelUID, Command command) {
138         logger.debug("Handling command '{}' for {} ({}) channel '{}'", command, getDeviceType(), getMACAddress(),
139                 channelUID.getId());
140     }
141
142     @Override
143     public void initialize() {
144         updateBridgeStatus();
145         updateTask(onlineStateUpdateTask);
146
147         // Add the message listener after dispose/initialize due to configuration update
148         if (isInitialized()) {
149             addMessageListener();
150         }
151
152         // Send configuration update commands after configuration update
153         if (thing.getStatus() == ONLINE) {
154             sendConfigurationUpdateCommands();
155         }
156     }
157
158     protected boolean isConfigurationPending() {
159         return false;
160     }
161
162     protected void ping() {
163         sendMessage(new PingRequestMessage(getMACAddress()));
164     }
165
166     protected boolean recentlySendConfigurationUpdate() {
167         return lastConfigurationUpdateSend != null
168                 && LocalDateTime.now().minus(Duration.ofMillis(500)).isBefore(lastConfigurationUpdateSend);
169     }
170
171     protected void removeMessageListener() {
172         if (stickHandler != null) {
173             stickHandler.removeMessageListener(this);
174         }
175     }
176
177     protected abstract boolean shouldOnlineTaskBeScheduled();
178
179     protected void sendCommandMessage(Message message) {
180         if (stickHandler != null) {
181             stickHandler.sendMessage(message, PlugwiseMessagePriority.COMMAND);
182         }
183     }
184
185     protected void sendConfigurationUpdateCommands() {
186         lastConfigurationUpdateSend = LocalDateTime.now();
187         if (getThingStatusDetail() != thing.getStatusInfo().getStatusDetail()) {
188             updateStatus(thing.getStatus(), getThingStatusDetail());
189         }
190     }
191
192     protected void sendFastUpdateMessage(Message message) {
193         if (stickHandler != null) {
194             stickHandler.sendMessage(message, PlugwiseMessagePriority.FAST_UPDATE);
195         }
196     }
197
198     protected void sendMessage(Message message) {
199         if (stickHandler != null) {
200             stickHandler.sendMessage(message, PlugwiseMessagePriority.UPDATE_AND_DISCOVERY);
201         }
202     }
203
204     protected void stopTasks(List<PlugwiseDeviceTask> tasks) {
205         for (PlugwiseDeviceTask task : tasks) {
206             task.stop();
207         }
208     }
209
210     /**
211      * Updates the thing state based on that of the Stick
212      */
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);
229         }
230     }
231
232     protected void updateInformation() {
233         sendMessage(new InformationRequestMessage(getMACAddress()));
234     }
235
236     protected void updateLastSeen() {
237         unansweredPings = 0;
238         lastSeen = LocalDateTime.now();
239         if (isLinked(CHANNEL_LAST_SEEN)) {
240             updateState(CHANNEL_LAST_SEEN, PlugwiseUtils.newDateTimeType(lastSeen));
241         }
242         if (thing.getStatus() == OFFLINE) {
243             updateStatus(ONLINE, getThingStatusDetail());
244         }
245     }
246
247     protected void updateOnlineState() {
248         ThingStatus status = thing.getStatus();
249         if (status == ONLINE && unansweredPings < MAX_UNANSWERED_PINGS
250                 && MESSAGE_TIMEOUT.minus(durationSinceLastSeen()).isNegative()) {
251             ping();
252             unansweredPings++;
253         } else if (status == ONLINE && unansweredPings >= MAX_UNANSWERED_PINGS) {
254             updateStatus(OFFLINE, getThingStatusDetail());
255             unansweredPings = 0;
256         } else if (status == OFFLINE) {
257             ping();
258         }
259     }
260
261     protected void updateProperties(InformationResponseMessage message) {
262         Map<String, String> properties = editProperties();
263         boolean update = PlugwiseUtils.updateProperties(properties, message);
264
265         if (update) {
266             updateProperties(properties);
267         }
268     }
269
270     @Override
271     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
272         ThingStatus oldStatus = thing.getStatus();
273         super.updateStatus(status, statusDetail, description);
274
275         updateTask(onlineStateUpdateTask);
276         if (oldStatus != ONLINE && status == ONLINE && isConfigurationPending()) {
277             sendConfigurationUpdateCommands();
278         }
279     }
280
281     protected void updateStatusOnDetailChange() {
282         if (thing.getStatusInfo().getStatusDetail() != getThingStatusDetail()) {
283             updateStatus(thing.getStatus(), getThingStatusDetail());
284         }
285     }
286
287     protected void updateTask(PlugwiseDeviceTask task) {
288         if (task.shouldBeScheduled()) {
289             if (!task.isScheduled() || task.getConfiguredInterval() != task.getInterval()) {
290                 if (task.isScheduled()) {
291                     task.stop();
292                 }
293                 task.update(getDeviceType(), getMACAddress());
294                 task.start();
295             }
296         } else if (!task.shouldBeScheduled() && task.isScheduled()) {
297             task.stop();
298         }
299     }
300
301     protected void updateTasks(List<PlugwiseDeviceTask> tasks) {
302         for (PlugwiseDeviceTask task : tasks) {
303             updateTask(task);
304         }
305     }
306 }