]> git.basschouten.com Git - openhab-addons.git/blob
396815bfb11b38d47990527ed894699598122c2b
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.mybmw.internal.handler;
14
15 import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
16
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;
25 import java.util.Set;
26
27 import javax.measure.Unit;
28 import javax.measure.quantity.Length;
29
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;
69
70 /**
71  * The {@link VehicleChannelHandler} handles Channel updates
72  *
73  * @author Bernd Weymann - Initial contribution
74  * @author Norbert Truchsess - edit and send of charge profile
75  */
76 @NonNullByDefault
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;
82
83     // List Interfaces
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;
90
91     protected MyBMWCommandOptionProvider commandOptionProvider;
92     private LocationProvider locationProvider;
93
94     // Data Caches
95     protected Optional<String> vehicleStatusCache = Optional.empty();
96     protected Optional<byte[]> imageCache = Optional.empty();
97
98     public VehicleChannelHandler(Thing thing, MyBMWCommandOptionProvider cop, LocationProvider lp, String type) {
99         super(thing);
100         commandOptionProvider = cop;
101         locationProvider = lp;
102         if (lp.getLocation() == null) {
103             logger.debug("Home location not available");
104         }
105
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;
111
112         setOptions(CHANNEL_GROUP_REMOTE, REMOTE_SERVICE_COMMAND, RemoteServiceUtils.getOptions(isElectric));
113     }
114
115     private void setOptions(final String group, final String id, List<CommandOption> options) {
116         commandOptionProvider.setCommandOptions(new ChannelUID(thing.getUID(), group, id), options);
117     }
118
119     protected void updateChannel(final String group, final String id, final State state) {
120         updateState(new ChannelUID(thing.getUID(), group, id), state);
121     }
122
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)));
129     }
130
131     protected void updateVehicle(Vehicle v) {
132         updateVehicleStatus(v);
133         updateRange(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);
140     }
141
142     private void updateTires(@Nullable Tires tires) {
143         if (tires == null) {
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);
152         } else {
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));
169         }
170     }
171
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));
185
186         if (isElectric) {
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))));
193         }
194     }
195
196     protected void updateRange(Vehicle v) {
197         // get the right unit
198         Unit<Length> lengthUnit = VehicleStatusUtils.getLengthUnit(v.status.fuelIndicators);
199         if (lengthUnit == null) {
200             return;
201         }
202         if (isElectric) {
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),
206                     lengthUnit);
207             updateChannel(CHANNEL_GROUP_RANGE, RANGE_ELECTRIC, qtElectricRange);
208             updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_ELECTRIC, qtElectricRadius);
209         }
210         if (hasFuel) {
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);
216         }
217         if (isHybrid) {
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),
221                     lengthUnit);
222             updateChannel(CHANNEL_GROUP_RANGE, RANGE_HYBRID, qtHybridRange);
223             updateChannel(CHANNEL_GROUP_RANGE, RANGE_RADIUS_HYBRID, qtHybridRadius);
224         }
225         if (v.status.currentMileage.mileage == Constants.INT_UNDEF) {
226             updateChannel(CHANNEL_GROUP_RANGE, MILEAGE, UnDefType.UNDEF);
227         } else {
228             updateChannel(CHANNEL_GROUP_RANGE, MILEAGE,
229                     QuantityType.valueOf(v.status.currentMileage.mileage, lengthUnit));
230         }
231         if (isElectric) {
232             updateChannel(CHANNEL_GROUP_RANGE, SOC,
233                     QuantityType.valueOf(v.properties.chargingState.chargePercentage, Units.PERCENT));
234         }
235         if (hasFuel) {
236             updateChannel(CHANNEL_GROUP_RANGE, REMAINING_FUEL,
237                     QuantityType.valueOf(v.properties.fuelLevel.value, Units.LITRE));
238         }
239     }
240
241     protected void updateCheckControls(List<CCMMessage> ccl) {
242         if (ccl.isEmpty()) {
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;
248             ccl.add(ccm);
249         }
250
251         // add all elements to options
252         checkControlList = ccl;
253         List<CommandOption> ccmDescriptionOptions = new ArrayList<>();
254         boolean isSelectedElementIn = false;
255         int index = 0;
256         for (CCMMessage ccEntry : checkControlList) {
257             ccmDescriptionOptions.add(new CommandOption(Integer.toString(index), ccEntry.title));
258             if (selectedCC.equals(ccEntry.title)) {
259                 isSelectedElementIn = true;
260             }
261             index++;
262         }
263         setOptions(CHANNEL_GROUP_CHECK_CONTROL, NAME, ccmDescriptionOptions);
264
265         // if current selected item isn't anymore in the list select first entry
266         if (!isSelectedElementIn) {
267             selectCheckControl(0);
268         }
269     }
270
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));
278         }
279     }
280
281     protected void updateServices(List<CBS> sl) {
282         // if list is empty add "undefined" element
283         if (sl.isEmpty()) {
284             CBS cbsm = new CBS();
285             cbsm.type = Constants.NO_ENTRIES;
286             sl.add(cbsm);
287         }
288
289         // add all elements to options
290         serviceList = sl;
291         List<CommandOption> serviceNameOptions = new ArrayList<>();
292         boolean isSelectedElementIn = false;
293         int index = 0;
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;
299             }
300             index++;
301         }
302         setOptions(CHANNEL_GROUP_SERVICE, NAME, serviceNameOptions);
303
304         // if current selected item isn't anymore in the list select first entry
305         if (!isSelectedElementIn) {
306             selectService(0);
307         }
308     }
309
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)));
318             } else {
319                 updateChannel(CHANNEL_GROUP_SERVICE, DATE, UnDefType.UNDEF);
320             }
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));
325                 } else {
326                     updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
327                             QuantityType.valueOf(serviceEntry.distance.value, ImperialUnits.MILE));
328                 }
329             } else {
330                 updateChannel(CHANNEL_GROUP_SERVICE, MILEAGE,
331                         QuantityType.valueOf(Constants.INT_UNDEF, Constants.KILOMETRE_UNIT));
332             }
333         }
334     }
335
336     protected void updateSessions(List<ChargeSession> sl) {
337         // if list is empty add "undefined" element
338         if (sl.isEmpty()) {
339             ChargeSession cs = new ChargeSession();
340             cs.title = Constants.NO_ENTRIES;
341             sl.add(cs);
342         }
343
344         // add all elements to options
345         sessionList = sl;
346         List<CommandOption> sessionNameOptions = new ArrayList<>();
347         boolean isSelectedElementIn = false;
348         int index = 0;
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;
354             }
355             index++;
356         }
357         setOptions(CHANNEL_GROUP_CHARGE_SESSION, TITLE, sessionNameOptions);
358
359         // if current selected item isn't anymore in the list select first entry
360         if (!isSelectedElementIn) {
361             selectSession(0);
362         }
363     }
364
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));
373             } else {
374                 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ENERGY, StringType.valueOf(Constants.UNDEF));
375             }
376             if (sessionEntry.issues != null) {
377                 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(sessionEntry.issues));
378             } else {
379                 updateChannel(CHANNEL_GROUP_CHARGE_SESSION, ISSUE, StringType.valueOf(Constants.HYPHEN));
380             }
381             updateChannel(CHANNEL_GROUP_CHARGE_SESSION, STATUS, StringType.valueOf(sessionEntry.sessionStatus));
382         }
383     }
384
385     protected void updateChargeProfile(ChargeProfile cp) {
386         ChargeProfileWrapper cpw = new ChargeProfileWrapper(cp);
387
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();
392         if (cs != null) {
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));
397         }
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);
407     }
408
409     protected void updateTimedState(ChargeProfileWrapper profile, ProfileKey key) {
410         final TimedChannel timed = ChargeProfileUtils.getTimedChannel(key);
411         if (timed != null) {
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));
420                 if (timed.hasDays) {
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)));
426                     });
427                 }
428             }
429         }
430     }
431
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)));
443     }
444
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)));
455     }
456
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);
463         } else {
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));
473             } else {
474                 updateChannel(CHANNEL_GROUP_LOCATION, HOME_DISTANCE, UnDefType.UNDEF);
475             }
476         }
477     }
478 }