2 * Copyright (c) 2010-2023 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.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;
38 * This class handles requests to the NibeUplink web interface. It manages authentication and wraps commands.
40 * @author Alexander Friese - initial contribution
43 public class UplinkWebInterface implements AtomicReferenceTrait {
45 private static final int NIBE_ID_THRESHOLD = 14;
47 private final Logger logger = LoggerFactory.getLogger(UplinkWebInterface.class);
52 private NibeUplinkConfiguration config;
55 * handler for updating thing status
57 private final NibeUplinkHandler uplinkHandler;
60 * holds authentication status
62 private boolean authenticated = false;
65 * HTTP client for asynchronous calls
67 private final HttpClient httpClient;
70 * the scheduler which periodically sends web requests to the solaredge API. Should be initiated with the thing's
71 * existing scheduler instance.
73 private final ScheduledExecutorService scheduler;
78 private final WebRequestExecutor requestExecutor;
81 * periodic request executor job
83 private AtomicReference<@Nullable Future<?>> requestExecutorJobReference = new AtomicReference<>(null);
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.
89 * @author afriese - initial contribution
92 private class WebRequestExecutor implements Runnable {
95 * queue which holds the commands to execute
97 private final Queue<@Nullable NibeUplinkCommand> commandQueue;
102 WebRequestExecutor() {
103 this.commandQueue = new BlockingArrayQueue<>(WEB_REQUEST_QUEUE_MAX_SIZE);
107 * puts a command into the queue
109 * @param command the command which will be queued
111 void enqueue(NibeUplinkCommand command) {
113 commandQueue.add(command);
114 } catch (IllegalStateException ex) {
115 if (commandQueue.size() >= WEB_REQUEST_QUEUE_MAX_SIZE) {
117 "Could not add command to command queue because queue is already full. Maybe NIBE Uplink is down?");
119 logger.warn("Could not add command to queue - IllegalStateException");
125 * executes the web request
129 if (!isAuthenticated()) {
133 else if (isAuthenticated() && !commandQueue.isEmpty()) {
134 StatusUpdateListener statusUpdater = new StatusUpdateListener() {
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);
144 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
145 "most likely your NibeId is wrong. please check your NibeId.");
146 setAuthenticated(false);
149 // no action needed as the thing is already online.
152 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
153 status.getMessage());
154 setAuthenticated(false);
159 NibeUplinkCommand command = commandQueue.poll();
160 if (command != null) {
161 command.setListener(statusUpdater);
162 command.performAction(httpClient);
169 * Constructor to set up interface
171 * @param config the Bridge configuration
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;
182 * starts the periodic request executor job which handles all web requests
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));
192 * queues any command for execution
194 * @param command the command which will be put into the queue
196 public void enqueueCommand(NibeUplinkCommand command) {
197 requestExecutor.enqueue(command);
201 * authenticates with the Nibe Uplink WEB interface
203 private synchronized void authenticate() {
204 setAuthenticated(false);
207 StatusUpdateListener statusUpdater = new StatusUpdateListener() {
210 public void update(CommunicationStatus status) {
211 switch (status.getHttpCode()) {
213 uplinkHandler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, "logged in");
214 setAuthenticated(true);
217 uplinkHandler.setStatusInfo(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
218 "invalid username or password");
219 setAuthenticated(false);
221 case SERVICE_UNAVAILABLE:
222 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
223 status.getMessage());
224 setAuthenticated(false);
227 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
228 status.getMessage());
229 setAuthenticated(false);
234 new Login(uplinkHandler, statusUpdater).performAction(httpClient);
239 * performs some pre cheks on configuration before attempting to login
241 * @return true on success, false otherwise
243 private boolean preCheck() {
244 String preCheckStatusMessage = "";
245 String localPassword = config.getPassword();
246 String localUser = config.getUser();
247 String localNibeId = config.getNibeId();
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.";
261 this.uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
262 preCheckStatusMessage);
267 * will be called by the ThingHandler to abort periodic jobs.
269 public void dispose() {
270 logger.debug("Webinterface disposed.");
271 cancelJobReference(requestExecutorJobReference);
272 setAuthenticated(false);
275 private boolean isAuthenticated() {
276 return authenticated;
279 private void setAuthenticated(boolean authenticated) {
280 this.authenticated = authenticated;