2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.bmwconnecteddrive.internal.dto;
15 import static org.junit.jupiter.api.Assertions.*;
16 import static org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.*;
18 import java.util.HashMap;
19 import java.util.List;
22 import javax.measure.Unit;
23 import javax.measure.quantity.Length;
24 import javax.measure.quantity.Time;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.VehicleType;
29 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Doors;
30 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatus;
31 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatusContainer;
32 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Windows;
33 import org.openhab.binding.bmwconnecteddrive.internal.utils.Constants;
34 import org.openhab.binding.bmwconnecteddrive.internal.utils.Converter;
35 import org.openhab.binding.bmwconnecteddrive.internal.utils.VehicleStatusUtils;
36 import org.openhab.core.library.types.DateTimeType;
37 import org.openhab.core.library.types.PointType;
38 import org.openhab.core.library.types.QuantityType;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.library.unit.ImperialUnits;
41 import org.openhab.core.library.unit.Units;
42 import org.openhab.core.thing.ChannelUID;
43 import org.openhab.core.types.State;
44 import org.openhab.core.types.UnDefType;
46 import com.google.gson.Gson;
49 * The {@link StatusWrapper} Test json responses from ConnectedDrive Portal
51 * @author Bernd Weymann - Initial contribution
54 @SuppressWarnings("null")
55 public class StatusWrapper {
56 private static final Gson GSON = new Gson();
57 private static final Unit<Length> KILOMETRE = Constants.KILOMETRE_UNIT;
58 private static final double ALLOWED_MILE_CONVERSION_DEVIATION = 1.5;
59 private static final double ALLOWED_KM_ROUND_DEVIATION = 0.1;
61 private VehicleStatus vStatus;
62 private boolean imperial;
63 private boolean isElectric;
64 private boolean hasFuel;
65 private boolean isHybrid;
67 private Map<String, State> specialHandlingMap = new HashMap<String, State>();
69 public StatusWrapper(String type, boolean imperial, String statusJson) {
70 this.imperial = imperial;
71 hasFuel = type.equals(VehicleType.CONVENTIONAL.toString()) || type.equals(VehicleType.PLUGIN_HYBRID.toString())
72 || type.equals(VehicleType.ELECTRIC_REX.toString());
73 isElectric = type.equals(VehicleType.PLUGIN_HYBRID.toString())
74 || type.equals(VehicleType.ELECTRIC_REX.toString()) || type.equals(VehicleType.ELECTRIC.toString());
75 isHybrid = hasFuel && isElectric;
76 VehicleStatusContainer container = GSON.fromJson(statusJson, VehicleStatusContainer.class);
77 assertNotNull(container);
78 assertNotNull(container.vehicleStatus);
79 vStatus = container.vehicleStatus;
83 * Test results auctomatically against json values
89 public boolean checkResults(@Nullable List<ChannelUID> channels, @Nullable List<State> states) {
90 assertNotNull(channels);
91 assertNotNull(states);
92 assertTrue(channels.size() == states.size(), "Same list sizes");
93 for (int i = 0; i < channels.size(); i++) {
94 checkResult(channels.get(i), states.get(i));
100 * Add a specific check for a value e.g. hard coded "Upcoming Service" in order to check the right ordering
105 public StatusWrapper append(Map<String, State> compareMap) {
106 specialHandlingMap.putAll(compareMap);
110 @SuppressWarnings({ "unchecked", "rawtypes" })
111 private void checkResult(ChannelUID channelUID, State state) {
112 String cUid = channelUID.getIdWithoutGroup();
113 String gUid = channelUID.getGroupId();
114 QuantityType<Length> qt;
115 QuantityType<Time> qtt;
122 assertTrue(state instanceof QuantityType);
123 qt = ((QuantityType) state);
125 assertEquals(ImperialUnits.MILE, qt.getUnit(), "Miles");
127 assertEquals(KILOMETRE, qt.getUnit(), "KM");
130 case CHANNEL_GROUP_RANGE:
131 assertEquals(qt.intValue(), vStatus.mileage, "Mileage");
133 case CHANNEL_GROUP_SERVICE:
134 if (vStatus.cbsData.isEmpty()) {
135 assertEquals(qt.intValue(), -1, "Service Mileage");
137 assertEquals(qt.intValue(), vStatus.cbsData.get(0).cbsRemainingMileage, "Service Mileage");
140 case CHANNEL_GROUP_CHECK_CONTROL:
141 if (vStatus.checkControlMessages.isEmpty()) {
142 assertEquals(qt.intValue(), -1, "CheckControl Mileage");
144 assertEquals(qt.intValue(), vStatus.checkControlMessages.get(0).ccmMileage,
145 "CheckControl Mileage");
149 assertFalse(true, "Channel " + channelUID + " " + state + " not found");
154 assertTrue(isElectric, "Is Eelctric");
155 assertTrue(state instanceof QuantityType);
156 qt = ((QuantityType) state);
158 assertEquals(ImperialUnits.MILE, qt.getUnit(), "Miles");
159 assertEquals(Converter.round(qt.floatValue()), Converter.round(vStatus.remainingRangeElectricMls),
160 ALLOWED_MILE_CONVERSION_DEVIATION, "Mileage");
162 assertEquals(KILOMETRE, qt.getUnit(), "KM");
163 assertEquals(Converter.round(qt.floatValue()), Converter.round(vStatus.remainingRangeElectric),
164 ALLOWED_KM_ROUND_DEVIATION, "Mileage");
168 assertTrue(hasFuel, "Has Fuel");
169 if (!(state instanceof UnDefType)) {
170 assertTrue(state instanceof QuantityType);
171 qt = ((QuantityType) state);
173 assertEquals(ImperialUnits.MILE, qt.getUnit(), "Miles");
174 assertEquals(Converter.round(qt.floatValue()), Converter.round(vStatus.remainingRangeFuelMls),
175 ALLOWED_MILE_CONVERSION_DEVIATION, "Mileage");
177 assertEquals(KILOMETRE, qt.getUnit(), "KM");
178 assertEquals(Converter.round(qt.floatValue()), Converter.round(vStatus.remainingRangeFuel),
179 ALLOWED_KM_ROUND_DEVIATION, "Mileage");
184 assertTrue(isHybrid, "Is Hybrid");
185 assertTrue(state instanceof QuantityType);
186 qt = ((QuantityType) state);
188 assertEquals(ImperialUnits.MILE, qt.getUnit(), "Miles");
189 assertEquals(Converter.round(qt.floatValue()),
190 Converter.round(vStatus.remainingRangeElectricMls + vStatus.remainingRangeFuelMls),
191 ALLOWED_MILE_CONVERSION_DEVIATION, "Mileage");
193 assertEquals(KILOMETRE, qt.getUnit(), "KM");
194 assertEquals(Converter.round(qt.floatValue()),
195 Converter.round(vStatus.remainingRangeElectric + vStatus.remainingRangeFuel),
196 ALLOWED_KM_ROUND_DEVIATION, "Mileage");
200 assertTrue(hasFuel, "Has Fuel");
201 assertTrue(state instanceof QuantityType);
202 qt = ((QuantityType) state);
203 assertEquals(Units.LITRE, qt.getUnit(), "Liter Unit");
204 assertEquals(Converter.round(vStatus.remainingFuel), Converter.round(qt.floatValue()), 0.01,
208 assertTrue(isElectric, "Is Eelctric");
209 assertTrue(state instanceof QuantityType);
210 qt = ((QuantityType) state);
211 assertEquals(Units.PERCENT, qt.getUnit(), "Percent");
212 assertEquals(Converter.round(vStatus.chargingLevelHv), Converter.round(qt.floatValue()), 0.01,
216 assertTrue(state instanceof StringType);
217 st = (StringType) state;
218 assertEquals(Converter.toTitleCase(vStatus.doorLockState), st.toString(), "Vehicle locked");
221 assertTrue(state instanceof StringType);
222 st = (StringType) state;
223 Doors doorState = GSON.fromJson(GSON.toJson(vStatus), Doors.class);
224 if (doorState != null) {
225 assertEquals(VehicleStatusUtils.checkClosed(doorState), st.toString(), "Doors Closed");
232 assertTrue(state instanceof StringType);
233 st = (StringType) state;
234 Windows windowState = GSON.fromJson(GSON.toJson(vStatus), Windows.class);
235 if (windowState != null) {
236 if (specialHandlingMap.containsKey(WINDOWS)) {
237 assertEquals(specialHandlingMap.get(WINDOWS).toString(), st.toString(), "Windows");
239 assertEquals(VehicleStatusUtils.checkClosed(windowState), st.toString(), "Windows");
247 assertTrue(state instanceof StringType);
248 st = (StringType) state;
249 if (specialHandlingMap.containsKey(CHECK_CONTROL)) {
250 assertEquals(specialHandlingMap.get(CHECK_CONTROL).toString(), st.toString(), "Check Control");
252 assertEquals(Converter.toTitleCase(VehicleStatusUtils.checkControlActive(vStatus)), st.toString(),
257 assertTrue(isElectric, "Is Electric");
258 assertTrue(state instanceof StringType);
259 st = (StringType) state;
260 if (vStatus.chargingStatus.contentEquals(Constants.INVALID)) {
261 assertEquals(Converter.toTitleCase(vStatus.lastChargingEndReason), st.toString(), "Charge Status");
263 assertEquals(Converter.toTitleCase(vStatus.chargingStatus), st.toString(), "Charge Status");
266 case CHARGE_REMAINING:
267 assertTrue(isElectric, "Is Electric");
268 if (vStatus.chargingTimeRemaining == null) {
269 assertTrue(state instanceof UnDefType, "expected UndefType");
271 assertTrue(state instanceof QuantityType);
272 qtt = ((QuantityType) state);
273 assertEquals(qtt.doubleValue(), vStatus.chargingTimeRemaining);
274 assertEquals(Units.MINUTE, qtt.getUnit(), "Minutes");
278 assertTrue(state instanceof DateTimeType);
279 dtt = (DateTimeType) state;
280 DateTimeType expected = DateTimeType
281 .valueOf(Converter.getLocalDateTime(VehicleStatusUtils.getUpdateTime(vStatus)));
282 assertEquals(expected.toString(), dtt.toString(), "Last Update");
285 assertTrue(state instanceof PointType);
286 pt = (PointType) state;
287 assertNotNull(vStatus.position);
288 assertEquals(vStatus.position.getCoordinates(), pt.toString(), "Coordinates");
291 assertTrue(state instanceof QuantityType);
292 qt = ((QuantityType) state);
293 assertEquals(Units.DEGREE_ANGLE, qt.getUnit(), "Angle Unit");
294 assertNotNull(vStatus.position);
295 assertEquals(vStatus.position.heading, qt.intValue(), 0.01, "Heading");
297 case RANGE_RADIUS_ELECTRIC:
298 assertTrue(state instanceof QuantityType);
299 assertTrue(isElectric);
300 qt = (QuantityType) state;
302 assertEquals(Converter.guessRangeRadius(vStatus.remainingRangeElectricMls), qt.floatValue(), 1,
303 "Range Radius Electric mi");
305 assertEquals(Converter.guessRangeRadius(vStatus.remainingRangeElectric), qt.floatValue(), 0.1,
306 "Range Radius Electric km");
309 case RANGE_RADIUS_FUEL:
310 assertTrue(state instanceof QuantityType);
312 qt = (QuantityType) state;
314 assertEquals(Converter.guessRangeRadius(vStatus.remainingRangeFuelMls), qt.floatValue(), 1,
315 "Range Radius Fuel mi");
317 assertEquals(Converter.guessRangeRadius(vStatus.remainingRangeFuel), qt.floatValue(), 0.1,
318 "Range Radius Fuel km");
321 case RANGE_RADIUS_HYBRID:
322 assertTrue(state instanceof QuantityType);
323 assertTrue(isHybrid);
324 qt = (QuantityType) state;
327 Converter.guessRangeRadius(
328 vStatus.remainingRangeElectricMls + vStatus.remainingRangeFuelMls),
329 qt.floatValue(), ALLOWED_MILE_CONVERSION_DEVIATION, "Range Radius Hybrid mi");
332 Converter.guessRangeRadius(vStatus.remainingRangeElectric + vStatus.remainingRangeFuel),
333 qt.floatValue(), ALLOWED_KM_ROUND_DEVIATION, "Range Radius Hybrid km");
336 case DOOR_DRIVER_FRONT:
337 assertTrue(state instanceof StringType);
338 st = (StringType) state;
339 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.doorDriverFront));
340 assertEquals(wanted.toString(), st.toString(), "Door");
342 case DOOR_DRIVER_REAR:
343 assertTrue(state instanceof StringType);
344 st = (StringType) state;
345 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.doorDriverRear));
346 assertEquals(wanted.toString(), st.toString(), "Door");
348 case DOOR_PASSENGER_FRONT:
349 assertTrue(state instanceof StringType);
350 st = (StringType) state;
351 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.doorPassengerFront));
352 assertEquals(wanted.toString(), st.toString(), "Door");
354 case DOOR_PASSENGER_REAR:
355 assertTrue(state instanceof StringType);
356 st = (StringType) state;
357 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.doorPassengerRear));
358 assertEquals(wanted.toString(), st.toString(), "Door");
361 assertTrue(state instanceof StringType);
362 st = (StringType) state;
363 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.trunk));
364 assertEquals(wanted.toString(), st.toString(), "Door");
367 assertTrue(state instanceof StringType);
368 st = (StringType) state;
369 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.hood));
370 assertEquals(wanted.toString(), st.toString(), "Door");
372 case WINDOW_DOOR_DRIVER_FRONT:
373 assertTrue(state instanceof StringType);
374 st = (StringType) state;
375 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.windowDriverFront));
376 assertEquals(wanted.toString(), st.toString(), "Window");
378 case WINDOW_DOOR_DRIVER_REAR:
379 assertTrue(state instanceof StringType);
380 st = (StringType) state;
381 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.windowDriverRear));
382 assertEquals(wanted.toString(), st.toString(), "Window");
384 case WINDOW_DOOR_PASSENGER_FRONT:
385 assertTrue(state instanceof StringType);
386 st = (StringType) state;
387 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.windowPassengerFront));
388 assertEquals(wanted.toString(), st.toString(), "Window");
390 case WINDOW_DOOR_PASSENGER_REAR:
391 assertTrue(state instanceof StringType);
392 st = (StringType) state;
393 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.windowPassengerRear));
394 assertEquals(wanted.toString(), st.toString(), "Window");
397 assertTrue(state instanceof StringType);
398 st = (StringType) state;
399 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.rearWindow));
400 assertEquals(wanted.toString(), st.toString(), "Window");
403 assertTrue(state instanceof StringType);
404 st = (StringType) state;
405 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.sunroof));
406 assertEquals(wanted.toString(), st.toString(), "Window");
409 assertTrue(state instanceof DateTimeType);
410 dtt = (DateTimeType) state;
411 if (gUid.contentEquals(CHANNEL_GROUP_STATUS)) {
412 if (specialHandlingMap.containsKey(SERVICE_DATE)) {
413 assertEquals(specialHandlingMap.get(SERVICE_DATE).toString(), dtt.toString(), "Next Service");
415 String dueDateString = VehicleStatusUtils.getNextServiceDate(vStatus);
416 DateTimeType expectedDTT = DateTimeType.valueOf(Converter.getLocalDateTime(dueDateString));
417 assertEquals(expectedDTT.toString(), dtt.toString(), "Next Service");
419 } else if (gUid.equals(CHANNEL_GROUP_SERVICE)) {
420 String dueDateString = vStatus.cbsData.get(0).getDueDate();
421 DateTimeType expectedDTT = DateTimeType.valueOf(Converter.getLocalDateTime(dueDateString));
422 assertEquals(expectedDTT.toString(), dtt.toString(), "First Service Date");
425 case SERVICE_MILEAGE:
426 assertTrue(state instanceof QuantityType);
427 qt = ((QuantityType) state);
428 if (gUid.contentEquals(CHANNEL_GROUP_STATUS)) {
430 assertEquals(ImperialUnits.MILE, qt.getUnit(), "Next Service Miles");
431 assertEquals(VehicleStatusUtils.getNextServiceMileage(vStatus), qt.intValue(), "Mileage");
433 assertEquals(KILOMETRE, qt.getUnit(), "Next Service KM");
434 assertEquals(VehicleStatusUtils.getNextServiceMileage(vStatus), qt.intValue(), "Mileage");
436 } else if (gUid.equals(CHANNEL_GROUP_SERVICE)) {
438 assertEquals(ImperialUnits.MILE, qt.getUnit(), "First Service Miles");
439 assertEquals(vStatus.cbsData.get(0).cbsRemainingMileage, qt.intValue(),
440 "First Service Mileage");
442 assertEquals(KILOMETRE, qt.getUnit(), "First Service KM");
443 assertEquals(vStatus.cbsData.get(0).cbsRemainingMileage, qt.intValue(),
444 "First Service Mileage");
449 assertTrue(state instanceof StringType);
450 st = (StringType) state;
452 case CHANNEL_GROUP_SERVICE:
453 wanted = StringType.valueOf(Converter.toTitleCase(Constants.NO_ENTRIES));
454 if (!vStatus.cbsData.isEmpty()) {
455 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.cbsData.get(0).getType()));
457 assertEquals(wanted.toString(), st.toString(), "Service Name");
459 case CHANNEL_GROUP_CHECK_CONTROL:
460 wanted = StringType.valueOf(Constants.NO_ENTRIES);
461 if (!vStatus.checkControlMessages.isEmpty()) {
462 wanted = StringType.valueOf(vStatus.checkControlMessages.get(0).ccmDescriptionShort);
464 assertEquals(wanted.toString(), st.toString(), "CheckControl Name");
467 assertFalse(true, "Channel " + channelUID + " " + state + " not found");
472 assertTrue(state instanceof StringType);
473 st = (StringType) state;
475 case CHANNEL_GROUP_SERVICE:
476 wanted = StringType.valueOf(Converter.toTitleCase(Constants.NO_ENTRIES));
477 if (!vStatus.cbsData.isEmpty()) {
478 wanted = StringType.valueOf(Converter.toTitleCase(vStatus.cbsData.get(0).getDescription()));
480 assertEquals(wanted.toString(), st.toString(), "Service Details");
482 case CHANNEL_GROUP_CHECK_CONTROL:
483 wanted = StringType.valueOf(Constants.NO_ENTRIES);
484 if (!vStatus.checkControlMessages.isEmpty()) {
485 wanted = StringType.valueOf(vStatus.checkControlMessages.get(0).ccmDescriptionLong);
487 assertEquals(wanted.toString(), st.toString(), "CheckControl Details");
490 assertFalse(true, "Channel " + channelUID + " " + state + " not found");
495 assertTrue(state instanceof DateTimeType);
496 dtt = (DateTimeType) state;
498 case CHANNEL_GROUP_SERVICE:
499 String dueDateString = Constants.NULL_DATE;
500 if (!vStatus.cbsData.isEmpty()) {
501 dueDateString = vStatus.cbsData.get(0).getDueDate();
503 DateTimeType expectedDTT = DateTimeType.valueOf(Converter.getLocalDateTime(dueDateString));
504 assertEquals(expectedDTT.toString(), dtt.toString(), "ServiceSate");
507 assertFalse(true, "Channel " + channelUID + " " + state + " not found");
512 // fail in case of unknown update
513 assertFalse(true, "Channel " + channelUID + " " + state + " not found");