]> git.basschouten.com Git - openhab-addons.git/blob
2479cc7200382f45856cf799d6c2cba0aae234eb
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.util.Collection;
18 import java.util.Set;
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;
23
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;
44
45 /**
46  * The {@link VerisureBridgeHandler} is responsible for handling commands, which are
47  * sent to one of the channels.
48  *
49  * @author l3rum - Initial contribution
50  * @author Jan Gustafsson - Furher development
51  */
52 @NonNullByDefault
53 public class VerisureBridgeHandler extends BaseBridgeHandler {
54
55     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_BRIDGE);
56
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;
61
62     private String authstring = "";
63     private @Nullable String pinCode;
64     private @Nullable ScheduledFuture<?> refreshJob;
65     private @Nullable ScheduledFuture<?> immediateRefreshJob;
66     private @Nullable VerisureSession session;
67
68     public VerisureBridgeHandler(Bridge bridge, HttpClient httpClient) {
69         super(bridge);
70         this.httpClient = httpClient;
71     }
72
73     @Override
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);
80             } else {
81                 logger.debug("Refresh command on channel {} will trigger refresh in {} seconds", channelUID,
82                         REFRESH_DELAY_SECONDS);
83                 scheduleImmediateRefresh(REFRESH_DELAY_SECONDS);
84             }
85         } else {
86             logger.warn("unknown command! {}", command);
87         }
88     }
89
90     public @Nullable VerisureSession getSession() {
91         return session;
92     }
93
94     public @Nullable ThingUID getUID() {
95         return getThing().getUID();
96     }
97
98     @Override
99     public void initialize() {
100         logger.debug("Initializing Verisure Binding");
101         VerisureBridgeConfiguration config = getConfigAs(VerisureBridgeConfiguration.class);
102
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");
107
108         } else if (config.refresh < 10) {
109             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
110                     "Refresh time is lower than min value of 10!");
111         } else {
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);
117                 }
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?");
124                     }
125                 }
126                 startAutomaticRefresh(config.refresh);
127             });
128         }
129     }
130
131     @Override
132     public void dispose() {
133         logger.debug("Handler disposed.");
134         stopAutomaticRefresh();
135         stopImmediateRefresh();
136         session = null;
137     }
138
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);
145         }
146         return false;
147     }
148
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);
155         }
156         return false;
157     }
158
159     @Override
160     public Collection<Class<? extends ThingHandlerService>> getServices() {
161         return Set.of(VerisureThingDiscoveryService.class);
162     }
163
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);
170             } else {
171                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
172             }
173         }
174     }
175
176     void scheduleImmediateRefresh(int refreshDelay) {
177         logger.debug("VerisureBridgeHandler - scheduleImmediateRefresh");
178         immediateRefreshJobLock.lock();
179         ScheduledFuture<?> refreshJob = this.refreshJob;
180         ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
181         try {
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),
185                         refreshJob);
186                 if (immediateRefreshJob != null) {
187                     logger.debug("Current remaining delay {} for immediate refresh job {}",
188                             immediateRefreshJob.getDelay(TimeUnit.SECONDS), immediateRefreshJob);
189                 }
190
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);
196                         }
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,
202                                 TimeUnit.SECONDS);
203                         logger.debug("Scheduling new immediate refresh job {}", immediateRefreshJob);
204                     }
205                 }
206             }
207         } catch (RejectedExecutionException e) {
208             logger.warn("Immediate refresh job cannot be scheduled!");
209         } finally {
210             immediateRefreshJobLock.unlock();
211         }
212     }
213
214     private void startAutomaticRefresh(int refresh) {
215         ScheduledFuture<?> refreshJob = this.refreshJob;
216         logger.debug("Start automatic refresh {}", refreshJob);
217         if (refreshJob == null || refreshJob.isCancelled()) {
218             try {
219                 this.refreshJob = scheduler.scheduleWithFixedDelay(this::refreshAndUpdateStatus, 0, refresh,
220                         TimeUnit.SECONDS);
221                 logger.debug("Scheduling at fixed delay refreshjob {}", this.refreshJob);
222             } catch (RejectedExecutionException e) {
223                 logger.warn("Automatic refresh job cannot be started!");
224             }
225         }
226     }
227
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;
234         }
235     }
236
237     private void stopImmediateRefresh() {
238         immediateRefreshJobLock.lock();
239         ScheduledFuture<?> immediateRefreshJob = this.immediateRefreshJob;
240         try {
241             logger.debug("Stop immediate refresh for job {}", immediateRefreshJob);
242             if (immediateRefreshJob != null) {
243                 immediateRefreshJob.cancel(true);
244                 this.immediateRefreshJob = null;
245             }
246         } catch (RejectedExecutionException e) {
247             logger.warn("Immediate refresh job cannot be scheduled!");
248         } finally {
249             immediateRefreshJobLock.unlock();
250         }
251     }
252 }