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.*;
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.Unit;
28 import javax.measure.quantity.Length;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
33 import org.openhab.binding.mybmw.internal.dto.charge.ChargeProfile;
34 import org.openhab.binding.mybmw.internal.dto.charge.ChargeSession;
35 import org.openhab.binding.mybmw.internal.dto.charge.ChargeStatisticsContainer;
36 import org.openhab.binding.mybmw.internal.dto.charge.ChargingSettings;
37 import org.openhab.binding.mybmw.internal.dto.properties.CBS;
38 import org.openhab.binding.mybmw.internal.dto.properties.DoorsWindows;
39 import org.openhab.binding.mybmw.internal.dto.properties.Location;
40 import org.openhab.binding.mybmw.internal.dto.properties.Tires;
41 import org.openhab.binding.mybmw.internal.dto.status.CCMMessage;
42 import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
43 import org.openhab.binding.mybmw.internal.utils.ChargeProfileUtils;
44 import org.openhab.binding.mybmw.internal.utils.ChargeProfileUtils.TimedChannel;
45 import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper;
46 import org.openhab.binding.mybmw.internal.utils.ChargeProfileWrapper.ProfileKey;
47 import org.openhab.binding.mybmw.internal.utils.Constants;
48 import org.openhab.binding.mybmw.internal.utils.Converter;
49 import org.openhab.binding.mybmw.internal.utils.RemoteServiceUtils;
50 import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
51 import org.openhab.core.i18n.LocationProvider;
52 import org.openhab.core.library.types.DateTimeType;
53 import org.openhab.core.library.types.DecimalType;
54 import org.openhab.core.library.types.OnOffType;
55 import org.openhab.core.library.types.PointType;
56 import org.openhab.core.library.types.QuantityType;
57 import org.openhab.core.library.types.StringType;
58 import org.openhab.core.library.unit.ImperialUnits;
59 import org.openhab.core.library.unit.SIUnits;
60 import org.openhab.core.library.unit.Units;
61 import org.openhab.core.thing.ChannelUID;
62 import org.openhab.core.thing.Thing;
63 import org.openhab.core.thing.binding.BaseThingHandler;
64 import org.openhab.core.types.CommandOption;
65 import org.openhab.core.types.State;
66 import org.openhab.core.types.UnDefType;
67 import org.slf4j.Logger;
68 import org.slf4j.LoggerFactory;
71 * The {@link VehicleChannelHandler} handles Channel updates
73 * @author Bernd Weymann - Initial contribution
74 * @author Norbert Truchsess - edit & send of charge profile
77 public abstract class VehicleChannelHandler extends BaseThingHandler {
78 protected final Logger logger = LoggerFactory.getLogger(VehicleChannelHandler.class);
79 protected boolean hasFuel = false;
80 protected boolean isElectric = false;
81 protected boolean isHybrid = false;
84 protected List<CBS> serviceList = new ArrayList<CBS>();
85 protected String selectedService = Constants.UNDEF;
86 protected List<CCMMessage> checkControlList = new ArrayList<CCMMessage>();
87 protected String selectedCC = Constants.UNDEF;
88 protected List<ChargeSession> sessionList = new ArrayList<ChargeSession>();
89 protected String selectedSession = Constants.UNDEF;
91 protected MyBMWCommandOptionProvider commandOptionProvider;
92 private LocationProvider locationProvider;
95 protected Optional<String> vehicleStatusCache = Optional.empty();
96 protected Optional<byte[]> imageCache = Optional.empty();
98 public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String type) {
100 commandOptionProvider = cop;
101 locationProvider = lp;
102 if (lp.getLocation() == null) {
103 logger.debug("Home location not available");
106 hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
107 || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.MILD_HYBRID.toString());
108 isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
109 || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
110 isHybrid = hasFuel && isElectric;
112 setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
115 private void setOptions(final String group, final String id, List<CommandOption> options) {
116 commandOptionProvider.setCommandOptions(new ChannelUID(thing.getUID(), group, id), options);
119 protected void updateChannel(final String group, final String id, final State state) {
120 updateState(new ChannelUID(thing.getUID(), group, id), state);
123 protected void updateChargeStatistics(ChargeStatisticsContainer csc) {
124 updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, TITLE, StringType.valueOf(csc.description));
125 updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, ENERGY,
126 QuantityType.valueOf(csc.statistics.totalEnergyCharged, Units.KILOWATT_HOUR));
127 updateChannel(CHANNEL_GROUP_CHARGE_STATISTICS, SESSIONS,
128 DecimalType.valueOf(Integer.toString(csc.statistics.numberOfChargingSessions)));
131 protected void updateVehicle(Vehicle v) {
132 updateVehicleStatus(v);
134 updateDoors(v.properties.doorsAndWindows);
135 updateWindows(v.properties.doorsAndWindows);
136 updatePosition(v.properties.vehicleLocation);
137 updateServices(v.properties.serviceRequired);
138 updateCheckControls(v.status.checkControlMessages);
139 updateTires(v.properties.tires);
142 private void updateTires(@Nullable Tires tires) {
144 updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT, UnDefType.UNDEF);
145 updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET, UnDefType.UNDEF);
146 updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT, UnDefType.UNDEF);
147 updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET, UnDefType.UNDEF);
148 updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT, UnDefType.UNDEF);
149 updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET, UnDefType.UNDEF);
150 updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT, UnDefType.UNDEF);
151 updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET, UnDefType.UNDEF);
153 updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_CURRENT,
154 QuantityType.valueOf(tires.frontLeft.status.currentPressure / 100, Units.BAR));
155 updateChannel(CHANNEL_GROUP_TIRES, FRONT_LEFT_TARGET,
156 QuantityType.valueOf(tires.frontLeft.status.targetPressure / 100, Units.BAR));
157 updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_CURRENT,
158 QuantityType.valueOf(tires.frontRight.status.currentPressure / 100, Units.BAR));
159 updateChannel(CHANNEL_GROUP_TIRES, FRONT_RIGHT_TARGET,
160 QuantityType.valueOf(tires.frontRight.status.targetPressure / 100, Units.BAR));
161 updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_CURRENT,
162 QuantityType.valueOf(tires.rearLeft.status.currentPressure / 100, Units.BAR));
163 updateChannel(CHANNEL_GROUP_TIRES, REAR_LEFT_TARGET,
164 QuantityType.valueOf(tires.rearLeft.status.targetPressure / 100, Units.BAR));
165 updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_CURRENT,
166 QuantityType.valueOf(tires.rearRight.status.currentPressure / 100, Units.BAR));
167 updateChannel(CHANNEL_GROUP_TIRES, REAR_RIGHT_TARGET,
168 QuantityType.valueOf(tires.rearRight.status.targetPressure / 100, Units.BAR));
172 protected void updateVehicleStatus(Vehicle v) {
173 updateChannel(CHANNEL_GROUP_STATUS, LOCK, Converter.getLockState(v.properties.areDoorsLocked));
174 updateChannel(CHANNEL_GROUP_STATUS, SERVICE_DATE,
175 VehicleStatusUtils.getNextServiceDate(v.properties.serviceRequired));
176 updateChannel(CHANNEL_GROUP_STATUS, SERVICE_MILEAGE,
177 VehicleStatusUtils.getNextServiceMileage(v.properties.serviceRequired));
178 updateChannel(CHANNEL_GROUP_STATUS, CHECK_CONTROL,
179 StringType.valueOf(v.status.checkControlMessagesGeneralState));
180 updateChannel(CHANNEL_GROUP_STATUS, MOTION, OnOffType.from(v.properties.inMotion));
181 updateChannel(CHANNEL_GROUP_STATUS, LAST_UPDATE,
182 DateTimeType.valueOf(Converter.zonedToLocalDateTime(v.properties.lastUpdatedAt)));
183 updateChannel(CHANNEL_GROUP_STATUS, DOORS, Converter.getClosedState(v.properties.areDoorsClosed));
184 updateChannel(CHANNEL_GROUP_STATUS, WINDOWS, Converter.getClosedState(v.properties.areWindowsClosed));
187 updateChannel(CHANNEL_GROUP_STATUS, PLUG_CONNECTION,
188 Converter.getConnectionState(v.properties.chargingState.isChargerConnected));
189 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_STATUS,
190 StringType.valueOf(Converter.toTitleCase(VehicleStatusUtils.getChargStatus(v))));
191 updateChannel(CHANNEL_GROUP_STATUS, CHARGE_INFO,
192 StringType.valueOf(Converter.getLocalTime(VehicleStatusUtils.getChargeInfo(v))));
196 protected void updateRange(Vehicle v) {
197 // get the right unit
198 Unit<Length> lengthUnit = VehicleStatusUtils.getLengthUnit(v.status.fuelIndicators);
199 if (lengthUnit == null) {
203 int rangeElectric = VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, v);
204 QuantityType<Length> qtElectricRange = QuantityType.valueOf(rangeElectric, lengthUnit);
205 QuantityType<Length> qtElectricRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeElectric),
207 updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC, qtElectricRange);
208 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC, qtElectricRadius);
211 int rangeFuel = VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, v);
212 QuantityType<Length> qtFuelRange = QuantityType.valueOf(rangeFuel, lengthUnit);
213 QuantityType<Length> qtFuelRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeFuel), lengthUnit);
214 updateChannel(CHANNEL_GROUP_RANGE, RANGE_FUEL, qtFuelRange);
215 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_FUEL, qtFuelRadius);
218 int rangeCombined = VehicleStatusUtils.getRange(Constants.PHEV, v);
219 QuantityType<Length> qtHybridRange = QuantityType.valueOf(rangeCombined, lengthUnit);
220 QuantityType<Length> qtHybridRadius = QuantityType.valueOf(Converter.guessRangeRadius(rangeCombined),
222 updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID, qtHybridRange);
223 updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID, qtHybridRadius);
225 if (v.status.currentMileage.mileage == Constants.INT_UNDEF) {
226 updateChannel(CHANNEL_GROUP_RANGE, MILEAGE, UnDefType.UNDEF);
228 updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
229 QuantityType.valueOf(v.status.currentMileage.mileage, lengthUnit));
232 updateChannel(CHANNEL_GROUP_RANGE, SOC,
233 QuantityType.valueOf(v.properties.chargingState.chargePercentage, Units.PERCENT));
236 updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
237 QuantityType.valueOf(v.properties.fuelLevel.value, Units.LITRE));
241 protected void updateCheckControls(List<CCMMessage> ccl) {
243 // No Check Control available - show not active
244 CCMMessage ccm = new CCMMessage();
245 ccm.title = Constants.NO_ENTRIES;
246 ccm.longDescription = Constants.NO_ENTRIES;
247 ccm.state = Constants.NO_ENTRIES;
251 // add all elements to options
252 checkControlList = ccl;
253 List<CommandOption> ccmDescriptionOptions = new ArrayList<>();
254 boolean isSelectedElementIn = false;
256 for (CCMMessage ccEntry : checkControlList) {
257 ccmDescriptionOptions.add(new CommandOption(Integer.toString(index), ccEntry.title));
258 if (selectedCC.equals(ccEntry.title)) {
259 isSelectedElementIn = true;
263 setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
265 // if current selected item isn't anymore in the list select first entry
266 if (!isSelectedElementIn) {
267 selectCheckControl(0);
271 protected void selectCheckControl(int index) {
272 if (index >= 0 && index < checkControlList.size()) {
273 CCMMessage ccEntry = checkControlList.get(index);
274 selectedCC = ccEntry.title;
275 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, NAME, StringType.valueOf(ccEntry.title));
276 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, DETAILS, StringType.valueOf(ccEntry.longDescription));
277 updateChannel(CHANNEL_GROUP_CHECK_CONTROL, SEVERITY, StringType.valueOf(ccEntry.state));
281 protected void updateServices(List<CBS> sl) {
282 // if list is empty add "undefined" element
284 CBS cbsm = new CBS();
285 cbsm.type = Constants.NO_ENTRIES;
289 // add all elements to options
291 List<CommandOption> serviceNameOptions = new ArrayList<>();
292 boolean isSelectedElementIn = false;
294 for (CBS serviceEntry : serviceList) {
295 // create StateOption with "value = list index" and "label = human readable string"
296 serviceNameOptions.add(new CommandOption(Integer.toString(index), serviceEntry.type));
297 if (selectedService.equals(serviceEntry.type)) {
298 isSelectedElementIn = true;
302 setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
304 // if current selected item isn't anymore in the list select first entry
305 if (!isSelectedElementIn) {
310 protected void selectService(int index) {
311 if (index >= 0 && index < serviceList.size()) {
312 CBS serviceEntry = serviceList.get(index);
313 selectedService = serviceEntry.type;
314 updateChannel(CHANNEL_GROUP_SERVICE, NAME, StringType.valueOf(Converter.toTitleCase(serviceEntry.type)));
315 if (serviceEntry.dateTime != null) {
316 updateChannel(CHANNEL_GROUP_SERVICE, DATE,
317 DateTimeType.valueOf(Converter.zonedToLocalDateTime(serviceEntry.dateTime)));
319 updateChannel(CHANNEL_GROUP_SERVICE, DATE, UnDefType.UNDEF);
321 if (serviceEntry.distance != null) {
322 if (Constants.KILOMETERS_JSON.equals(serviceEntry.distance.units)) {
323 updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
324 QuantityType.valueOf(serviceEntry.distance.value, Constants.KILOMETRE_UNIT));
326 updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
327 QuantityType.valueOf(serviceEntry.distance.value, ImperialUnits.MILE));
330 updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
331 QuantityType.valueOf(Constants.INT_UNDEF, Constants.KILOMETRE_UNIT));
336 protected void updateSessions(List<ChargeSession> sl) {
337 // if list is empty add "undefined" element
339 ChargeSession cs = new ChargeSession();
340 cs.title = Constants.NO_ENTRIES;
344 // add all elements to options
346 List<CommandOption> sessionNameOptions = new ArrayList<>();
347 boolean isSelectedElementIn = false;
349 for (ChargeSession session : sessionList) {
350 // create StateOption with "value = list index" and "label = human readable string"
351 sessionNameOptions.add(new CommandOption(Integer.toString(index), session.title));
352 if (selectedService.equals(session.title)) {
353 isSelectedElementIn = true;
357 setOptions(CHANNEL_GROUP_CHARGE_SESSION, TITLE, sessionNameOptions);
359 // if current selected item isn't anymore in the list select first entry
360 if (!isSelectedElementIn) {
365 protected void selectSession(int index) {
366 if (index >= 0 && index < sessionList.size()) {
367 ChargeSession sessionEntry = sessionList.get(index);
368 selectedService = sessionEntry.title;
369 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, TITLE, StringType.valueOf(sessionEntry.title));
370 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, SUBTITLE, StringType.valueOf(sessionEntry.subtitle));
371 if (sessionEntry.energyCharged != null) {
372 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(sessionEntry.energyCharged));
374 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(Constants.UNDEF));
376 if (sessionEntry.issues != null) {
377 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(sessionEntry.issues));
379 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(Constants.HYPHEN));
381 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, STATUS, StringType.valueOf(sessionEntry.sessionStatus));
385 protected void updateChargeProfile(ChargeProfile cp) {
386 ChargeProfileWrapper cpw = new ChargeProfileWrapper(cp);
388 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_PREFERENCE, StringType.valueOf(cpw.getPreference()));
389 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_MODE, StringType.valueOf(cpw.getMode()));
390 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CONTROL, StringType.valueOf(cpw.getControlType()));
391 ChargingSettings cs = cpw.getChargeSettings();
393 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_TARGET,
394 DecimalType.valueOf(Integer.toString(cs.targetSoc)));
395 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_LIMIT,
396 OnOffType.from(cs.isAcCurrentLimitActive));
398 final Boolean climate = cpw.isEnabled(ProfileKey.CLIMATE);
399 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, CHARGE_PROFILE_CLIMATE,
400 climate == null ? UnDefType.UNDEF : OnOffType.from(climate));
401 updateTimedState(cpw, ProfileKey.WINDOWSTART);
402 updateTimedState(cpw, ProfileKey.WINDOWEND);
403 updateTimedState(cpw, ProfileKey.TIMER1);
404 updateTimedState(cpw, ProfileKey.TIMER2);
405 updateTimedState(cpw, ProfileKey.TIMER3);
406 updateTimedState(cpw, ProfileKey.TIMER4);
409 protected void updateTimedState(ChargeProfileWrapper profile, ProfileKey key) {
410 final TimedChannel timed = ChargeProfileUtils.getTimedChannel(key);
412 final LocalTime time = profile.getTime(key);
413 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.time,
414 time.equals(Constants.NULL_LOCAL_TIME) ? UnDefType.UNDEF
415 : new DateTimeType(ZonedDateTime.of(Constants.EPOCH_DAY, time, ZoneId.systemDefault())));
416 if (timed.timer != null) {
417 final Boolean enabled = profile.isEnabled(key);
418 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE, timed.timer + CHARGE_ENABLED,
419 enabled == null ? UnDefType.UNDEF : OnOffType.from(enabled));
421 final Set<DayOfWeek> days = profile.getDays(key);
422 EnumSet.allOf(DayOfWeek.class).forEach(day -> {
423 updateChannel(CHANNEL_GROUP_CHARGE_PROFILE,
424 timed.timer + ChargeProfileUtils.getDaysChannel(day),
425 days == null ? UnDefType.UNDEF : OnOffType.from(days.contains(day)));
432 protected void updateDoors(DoorsWindows dw) {
433 updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_FRONT,
434 StringType.valueOf(Converter.toTitleCase(dw.doors.driverFront)));
435 updateChannel(CHANNEL_GROUP_DOORS, DOOR_DRIVER_REAR,
436 StringType.valueOf(Converter.toTitleCase(dw.doors.driverRear)));
437 updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_FRONT,
438 StringType.valueOf(Converter.toTitleCase(dw.doors.passengerFront)));
439 updateChannel(CHANNEL_GROUP_DOORS, DOOR_PASSENGER_REAR,
440 StringType.valueOf(Converter.toTitleCase(dw.doors.passengerRear)));
441 updateChannel(CHANNEL_GROUP_DOORS, TRUNK, StringType.valueOf(Converter.toTitleCase(dw.trunk)));
442 updateChannel(CHANNEL_GROUP_DOORS, HOOD, StringType.valueOf(Converter.toTitleCase(dw.hood)));
445 protected void updateWindows(DoorsWindows dw) {
446 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_FRONT,
447 StringType.valueOf(Converter.toTitleCase(dw.windows.driverFront)));
448 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_DRIVER_REAR,
449 StringType.valueOf(Converter.toTitleCase(dw.windows.driverRear)));
450 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_FRONT,
451 StringType.valueOf(Converter.toTitleCase(dw.windows.passengerFront)));
452 updateChannel(CHANNEL_GROUP_DOORS, WINDOW_DOOR_PASSENGER_REAR,
453 StringType.valueOf(Converter.toTitleCase(dw.windows.passengerRear)));
454 updateChannel(CHANNEL_GROUP_DOORS, SUNROOF, StringType.valueOf(Converter.toTitleCase(dw.moonroof)));
457 protected void updatePosition(Location pos) {
458 if (pos.coordinates.latitude < 0) {
459 updateChannel(CHANNEL_GROUP_LOCATION, GPS, UnDefType.UNDEF);
460 updateChannel(CHANNEL_GROUP_LOCATION, HEADING, UnDefType.UNDEF);
461 updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, UnDefType.UNDEF);
462 updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
464 PointType vehicleLocation = PointType.valueOf(
465 Double.toString(pos.coordinates.latitude) + "," + Double.toString(pos.coordinates.longitude));
466 updateChannel(CHANNEL_GROUP_LOCATION, GPS, vehicleLocation);
467 updateChannel(CHANNEL_GROUP_LOCATION, HEADING, QuantityType.valueOf(pos.heading, Units.DEGREE_ANGLE));
468 updateChannel(CHANNEL_GROUP_LOCATION, ADDRESS, StringType.valueOf(pos.address.formatted));
469 PointType homeLocation = locationProvider.getLocation();
470 if (homeLocation != null) {
471 updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE,
472 QuantityType.valueOf(vehicleLocation.distanceFrom(homeLocation).intValue(), SIUnits.METRE));
474 updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);