2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mybmw.internal.handler;
15 import java.util.Optional;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
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;
31 * The {@link RemoteServiceExecutor} handles executions of remote services
32 * towards your Vehicle
34 * @see https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/remote_services.py
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
41 public class RemoteServiceExecutor {
42 private final Logger logger = LoggerFactory.getLogger(RemoteServiceExecutor.class);
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
47 private final MyBMWProxy proxy;
48 private final VehicleHandler handler;
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();
55 public RemoteServiceExecutor(VehicleHandler vehicleHandler, MyBMWProxy myBmwProxy) {
56 handler = vehicleHandler;
60 public boolean execute(RemoteService service) {
62 if (serviceExecuting.isPresent()) {
63 logger.debug("Execution rejected - {} still pending", serviceExecuting.get());
64 // only one service executing
67 serviceExecuting = Optional.of(service.getId());
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);
81 private void getState() {
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());
89 // immediately refresh data
94 ExecutionStatusContainer executionStatusContainer = proxy.executeRemoteServiceStatusCall(
95 handler.getVehicleConfiguration().get().getVehicleBrand(), executingEventId.get());
96 handleRemoteExecution(executionStatusContainer);
97 } catch (NetworkException e) {
98 handleRemoteServiceException(e);
102 logger.warn("No Service executed to get state");
104 stateJob = Optional.empty();
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()));
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!
137 // schedule even if no result is present until retries exceeded
138 synchronized (this) {
139 stateJob.ifPresent(job -> {
144 stateJob = Optional.of(handler.getScheduler().schedule(this::getState, STATE_UPDATE_SEC, TimeUnit.SECONDS));
148 private void reset() {
149 serviceExecuting = Optional.empty();
150 executingEventId = Optional.empty();
154 public void cancel() {
155 synchronized (this) {
156 stateJob.ifPresent(action -> {
157 if (!action.isDone()) {
160 stateJob = Optional.empty();