2 * Copyright (c) 2010-2022 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.generacmobilelink.internal.handler;
15 import java.util.Optional;
16 import java.util.concurrent.CompletableFuture;
17 import java.util.concurrent.ExecutionException;
18 import java.util.concurrent.Future;
19 import java.util.concurrent.TimeUnit;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.eclipse.jetty.client.HttpClient;
24 import org.eclipse.jetty.client.api.ContentProvider;
25 import org.eclipse.jetty.client.api.Request;
26 import org.eclipse.jetty.client.api.Result;
27 import org.eclipse.jetty.client.util.BufferingResponseListener;
28 import org.eclipse.jetty.client.util.StringContentProvider;
29 import org.eclipse.jetty.http.HttpMethod;
30 import org.eclipse.jetty.http.HttpStatus;
31 import org.openhab.binding.generacmobilelink.internal.GeneracMobileLinkBindingConstants;
32 import org.openhab.binding.generacmobilelink.internal.config.GeneracMobileLinkAccountConfiguration;
33 import org.openhab.binding.generacmobilelink.internal.discovery.GeneracMobileLinkDiscoveryService;
34 import org.openhab.binding.generacmobilelink.internal.dto.ErrorResponseDTO;
35 import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusDTO;
36 import org.openhab.binding.generacmobilelink.internal.dto.GeneratorStatusResponseDTO;
37 import org.openhab.binding.generacmobilelink.internal.dto.LoginRequestDTO;
38 import org.openhab.binding.generacmobilelink.internal.dto.LoginResponseDTO;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.thing.ThingStatus;
43 import org.openhab.core.thing.ThingStatusDetail;
44 import org.openhab.core.thing.ThingUID;
45 import org.openhab.core.thing.binding.BaseBridgeHandler;
46 import org.openhab.core.thing.binding.ThingHandler;
47 import org.openhab.core.types.Command;
48 import org.openhab.core.types.RefreshType;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
52 import com.google.gson.FieldNamingPolicy;
53 import com.google.gson.Gson;
54 import com.google.gson.GsonBuilder;
57 * The {@link GeneracMobileLinkAccountHandler} is responsible for connecting to the MobileLink cloud service and
58 * discovering generator things
60 * @author Dan Cunningham - Initial contribution
63 public class GeneracMobileLinkAccountHandler extends BaseBridgeHandler {
64 private static final String BASE_URL = "https://api.mobilelinkgen.com";
65 private static final String SHARED_KEY = "GeneseeDepot13";
66 private final Logger logger = LoggerFactory.getLogger(GeneracMobileLinkAccountHandler.class);
67 private final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
68 private @Nullable Future<?> pollFuture;
69 private @Nullable String authToken;
70 private @Nullable GeneratorStatusResponseDTO generators;
71 private GeneracMobileLinkDiscoveryService discoveryService;
72 private HttpClient httpClient;
73 private int refreshIntervalSeconds = 60;
75 public GeneracMobileLinkAccountHandler(Bridge bridge, HttpClient httpClient,
76 GeneracMobileLinkDiscoveryService discoveryService) {
78 this.httpClient = httpClient;
79 this.discoveryService = discoveryService;
83 public void initialize() {
84 updateStatus(ThingStatus.UNKNOWN);
90 public void dispose() {
95 public void handleCommand(ChannelUID channelUID, Command command) {
96 if (command instanceof RefreshType) {
97 updateGeneratorThings();
102 public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
103 GeneratorStatusResponseDTO generatorsLocal = generators;
104 if (generatorsLocal != null) {
105 Optional<GeneratorStatusDTO> generatorOpt = generatorsLocal.stream()
106 .filter(g -> String.valueOf(g.gensetID).equals(childThing.getUID().getId())).findFirst();
107 if (generatorOpt.isPresent()) {
108 ((GeneracMobileLinkGeneratorHandler) childHandler).updateGeneratorStatus(generatorOpt.get());
113 private void stopPoll() {
114 Future<?> localPollFuture = pollFuture;
115 if (localPollFuture != null) {
116 localPollFuture.cancel(true);
120 private void restartPoll() {
122 pollFuture = scheduler.scheduleWithFixedDelay(this::poll, 0, refreshIntervalSeconds, TimeUnit.SECONDS);
125 private void poll() {
127 if (authToken == null) {
128 logger.debug("Attempting Login");
132 } catch (InterruptedException e) {
136 private synchronized void login() throws InterruptedException {
137 GeneracMobileLinkAccountConfiguration config = getConfigAs(GeneracMobileLinkAccountConfiguration.class);
138 refreshIntervalSeconds = config.refreshInterval;
139 HTTPResult result = sendRequest(BASE_URL + "/Users/login", HttpMethod.POST, null,
140 new StringContentProvider(
141 gson.toJson(new LoginRequestDTO(SHARED_KEY, config.username, config.password))),
143 if (result.responseCode == HttpStatus.OK_200) {
144 LoginResponseDTO loginResponse = gson.fromJson(result.content, LoginResponseDTO.class);
145 if (loginResponse != null) {
146 authToken = loginResponse.authToken;
147 updateStatus(ThingStatus.ONLINE);
150 handleErrorResponse(result);
151 if (thing.getStatusInfo().getStatusDetail() == ThingStatusDetail.CONFIGURATION_ERROR) {
152 // bad credentials, stop trying to login
158 private void getStatuses(boolean retry) throws InterruptedException {
159 if (authToken == null) {
162 HTTPResult result = sendRequest(BASE_URL + "/Generator/GeneratorStatus", HttpMethod.GET, authToken, null, null);
163 if (result.responseCode == HttpStatus.OK_200) {
164 generators = gson.fromJson(result.content, GeneratorStatusResponseDTO.class);
165 updateGeneratorThings();
166 if (getThing().getStatus() != ThingStatus.ONLINE) {
167 updateStatus(ThingStatus.ONLINE);
171 logger.debug("Retrying status request");
174 handleErrorResponse(result);
179 private HTTPResult sendRequest(String url, HttpMethod method, @Nullable String token,
180 @Nullable ContentProvider content, @Nullable String contentType) throws InterruptedException {
182 Request request = httpClient.newRequest(url).method(method).timeout(10, TimeUnit.SECONDS);
184 request = request.header("AuthToken", token);
186 if (content != null & contentType != null) {
187 request = request.content(content, contentType);
189 logger.trace("Sending {} to {}", request.getMethod(), request.getURI());
190 final CompletableFuture<HTTPResult> futureResult = new CompletableFuture<>();
191 request.send(new BufferingResponseListener() {
192 @NonNullByDefault({})
194 public void onComplete(Result result) {
195 futureResult.complete(new HTTPResult(result.getResponse().getStatus(), getContentAsString()));
198 HTTPResult result = futureResult.get();
199 logger.trace("Response - status: {} content: {}", result.responseCode, result.content);
201 } catch (ExecutionException e) {
202 return new HTTPResult(0, e.getMessage());
206 private void handleErrorResponse(HTTPResult result) {
207 switch (result.responseCode) {
208 case HttpStatus.UNAUTHORIZED_401:
209 // the server responds with a 500 error in some cases when credentials are not correct
210 case HttpStatus.INTERNAL_SERVER_ERROR_500:
211 // server returned a valid error response
212 ErrorResponseDTO error = gson.fromJson(result.content, ErrorResponseDTO.class);
213 if (error != null && error.errorCode > 0) {
214 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
215 "Unauthorized: " + result.content);
220 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, result.content);
224 private void updateGeneratorThings() {
225 GeneratorStatusResponseDTO generatorsLocal = generators;
226 if (generatorsLocal != null) {
227 generatorsLocal.forEach(generator -> {
228 Thing thing = getThing().getThing(new ThingUID(GeneracMobileLinkBindingConstants.THING_TYPE_GENERATOR,
229 getThing().getUID(), String.valueOf(generator.gensetID)));
231 discoveryService.generatorDiscovered(generator, getThing().getUID());
233 ThingHandler handler = thing.getHandler();
234 if (handler != null) {
235 ((GeneracMobileLinkGeneratorHandler) handler).updateGeneratorStatus(generator);
242 public static class HTTPResult {
243 public @Nullable String content;
244 public final int responseCode;
246 public HTTPResult(int responseCode, @Nullable String content) {
247 this.responseCode = responseCode;
248 this.content = content;