2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mybmw.internal.utils;
15 import java.time.ZonedDateTime;
16 import java.util.List;
18 import javax.measure.Unit;
19 import javax.measure.quantity.Length;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.mybmw.internal.MyBMWConstants.VehicleType;
24 import org.openhab.binding.mybmw.internal.dto.properties.CBS;
25 import org.openhab.binding.mybmw.internal.dto.status.FuelIndicator;
26 import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
27 import org.openhab.core.library.types.DateTimeType;
28 import org.openhab.core.library.types.QuantityType;
29 import org.openhab.core.library.unit.ImperialUnits;
30 import org.openhab.core.types.State;
31 import org.openhab.core.types.UnDefType;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * The {@link VehicleStatusUtils} Data Transfer Object
38 * @author Bernd Weymann - Initial contribution
41 public class VehicleStatusUtils {
42 public static final Logger LOGGER = LoggerFactory.getLogger(VehicleStatusUtils.class);
44 public static State getNextServiceDate(List<CBS> cbsMessageList) {
45 ZonedDateTime farFuture = ZonedDateTime.now().plusYears(100);
46 ZonedDateTime serviceDate = farFuture;
47 for (CBS service : cbsMessageList) {
48 if (service.dateTime != null) {
49 ZonedDateTime d = ZonedDateTime.parse(service.dateTime);
50 if (d.isBefore(serviceDate)) {
55 if (serviceDate.equals(farFuture)) {
56 return UnDefType.UNDEF;
58 DateTimeType dt = DateTimeType.valueOf(serviceDate.format(Converter.DATE_INPUT_PATTERN));
63 public static State getNextServiceMileage(List<CBS> cbsMessageList) {
64 boolean imperial = false;
65 int serviceMileage = Integer.MAX_VALUE;
66 for (CBS service : cbsMessageList) {
67 if (service.distance != null) {
68 if (service.distance.value < serviceMileage) {
69 serviceMileage = service.distance.value;
70 imperial = !Constants.KILOMETERS_JSON.equals(service.distance.units);
74 if (serviceMileage != Integer.MAX_VALUE) {
76 return QuantityType.valueOf(serviceMileage, ImperialUnits.MILE);
78 return QuantityType.valueOf(serviceMileage, Constants.KILOMETRE_UNIT);
81 return UnDefType.UNDEF;
86 * calculates the mapping of thing type
92 public static VehicleType vehicleType(String driveTrain, String model) {
93 if (Constants.BEV.equals(driveTrain)) {
94 if (model.endsWith(Constants.REX_EXTENSION)) {
95 return VehicleType.ELECTRIC_REX;
97 return VehicleType.ELECTRIC;
99 } else if (Constants.PHEV.equals(driveTrain)) {
100 return VehicleType.PLUGIN_HYBRID;
101 } else if (Constants.CONV.equals(driveTrain) || Constants.HYBRID.equals(driveTrain)) {
102 return VehicleType.CONVENTIONAL;
104 LOGGER.warn("Unknown Vehicle Type: {} | {}", model, driveTrain);
105 return VehicleType.UNKNOWN;
108 public static @Nullable Unit<Length> getLengthUnit(List<FuelIndicator> indicators) {
109 Unit<Length> ret = null;
110 for (FuelIndicator fuelIndicator : indicators) {
111 String unitAbbrev = fuelIndicator.rangeUnits;
112 switch (unitAbbrev) {
113 case Constants.KM_JSON:
115 if (!ret.equals(Constants.KILOMETRE_UNIT)) {
116 LOGGER.debug("Ambigious Unit declarations. Found {} before {}", ret, Constants.KM_JSON);
119 ret = Constants.KILOMETRE_UNIT;
122 case Constants.MI_JSON:
124 if (!ret.equals(ImperialUnits.MILE)) {
125 LOGGER.debug("Ambigious Unit declarations. Found {} before {}", ret, Constants.MI_JSON);
128 ret = ImperialUnits.MILE;
132 LOGGER.debug("Cannot evaluate Unit for {}", unitAbbrev);
140 * The range values delivered by BMW are quite ambiguous!
141 * - status fuel indicators are missing a unique identifier
142 * - properties ranges delivering wrong values for hybrid and fuel range
143 * - properties ranges are not reflecting mi / km - every time km
145 * So getRange will try
147 * 2) ranges from properties, except combined range
148 * 3) take a guess from fuel indicators
154 public static int getRange(String unitJson, Vehicle vehicle) {
155 if (vehicle.status.fuelIndicators.size() == 1) {
156 return Converter.stringToInt(vehicle.status.fuelIndicators.get(0).rangeValue);
158 return guessRange(unitJson, vehicle);
163 * Guesses the range from 3 fuelindicators
164 * - electric range calculation is correct
165 * - for the 2 other values:
166 * -- smaller one is assigned to fuel range
167 * -- bigger one is assigned to hybrid range
169 * @see VehicleStatusTest testGuessRange
175 public static int guessRange(String unitJson, Vehicle vehicle) {
176 int electricGuess = Constants.INT_UNDEF;
177 int fuelGuess = Constants.INT_UNDEF;
178 int hybridGuess = Constants.INT_UNDEF;
179 for (FuelIndicator fuelIndicator : vehicle.status.fuelIndicators) {
180 // electric range - this fits 100%
181 if (Constants.UNIT_PRECENT_JSON.equals(fuelIndicator.levelUnits)
182 && fuelIndicator.chargingStatusType != null) {
184 electricGuess = Converter.stringToInt(fuelIndicator.rangeValue);
186 if (fuelGuess == Constants.INT_UNDEF) {
187 // fuel not set? then assume it's fuel
188 fuelGuess = Converter.stringToInt(fuelIndicator.rangeValue);
190 // fuel already guessed - take smaller value for fuel, bigger for hybrid
191 int newGuess = Converter.stringToInt(fuelIndicator.rangeValue);
192 hybridGuess = Math.max(fuelGuess, newGuess);
193 fuelGuess = Math.min(fuelGuess, newGuess);
198 case Constants.UNIT_PRECENT_JSON:
199 return electricGuess;
200 case Constants.UNIT_LITER_JSON:
205 return Constants.INT_UNDEF;
209 public static String getChargStatus(Vehicle vehicle) {
210 FuelIndicator fi = getElectricFuelIndicator(vehicle);
211 if (fi.chargingStatusType != null) {
212 if (fi.chargingStatusType.equals(Constants.DEFAULT)) {
213 return Constants.NOT_CHARGING_STATE;
215 return fi.chargingStatusType;
218 return Constants.UNDEF;
221 public static String getChargeInfo(Vehicle vehicle) {
222 FuelIndicator fi = getElectricFuelIndicator(vehicle);
223 if (fi.chargingStatusType != null && fi.infoLabel != null) {
224 if (fi.chargingStatusType.equals(Constants.CHARGING_STATE)
225 || fi.chargingStatusType.equals(Constants.PLUGGED_STATE)) {
229 return Constants.HYPHEN;
232 private static FuelIndicator getElectricFuelIndicator(Vehicle vehicle) {
233 for (FuelIndicator fuelIndicator : vehicle.status.fuelIndicators) {
234 if (Constants.UNIT_PRECENT_JSON.equals(fuelIndicator.levelUnits)
235 && fuelIndicator.chargingStatusType != null) {
236 return fuelIndicator;
239 return new FuelIndicator();