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