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