2 * Copyright (c) 2010-2023 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!");
113 authstring = "j_username=" + config.username;
114 scheduler.execute(() -> {
115 if (session == null) {
116 logger.debug("Session is null, let's create a new one");
117 session = new VerisureSession(this.httpClient);
119 VerisureSession session = this.session;
120 updateStatus(ThingStatus.UNKNOWN);
121 if (session != null) {
122 if (!session.initialize(authstring, pinCode, config.username, config.password)) {
123 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
124 "Failed to login to Verisure, please check your account settings! Is MFA activated?");
127 startAutomaticRefresh(config.refresh);
133 public void dispose() {
134 logger.debug("Handler disposed.");
135 stopAutomaticRefresh();
136 stopImmediateRefresh();
140 public <T extends VerisureThingDTO> boolean registerObjectStatusListener(
141 DeviceStatusListener<T> deviceStatusListener) {
142 VerisureSession mySession = session;
143 if (mySession != null) {
144 logger.debug("registerObjectStatusListener for listener {}", deviceStatusListener);
145 return mySession.registerDeviceStatusListener(deviceStatusListener);
150 public <T extends VerisureThingDTO> boolean unregisterObjectStatusListener(
151 DeviceStatusListener<T> deviceStatusListener) {
152 VerisureSession mySession = session;
153 if (mySession != null) {
154 logger.debug("unregisterObjectStatusListener for listener {}", deviceStatusListener);
155 return mySession.unregisterDeviceStatusListener(deviceStatusListener);
161 public Collection<Class<? extends ThingHandlerService>> getServices() {
162 return Collections.singleton(VerisureThingDiscoveryService.class);
165 private void refreshAndUpdateStatus() {
166 logger.debug("Refresh and update status!");
167 VerisureSession session = this.session;
168 if (session != null) {
169 if (session.refresh()) {
170 updateStatus(ThingStatus.ONLINE);
172 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
177 void scheduleImmediateRefresh(int refreshDelay) {
178 logger.debug("VerisureBridgeHandler - scheduleImmediateRefresh");
179 immediateRefreshJobLock.lock();
180 ScheduledFuture<?> refreshJob = this.refreshJob;
181 ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
183 // We schedule in 10 sec, to avoid multiple updates
184 if (refreshJob != null) {
185 logger.debug("Current remaining delay {} for refresh job {}", refreshJob.getDelay(TimeUnit.SECONDS),
187 if (immediateRefreshJob != null) {
188 logger.debug("Current remaining delay {} for immediate refresh job {}",
189 immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
192 if (refreshJob.getDelay(TimeUnit.SECONDS) > refreshDelay) {
193 if (immediateRefreshJob == null || immediateRefreshJob.getDelay(TimeUnit.SECONDS) <= 0) {
194 if (immediateRefreshJob != null) {
195 logger.debug("Current remaining delay {} for immediate refresh job {}",
196 immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
198 // Note we are using getDelay() instead of isDone() as we want to allow Things to schedule a
199 // refresh if their status is pending. As the status update happens inside the
200 // refreshAndUpdateStatus
201 // execution the isDone() will return false and would not allow the rescheduling of the task.
202 this.immediateRefreshJob = scheduler.schedule(this::refreshAndUpdateStatus, refreshDelay,
204 logger.debug("Scheduling new immediate refresh job {}", immediateRefreshJob);
208 } catch (RejectedExecutionException e) {
209 logger.warn("Immediate refresh job cannot be scheduled!");
211 immediateRefreshJobLock.unlock();
215 private void startAutomaticRefresh(int refresh) {
216 ScheduledFuture<?> refreshJob = this.refreshJob;
217 logger.debug("Start automatic refresh {}", refreshJob);
218 if (refreshJob == null || refreshJob.isCancelled()) {
220 this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, refresh,
222 logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
223 } catch (RejectedExecutionException e) {
224 logger.warn("Automatic refresh job cannot be started!");
229 private void stopAutomaticRefresh() {
230 ScheduledFuture<?> refreshJob = this.refreshJob;
231 logger.debug("Stop automatic refresh for job {}", refreshJob);
232 if (refreshJob != null) {
233 refreshJob.cancel(true);
234 this.refreshJob = null;
238 private void stopImmediateRefresh() {
239 immediateRefreshJobLock.lock();
240 ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
242 logger.debug("Stop immediate refresh for job {}", immediateRefreshJob);
243 if (immediateRefreshJob != null) {
244 immediateRefreshJob.cancel(true);
245 this.immediateRefreshJob = null;
247 } catch (RejectedExecutionException e) {
248 logger.warn("Immediate refresh job cannot be scheduled!");
250 immediateRefreshJobLock.unlock();