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.Instant;
16 import java.time.ZoneId;
17 import java.time.ZonedDateTime;
18 import java.time.format.DateTimeFormatter;
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.eclipse.jdt.annotation.Nullable;
22 import org.openhab.binding.mybmw.internal.dto.charge.Time;
23 import org.openhab.core.library.types.DateTimeType;
24 import org.openhab.core.library.types.StringType;
25 import org.openhab.core.types.State;
26 import org.openhab.core.types.UnDefType;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * The {@link Converter} Conversion Helpers
33 * @author Bernd Weymann - Initial contribution
34 * @author Martin Grassl - extract some methods to other classes
37 public interface Converter {
38 static final Logger LOGGER = LoggerFactory.getLogger(Converter.class);
40 static final String SPLIT_HYPHEN = "-";
41 static final String SPLIT_BRACKET = "\\(";
43 static State zonedToLocalDateTime(@Nullable String input, ZoneId timezone) {
44 if (input != null && !input.isEmpty()) {
46 String localTimeString = Instant.parse(input).atZone(timezone)
47 .format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
48 return DateTimeType.valueOf(localTimeString);
49 } catch (Exception e) {
50 LOGGER.debug("Unable to parse date {} - {}", input, e.getMessage());
51 return UnDefType.UNDEF;
54 return UnDefType.UNDEF;
59 * converts a string into a unified format
60 * - string is Capitalized
61 * - null is empty string
62 * - single character remains
67 static String toTitleCase(@Nullable String input) {
68 if (input == null || input.isEmpty()) {
69 return toTitleCase(Constants.UNDEF);
70 } else if (input.length() == 1) {
73 // first, replace all underscores with spaces and make it lower case
74 String lower = input.replaceAll(Constants.UNDERLINE, Constants.SPACE).toLowerCase();
77 String converted = toTitleCase(lower, Constants.SPACE);
78 converted = toTitleCase(converted, SPLIT_HYPHEN);
79 converted = toTitleCase(converted, SPLIT_BRACKET);
84 private static String toTitleCase(String input, String splitter) {
85 // first, split all parts by the splitting string
86 String[] arr = input.split(splitter);
88 StringBuilder sb = new StringBuilder();
89 for (int i = 0; i < arr.length; i++) {
91 sb.append(splitter.replaceAll("\\\\", Constants.EMPTY));
93 sb.append(Character.toUpperCase(arr[i].charAt(0))).append(arr[i].substring(1));
95 return sb.toString().trim();
99 * Measure distance between 2 coordinates
101 * @param sourceLatitude
102 * @param sourceLongitude
103 * @param destinationLatitude
104 * @param destinationLongitude
107 static double measureDistance(double sourceLatitude, double sourceLongitude, double destinationLatitude,
108 double destinationLongitude) {
109 double earthRadius = 6378.137; // Radius of earth in KM
110 double dLat = destinationLatitude * Math.PI / 180 - sourceLatitude * Math.PI / 180;
111 double dLon = destinationLongitude * Math.PI / 180 - sourceLongitude * Math.PI / 180;
112 double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(sourceLatitude * Math.PI / 180)
113 * Math.cos(destinationLatitude * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
114 double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
115 return earthRadius * c;
119 * Easy function but there's some measures behind:
120 * Guessing the range of the Vehicle on Map. If you can drive x kilometers with
121 * your Vehicle it's not feasible to
122 * project this x km Radius on Map. The roads to be taken are causing some
123 * overhead because they are not a straight
124 * line from Location A to B.
125 * I've taken some measurements to calculate the overhead factor based on Google
127 * Berlin - Dresden: Road Distance: 193 air-line Distance 167 = Factor 87%
128 * Kassel - Frankfurt: Road Distance: 199 air-line Distance 143 = Factor 72%
129 * After measuring more distances you'll find out that the outcome is between
132 * This depends also on the roads of a concrete route but this is only a guess
133 * without any Route Navigation behind
135 * In future it's foreseen to replace this with BMW RangeMap Service which isn't
136 * running at the moment.
139 * @return mapping from air-line distance to "real road" distance
141 static int guessRangeRadius(double range) {
142 return (int) (range * 0.8);
146 * checks if a string is a valid integer
151 static int parseIntegerString(String fullString) {
154 index = Integer.parseInt(fullString);
155 } catch (NumberFormatException nfe) {
160 static State getConnectionState(boolean connected) {
162 return StringType.valueOf(Constants.CONNECTED);
164 return StringType.valueOf(Constants.UNCONNECTED);
168 static String getCurrentISOTime() {
169 return ZonedDateTime.now().format(DateTimeFormatter.ISO_INSTANT);
172 static String getTime(Time t) {
173 StringBuffer time = new StringBuffer();
174 if (t.getHour() < 10) {
177 time.append(Integer.toString(t.getHour())).append(":");
178 if (t.getMinute() < 10) {
181 time.append(Integer.toString(t.getMinute()));
182 return time.toString();