]> git.basschouten.com Git - openhab-addons.git/blob
b6a5c5f43c7d7e066552fd585315afd48354e18d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.groupepsa.internal.bridge;
14
15 import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.THING_TYPE_BRIDGE;
16 import static org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.VendorConstants;
17
18 import java.io.IOException;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.Set;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.HttpClient;
29 import org.openhab.binding.groupepsa.internal.discovery.GroupePSADiscoveryService;
30 import org.openhab.binding.groupepsa.internal.rest.api.GroupePSAConnectApi;
31 import org.openhab.binding.groupepsa.internal.rest.api.dto.Vehicle;
32 import org.openhab.binding.groupepsa.internal.rest.api.dto.VehicleStatus;
33 import org.openhab.binding.groupepsa.internal.rest.exceptions.GroupePSACommunicationException;
34 import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
35 import org.openhab.core.auth.client.oauth2.OAuthClientService;
36 import org.openhab.core.auth.client.oauth2.OAuthException;
37 import org.openhab.core.auth.client.oauth2.OAuthFactory;
38 import org.openhab.core.auth.client.oauth2.OAuthResponseException;
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.ThingTypeUID;
44 import org.openhab.core.thing.binding.BaseBridgeHandler;
45 import org.openhab.core.thing.binding.ThingHandlerService;
46 import org.openhab.core.types.Command;
47
48 /**
49  * The {@link GroupePSABridgeHandler} is responsible for handling commands,
50  * which are sent to one of the channels.
51  *
52  * @author Arjan Mels - Initial contribution
53  */
54 @NonNullByDefault
55 public class GroupePSABridgeHandler extends BaseBridgeHandler {
56     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
57     private static final long DEFAULT_POLLING_INTERVAL_M = TimeUnit.HOURS.toMinutes(1);
58
59     private final OAuthFactory oAuthFactory;
60
61     private @Nullable OAuthClientService oAuthService;
62     private @Nullable ScheduledFuture<?> groupepsaBridgePollingJob;
63     private final HttpClient httpClient;
64
65     private String vendor = "";
66     private @Nullable VendorConstants vendorConstants;
67     private String userName = "";
68     private String password = "";
69     private String clientId = "";
70     private String clientSecret = "";
71
72     private @Nullable GroupePSAConnectApi groupePSAApi;
73
74     public GroupePSABridgeHandler(Bridge bridge, OAuthFactory oAuthFactory, HttpClient httpClient) {
75         super(bridge);
76         this.oAuthFactory = oAuthFactory;
77         this.httpClient = httpClient;
78     }
79
80     private void pollGroupePSAs() {
81         try {
82             List<Vehicle> vehicles = getVehicles();
83             if (vehicles != null) {
84                 updateStatus(ThingStatus.ONLINE);
85             } else {
86                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
87                         "@text/comm-error-query-vehicles-failed");
88             }
89         } catch (GroupePSACommunicationException e) {
90             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
91         }
92     }
93
94     @Override
95     public void dispose() {
96         stopGroupePSABridgePolling();
97         oAuthFactory.ungetOAuthService(thing.getUID().getAsString());
98     }
99
100     @Override
101     public void initialize() {
102         GroupePSABridgeConfiguration bridgeConfiguration = getConfigAs(GroupePSABridgeConfiguration.class);
103
104         vendor = bridgeConfiguration.getVendor();
105         userName = bridgeConfiguration.getUserName();
106         password = bridgeConfiguration.getPassword();
107         clientId = bridgeConfiguration.getClientId();
108         clientSecret = bridgeConfiguration.getClientSecret();
109
110         final Integer pollingIntervalM = bridgeConfiguration.getPollingInterval();
111
112         if (vendor.isEmpty()) {
113             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-vendor");
114         } else if (userName.isEmpty()) {
115             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
116         } else if (password.isEmpty()) {
117             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-password");
118         } else if (clientId.isEmpty()) {
119             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-clientid");
120         } else if (clientSecret.isEmpty()) {
121             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
122                     "@text/conf-error-no-clientsecret");
123         } else if (pollingIntervalM < 1) {
124             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
125                     "@text/conf-error-invalid-polling-interval");
126         } else {
127             VendorConstants localVendorConstants = VendorConstants.valueOf(vendor);
128             vendorConstants = localVendorConstants;
129
130             oAuthService = oAuthFactory.createOAuthClientService(thing.getUID().getAsString(), localVendorConstants.url,
131                     null, clientId, clientSecret, localVendorConstants.scope, true);
132
133             groupePSAApi = new GroupePSAConnectApi(httpClient, this, clientId, localVendorConstants.realm);
134
135             startGroupePSABridgePolling(pollingIntervalM);
136
137             updateStatus(ThingStatus.UNKNOWN);
138         }
139     }
140
141     private void startGroupePSABridgePolling(@Nullable Integer pollingIntervalM) {
142         if (groupepsaBridgePollingJob == null) {
143             final long pollingIntervalToUse = pollingIntervalM == null ? DEFAULT_POLLING_INTERVAL_M : pollingIntervalM;
144             groupepsaBridgePollingJob = scheduler.scheduleWithFixedDelay(() -> pollGroupePSAs(), 1,
145                     TimeUnit.MINUTES.toSeconds(pollingIntervalToUse), TimeUnit.SECONDS);
146         }
147     }
148
149     private void stopGroupePSABridgePolling() {
150         final ScheduledFuture<?> job = groupepsaBridgePollingJob;
151         if (job != null) {
152             job.cancel(true);
153             groupepsaBridgePollingJob = null;
154         }
155     }
156
157     @Override
158     public void handleCommand(ChannelUID channelUID, Command command) {
159     }
160
161     static Throwable getRootCause(Throwable e) {
162         Throwable nextE;
163         do {
164             nextE = e.getCause();
165             if (nextE != null) {
166                 e = nextE;
167             }
168         } while (nextE != null);
169         return e;
170     }
171
172     public String authenticate() throws GroupePSACommunicationException {
173         OAuthClientService localOAuthService = oAuthService;
174         VendorConstants localVendorConstants = vendorConstants;
175         if (localOAuthService == null) {
176             throw new GroupePSACommunicationException("OAuth service is unexpectedly null");
177         }
178         if (localVendorConstants == null) {
179             throw new GroupePSACommunicationException("Vendor constants are unexpectedly null");
180         }
181         try {
182             AccessTokenResponse result = localOAuthService.getAccessTokenResponse();
183             if (result == null) {
184                 result = localOAuthService.getAccessTokenByResourceOwnerPasswordCredentials(userName, password,
185                         localVendorConstants.scope);
186             }
187             return result.getAccessToken();
188         } catch (OAuthException | IOException | OAuthResponseException e) {
189             throw new GroupePSACommunicationException("Unable to authenticate: " + getRootCause(e).getMessage(), e);
190         }
191     }
192
193     public GroupePSAConnectApi getAPI() throws GroupePSACommunicationException {
194         GroupePSAConnectApi localGroupePSAApi = groupePSAApi;
195         if (localGroupePSAApi == null) {
196             throw new GroupePSACommunicationException("groupePSAApi is unexpectedly null");
197         } else {
198             return localGroupePSAApi;
199         }
200     }
201
202     /**
203      * @return A list of vehicles
204      * @throws GroupePSACommunicationException In case the query cannot be executed
205      *             successfully
206      */
207     public @Nullable List<Vehicle> getVehicles() throws GroupePSACommunicationException {
208         return getAPI().getVehicles();
209     }
210
211     /**
212      * @param id The id of the mower to query
213      * @return A detailed status of the mower with the specified id
214      * @throws GroupePSACommunicationException In case the query cannot be executed
215      *             successfully
216      */
217     public @Nullable VehicleStatus getVehicleStatus(String vin) throws GroupePSACommunicationException {
218         return getAPI().getVehicleStatus(vin);
219     }
220
221     @Override
222     public Collection<Class<? extends ThingHandlerService>> getServices() {
223         return Collections.singleton(GroupePSADiscoveryService.class);
224     }
225 }