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