2 * Copyright (c) 2010-2021 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.util.Collection;
18 import java.util.Collections;
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;
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;
47 * The {@link VerisureBridgeHandler} is responsible for handling commands, which are
48 * sent to one of the channels.
50 * @author l3rum - Initial contribution
51 * @author Jan Gustafsson - Furher development
54 public class VerisureBridgeHandler extends BaseBridgeHandler {
56 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
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;
63 private String authstring = "";
64 private @Nullable String pinCode;
65 private @Nullable ScheduledFuture<?> refreshJob;
66 private @Nullable ScheduledFuture<?> immediateRefreshJob;
67 private @Nullable VerisureSession session;
69 public VerisureBridgeHandler(Bridge bridge, HttpClient httpClient) {
71 this.httpClient = httpClient;
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);
82 logger.debug("Refresh command on channel {} will trigger refresh in {} seconds", channelUID,
83 REFRESH_DELAY_SECONDS);
84 scheduleImmediateRefresh(REFRESH_DELAY_SECONDS);
87 logger.warn("unknown command! {}", command);
91 public @Nullable VerisureSession getSession() {
95 public @Nullable ThingUID getUID() {
96 return getThing().getUID();
100 public void initialize() {
101 logger.debug("Initializing Verisure Binding");
102 VerisureBridgeConfiguration config = getConfigAs(VerisureBridgeConfiguration.class);
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");
109 } else if (config.refresh < 10) {
110 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
111 "Refresh time is lower than min value of 10!");
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);
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?");
128 startAutomaticRefresh(config.refresh);
131 } catch (RuntimeException e) {
132 logger.warn("Failed to initialize: {}", e.getMessage());
133 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
139 public void dispose() {
140 logger.debug("Handler disposed.");
141 stopAutomaticRefresh();
142 stopImmediateRefresh();
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);
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);
167 public Collection<Class<? extends ThingHandlerService>> getServices() {
168 return Collections.singleton(VerisureThingDiscoveryService.class);
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();
177 updateStatus(ThingStatus.ONLINE);
179 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
184 void scheduleImmediateRefresh(int refreshDelay) {
185 logger.debug("VerisureBridgeHandler - scheduleImmediateRefresh");
186 immediateRefreshJobLock.lock();
187 ScheduledFuture<?> refreshJob = this.refreshJob;
188 ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
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),
194 if (immediateRefreshJob != null) {
195 logger.debug("Current remaining delay {} for immediate refresh job {}",
196 immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
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);
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,
211 logger.debug("Scheduling new immediate refresh job {}", immediateRefreshJob);
215 } catch (RejectedExecutionException e) {
216 logger.warn("Immediate refresh job cannot be scheduled!");
218 immediateRefreshJobLock.unlock();
222 private void startAutomaticRefresh(int refresh) {
223 ScheduledFuture<?> refreshJob = this.refreshJob;
224 logger.debug("Start automatic refresh {}", refreshJob);
225 if (refreshJob == null || refreshJob.isCancelled()) {
227 this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, refresh,
229 logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
230 } catch (RejectedExecutionException e) {
231 logger.warn("Automatic refresh job cannot be started!");
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;
245 private void stopImmediateRefresh() {
246 immediateRefreshJobLock.lock();
247 ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
249 logger.debug("Stop immediate refresh for job {}", immediateRefreshJob);
250 if (immediateRefreshJob != null) {
251 immediateRefreshJob.cancel(true);
252 this.immediateRefreshJob = null;
254 } catch (RejectedExecutionException e) {
255 logger.warn("Immediate refresh job cannot be scheduled!");
257 immediateRefreshJobLock.unlock();