]> git.basschouten.com Git - openhab-addons.git/blob
afd79fc233af4c61c3caf90ba92d0ef9ed9c27fd
[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.dto;
14
15 import static org.junit.jupiter.api.Assertions.assertEquals;
16 import static org.junit.jupiter.api.Assertions.assertFalse;
17 import static org.junit.jupiter.api.Assertions.assertNotNull;
18 import static org.junit.jupiter.api.Assertions.assertTrue;
19 import static org.openhab.binding.mybmw.internal.MyBMWConstants.ADDRESS;
20 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHARGE_PROFILE;
21 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_CHECK_CONTROL;
22 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_RANGE;
23 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_SERVICE;
24 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHANNEL_GROUP_STATUS;
25 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_REMAINING;
26 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHARGE_STATUS;
27 import static org.openhab.binding.mybmw.internal.MyBMWConstants.CHECK_CONTROL;
28 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DATE;
29 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DETAILS;
30 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOORS;
31 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_DRIVER_FRONT;
32 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_DRIVER_REAR;
33 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_PASSENGER_FRONT;
34 import static org.openhab.binding.mybmw.internal.MyBMWConstants.DOOR_PASSENGER_REAR;
35 import static org.openhab.binding.mybmw.internal.MyBMWConstants.ESTIMATED_FUEL_L_100KM;
36 import static org.openhab.binding.mybmw.internal.MyBMWConstants.ESTIMATED_FUEL_MPG;
37 import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_LEFT_CURRENT;
38 import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_LEFT_TARGET;
39 import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_RIGHT_CURRENT;
40 import static org.openhab.binding.mybmw.internal.MyBMWConstants.FRONT_RIGHT_TARGET;
41 import static org.openhab.binding.mybmw.internal.MyBMWConstants.GPS;
42 import static org.openhab.binding.mybmw.internal.MyBMWConstants.HEADING;
43 import static org.openhab.binding.mybmw.internal.MyBMWConstants.HOME_DISTANCE;
44 import static org.openhab.binding.mybmw.internal.MyBMWConstants.HOOD;
45 import static org.openhab.binding.mybmw.internal.MyBMWConstants.LAST_FETCHED;
46 import static org.openhab.binding.mybmw.internal.MyBMWConstants.LAST_UPDATE;
47 import static org.openhab.binding.mybmw.internal.MyBMWConstants.LOCK;
48 import static org.openhab.binding.mybmw.internal.MyBMWConstants.MILEAGE;
49 import static org.openhab.binding.mybmw.internal.MyBMWConstants.NAME;
50 import static org.openhab.binding.mybmw.internal.MyBMWConstants.PLUG_CONNECTION;
51 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_ELECTRIC;
52 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_FUEL;
53 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_HYBRID;
54 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_ELECTRIC;
55 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_FUEL;
56 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RANGE_RADIUS_HYBRID;
57 import static org.openhab.binding.mybmw.internal.MyBMWConstants.RAW;
58 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_LEFT_CURRENT;
59 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_LEFT_TARGET;
60 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_RIGHT_CURRENT;
61 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REAR_RIGHT_TARGET;
62 import static org.openhab.binding.mybmw.internal.MyBMWConstants.REMAINING_FUEL;
63 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SERVICE_DATE;
64 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SERVICE_MILEAGE;
65 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SEVERITY;
66 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SOC;
67 import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUNROOF;
68 import static org.openhab.binding.mybmw.internal.MyBMWConstants.TRUNK;
69 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOWS;
70 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_DRIVER_FRONT;
71 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_DRIVER_REAR;
72 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_PASSENGER_FRONT;
73 import static org.openhab.binding.mybmw.internal.MyBMWConstants.WINDOW_DOOR_PASSENGER_REAR;
74
75 import java.time.ZoneId;
76 import java.util.HashMap;
77 import java.util.List;
78 import java.util.Map;
79
80 import javax.measure.Unit;
81 import javax.measure.quantity.Length;
82
83 import org.eclipse.jdt.annotation.NonNullByDefault;
84 import org.eclipse.jdt.annotation.Nullable;
85 import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
86 import org.openhab.binding.mybmw.internal.dto.vehicle.RequiredService;
87 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleState;
88 import org.openhab.binding.mybmw.internal.dto.vehicle.VehicleStateContainer;
89 import org.openhab.binding.mybmw.internal.handler.VehicleHandlerTest;
90 import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer;
91 import org.openhab.binding.mybmw.internal.utils.Constants;
92 import org.openhab.binding.mybmw.internal.utils.Converter;
93 import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
94 import org.openhab.core.library.types.DateTimeType;
95 import org.openhab.core.library.types.DecimalType;
96 import org.openhab.core.library.types.PointType;
97 import org.openhab.core.library.types.QuantityType;
98 import org.openhab.core.library.types.StringType;
99 import org.openhab.core.library.unit.SIUnits;
100 import org.openhab.core.library.unit.Units;
101 import org.openhab.core.thing.ChannelUID;
102 import org.openhab.core.types.State;
103 import org.openhab.core.types.UnDefType;
104
105 /**
106  * The {@link StatusWrapper} tests stored fingerprint responses from BMW API
107  *
108  * @author Bernd Weymann - Initial contribution
109  * @author Martin Grassl - updates for v2 API
110  * @author Mark Herwege - remaining charging time test
111  */
112 @NonNullByDefault
113 @SuppressWarnings("null")
114 public class StatusWrapper {
115     private static final Unit<Length> KILOMETRE = Constants.KILOMETRE_UNIT;
116
117     private VehicleState vehicleState;
118     private boolean isElectric;
119     private boolean hasFuel;
120     private boolean isHybrid;
121
122     private Map<String, State> specialHandlingMap = new HashMap<String, State>();
123
124     public StatusWrapper(String type, String statusJson) {
125         hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
126                 || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.MILD_HYBRID.toString());
127         isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
128                 || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
129         isHybrid = hasFuel && isElectric;
130         VehicleStateContainer vehicleStateContainer = JsonStringDeserializer.getVehicleState(statusJson);
131         vehicleState = vehicleStateContainer.getState();
132     }
133
134     /**
135      * Test results automatically against json values
136      *
137      * @param channels
138      * @param states
139      * @return
140      */
141     public boolean checkResults(@Nullable List<ChannelUID> channels, @Nullable List<State> states) {
142         assertNotNull(channels);
143         assertNotNull(states);
144         assertTrue(channels.size() == states.size(), "Same list sizes");
145         for (int i = 0; i < channels.size(); i++) {
146             checkResult(channels.get(i), states.get(i));
147         }
148         return true;
149     }
150
151     /**
152      * Add a specific check for a value e.g. hard coded "Upcoming Service" in order to check the right ordering
153      *
154      * @param specialHand
155      * @return
156      */
157     public StatusWrapper append(Map<String, State> compareMap) {
158         specialHandlingMap.putAll(compareMap);
159         return this;
160     }
161
162     @SuppressWarnings({ "unchecked", "rawtypes" })
163     private void checkResult(ChannelUID channelUID, State state) {
164         String cUid = channelUID.getIdWithoutGroup();
165         String gUid = channelUID.getGroupId();
166         QuantityType<Length> qt;
167         StringType st;
168         StringType wanted;
169         DateTimeType dtt;
170         PointType pt;
171         DecimalType dt;
172         Unit<Length> wantedUnit = KILOMETRE;
173         switch (cUid) {
174             case MILEAGE:
175                 switch (gUid) {
176                     case CHANNEL_GROUP_RANGE:
177                         if (!state.equals(UnDefType.UNDEF)) {
178                             assertTrue(state instanceof QuantityType);
179                             qt = ((QuantityType) state);
180                             assertEquals(qt.intValue(), vehicleState.getCurrentMileage(), "Mileage");
181                         } else {
182                             assertEquals(Constants.INT_UNDEF, vehicleState.getCurrentMileage(), "Mileage undefined");
183                         }
184                         break;
185                     case CHANNEL_GROUP_SERVICE:
186                         State wantedMileage = UnDefType.UNDEF;
187                         if (!vehicleState.getRequiredServices().isEmpty()) {
188                             if (vehicleState.getRequiredServices().get(0).getMileage() > 0) {
189                                 wantedMileage = QuantityType.valueOf(
190                                         vehicleState.getRequiredServices().get(0).getMileage(),
191                                         Constants.KILOMETRE_UNIT);
192                             } else {
193                                 wantedMileage = UnDefType.UNDEF;
194                             }
195                         }
196                         assertEquals(wantedMileage, state, "Service Mileage");
197                         break;
198                     default:
199                         assertFalse(true, "Channel " + channelUID + " " + state + " not found");
200                         break;
201                 }
202                 break;
203             case RANGE_ELECTRIC:
204                 assertTrue(isElectric, "Is Electric");
205                 assertTrue(state instanceof QuantityType);
206                 qt = ((QuantityType) state);
207                 assertEquals(wantedUnit, qt.getUnit());
208                 assertEquals(vehicleState.getElectricChargingState().getRange(), qt.intValue(), "Range Electric");
209                 break;
210             case RANGE_HYBRID:
211                 assertTrue(isHybrid, "Is hybrid");
212                 assertTrue(state instanceof QuantityType);
213                 qt = ((QuantityType) state);
214                 assertEquals(wantedUnit, qt.getUnit());
215                 assertEquals(vehicleState.getRange(), qt.intValue(), "Range combined hybrid");
216                 break;
217             case RANGE_FUEL:
218                 assertTrue(hasFuel, "Has Fuel");
219                 assertTrue(state instanceof QuantityType);
220                 qt = ((QuantityType) state);
221                 if (!isHybrid) {
222                     assertEquals(vehicleState.getCombustionFuelLevel().getRange(), qt.intValue(), "Range Combustion");
223                 } else {
224                     assertEquals(
225                             vehicleState.getCombustionFuelLevel().getRange()
226                                     - vehicleState.getElectricChargingState().getRange(),
227                             qt.intValue(), "Range Combustion");
228                 }
229                 break;
230             case REMAINING_FUEL:
231                 assertTrue(hasFuel, "Has Fuel");
232                 assertTrue(state instanceof QuantityType);
233                 qt = ((QuantityType) state);
234                 assertEquals(Units.LITRE, qt.getUnit(), "Liter Unit");
235                 assertEquals(vehicleState.getCombustionFuelLevel().getRemainingFuelLiters(), qt.intValue(),
236                         "Fuel Level");
237                 break;
238             case ESTIMATED_FUEL_L_100KM:
239                 assertTrue(hasFuel, "Has Fuel");
240
241                 if (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() > 0
242                         && vehicleState.getCombustionFuelLevel().getRange() > 0) {
243                     assertTrue(state instanceof DecimalType);
244                     dt = ((DecimalType) state);
245                     double estimatedFuelConsumptionL100km = vehicleState.getCombustionFuelLevel()
246                             .getRemainingFuelLiters() * 1.0 / vehicleState.getCombustionFuelLevel().getRange() * 100.0;
247                     assertEquals(estimatedFuelConsumptionL100km, dt.doubleValue(),
248                             "Estimated Fuel Consumption l/100km");
249                 } else {
250                     assertTrue(state instanceof UnDefType);
251                 }
252                 break;
253             case ESTIMATED_FUEL_MPG:
254                 assertTrue(hasFuel, "Has Fuel");
255
256                 if (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() > 0
257                         && vehicleState.getCombustionFuelLevel().getRange() > 0) {
258                     assertTrue(state instanceof DecimalType);
259                     dt = ((DecimalType) state);
260                     double estimatedFuelConsumptionMpg = 235.214583
261                             / (vehicleState.getCombustionFuelLevel().getRemainingFuelLiters() * 1.0
262                                     / vehicleState.getCombustionFuelLevel().getRange() * 100.0);
263                     assertEquals(estimatedFuelConsumptionMpg, dt.doubleValue(), "Estimated Fuel Consumption mpg");
264                 } else {
265                     assertTrue(state instanceof UnDefType);
266                 }
267                 break;
268             case SOC:
269                 assertTrue(isElectric, "Is Electric");
270                 assertTrue(state instanceof QuantityType);
271                 qt = ((QuantityType) state);
272                 assertEquals(Units.PERCENT, qt.getUnit(), "Percent");
273                 assertEquals(vehicleState.getElectricChargingState().getChargingLevelPercent(), qt.intValue(),
274                         "Charge Level");
275                 break;
276             case LOCK:
277                 assertTrue(state instanceof StringType);
278                 st = (StringType) state;
279                 wanted = StringType
280                         .valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getCombinedSecurityState()));
281                 assertEquals(wanted.toString(), st.toString(), "Vehicle locked");
282                 break;
283             case DOORS:
284                 assertTrue(state instanceof StringType);
285                 st = (StringType) state;
286                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getCombinedState()));
287                 assertEquals(wanted.toString(), st.toString(), "Doors closed");
288                 break;
289             case WINDOWS:
290                 assertTrue(state instanceof StringType);
291                 st = (StringType) state;
292                 if (specialHandlingMap.containsKey(WINDOWS)) {
293                     assertEquals(specialHandlingMap.get(WINDOWS).toString(), st.toString(), "Windows");
294                 } else {
295                     wanted = StringType
296                             .valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getCombinedState()));
297                     assertEquals(wanted.toString(), st.toString(), "Windows");
298                 }
299
300                 break;
301             case CHECK_CONTROL:
302                 assertTrue(state instanceof StringType);
303                 st = (StringType) state;
304                 if (specialHandlingMap.containsKey(CHECK_CONTROL)) {
305                     assertEquals(specialHandlingMap.get(CHECK_CONTROL).toString(), st.toString(), "Check Control");
306                 } else {
307                     wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getOverallCheckControlStatus()));
308                     assertEquals(wanted.toString(), st.toString(), "Check Control");
309                 }
310                 break;
311             case CHARGE_REMAINING:
312                 // charge-remaining can be either a number in minutes, or UNDEF
313                 assertTrue(isElectric, "Is Electric");
314                 if (state instanceof QuantityType) {
315                     assertTrue(state instanceof QuantityType);
316                     qt = ((QuantityType) state);
317                     assertEquals(Units.MINUTE, qt.getUnit(), "Minute Unit");
318                     assertEquals(vehicleState.getElectricChargingState().getRemainingChargingMinutes(), qt.intValue(),
319                             "Charge Time Remaining");
320                 } else {
321                     assertTrue(state instanceof UnDefType);
322                 }
323                 break;
324             case CHARGE_STATUS:
325                 assertTrue(isElectric, "Is Electric");
326                 assertTrue(state instanceof StringType);
327                 st = (StringType) state;
328                 assertEquals(Converter.toTitleCase(vehicleState.getElectricChargingState().getChargingStatus()),
329                         st.toString(), "Charge Status");
330                 break;
331             case PLUG_CONNECTION:
332                 assertTrue(state instanceof StringType);
333                 st = (StringType) state;
334                 assertEquals(Converter.getConnectionState(vehicleState.getElectricChargingState().isChargerConnected()),
335                         st, "Plug Connection State");
336                 break;
337             case LAST_UPDATE:
338                 assertTrue(state instanceof DateTimeType);
339                 dtt = (DateTimeType) state;
340                 State expectedUpdateDate = Converter.zonedToLocalDateTime(vehicleState.getLastUpdatedAt(),
341                         ZoneId.systemDefault());
342                 assertEquals(expectedUpdateDate.toString(), dtt.toString(), "Last Update");
343                 break;
344             case LAST_FETCHED:
345                 assertTrue(state instanceof DateTimeType);
346                 dtt = (DateTimeType) state;
347                 State expectedFetchedDate = Converter.zonedToLocalDateTime(vehicleState.getLastFetched(),
348                         ZoneId.systemDefault());
349                 assertEquals(expectedFetchedDate.toString(), dtt.toString(), "Last Fetched");
350                 break;
351             case GPS:
352                 if (state instanceof PointType) {
353                     pt = (PointType) state;
354                     assertNotNull(vehicleState.getLocation());
355                     assertEquals(
356                             PointType.valueOf(Double.toString(vehicleState.getLocation().getCoordinates().getLatitude())
357                                     + ","
358                                     + Double.toString(vehicleState.getLocation().getCoordinates().getLongitude())),
359                             pt, "Coordinates");
360                 } // else no check needed
361                 break;
362             case HEADING:
363                 if (state instanceof QuantityType quantityCommand) {
364                     qt = quantityCommand;
365                     assertEquals(Units.DEGREE_ANGLE, qt.getUnit(), "Angle Unit");
366                     assertNotNull(vehicleState.getLocation());
367                     assertEquals(vehicleState.getLocation().getHeading(), qt.intValue(), 0.01, "Heading");
368                 } // else no check needed
369                 break;
370             case RANGE_RADIUS_ELECTRIC:
371                 assertTrue(state instanceof QuantityType);
372                 assertTrue(isElectric);
373                 qt = ((QuantityType) state);
374                 assertEquals(Converter.guessRangeRadius(vehicleState.getElectricChargingState().getRange()),
375                         qt.intValue(), "Range Radius Electric");
376                 break;
377             case RANGE_RADIUS_FUEL:
378                 assertTrue(state instanceof QuantityType);
379                 assertTrue(hasFuel);
380                 qt = (QuantityType) state;
381                 if (!isHybrid) {
382                     assertEquals(Converter.guessRangeRadius(vehicleState.getCombustionFuelLevel().getRange()),
383                             qt.intValue(), "Range Radius Fuel");
384                 } else {
385                     assertEquals(
386                             Converter.guessRangeRadius(vehicleState.getCombustionFuelLevel().getRange()
387                                     - vehicleState.getElectricChargingState().getRange()),
388                             qt.intValue(), "Range Radius Fuel");
389                 }
390                 break;
391             case RANGE_RADIUS_HYBRID:
392                 assertTrue(state instanceof QuantityType);
393                 assertTrue(isHybrid);
394                 qt = (QuantityType) state;
395                 assertEquals(Converter.guessRangeRadius(vehicleState.getRange()), qt.intValue(), "Range Radius Hybrid");
396                 break;
397             case DOOR_DRIVER_FRONT:
398                 assertTrue(state instanceof StringType);
399                 st = (StringType) state;
400                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getLeftFront()));
401                 assertEquals(wanted.toString(), st.toString(), "Door");
402                 break;
403             case DOOR_DRIVER_REAR:
404                 assertTrue(state instanceof StringType);
405                 st = (StringType) state;
406                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getLeftRear()));
407                 assertEquals(wanted.toString(), st.toString(), "Door");
408                 break;
409             case DOOR_PASSENGER_FRONT:
410                 assertTrue(state instanceof StringType);
411                 st = (StringType) state;
412                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getRightFront()));
413                 assertEquals(wanted.toString(), st.toString(), "Door");
414                 break;
415             case DOOR_PASSENGER_REAR:
416                 assertTrue(state instanceof StringType);
417                 st = (StringType) state;
418                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getRightRear()));
419                 assertEquals(wanted.toString(), st.toString(), "Door");
420                 break;
421             case TRUNK:
422                 assertTrue(state instanceof StringType);
423                 st = (StringType) state;
424                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getTrunk()));
425                 assertEquals(wanted.toString(), st.toString(), "Door");
426                 break;
427             case HOOD:
428                 assertTrue(state instanceof StringType);
429                 st = (StringType) state;
430                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getDoorsState().getHood()));
431                 assertEquals(wanted.toString(), st.toString(), "Door");
432                 break;
433             case WINDOW_DOOR_DRIVER_FRONT:
434                 assertTrue(state instanceof StringType);
435                 st = (StringType) state;
436                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getLeftFront()));
437                 assertEquals(wanted.toString(), st.toString(), "Window");
438                 break;
439             case WINDOW_DOOR_DRIVER_REAR:
440                 assertTrue(state instanceof StringType);
441                 st = (StringType) state;
442                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getLeftRear()));
443                 assertEquals(wanted.toString(), st.toString(), "Window");
444                 break;
445             case WINDOW_DOOR_PASSENGER_FRONT:
446                 assertTrue(state instanceof StringType);
447                 st = (StringType) state;
448                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getRightFront()));
449                 assertEquals(wanted.toString(), st.toString(), "Window");
450                 break;
451             case WINDOW_DOOR_PASSENGER_REAR:
452                 assertTrue(state instanceof StringType);
453                 st = (StringType) state;
454                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getWindowsState().getRightRear()));
455                 assertEquals(wanted.toString(), st.toString(), "Window");
456                 break;
457             case SUNROOF:
458                 assertTrue(state instanceof StringType);
459                 st = (StringType) state;
460                 wanted = StringType.valueOf(Converter.toTitleCase(vehicleState.getRoofState().getRoofState()));
461                 assertEquals(wanted.toString(), st.toString(), "Window");
462                 break;
463             case SERVICE_DATE:
464                 if (!state.equals(UnDefType.UNDEF)) {
465                     assertTrue(state instanceof DateTimeType);
466                     dtt = (DateTimeType) state;
467                     if (gUid.contentEquals(CHANNEL_GROUP_STATUS)) {
468                         if (specialHandlingMap.containsKey(SERVICE_DATE)) {
469                             assertEquals(specialHandlingMap.get(SERVICE_DATE).toString(), dtt.toString(),
470                                     "Next Service");
471                         } else {
472                             String dueDateString = VehicleStatusUtils
473                                     .getNextServiceDate(vehicleState.getRequiredServices()).toString();
474                             DateTimeType expectedDTT = DateTimeType.valueOf(dueDateString);
475                             assertEquals(expectedDTT.toString(), dtt.toString(), "Next Service");
476                         }
477                     } else if (gUid.equals(CHANNEL_GROUP_SERVICE)) {
478                         String dueDateString = vehicleState.getRequiredServices().get(0).getDateTime();
479                         State expectedDTT = Converter.zonedToLocalDateTime(dueDateString, ZoneId.systemDefault());
480                         assertEquals(expectedDTT.toString(), dtt.toString(), "First Service Date");
481                     }
482                 }
483                 break;
484             case SERVICE_MILEAGE:
485                 if (!state.equals(UnDefType.UNDEF)) {
486                     qt = ((QuantityType) state);
487                     if (gUid.contentEquals(CHANNEL_GROUP_STATUS)) {
488                         QuantityType<Length> wantedQt = (QuantityType) VehicleStatusUtils
489                                 .getNextServiceMileage(vehicleState.getRequiredServices());
490                         assertEquals(wantedQt.getUnit(), qt.getUnit(), "Next Service Miles");
491                         assertEquals(wantedQt.intValue(), qt.intValue(), "Mileage");
492                     } else if (gUid.equals(CHANNEL_GROUP_SERVICE)) {
493                         assertEquals(vehicleState.getRequiredServices().get(0).getMileage(), qt.intValue(),
494                                 "First Service Mileage");
495                     }
496                 }
497                 break;
498             case NAME:
499                 assertTrue(state instanceof StringType);
500                 st = (StringType) state;
501                 switch (gUid) {
502                     case CHANNEL_GROUP_SERVICE:
503                         wanted = StringType.valueOf(Constants.NO_ENTRIES);
504                         if (!vehicleState.getRequiredServices().isEmpty()) {
505                             wanted = StringType.valueOf(
506                                     Converter.toTitleCase(vehicleState.getRequiredServices().get(0).getType()));
507                         }
508                         assertEquals(wanted.toString(), st.toString(), "Service Name");
509                         break;
510                     case CHANNEL_GROUP_CHECK_CONTROL:
511                         wanted = StringType.valueOf(Constants.NO_ENTRIES);
512                         if (!vehicleState.getCheckControlMessages().isEmpty()) {
513                             wanted = StringType.valueOf(
514                                     Converter.toTitleCase(vehicleState.getCheckControlMessages().get(0).getType()));
515                         }
516                         assertEquals(wanted.toString(), st.toString(), "CheckControl Name");
517                         break;
518                     default:
519                         assertFalse(true, "Channel " + channelUID + " " + state + " not found");
520                         break;
521                 }
522                 break;
523             case DETAILS:
524                 assertTrue(state instanceof StringType);
525                 st = (StringType) state;
526                 switch (gUid) {
527                     case CHANNEL_GROUP_SERVICE:
528                         wanted = StringType.valueOf(Converter.toTitleCase(Constants.NO_ENTRIES));
529                         if (!vehicleState.getRequiredServices().isEmpty()) {
530                             wanted = StringType.valueOf(vehicleState.getRequiredServices().get(0).getDescription());
531                         }
532                         assertEquals(wanted.toString(), st.toString(), "Service Details");
533                         break;
534                     case CHANNEL_GROUP_CHECK_CONTROL:
535                         wanted = StringType.valueOf(Constants.NO_ENTRIES);
536                         if (!vehicleState.getCheckControlMessages().isEmpty()) {
537                             wanted = StringType.valueOf(vehicleState.getCheckControlMessages().get(0).getDescription());
538                         }
539                         assertEquals(wanted.toString(), st.toString(), "CheckControl Details");
540                         break;
541                     default:
542                         assertFalse(true, "Channel " + channelUID + " " + state + " not found");
543                         break;
544                 }
545                 break;
546             case SEVERITY:
547                 assertTrue(state instanceof StringType);
548                 st = (StringType) state;
549                 wanted = StringType.valueOf(Constants.NO_ENTRIES);
550                 if (!vehicleState.getCheckControlMessages().isEmpty()) {
551                     wanted = StringType.valueOf(
552                             Converter.toTitleCase(vehicleState.getCheckControlMessages().get(0).getSeverity()));
553                 }
554                 assertEquals(wanted.toString(), st.toString(), "CheckControl Severity");
555                 break;
556             case DATE:
557                 if (state.equals(UnDefType.UNDEF)) {
558                     for (RequiredService serviceEntry : vehicleState.getRequiredServices()) {
559                         assertTrue(serviceEntry.getDateTime() == null, "No Service Date available");
560                     }
561                 } else {
562                     assertTrue(state instanceof DateTimeType);
563                     dtt = (DateTimeType) state;
564                     switch (gUid) {
565                         case CHANNEL_GROUP_SERVICE:
566                             String dueDateString = vehicleState.getRequiredServices().get(0).getDateTime();
567                             State expectedDTT = Converter.zonedToLocalDateTime(dueDateString, ZoneId.systemDefault());
568                             assertEquals(expectedDTT.toString(), dtt.toString(), "ServiceSate");
569                             break;
570                         default:
571                             assertFalse(true, "Channel " + channelUID + " " + state + " not found");
572                             break;
573                     }
574                 }
575                 break;
576             case FRONT_LEFT_CURRENT:
577                 if (vehicleState.getTireState().getFrontLeft().getStatus().getCurrentPressure() > 0) {
578                     assertTrue(state instanceof QuantityType);
579                     qt = (QuantityType) state;
580                     assertEquals(vehicleState.getTireState().getFrontLeft().getStatus().getCurrentPressure() / 100.0,
581                             qt.doubleValue(), "Fron Left Current");
582                 } else {
583                     assertEquals(state, UnDefType.UNDEF);
584                 }
585                 break;
586             case FRONT_LEFT_TARGET:
587                 if (vehicleState.getTireState().getFrontLeft().getStatus().getTargetPressure() > 0) {
588                     assertTrue(state instanceof QuantityType);
589                     qt = (QuantityType) state;
590                     assertEquals(vehicleState.getTireState().getFrontLeft().getStatus().getTargetPressure() / 100.0,
591                             qt.doubleValue(), "Fron Left Current");
592                 } else {
593                     assertTrue(state.equals(UnDefType.UNDEF));
594                 }
595                 break;
596             case FRONT_RIGHT_CURRENT:
597                 if (vehicleState.getTireState().getFrontRight().getStatus().getCurrentPressure() > 0) {
598                     assertTrue(state instanceof QuantityType);
599                     qt = (QuantityType) state;
600                     assertEquals(vehicleState.getTireState().getFrontRight().getStatus().getCurrentPressure() / 100.0,
601                             qt.doubleValue(), "Fron Left Current");
602                 } else {
603                     assertTrue(state.equals(UnDefType.UNDEF));
604                 }
605                 break;
606             case FRONT_RIGHT_TARGET:
607                 if (vehicleState.getTireState().getFrontRight().getStatus().getTargetPressure() > 0) {
608                     assertTrue(state instanceof QuantityType);
609                     qt = (QuantityType) state;
610                     assertEquals(vehicleState.getTireState().getFrontRight().getStatus().getTargetPressure() / 100.0,
611                             qt.doubleValue(), "Fron Left Current");
612                 } else {
613                     assertTrue(state.equals(UnDefType.UNDEF));
614                 }
615                 break;
616             case REAR_LEFT_CURRENT:
617                 if (vehicleState.getTireState().getRearLeft().getStatus().getCurrentPressure() > 0) {
618                     assertTrue(state instanceof QuantityType);
619                     qt = (QuantityType) state;
620                     assertEquals(vehicleState.getTireState().getRearLeft().getStatus().getCurrentPressure() / 100.0,
621                             qt.doubleValue(), "Fron Left Current");
622                 } else {
623                     assertTrue(state.equals(UnDefType.UNDEF));
624                 }
625                 break;
626             case REAR_LEFT_TARGET:
627                 if (vehicleState.getTireState().getRearLeft().getStatus().getTargetPressure() > 0) {
628                     assertTrue(state instanceof QuantityType);
629                     qt = (QuantityType) state;
630                     assertEquals(vehicleState.getTireState().getRearLeft().getStatus().getTargetPressure() / 100.0,
631                             qt.doubleValue(), "Fron Left Current");
632                 } else {
633                     assertTrue(state.equals(UnDefType.UNDEF));
634                 }
635                 break;
636             case REAR_RIGHT_CURRENT:
637                 if (vehicleState.getTireState().getRearRight().getStatus().getCurrentPressure() > 0) {
638                     assertTrue(state instanceof QuantityType);
639                     qt = (QuantityType) state;
640                     assertEquals(vehicleState.getTireState().getRearRight().getStatus().getCurrentPressure() / 100.0,
641                             qt.doubleValue(), "Fron Left Current");
642                 } else {
643                     assertTrue(state.equals(UnDefType.UNDEF));
644                 }
645                 break;
646             case REAR_RIGHT_TARGET:
647                 if (vehicleState.getTireState().getRearRight().getStatus().getTargetPressure() > 0) {
648                     assertTrue(state instanceof QuantityType);
649                     qt = (QuantityType) state;
650                     assertEquals(vehicleState.getTireState().getRearRight().getStatus().getTargetPressure() / 100.0,
651                             qt.doubleValue(), "Fron Left Current");
652                 } else {
653                     assertTrue(state.equals(UnDefType.UNDEF));
654                 }
655                 break;
656             case ADDRESS:
657                 if (state instanceof StringType) {
658                     st = (StringType) state;
659                     assertEquals(st.toFullString(), vehicleState.getLocation().getAddress().getFormatted(),
660                             "Location Address");
661                 } // else no check needed
662                 break;
663             case HOME_DISTANCE:
664                 if (state instanceof QuantityType quantity) {
665                     qt = quantity;
666                     PointType vehicleLocation = PointType
667                             .valueOf(Double.toString(vehicleState.getLocation().getCoordinates().getLatitude()) + ","
668                                     + Double.toString(vehicleState.getLocation().getCoordinates().getLongitude()));
669                     int distance = vehicleLocation.distanceFrom(VehicleHandlerTest.HOME_LOCATION).intValue();
670                     assertEquals(qt.intValue(), distance, "Distance from Home");
671                     assertEquals(qt.getUnit(), SIUnits.METRE, "Distance from Home Unit");
672                 } // else no check needed
673                 break;
674             case RAW:
675                 // don't assert raw channel
676                 break;
677             default:
678                 if (!gUid.equals(CHANNEL_GROUP_CHARGE_PROFILE)) {
679                     // fail in case of unknown update
680                     assertFalse(true, "Channel " + channelUID + " " + state + " not found");
681                 }
682                 break;
683         }
684     }
685 }