]> git.basschouten.com Git - openhab-addons.git/blob
a50ffe7c6a93604fdddeed21333713da2642d8d6
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2024 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.handler.NibeUplinkHandler;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * This class handles requests to the NibeUplink web interface. It manages authentication and wraps commands.
38  *
39  * @author Alexander Friese - initial contribution
40  */
41 @NonNullByDefault
42 public class UplinkWebInterface implements AtomicReferenceTrait {
43
44     private final Logger logger = LoggerFactory.getLogger(UplinkWebInterface.class);
45
46     /**
47      * handler for updating thing status
48      */
49     private final NibeUplinkHandler uplinkHandler;
50
51     /**
52      * holds authentication status
53      */
54     private boolean authenticated = false;
55
56     /**
57      * HTTP client for asynchronous calls
58      */
59     private final HttpClient httpClient;
60
61     /**
62      * the scheduler which periodically sends web requests to the solaredge API. Should be initiated with the thing's
63      * existing scheduler instance.
64      */
65     private final ScheduledExecutorService scheduler;
66
67     /**
68      * request executor
69      */
70     private final WebRequestExecutor requestExecutor;
71
72     /**
73      * periodic request executor job
74      */
75     private AtomicReference<@Nullable Future<?>> requestExecutorJobReference = new AtomicReference<>(null);
76
77     /**
78      * this class is responsible for executing periodic web requests. This ensures that only one request is executed at
79      * the same time and there will be a guaranteed minimum delay between subsequent requests.
80      *
81      * @author afriese - initial contribution
82      */
83     private class WebRequestExecutor implements Runnable {
84
85         /**
86          * queue which holds the commands to execute
87          */
88         private final Queue<@Nullable NibeUplinkCommand> commandQueue;
89
90         /**
91          * constructor
92          */
93         WebRequestExecutor() {
94             this.commandQueue = new BlockingArrayQueue<>(WEB_REQUEST_QUEUE_MAX_SIZE);
95         }
96
97         /**
98          * puts a command into the queue
99          *
100          * @param command the command which will be queued
101          */
102         void enqueue(NibeUplinkCommand command) {
103             try {
104                 commandQueue.add(command);
105             } catch (IllegalStateException ex) {
106                 if (commandQueue.size() >= WEB_REQUEST_QUEUE_MAX_SIZE) {
107                     logger.debug(
108                             "Could not add command to command queue because queue is already full. Maybe NIBE Uplink is down?");
109                 } else {
110                     logger.warn("Could not add command to queue - IllegalStateException");
111                 }
112             }
113         }
114
115         /**
116          * executes the web request
117          */
118         @Override
119         public void run() {
120             logger.debug("run queued commands, queue size is {}", commandQueue.size());
121             if (!isAuthenticated()) {
122                 authenticate();
123             } else if (isAuthenticated() && !commandQueue.isEmpty()) {
124                 try {
125                     executeCommand();
126                 } catch (Exception ex) {
127                     logger.warn("command execution ended with exception:", ex);
128                 }
129             }
130         }
131
132         /**
133          * executes the next command in the queue. requires authenticated session.
134          *
135          * @throws ValidationException
136          */
137         private void executeCommand() {
138             NibeUplinkCommand command = commandQueue.poll();
139             if (command != null) {
140                 command.setListener(this::processExecutionResult);
141                 command.performAction(httpClient);
142             }
143         }
144
145         /**
146          * callback that handles result from command execution.
147          *
148          * @param status status information to be evaluated
149          */
150         private void processExecutionResult(CommunicationStatus status) {
151             switch (status.getHttpCode()) {
152                 case SERVICE_UNAVAILABLE:
153                     uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
154                             status.getMessage());
155                     setAuthenticated(false);
156                     break;
157                 case FOUND:
158                     uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
159                             STATUS_INVALID_NIBE_ID);
160                     setAuthenticated(false);
161                     break;
162                 case OK:
163                     // no action needed as the thing is already online.
164                     break;
165                 default:
166                     uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
167                             status.getMessage());
168                     setAuthenticated(false);
169             }
170         }
171
172         /**
173          * authenticates with the Nibe Uplink WEB interface
174          */
175         private synchronized void authenticate() {
176             setAuthenticated(false);
177             new Login(uplinkHandler, this::processAuthenticationResult).performAction(httpClient);
178         }
179
180         /**
181          * callback that handles result from authentication.
182          *
183          * @param status status information to be evaluated
184          */
185         private void processAuthenticationResult(CommunicationStatus status) {
186             switch (status.getHttpCode()) {
187                 case FOUND:
188                     uplinkHandler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
189                     setAuthenticated(true);
190                     break;
191                 case OK:
192                     uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
193                             STATUS_INVALID_CREDENTIALS);
194                     setAuthenticated(false);
195                     break;
196                 case SERVICE_UNAVAILABLE:
197                     uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
198                             status.getMessage());
199                     setAuthenticated(false);
200                     break;
201                 default:
202                     uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
203                             status.getMessage());
204                     setAuthenticated(false);
205             }
206         }
207     }
208
209     /**
210      * Constructor to set up interface
211      */
212     public UplinkWebInterface(ScheduledExecutorService scheduler, NibeUplinkHandler handler, HttpClient httpClient) {
213         this.uplinkHandler = handler;
214         this.scheduler = scheduler;
215         this.requestExecutor = new WebRequestExecutor();
216         this.httpClient = httpClient;
217     }
218
219     /**
220      * starts the periodic request executor job which handles all web requests
221      */
222     public void start() {
223         setAuthenticated(false);
224         updateJobReference(requestExecutorJobReference, scheduler.scheduleWithFixedDelay(requestExecutor,
225                 WEB_REQUEST_INITIAL_DELAY, WEB_REQUEST_INTERVAL, TimeUnit.MILLISECONDS));
226     }
227
228     /**
229      * queues any command for execution
230      *
231      * @param command the command which will be put into the queue
232      */
233     public void enqueueCommand(NibeUplinkCommand command) {
234         requestExecutor.enqueue(command);
235     }
236
237     /**
238      * will be called by the ThingHandler to abort periodic jobs.
239      */
240     public void dispose() {
241         logger.debug("Webinterface disposed.");
242         cancelJobReference(requestExecutorJobReference);
243         setAuthenticated(false);
244     }
245
246     private boolean isAuthenticated() {
247         return authenticated;
248     }
249
250     private void setAuthenticated(boolean authenticated) {
251         this.authenticated = authenticated;
252     }
253 }