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.airgradient.internal.communication;
15 import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CONTENTTYPE_JSON;
16 import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CONTENTTYPE_OPENMETRICS;
17 import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.CONTENTTYPE_TEXT;
18 import static org.openhab.binding.airgradient.internal.AirGradientBindingConstants.REQUEST_TIMEOUT;
20 import java.nio.charset.StandardCharsets;
21 import java.util.Collections;
22 import java.util.List;
23 import java.util.concurrent.ExecutionException;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.TimeoutException;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.eclipse.jetty.client.HttpClient;
30 import org.eclipse.jetty.client.api.ContentResponse;
31 import org.eclipse.jetty.client.api.Request;
32 import org.eclipse.jetty.client.util.StringContentProvider;
33 import org.eclipse.jetty.http.HttpHeader;
34 import org.eclipse.jetty.http.HttpMethod;
35 import org.eclipse.jetty.http.HttpStatus;
36 import org.openhab.binding.airgradient.internal.config.AirGradientAPIConfiguration;
37 import org.openhab.binding.airgradient.internal.model.LedMode;
38 import org.openhab.binding.airgradient.internal.model.Measure;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
42 import com.google.gson.Gson;
45 * Helper for doing rest calls to the AirGradient API.
47 * @author Jørgen Austvik - Initial contribution
50 public class RemoteAPIController {
52 private final Logger logger = LoggerFactory.getLogger(RemoteAPIController.class);
54 private final HttpClient httpClient;
55 private final Gson gson;
56 private final AirGradientAPIConfiguration apiConfig;
58 public RemoteAPIController(HttpClient httpClient, Gson gson, AirGradientAPIConfiguration apiConfig) {
59 this.httpClient = httpClient;
61 this.apiConfig = apiConfig;
65 * Return list of measures from AirGradient API.
67 * @return list of measures
68 * @throws AirGradientCommunicationException if unable to communicate with sensor or API.
70 public List<Measure> getMeasures() throws AirGradientCommunicationException {
71 ContentResponse response = sendRequest(
72 RESTHelper.generateRequest(httpClient, RESTHelper.generateMeasuresUrl(apiConfig)));
73 if (response != null) {
74 String contentType = response.getMediaType();
75 logger.debug("Got measurements with status {}: {} ({})", response.getStatus(),
76 response.getContentAsString(), contentType);
78 if (HttpStatus.isSuccess(response.getStatus())) {
79 String stringResponse = response.getContentAsString().trim();
81 if (null != contentType) {
82 switch (contentType) {
83 case CONTENTTYPE_JSON:
84 return JsonParserHelper.parseJson(gson, stringResponse);
85 case CONTENTTYPE_TEXT:
86 return PrometheusParserHelper.parsePrometheus(stringResponse);
87 case CONTENTTYPE_OPENMETRICS:
88 return PrometheusParserHelper.parsePrometheus(stringResponse);
90 logger.debug("Unhandled content type returned: {}", contentType);
96 return Collections.emptyList();
99 public void setLedMode(String serialNo, String mode) throws AirGradientCommunicationException {
100 Request request = httpClient.newRequest(RESTHelper.generateGetLedsModeUrl(apiConfig, serialNo));
101 request.timeout(REQUEST_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
102 request.method(HttpMethod.PUT);
103 request.header(HttpHeader.CONTENT_TYPE, CONTENTTYPE_JSON);
104 LedMode ledMode = new LedMode();
106 String modeJson = gson.toJson(ledMode);
107 logger.debug("Setting LEDS mode for {}: {}", serialNo, modeJson);
108 request.content(new StringContentProvider(CONTENTTYPE_JSON, modeJson, StandardCharsets.UTF_8));
109 sendRequest(request);
112 public void calibrateCo2(String serialNo) throws AirGradientCommunicationException {
113 logger.debug("Triggering CO2 calibration for {}", serialNo);
114 sendRequest(RESTHelper.generateRequest(httpClient, RESTHelper.generateCalibrationCo2Url(apiConfig, serialNo),
118 private @Nullable ContentResponse sendRequest(@Nullable final Request request)
119 throws AirGradientCommunicationException {
120 if (request == null) {
121 throw new AirGradientCommunicationException("Unable to generate request");
125 ContentResponse response = null;
127 response = request.send();
128 if (response != null) {
129 logger.debug("Response from {}: {}", request.getURI(), response.getStatus());
130 if (!HttpStatus.isSuccess(response.getStatus())) {
131 throw new AirGradientCommunicationException("Returned status code: " + response.getStatus());
134 throw new AirGradientCommunicationException("No response");
136 } catch (InterruptedException | ExecutionException | TimeoutException e) {
137 String message = e.getMessage();
138 if (message == null) {
139 message = "Communication error";
141 throw new AirGradientCommunicationException(message, e);