2 * Copyright (c) 2010-2021 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.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;
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;
39 * This class handles requests to the NibeUplink web interface. It manages authentication and wraps commands.
41 * @author Alexander Friese - initial contribution
44 public class UplinkWebInterface implements AtomicReferenceTrait {
46 private static final int NIBE_ID_THRESHOLD = 14;
48 private final Logger logger = LoggerFactory.getLogger(UplinkWebInterface.class);
53 private NibeUplinkConfiguration config;
56 * handler for updating thing status
58 private final NibeUplinkHandler uplinkHandler;
61 * holds authentication status
63 private boolean authenticated = false;
66 * HTTP client for asynchronous calls
68 private final HttpClient httpClient;
71 * the scheduler which periodically sends web requests to the solaredge API. Should be initiated with the thing's
72 * existing scheduler instance.
74 private final ScheduledExecutorService scheduler;
79 private final WebRequestExecutor requestExecutor;
82 * periodic request executor job
84 private AtomicReference<@Nullable Future<?>> requestExecutorJobReference = new AtomicReference<>(null);
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.
90 * @author afriese - initial contribution
93 private class WebRequestExecutor implements Runnable {
96 * queue which holds the commands to execute
98 private final Queue<@Nullable NibeUplinkCommand> commandQueue;
103 WebRequestExecutor() {
104 this.commandQueue = new BlockingArrayQueue<>(WEB_REQUEST_QUEUE_MAX_SIZE);
108 * puts a command into the queue
110 * @param command the command which will be queued
112 void enqueue(NibeUplinkCommand command) {
114 commandQueue.add(command);
115 } catch (IllegalStateException ex) {
116 if (commandQueue.size() >= WEB_REQUEST_QUEUE_MAX_SIZE) {
118 "Could not add command to command queue because queue is already full. Maybe NIBE Uplink is down?");
120 logger.warn("Could not add command to queue - IllegalStateException");
126 * executes the web request
130 if (!isAuthenticated()) {
134 else if (isAuthenticated() && !commandQueue.isEmpty()) {
135 StatusUpdateListener statusUpdater = new StatusUpdateListener() {
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);
145 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
146 "most likely your NibeId is wrong. please check your NibeId.");
147 setAuthenticated(false);
150 // no action needed as the thing is already online.
153 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
154 status.getMessage());
155 setAuthenticated(false);
160 NibeUplinkCommand command = commandQueue.poll();
161 if (command != null) {
162 command.setListener(statusUpdater);
163 command.performAction(httpClient);
170 * Constructor to set up interface
172 * @param config the Bridge configuration
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;
183 * starts the periodic request executor job which handles all web requests
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));
193 * queues any command for execution
195 * @param command the command which will be put into the queue
197 public void enqueueCommand(NibeUplinkCommand command) {
198 requestExecutor.enqueue(command);
202 * authenticates with the Nibe Uplink WEB interface
204 * @throws UnsupportedEncodingException
206 private synchronized void authenticate() {
207 setAuthenticated(false);
210 StatusUpdateListener statusUpdater = new StatusUpdateListener() {
213 public void update(CommunicationStatus status) {
214 switch (status.getHttpCode()) {
216 uplinkHandler.setStatusInfo(ThingStatus.ONLINE, ThingStatusDetail.NONE, "logged in");
217 setAuthenticated(true);
220 uplinkHandler.setStatusInfo(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_ERROR,
221 "invalid username or password");
222 setAuthenticated(false);
224 case SERVICE_UNAVAILABLE:
225 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
226 status.getMessage());
227 setAuthenticated(false);
230 uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
231 status.getMessage());
232 setAuthenticated(false);
237 new Login(uplinkHandler, statusUpdater).performAction(httpClient);
242 * performs some pre cheks on configuration before attempting to login
244 * @return true on success, false otherwise
246 private boolean preCheck() {
247 String preCheckStatusMessage = "";
248 String localPassword = config.getPassword();
249 String localUser = config.getUser();
250 String localNibeId = config.getNibeId();
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.";
264 this.uplinkHandler.setStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
265 preCheckStatusMessage);
270 * will be called by the ThingHandler to abort periodic jobs.
272 public void dispose() {
273 logger.debug("Webinterface disposed.");
274 cancelJobReference(requestExecutorJobReference);
275 setAuthenticated(false);
278 private boolean isAuthenticated() {
279 return authenticated;
282 private void setAuthenticated(boolean authenticated) {
283 this.authenticated = authenticated;