2 * Copyright (c) 2010-2022 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.bmwconnecteddrive.internal.utils;
15 import static org.openhab.binding.bmwconnecteddrive.internal.utils.ChargeProfileWrapper.ProfileKey.*;
16 import static org.openhab.binding.bmwconnecteddrive.internal.utils.Constants.*;
18 import java.time.DayOfWeek;
19 import java.time.LocalTime;
20 import java.time.format.DateTimeParseException;
21 import java.util.ArrayList;
22 import java.util.EnumSet;
23 import java.util.HashMap;
25 import java.util.Optional;
28 import org.eclipse.jdt.annotation.NonNullByDefault;
29 import org.eclipse.jdt.annotation.Nullable;
30 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.ChargingMode;
31 import org.openhab.binding.bmwconnecteddrive.internal.ConnectedDriveConstants.ChargingPreference;
32 import org.openhab.binding.bmwconnecteddrive.internal.dto.charge.ChargeProfile;
33 import org.openhab.binding.bmwconnecteddrive.internal.dto.charge.ChargingWindow;
34 import org.openhab.binding.bmwconnecteddrive.internal.dto.charge.Timer;
35 import org.openhab.binding.bmwconnecteddrive.internal.dto.charge.WeeklyPlanner;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 import com.google.gson.JsonSyntaxException;
42 * The {@link ChargeProfileWrapper} Wrapper for ChargeProfiles
44 * @author Bernd Weymann - Initial contribution
45 * @author Norbert Truchsess - add ChargeProfileActions
48 public class ChargeProfileWrapper {
49 private static final Logger LOGGER = LoggerFactory.getLogger(ChargeProfileWrapper.class);
51 public enum ProfileType {
57 public enum ProfileKey {
68 protected final ProfileType type;
70 private Optional<ChargingMode> mode = Optional.empty();
71 private Optional<ChargingPreference> preference = Optional.empty();
73 private final Map<ProfileKey, Boolean> enabled = new HashMap<>();
74 private final Map<ProfileKey, LocalTime> times = new HashMap<>();
75 private final Map<ProfileKey, Set<DayOfWeek>> daysOfWeek = new HashMap<>();
77 public static Optional<ChargeProfileWrapper> fromJson(final String content) {
79 final ChargeProfile cp = Converter.getGson().fromJson(content, ChargeProfile.class);
81 return Optional.of(new ChargeProfileWrapper(cp));
83 } catch (JsonSyntaxException jse) {
84 LOGGER.debug("ChargeProfile unparsable: {}", content);
86 return Optional.empty();
89 private ChargeProfileWrapper(final ChargeProfile profile) {
90 final WeeklyPlanner planner;
92 if (profile.weeklyPlanner != null) {
93 type = ProfileType.WEEKLY;
94 planner = profile.weeklyPlanner;
95 } else if (profile.twoTimesTimer != null) {
96 type = ProfileType.TWO_TIMES;
97 planner = profile.twoTimesTimer;
98 // timer days not supported
100 type = ProfileType.EMPTY;
104 setPreference(planner.chargingPreferences);
105 setMode(planner.chargingMode);
107 setEnabled(CLIMATE, planner.climatizationEnabled);
109 addTimer(TIMER1, planner.timer1);
110 addTimer(TIMER2, planner.timer2);
112 if (planner.preferredChargingWindow != null) {
113 addTime(WINDOWSTART, planner.preferredChargingWindow.startTime);
114 addTime(WINDOWEND, planner.preferredChargingWindow.endTime);
116 preference.ifPresent(pref -> {
117 if (ChargingPreference.CHARGING_WINDOW.equals(pref)) {
118 addTime(WINDOWSTART, null);
119 addTime(WINDOWEND, null);
125 addTimer(TIMER3, planner.timer3);
126 addTimer(OVERRIDE, planner.overrideTimer);
130 public @Nullable Boolean isEnabled(final ProfileKey key) {
131 return enabled.get(key);
134 public void setEnabled(final ProfileKey key, @Nullable final Boolean enabled) {
135 if (enabled == null) {
136 this.enabled.remove(key);
138 this.enabled.put(key, enabled);
142 public @Nullable String getMode() {
143 return mode.map(m -> m.name()).orElse(null);
146 public void setMode(final @Nullable String mode) {
149 this.mode = Optional.of(ChargingMode.valueOf(mode));
151 } catch (IllegalArgumentException iae) {
152 LOGGER.warn("unexpected value for chargingMode: {}", mode);
155 this.mode = Optional.empty();
158 public @Nullable String getPreference() {
159 return preference.map(pref -> pref.name()).orElse(null);
162 public void setPreference(final @Nullable String preference) {
163 if (preference != null) {
165 this.preference = Optional.of(ChargingPreference.valueOf(preference));
167 } catch (IllegalArgumentException iae) {
168 LOGGER.warn("unexpected value for chargingPreference: {}", preference);
171 this.preference = Optional.empty();
174 public @Nullable Set<DayOfWeek> getDays(final ProfileKey key) {
175 return daysOfWeek.get(key);
178 public void setDays(final ProfileKey key, final @Nullable Set<DayOfWeek> days) {
180 daysOfWeek.remove(key);
182 daysOfWeek.put(key, days);
186 public void setDayEnabled(final ProfileKey key, final DayOfWeek day, final boolean enabled) {
187 final Set<DayOfWeek> days = daysOfWeek.get(key);
189 daysOfWeek.put(key, enabled ? EnumSet.of(day) : EnumSet.noneOf(DayOfWeek.class));
199 public @Nullable LocalTime getTime(final ProfileKey key) {
200 return times.get(key);
203 public void setTime(final ProfileKey key, @Nullable LocalTime time) {
207 times.put(key, time);
211 public String getJson() {
212 final ChargeProfile profile = new ChargeProfile();
213 final WeeklyPlanner planner = new WeeklyPlanner();
215 preference.ifPresent(pref -> planner.chargingPreferences = pref.name());
216 planner.climatizationEnabled = isEnabled(CLIMATE);
217 preference.ifPresent(pref -> {
218 if (ChargingPreference.CHARGING_WINDOW.equals(pref)) {
219 planner.chargingMode = getMode();
220 final LocalTime start = getTime(WINDOWSTART);
221 final LocalTime end = getTime(WINDOWEND);
222 if (start != null || end != null) {
223 planner.preferredChargingWindow = new ChargingWindow();
224 planner.preferredChargingWindow.startTime = start == null ? null : start.format(TIME_FORMATER);
225 planner.preferredChargingWindow.endTime = end == null ? null : end.format(TIME_FORMATER);
229 planner.timer1 = getTimer(TIMER1);
230 planner.timer2 = getTimer(TIMER2);
232 planner.timer3 = getTimer(TIMER3);
233 planner.overrideTimer = getTimer(OVERRIDE);
234 profile.weeklyPlanner = planner;
235 } else if (isTwoTimes()) {
236 profile.twoTimesTimer = planner;
238 return Converter.getGson().toJson(profile);
241 private void addTime(final ProfileKey key, @Nullable final String time) {
243 times.put(key, time == null ? NULL_LOCAL_TIME : LocalTime.parse(time, TIME_FORMATER));
244 } catch (DateTimeParseException dtpe) {
245 LOGGER.warn("unexpected value for {} time: {}", key.name(), time);
249 private void addTimer(final ProfileKey key, @Nullable final Timer timer) {
251 enabled.put(key, false);
254 daysOfWeek.put(key, EnumSet.noneOf(DayOfWeek.class));
257 enabled.put(key, timer.timerEnabled);
258 addTime(key, timer.departureTime);
260 final EnumSet<DayOfWeek> daySet = EnumSet.noneOf(DayOfWeek.class);
261 if (timer.weekdays != null) {
262 for (String day : timer.weekdays) {
264 daySet.add(DayOfWeek.valueOf(day));
265 } catch (IllegalArgumentException iae) {
266 LOGGER.warn("unexpected value for {} day: {}", key.name(), day);
270 daysOfWeek.put(key, daySet);
275 private @Nullable Timer getTimer(final ProfileKey key) {
276 final Timer timer = new Timer();
277 timer.timerEnabled = enabled.get(key);
278 final LocalTime time = times.get(key);
279 timer.departureTime = time == null ? null : time.format(TIME_FORMATER);
281 final Set<DayOfWeek> days = daysOfWeek.get(key);
283 timer.weekdays = new ArrayList<>();
284 for (DayOfWeek day : days) {
285 timer.weekdays.add(day.name());
289 return timer.timerEnabled == null && timer.departureTime == null && timer.weekdays == null ? null : timer;
292 private boolean isWeekly() {
293 return ProfileType.WEEKLY.equals(type);
296 private boolean isTwoTimes() {
297 return ProfileType.TWO_TIMES.equals(type);