]> git.basschouten.com Git - openhab-addons.git/blob
04b8b5698bdcd47451e2214cf612a695cdacb302
[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.nibeuplink.internal.connector;
14
15 import static org.openhab.binding.nibeuplink.internal.NibeUplinkBindingConstants.*;
16
17 import java.util.Queue;
18 import java.util.concurrent.Future;
19 import java.util.concurrent.ScheduledExecutorService;
20 import java.util.concurrent.TimeUnit;
21 import java.util.concurrent.atomic.AtomicReference;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.eclipse.jetty.client.HttpClient;
26 import org.eclipse.jetty.util.BlockingArrayQueue;
27 import org.openhab.binding.nibeuplink.internal.AtomicReferenceTrait;
28 import org.openhab.binding.nibeuplink.internal.command.Login;
29 import org.openhab.binding.nibeuplink.internal.command.NibeUplinkCommand;
30 import org.openhab.binding.nibeuplink.internal.config.NibeUplinkConfiguration;
31 import org.openhab.binding.nibeuplink.internal.handler.NibeUplinkHandler;
32 import org.openhab.core.thing.ThingStatus;
33 import org.openhab.core.thing.ThingStatusDetail;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 /**
38  * This class handles requests to the NibeUplink web interface. It manages authentication and wraps commands.
39  *
40  * @author Alexander Friese - initial contribution
41  */
42 @NonNullByDefault
43 public class UplinkWebInterface implements AtomicReferenceTrait {
44
45     private static final int NIBE_ID_THRESHOLD = 14;
46
47     private final Logger logger = LoggerFactory.getLogger(UplinkWebInterface.class);
48
49     /**
50      * Configuration
51      */
52     private NibeUplinkConfiguration config;
53
54     /**
55      * handler for updating thing status
56      */
57     private final NibeUplinkHandler uplinkHandler;
58
59     /**
60      * holds authentication status
61      */
62     private boolean authenticated = false;
63
64     /**
65      * HTTP client for asynchronous calls
66      */
67     private final HttpClient httpClient;
68
69     /**
70      * the scheduler which periodically sends web requests to the solaredge API. Should be initiated with the thing's
71      * existing scheduler instance.
72      */
73     private final ScheduledExecutorService scheduler;
74
75     /**
76      * request executor
77      */
78     private final WebRequestExecutor requestExecutor;
79
80     /**
81      * periodic request executor job
82      */
83     private AtomicReference<@Nullable Future<?>> requestExecutorJobReference = new AtomicReference<>(null);
84
85     /**
86      * this class is responsible for executing periodic web requests. This ensures that only one request is executed at
87      * the same time and there will be a guaranteed minimum delay between subsequent requests.
88      *
89      * @author afriese - initial contribution
90      */
91     @NonNullByDefault
92     private class WebRequestExecutor implements Runnable {
93
94         /**
95          * queue which holds the commands to execute
96          */
97         private final Queue<@Nullable NibeUplinkCommand> commandQueue;
98
99         /**
100          * constructor
101          */
102         WebRequestExecutor() {
103             this.commandQueue = new BlockingArrayQueue<>(WEB_REQUEST_QUEUE_MAX_SIZE);
104         }
105
106         /**
107          * puts a command into the queue
108          *
109          * @param command the command which will be queued
110          */
111         void enqueue(NibeUplinkCommand command) {
112             try {
113                 commandQueue.add(command);
114             } catch (IllegalStateException ex) {
115                 if (commandQueue.size() >= WEB_REQUEST_QUEUE_MAX_SIZE) {
116                     logger.debug(
117                             "Could not add command to command queue because queue is already full. Maybe NIBE Uplink is down?");
118                 } else {
119                     logger.warn("Could not add command to queue - IllegalStateException");
120                 }
121             }
122         }
123
124         /**
125          * executes the web request
126          */
127         @Override
128         public void run() {
129             if (!isAuthenticated()) {
130                 authenticate();
131             }
132
133             else if (isAuthenticated() && !commandQueue.isEmpty()) {
134                 StatusUpdateListener statusUpdater = new StatusUpdateListener() {
135                     @Override
136                     public void update(CommunicationStatus status) {
137                         switch (status.getHttpCode()) {
138                             case SERVICE_UNAVAILABLE:
139                                 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
140                                         status.getMessage());
141                                 setAuthenticated(false);
142                                 break;
143                             case FOUND:
144                                 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
145                                         "most likely your NibeId is wrong. please check your NibeId.");
146                                 setAuthenticated(false);
147                                 break;
148                             case OK:
149                                 // no action needed as the thing is already online.
150                                 break;
151                             default:
152                                 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
153                                         status.getMessage());
154                                 setAuthenticated(false);
155                         }
156                     }
157                 };
158
159                 NibeUplinkCommand command = commandQueue.poll();
160                 if (command != null) {
161                     command.setListener(statusUpdater);
162                     command.performAction(httpClient);
163                 }
164             }
165         }
166     }
167
168     /**
169      * Constructor to set up interface
170      *
171      * @param config the Bridge configuration
172      */
173     public UplinkWebInterface(ScheduledExecutorService scheduler, NibeUplinkHandler handler, HttpClient httpClient) {
174         this.config = handler.getConfiguration();
175         this.uplinkHandler = handler;
176         this.scheduler = scheduler;
177         this.requestExecutor = new WebRequestExecutor();
178         this.httpClient = httpClient;
179     }
180
181     /**
182      * starts the periodic request executor job which handles all web requests
183      */
184     public void start() {
185         this.config = uplinkHandler.getConfiguration();
186         setAuthenticated(false);
187         updateJobReference(requestExecutorJobReference, scheduler.scheduleWithFixedDelay(requestExecutor,
188                 WEB_REQUEST_INITIAL_DELAY, WEB_REQUEST_INTERVAL, TimeUnit.MILLISECONDS));
189     }
190
191     /**
192      * queues any command for execution
193      *
194      * @param command the command which will be put into the queue
195      */
196     public void enqueueCommand(NibeUplinkCommand command) {
197         requestExecutor.enqueue(command);
198     }
199
200     /**
201      * authenticates with the Nibe Uplink WEB interface
202      */
203     private synchronized void authenticate() {
204         setAuthenticated(false);
205
206         if (preCheck()) {
207             StatusUpdateListener statusUpdater = new StatusUpdateListener() {
208
209                 @Override
210                 public void update(CommunicationStatus status) {
211                     switch (status.getHttpCode()) {
212                         case FOUND:
213                             uplinkHandler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, "logged in");
214                             setAuthenticated(true);
215                             break;
216                         case OK:
217                             uplinkHandler.setStatusInfo(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
218                                     "invalid username or password");
219                             setAuthenticated(false);
220                             break;
221                         case SERVICE_UNAVAILABLE:
222                             uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
223                                     status.getMessage());
224                             setAuthenticated(false);
225                             break;
226                         default:
227                             uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
228                                     status.getMessage());
229                             setAuthenticated(false);
230                     }
231                 }
232             };
233
234             new Login(uplinkHandler, statusUpdater).performAction(httpClient);
235         }
236     }
237
238     /**
239      * performs some pre cheks on configuration before attempting to login
240      *
241      * @return true on success, false otherwise
242      */
243     private boolean preCheck() {
244         String preCheckStatusMessage = "";
245         String localPassword = config.getPassword();
246         String localUser = config.getUser();
247         String localNibeId = config.getNibeId();
248
249         if (localPassword == null || localPassword.isEmpty()) {
250             preCheckStatusMessage = "please configure password first";
251         } else if (localUser == null || localUser.isEmpty()) {
252             preCheckStatusMessage = "please configure user first";
253         } else if (localNibeId == null || localNibeId.isEmpty()) {
254             preCheckStatusMessage = "please configure nibeId first";
255         } else if (localNibeId.length() > NIBE_ID_THRESHOLD) {
256             preCheckStatusMessage = "your NibeId is too long. Please refer to the documentation on how to set this value.";
257         } else {
258             return true;
259         }
260
261         this.uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
262                 preCheckStatusMessage);
263         return false;
264     }
265
266     /**
267      * will be called by the ThingHandler to abort periodic jobs.
268      */
269     public void dispose() {
270         logger.debug("Webinterface disposed.");
271         cancelJobReference(requestExecutorJobReference);
272         setAuthenticated(false);
273     }
274
275     private boolean isAuthenticated() {
276         return authenticated;
277     }
278
279     private void setAuthenticated(boolean authenticated) {
280         this.authenticated = authenticated;
281     }
282 }