]> git.basschouten.com Git - openhab-addons.git/blob
4e783d137568f6e3a8d4d1a3f8823e8f86d962c0
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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.bmwconnecteddrive.internal.utils;
14
15 import java.time.LocalDateTime;
16 import java.time.ZoneId;
17 import java.time.ZonedDateTime;
18 import java.time.format.DateTimeFormatter;
19 import java.time.format.DateTimeParseException;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.Optional;
23
24 import javax.measure.quantity.Length;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.bmwconnecteddrive.internal.dto.compat.VehicleAttributes;
29 import org.openhab.binding.bmwconnecteddrive.internal.dto.compat.VehicleAttributesContainer;
30 import org.openhab.binding.bmwconnecteddrive.internal.dto.compat.VehicleMessages;
31 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CBSMessage;
32 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.CCMMessage;
33 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.Position;
34 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatus;
35 import org.openhab.binding.bmwconnecteddrive.internal.dto.status.VehicleStatusContainer;
36 import org.openhab.core.i18n.TimeZoneProvider;
37 import org.openhab.core.library.types.QuantityType;
38 import org.openhab.core.library.unit.ImperialUnits;
39 import org.openhab.core.types.State;
40 import org.openhab.core.types.UnDefType;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import com.google.gson.Gson;
45
46 /**
47  * The {@link Converter} Conversion Helpers
48  *
49  * @author Bernd Weymann - Initial contribution
50  */
51 @NonNullByDefault
52 public class Converter {
53     public static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
54
55     public static final DateTimeFormatter SERVICE_DATE_INPUT_PATTERN = DateTimeFormatter.ofPattern("yyyy-MM-dd");
56     public static final DateTimeFormatter SERVICE_DATE_OUTPUT_PATTERN = DateTimeFormatter.ofPattern("MMM yyyy");
57
58     public static final String LOCAL_DATE_INPUT_PATTERN_STRING = "dd.MM.yyyy HH:mm";
59     public static final DateTimeFormatter LOCAL_DATE_INPUT_PATTERN = DateTimeFormatter
60             .ofPattern(LOCAL_DATE_INPUT_PATTERN_STRING);
61
62     public static final String DATE_INPUT_PATTERN_STRING = "yyyy-MM-dd'T'HH:mm:ss";
63     public static final DateTimeFormatter DATE_INPUT_PATTERN = DateTimeFormatter.ofPattern(DATE_INPUT_PATTERN_STRING);
64
65     public static final String DATE_INPUT_ZONE_PATTERN_STRING = "yyyy-MM-dd'T'HH:mm:ssZ";
66     public static final DateTimeFormatter DATE_INPUT_ZONE_PATTERN = DateTimeFormatter
67             .ofPattern(DATE_INPUT_ZONE_PATTERN_STRING);
68
69     public static final DateTimeFormatter DATE_OUTPUT_PATTERN = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
70
71     private static final Gson GSON = new Gson();
72     private static final double SCALE = 10;
73     public static final double MILES_TO_KM_RATIO = 1.60934;
74     private static final String SPLIT_HYPHEN = "-";
75     private static final String SPLIT_BRACKET = "\\(";
76
77     public static Optional<TimeZoneProvider> timeZoneProvider = Optional.empty();
78
79     public static double round(double value) {
80         return Math.round(value * SCALE) / SCALE;
81     }
82
83     public static String getLocalDateTimeWithoutOffest(@Nullable String input) {
84         if (input == null) {
85             return Constants.NULL_DATE;
86         }
87         LocalDateTime ldt;
88         if (input.contains(Constants.PLUS)) {
89             ldt = LocalDateTime.parse(input, Converter.DATE_INPUT_ZONE_PATTERN);
90         } else {
91             ldt = LocalDateTime.parse(input, Converter.DATE_INPUT_PATTERN);
92         }
93         return ldt.format(Converter.DATE_INPUT_PATTERN);
94     }
95
96     public static String getLocalDateTime(@Nullable String input) {
97         if (input == null) {
98             return Constants.NULL_DATE;
99         }
100
101         LocalDateTime ldt;
102         if (input.contains(Constants.PLUS)) {
103             ldt = LocalDateTime.parse(input, Converter.DATE_INPUT_ZONE_PATTERN);
104         } else {
105             try {
106                 ldt = LocalDateTime.parse(input, Converter.DATE_INPUT_PATTERN);
107             } catch (DateTimeParseException dtpe) {
108                 ldt = LocalDateTime.parse(input, Converter.LOCAL_DATE_INPUT_PATTERN);
109             }
110         }
111         ZonedDateTime zdtUTC = ldt.atZone(ZoneId.of("UTC"));
112         ZonedDateTime zdtLZ;
113         zdtLZ = zdtUTC.withZoneSameInstant(ZoneId.systemDefault());
114         if (timeZoneProvider.isPresent()) {
115             zdtLZ = zdtUTC.withZoneSameInstant(timeZoneProvider.get().getTimeZone());
116         } else {
117             zdtLZ = zdtUTC.withZoneSameInstant(ZoneId.systemDefault());
118         }
119         return zdtLZ.format(Converter.DATE_INPUT_PATTERN);
120     }
121
122     public static void setTimeZoneProvider(TimeZoneProvider tzp) {
123         timeZoneProvider = Optional.of(tzp);
124     }
125
126     public static String toTitleCase(@Nullable String input) {
127         if (input == null) {
128             return toTitleCase(Constants.UNDEF);
129         } else {
130             String lower = input.replaceAll(Constants.UNDERLINE, Constants.SPACE).toLowerCase();
131             String converted = toTitleCase(lower, Constants.SPACE);
132             converted = toTitleCase(converted, SPLIT_HYPHEN);
133             converted = toTitleCase(converted, SPLIT_BRACKET);
134             return converted;
135         }
136     }
137
138     private static String toTitleCase(String input, String splitter) {
139         String[] arr = input.split(splitter);
140         StringBuilder sb = new StringBuilder();
141         for (int i = 0; i < arr.length; i++) {
142             if (i > 0) {
143                 sb.append(splitter.replaceAll("\\\\", Constants.EMPTY));
144             }
145             sb.append(Character.toUpperCase(arr[i].charAt(0))).append(arr[i].substring(1));
146         }
147         return sb.toString().trim();
148     }
149
150     public static String capitalizeFirst(String str) {
151         return str.substring(0, 1).toUpperCase() + str.substring(1);
152     }
153
154     public static Gson getGson() {
155         return GSON;
156     }
157
158     /**
159      * Measure distance between 2 coordinates
160      *
161      * @param sourceLatitude
162      * @param sourceLongitude
163      * @param destinationLatitude
164      * @param destinationLongitude
165      * @return distance
166      */
167     public static double measureDistance(double sourceLatitude, double sourceLongitude, double destinationLatitude,
168             double destinationLongitude) {
169         double earthRadius = 6378.137; // Radius of earth in KM
170         double dLat = destinationLatitude * Math.PI / 180 - sourceLatitude * Math.PI / 180;
171         double dLon = destinationLongitude * Math.PI / 180 - sourceLongitude * Math.PI / 180;
172         double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(sourceLatitude * Math.PI / 180)
173                 * Math.cos(destinationLatitude * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
174         double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
175         return earthRadius * c;
176     }
177
178     /**
179      * Easy function but there's some measures behind:
180      * Guessing the range of the Vehicle on Map. If you can drive x kilometers with your Vehicle it's not feasible to
181      * project this x km Radius on Map. The roads to be taken are causing some overhead because they are not a straight
182      * line from Location A to B.
183      * I've taken some measurements to calculate the overhead factor based on Google Maps
184      * Berlin - Dresden: Road Distance: 193 air-line Distance 167 = Factor 87%
185      * Kassel - Frankfurt: Road Distance: 199 air-line Distance 143 = Factor 72%
186      * After measuring more distances you'll find out that the outcome is between 70% and 90%. So
187      *
188      * This depends also on the roads of a concrete route but this is only a guess without any Route Navigation behind
189      *
190      * In future it's foreseen to replace this with BMW RangeMap Service which isn't running at the moment.
191      *
192      * @param range
193      * @return mapping from air-line distance to "real road" distance
194      */
195     public static double guessRangeRadius(double range) {
196         return range * 0.8;
197     }
198
199     public static State getMiles(QuantityType<Length> qtLength) {
200         if (qtLength.intValue() == -1) {
201             return UnDefType.UNDEF;
202         }
203         QuantityType<Length> qt = qtLength.toUnit(ImperialUnits.MILE);
204         if (qt != null) {
205             return qt;
206         } else {
207             LOGGER.debug("Cannot convert {} to miles", qt);
208             return UnDefType.UNDEF;
209         }
210     }
211
212     public static int getIndex(String fullString) {
213         int index = -1;
214         try {
215             index = Integer.parseInt(fullString);
216         } catch (NumberFormatException nfe) {
217         }
218         return index;
219     }
220
221     public static String transformLegacyStatus(@Nullable VehicleAttributesContainer vac) {
222         if (vac != null) {
223             if (vac.attributesMap != null && vac.vehicleMessages != null) {
224                 VehicleAttributes attributesMap = vac.attributesMap;
225                 VehicleMessages vehicleMessages = vac.vehicleMessages;
226                 // create target objects
227                 VehicleStatusContainer vsc = new VehicleStatusContainer();
228                 VehicleStatus vs = new VehicleStatus();
229                 vsc.vehicleStatus = vs;
230
231                 vs.mileage = attributesMap.mileage;
232                 vs.doorLockState = attributesMap.doorLockState;
233
234                 vs.doorDriverFront = attributesMap.doorDriverFront;
235                 vs.doorDriverRear = attributesMap.doorDriverRear;
236                 vs.doorPassengerFront = attributesMap.doorPassengerFront;
237                 vs.doorPassengerRear = attributesMap.doorPassengerRear;
238                 vs.hood = attributesMap.hoodState;
239                 vs.trunk = attributesMap.trunkState;
240
241                 vs.windowDriverFront = attributesMap.winDriverFront;
242                 vs.windowDriverRear = attributesMap.winDriverRear;
243                 vs.windowPassengerFront = attributesMap.winPassengerFront;
244                 vs.windowPassengerRear = attributesMap.winPassengerRear;
245                 vs.sunroof = attributesMap.sunroofState;
246
247                 vs.remainingFuel = attributesMap.remainingFuel;
248                 vs.remainingRangeElectric = attributesMap.beRemainingRangeElectricKm;
249                 vs.remainingRangeElectricMls = attributesMap.beRemainingRangeElectricMile;
250                 vs.remainingRangeFuel = attributesMap.beRemainingRangeFuelKm;
251                 vs.remainingRangeFuelMls = attributesMap.beRemainingRangeFuelMile;
252                 vs.remainingFuel = attributesMap.remainingFuel;
253                 vs.chargingLevelHv = attributesMap.chargingLevelHv;
254                 vs.maxRangeElectric = attributesMap.beMaxRangeElectric;
255                 vs.maxRangeElectricMls = attributesMap.beMaxRangeElectricMile;
256                 vs.chargingStatus = attributesMap.chargingHVStatus;
257                 vs.connectionStatus = attributesMap.connectorStatus;
258                 vs.lastChargingEndReason = attributesMap.lastChargingEndReason;
259
260                 vs.updateTime = attributesMap.updateTimeConverted;
261                 vs.updateReason = attributesMap.lastUpdateReason;
262
263                 Position p = new Position();
264                 p.lat = attributesMap.gpsLat;
265                 p.lon = attributesMap.gpsLon;
266                 p.heading = attributesMap.heading;
267                 vs.position = p;
268
269                 final List<CCMMessage> ccml = new ArrayList<CCMMessage>();
270                 if (vehicleMessages != null) {
271                     if (vehicleMessages.ccmMessages != null) {
272                         vehicleMessages.ccmMessages.forEach(entry -> {
273                             CCMMessage ccmM = new CCMMessage();
274                             ccmM.ccmDescriptionShort = entry.text;
275                             ccmM.ccmDescriptionLong = Constants.HYPHEN;
276                             ccmM.ccmMileage = entry.unitOfLengthRemaining;
277                             ccml.add(ccmM);
278                         });
279                     }
280                 }
281                 vs.checkControlMessages = ccml;
282
283                 final List<CBSMessage> cbsl = new ArrayList<CBSMessage>();
284                 if (vehicleMessages != null) {
285                     if (vehicleMessages.cbsMessages != null) {
286                         vehicleMessages.cbsMessages.forEach(entry -> {
287                             CBSMessage cbsm = new CBSMessage();
288                             cbsm.cbsType = entry.text;
289                             cbsm.cbsDescription = entry.description;
290                             cbsm.cbsDueDate = entry.date;
291                             cbsm.cbsRemainingMileage = entry.unitOfLengthRemaining;
292                             cbsl.add(cbsm);
293                         });
294                     }
295                 }
296                 vs.cbsData = cbsl;
297                 return Converter.getGson().toJson(vsc);
298             }
299         }
300         return Constants.EMPTY_JSON;
301     }
302 }