]> git.basschouten.com Git - openhab-addons.git/blob
94623f750b68f5ff7473f85c16c6a5785cfce11f
[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
17 import java.io.IOException;
18 import java.util.Collection;
19 import java.util.Collections;
20 import java.util.List;
21 import java.util.Set;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.HttpClient;
28 import org.openhab.binding.groupepsa.internal.GroupePSABindingConstants.VendorConstants;
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         if (oAuthService != null) {
98             oAuthFactory.ungetOAuthService(thing.getUID().getAsString());
99             oAuthService = null;
100         }
101     }
102
103     @Override
104     public void initialize() {
105         GroupePSABridgeConfiguration bridgeConfiguration = getConfigAs(GroupePSABridgeConfiguration.class);
106
107         vendor = bridgeConfiguration.getVendor();
108         userName = bridgeConfiguration.getUserName();
109         password = bridgeConfiguration.getPassword();
110         clientId = bridgeConfiguration.getClientId();
111         clientSecret = bridgeConfiguration.getClientSecret();
112
113         final Integer pollingIntervalM = bridgeConfiguration.getPollingInterval();
114
115         if (vendor.isEmpty()) {
116             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-vendor");
117         } else if (userName.isEmpty()) {
118             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-username");
119         } else if (password.isEmpty()) {
120             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-password");
121         } else if (clientId.isEmpty()) {
122             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/conf-error-no-clientid");
123         } else if (clientSecret.isEmpty()) {
124             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
125                     "@text/conf-error-no-clientsecret");
126         } else if (pollingIntervalM < 1) {
127             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
128                     "@text/conf-error-invalid-polling-interval");
129         } else {
130             VendorConstants localVendorConstants = VendorConstants.valueOf(vendor);
131             vendorConstants = localVendorConstants;
132
133             oAuthService = oAuthFactory.createOAuthClientService(thing.getUID().getAsString(), localVendorConstants.url,
134                     null, clientId, clientSecret, localVendorConstants.scope, true);
135
136             groupePSAApi = new GroupePSAConnectApi(httpClient, this, clientId, localVendorConstants.realm);
137
138             startGroupePSABridgePolling(pollingIntervalM);
139
140             updateStatus(ThingStatus.UNKNOWN);
141         }
142     }
143
144     @Override
145     public void handleRemoval() {
146         if (oAuthService != null) {
147             oAuthFactory.deleteServiceAndAccessToken(thing.getUID().getAsString());
148         }
149         super.handleRemoval();
150     }
151
152     private void startGroupePSABridgePolling(@Nullable Integer pollingIntervalM) {
153         if (groupepsaBridgePollingJob == null) {
154             final long pollingIntervalToUse = pollingIntervalM == null ? DEFAULT_POLLING_INTERVAL_M : pollingIntervalM;
155             groupepsaBridgePollingJob = scheduler.scheduleWithFixedDelay(() -> pollGroupePSAs(), 1,
156                     TimeUnit.MINUTES.toSeconds(pollingIntervalToUse), TimeUnit.SECONDS);
157         }
158     }
159
160     private void stopGroupePSABridgePolling() {
161         final ScheduledFuture<?> job = groupepsaBridgePollingJob;
162         if (job != null) {
163             job.cancel(true);
164             groupepsaBridgePollingJob = null;
165         }
166     }
167
168     @Override
169     public void handleCommand(ChannelUID channelUID, Command command) {
170     }
171
172     static Throwable getRootCause(Throwable e) {
173         Throwable nextE;
174         do {
175             nextE = e.getCause();
176             if (nextE != null) {
177                 e = nextE;
178             }
179         } while (nextE != null);
180         return e;
181     }
182
183     public String authenticate() throws GroupePSACommunicationException {
184         OAuthClientService localOAuthService = oAuthService;
185         VendorConstants localVendorConstants = vendorConstants;
186         if (localOAuthService == null) {
187             throw new GroupePSACommunicationException("OAuth service is unexpectedly null");
188         }
189         if (localVendorConstants == null) {
190             throw new GroupePSACommunicationException("Vendor constants are unexpectedly null");
191         }
192         try {
193             AccessTokenResponse result = localOAuthService.getAccessTokenResponse();
194             if (result == null) {
195                 result = localOAuthService.getAccessTokenByResourceOwnerPasswordCredentials(userName, password,
196                         localVendorConstants.scope);
197             }
198             return result.getAccessToken();
199         } catch (OAuthException | IOException | OAuthResponseException e) {
200             throw new GroupePSACommunicationException("Unable to authenticate: " + getRootCause(e).getMessage(), e);
201         }
202     }
203
204     public GroupePSAConnectApi getAPI() throws GroupePSACommunicationException {
205         GroupePSAConnectApi localGroupePSAApi = groupePSAApi;
206         if (localGroupePSAApi == null) {
207             throw new GroupePSACommunicationException("groupePSAApi is unexpectedly null");
208         } else {
209             return localGroupePSAApi;
210         }
211     }
212
213     /**
214      * @return A list of vehicles
215      * @throws GroupePSACommunicationException In case the query cannot be executed
216      *             successfully
217      */
218     public @Nullable List<Vehicle> getVehicles() throws GroupePSACommunicationException {
219         return getAPI().getVehicles();
220     }
221
222     /**
223      * @param id The id of the mower to query
224      * @return A detailed status of the mower with the specified id
225      * @throws GroupePSACommunicationException In case the query cannot be executed
226      *             successfully
227      */
228     public @Nullable VehicleStatus getVehicleStatus(String vin) throws GroupePSACommunicationException {
229         return getAPI().getVehicleStatus(vin);
230     }
231
232     @Override
233     public Collection<Class<? extends ThingHandlerService>> getServices() {
234         return Collections.singleton(GroupePSADiscoveryService.class);
235     }
236 }