2 * Copyright (c) 2010-2021 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.bmwconnecteddrive.internal.handler;
15 import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.*;
17 import java.time.DayOfWeek;
18 import java.time.LocalTime;
19 import java.time.ZoneId;
20 import java.time.ZonedDateTime;
21 import java.util.ArrayList;
22 import java.util.EnumSet;
23 import java.util.List;
24 import java.util.Optional;
27 import javax.measure.quantity.Length;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.VehicleType;
31 import org.openhab.binding.bmwconnecteddrive.internal.dto.Destination;
32 import org.openhab.binding.bmwconnecteddrive.internal.dto.statistics.AllTrips;
33 import org.openhab.binding.bmwconnecteddrive.internal.dto.statistics.LastTrip;
34 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CBSMessage;
35 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CCMMessage;
36 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Doors;
37 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Position;
38 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatus;
39 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Windows;
40 import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileUtils;
41 import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileUtils.TimedChannel;
42 import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper;
43 import org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper.ProfileKey;
44 import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
45 import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
46 import org.openhab.binding.bmwconnecteddrive.internal.utils.RemoteServiceUtils;
47 import org.openhab.binding.bmwconnecteddrive.internal.utils.VehicleStatusUtils;
48 import org.openhab.core.library.types.DateTimeType;
49 import org.openhab.core.library.types.OnOffType;
50 import org.openhab.core.library.types.PointType;
51 import org.openhab.core.library.types.QuantityType;
52 import org.openhab.core.library.types.StringType;
53 import org.openhab.core.library.unit.ImperialUnits;
54 import org.openhab.core.library.unit.Units;
55 import org.openhab.core.thing.ChannelUID;
56 import org.openhab.core.thing.Thing;
57 import org.openhab.core.thing.binding.BaseThingHandler;
58 import org.openhab.core.types.State;
59 import org.openhab.core.types.StateOption;
60 import org.openhab.core.types.UnDefType;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
64 import com.google.gson.JsonSyntaxException;
67 * The {@link VehicleChannelHandler} is responsible for handling commands, which are
68 * sent to one of the channels.
70 * @author Bernd Weymann - Initial contribution
71 * @author Norbert Truchsess - edit & send of charge profile
74 public abstract class VehicleChannelHandler extends BaseThingHandler {
75 protected final Logger logger = LoggerFactory.getLogger(VehicleChannelHandler.class);
76 protected boolean imperial = false;
77 protected boolean hasFuel = false;
78 protected boolean isElectric = false;
79 protected boolean isHybrid = false;
82 protected List<CBSMessage> serviceList = new ArrayList<CBSMessage>();
83 protected String selectedService = Constants.UNDEF;
84 protected List<CCMMessage> checkControlList = new ArrayList<CCMMessage>();
85 protected String selectedCC = Constants.UNDEF;
86 protected List<Destination> destinationList = new ArrayList<Destination>();
87 protected String selectedDestination = Constants.UNDEF;
89 protected BMWConnectedDriveOptionProvider optionProvider;
92 protected Optional<String> vehicleStatusCache = Optional.empty();
93 protected Optional<String> lastTripCache = Optional.empty();
94 protected Optional<String> allTripsCache = Optional.empty();
95 protected Optional<String> chargeProfileCache = Optional.empty();
96 protected Optional<String> rangeMapCache = Optional.empty();
97 protected Optional<String> destinationCache = Optional.empty();
98 protected Optional<byte[]> imageCache = Optional.empty();
100 public VehicleChannelHandler(Thing thing, BMWConnectedDriveOptionProvider op, String type, boolean imperial) {
104 this.imperial = imperial;
105 hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
106 || type.equals(VehicleType.ELECTRIC_REX.toString());
107 isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
108 || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
109 isHybrid = hasFuel && isElectric;
111 setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
114 private void setOptions(final String group, final String id, List<StateOption> options) {
115 optionProvider.setStateOptions(new ChannelUID(thing.getUID(), group, id), options);
118 protected void updateChannel(final String group, final String id, final State state) {
119 updateState(new ChannelUID(thing.getUID(), group, id), state);
122 protected void updateCheckControls(List<CCMMessage> ccl) {
124 // No Check Control available - show not active
125 CCMMessage ccm = new CCMMessage();
126 ccm.ccmDescriptionLong = Constants.NO_ENTRIES;
127 ccm.ccmDescriptionShort = Constants.NO_ENTRIES;
133 // add all elements to options
134 checkControlList = ccl;
135 List<StateOption> ccmDescriptionOptions = new ArrayList<>();
136 List<StateOption> ccmDetailsOptions = new ArrayList<>();
137 List<StateOption> ccmMileageOptions = new ArrayList<>();
138 boolean isSelectedElementIn = false;
140 for (CCMMessage ccEntry : checkControlList) {
141 ccmDescriptionOptions.add(new StateOption(Integer.toString(index), ccEntry.ccmDescriptionShort));
142 ccmDetailsOptions.add(new StateOption(Integer.toString(index), ccEntry.ccmDescriptionLong));
143 ccmMileageOptions.add(new StateOption(Integer.toString(index), Integer.toString(ccEntry.ccmMileage)));
144 if (selectedCC.equals(ccEntry.ccmDescriptionShort)) {
145 isSelectedElementIn = true;
149 setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
150 setOptions(CHANNEL_GROUP_CHECK_CONTROL, DETAILS, ccmDetailsOptions);
151 setOptions(CHANNEL_GROUP_CHECK_CONTROL, MILEAGE, ccmMileageOptions);
153 // if current selected item isn't anymore in the list select first entry
154 if (!isSelectedElementIn) {
155 selectCheckControl(0);
159 protected void selectCheckControl(int index) {
160 if (index >= 0 && index < checkControlList.size()) {
161 CCMMessage ccEntry = checkControlList.get(index);
162 selectedCC = ccEntry.ccmDescriptionShort;
163 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, StringType.valueOf(ccEntry.ccmDescriptionShort));
164 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS, StringType.valueOf(ccEntry.ccmDescriptionLong));
165 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, MILEAGE, QuantityType.valueOf(
166 Converter.round(ccEntry.ccmMileage), imperial ? ImperialUnits.MILE : Constants.KILOMETRE_UNIT));
170 protected void updateServices(List<CBSMessage> sl) {
171 // if list is empty add "undefined" element
173 CBSMessage cbsm = new CBSMessage();
174 cbsm.cbsType = Constants.NO_ENTRIES;
175 cbsm.cbsDescription = Constants.NO_ENTRIES;
179 // add all elements to options
181 List<StateOption> serviceNameOptions = new ArrayList<>();
182 List<StateOption> serviceDetailsOptions = new ArrayList<>();
183 List<StateOption> serviceDateOptions = new ArrayList<>();
184 List<StateOption> serviceMileageOptions = new ArrayList<>();
185 boolean isSelectedElementIn = false;
187 for (CBSMessage serviceEntry : serviceList) {
188 // create StateOption with "value = list index" and "label = human readable string"
189 serviceNameOptions.add(new StateOption(Integer.toString(index), serviceEntry.getType()));
190 serviceDetailsOptions.add(new StateOption(Integer.toString(index), serviceEntry.getDescription()));
191 serviceDateOptions.add(new StateOption(Integer.toString(index), serviceEntry.getDueDate()));
192 serviceMileageOptions
193 .add(new StateOption(Integer.toString(index), Integer.toString(serviceEntry.cbsRemainingMileage)));
194 if (selectedService.equals(serviceEntry.getType())) {
195 isSelectedElementIn = true;
199 setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
200 setOptions(CHANNEL_GROUP_SERVICE, DETAILS, serviceDetailsOptions);
201 setOptions(CHANNEL_GROUP_SERVICE, DATE, serviceDateOptions);
202 setOptions(CHANNEL_GROUP_SERVICE, MILEAGE, serviceMileageOptions);
204 // if current selected item isn't anymore in the list select first entry
205 if (!isSelectedElementIn) {
210 protected void selectService(int index) {
211 if (index >= 0 && index < serviceList.size()) {
212 CBSMessage serviceEntry = serviceList.get(index);
213 selectedService = serviceEntry.cbsType;
214 updateChannel(CHANNEL_GROUP_SERVICE, NAME,
215 StringType.valueOf(Converter.toTitleCase(serviceEntry.getType())));
216 updateChannel(CHANNEL_GROUP_SERVICE, DETAILS,
217 StringType.valueOf(Converter.toTitleCase(serviceEntry.getDescription())));
218 updateChannel(CHANNEL_GROUP_SERVICE, DATE,
219 DateTimeType.valueOf(Converter.getLocalDateTime(serviceEntry.getDueDate())));
220 updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
221 QuantityType.valueOf(Converter.round(serviceEntry.cbsRemainingMileage),
222 imperial ? ImperialUnits.MILE : Constants.KILOMETRE_UNIT));
226 protected void updateDestinations(List<Destination> dl) {
227 // if list is empty add "undefined" element
229 Destination dest = new Destination();
230 dest.city = Constants.NO_ENTRIES;
236 // add all elements to options
237 destinationList = dl;
238 List<StateOption> destinationNameOptions = new ArrayList<>();
239 List<StateOption> destinationGPSOptions = new ArrayList<>();
240 boolean isSelectedElementIn = false;
242 for (Destination destination : destinationList) {
243 destinationNameOptions.add(new StateOption(Integer.toString(index), destination.getAddress()));
244 destinationGPSOptions.add(new StateOption(Integer.toString(index), destination.getCoordinates()));
245 if (selectedDestination.equals(destination.getAddress())) {
246 isSelectedElementIn = true;
250 setOptions(CHANNEL_GROUP_DESTINATION, NAME, destinationNameOptions);
251 setOptions(CHANNEL_GROUP_DESTINATION, GPS, destinationGPSOptions);
253 // if current selected item isn't anymore in the list select first entry
254 if (!isSelectedElementIn) {
255 selectDestination(0);
259 protected void selectDestination(int index) {
260 if (index >= 0 && index < destinationList.size()) {
261 Destination destinationEntry = destinationList.get(index);
262 // update selected Item
263 selectedDestination = destinationEntry.getAddress();
264 // update coordinates according to new set location
265 updateChannel(CHANNEL_GROUP_DESTINATION, NAME, StringType.valueOf(destinationEntry.getAddress()));
266 updateChannel(CHANNEL_GROUP_DESTINATION, GPS, PointType.valueOf(destinationEntry.getCoordinates()));
270 protected void updateAllTrips(AllTrips allTrips) {
271 QuantityType<Length> qtTotalElectric = QuantityType
272 .valueOf(Converter.round(allTrips.totalElectricDistance.userTotal), Constants.KILOMETRE_UNIT);
273 QuantityType<Length> qtLongestElectricRange = QuantityType
274 .valueOf(Converter.round(allTrips.chargecycleRange.userHigh), Constants.KILOMETRE_UNIT);
275 QuantityType<Length> qtDistanceSinceCharge = QuantityType
276 .valueOf(Converter.round(allTrips.chargecycleRange.userCurrentChargeCycle), Constants.KILOMETRE_UNIT);
278 updateChannel(CHANNEL_GROUP_LIFETIME, TOTAL_DRIVEN_DISTANCE,
279 imperial ? Converter.getMiles(qtTotalElectric) : qtTotalElectric);
280 updateChannel(CHANNEL_GROUP_LIFETIME, SINGLE_LONGEST_DISTANCE,
281 imperial ? Converter.getMiles(qtLongestElectricRange) : qtLongestElectricRange);
282 updateChannel(CHANNEL_GROUP_LAST_TRIP, DISTANCE_SINCE_CHARGING,
283 imperial ? Converter.getMiles(qtDistanceSinceCharge) : qtDistanceSinceCharge);
285 // Conversion from kwh/100km to kwh/10mi has to be done manually
286 double avgConsumotion = imperial ? allTrips.avgElectricConsumption.userAverage * Converter.MILES_TO_KM_RATIO
287 : allTrips.avgElectricConsumption.userAverage;
288 double avgCombinedConsumption = imperial
289 ? allTrips.avgCombinedConsumption.userAverage * Converter.MILES_TO_KM_RATIO
290 : allTrips.avgCombinedConsumption.userAverage;
291 double avgRecuperation = imperial ? allTrips.avgRecuperation.userAverage * Converter.MILES_TO_KM_RATIO
292 : allTrips.avgRecuperation.userAverage;
294 updateChannel(CHANNEL_GROUP_LIFETIME, AVG_CONSUMPTION,
295 QuantityType.valueOf(Converter.round(avgConsumotion), Units.KILOWATT_HOUR));
296 updateChannel(CHANNEL_GROUP_LIFETIME, AVG_COMBINED_CONSUMPTION,
297 QuantityType.valueOf(Converter.round(avgCombinedConsumption), Units.LITRE));
298 updateChannel(CHANNEL_GROUP_LIFETIME, AVG_RECUPERATION,
299 QuantityType.valueOf(Converter.round(avgRecuperation), Units.KILOWATT_HOUR));
302 protected void updateLastTrip(LastTrip trip) {
303 // Whyever the Last Trip DateTime is delivered without offest - so LocalTime
304 updateChannel(CHANNEL_GROUP_LAST_TRIP, DATE,
305 DateTimeType.valueOf(Converter.getLocalDateTimeWithoutOffest(trip.date)));
306 updateChannel(CHANNEL_GROUP_LAST_TRIP, DURATION, QuantityType.valueOf(trip.duration, Units.MINUTE));
308 QuantityType<Length> qtTotalDistance = QuantityType.valueOf(Converter.round(trip.totalDistance),
309 Constants.KILOMETRE_UNIT);
310 updateChannel(CHANNEL_GROUP_LAST_TRIP, DISTANCE,
311 imperial ? Converter.getMiles(qtTotalDistance) : qtTotalDistance);
313 // Conversion from kwh/100km to kwh/10mi has to be done manually
314 double avgConsumtption = imperial ? trip.avgElectricConsumption * Converter.MILES_TO_KM_RATIO
315 : trip.avgElectricConsumption;
316 double avgCombinedConsumption = imperial ? trip.avgCombinedConsumption * Converter.MILES_TO_KM_RATIO
317 : trip.avgCombinedConsumption;
318 double avgRecuperation = imperial ? trip.avgRecuperation * Converter.MILES_TO_KM_RATIO : trip.avgRecuperation;
320 updateChannel(CHANNEL_GROUP_LAST_TRIP, AVG_CONSUMPTION,
321 QuantityType.valueOf(Converter.round(avgConsumtption), Units.KILOWATT_HOUR));
322 updateChannel(CHANNEL_GROUP_LAST_TRIP, AVG_COMBINED_CONSUMPTION,
323 QuantityType.valueOf(Converter.round(avgCombinedConsumption), Units.LITRE));
324 updateChannel(CHANNEL_GROUP_LAST_TRIP, AVG_RECUPERATION,
325 QuantityType.valueOf(Converter.round(avgRecuperation), Units.KILOWATT_HOUR));
328 protected void updateChargeProfileFromContent(String content) {
329 ChargeProfileWrapper.fromJson(content).ifPresent(this::updateChargeProfile);
332 protected void updateChargeProfile(ChargeProfileWrapper wrapper) {
333 updateChannel(CHANNEL_GROUP_CHARGE, CHARGE_PROFILE_PREFERENCE,
334 StringType.valueOf(Converter.toTitleCase(wrapper.getPreference())));
335 updateChannel(CHANNEL_GROUP_CHARGE, CHARGE_PROFILE_MODE,
336 StringType.valueOf(Converter.toTitleCase(wrapper.getMode())));
337 final Boolean climate = wrapper.isEnabled(ProfileKey.CLIMATE);
338 updateChannel(CHANNEL_GROUP_CHARGE, CHARGE_PROFILE_CLIMATE,
339 climate == null ? UnDefType.UNDEF : OnOffType.from(climate));
340 updateTimedState(wrapper, ProfileKey.WINDOWSTART);
341 updateTimedState(wrapper, ProfileKey.WINDOWEND);
342 updateTimedState(wrapper, ProfileKey.TIMER1);
343 updateTimedState(wrapper, ProfileKey.TIMER2);
344 updateTimedState(wrapper, ProfileKey.TIMER3);
345 updateTimedState(wrapper, ProfileKey.OVERRIDE);
348 protected void updateTimedState(ChargeProfileWrapper profile, ProfileKey key) {
349 final TimedChannel timed = ChargeProfileUtils.getTimedChannel(key);
351 final LocalTime time = profile.getTime(key);
352 updateChannel(CHANNEL_GROUP_CHARGE, timed.time, time == null ? UnDefType.UNDEF
353 : new DateTimeType(ZonedDateTime.of(Constants.EPOCH_DAY, time, ZoneId.systemDefault())));
354 if (timed.timer != null) {
355 final Boolean enabled = profile.isEnabled(key);
356 updateChannel(CHANNEL_GROUP_CHARGE, timed.timer + CHARGE_ENABLED,
357 enabled == null ? UnDefType.UNDEF : OnOffType.from(enabled));
359 final Set<DayOfWeek> days = profile.getDays(key);
360 updateChannel(CHANNEL_GROUP_CHARGE, timed.timer + CHARGE_DAYS,
361 days == null ? UnDefType.UNDEF : StringType.valueOf(ChargeProfileUtils.formatDays(days)));
362 EnumSet.allOf(DayOfWeek.class).forEach(day -> {
363 updateChannel(CHANNEL_GROUP_CHARGE, timed.timer + ChargeProfileUtils.getDaysChannel(day),
364 days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)));
371 protected void updateDoors(Doors doorState) {
372 updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
373 StringType.valueOf(Converter.toTitleCase(doorState.doorDriverFront)));
374 updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
375 StringType.valueOf(Converter.toTitleCase(doorState.doorDriverRear)));
376 updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
377 StringType.valueOf(Converter.toTitleCase(doorState.doorPassengerFront)));
378 updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
379 StringType.valueOf(Converter.toTitleCase(doorState.doorPassengerRear)));
380 updateChannel(CHANNEL_GROUP_DOORS, TRUNK, StringType.valueOf(Converter.toTitleCase(doorState.trunk)));
381 updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(doorState.hood)));
384 protected void updateWindows(Windows windowState) {
385 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
386 StringType.valueOf(Converter.toTitleCase(windowState.windowDriverFront)));
387 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
388 StringType.valueOf(Converter.toTitleCase(windowState.windowDriverRear)));
389 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
390 StringType.valueOf(Converter.toTitleCase(windowState.windowPassengerFront)));
391 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
392 StringType.valueOf(Converter.toTitleCase(windowState.windowPassengerRear)));
393 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_REAR,
394 StringType.valueOf(Converter.toTitleCase(windowState.rearWindow)));
395 updateChannel(CHANNEL_GROUP_DOORS, SUNROOF, StringType.valueOf(Converter.toTitleCase(windowState.sunroof)));
398 protected void updatePosition(Position pos) {
399 updateChannel(CHANNEL_GROUP_LOCATION, GPS, PointType.valueOf(pos.getCoordinates()));
400 updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
403 protected void updateVehicleStatus(VehicleStatus vStatus) {
405 updateChannel(CHANNEL_GROUP_STATUS, LOCK, StringType.valueOf(Converter.toTitleCase(vStatus.doorLockState)));
408 updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
409 DateTimeType.valueOf(Converter.getLocalDateTime(VehicleStatusUtils.getNextServiceDate(vStatus))));
411 updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
412 QuantityType.valueOf(Converter.round(VehicleStatusUtils.getNextServiceMileage(vStatus)),
413 imperial ? ImperialUnits.MILE : Constants.KILOMETRE_UNIT));
414 // CheckControl Active?
415 updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
416 StringType.valueOf(Converter.toTitleCase(VehicleStatusUtils.checkControlActive(vStatus))));
418 updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
419 DateTimeType.valueOf(Converter.getLocalDateTime(VehicleStatusUtils.getUpdateTime(vStatus))));
420 // last update reason
421 updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE_REASON,
422 StringType.valueOf(Converter.toTitleCase(vStatus.updateReason)));
424 Doors doorState = null;
426 doorState = Converter.getGson().fromJson(Converter.getGson().toJson(vStatus), Doors.class);
427 } catch (JsonSyntaxException jse) {
428 logger.debug("Doors parse exception {}", jse.getMessage());
430 if (doorState != null) {
431 updateChannel(CHANNEL_GROUP_STATUS, DOORS, StringType.valueOf(VehicleStatusUtils.checkClosed(doorState)));
432 updateDoors(doorState);
434 Windows windowState = null;
436 windowState = Converter.getGson().fromJson(Converter.getGson().toJson(vStatus), Windows.class);
437 } catch (JsonSyntaxException jse) {
438 logger.debug("Windows parse exception {}", jse.getMessage());
440 if (windowState != null) {
441 updateChannel(CHANNEL_GROUP_STATUS, WINDOWS,
442 StringType.valueOf(VehicleStatusUtils.checkClosed(windowState)));
443 updateWindows(windowState);
447 // based on unit of length decide if range shall be reported in km or miles
448 double totalRange = 0;
449 double maxTotalRange = 0;
451 totalRange += vStatus.remainingRangeElectric;
452 QuantityType<Length> qtElectricRange = QuantityType.valueOf(vStatus.remainingRangeElectric,
453 Constants.KILOMETRE_UNIT);
454 QuantityType<Length> qtElectricRadius = QuantityType
455 .valueOf(Converter.guessRangeRadius(vStatus.remainingRangeElectric), Constants.KILOMETRE_UNIT);
457 updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC,
458 imperial ? Converter.getMiles(qtElectricRange) : qtElectricRange);
459 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC,
460 imperial ? Converter.getMiles(qtElectricRadius) : qtElectricRadius);
462 maxTotalRange += vStatus.maxRangeElectric;
463 QuantityType<Length> qtMaxElectricRange = QuantityType.valueOf(vStatus.maxRangeElectric,
464 Constants.KILOMETRE_UNIT);
465 QuantityType<Length> qtMaxElectricRadius = QuantityType
466 .valueOf(Converter.guessRangeRadius(vStatus.maxRangeElectric), Constants.KILOMETRE_UNIT);
468 updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC_MAX,
469 imperial ? Converter.getMiles(qtMaxElectricRange) : qtMaxElectricRange);
470 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC_MAX,
471 imperial ? Converter.getMiles(qtMaxElectricRadius) : qtMaxElectricRadius);
474 totalRange += vStatus.remainingRangeFuel;
475 maxTotalRange += vStatus.remainingRangeFuel;
476 QuantityType<Length> qtFuelRange = QuantityType.valueOf(vStatus.remainingRangeFuel,
477 Constants.KILOMETRE_UNIT);
478 QuantityType<Length> qtFuelRadius = QuantityType
479 .valueOf(Converter.guessRangeRadius(vStatus.remainingRangeFuel), Constants.KILOMETRE_UNIT);
481 updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, imperial ? Converter.getMiles(qtFuelRange) : qtFuelRange);
482 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL,
483 imperial ? Converter.getMiles(qtFuelRadius) : qtFuelRadius);
486 QuantityType<Length> qtHybridRange = QuantityType.valueOf(totalRange, Constants.KILOMETRE_UNIT);
487 QuantityType<Length> qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(totalRange),
488 Constants.KILOMETRE_UNIT);
489 QuantityType<Length> qtMaxHybridRange = QuantityType.valueOf(maxTotalRange, Constants.KILOMETRE_UNIT);
490 QuantityType<Length> qtMaxHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(maxTotalRange),
491 Constants.KILOMETRE_UNIT);
492 updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID,
493 imperial ? Converter.getMiles(qtHybridRange) : qtHybridRange);
494 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID,
495 imperial ? Converter.getMiles(qtHybridRadius) : qtHybridRadius);
496 updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID_MAX,
497 imperial ? Converter.getMiles(qtMaxHybridRange) : qtMaxHybridRange);
498 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID_MAX,
499 imperial ? Converter.getMiles(qtMaxHybridRadius) : qtMaxHybridRadius);
502 updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
503 QuantityType.valueOf(vStatus.mileage, imperial ? ImperialUnits.MILE : Constants.KILOMETRE_UNIT));
505 updateChannel(CHANNEL_GROUP_RANGE, SOC, QuantityType.valueOf(vStatus.chargingLevelHv, Units.PERCENT));
508 updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
509 QuantityType.valueOf(vStatus.remainingFuel, Units.LITRE));
514 if (vStatus.connectionStatus != null) {
515 updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
516 StringType.valueOf(Converter.toTitleCase(vStatus.connectionStatus)));
518 updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION, UnDefType.NULL);
520 if (vStatus.chargingStatus != null) {
521 if (Constants.INVALID.equals(vStatus.chargingStatus)) {
522 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
523 StringType.valueOf(Converter.toTitleCase(vStatus.lastChargingEndReason)));
525 // State INVALID is somehow misleading. Instead show the Last Charging End Reason
526 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
527 StringType.valueOf(Converter.toTitleCase(vStatus.chargingStatus)));
530 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS, UnDefType.NULL);
532 if (vStatus.chargingTimeRemaining != null) {
534 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING,
535 QuantityType.valueOf(vStatus.chargingTimeRemaining, Units.MINUTE));
536 } catch (NumberFormatException nfe) {
537 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING, UnDefType.UNDEF);
540 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_REMAINING, UnDefType.NULL);