]> git.basschouten.com Git - openhab-addons.git/blob
9c6f478156883677c2cde4c547fbfd3273112a7e
[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.mybmw.internal.handler;
14
15 import java.util.Optional;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.openhab.binding.mybmw.internal.dto.remote.ExecutionStatusContainer;
21 import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
22 import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
23 import org.openhab.binding.mybmw.internal.handler.enums.ExecutionState;
24 import org.openhab.binding.mybmw.internal.handler.enums.RemoteService;
25 import org.openhab.binding.mybmw.internal.utils.Constants;
26 import org.openhab.binding.mybmw.internal.utils.HTTPConstants;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 /**
31  * The {@link RemoteServiceExecutor} handles executions of remote services
32  * towards your Vehicle
33  *
34  * @see https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py
35  *
36  * @author Bernd Weymann - Initial contribution
37  * @author Norbert Truchsess - edit and send of charge profile
38  * @author Martin Grassl - rename and refactor for v2
39  */
40 @NonNullByDefault
41 public class RemoteServiceExecutor {
42     private final Logger logger = LoggerFactory.getLogger(RemoteServiceExecutor.class);
43
44     private static final int GIVEUP_COUNTER = 12; // after 12 retries the state update will give up
45     private static final int STATE_UPDATE_SEC = HTTPConstants.HTTP_TIMEOUT_SEC + 1; // regular timeout + 1sec
46
47     private final MyBMWProxy proxy;
48     private final VehicleHandler handler;
49
50     private int counter = 0;
51     private Optional<ScheduledFuture<?>> stateJob = Optional.empty();
52     private Optional<String> serviceExecuting = Optional.empty();
53     private Optional<String> executingEventId = Optional.empty();
54
55     public RemoteServiceExecutor(VehicleHandler vehicleHandler, MyBMWProxy myBmwProxy) {
56         handler = vehicleHandler;
57         proxy = myBmwProxy;
58     }
59
60     public boolean execute(RemoteService service) {
61         synchronized (this) {
62             if (serviceExecuting.isPresent()) {
63                 logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
64                 // only one service executing
65                 return false;
66             }
67             serviceExecuting = Optional.of(service.getId());
68         }
69         try {
70             ExecutionStatusContainer executionStatus = proxy.executeRemoteServiceCall(
71                     handler.getVehicleConfiguration().get().getVin(),
72                     handler.getVehicleConfiguration().get().getVehicleBrand(), service);
73             handleRemoteExecution(executionStatus);
74         } catch (NetworkException e) {
75             handleRemoteServiceException(e);
76         }
77
78         return true;
79     }
80
81     private void getState() {
82         synchronized (this) {
83             serviceExecuting.ifPresentOrElse(service -> {
84                 if (counter >= GIVEUP_COUNTER) {
85                     logger.warn("Giving up updating state for {} after {} times", service, GIVEUP_COUNTER);
86                     handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
87                             ExecutionState.TIMEOUT.name().toLowerCase());
88                     reset();
89                     // immediately refresh data
90                     handler.getData();
91                 } else {
92                     counter++;
93                     try {
94                         ExecutionStatusContainer executionStatusContainer = proxy.executeRemoteServiceStatusCall(
95                                 handler.getVehicleConfiguration().get().getVehicleBrand(), executingEventId.get());
96                         handleRemoteExecution(executionStatusContainer);
97                     } catch (NetworkException e) {
98                         handleRemoteServiceException(e);
99                     }
100                 }
101             }, () -> {
102                 logger.warn("No Service executed to get state");
103             });
104             stateJob = Optional.empty();
105         }
106     }
107
108     private void handleRemoteServiceException(NetworkException e) {
109         synchronized (this) {
110             handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
111                     ExecutionState.ERROR.name().toLowerCase() + Constants.SPACE + Integer.toString(e.getStatus()));
112             reset();
113         }
114     }
115
116     private void handleRemoteExecution(ExecutionStatusContainer executionStatusContainer) {
117         if (!executionStatusContainer.getEventId().isEmpty()) {
118             // service initiated - store event id for further MyBMW updates
119             executingEventId = Optional.of(executionStatusContainer.getEventId());
120             handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
121                     ExecutionState.INITIATED.name().toLowerCase());
122         } else if (!executionStatusContainer.getEventStatus().isEmpty()) {
123             // service status updated
124             synchronized (this) {
125                 handler.updateRemoteExecutionStatus(serviceExecuting.orElse(null),
126                         executionStatusContainer.getEventStatus().toLowerCase());
127                 if (ExecutionState.EXECUTED.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())
128                         || ExecutionState.ERROR.name().equalsIgnoreCase(executionStatusContainer.getEventStatus())) {
129                     // refresh loop ends - update of status handled in the normal refreshInterval.
130                     // Earlier update doesn't show better results!
131                     reset();
132                     return;
133                 }
134             }
135         }
136
137         // schedule even if no result is present until retries exceeded
138         synchronized (this) {
139             stateJob.ifPresent(job -> {
140                 if (!job.isDone()) {
141                     job.cancel(true);
142                 }
143             });
144             stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
145         }
146     }
147
148     private void reset() {
149         serviceExecuting = Optional.empty();
150         executingEventId = Optional.empty();
151         counter = 0;
152     }
153
154     public void cancel() {
155         synchronized (this) {
156             stateJob.ifPresent(action -> {
157                 if (!action.isDone()) {
158                     action.cancel(true);
159                 }
160                 stateJob = Optional.empty();
161             });
162         }
163     }
164 }