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.doorbird.internal.api;
15 import java.io.IOException;
16 import java.time.Duration;
17 import java.time.ZonedDateTime;
18 import java.util.concurrent.ExecutionException;
19 import java.util.concurrent.TimeUnit;
20 import java.util.concurrent.TimeoutException;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.eclipse.jetty.client.HttpClient;
25 import org.eclipse.jetty.client.api.ContentResponse;
26 import org.eclipse.jetty.client.api.Request;
27 import org.eclipse.jetty.http.HttpHeader;
28 import org.eclipse.jetty.http.HttpMethod;
29 import org.eclipse.jetty.http.HttpStatus;
30 import org.openhab.core.io.net.http.HttpRequestBuilder;
31 import org.openhab.core.library.types.RawType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
35 import com.google.gson.Gson;
36 import com.google.gson.JsonSyntaxException;
39 * The {@link DoorbirdAPI} class exposes the functionality provided by the Doorbird API.
41 * @author Mark Hilbush - Initial contribution
44 public final class DoorbirdAPI {
45 private static final long API_REQUEST_TIMEOUT_SECONDS = 16L;
47 // Single Gson instance shared by multiple classes
48 private static final Gson GSON = new Gson();
50 private final Logger logger = LoggerFactory.getLogger(DoorbirdAPI.class);
52 private @Nullable Authorization authorization;
53 private @Nullable HttpClient httpClient;
55 public static Gson getGson() {
59 public static <T> T fromJson(String json, Class<T> dataClass) {
60 return GSON.fromJson(json, dataClass);
63 public void setAuthorization(String doorbirdHost, String userId, String userPassword) {
64 this.authorization = new Authorization(doorbirdHost, userId, userPassword);
67 public void setHttpClient(HttpClient httpClient) {
68 this.httpClient = httpClient;
71 public @Nullable DoorbirdInfo getDoorbirdInfo() {
72 DoorbirdInfo doorbirdInfo = null;
74 String infoResponse = executeGetRequest("/bha-api/info.cgi");
75 logger.debug("Doorbird returned json response: {}", infoResponse);
76 doorbirdInfo = new DoorbirdInfo(infoResponse);
77 } catch (IOException e) {
78 logger.info("Unable to communicate with Doorbird: {}", e.getMessage());
79 } catch (JsonSyntaxException e) {
80 logger.info("Unable to parse Doorbird response: {}", e.getMessage());
81 } catch (DoorbirdUnauthorizedException e) {
82 logAuthorizationError("getDoorbirdName");
87 public @Nullable SipStatus getSipStatus() {
88 SipStatus sipStatus = null;
90 String statusResponse = executeGetRequest("/bha-api/sip.cgi&action=status");
91 logger.debug("Doorbird returned json response: {}", statusResponse);
92 sipStatus = new SipStatus(statusResponse);
93 } catch (IOException e) {
94 logger.info("Unable to communicate with Doorbird: {}", e.getMessage());
95 } catch (JsonSyntaxException e) {
96 logger.info("Unable to parse Doorbird response: {}", e.getMessage());
97 } catch (DoorbirdUnauthorizedException e) {
98 logAuthorizationError("getSipStatus");
103 public void lightOn() {
105 String response = executeGetRequest("/bha-api/light-on.cgi");
106 logger.debug("Response={}", response);
107 } catch (IOException e) {
108 logger.debug("IOException turning on light: {}", e.getMessage());
109 } catch (DoorbirdUnauthorizedException e) {
110 logAuthorizationError("lightOn");
114 public void restart() {
116 String response = executeGetRequest("/bha-api/restart.cgi");
117 logger.debug("Response={}", response);
118 } catch (IOException e) {
119 logger.debug("IOException restarting device: {}", e.getMessage());
120 } catch (DoorbirdUnauthorizedException e) {
121 logAuthorizationError("restart");
125 public void sipHangup() {
127 String response = executeGetRequest("/bha-api/sip.cgi?action=hangup");
128 logger.debug("Response={}", response);
129 } catch (IOException e) {
130 logger.debug("IOException hanging up SIP call: {}", e.getMessage());
131 } catch (DoorbirdUnauthorizedException e) {
132 logAuthorizationError("sipHangup");
136 public @Nullable DoorbirdImage downloadCurrentImage() {
137 return downloadImage("/bha-api/image.cgi");
140 public @Nullable DoorbirdImage downloadDoorbellHistoryImage(String imageNumber) {
141 return downloadImage("/bha-api/history.cgi?event=doorbell&index=" + imageNumber);
144 public @Nullable DoorbirdImage downloadMotionHistoryImage(String imageNumber) {
145 return downloadImage("/bha-api/history.cgi?event=motionsensor&index=" + imageNumber);
148 public void openDoorController(String controllerId, String doorNumber) {
149 openDoor("/bha-api/open-door.cgi?r=" + controllerId + "@" + doorNumber);
152 public void openDoorDoorbell(String doorNumber) {
153 openDoor("/bha-api/open-door.cgi?r=" + doorNumber);
156 private void openDoor(String urlFragment) {
158 String response = executeGetRequest(urlFragment);
159 logger.debug("Response={}", response);
160 } catch (IOException e) {
161 logger.debug("IOException opening door: {}", e.getMessage());
162 } catch (DoorbirdUnauthorizedException e) {
163 logAuthorizationError("openDoor");
167 private @Nullable synchronized DoorbirdImage downloadImage(String urlFragment) {
168 Authorization auth = authorization;
170 logAuthorizationError("downloadImage");
173 HttpClient client = httpClient;
174 if (client == null) {
175 logger.info("Unable to download image because httpClient is not set");
180 String url = buildUrl(auth, urlFragment);
181 logger.debug("Downloading image from doorbird: {}", url);
182 Request request = client.newRequest(url);
183 request.method(HttpMethod.GET);
184 request.header("Authorization", "Basic " + auth.getAuthorization());
185 request.timeout(API_REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
187 ContentResponse contentResponse = request.send();
188 switch (contentResponse.getStatus()) {
189 case HttpStatus.OK_200:
190 DoorbirdImage doorbirdImage = new DoorbirdImage();
191 doorbirdImage.setImage(new RawType(contentResponse.getContent(),
192 contentResponse.getHeaders().get(HttpHeader.CONTENT_TYPE)));
193 doorbirdImage.setTimestamp(convertXTimestamp(contentResponse.getHeaders().get("X-Timestamp")));
194 return doorbirdImage;
197 errorMsg = String.format("HTTP GET failed: %d, %s", contentResponse.getStatus(),
198 contentResponse.getReason());
201 } catch (TimeoutException e) {
202 errorMsg = "TimeoutException: Call to Doorbird API timed out";
203 } catch (ExecutionException e) {
204 errorMsg = String.format("ExecutionException: %s", e.getMessage());
205 } catch (InterruptedException e) {
206 errorMsg = String.format("InterruptedException: %s", e.getMessage());
207 Thread.currentThread().interrupt();
209 logger.debug("{}", errorMsg);
213 private long convertXTimestamp(@Nullable String timestamp) {
214 // Convert Unix Epoch string timestamp to long value
215 // Use current time if passed null string or if conversion fails
217 if (timestamp != null) {
219 value = Integer.parseInt(timestamp);
220 } catch (NumberFormatException e) {
221 logger.debug("X-Timestamp header is not a number: {}", timestamp);
222 value = ZonedDateTime.now().toEpochSecond();
225 value = ZonedDateTime.now().toEpochSecond();
230 private String buildUrl(Authorization auth, String path) {
231 return "http://" + auth.getHost() + path;
234 private synchronized String executeGetRequest(String urlFragment)
235 throws IOException, DoorbirdUnauthorizedException {
236 Authorization auth = authorization;
238 throw new DoorbirdUnauthorizedException();
240 String url = buildUrl(auth, urlFragment);
241 logger.debug("Executing doorbird API request: {}", url);
243 return HttpRequestBuilder.getFrom(url)
244 .withTimeout(Duration.ofSeconds(API_REQUEST_TIMEOUT_SECONDS))
245 .withHeader("Authorization", "Basic " + auth.getAuthorization())
246 .withHeader("charset", "utf-8")
247 .withHeader("Accept-language", "en-us")
248 .getContentAsString();
252 private void logAuthorizationError(String operation) {
253 logger.info("Authorization info is not set or is incorrect on call to '{}' API", operation);