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;
19 import java.util.concurrent.RejectedExecutionException;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22 import java.util.concurrent.locks.ReentrantLock;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.eclipse.jetty.client.HttpClient;
27 import org.openhab.binding.verisure.internal.DeviceStatusListener;
28 import org.openhab.binding.verisure.internal.VerisureBridgeConfiguration;
29 import org.openhab.binding.verisure.internal.VerisureSession;
30 import org.openhab.binding.verisure.internal.discovery.VerisureThingDiscoveryService;
31 import org.openhab.binding.verisure.internal.dto.VerisureThingDTO;
32 import org.openhab.core.thing.Bridge;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.ThingStatus;
35 import org.openhab.core.thing.ThingStatusDetail;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.thing.ThingUID;
38 import org.openhab.core.thing.binding.BaseBridgeHandler;
39 import org.openhab.core.thing.binding.ThingHandlerService;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link VerisureBridgeHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author l3rum - Initial contribution
50 * @author Jan Gustafsson - Furher development
53 public class VerisureBridgeHandler extends BaseBridgeHandler {
55 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE);
57 private static final int REFRESH_DELAY_SECONDS = 30;
58 private final Logger logger = LoggerFactory.getLogger(VerisureBridgeHandler.class);
59 private final ReentrantLock immediateRefreshJobLock = new ReentrantLock();
60 private final HttpClient httpClient;
62 private String authstring = "";
63 private @Nullable String pinCode;
64 private @Nullable ScheduledFuture<?> refreshJob;
65 private @Nullable ScheduledFuture<?> immediateRefreshJob;
66 private @Nullable VerisureSession session;
68 public VerisureBridgeHandler(Bridge bridge, HttpClient httpClient) {
70 this.httpClient = httpClient;
74 public void handleCommand(ChannelUID channelUID, Command command) {
75 logger.debug("VerisureBridgeHandler Handle command {} on channelUID: {}", command, channelUID);
76 if (command instanceof RefreshType) {
77 if (channelUID.getId().equals(CHANNEL_STATUS) && channelUID.getThingUID().equals(getThing().getUID())) {
78 logger.debug("Refresh command on status channel {} will trigger instant refresh", channelUID);
79 scheduleImmediateRefresh(0);
81 logger.debug("Refresh command on channel {} will trigger refresh in {} seconds", channelUID,
82 REFRESH_DELAY_SECONDS);
83 scheduleImmediateRefresh(REFRESH_DELAY_SECONDS);
86 logger.warn("unknown command! {}", command);
90 public @Nullable VerisureSession getSession() {
94 public @Nullable ThingUID getUID() {
95 return getThing().getUID();
99 public void initialize() {
100 logger.debug("Initializing Verisure Binding");
101 VerisureBridgeConfiguration config = getConfigAs(VerisureBridgeConfiguration.class);
103 this.pinCode = config.pin;
104 if (config.username.isBlank() || config.password.isBlank()) {
105 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
106 "Configuration of username and password is mandatory");
108 } else if (config.refresh < 10) {
109 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110 "Refresh time is lower than min value of 10!");
112 authstring = "j_username=" + config.username;
113 scheduler.execute(() -> {
114 if (session == null) {
115 logger.debug("Session is null, let's create a new one");
116 session = new VerisureSession(this.httpClient);
118 VerisureSession session = this.session;
119 updateStatus(ThingStatus.UNKNOWN);
120 if (session != null) {
121 if (!session.initialize(authstring, pinCode, config.username, config.password)) {
122 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
123 "Failed to login to Verisure, please check your account settings! Is MFA activated?");
126 startAutomaticRefresh(config.refresh);
132 public void dispose() {
133 logger.debug("Handler disposed.");
134 stopAutomaticRefresh();
135 stopImmediateRefresh();
139 public <T extends VerisureThingDTO> boolean registerObjectStatusListener(
140 DeviceStatusListener<T> deviceStatusListener) {
141 VerisureSession mySession = session;
142 if (mySession != null) {
143 logger.debug("registerObjectStatusListener for listener {}", deviceStatusListener);
144 return mySession.registerDeviceStatusListener(deviceStatusListener);
149 public <T extends VerisureThingDTO> boolean unregisterObjectStatusListener(
150 DeviceStatusListener<T> deviceStatusListener) {
151 VerisureSession mySession = session;
152 if (mySession != null) {
153 logger.debug("unregisterObjectStatusListener for listener {}", deviceStatusListener);
154 return mySession.unregisterDeviceStatusListener(deviceStatusListener);
160 public Collection<Class<? extends ThingHandlerService>> getServices() {
161 return Set.of(VerisureThingDiscoveryService.class);
164 private void refreshAndUpdateStatus() {
165 logger.debug("Refresh and update status!");
166 VerisureSession session = this.session;
167 if (session != null) {
168 if (session.refresh()) {
169 updateStatus(ThingStatus.ONLINE);
171 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
176 void scheduleImmediateRefresh(int refreshDelay) {
177 logger.debug("VerisureBridgeHandler - scheduleImmediateRefresh");
178 immediateRefreshJobLock.lock();
179 ScheduledFuture<?> refreshJob = this.refreshJob;
180 ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
182 // We schedule in 10 sec, to avoid multiple updates
183 if (refreshJob != null) {
184 logger.debug("Current remaining delay {} for refresh job {}", refreshJob.getDelay(TimeUnit.SECONDS),
186 if (immediateRefreshJob != null) {
187 logger.debug("Current remaining delay {} for immediate refresh job {}",
188 immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
191 if (refreshJob.getDelay(TimeUnit.SECONDS) > refreshDelay) {
192 if (immediateRefreshJob == null || immediateRefreshJob.getDelay(TimeUnit.SECONDS) <= 0) {
193 if (immediateRefreshJob != null) {
194 logger.debug("Current remaining delay {} for immediate refresh job {}",
195 immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
197 // Note we are using getDelay() instead of isDone() as we want to allow Things to schedule a
198 // refresh if their status is pending. As the status update happens inside the
199 // refreshAndUpdateStatus
200 // execution the isDone() will return false and would not allow the rescheduling of the task.
201 this.immediateRefreshJob = scheduler.schedule(this::refreshAndUpdateStatus, refreshDelay,
203 logger.debug("Scheduling new immediate refresh job {}", immediateRefreshJob);
207 } catch (RejectedExecutionException e) {
208 logger.warn("Immediate refresh job cannot be scheduled!");
210 immediateRefreshJobLock.unlock();
214 private void startAutomaticRefresh(int refresh) {
215 ScheduledFuture<?> refreshJob = this.refreshJob;
216 logger.debug("Start automatic refresh {}", refreshJob);
217 if (refreshJob == null || refreshJob.isCancelled()) {
219 this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, refresh,
221 logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
222 } catch (RejectedExecutionException e) {
223 logger.warn("Automatic refresh job cannot be started!");
228 private void stopAutomaticRefresh() {
229 ScheduledFuture<?> refreshJob = this.refreshJob;
230 logger.debug("Stop automatic refresh for job {}", refreshJob);
231 if (refreshJob != null) {
232 refreshJob.cancel(true);
233 this.refreshJob = null;
237 private void stopImmediateRefresh() {
238 immediateRefreshJobLock.lock();
239 ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
241 logger.debug("Stop immediate refresh for job {}", immediateRefreshJob);
242 if (immediateRefreshJob != null) {
243 immediateRefreshJob.cancel(true);
244 this.immediateRefreshJob = null;
246 } catch (RejectedExecutionException e) {
247 logger.warn("Immediate refresh job cannot be scheduled!");
249 immediateRefreshJobLock.unlock();