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 return DateTimeType.valueOf(serviceDate.format(Converter.DATE_INPUT_PATTERN));
62 public static State getNextServiceMileage(List<CBS> cbsMessageList) {
63 boolean imperial = false;
64 int serviceMileage = Integer.MAX_VALUE;
65 for (CBS service : cbsMessageList) {
66 if (service.distance != null) {
67 if (service.distance.value < serviceMileage) {
68 serviceMileage = service.distance.value;
69 imperial = !Constants.KILOMETERS_JSON.equals(service.distance.units);
73 if (serviceMileage != Integer.MAX_VALUE) {
75 return QuantityType.valueOf(serviceMileage, ImperialUnits.MILE);
77 return QuantityType.valueOf(serviceMileage, Constants.KILOMETRE_UNIT);
80 return UnDefType.UNDEF;
85 * calculates the mapping of thing type
91 public static VehicleType vehicleType(String driveTrain, String model) {
92 if (Constants.BEV.equals(driveTrain)) {
93 if (model.endsWith(Constants.REX_EXTENSION)) {
94 return VehicleType.ELECTRIC_REX;
96 return VehicleType.ELECTRIC;
98 } else if (Constants.PHEV.equals(driveTrain)) {
99 return VehicleType.PLUGIN_HYBRID;
100 } else if (Constants.CONV.equals(driveTrain) || Constants.HYBRID.equals(driveTrain)) {
101 return VehicleType.CONVENTIONAL;
103 LOGGER.warn("Unknown Vehicle Type: {} | {}", model, driveTrain);
104 return VehicleType.UNKNOWN;
107 public static @Nullable Unit<Length> getLengthUnit(List<FuelIndicator> indicators) {
108 Unit<Length> ret = null;
109 for (FuelIndicator fuelIndicator : indicators) {
110 String unitAbbrev = fuelIndicator.rangeUnits;
111 switch (unitAbbrev) {
112 case Constants.KM_JSON:
114 if (!ret.equals(Constants.KILOMETRE_UNIT)) {
115 LOGGER.debug("Ambigious Unit declarations. Found {} before {}", ret, Constants.KM_JSON);
118 ret = Constants.KILOMETRE_UNIT;
121 case Constants.MI_JSON:
123 if (!ret.equals(ImperialUnits.MILE)) {
124 LOGGER.debug("Ambigious Unit declarations. Found {} before {}", ret, Constants.MI_JSON);
127 ret = ImperialUnits.MILE;
131 LOGGER.debug("Cannot evaluate Unit for {}", unitAbbrev);
139 * The range values delivered by BMW are quite ambiguous!
140 * - status fuel indicators are missing a unique identifier
141 * - properties ranges delivering wrong values for hybrid and fuel range
142 * - properties ranges are not reflecting mi / km - every time km
144 * So getRange will try
146 * 2) ranges from properties, except combined range
147 * 3) take a guess from fuel indicators
153 public static int getRange(String unitJson, Vehicle vehicle) {
154 if (vehicle.status.fuelIndicators.size() == 1) {
155 return Converter.stringToInt(vehicle.status.fuelIndicators.get(0).rangeValue);
157 return guessRange(unitJson, vehicle);
162 * Guesses the range from 3 fuelindicators
163 * - electric range calculation is correct
164 * - for the 2 other values:
165 * -- smaller one is assigned to fuel range
166 * -- bigger one is assigned to hybrid range
168 * @see VehicleStatusTest testGuessRange
174 public static int guessRange(String unitJson, Vehicle vehicle) {
175 int electricGuess = Constants.INT_UNDEF;
176 int fuelGuess = Constants.INT_UNDEF;
177 int hybridGuess = Constants.INT_UNDEF;
178 for (FuelIndicator fuelIndicator : vehicle.status.fuelIndicators) {
179 // electric range - this fits 100%
180 if (Constants.UNIT_PRECENT_JSON.equals(fuelIndicator.levelUnits)
181 && fuelIndicator.chargingStatusType != null) {
183 electricGuess = Converter.stringToInt(fuelIndicator.rangeValue);
185 if (fuelGuess == Constants.INT_UNDEF) {
186 // fuel not set? then assume it's fuel
187 fuelGuess = Converter.stringToInt(fuelIndicator.rangeValue);
189 // fuel already guessed - take smaller value for fuel, bigger for hybrid
190 int newGuess = Converter.stringToInt(fuelIndicator.rangeValue);
191 hybridGuess = Math.max(fuelGuess, newGuess);
192 fuelGuess = Math.min(fuelGuess, newGuess);
197 case Constants.UNIT_PRECENT_JSON:
198 return electricGuess;
199 case Constants.UNIT_LITER_JSON:
204 return Constants.INT_UNDEF;
208 public static String getChargStatus(Vehicle vehicle) {
209 FuelIndicator fi = getElectricFuelIndicator(vehicle);
210 if (fi.chargingStatusType != null) {
211 if (fi.chargingStatusType.equals(Constants.DEFAULT)) {
212 return Constants.NOT_CHARGING_STATE;
214 return fi.chargingStatusType;
217 return Constants.UNDEF;
220 public static String getChargeInfo(Vehicle vehicle) {
221 FuelIndicator fi = getElectricFuelIndicator(vehicle);
222 if (fi.chargingStatusType != null && fi.infoLabel != null) {
223 if (fi.chargingStatusType.equals(Constants.CHARGING_STATE)
224 || fi.chargingStatusType.equals(Constants.PLUGGED_STATE)) {
228 return Constants.HYPHEN;
231 private static FuelIndicator getElectricFuelIndicator(Vehicle vehicle) {
232 for (FuelIndicator fuelIndicator : vehicle.status.fuelIndicators) {
233 if (Constants.UNIT_PRECENT_JSON.equals(fuelIndicator.levelUnits)
234 && fuelIndicator.chargingStatusType != null) {
235 return fuelIndicator;
238 return new FuelIndicator();