2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.verisure.internal.handler;
15 import static org.openhab.binding.verisure.internal.VerisureBindingConstants.*;
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;
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;
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;
50 * The {@link VerisureBridgeHandler} is responsible for handling commands, which are
51 * sent to one of the channels.
53 * @author l3rum - Initial contribution
54 * @author Jan Gustafsson - Furher development
57 public class VerisureBridgeHandler extends BaseBridgeHandler {
59 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
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;
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;
73 public VerisureBridgeHandler(Bridge bridge, HttpClient httpClient) {
75 this.httpClient = httpClient;
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);
86 logger.debug("Refresh command on channel {} will trigger refresh in {} seconds", channelUID,
87 REFRESH_DELAY_SECONDS);
88 scheduleImmediateRefresh(REFRESH_DELAY_SECONDS);
91 logger.warn("unknown command! {}", command);
95 public @Nullable VerisureSession getSession() {
99 public @Nullable ThingUID getUID() {
100 return getThing().getUID();
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!");
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(() -> {
121 if (session == null) {
122 logger.debug("Session is null, let's create a new one");
123 session = new VerisureSession(this.httpClient);
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!");
134 startAutomaticRefresh();
137 } catch (RuntimeException | UnsupportedEncodingException e) {
138 logger.warn("Failed to initialize: {}", e.getMessage());
139 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
145 public void dispose() {
146 logger.debug("Handler disposed.");
147 stopAutomaticRefresh();
148 stopImmediateRefresh();
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);
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);
173 public Collection<Class<? extends ThingHandlerService>> getServices() {
174 return Collections.singleton(VerisureThingDiscoveryService.class);
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();
183 updateStatus(ThingStatus.ONLINE);
185 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
190 void scheduleImmediateRefresh(int refreshDelay) {
191 logger.debug("VerisureBridgeHandler - scheduleImmediateRefresh");
192 immediateRefreshJobLock.lock();
193 ScheduledFuture<?> refreshJob = this.refreshJob;
194 ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
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),
200 if (immediateRefreshJob != null) {
201 logger.debug("Current remaining delay {} for immediate refresh job {}",
202 immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
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);
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,
217 logger.debug("Scheduling new immediate refresh job {}", immediateRefreshJob);
221 } catch (RejectedExecutionException e) {
222 logger.warn("Immediate refresh job cannot be scheduled!");
224 immediateRefreshJobLock.unlock();
228 private void startAutomaticRefresh() {
229 ScheduledFuture<?> refreshJob = this.refreshJob;
230 logger.debug("Start automatic refresh {}", refreshJob);
231 if (refreshJob == null || refreshJob.isCancelled()) {
233 this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, REFRESH_SEC,
235 logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
236 } catch (RejectedExecutionException e) {
237 logger.warn("Automatic refresh job cannot be started!");
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;
251 private void stopImmediateRefresh() {
252 immediateRefreshJobLock.lock();
253 ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
255 logger.debug("Stop immediate refresh for job {}", immediateRefreshJob);
256 if (immediateRefreshJob != null) {
257 immediateRefreshJob.cancel(true);
258 this.immediateRefreshJob = null;
260 } catch (RejectedExecutionException e) {
261 logger.warn("Immediate refresh job cannot be scheduled!");
263 immediateRefreshJobLock.unlock();