2 * Copyright (c) 2010-2022 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.dto;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
18 import java.util.HashMap;
19 import java.util.List;
22 import javax.measure.Unit;
23 import javax.measure.quantity.Length;
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
28 import org.openhab.binding.mybmw.internal.dto.properties.CBS;
29 import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
30 import org.openhab.binding.mybmw.internal.utils.Constants;
31 import org.openhab.binding.mybmw.internal.utils.Converter;
32 import org.openhab.binding.mybmw.internal.utils.VehicleStatusUtils;
33 import org.openhab.core.library.types.DateTimeType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.PointType;
36 import org.openhab.core.library.types.QuantityType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.library.unit.ImperialUnits;
39 import org.openhab.core.library.unit.Units;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.types.State;
42 import org.openhab.core.types.UnDefType;
45 * The {@link StatusWrapper} tests stored fingerprint responses from BMW API
47 * @author Bernd Weymann - Initial contribution
50 @SuppressWarnings("null")
51 public class StatusWrapper {
52 private static final Unit<Length> KILOMETRE = Constants.KILOMETRE_UNIT;
54 private Vehicle vehicle;
55 private boolean isElectric;
56 private boolean hasFuel;
57 private boolean isHybrid;
59 private Map<String, State> specialHandlingMap = new HashMap<String, State>();
61 public StatusWrapper(String type, String statusJson) {
62 hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
63 || type.equals(VehicleType.ELECTRIC_REX.toString());
64 isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
65 || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
66 isHybrid = hasFuel && isElectric;
67 List<Vehicle> vl = Converter.getVehicleList(statusJson);
68 assertEquals(1, vl.size(), "Vehciles found");
69 vehicle = Converter.getConsistentVehcile(vl.get(0));
73 * Test results auctomatically against json values
79 public boolean checkResults(@Nullable List<ChannelUID> channels, @Nullable List<State> states) {
80 assertNotNull(channels);
81 assertNotNull(states);
82 assertTrue(channels.size() == states.size(), "Same list sizes");
83 for (int i = 0; i < channels.size(); i++) {
84 checkResult(channels.get(i), states.get(i));
90 * Add a specific check for a value e.g. hard coded "Upcoming Service" in order to check the right ordering
95 public StatusWrapper append(Map<String, State> compareMap) {
96 specialHandlingMap.putAll(compareMap);
100 @SuppressWarnings({ "unchecked", "rawtypes" })
101 private void checkResult(ChannelUID channelUID, State state) {
102 String cUid = channelUID.getIdWithoutGroup();
103 String gUid = channelUID.getGroupId();
104 QuantityType<Length> qt;
110 Unit<Length> wantedUnit;
114 case CHANNEL_GROUP_RANGE:
115 if (!state.equals(UnDefType.UNDEF)) {
116 assertTrue(state instanceof QuantityType);
117 qt = ((QuantityType) state);
118 if (Constants.KM_JSON.equals(vehicle.status.currentMileage.units)) {
119 assertEquals(KILOMETRE, qt.getUnit(), "KM");
121 assertEquals(ImperialUnits.MILE, qt.getUnit(), "Miles");
123 assertEquals(qt.intValue(), vehicle.status.currentMileage.mileage, "Mileage");
125 assertEquals(Constants.INT_UNDEF, vehicle.status.currentMileage.mileage,
126 "Mileage undefined");
129 case CHANNEL_GROUP_SERVICE:
130 State wantedMileage = QuantityType.valueOf(Constants.INT_UNDEF, Constants.KILOMETRE_UNIT);
131 if (!vehicle.properties.serviceRequired.isEmpty()) {
132 if (vehicle.properties.serviceRequired.get(0).distance != null) {
133 if (vehicle.properties.serviceRequired.get(0).distance.units
134 .equals(Constants.KILOMETERS_JSON)) {
135 wantedMileage = QuantityType.valueOf(
136 vehicle.properties.serviceRequired.get(0).distance.value,
137 Constants.KILOMETRE_UNIT);
139 wantedMileage = QuantityType.valueOf(
140 vehicle.properties.serviceRequired.get(0).distance.value,
145 assertEquals(wantedMileage, state, "Service Mileage");
148 assertFalse(true, "Channel " + channelUID + " " + state + " not found");
153 assertTrue(isElectric, "Is Electric");
154 assertTrue(state instanceof QuantityType);
155 qt = ((QuantityType) state);
156 wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
157 assertEquals(wantedUnit, qt.getUnit());
158 assertEquals(VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, vehicle), qt.intValue(),
162 assertTrue(hasFuel, "Has Fuel");
163 assertTrue(state instanceof QuantityType);
164 qt = ((QuantityType) state);
165 wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
166 assertEquals(wantedUnit, qt.getUnit());
167 assertEquals(VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, vehicle), qt.intValue(),
171 assertTrue(isHybrid, "Is Hybrid");
172 assertTrue(state instanceof QuantityType);
173 qt = ((QuantityType) state);
174 wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
175 assertEquals(wantedUnit, qt.getUnit());
176 assertEquals(VehicleStatusUtils.getRange(Constants.PHEV, vehicle), qt.intValue(), "Range Combined");
179 assertTrue(hasFuel, "Has Fuel");
180 assertTrue(state instanceof QuantityType);
181 qt = ((QuantityType) state);
182 assertEquals(Units.LITRE, qt.getUnit(), "Liter Unit");
183 assertEquals(vehicle.properties.fuelLevel.value, qt.intValue(), "Fuel Level");
186 assertTrue(isElectric, "Is Ee<lctric");
187 assertTrue(state instanceof QuantityType);
188 qt = ((QuantityType) state);
189 assertEquals(Units.PERCENT, qt.getUnit(), "Percent");
190 assertEquals(vehicle.properties.chargingState.chargePercentage, qt.intValue(), "Charge Level");
193 assertTrue(state instanceof StringType);
194 st = (StringType) state;
195 assertEquals(Converter.getLockState(vehicle.properties.areDoorsLocked), st, "Vehicle locked");
198 assertTrue(state instanceof StringType);
199 st = (StringType) state;
200 assertEquals(Converter.getClosedState(vehicle.properties.areDoorsClosed), st, "Doors Closed");
203 assertTrue(state instanceof StringType);
204 st = (StringType) state;
205 if (specialHandlingMap.containsKey(WINDOWS)) {
206 assertEquals(specialHandlingMap.get(WINDOWS).toString(), st.toString(), "Windows");
208 assertEquals(Converter.getClosedState(vehicle.properties.areWindowsClosed), st, "Windows");
213 assertTrue(state instanceof StringType);
214 st = (StringType) state;
215 if (specialHandlingMap.containsKey(CHECK_CONTROL)) {
216 assertEquals(specialHandlingMap.get(CHECK_CONTROL).toString(), st.toString(), "Check Control");
218 assertEquals(vehicle.status.checkControlMessagesGeneralState, st.toString(), "Check Control");
222 assertTrue(isElectric, "Is Electric");
223 assertTrue(state instanceof StringType);
224 st = (StringType) state;
225 assertEquals(Converter.getLocalTime(VehicleStatusUtils.getChargeInfo(vehicle)), st.toString(),
229 assertTrue(isElectric, "Is Electric");
230 assertTrue(state instanceof StringType);
231 st = (StringType) state;
232 assertEquals(Converter.toTitleCase(VehicleStatusUtils.getChargStatus(vehicle)), st.toString(),
235 case PLUG_CONNECTION:
236 assertTrue(state instanceof StringType);
237 st = (StringType) state;
238 assertEquals(Converter.getConnectionState(vehicle.properties.chargingState.isChargerConnected), st,
239 "Plug Connection State");
242 assertTrue(state instanceof DateTimeType);
243 dtt = (DateTimeType) state;
244 DateTimeType expected = DateTimeType
245 .valueOf(Converter.zonedToLocalDateTime(vehicle.properties.lastUpdatedAt));
246 assertEquals(expected.toString(), dtt.toString(), "Last Update");
249 assertTrue(state instanceof PointType);
250 pt = (PointType) state;
251 assertNotNull(vehicle.properties.vehicleLocation);
253 PointType.valueOf(Double.toString(vehicle.properties.vehicleLocation.coordinates.latitude) + ","
254 + Double.toString(vehicle.properties.vehicleLocation.coordinates.longitude)),
258 assertTrue(state instanceof QuantityType);
259 qt = ((QuantityType) state);
260 assertEquals(Units.DEGREE_ANGLE, qt.getUnit(), "Angle Unit");
261 assertNotNull(vehicle.properties.vehicleLocation);
262 assertEquals(vehicle.properties.vehicleLocation.heading, qt.intValue(), 0.01, "Heading");
264 case RANGE_RADIUS_ELECTRIC:
265 assertTrue(state instanceof QuantityType);
266 assertTrue(isElectric);
267 qt = ((QuantityType) state);
268 wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
269 assertEquals(wantedUnit, qt.getUnit());
271 Converter.guessRangeRadius(VehicleStatusUtils.getRange(Constants.UNIT_PRECENT_JSON, vehicle)),
272 qt.intValue(), "Range Radius Electric");
274 case RANGE_RADIUS_FUEL:
275 assertTrue(state instanceof QuantityType);
277 qt = (QuantityType) state;
278 wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
279 assertEquals(wantedUnit, qt.getUnit());
281 Converter.guessRangeRadius(VehicleStatusUtils.getRange(Constants.UNIT_LITER_JSON, vehicle)),
282 qt.intValue(), "Range Radius Fuel");
284 case RANGE_RADIUS_HYBRID:
285 assertTrue(state instanceof QuantityType);
286 assertTrue(isHybrid);
287 qt = (QuantityType) state;
288 wantedUnit = VehicleStatusUtils.getLengthUnit(vehicle.status.fuelIndicators);
289 assertEquals(wantedUnit, qt.getUnit());
290 assertEquals(Converter.guessRangeRadius(VehicleStatusUtils.getRange(Constants.PHEV, vehicle)),
291 qt.intValue(), "Range Radius Combined");
293 case DOOR_DRIVER_FRONT:
294 assertTrue(state instanceof StringType);
295 st = (StringType) state;
297 .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.doors.driverFront));
298 assertEquals(wanted.toString(), st.toString(), "Door");
300 case DOOR_DRIVER_REAR:
301 assertTrue(state instanceof StringType);
302 st = (StringType) state;
303 wanted = StringType.valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.doors.driverRear));
304 assertEquals(wanted.toString(), st.toString(), "Door");
306 case DOOR_PASSENGER_FRONT:
307 assertTrue(state instanceof StringType);
308 st = (StringType) state;
310 .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.doors.passengerFront));
311 assertEquals(wanted.toString(), st.toString(), "Door");
313 case DOOR_PASSENGER_REAR:
314 assertTrue(state instanceof StringType);
315 st = (StringType) state;
317 .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.doors.passengerRear));
318 assertEquals(wanted.toString(), st.toString(), "Door");
321 assertTrue(state instanceof StringType);
322 st = (StringType) state;
323 wanted = StringType.valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.trunk));
324 assertEquals(wanted.toString(), st.toString(), "Door");
327 assertTrue(state instanceof StringType);
328 st = (StringType) state;
329 wanted = StringType.valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.hood));
330 assertEquals(wanted.toString(), st.toString(), "Door");
332 case WINDOW_DOOR_DRIVER_FRONT:
333 assertTrue(state instanceof StringType);
334 st = (StringType) state;
336 .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.windows.driverFront));
337 assertEquals(wanted.toString(), st.toString(), "Window");
339 case WINDOW_DOOR_DRIVER_REAR:
340 assertTrue(state instanceof StringType);
341 st = (StringType) state;
343 .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.windows.driverRear));
344 assertEquals(wanted.toString(), st.toString(), "Window");
346 case WINDOW_DOOR_PASSENGER_FRONT:
347 assertTrue(state instanceof StringType);
348 st = (StringType) state;
350 .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.windows.passengerFront));
351 assertEquals(wanted.toString(), st.toString(), "Window");
353 case WINDOW_DOOR_PASSENGER_REAR:
354 assertTrue(state instanceof StringType);
355 st = (StringType) state;
357 .valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.windows.passengerRear));
358 assertEquals(wanted.toString(), st.toString(), "Window");
361 assertTrue(state instanceof StringType);
362 st = (StringType) state;
363 wanted = StringType.valueOf(Converter.toTitleCase(vehicle.properties.doorsAndWindows.moonroof));
364 assertEquals(wanted.toString(), st.toString(), "Window");
367 if (!state.equals(UnDefType.UNDEF)) {
368 assertTrue(state instanceof DateTimeType);
369 dtt = (DateTimeType) state;
370 if (gUid.contentEquals(CHANNEL_GROUP_STATUS)) {
371 if (specialHandlingMap.containsKey(SERVICE_DATE)) {
372 assertEquals(specialHandlingMap.get(SERVICE_DATE).toString(), dtt.toString(),
375 String dueDateString = VehicleStatusUtils
376 .getNextServiceDate(vehicle.properties.serviceRequired).toString();
377 DateTimeType expectedDTT = DateTimeType.valueOf(dueDateString);
378 assertEquals(expectedDTT.toString(), dtt.toString(), "Next Service");
380 } else if (gUid.equals(CHANNEL_GROUP_SERVICE)) {
381 String dueDateString = vehicle.properties.serviceRequired.get(0).dateTime;
382 DateTimeType expectedDTT = DateTimeType.valueOf(Converter.zonedToLocalDateTime(dueDateString));
383 assertEquals(expectedDTT.toString(), dtt.toString(), "First Service Date");
387 case SERVICE_MILEAGE:
388 if (!state.equals(UnDefType.UNDEF)) {
389 qt = ((QuantityType) state);
390 if (gUid.contentEquals(CHANNEL_GROUP_STATUS)) {
391 QuantityType<Length> wantedQt = (QuantityType) VehicleStatusUtils
392 .getNextServiceMileage(vehicle.properties.serviceRequired);
393 assertEquals(wantedQt.getUnit(), qt.getUnit(), "Next Service Miles");
394 assertEquals(wantedQt.intValue(), qt.intValue(), "Mileage");
395 } else if (gUid.equals(CHANNEL_GROUP_SERVICE)) {
396 assertEquals(vehicle.properties.serviceRequired.get(0).distance.units, qt.getUnit(),
397 "First Service Unit");
398 assertEquals(vehicle.properties.serviceRequired.get(0).distance.value, qt.intValue(),
399 "First Service Mileage");
404 assertTrue(state instanceof StringType);
405 st = (StringType) state;
407 case CHANNEL_GROUP_SERVICE:
408 wanted = StringType.valueOf(Constants.NO_ENTRIES);
409 if (!vehicle.properties.serviceRequired.isEmpty()) {
411 .valueOf(Converter.toTitleCase(vehicle.properties.serviceRequired.get(0).type));
413 assertEquals(wanted.toString(), st.toString(), "Service Name");
415 case CHANNEL_GROUP_CHECK_CONTROL:
416 wanted = StringType.valueOf(Constants.NO_ENTRIES);
417 if (!vehicle.status.checkControlMessages.isEmpty()) {
418 wanted = StringType.valueOf(vehicle.status.checkControlMessages.get(0).title);
420 assertEquals(wanted.toString(), st.toString(), "CheckControl Name");
423 assertFalse(true, "Channel " + channelUID + " " + state + " not found");
428 assertTrue(state instanceof StringType);
429 st = (StringType) state;
431 case CHANNEL_GROUP_SERVICE:
432 wanted = StringType.valueOf(Converter.toTitleCase(Constants.NO_ENTRIES));
433 if (!vehicle.properties.serviceRequired.isEmpty()) {
435 .valueOf(Converter.toTitleCase(vehicle.properties.serviceRequired.get(0).type));
437 assertEquals(wanted.toString(), st.toString(), "Service Details");
439 case CHANNEL_GROUP_CHECK_CONTROL:
440 wanted = StringType.valueOf(Constants.NO_ENTRIES);
441 if (!vehicle.status.checkControlMessages.isEmpty()) {
442 wanted = StringType.valueOf(vehicle.status.checkControlMessages.get(0).longDescription);
444 assertEquals(wanted.toString(), st.toString(), "CheckControl Details");
447 assertFalse(true, "Channel " + channelUID + " " + state + " not found");
452 assertTrue(state instanceof StringType);
453 st = (StringType) state;
454 wanted = StringType.valueOf(Constants.NO_ENTRIES);
455 if (!vehicle.status.checkControlMessages.isEmpty()) {
456 wanted = StringType.valueOf(vehicle.status.checkControlMessages.get(0).state);
458 assertEquals(wanted.toString(), st.toString(), "CheckControl Details");
461 if (state.equals(UnDefType.UNDEF)) {
462 for (CBS serviceEntry : vehicle.properties.serviceRequired) {
463 assertTrue(serviceEntry.dateTime == null, "No Service Date available");
466 assertTrue(state instanceof DateTimeType);
467 dtt = (DateTimeType) state;
469 case CHANNEL_GROUP_SERVICE:
470 String dueDateString = vehicle.properties.serviceRequired.get(0).dateTime;
471 DateTimeType expectedDTT = DateTimeType
472 .valueOf(Converter.zonedToLocalDateTime(dueDateString));
473 assertEquals(expectedDTT.toString(), dtt.toString(), "ServiceSate");
476 assertFalse(true, "Channel " + channelUID + " " + state + " not found");
481 case FRONT_LEFT_CURRENT:
482 if (vehicle.properties.tires != null) {
483 assertTrue(state instanceof QuantityType);
484 qt = (QuantityType) state;
485 assertEquals(vehicle.properties.tires.frontLeft.status.currentPressure / 100, qt.doubleValue(),
486 "Fron Left Current");
488 assertTrue(state.equals(UnDefType.UNDEF));
491 case FRONT_LEFT_TARGET:
492 if (vehicle.properties.tires != null) {
493 assertTrue(state instanceof QuantityType);
494 qt = (QuantityType) state;
495 assertEquals(vehicle.properties.tires.frontLeft.status.targetPressure / 100, qt.doubleValue(),
496 "Fron Left Current");
498 assertTrue(state.equals(UnDefType.UNDEF));
501 case FRONT_RIGHT_CURRENT:
502 if (vehicle.properties.tires != null) {
503 assertTrue(state instanceof QuantityType);
504 qt = (QuantityType) state;
505 assertEquals(vehicle.properties.tires.frontRight.status.currentPressure / 100, qt.doubleValue(),
506 "Fron Left Current");
508 assertTrue(state.equals(UnDefType.UNDEF));
511 case FRONT_RIGHT_TARGET:
512 if (vehicle.properties.tires != null) {
513 assertTrue(state instanceof QuantityType);
514 qt = (QuantityType) state;
515 assertEquals(vehicle.properties.tires.frontRight.status.targetPressure / 100, qt.doubleValue(),
516 "Fron Left Current");
518 assertTrue(state.equals(UnDefType.UNDEF));
521 case REAR_LEFT_CURRENT:
522 if (vehicle.properties.tires != null) {
523 assertTrue(state instanceof QuantityType);
524 qt = (QuantityType) state;
525 assertEquals(vehicle.properties.tires.rearLeft.status.currentPressure / 100, qt.doubleValue(),
526 "Fron Left Current");
528 assertTrue(state.equals(UnDefType.UNDEF));
531 case REAR_LEFT_TARGET:
532 if (vehicle.properties.tires != null) {
533 assertTrue(state instanceof QuantityType);
534 qt = (QuantityType) state;
535 assertEquals(vehicle.properties.tires.rearLeft.status.targetPressure / 100, qt.doubleValue(),
536 "Fron Left Current");
538 assertTrue(state.equals(UnDefType.UNDEF));
541 case REAR_RIGHT_CURRENT:
542 if (vehicle.properties.tires != null) {
543 assertTrue(state instanceof QuantityType);
544 qt = (QuantityType) state;
545 assertEquals(vehicle.properties.tires.rearRight.status.currentPressure / 100, qt.doubleValue(),
546 "Fron Left Current");
548 assertTrue(state.equals(UnDefType.UNDEF));
551 case REAR_RIGHT_TARGET:
552 if (vehicle.properties.tires != null) {
553 assertTrue(state instanceof QuantityType);
554 qt = (QuantityType) state;
555 assertEquals(vehicle.properties.tires.rearRight.status.targetPressure / 100, qt.doubleValue(),
556 "Fron Left Current");
558 assertTrue(state.equals(UnDefType.UNDEF));
562 assertTrue(state instanceof OnOffType);
563 oot = (OnOffType) state;
564 if (vehicle.properties.inMotion) {
565 assertEquals(oot.toFullString(), OnOffType.ON.toFullString(), "Vehicle Driving");
567 assertEquals(oot.toFullString(), OnOffType.OFF.toFullString(), "Vehicle Stationary");
571 assertTrue(state instanceof StringType);
572 st = (StringType) state;
573 assertEquals(st.toFullString(), vehicle.properties.vehicleLocation.address.formatted,
577 // don't assert raw channel
580 if (!gUid.equals(CHANNEL_GROUP_CHARGE_PROFILE)) {
581 // fail in case of unknown update
582 assertFalse(true, "Channel " + channelUID + " " + state + " not found");