]> git.basschouten.com Git - openhab-addons.git/blob
bfaa0e664e650606e7b23de7391776205e77e658
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.verisure.internal.handler;
14
15 import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
16
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.Set;
20 import java.util.concurrent.RejectedExecutionException;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
23 import java.util.concurrent.locks.ReentrantLock;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.openhab.binding.verisure.internal.DeviceStatusListener;
29 import org.openhab.binding.verisure.internal.VerisureBridgeConfiguration;
30 import org.openhab.binding.verisure.internal.VerisureSession;
31 import org.openhab.binding.verisure.internal.discovery.VerisureThingDiscoveryService;
32 import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
33 import org.openhab.core.thing.Bridge;
34 import org.openhab.core.thing.ChannelUID;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.ThingTypeUID;
38 import org.openhab.core.thing.ThingUID;
39 import org.openhab.core.thing.binding.BaseBridgeHandler;
40 import org.openhab.core.thing.binding.ThingHandlerService;
41 import org.openhab.core.types.Command;
42 import org.openhab.core.types.RefreshType;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 /**
47  * The {@link VerisureBridgeHandler} is responsible for handling commands, which are
48  * sent to one of the channels.
49  *
50  * @author l3rum - Initial contribution
51  * @author Jan Gustafsson - Furher development
52  */
53 @NonNullByDefault
54 public class VerisureBridgeHandler extends BaseBridgeHandler {
55
56     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
57
58     private static final int REFRESH_DELAY_SECONDS = 30;
59     private final Logger logger = LoggerFactory.getLogger(VerisureBridgeHandler.class);
60     private final ReentrantLock immediateRefreshJobLock = new ReentrantLock();
61     private final HttpClient httpClient;
62
63     private String authstring = "";
64     private @Nullable String pinCode;
65     private @Nullable ScheduledFuture<?> refreshJob;
66     private @Nullable ScheduledFuture<?> immediateRefreshJob;
67     private @Nullable VerisureSession session;
68
69     public VerisureBridgeHandler(Bridge bridge, HttpClient httpClient) {
70         super(bridge);
71         this.httpClient = httpClient;
72     }
73
74     @Override
75     public void handleCommand(ChannelUID channelUID, Command command) {
76         logger.debug("VerisureBridgeHandler Handle command {} on channelUID: {}", command, channelUID);
77         if (command instanceof RefreshType) {
78             if (channelUID.getId().equals(CHANNEL_STATUS) && channelUID.getThingUID().equals(getThing().getUID())) {
79                 logger.debug("Refresh command on status channel {} will trigger instant refresh", channelUID);
80                 scheduleImmediateRefresh(0);
81             } else {
82                 logger.debug("Refresh command on channel {} will trigger refresh in {} seconds", channelUID,
83                         REFRESH_DELAY_SECONDS);
84                 scheduleImmediateRefresh(REFRESH_DELAY_SECONDS);
85             }
86         } else {
87             logger.warn("unknown command! {}", command);
88         }
89     }
90
91     public @Nullable VerisureSession getSession() {
92         return session;
93     }
94
95     public @Nullable ThingUID getUID() {
96         return getThing().getUID();
97     }
98
99     @Override
100     public void initialize() {
101         logger.debug("Initializing Verisure Binding");
102         VerisureBridgeConfiguration config = getConfigAs(VerisureBridgeConfiguration.class);
103
104         this.pinCode = config.pin;
105         if (config.username.isBlank() || config.password.isBlank()) {
106             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
107                     "Configuration of username and password is mandatory");
108
109         } else if (config.refresh < 10) {
110             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
111                     "Refresh time is lower than min value of 10!");
112         } else {
113             try {
114                 authstring = "j_username=" + config.username;
115                 scheduler.execute(() -> {
116                     if (session == null) {
117                         logger.debug("Session is null, let's create a new one");
118                         session = new VerisureSession(this.httpClient);
119                     }
120                     VerisureSession session = this.session;
121                     updateStatus(ThingStatus.UNKNOWN);
122                     if (session != null) {
123                         if (!session.initialize(authstring, pinCode, config.username, config.password)) {
124                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_REGISTERING_ERROR,
125                                     "Failed to login to Verisure, please check your account settings! Is MFA activated?");
126                             return;
127                         }
128                         startAutomaticRefresh(config.refresh);
129                     }
130                 });
131             } catch (RuntimeException e) {
132                 logger.warn("Failed to initialize: {}", e.getMessage());
133                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
134             }
135         }
136     }
137
138     @Override
139     public void dispose() {
140         logger.debug("Handler disposed.");
141         stopAutomaticRefresh();
142         stopImmediateRefresh();
143         session = null;
144     }
145
146     public <T extends VerisureThingDTO> boolean registerObjectStatusListener(
147             DeviceStatusListener<T> deviceStatusListener) {
148         VerisureSession mySession = session;
149         if (mySession != null) {
150             logger.debug("registerObjectStatusListener for listener {}", deviceStatusListener);
151             return mySession.registerDeviceStatusListener(deviceStatusListener);
152         }
153         return false;
154     }
155
156     public <T extends VerisureThingDTO> boolean unregisterObjectStatusListener(
157             DeviceStatusListener<T> deviceStatusListener) {
158         VerisureSession mySession = session;
159         if (mySession != null) {
160             logger.debug("unregisterObjectStatusListener for listener {}", deviceStatusListener);
161             return mySession.unregisterDeviceStatusListener(deviceStatusListener);
162         }
163         return false;
164     }
165
166     @Override
167     public Collection<Class<? extends ThingHandlerService>> getServices() {
168         return Collections.singleton(VerisureThingDiscoveryService.class);
169     }
170
171     private void refreshAndUpdateStatus() {
172         logger.debug("Refresh and update status!");
173         VerisureSession session = this.session;
174         if (session != null) {
175             boolean success = session.refresh();
176             if (success) {
177                 updateStatus(ThingStatus.ONLINE);
178             } else {
179                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
180             }
181         }
182     }
183
184     void scheduleImmediateRefresh(int refreshDelay) {
185         logger.debug("VerisureBridgeHandler - scheduleImmediateRefresh");
186         immediateRefreshJobLock.lock();
187         ScheduledFuture<?> refreshJob = this.refreshJob;
188         ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
189         try {
190             // We schedule in 10 sec, to avoid multiple updates
191             if (refreshJob != null) {
192                 logger.debug("Current remaining delay {} for refresh job {}", refreshJob.getDelay(TimeUnit.SECONDS),
193                         refreshJob);
194                 if (immediateRefreshJob != null) {
195                     logger.debug("Current remaining delay {} for immediate refresh job {}",
196                             immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
197                 }
198
199                 if (refreshJob.getDelay(TimeUnit.SECONDS) > refreshDelay) {
200                     if (immediateRefreshJob == null || immediateRefreshJob.getDelay(TimeUnit.SECONDS) <= 0) {
201                         if (immediateRefreshJob != null) {
202                             logger.debug("Current remaining delay {} for immediate refresh job {}",
203                                     immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
204                         }
205                         // Note we are using getDelay() instead of isDone() as we want to allow Things to schedule a
206                         // refresh if their status is pending. As the status update happens inside the
207                         // refreshAndUpdateStatus
208                         // execution the isDone() will return false and would not allow the rescheduling of the task.
209                         this.immediateRefreshJob = scheduler.schedule(this::refreshAndUpdateStatus, refreshDelay,
210                                 TimeUnit.SECONDS);
211                         logger.debug("Scheduling new immediate refresh job {}", immediateRefreshJob);
212                     }
213                 }
214             }
215         } catch (RejectedExecutionException e) {
216             logger.warn("Immediate refresh job cannot be scheduled!");
217         } finally {
218             immediateRefreshJobLock.unlock();
219         }
220     }
221
222     private void startAutomaticRefresh(int refresh) {
223         ScheduledFuture<?> refreshJob = this.refreshJob;
224         logger.debug("Start automatic refresh {}", refreshJob);
225         if (refreshJob == null || refreshJob.isCancelled()) {
226             try {
227                 this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, refresh,
228                         TimeUnit.SECONDS);
229                 logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
230             } catch (RejectedExecutionException e) {
231                 logger.warn("Automatic refresh job cannot be started!");
232             }
233         }
234     }
235
236     private void stopAutomaticRefresh() {
237         ScheduledFuture<?> refreshJob = this.refreshJob;
238         logger.debug("Stop automatic refresh for job {}", refreshJob);
239         if (refreshJob != null) {
240             refreshJob.cancel(true);
241             this.refreshJob = null;
242         }
243     }
244
245     private void stopImmediateRefresh() {
246         immediateRefreshJobLock.lock();
247         ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
248         try {
249             logger.debug("Stop immediate refresh for job {}", immediateRefreshJob);
250             if (immediateRefreshJob != null) {
251                 immediateRefreshJob.cancel(true);
252                 this.immediateRefreshJob = null;
253             }
254         } catch (RejectedExecutionException e) {
255             logger.warn("Immediate refresh job cannot be scheduled!");
256         } finally {
257             immediateRefreshJobLock.unlock();
258         }
259     }
260 }