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.warmup.internal.api;
15 import java.util.concurrent.ExecutionException;
16 import java.util.concurrent.TimeUnit;
17 import java.util.concurrent.TimeoutException;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.eclipse.jetty.client.HttpClient;
22 import org.eclipse.jetty.client.api.ContentResponse;
23 import org.eclipse.jetty.client.api.Request;
24 import org.eclipse.jetty.client.util.StringContentProvider;
25 import org.eclipse.jetty.http.HttpHeader;
26 import org.eclipse.jetty.http.HttpMethod;
27 import org.eclipse.jetty.http.HttpStatus;
28 import org.openhab.binding.warmup.internal.WarmupBindingConstants;
29 import org.openhab.binding.warmup.internal.handler.MyWarmupConfigurationDTO;
30 import org.openhab.binding.warmup.internal.model.auth.AuthRequestDTO;
31 import org.openhab.binding.warmup.internal.model.auth.AuthResponseDTO;
32 import org.openhab.binding.warmup.internal.model.query.QueryResponseDTO;
33 import org.openhab.core.library.types.OnOffType;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
37 import com.google.gson.Gson;
40 * The {@link MyWarmupApi} class contains code specific to calling the My Warmup API.
42 * @author James Melville - Initial contribution
45 public class MyWarmupApi {
47 private static final Gson GSON = new Gson();
49 private final Logger logger = LoggerFactory.getLogger(MyWarmupApi.class);
50 private final HttpClient httpClient;
52 private MyWarmupConfigurationDTO configuration;
53 private @Nullable String authToken;
56 * Construct the API client
58 * @param httpClient HttpClient to make HTTP Calls
59 * @param configuration Thing configuration which contains API credentials
61 public MyWarmupApi(final HttpClient httpClient, MyWarmupConfigurationDTO configuration) {
62 this.httpClient = httpClient;
63 this.configuration = configuration;
67 * Update the configuration, trigger a refresh of the access token
69 * @param configuration contains username and password
71 public void setConfiguration(MyWarmupConfigurationDTO configuration) {
73 this.configuration = configuration;
76 private void validateSession() throws MyWarmupApiException {
77 if (authToken == null) {
82 private void authenticate() throws MyWarmupApiException {
83 String body = GSON.toJson(new AuthRequestDTO(configuration.username, configuration.password,
84 WarmupBindingConstants.AUTH_METHOD, WarmupBindingConstants.AUTH_APP_ID));
86 ContentResponse response = callWarmup(WarmupBindingConstants.APP_ENDPOINT, body, false);
88 AuthResponseDTO ar = GSON.fromJson(response.getContentAsString(), AuthResponseDTO.class);
90 if (ar != null && ar.getStatus() != null && "success".equals(ar.getStatus().getResult())) {
91 authToken = ar.getResponse().getToken();
93 throw new MyWarmupApiException("Authentication Failed");
98 * Query the API to get the status of all devices connected to the Bridge.
100 * @return The {@link QueryResponseDTO} object if retrieved, else null
101 * @throws MyWarmupApiException API callout error
103 public synchronized QueryResponseDTO getStatus() throws MyWarmupApiException {
104 return callWarmupGraphQL("""
105 query QUERY { user { locations{ id name \
106 rooms { id roomName runMode overrideDur targetTemp currentTemp \
107 thermostat4ies{ deviceSN lastPoll }}}}}\
112 * Call the API to set a temperature override on a specific room
114 * @param locationId Id of the location
115 * @param roomId Id of the room
116 * @param temperature Temperature to set * 10
117 * @param duration Duration in minutes of the override
118 * @throws MyWarmupApiException API callout error
120 public void setOverride(String locationId, String roomId, int temperature, Integer duration)
121 throws MyWarmupApiException {
122 callWarmupGraphQL(String.format("mutation{deviceOverride(lid:%s,rid:%s,temperature:%d,minutes:%d)}", locationId,
123 roomId, temperature, duration));
127 * Call the API to toggle frost protection mode on a specific room
129 * @param locationId Id of the location
130 * @param roomId Id of the room
131 * @param command Temperature to set
132 * @throws MyWarmupApiException API callout error
134 public void toggleFrostProtectionMode(String locationId, String roomId, OnOffType command)
135 throws MyWarmupApiException {
136 callWarmupGraphQL(String.format("mutation{turn%s(lid:%s,rid:%s){id}}", command == OnOffType.ON ? "Off" : "On",
137 locationId, roomId));
140 private QueryResponseDTO callWarmupGraphQL(String body) throws MyWarmupApiException {
142 ContentResponse response = callWarmup(WarmupBindingConstants.QUERY_ENDPOINT, "{\"query\": \"" + body + "\"}",
145 QueryResponseDTO qr = GSON.fromJson(response.getContentAsString(), QueryResponseDTO.class);
147 if (qr != null && "success".equals(qr.getStatus())) {
150 throw new MyWarmupApiException("Unexpected reponse from API");
154 private synchronized ContentResponse callWarmup(String endpoint, String body, Boolean authenticated)
155 throws MyWarmupApiException {
157 final Request request = httpClient.newRequest(endpoint);
159 request.method(HttpMethod.POST);
161 request.getHeaders().remove(HttpHeader.USER_AGENT);
162 request.header(HttpHeader.USER_AGENT, WarmupBindingConstants.USER_AGENT);
163 request.header(HttpHeader.CONTENT_TYPE, "application/json");
164 request.header("App-Token", WarmupBindingConstants.APP_TOKEN);
166 request.header("Warmup-Authorization", authToken);
169 request.content(new StringContentProvider(body));
171 request.timeout(10, TimeUnit.SECONDS);
173 logger.trace("Sending body to My Warmup: Endpoint {}, Body {}", endpoint, body);
174 ContentResponse response = request.send();
175 logger.trace("Response from my warmup: Status {}, Body {}", response.getStatus(),
176 response.getContentAsString());
178 if (response.getStatus() == HttpStatus.OK_200) {
180 } else if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
181 logger.debug("Authentication failure {} {}", response.getStatus(), response.getContentAsString());
183 throw new MyWarmupApiException("Authentication failure");
185 logger.debug("Unexpected response {} {}", response.getStatus(), response.getContentAsString());
187 throw new MyWarmupApiException("Callout failed");
188 } catch (InterruptedException | TimeoutException | ExecutionException e) {
189 throw new MyWarmupApiException(e);