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 static org.openhab.binding.mybmw.internal.MyBMWConstants.ADDRESS;
16 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_PROFILE;
17 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_SESSION;
18 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_STATISTICS;
19 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHECK_CONTROL;
20 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_DOORS;
21 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_LOCATION;
22 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_RANGE;
23 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_REMOTE;
24 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_SERVICE;
25 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_STATUS;
26 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_TIRES;
27 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_VEHICLE_IMAGE;
28 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_ENABLED;
29 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_CLIMATE;
30 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_CONTROL;
31 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_LIMIT;
32 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_MODE;
33 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_PREFERENCE;
34 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_PROFILE_TARGET;
35 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_REMAINING;
36 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_STATUS;
37 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHECK_CONTROL;
38 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DATE;
39 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DETAILS;
40 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOORS;
41 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_DRIVER_FRONT;
42 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_DRIVER_REAR;
43 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_PASSENGER_FRONT;
44 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_PASSENGER_REAR;
45 import static org.openhab.binding.mybmw.internal.MyBMWConstants.ENERGY;
46 import static org.openhab.binding.mybmw.internal.MyBMWConstants.ESTIMATED_FUEL_L_100KM;
47 import static org.openhab.binding.mybmw.internal.MyBMWConstants.ESTIMATED_FUEL_MPG;
48 import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_LEFT_CURRENT;
49 import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_LEFT_TARGET;
50 import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_RIGHT_CURRENT;
51 import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_RIGHT_TARGET;
52 import static org.openhab.binding.mybmw.internal.MyBMWConstants.GPS;
53 import static org.openhab.binding.mybmw.internal.MyBMWConstants.HEADING;
54 import static org.openhab.binding.mybmw.internal.MyBMWConstants.HOME_DISTANCE;
55 import static org.openhab.binding.mybmw.internal.MyBMWConstants.HOOD;
56 import static org.openhab.binding.mybmw.internal.MyBMWConstants.IMAGE_FORMAT;
57 import static org.openhab.binding.mybmw.internal.MyBMWConstants.IMAGE_VIEWPORT;
58 import static org.openhab.binding.mybmw.internal.MyBMWConstants.ISSUE;
59 import static org.openhab.binding.mybmw.internal.MyBMWConstants.LAST_FETCHED;
60 import static org.openhab.binding.mybmw.internal.MyBMWConstants.LAST_UPDATE;
61 import static org.openhab.binding.mybmw.internal.MyBMWConstants.LOCK;
62 import static org.openhab.binding.mybmw.internal.MyBMWConstants.MILEAGE;
63 import static org.openhab.binding.mybmw.internal.MyBMWConstants.NAME;
64 import static org.openhab.binding.mybmw.internal.MyBMWConstants.PLUG_CONNECTION;
65 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_ELECTRIC;
66 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_FUEL;
67 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_HYBRID;
68 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_ELECTRIC;
69 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_FUEL;
70 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_HYBRID;
71 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RAW;
72 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_LEFT_CURRENT;
73 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_LEFT_TARGET;
74 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_RIGHT_CURRENT;
75 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_RIGHT_TARGET;
76 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMAINING_FUEL;
77 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_SERVICE_COMMAND;
78 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMOTE_STATE;
79 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SERVICE_DATE;
80 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SERVICE_MILEAGE;
81 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SESSIONS;
82 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SEVERITY;
83 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SOC;
84 import static org.openhab.binding.mybmw.internal.MyBMWConstants.STATUS;
85 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUBTITLE;
86 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUNROOF;
87 import static org.openhab.binding.mybmw.internal.MyBMWConstants.TITLE;
88 import static org.openhab.binding.mybmw.internal.MyBMWConstants.TRUNK;
89 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOWS;
90 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_DRIVER_FRONT;
91 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_DRIVER_REAR;
92 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_PASSENGER_FRONT;
93 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_PASSENGER_REAR;
95 import java.time.DayOfWeek;
96 import java.time.LocalTime;
97 import java.time.ZoneId;
98 import java.time.ZonedDateTime;
99 import java.util.ArrayList;
100 import java.util.EnumSet;
101 import java.util.List;
102 import java.util.Optional;
103 import java.util.Set;
104 import java.util.concurrent.ScheduledExecutorService;
105 import java.util.concurrent.ScheduledFuture;
106 import java.util.concurrent.TimeUnit;
108 import javax.measure.Unit;
109 import javax.measure.quantity.Length;
111 import org.eclipse.jdt.annotation.NonNullByDefault;
112 import org.eclipse.jdt.annotation.Nullable;
113 import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
114 import org.openhab.binding.mybmw.internal.MyBMWVehicleConfiguration;
115 import org.openhab.binding.mybmw.internal.dto.charge.ChargingProfile;
116 import org.openhab.binding.mybmw.internal.dto.charge.ChargingSession;
117 import org.openhab.binding.mybmw.internal.dto.charge.ChargingSessionsContainer;
118 import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
119 import org.openhab.binding.mybmw.internal.dto.charge.ChargingStatisticsContainer;
120 import org.openhab.binding.mybmw.internal.dto.vehicle.CheckControlMessage;
121 import org.openhab.binding.mybmw.internal.dto.vehicle.RequiredService;
122 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleDoorsState;
123 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleLocation;
124 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleRoofState;
125 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleState;
126 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
127 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleTireStates;
128 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleWindowsState;
129 import org.openhab.binding.mybmw.internal.handler.backend.MyBMWProxy;
130 import org.openhab.binding.mybmw.internal.handler.backend.NetworkException;
131 import org.openhab.binding.mybmw.internal.utils.ChargingProfileUtils;
132 import org.openhab.binding.mybmw.internal.utils.ChargingProfileUtils.TimedChannel;
133 import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper;
134 import org.openhab.binding.mybmw.internal.utils.ChargingProfileWrapper.ProfileKey;
135 import org.openhab.binding.mybmw.internal.utils.Constants;
136 import org.openhab.binding.mybmw.internal.utils.Converter;
137 import org.openhab.binding.mybmw.internal.utils.ImageProperties;
138 import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
139 import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
140 import org.openhab.core.i18n.LocationProvider;
141 import org.openhab.core.i18n.TimeZoneProvider;
142 import org.openhab.core.io.net.http.HttpUtil;
143 import org.openhab.core.library.types.DateTimeType;
144 import org.openhab.core.library.types.DecimalType;
145 import org.openhab.core.library.types.OnOffType;
146 import org.openhab.core.library.types.PointType;
147 import org.openhab.core.library.types.QuantityType;
148 import org.openhab.core.library.types.RawType;
149 import org.openhab.core.library.types.StringType;
150 import org.openhab.core.library.unit.SIUnits;
151 import org.openhab.core.library.unit.Units;
152 import org.openhab.core.thing.Bridge;
153 import org.openhab.core.thing.ChannelUID;
154 import org.openhab.core.thing.Thing;
155 import org.openhab.core.thing.ThingStatus;
156 import org.openhab.core.thing.ThingStatusDetail;
157 import org.openhab.core.thing.binding.BaseThingHandler;
158 import org.openhab.core.thing.binding.BridgeHandler;
159 import org.openhab.core.types.Command;
160 import org.openhab.core.types.CommandOption;
161 import org.openhab.core.types.RefreshType;
162 import org.openhab.core.types.State;
163 import org.openhab.core.types.UnDefType;
164 import org.slf4j.Logger;
165 import org.slf4j.LoggerFactory;
168 * The {@link VehicleHandler} handles responses from BMW API
170 * the introduction of channelToBeUpdated is ugly, but if there is a refresh of one channel, always all channels were
173 * @author Bernd Weymann - Initial contribution
174 * @author Norbert Truchsess - edit and send charge profile
175 * @author Martin Grassl - refactoring, merge with VehicleChannelHandler
176 * @author Mark Herwege - refactoring, V2 API charging
179 public class VehicleHandler extends BaseThingHandler {
180 private final Logger logger = LoggerFactory.getLogger(VehicleHandler.class);
182 private boolean hasFuel = false;
183 private boolean isElectric = false;
184 private boolean isHybrid = false;
187 private volatile List<RequiredService> serviceList = List.of();
188 private volatile String selectedService = Constants.UNDEF;
189 private volatile List<CheckControlMessage> checkControlList = List.of();
190 private volatile String selectedCC = Constants.UNDEF;
191 private volatile List<ChargingSession> sessionList = List.of();
192 private volatile String selectedSession = Constants.UNDEF;
194 private MyBMWCommandOptionProvider commandOptionProvider;
195 private LocationProvider locationProvider;
196 private TimeZoneProvider timeZoneProvider;
199 private Optional<VehicleStateContainer> vehicleStatusCache = Optional.empty();
200 private Optional<byte[]> imageCache = Optional.empty();
202 private Optional<MyBMWProxy> proxy = Optional.empty();
203 private Optional<RemoteServiceExecutor> remote = Optional.empty();
204 private Optional<MyBMWVehicleConfiguration> vehicleConfiguration = Optional.empty();
205 private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
206 private Optional<ScheduledFuture<?>> editTimeout = Optional.empty();
208 private ImageProperties imageProperties = new ImageProperties();
210 public VehicleHandler(Thing thing, MyBMWCommandOptionProvider commandOptionProvider,
211 LocationProvider locationProvider, TimeZoneProvider timeZoneProvider, String driveTrain) {
213 logger.trace("VehicleHandler.constructor {}, {}", thing.getUID(), driveTrain);
214 this.commandOptionProvider = commandOptionProvider;
215 this.timeZoneProvider = timeZoneProvider;
216 this.locationProvider = locationProvider;
217 if (locationProvider.getLocation() == null) {
218 logger.debug("Home location not available");
221 hasFuel = driveTrain.equals(VehicleType.CONVENTIONAL.toString())
222 || driveTrain.equals(VehicleType.PLUGIN_HYBRID.toString())
223 || driveTrain.equals(VehicleType.ELECTRIC_REX.toString())
224 || driveTrain.equals(VehicleType.MILD_HYBRID.toString());
225 isElectric = driveTrain.equals(VehicleType.PLUGIN_HYBRID.toString())
226 || driveTrain.equals(VehicleType.ELECTRIC_REX.toString())
227 || driveTrain.equals(VehicleType.ELECTRIC.toString());
228 isHybrid = hasFuel && isElectric;
230 setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
233 private void setOptions(final String group, final String id, List<CommandOption> options) {
234 commandOptionProvider.setCommandOptions(new ChannelUID(thing.getUID(), group, id), options);
238 public void initialize() {
239 logger.trace("VehicleHandler.initialize");
240 updateStatus(ThingStatus.UNKNOWN);
241 vehicleConfiguration = Optional.of(getConfigAs(MyBMWVehicleConfiguration.class));
243 Bridge bridge = getBridge();
244 if (bridge != null) {
245 BridgeHandler handler = bridge.getHandler();
246 if (handler != null) {
247 proxy = ((MyBMWBridgeHandler) handler).getMyBmwProxy();
248 remote = Optional.of(new RemoteServiceExecutor(this, proxy.get()));
250 logger.debug("Bridge Handler null");
253 logger.debug("Bridge null");
256 imageProperties = new ImageProperties();
257 updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, Converter.toTitleCase(imageProperties.viewport),
260 // start update schedule
261 startSchedule(vehicleConfiguration.get().getRefreshInterval());
264 private void startSchedule(int interval) {
265 logger.trace("VehicleHandler.startSchedule");
266 refreshJob.ifPresentOrElse(job -> {
267 if (job.isCancelled()) {
268 refreshJob = Optional
269 .of(scheduler.scheduleWithFixedDelay(this::getData, 0, interval, TimeUnit.MINUTES));
270 } // else - scheduler is already running!
272 refreshJob = Optional.of(scheduler.scheduleWithFixedDelay(this::getData, 0, interval, TimeUnit.MINUTES));
277 public void dispose() {
278 logger.trace("VehicleHandler.dispose");
279 refreshJob.ifPresent(job -> job.cancel(true));
280 editTimeout.ifPresent(job -> job.cancel(true));
281 remote.ifPresent(RemoteServiceExecutor::cancel);
284 public void getData() {
285 logger.trace("VehicleHandler.getData");
286 proxy.ifPresentOrElse(prox -> {
287 vehicleConfiguration.ifPresentOrElse(config -> {
289 boolean stateError = false;
291 VehicleStateContainer vehicleState = prox.requestVehicleState(config.getVin(),
292 config.getVehicleBrand());
293 triggerVehicleStatusUpdate(vehicleState, null);
295 } catch (NetworkException e) {
296 logger.debug("{}", e.toString());
297 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
298 "Vehicle State Update failed");
302 if (!stateError && isElectric) {
304 updateChargingStatistics(
305 prox.requestChargeStatistics(config.getVin(), config.getVehicleBrand()), null);
306 updateChargingSessions(prox.requestChargeSessions(config.getVin(), config.getVehicleBrand()),
308 } catch (NetworkException e) {
309 logger.debug("{}", e.toString());
312 if (!stateError && !imageCache.isPresent() && !imageProperties.failLimitReached()) {
314 updateImage(prox.requestImage(config.getVin(), config.getVehicleBrand(), imageProperties));
315 } catch (NetworkException e) {
316 logger.debug("{}", e.toString());
320 logger.warn("MyBMW Vehicle Configuration isn't present");
323 logger.warn("MyBMWProxy isn't present");
327 private void triggerVehicleStatusUpdate(VehicleStateContainer vehicleState, @Nullable String channelToBeUpdated) {
328 logger.trace("VehicleHandler.triggerVehicleStatusUpdate for {}", channelToBeUpdated);
329 if (vehicleConfiguration.isPresent()) {
330 vehicleStatusCache = Optional.of(vehicleState);
331 updateChannel(CHANNEL_GROUP_STATUS, RAW, vehicleState.getRawStateJson(), channelToBeUpdated);
333 updateVehicleStatus(vehicleState.getState(), channelToBeUpdated);
335 updateChargingProfile(vehicleState.getState().getChargingProfile(), channelToBeUpdated);
338 updateStatus(ThingStatus.ONLINE);
340 logger.debug("configuration not present");
344 public void updateRemoteExecutionStatus(@Nullable String service, String status) {
345 updateChannel(CHANNEL_GROUP_REMOTE, REMOTE_STATE,
346 (service == null ? "-" : service) + Constants.SPACE + status.toLowerCase(), null);
349 public Optional<MyBMWVehicleConfiguration> getVehicleConfiguration() {
350 logger.trace("VehicleHandler.getVehicleConfiguration");
351 return vehicleConfiguration;
354 public ScheduledExecutorService getScheduler() {
355 logger.trace("VehicleHandler.getScheduler");
359 private void updateChannel(final String group, final String id, final String state,
360 @Nullable final String channelToBeUpdated) {
361 updateChannel(group, id, StringType.valueOf(state), channelToBeUpdated);
365 * this method sets the state for a single channel. if a channelToBeUpdated is provided, the update will only take
366 * place for that single channel.
368 private void updateChannel(final String group, final String id, final State state,
369 @Nullable final String channelToBeUpdated) {
370 if (channelToBeUpdated == null || id.equals(channelToBeUpdated)) {
371 if (!"png".equals(id)) {
372 logger.trace("updating channel {}, {}, {}", group, id, state.toFullString());
374 logger.trace("updating channel {}, {}, {}", group, id, "not printed");
377 updateState(new ChannelUID(thing.getUID(), group, id), state);
381 private void updateChargingStatistics(ChargingStatisticsContainer chargingStatisticsContainer,
382 @Nullable String channelToBeUpdated) {
383 if (!"".equals(chargingStatisticsContainer.getDescription())) {
384 updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, TITLE, chargingStatisticsContainer.getDescription(),
386 updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, ENERGY, QuantityType
387 .valueOf(chargingStatisticsContainer.getStatistics().getTotalEnergyCharged(), Units.KILOWATT_HOUR),
389 updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, SESSIONS,
390 DecimalType.valueOf(Integer
391 .toString(chargingStatisticsContainer.getStatistics().getNumberOfChargingSessions())),
397 * updates the channels with the current state of the vehicle
399 * @param vehicleStateState
401 private void updateVehicleStatus(VehicleState vehicleStateState, @Nullable String channelToBeUpdated) {
402 boolean isLeftSteering = vehicleStateState.isLeftSteering();
404 updateVehicleOverallStatus(vehicleStateState, channelToBeUpdated);
405 updateRange(vehicleStateState, channelToBeUpdated);
406 updateDoors(vehicleStateState.getDoorsState(), isLeftSteering, channelToBeUpdated);
407 updateWindows(vehicleStateState.getWindowsState(), isLeftSteering, channelToBeUpdated);
408 updateRoof(vehicleStateState.getRoofState(), channelToBeUpdated);
409 updatePosition(vehicleStateState.getLocation(), channelToBeUpdated);
410 updateServices(vehicleStateState.getRequiredServices(), channelToBeUpdated);
411 updateCheckControls(vehicleStateState.getCheckControlMessages(), channelToBeUpdated);
412 updateTires(vehicleStateState.getTireState(), channelToBeUpdated);
415 private void updateTires(@Nullable VehicleTireStates vehicleTireStates, @Nullable String channelToBeUpdated) {
416 if (vehicleTireStates == null) {
417 updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
418 updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
419 updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
420 updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
421 updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
422 updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
423 updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT, UnDefType.UNDEF, channelToBeUpdated);
424 updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET, UnDefType.UNDEF, channelToBeUpdated);
426 updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT,
427 calculatePressure(vehicleTireStates.getFrontLeft().getStatus().getCurrentPressure()),
429 updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET,
430 calculatePressure(vehicleTireStates.getFrontLeft().getStatus().getTargetPressure()),
432 updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT,
433 calculatePressure(vehicleTireStates.getFrontRight().getStatus().getCurrentPressure()),
435 updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET,
436 calculatePressure(vehicleTireStates.getFrontRight().getStatus().getTargetPressure()),
438 updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT,
439 calculatePressure(vehicleTireStates.getRearLeft().getStatus().getCurrentPressure()),
441 updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET,
442 calculatePressure(vehicleTireStates.getRearLeft().getStatus().getTargetPressure()),
444 updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT,
445 calculatePressure(vehicleTireStates.getRearRight().getStatus().getCurrentPressure()),
447 updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET,
448 calculatePressure(vehicleTireStates.getRearRight().getStatus().getTargetPressure()),
454 * if the pressure is undef it is < 0
459 private State calculatePressure(int pressure) {
461 return QuantityType.valueOf(pressure / 100.0, Units.BAR);
463 return UnDefType.UNDEF;
467 private void updateVehicleOverallStatus(VehicleState vehicleState, @Nullable String channelToBeUpdated) {
468 updateChannel(CHANNEL_GROUP_STATUS, LOCK,
469 Converter.toTitleCase(vehicleState.getDoorsState().getCombinedSecurityState()), channelToBeUpdated);
470 updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
471 VehicleStatusUtils.getNextServiceDate(vehicleState.getRequiredServices()), channelToBeUpdated);
472 updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
473 VehicleStatusUtils.getNextServiceMileage(vehicleState.getRequiredServices()), channelToBeUpdated);
474 updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
475 Converter.toTitleCase(vehicleState.getOverallCheckControlStatus()), channelToBeUpdated);
476 updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
477 Converter.zonedToLocalDateTime(vehicleState.getLastUpdatedAt(), timeZoneProvider.getTimeZone()),
479 updateChannel(CHANNEL_GROUP_STATUS, LAST_FETCHED,
480 Converter.zonedToLocalDateTime(vehicleState.getLastFetched(), timeZoneProvider.getTimeZone()),
482 updateChannel(CHANNEL_GROUP_STATUS, DOORS,
483 Converter.toTitleCase(vehicleState.getDoorsState().getCombinedState()), channelToBeUpdated);
484 updateChannel(CHANNEL_GROUP_STATUS, WINDOWS,
485 Converter.toTitleCase(vehicleState.getWindowsState().getCombinedState()), channelToBeUpdated);
488 updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
489 Converter.getConnectionState(vehicleState.getElectricChargingState().isChargerConnected()),
491 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
492 Converter.toTitleCase(vehicleState.getElectricChargingState().getChargingStatus()),
495 int remainingTime = vehicleState.getElectricChargingState().getRemainingChargingMinutes();
496 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING,
497 remainingTime >= 0 ? QuantityType.valueOf(remainingTime, Units.MINUTE) : UnDefType.UNDEF,
502 private void updateRange(VehicleState vehicleState, @Nullable String channelToBeUpdated) {
503 // get the right unit
504 Unit<Length> lengthUnit = Constants.KILOMETRE_UNIT;
507 int rangeElectric = vehicleState.getElectricChargingState().getRange();
508 QuantityType<Length> qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
509 QuantityType<Length> qtElectricRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeElectric),
511 updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC, qtElectricRange, channelToBeUpdated);
512 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC, qtElectricRadius, channelToBeUpdated);
515 if (hasFuel && !isHybrid) {
516 int rangeFuel = vehicleState.getCombustionFuelLevel().getRange();
517 QuantityType<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
518 QuantityType<Length> qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
519 updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange, channelToBeUpdated);
520 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius, channelToBeUpdated);
524 int rangeCombined = vehicleState.getRange();
526 // there is a bug/feature in the API that the fuel range is the same like the combined range, hence in case
527 // of hybrid the fuel range has to be subtracted by the electric range
528 int rangeFuel = vehicleState.getCombustionFuelLevel().getRange()
529 - vehicleState.getElectricChargingState().getRange();
531 QuantityType<Length> qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
532 QuantityType<Length> qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeCombined),
534 updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID, qtHybridRange, channelToBeUpdated);
535 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID, qtHybridRadius, channelToBeUpdated);
537 QuantityType<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
538 QuantityType<Length> qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
539 updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange, channelToBeUpdated);
540 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius, channelToBeUpdated);
543 if (vehicleState.getCurrentMileage() == Constants.INT_UNDEF) {
544 updateChannel(CHANNEL_GROUP_RANGE, MILEAGE, UnDefType.UNDEF, channelToBeUpdated);
546 updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
547 QuantityType.valueOf(vehicleState.getCurrentMileage(), lengthUnit), channelToBeUpdated);
551 CHANNEL_GROUP_RANGE, SOC, QuantityType
552 .valueOf(vehicleState.getElectricChargingState().getChargingLevelPercent(), Units.PERCENT),
556 updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
557 QuantityType.valueOf(vehicleState.getCombustionFuelLevel().getRemainingFuelLiters(), Units.LITRE),
560 if (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() > 0
561 && vehicleState.getCombustionFuelLevel().getRange() > 1) {
562 double estimatedFuelConsumption = vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() * 1.0
563 / vehicleState.getCombustionFuelLevel().getRange() * 100.0;
564 updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_L_100KM,
565 DecimalType.valueOf(estimatedFuelConsumption + ""), channelToBeUpdated);
566 updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_MPG,
567 DecimalType.valueOf((235.214583 / estimatedFuelConsumption) + ""), channelToBeUpdated);
569 updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_L_100KM, UnDefType.UNDEF, channelToBeUpdated);
570 updateChannel(CHANNEL_GROUP_RANGE, ESTIMATED_FUEL_MPG, UnDefType.UNDEF, channelToBeUpdated);
575 private void updateCheckControls(List<CheckControlMessage> checkControlMessages,
576 @Nullable String channelToBeUpdated) {
577 if (checkControlMessages.isEmpty()) {
578 // No Check Control available - show not active
579 CheckControlMessage checkControlMessage = new CheckControlMessage();
580 checkControlMessage.setName(Constants.NO_ENTRIES);
581 checkControlMessage.setDescription(Constants.NO_ENTRIES);
582 checkControlMessage.setSeverity(Constants.NO_ENTRIES);
583 checkControlMessage.setType(Constants.NO_ENTRIES);
584 checkControlMessage.setId(-1);
585 checkControlMessages.add(checkControlMessage);
588 // add all elements to options
589 checkControlList = checkControlMessages;
590 List<CommandOption> ccmDescriptionOptions = new ArrayList<>();
591 boolean isSelectedElementIn = false;
593 for (CheckControlMessage checkControlMessage : checkControlList) {
594 ccmDescriptionOptions.add(
595 new CommandOption(Integer.toString(index), Converter.toTitleCase(checkControlMessage.getType())));
596 if (selectedCC.equals(checkControlMessage.getType())) {
597 isSelectedElementIn = true;
601 setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
603 // if current selected item isn't anymore in the list select first entry
604 if (!isSelectedElementIn) {
605 selectCheckControl(0, channelToBeUpdated);
609 private void selectCheckControl(int index, @Nullable String channelToBeUpdated) {
610 if (index >= 0 && index < checkControlList.size()) {
611 CheckControlMessage checkControlMessage = checkControlList.get(index);
612 selectedCC = checkControlMessage.getType();
613 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, Converter.toTitleCase(checkControlMessage.getType()),
615 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS,
616 StringType.valueOf(checkControlMessage.getDescription()), channelToBeUpdated);
617 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, SEVERITY,
618 Converter.toTitleCase(checkControlMessage.getSeverity()), channelToBeUpdated);
622 private void updateServices(List<RequiredService> requiredServiceList, @Nullable String channelToBeUpdated) {
623 // if list is empty add "undefined" element
624 if (requiredServiceList.isEmpty()) {
625 RequiredService requiredService = new RequiredService();
626 requiredService.setType(Constants.NO_ENTRIES);
627 requiredService.setDescription(Constants.NO_ENTRIES);
628 requiredServiceList.add(requiredService);
631 // add all elements to options
632 serviceList = requiredServiceList;
633 List<CommandOption> serviceNameOptions = new ArrayList<>();
634 boolean isSelectedElementIn = false;
636 for (RequiredService requiredService : requiredServiceList) {
637 // create StateOption with "value = list index" and "label = human readable
640 .add(new CommandOption(Integer.toString(index), Converter.toTitleCase(requiredService.getType())));
641 if (selectedService.equals(requiredService.getType())) {
642 isSelectedElementIn = true;
647 setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
649 // if current selected item isn't anymore in the list select first entry
650 if (!isSelectedElementIn) {
651 selectService(0, channelToBeUpdated);
655 private void selectService(int index, @Nullable String channelToBeUpdated) {
656 if (index >= 0 && index < serviceList.size()) {
657 RequiredService serviceEntry = serviceList.get(index);
658 selectedService = serviceEntry.getType();
659 updateChannel(CHANNEL_GROUP_SERVICE, NAME, Converter.toTitleCase(serviceEntry.getType()),
661 updateChannel(CHANNEL_GROUP_SERVICE, DETAILS, StringType.valueOf(serviceEntry.getDescription()),
663 updateChannel(CHANNEL_GROUP_SERVICE, DATE,
664 Converter.zonedToLocalDateTime(serviceEntry.getDateTime(), timeZoneProvider.getTimeZone()),
667 if (serviceEntry.getMileage() > 0) {
668 updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
669 QuantityType.valueOf(serviceEntry.getMileage(), Constants.KILOMETRE_UNIT), channelToBeUpdated);
671 updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE, UnDefType.UNDEF, channelToBeUpdated);
676 private void updateChargingSessions(ChargingSessionsContainer chargeSessionsContainer,
677 @Nullable String channelToBeUpdated) {
678 List<ChargingSession> chargeSessions = new ArrayList<>();
680 if (chargeSessionsContainer.chargingSessions != null
681 && chargeSessionsContainer.chargingSessions.getSessions() != null
682 && !chargeSessionsContainer.chargingSessions.getSessions().isEmpty()) {
683 chargeSessions.addAll(chargeSessionsContainer.chargingSessions.getSessions());
685 // if list is empty add "undefined" element
686 ChargingSession cs = new ChargingSession();
687 cs.setTitle(Constants.NO_ENTRIES);
688 chargeSessions.add(cs);
691 // add all elements to options
692 sessionList = chargeSessions;
693 List<CommandOption> sessionNameOptions = new ArrayList<>();
694 boolean isSelectedElementIn = false;
696 for (ChargingSession session : sessionList) {
697 // create StateOption with "value = list index" and "label = human readable
699 sessionNameOptions.add(new CommandOption(Integer.toString(index), session.getTitle()));
700 if (selectedSession.equals(session.getTitle())) {
701 isSelectedElementIn = true;
705 setOptions(CHANNEL_GROUP_CHARGE_SESSION, TITLE, sessionNameOptions);
707 // if current selected item isn't anymore in the list select first entry
708 if (!isSelectedElementIn) {
709 selectSession(0, channelToBeUpdated);
713 private void selectSession(int index, @Nullable String channelToBeUpdated) {
714 if (index >= 0 && index < sessionList.size()) {
715 ChargingSession sessionEntry = sessionList.get(index);
716 selectedSession = sessionEntry.getTitle();
717 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, TITLE, StringType.valueOf(sessionEntry.getTitle()),
719 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, SUBTITLE, StringType.valueOf(sessionEntry.getSubtitle()),
721 if (sessionEntry.getEnergyCharged() != null) {
722 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(sessionEntry.getEnergyCharged()),
725 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(Constants.UNDEF),
728 if (sessionEntry.getIssues() != null) {
729 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(sessionEntry.getIssues()),
732 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(Constants.HYPHEN),
735 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, STATUS, StringType.valueOf(sessionEntry.getSessionStatus()),
740 private void updateChargingProfile(ChargingProfile cp, @Nullable String channelToBeUpdated) {
741 ChargingProfileWrapper cpw = new ChargingProfileWrapper(cp);
743 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_PREFERENCE, StringType.valueOf(cpw.getPreference()),
745 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_MODE, StringType.valueOf(cpw.getMode()),
747 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CONTROL, StringType.valueOf(cpw.getControlType()),
749 ChargingSettings cs = cpw.getChargingSettings();
751 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_TARGET,
752 QuantityType.valueOf(cs.getTargetSoc(), Units.PERCENT), channelToBeUpdated);
754 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_LIMIT,
755 OnOffType.from(cs.isAcCurrentLimitActive()), channelToBeUpdated);
757 final Boolean climate = cpw.isEnabled(ProfileKey.CLIMATE);
758 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CLIMATE,
759 climate == null ? UnDefType.UNDEF : OnOffType.from(climate), channelToBeUpdated);
760 updateTimedState(cpw, ProfileKey.WINDOWSTART, channelToBeUpdated);
761 updateTimedState(cpw, ProfileKey.WINDOWEND, channelToBeUpdated);
762 updateTimedState(cpw, ProfileKey.TIMER1, channelToBeUpdated);
763 updateTimedState(cpw, ProfileKey.TIMER2, channelToBeUpdated);
764 updateTimedState(cpw, ProfileKey.TIMER3, channelToBeUpdated);
765 updateTimedState(cpw, ProfileKey.TIMER4, channelToBeUpdated);
768 private void updateTimedState(ChargingProfileWrapper profile, ProfileKey key, @Nullable String channelToBeUpdated) {
769 final TimedChannel timed = ChargingProfileUtils.getTimedChannel(key);
771 final LocalTime time = profile.getTime(key);
772 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.time,
773 time.equals(Constants.NULL_LOCAL_TIME) ? UnDefType.UNDEF
774 : new DateTimeType(ZonedDateTime.of(Constants.EPOCH_DAY, time, ZoneId.systemDefault())),
776 if (timed.timer != null) {
777 final Boolean enabled = profile.isEnabled(key);
778 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.timer + CHARGE_ENABLED,
779 enabled == null ? UnDefType.UNDEF : OnOffType.from(enabled), channelToBeUpdated);
781 final Set<DayOfWeek> days = profile.getDays(key);
782 EnumSet.allOf(DayOfWeek.class).forEach(day -> {
783 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE,
784 timed.timer + ChargingProfileUtils.getDaysChannel(day),
785 days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)),
793 private void updateDoors(VehicleDoorsState vehicleDoorsState, boolean isLeftSteering,
794 @Nullable String channelToBeUpdated) {
795 updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
796 StringType.valueOf(Converter.toTitleCase(
797 isLeftSteering ? vehicleDoorsState.getLeftFront() : vehicleDoorsState.getRightFront())),
799 updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
800 StringType.valueOf(Converter.toTitleCase(
801 isLeftSteering ? vehicleDoorsState.getLeftRear() : vehicleDoorsState.getRightRear())),
803 updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
804 StringType.valueOf(Converter.toTitleCase(
805 isLeftSteering ? vehicleDoorsState.getRightFront() : vehicleDoorsState.getLeftFront())),
807 updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
808 StringType.valueOf(Converter.toTitleCase(
809 isLeftSteering ? vehicleDoorsState.getRightRear() : vehicleDoorsState.getLeftRear())),
811 updateChannel(CHANNEL_GROUP_DOORS, TRUNK,
812 StringType.valueOf(Converter.toTitleCase(vehicleDoorsState.getTrunk())), channelToBeUpdated);
813 updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(vehicleDoorsState.getHood())),
817 private void updateWindows(VehicleWindowsState vehicleWindowState, boolean isLeftSteering,
818 @Nullable String channelToBeUpdated) {
819 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
820 StringType.valueOf(Converter.toTitleCase(
821 isLeftSteering ? vehicleWindowState.getLeftFront() : vehicleWindowState.getRightFront())),
823 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
824 StringType.valueOf(Converter.toTitleCase(
825 isLeftSteering ? vehicleWindowState.getLeftRear() : vehicleWindowState.getRightRear())),
827 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
828 StringType.valueOf(Converter.toTitleCase(
829 isLeftSteering ? vehicleWindowState.getRightFront() : vehicleWindowState.getLeftFront())),
831 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
832 StringType.valueOf(Converter.toTitleCase(
833 isLeftSteering ? vehicleWindowState.getRightRear() : vehicleWindowState.getLeftRear())),
837 private void updateRoof(VehicleRoofState vehicleRoofState, @Nullable String channelToBeUpdated) {
838 updateChannel(CHANNEL_GROUP_DOORS, SUNROOF,
839 StringType.valueOf(Converter.toTitleCase(vehicleRoofState.getRoofState())), channelToBeUpdated);
842 private void updatePosition(VehicleLocation location, @Nullable String channelToBeUpdated) {
843 if (location.getCoordinates().getLatitude() < 0) {
844 updateChannel(CHANNEL_GROUP_LOCATION, GPS, UnDefType.UNDEF, channelToBeUpdated);
845 updateChannel(CHANNEL_GROUP_LOCATION, HEADING, UnDefType.UNDEF, channelToBeUpdated);
846 updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, UnDefType.UNDEF, channelToBeUpdated);
847 updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF, channelToBeUpdated);
849 PointType vehicleLocation = PointType.valueOf(Double.toString(location.getCoordinates().getLatitude()) + ","
850 + Double.toString(location.getCoordinates().getLongitude()));
851 updateChannel(CHANNEL_GROUP_LOCATION, GPS, vehicleLocation, channelToBeUpdated);
852 updateChannel(CHANNEL_GROUP_LOCATION, HEADING,
853 QuantityType.valueOf(location.getHeading(), Units.DEGREE_ANGLE), channelToBeUpdated);
854 updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(location.getAddress().getFormatted()),
856 PointType homeLocation = locationProvider.getLocation();
857 if (homeLocation != null) {
858 updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE,
859 QuantityType.valueOf(vehicleLocation.distanceFrom(homeLocation).intValue(), SIUnits.METRE),
862 updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF, channelToBeUpdated);
868 public void handleCommand(ChannelUID channelUID, Command command) {
869 logger.trace("VehicleHandler.handleCommand {}, {}, {}", command.toFullString(), channelUID.getAsString(),
870 channelUID.getIdWithoutGroup());
871 String group = channelUID.getGroupId();
874 logger.debug("Cannot handle command {}, no group for channel {}", command.toFullString(),
875 channelUID.getAsString());
879 if (command instanceof RefreshType) {
880 // Refresh of Channels with cached values
881 if (CHANNEL_GROUP_VEHICLE_IMAGE.equals(group)) {
882 imageCache.ifPresent(image -> updateImage(image));
884 vehicleStatusCache.ifPresent(
885 vehicleStatus -> triggerVehicleStatusUpdate(vehicleStatus, channelUID.getIdWithoutGroup()));
887 } else if (command instanceof StringType) {
888 // Check for Channel Group and corresponding Actions
890 case CHANNEL_GROUP_REMOTE:
891 // Executing Remote Services
892 String serviceCommand = ((StringType) command).toFullString();
893 remote.ifPresent(remot -> {
894 RemoteServiceUtils.getRemoteServiceFromCommand(serviceCommand)
895 .ifPresentOrElse(service -> remot.execute(service), () -> {
896 logger.debug("Remote service execution {} unknown", serviceCommand);
900 case CHANNEL_GROUP_VEHICLE_IMAGE:
902 vehicleConfiguration.ifPresent(config -> {
903 if (channelUID.getIdWithoutGroup().equals(IMAGE_VIEWPORT)) {
904 String newViewport = command.toString();
905 synchronized (imageProperties) {
906 if (!imageProperties.viewport.equals(newViewport)) {
907 imageProperties = new ImageProperties(newViewport);
908 imageCache = Optional.empty();
909 Optional<byte[]> imageContent = proxy.map(prox -> {
911 return prox.requestImage(config.getVin(), config.getVehicleBrand(),
913 } catch (NetworkException e) {
914 logger.debug("{}", e.toString());
915 return "".getBytes();
918 imageContent.ifPresent(imageContentData -> updateImage(imageContentData));
921 updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_VIEWPORT, StringType.valueOf(newViewport),
926 case CHANNEL_GROUP_SERVICE:
927 int serviceIndex = Converter.parseIntegerString(command.toFullString());
928 if (serviceIndex != -1) {
929 selectService(serviceIndex, null);
931 logger.debug("Cannot select Service index {}", command.toFullString());
934 case CHANNEL_GROUP_CHECK_CONTROL:
935 int checkControlIndex = Converter.parseIntegerString(command.toFullString());
936 if (checkControlIndex != -1) {
937 selectCheckControl(checkControlIndex, null);
939 logger.debug("Cannot select CheckControl index {}", command.toFullString());
942 case CHANNEL_GROUP_CHARGE_SESSION:
943 int sessionIndex = Converter.parseIntegerString(command.toFullString());
944 if (sessionIndex != -1) {
945 selectSession(sessionIndex, null);
947 logger.debug("Cannot select Session index {}", command.toFullString());
951 logger.debug("Cannot handle command {}, channel {} in group {} not a command channel",
952 command.toFullString(), channelUID.getAsString(), group);
957 private void updateImage(byte[] imageContent) {
958 if (imageContent.length > 0) {
959 imageCache = Optional.of(imageContent);
960 String contentType = HttpUtil.guessContentTypeFromData(imageContent);
961 updateChannel(CHANNEL_GROUP_VEHICLE_IMAGE, IMAGE_FORMAT, new RawType(imageContent, contentType),
964 synchronized (imageProperties) {
965 imageProperties.failed();