]> git.basschouten.com Git - openhab-addons.git/blob
e2764334b142c30a86be669cca9072868d75976e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.volvooncall.internal.handler;
14
15 import static org.openhab.binding.volvooncall.internal.VolvoOnCallBindingConstants.*;
16
17 import java.io.ByteArrayInputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.nio.charset.StandardCharsets;
21 import java.time.ZonedDateTime;
22 import java.util.List;
23 import java.util.Properties;
24 import java.util.Stack;
25 import java.util.concurrent.ScheduledFuture;
26 import java.util.concurrent.TimeUnit;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.volvooncall.internal.VolvoOnCallException;
31 import org.openhab.binding.volvooncall.internal.VolvoOnCallException.ErrorType;
32 import org.openhab.binding.volvooncall.internal.config.VolvoOnCallBridgeConfiguration;
33 import org.openhab.binding.volvooncall.internal.dto.CustomerAccounts;
34 import org.openhab.binding.volvooncall.internal.dto.PostResponse;
35 import org.openhab.binding.volvooncall.internal.dto.VocAnswer;
36 import org.openhab.core.io.net.http.HttpUtil;
37 import org.openhab.core.library.types.OnOffType;
38 import org.openhab.core.library.types.OpenClosedType;
39 import org.openhab.core.thing.Bridge;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.binding.BaseBridgeHandler;
44 import org.openhab.core.types.Command;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48 import com.google.gson.Gson;
49 import com.google.gson.GsonBuilder;
50 import com.google.gson.JsonDeserializer;
51 import com.google.gson.JsonSyntaxException;
52
53 /**
54  * The {@link VolvoOnCallBridgeHandler} is responsible for handling commands, which are
55  * sent to one of the channels.
56  *
57  * @author GaĆ«l L'hopital - Initial contribution
58  */
59 @NonNullByDefault
60 public class VolvoOnCallBridgeHandler extends BaseBridgeHandler {
61     private static final int REQUEST_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(20);
62     private final Logger logger = LoggerFactory.getLogger(VolvoOnCallBridgeHandler.class);
63     private final Properties httpHeader = new Properties();
64     private final List<ScheduledFuture<?>> pendingActions = new Stack<>();
65     private final Gson gson;
66
67     private @NonNullByDefault({}) CustomerAccounts customerAccount;
68
69     public VolvoOnCallBridgeHandler(Bridge bridge) {
70         super(bridge);
71
72         httpHeader.put("cache-control", "no-cache");
73         httpHeader.put("content-type", JSON_CONTENT_TYPE);
74         httpHeader.put("x-device-id", "Device");
75         httpHeader.put("x-originator-type", "App");
76         httpHeader.put("x-os-type", "Android");
77         httpHeader.put("x-os-version", "22");
78         httpHeader.put("Accept", "*/*");
79
80         gson = new GsonBuilder()
81                 .registerTypeAdapter(ZonedDateTime.class,
82                         (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
83                                 .parse(json.getAsJsonPrimitive().getAsString().replaceAll("\\+0000", "Z")))
84                 .registerTypeAdapter(OpenClosedType.class,
85                         (JsonDeserializer<OpenClosedType>) (json, type,
86                                 jsonDeserializationContext) -> json.getAsBoolean() ? OpenClosedType.OPEN
87                                         : OpenClosedType.CLOSED)
88                 .registerTypeAdapter(OnOffType.class,
89                         (JsonDeserializer<OnOffType>) (json, type,
90                                 jsonDeserializationContext) -> json.getAsBoolean() ? OnOffType.ON : OnOffType.OFF)
91                 .create();
92     }
93
94     @Override
95     public void initialize() {
96         logger.debug("Initializing VolvoOnCall API bridge handler.");
97         VolvoOnCallBridgeConfiguration configuration = getConfigAs(VolvoOnCallBridgeConfiguration.class);
98
99         httpHeader.setProperty("Authorization", configuration.getAuthorization());
100         try {
101             customerAccount = getURL(SERVICE_URL + "customeraccounts/", CustomerAccounts.class);
102             if (customerAccount.username != null) {
103                 updateStatus(ThingStatus.ONLINE);
104             } else {
105                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
106                         "Incorrect username or password");
107             }
108         } catch (JsonSyntaxException | VolvoOnCallException e) {
109             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
110         }
111     }
112
113     @Override
114     public void handleCommand(ChannelUID channelUID, Command command) {
115         logger.debug("VolvoOnCall Bridge is read-only and does not handle commands");
116     }
117
118     public String[] getVehiclesRelationsURL() {
119         if (customerAccount != null) {
120             return customerAccount.accountVehicleRelationsURL;
121         }
122         return new String[0];
123     }
124
125     public <T extends VocAnswer> T getURL(Class<T> objectClass, String vin) throws VolvoOnCallException {
126         String url = SERVICE_URL + "vehicles/" + vin + "/" + objectClass.getSimpleName().toLowerCase();
127         return getURL(url, objectClass);
128     }
129
130     public <T extends VocAnswer> T getURL(String url, Class<T> objectClass) throws VolvoOnCallException {
131         try {
132             String jsonResponse = HttpUtil.executeUrl("GET", url, httpHeader, null, JSON_CONTENT_TYPE, REQUEST_TIMEOUT);
133             logger.debug("Request for : {}", url);
134             logger.debug("Received : {}", jsonResponse);
135             T response = gson.fromJson(jsonResponse, objectClass);
136             String error = response.getErrorLabel();
137             if (error != null) {
138                 throw new VolvoOnCallException(error, response.getErrorDescription());
139             }
140             return response;
141         } catch (JsonSyntaxException | IOException e) {
142             throw new VolvoOnCallException(e);
143         }
144     }
145
146     public class ActionResultControler implements Runnable {
147         PostResponse postResponse;
148
149         ActionResultControler(PostResponse postResponse) {
150             this.postResponse = postResponse;
151         }
152
153         @Override
154         public void run() {
155             switch (postResponse.status) {
156                 case SUCCESSFULL:
157                 case FAILED:
158                     logger.info("Action status : {} for vehicle : {}.", postResponse.status.toString(),
159                             postResponse.vehicleId);
160                     getThing().getThings().stream().filter(VehicleHandler.class::isInstance)
161                             .map(VehicleHandler.class::cast)
162                             .forEach(handler -> handler.updateIfMatches(postResponse.vehicleId));
163                     break;
164                 default:
165                     try {
166                         postResponse = getURL(postResponse.serviceURL, PostResponse.class);
167                         scheduler.schedule(new ActionResultControler(postResponse), 1000, TimeUnit.MILLISECONDS);
168                     } catch (VolvoOnCallException e) {
169                         if (e.getType() == ErrorType.SERVICE_UNAVAILABLE) {
170                             scheduler.schedule(new ActionResultControler(postResponse), 1000, TimeUnit.MILLISECONDS);
171                         }
172                     }
173             }
174         }
175     }
176
177     void postURL(String URL, @Nullable String body) throws VolvoOnCallException {
178         InputStream inputStream = body != null ? new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)) : null;
179         try {
180             String jsonString = HttpUtil.executeUrl("POST", URL, httpHeader, inputStream, null, REQUEST_TIMEOUT);
181             logger.debug("Post URL: {} Attributes {}", URL, httpHeader);
182             PostResponse postResponse = gson.fromJson(jsonString, PostResponse.class);
183             String error = postResponse.getErrorLabel();
184             if (error == null) {
185                 pendingActions
186                         .add(scheduler.schedule(new ActionResultControler(postResponse), 1000, TimeUnit.MILLISECONDS));
187             } else {
188                 throw new VolvoOnCallException(error, postResponse.getErrorDescription());
189             }
190             pendingActions.removeIf(ScheduledFuture::isDone);
191         } catch (JsonSyntaxException | IOException e) {
192             throw new VolvoOnCallException(e);
193         }
194     }
195
196     @Override
197     public void dispose() {
198         super.dispose();
199         pendingActions.stream().filter(f -> !f.isCancelled()).forEach(f -> f.cancel(true));
200     }
201 }