]> git.basschouten.com Git - openhab-addons.git/blob
5a830e158935cd1d8d92c3f2a29b56d24bd26936
[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         oAuthFactory.deleteServiceAndAccessToken(thing.getUID().getAsString());
147         super.handleRemoval();
148     }
149
150     private void startGroupePSABridgePolling(@Nullable Integer pollingIntervalM) {
151         if (groupepsaBridgePollingJob == null) {
152             final long pollingIntervalToUse = pollingIntervalM == null ? DEFAULT_POLLING_INTERVAL_M : pollingIntervalM;
153             groupepsaBridgePollingJob = scheduler.scheduleWithFixedDelay(() -> pollGroupePSAs(), 1,
154                     TimeUnit.MINUTES.toSeconds(pollingIntervalToUse), TimeUnit.SECONDS);
155         }
156     }
157
158     private void stopGroupePSABridgePolling() {
159         final ScheduledFuture<?> job = groupepsaBridgePollingJob;
160         if (job != null) {
161             job.cancel(true);
162             groupepsaBridgePollingJob = null;
163         }
164     }
165
166     @Override
167     public void handleCommand(ChannelUID channelUID, Command command) {
168     }
169
170     static Throwable getRootCause(Throwable e) {
171         Throwable nextE;
172         do {
173             nextE = e.getCause();
174             if (nextE != null) {
175                 e = nextE;
176             }
177         } while (nextE != null);
178         return e;
179     }
180
181     public String authenticate() throws GroupePSACommunicationException {
182         OAuthClientService localOAuthService = oAuthService;
183         VendorConstants localVendorConstants = vendorConstants;
184         if (localOAuthService == null) {
185             throw new GroupePSACommunicationException("OAuth service is unexpectedly null");
186         }
187         if (localVendorConstants == null) {
188             throw new GroupePSACommunicationException("Vendor constants are unexpectedly null");
189         }
190         try {
191             AccessTokenResponse result = localOAuthService.getAccessTokenResponse();
192             if (result == null) {
193                 result = localOAuthService.getAccessTokenByResourceOwnerPasswordCredentials(userName, password,
194                         localVendorConstants.scope);
195             }
196             return result.getAccessToken();
197         } catch (OAuthException | IOException | OAuthResponseException e) {
198             throw new GroupePSACommunicationException("Unable to authenticate: " + getRootCause(e).getMessage(), e);
199         }
200     }
201
202     public GroupePSAConnectApi getAPI() throws GroupePSACommunicationException {
203         GroupePSAConnectApi localGroupePSAApi = groupePSAApi;
204         if (localGroupePSAApi == null) {
205             throw new GroupePSACommunicationException("groupePSAApi is unexpectedly null");
206         } else {
207             return localGroupePSAApi;
208         }
209     }
210
211     /**
212      * @return A list of vehicles
213      * @throws GroupePSACommunicationException In case the query cannot be executed
214      *             successfully
215      */
216     public @Nullable List<Vehicle> getVehicles() throws GroupePSACommunicationException {
217         return getAPI().getVehicles();
218     }
219
220     /**
221      * @param id The id of the mower to query
222      * @return A detailed status of the mower with the specified id
223      * @throws GroupePSACommunicationException In case the query cannot be executed
224      *             successfully
225      */
226     public @Nullable VehicleStatus getVehicleStatus(String vin) throws GroupePSACommunicationException {
227         return getAPI().getVehicleStatus(vin);
228     }
229
230     @Override
231     public Collection<Class<? extends ThingHandlerService>> getServices() {
232         return Collections.singleton(GroupePSADiscoveryService.class);
233     }
234 }