]> git.basschouten.com Git - openhab-addons.git/blob
95706a12f57daf92ddec082056c8f24c70263f06
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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         LocalDateTime lastConfigurationUpdateSend = this.lastConfigurationUpdateSend;
168         return lastConfigurationUpdateSend != null
169                 && LocalDateTime.now().minus(Duration.ofMillis(500)).isBefore(lastConfigurationUpdateSend);
170     }
171
172     protected void removeMessageListener() {
173         if (stickHandler != null) {
174             stickHandler.removeMessageListener(this);
175         }
176     }
177
178     protected abstract boolean shouldOnlineTaskBeScheduled();
179
180     protected void sendCommandMessage(Message message) {
181         if (stickHandler != null) {
182             stickHandler.sendMessage(message, PlugwiseMessagePriority.COMMAND);
183         }
184     }
185
186     protected void sendConfigurationUpdateCommands() {
187         lastConfigurationUpdateSend = LocalDateTime.now();
188         if (getThingStatusDetail() != thing.getStatusInfo().getStatusDetail()) {
189             updateStatus(thing.getStatus(), getThingStatusDetail());
190         }
191     }
192
193     protected void sendFastUpdateMessage(Message message) {
194         if (stickHandler != null) {
195             stickHandler.sendMessage(message, PlugwiseMessagePriority.FAST_UPDATE);
196         }
197     }
198
199     protected void sendMessage(Message message) {
200         if (stickHandler != null) {
201             stickHandler.sendMessage(message, PlugwiseMessagePriority.UPDATE_AND_DISCOVERY);
202         }
203     }
204
205     protected void stopTasks(List<PlugwiseDeviceTask> tasks) {
206         for (PlugwiseDeviceTask task : tasks) {
207             task.stop();
208         }
209     }
210
211     /**
212      * Updates the thing state based on that of the Stick
213      */
214     protected void updateBridgeStatus() {
215         Bridge bridge = getBridge();
216         ThingStatus bridgeStatus = bridge != null ? bridge.getStatus() : null;
217         if (bridge == null) {
218             removeMessageListener();
219             updateStatus(OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
220         } else if (bridgeStatus == ONLINE && thing.getStatus() != ONLINE) {
221             stickHandler = (PlugwiseStickHandler) bridge.getHandler();
222             addMessageListener();
223             updateStatus(OFFLINE, getThingStatusDetail());
224         } else if (bridgeStatus == OFFLINE) {
225             removeMessageListener();
226             updateStatus(OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
227         } else if (bridgeStatus == UNKNOWN) {
228             removeMessageListener();
229             updateStatus(UNKNOWN);
230         }
231     }
232
233     protected void updateInformation() {
234         sendMessage(new InformationRequestMessage(getMACAddress()));
235     }
236
237     protected void updateLastSeen() {
238         unansweredPings = 0;
239         lastSeen = LocalDateTime.now();
240         if (isLinked(CHANNEL_LAST_SEEN)) {
241             updateState(CHANNEL_LAST_SEEN, PlugwiseUtils.newDateTimeType(lastSeen));
242         }
243         if (thing.getStatus() == OFFLINE) {
244             updateStatus(ONLINE, getThingStatusDetail());
245         }
246     }
247
248     protected void updateOnlineState() {
249         ThingStatus status = thing.getStatus();
250         if (status == ONLINE && unansweredPings < MAX_UNANSWERED_PINGS
251                 && MESSAGE_TIMEOUT.minus(durationSinceLastSeen()).isNegative()) {
252             ping();
253             unansweredPings++;
254         } else if (status == ONLINE && unansweredPings >= MAX_UNANSWERED_PINGS) {
255             updateStatus(OFFLINE, getThingStatusDetail());
256             unansweredPings = 0;
257         } else if (status == OFFLINE) {
258             ping();
259         }
260     }
261
262     protected void updateProperties(InformationResponseMessage message) {
263         Map<String, String> properties = editProperties();
264         boolean update = PlugwiseUtils.updateProperties(properties, message);
265
266         if (update) {
267             updateProperties(properties);
268         }
269     }
270
271     @Override
272     protected void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, @Nullable String description) {
273         ThingStatus oldStatus = thing.getStatus();
274         super.updateStatus(status, statusDetail, description);
275
276         updateTask(onlineStateUpdateTask);
277         if (oldStatus != ONLINE && status == ONLINE && isConfigurationPending()) {
278             sendConfigurationUpdateCommands();
279         }
280     }
281
282     protected void updateStatusOnDetailChange() {
283         if (thing.getStatusInfo().getStatusDetail() != getThingStatusDetail()) {
284             updateStatus(thing.getStatus(), getThingStatusDetail());
285         }
286     }
287
288     protected void updateTask(PlugwiseDeviceTask task) {
289         if (task.shouldBeScheduled()) {
290             if (!task.isScheduled() || !task.getConfiguredInterval().equals(task.getInterval())) {
291                 if (task.isScheduled()) {
292                     task.stop();
293                 }
294                 task.update(getDeviceType(), getMACAddress());
295                 task.start();
296             }
297         } else if (!task.shouldBeScheduled() && task.isScheduled()) {
298             task.stop();
299         }
300     }
301
302     protected void updateTasks(List<PlugwiseDeviceTask> tasks) {
303         for (PlugwiseDeviceTask task : tasks) {
304             updateTask(task);
305         }
306     }
307 }