]> git.basschouten.com Git - openhab-addons.git/blob
5530a131cc78d9708f8ad9aee213daa272b06ed2
[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.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                             dispose();
133                             initialize();
134                             return;
135                         }
136                         startAutomaticRefresh();
137                     }
138                 });
139             } catch (RuntimeException | UnsupportedEncodingException e) {
140                 logger.warn("Failed to initialize: {}", e.getMessage());
141                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
142             }
143         }
144     }
145
146     @Override
147     public void dispose() {
148         logger.debug("Handler disposed.");
149         stopAutomaticRefresh();
150         stopImmediateRefresh();
151         session = null;
152     }
153
154     public <T extends VerisureThingDTO> boolean registerObjectStatusListener(
155             DeviceStatusListener<T> deviceStatusListener) {
156         VerisureSession mySession = session;
157         if (mySession != null) {
158             logger.debug("registerObjectStatusListener for listener {}", deviceStatusListener);
159             return mySession.registerDeviceStatusListener(deviceStatusListener);
160         }
161         return false;
162     }
163
164     public <T extends VerisureThingDTO> boolean unregisterObjectStatusListener(
165             DeviceStatusListener<T> deviceStatusListener) {
166         VerisureSession mySession = session;
167         if (mySession != null) {
168             logger.debug("unregisterObjectStatusListener for listener {}", deviceStatusListener);
169             return mySession.unregisterDeviceStatusListener(deviceStatusListener);
170         }
171         return false;
172     }
173
174     @Override
175     public Collection<Class<? extends ThingHandlerService>> getServices() {
176         return Collections.singleton(VerisureThingDiscoveryService.class);
177     }
178
179     private void refreshAndUpdateStatus() {
180         logger.debug("Refresh and update status!");
181         VerisureSession session = this.session;
182         if (session != null) {
183             boolean success = session.refresh();
184             if (success) {
185                 updateStatus(ThingStatus.ONLINE);
186             } else {
187                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
188             }
189         }
190     }
191
192     void scheduleImmediateRefresh(int refreshDelay) {
193         logger.debug("VerisureBridgeHandler - scheduleImmediateRefresh");
194         immediateRefreshJobLock.lock();
195         ScheduledFuture<?> refreshJob = this.refreshJob;
196         ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
197         try {
198             // We schedule in 10 sec, to avoid multiple updates
199             if (refreshJob != null) {
200                 logger.debug("Current remaining delay {} for refresh job {}", refreshJob.getDelay(TimeUnit.SECONDS),
201                         refreshJob);
202                 if (immediateRefreshJob != null) {
203                     logger.debug("Current remaining delay {} for immediate refresh job {}",
204                             immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
205                 }
206
207                 if (refreshJob.getDelay(TimeUnit.SECONDS) > refreshDelay) {
208                     if (immediateRefreshJob == null || immediateRefreshJob.getDelay(TimeUnit.SECONDS) <= 0) {
209                         if (immediateRefreshJob != null) {
210                             logger.debug("Current remaining delay {} for immediate refresh job {}",
211                                     immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
212                         }
213                         // Note we are using getDelay() instead of isDone() as we want to allow Things to schedule a
214                         // refresh if their status is pending. As the status update happens inside the
215                         // refreshAndUpdateStatus
216                         // execution the isDone() will return false and would not allow the rescheduling of the task.
217                         this.immediateRefreshJob = scheduler.schedule(this::refreshAndUpdateStatus, refreshDelay,
218                                 TimeUnit.SECONDS);
219                         logger.debug("Scheduling new immediate refresh job {}", immediateRefreshJob);
220                     }
221                 }
222             }
223         } catch (RejectedExecutionException e) {
224             logger.warn("Immediate refresh job cannot be scheduled!");
225         } finally {
226             immediateRefreshJobLock.unlock();
227         }
228     }
229
230     private void startAutomaticRefresh() {
231         ScheduledFuture<?> refreshJob = this.refreshJob;
232         logger.debug("Start automatic refresh {}", refreshJob);
233         if (refreshJob == null || refreshJob.isCancelled()) {
234             try {
235                 this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, REFRESH_SEC,
236                         TimeUnit.SECONDS);
237                 logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
238             } catch (RejectedExecutionException e) {
239                 logger.warn("Automatic refresh job cannot be started!");
240             }
241         }
242     }
243
244     private void stopAutomaticRefresh() {
245         ScheduledFuture<?> refreshJob = this.refreshJob;
246         logger.debug("Stop automatic refresh for job {}", refreshJob);
247         if (refreshJob != null) {
248             refreshJob.cancel(true);
249             this.refreshJob = null;
250         }
251     }
252
253     private void stopImmediateRefresh() {
254         immediateRefreshJobLock.lock();
255         ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
256         try {
257             logger.debug("Stop immediate refresh for job {}", immediateRefreshJob);
258             if (immediateRefreshJob != null) {
259                 immediateRefreshJob.cancel(true);
260                 this.immediateRefreshJob = null;
261             }
262         } catch (RejectedExecutionException e) {
263             logger.warn("Immediate refresh job cannot be scheduled!");
264         } finally {
265             immediateRefreshJobLock.unlock();
266         }
267     }
268 }