]> git.basschouten.com Git - openhab-addons.git/blob
672a9bb836e13544484370da70790c0ab8904e02
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.mybmw.internal.utils;
14
15 import java.lang.reflect.Type;
16 import java.text.SimpleDateFormat;
17 import java.time.LocalTime;
18 import java.time.ZoneId;
19 import java.time.ZoneOffset;
20 import java.time.ZonedDateTime;
21 import java.time.format.DateTimeFormatter;
22 import java.util.ArrayList;
23 import java.util.Date;
24 import java.util.List;
25 import java.util.Locale;
26 import java.util.TimeZone;
27
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.mybmw.internal.MyBMWConstants;
31 import org.openhab.binding.mybmw.internal.dto.charge.Time;
32 import org.openhab.binding.mybmw.internal.dto.properties.Address;
33 import org.openhab.binding.mybmw.internal.dto.properties.Coordinates;
34 import org.openhab.binding.mybmw.internal.dto.properties.Distance;
35 import org.openhab.binding.mybmw.internal.dto.properties.Location;
36 import org.openhab.binding.mybmw.internal.dto.properties.Range;
37 import org.openhab.binding.mybmw.internal.dto.status.Mileage;
38 import org.openhab.binding.mybmw.internal.dto.vehicle.Vehicle;
39 import org.openhab.core.library.types.StringType;
40 import org.openhab.core.types.State;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import com.google.gson.Gson;
45 import com.google.gson.JsonArray;
46 import com.google.gson.JsonObject;
47 import com.google.gson.JsonParser;
48 import com.google.gson.JsonSyntaxException;
49 import com.google.gson.reflect.TypeToken;
50
51 /**
52  * The {@link Converter} Conversion Helpers
53  *
54  * @author Bernd Weymann - Initial contribution
55  */
56 @NonNullByDefault
57 public class Converter {
58     public static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
59
60     public static final String DATE_INPUT_PATTERN_STRING = "yyyy-MM-dd'T'HH:mm:ss";
61     public static final DateTimeFormatter DATE_INPUT_PATTERN = DateTimeFormatter.ofPattern(DATE_INPUT_PATTERN_STRING);
62     public static final DateTimeFormatter LOCALE_ENGLISH_TIMEFORMATTER = DateTimeFormatter.ofPattern("hh:mm a",
63             Locale.ENGLISH);
64     public static final SimpleDateFormat ISO_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS");
65
66     private static final Gson GSON = new Gson();
67     private static final Vehicle INVALID_VEHICLE = new Vehicle();
68     private static final String SPLIT_HYPHEN = "-";
69     private static final String SPLIT_BRACKET = "\\(";
70     private static final String VIN_PATTERN = "\"vin\":";
71     private static final String VEHICLE_LOCATION_PATTERN = "\"vehicleLocation\":";
72     private static final String VEHICLE_LOCATION_REPLACEMENT = "\"vehicleLocation\": {\"coordinates\": {\"latitude\": 1.1,\"longitude\": 2.2},\"address\": {\"formatted\": \"anonymous\"},\"heading\": -1}";
73     private static final char OPEN_BRACKET = "{".charAt(0);
74     private static final char CLOSING_BRACKET = "}".charAt(0);
75
76     // https://www.baeldung.com/gson-list
77     public static final Type VEHICLE_LIST_TYPE = new TypeToken<ArrayList<Vehicle>>() {
78     }.getType();
79     public static int offsetMinutes = -1;
80
81     public static String zonedToLocalDateTime(String input) {
82         try {
83             ZonedDateTime d = ZonedDateTime.parse(input).withZoneSameInstant(ZoneId.systemDefault());
84             return d.toLocalDateTime().format(Converter.DATE_INPUT_PATTERN);
85         } catch (Exception e) {
86             LOGGER.debug("Unable to parse date {} - {}", input, e.getMessage());
87         }
88         return input;
89     }
90
91     public static String toTitleCase(@Nullable String input) {
92         if (input == null) {
93             return toTitleCase(Constants.UNDEF);
94         } else if (input.length() == 1) {
95             return input;
96         } else {
97             String lower = input.replaceAll(Constants.UNDERLINE, Constants.SPACE).toLowerCase();
98             String converted = toTitleCase(lower, Constants.SPACE);
99             converted = toTitleCase(converted, SPLIT_HYPHEN);
100             converted = toTitleCase(converted, SPLIT_BRACKET);
101             return converted;
102         }
103     }
104
105     private static String toTitleCase(String input, String splitter) {
106         String[] arr = input.split(splitter);
107         StringBuilder sb = new StringBuilder();
108         for (int i = 0; i < arr.length; i++) {
109             if (i > 0) {
110                 sb.append(splitter.replaceAll("\\\\", Constants.EMPTY));
111             }
112             sb.append(Character.toUpperCase(arr[i].charAt(0))).append(arr[i].substring(1));
113         }
114         return sb.toString().trim();
115     }
116
117     public static String capitalizeFirst(String str) {
118         return str.substring(0, 1).toUpperCase() + str.substring(1);
119     }
120
121     public static Gson getGson() {
122         return GSON;
123     }
124
125     /**
126      * Measure distance between 2 coordinates
127      *
128      * @param sourceLatitude
129      * @param sourceLongitude
130      * @param destinationLatitude
131      * @param destinationLongitude
132      * @return distance
133      */
134     public static double measureDistance(double sourceLatitude, double sourceLongitude, double destinationLatitude,
135             double destinationLongitude) {
136         double earthRadius = 6378.137; // Radius of earth in KM
137         double dLat = destinationLatitude * Math.PI / 180 - sourceLatitude * Math.PI / 180;
138         double dLon = destinationLongitude * Math.PI / 180 - sourceLongitude * Math.PI / 180;
139         double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(sourceLatitude * Math.PI / 180)
140                 * Math.cos(destinationLatitude * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
141         double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
142         return earthRadius * c;
143     }
144
145     /**
146      * Easy function but there's some measures behind:
147      * Guessing the range of the Vehicle on Map. If you can drive x kilometers with your Vehicle it's not feasible to
148      * project this x km Radius on Map. The roads to be taken are causing some overhead because they are not a straight
149      * line from Location A to B.
150      * I've taken some measurements to calculate the overhead factor based on Google Maps
151      * Berlin - Dresden: Road Distance: 193 air-line Distance 167 = Factor 87%
152      * Kassel - Frankfurt: Road Distance: 199 air-line Distance 143 = Factor 72%
153      * After measuring more distances you'll find out that the outcome is between 70% and 90%. So
154      *
155      * This depends also on the roads of a concrete route but this is only a guess without any Route Navigation behind
156      *
157      * In future it's foreseen to replace this with BMW RangeMap Service which isn't running at the moment.
158      *
159      * @param range
160      * @return mapping from air-line distance to "real road" distance
161      */
162     public static int guessRangeRadius(double range) {
163         return (int) (range * 0.8);
164     }
165
166     public static int getIndex(String fullString) {
167         int index = -1;
168         try {
169             index = Integer.parseInt(fullString);
170         } catch (NumberFormatException nfe) {
171         }
172         return index;
173     }
174
175     /**
176      * Returns list of found vehicles
177      * In case of errors return empty list
178      *
179      * @param json
180      * @return
181      */
182     public static List<Vehicle> getVehicleList(String json) {
183         try {
184             List<Vehicle> l = GSON.fromJson(json, VEHICLE_LIST_TYPE);
185             if (l != null) {
186                 return l;
187             } else {
188                 return new ArrayList<Vehicle>();
189             }
190         } catch (JsonSyntaxException e) {
191             LOGGER.warn("JsonSyntaxException {}", e.getMessage());
192             return new ArrayList<Vehicle>();
193         }
194     }
195
196     public static Vehicle getVehicle(String vin, String json) {
197         List<Vehicle> l = getVehicleList(json);
198         for (Vehicle vehicle : l) {
199             if (vin.equals(vehicle.vin)) {
200                 // declare vehicle as valid
201                 vehicle.valid = true;
202                 return getConsistentVehcile(vehicle);
203             }
204         }
205         return INVALID_VEHICLE;
206     }
207
208     public static String getRawVehicleContent(String vin, String json) {
209         JsonArray jArr = JsonParser.parseString(json).getAsJsonArray();
210         for (int i = 0; i < jArr.size(); i++) {
211             JsonObject jo = jArr.get(i).getAsJsonObject();
212             String jsonVin = jo.getAsJsonPrimitive(MyBMWConstants.VIN).getAsString();
213             if (vin.equals(jsonVin)) {
214                 return jo.toString();
215             }
216         }
217         return Constants.EMPTY_JSON;
218     }
219
220     /**
221      * ensure basic data like mileage and location data are available every time
222      *
223      * @param v
224      * @return
225      */
226     public static Vehicle getConsistentVehcile(Vehicle v) {
227         if (v.status.currentMileage == null) {
228             v.status.currentMileage = new Mileage();
229             v.status.currentMileage.mileage = -1;
230             v.status.currentMileage.units = "km";
231         }
232         if (v.properties.combustionRange == null) {
233             v.properties.combustionRange = new Range();
234             v.properties.combustionRange.distance = new Distance();
235             v.properties.combustionRange.distance.value = -1;
236             v.properties.combustionRange.distance.units = Constants.EMPTY;
237         }
238         if (v.properties.vehicleLocation == null) {
239             v.properties.vehicleLocation = new Location();
240             v.properties.vehicleLocation.heading = Constants.INT_UNDEF;
241             v.properties.vehicleLocation.coordinates = new Coordinates();
242             v.properties.vehicleLocation.coordinates.latitude = Constants.INT_UNDEF;
243             v.properties.vehicleLocation.coordinates.longitude = Constants.INT_UNDEF;
244             v.properties.vehicleLocation.address = new Address();
245             v.properties.vehicleLocation.address.formatted = Constants.UNDEF;
246         }
247         return v;
248     }
249
250     public static State getLockState(boolean lock) {
251         if (lock) {
252             return StringType.valueOf(Constants.LOCKED);
253         } else {
254             return StringType.valueOf(Constants.UNLOCKED);
255         }
256     }
257
258     public static State getClosedState(boolean close) {
259         if (close) {
260             return StringType.valueOf(Constants.CLOSED);
261         } else {
262             return StringType.valueOf(Constants.OPEN);
263         }
264     }
265
266     public static State getConnectionState(boolean connected) {
267         if (connected) {
268             return StringType.valueOf(Constants.CONNECTED);
269         } else {
270             return StringType.valueOf(Constants.UNCONNECTED);
271         }
272     }
273
274     public static String getCurrentISOTime() {
275         Date date = new Date(System.currentTimeMillis());
276         synchronized (ISO_FORMATTER) {
277             ISO_FORMATTER.setTimeZone(TimeZone.getTimeZone("UTC"));
278             return ISO_FORMATTER.format(date);
279         }
280     }
281
282     public static String getTime(Time t) {
283         StringBuffer time = new StringBuffer();
284         if (t.hour < 10) {
285             time.append("0");
286         }
287         time.append(Integer.toString(t.hour)).append(":");
288         if (t.minute < 10) {
289             time.append("0");
290         }
291         time.append(Integer.toString(t.minute));
292         return time.toString();
293     }
294
295     public static int getOffsetMinutes() {
296         if (offsetMinutes == -1) {
297             ZoneOffset zo = ZonedDateTime.now().getOffset();
298             offsetMinutes = zo.getTotalSeconds() / 60;
299         }
300         return offsetMinutes;
301     }
302
303     public static int stringToInt(String intStr) {
304         int integer = Constants.INT_UNDEF;
305         try {
306             integer = Integer.parseInt(intStr);
307
308         } catch (Exception e) {
309             LOGGER.debug("Unable to convert range {} into int value", intStr);
310         }
311         return integer;
312     }
313
314     public static String getLocalTime(String chrageInfoLabel) {
315         String[] timeSplit = chrageInfoLabel.split(Constants.TILDE);
316         if (timeSplit.length == 2) {
317             try {
318                 LocalTime timeL = LocalTime.parse(timeSplit[1].trim(), LOCALE_ENGLISH_TIMEFORMATTER);
319                 return timeSplit[0] + Constants.TILDE + timeL.toString();
320             } catch (Exception e) {
321                 LOGGER.debug("Unable to parse date {} - {}", timeSplit[1], e.getMessage());
322             }
323         }
324         return chrageInfoLabel;
325     }
326
327     public static String anonymousFingerprint(String raw) {
328         String anonymousFingerprintString = raw;
329         int vinStartIndex = raw.indexOf(VIN_PATTERN);
330         if (vinStartIndex != -1) {
331             String[] arr = raw.substring(vinStartIndex + VIN_PATTERN.length()).trim().split("\"");
332             String vin = arr[1].trim();
333             anonymousFingerprintString = raw.replace(vin, "anonymous");
334         }
335
336         int locationStartIndex = raw.indexOf(VEHICLE_LOCATION_PATTERN);
337         int bracketCounter = -1;
338         if (locationStartIndex != -1) {
339             int endLocationIndex = 0;
340             for (int i = locationStartIndex; i < raw.length() && bracketCounter != 0; i++) {
341                 endLocationIndex = i;
342                 if (raw.charAt(i) == OPEN_BRACKET) {
343                     if (bracketCounter == -1) {
344                         // start point
345                         bracketCounter = 1;
346                     } else {
347                         bracketCounter++;
348                     }
349                 } else if (raw.charAt(i) == CLOSING_BRACKET) {
350                     bracketCounter--;
351                 }
352             }
353             String locationReplacement = raw.substring(locationStartIndex, endLocationIndex + 1);
354             anonymousFingerprintString = anonymousFingerprintString.replace(locationReplacement,
355                     VEHICLE_LOCATION_REPLACEMENT);
356         }
357         return anonymousFingerprintString;
358     }
359 }