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