2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.nibeuplink.internal.connector;
15 import static org.openhab.binding.nibeuplink.internal.NibeUplinkBindingConstants.*;
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;
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;
37 * This class handles requests to the NibeUplink web interface. It manages authentication and wraps commands.
39 * @author Alexander Friese - initial contribution
42 public class UplinkWebInterface implements AtomicReferenceTrait {
44 private final Logger logger = LoggerFactory.getLogger(UplinkWebInterface.class);
47 * handler for updating thing status
49 private final NibeUplinkHandler uplinkHandler;
52 * holds authentication status
54 private boolean authenticated = false;
57 * HTTP client for asynchronous calls
59 private final HttpClient httpClient;
62 * the scheduler which periodically sends web requests to the solaredge API. Should be initiated with the thing's
63 * existing scheduler instance.
65 private final ScheduledExecutorService scheduler;
70 private final WebRequestExecutor requestExecutor;
73 * periodic request executor job
75 private AtomicReference<@Nullable Future<?>> requestExecutorJobReference = new AtomicReference<>(null);
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.
81 * @author afriese - initial contribution
83 private class WebRequestExecutor implements Runnable {
86 * queue which holds the commands to execute
88 private final Queue<@Nullable NibeUplinkCommand> commandQueue;
93 WebRequestExecutor() {
94 this.commandQueue = new BlockingArrayQueue<>(WEB_REQUEST_QUEUE_MAX_SIZE);
98 * puts a command into the queue
100 * @param command the command which will be queued
102 void enqueue(NibeUplinkCommand command) {
104 commandQueue.add(command);
105 } catch (IllegalStateException ex) {
106 if (commandQueue.size() >= WEB_REQUEST_QUEUE_MAX_SIZE) {
108 "Could not add command to command queue because queue is already full. Maybe NIBE Uplink is down?");
110 logger.warn("Could not add command to queue - IllegalStateException");
116 * executes the web request
120 logger.debug("run queued commands, queue size is {}", commandQueue.size());
121 if (!isAuthenticated()) {
123 } else if (isAuthenticated() && !commandQueue.isEmpty()) {
126 } catch (Exception ex) {
127 logger.warn("command execution ended with exception:", ex);
133 * executes the next command in the queue. requires authenticated session.
135 * @throws ValidationException
137 private void executeCommand() {
138 NibeUplinkCommand command = commandQueue.poll();
139 if (command != null) {
140 command.setListener(this::processExecutionResult);
141 command.performAction(httpClient);
146 * callback that handles result from command execution.
148 * @param status status information to be evaluated
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);
158 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
159 STATUS_INVALID_NIBE_ID);
160 setAuthenticated(false);
163 // no action needed as the thing is already online.
166 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
167 status.getMessage());
168 setAuthenticated(false);
173 * authenticates with the Nibe Uplink WEB interface
175 private synchronized void authenticate() {
176 setAuthenticated(false);
177 new Login(uplinkHandler, this::processAuthenticationResult).performAction(httpClient);
181 * callback that handles result from authentication.
183 * @param status status information to be evaluated
185 private void processAuthenticationResult(CommunicationStatus status) {
186 switch (status.getHttpCode()) {
188 uplinkHandler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, null);
189 setAuthenticated(true);
192 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
193 STATUS_INVALID_CREDENTIALS);
194 setAuthenticated(false);
196 case SERVICE_UNAVAILABLE:
197 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
198 status.getMessage());
199 setAuthenticated(false);
202 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
203 status.getMessage());
204 setAuthenticated(false);
210 * Constructor to set up interface
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;
220 * starts the periodic request executor job which handles all web requests
222 public void start() {
223 setAuthenticated(false);
224 updateJobReference(requestExecutorJobReference, scheduler.scheduleWithFixedDelay(requestExecutor,
225 WEB_REQUEST_INITIAL_DELAY, WEB_REQUEST_INTERVAL, TimeUnit.MILLISECONDS));
229 * queues any command for execution
231 * @param command the command which will be put into the queue
233 public void enqueueCommand(NibeUplinkCommand command) {
234 requestExecutor.enqueue(command);
238 * will be called by the ThingHandler to abort periodic jobs.
240 public void dispose() {
241 logger.debug("Webinterface disposed.");
242 cancelJobReference(requestExecutorJobReference);
243 setAuthenticated(false);
246 private boolean isAuthenticated() {
247 return authenticated;
250 private void setAuthenticated(boolean authenticated) {
251 this.authenticated = authenticated;