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.km200.internal.handler;
15 import java.math.BigDecimal;
16 import java.util.ArrayList;
17 import java.util.Arrays;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
23 import org.eclipse.jdt.annotation.NonNull;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.km200.internal.KM200Device;
26 import org.openhab.binding.km200.internal.KM200ServiceObject;
27 import org.openhab.core.types.StateOption;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
31 import com.google.gson.JsonArray;
32 import com.google.gson.JsonObject;
35 * The KM200SwitchProgramService representing a switch program service with its all capabilities
37 * @author Markus Eckhardt - Initial contribution
38 * @NonNullByDefault is not working here because of the switchMap array handling
41 public class KM200SwitchProgramServiceHandler {
42 private final Logger logger = LoggerFactory.getLogger(KM200SwitchProgramServiceHandler.class);
44 private int maxNbOfSwitchPoints = 8;
45 private int maxNbOfSwitchPointsPerDay = 8;
46 private int switchPointTimeRaster = 10;
47 private String setpointProperty = "";
48 private String positiveSwitch = "";
49 private String negativeSwitch = "";
51 protected final Integer MIN_TIME = 0;
52 protected final Integer MAX_TIME = 1430;
53 protected final static String TYPE_MONDAY = "Mo";
54 protected final static String TYPE_TUESDAY = "Tu";
55 protected final static String TYPE_WEDNESDAY = "We";
56 protected final static String TYPE_THURSDAY = "Th";
57 protected final static String TYPE_FRIDAY = "Fr";
58 protected final static String TYPE_SATURDAY = "Sa";
59 protected final static String TYPE_SUNDAY = "Su";
61 private String activeDay = TYPE_MONDAY;
62 private Integer activeCycle = 1;
64 /* Night- and daylist for all weekdays */
65 public Map<String, Map<String, List<Integer>>> switchMap = new HashMap<>();
67 /* List with all days */
68 private static List<String> days = new ArrayList<>(Arrays.asList(TYPE_MONDAY, TYPE_TUESDAY, TYPE_WEDNESDAY,
69 TYPE_THURSDAY, TYPE_FRIDAY, TYPE_SATURDAY, TYPE_SUNDAY));
71 public static List<@NonNull StateOption> daysList = new ArrayList<>(
72 Arrays.asList(new StateOption(TYPE_MONDAY, "Monday"), new StateOption(TYPE_TUESDAY, "Tuesday"),
73 new StateOption(TYPE_WEDNESDAY, "Wednesday"), new StateOption(TYPE_THURSDAY, "Thursday"),
74 new StateOption(TYPE_FRIDAY, "Friday"), new StateOption(TYPE_SATURDAY, "Saturday"),
75 new StateOption(TYPE_SUNDAY, "Sunday")));
77 /* List with setpoints */
78 private List<String> setpoints = new ArrayList<>();
81 * This function inits the week list
83 void initWeeklist(String setpoint) {
84 Map<String, List<Integer>> weekMap = switchMap.get(setpoint);
85 if (weekMap == null) {
86 weekMap = new HashMap<>();
87 for (String day : days) {
88 weekMap.put(day, new ArrayList<>());
90 switchMap.put(setpoint, weekMap);
95 * This function adds a switch to the switchmap
97 void addSwitch(String day, String setpoint, int time) {
98 logger.trace("Adding day: {} setpoint: {} time: {}", day, setpoint, time);
99 if (!days.contains(day)) {
100 logger.warn("This type of weekday is not supported, get day: {}", day);
101 throw new IllegalArgumentException("This type of weekday is not supported, get day: " + day);
103 if (!setpoints.contains(setpoint)) {
104 if (setpoints.size() == 2 && "on".compareTo(setpoint) == 0) {
105 if ("high".compareTo(setpoints.get(0)) == 0 && "off".compareTo(setpoints.get(1)) == 0) {
106 if ("on".compareTo(positiveSwitch) == 0 && "off".compareTo(negativeSwitch) == 0) {
108 "!!! Wrong configuration on device. 'on' instead of 'high' in switch program. It seems that's a firmware problem-> ignoring it !!!");
110 throw new IllegalArgumentException(
111 "This type of setpoint is not supported, get setpoint: " + setpoint);
116 Map<String, List<Integer>> weekMap = switchMap.get(setpoint);
117 if (weekMap == null) {
118 initWeeklist(setpoint);
119 weekMap = switchMap.get(setpoint);
121 List<Integer> dayList = weekMap.get(day);
123 Collections.sort(dayList);
127 * This function removes all switches from the switchmap
130 void removeAllSwitches() {
134 public void setMaxNbOfSwitchPoints(Integer nbr) {
135 maxNbOfSwitchPoints = nbr;
138 public void setMaxNbOfSwitchPointsPerDay(Integer nbr) {
139 maxNbOfSwitchPointsPerDay = nbr;
142 public void setSwitchPointTimeRaster(Integer raster) {
143 switchPointTimeRaster = raster;
146 public void setSetpointProperty(String property) {
147 setpointProperty = property;
151 * This function sets the day
153 public void setActiveDay(String day) {
154 if (!days.contains(day)) {
155 logger.warn("This type of weekday is not supported, get day: {}", day);
162 * This function sets the cycle
164 public void setActiveCycle(Integer cycle) {
165 if (cycle > this.getMaxNbOfSwitchPoints() / 2 || cycle > this.getMaxNbOfSwitchPointsPerDay() / 2 || cycle < 1) {
166 logger.warn("The value of cycle is not valid, get cycle: {}", cycle);
169 /* limit the cycle to the next one after last (for creating a new one) */
170 if (cycle > (getNbrCycles() + 1) || getNbrCycles() == 0) {
171 activeCycle = getNbrCycles() + 1;
178 * This function sets the positive switch to the selected day and cycle
180 public void setActivePositiveSwitch(Integer time) {
182 if (time < MIN_TIME) {
184 } else if (time > MAX_TIME) {
189 synchronized (switchMap) {
190 Map<String, List<Integer>> week = switchMap.get(getPositiveSwitch());
192 List<Integer> daysList = week.get(getActiveDay());
193 if (daysList != null) {
194 Integer actC = getActiveCycle();
195 Integer nbrC = getNbrCycles();
196 Integer nSwitch = null;
197 boolean newS = false;
202 if (switchMap.get(getNegativeSwitch()).get(getActiveDay()).size() < actC) {
205 nSwitch = switchMap.get(getNegativeSwitch()).get(getActiveDay()).get(actC - 1);
207 /* The positiv switch cannot be higher then the negative */
208 if (actTime > (nSwitch - getSwitchPointTimeRaster()) && nSwitch > 0) {
210 if (nSwitch < MAX_TIME) {
211 actTime -= getSwitchPointTimeRaster();
214 /* Check whether the time would overlap with the previous one */
216 Integer nPrevSwitch = switchMap.get(getNegativeSwitch()).get(getActiveDay()).get(actC - 2);
217 /* The positiv switch cannot be lower then the previous negative */
218 if (actTime < (nPrevSwitch + getSwitchPointTimeRaster())) {
219 actTime = nPrevSwitch + getSwitchPointTimeRaster();
223 daysList.add(actTime);
225 daysList.set(actC - 1, actTime);
234 * This function sets the negative switch to the selected day and cycle
236 public void setActiveNegativeSwitch(Integer time) {
238 if (time < MIN_TIME) {
240 } else if (time > MAX_TIME) {
245 synchronized (switchMap) {
246 Map<String, List<Integer>> week = switchMap.get(getNegativeSwitch());
248 List<Integer> daysList = week.get(getActiveDay());
249 if (daysList != null) {
250 Integer nbrC = getNbrCycles();
251 Integer actC = getActiveCycle();
252 Integer pSwitch = null;
253 boolean newS = false;
258 /* Check whether the positive switch is existing too */
259 if (switchMap.get(getPositiveSwitch()).get(getActiveDay()).size() < actC) {
260 /* No -> new Switch */
263 pSwitch = switchMap.get(getPositiveSwitch()).get(getActiveDay()).get(actC - 1);
265 /* The negative switch cannot be lower then the positive */
266 if (actTime < (pSwitch + getSwitchPointTimeRaster())) {
267 actTime = pSwitch + getSwitchPointTimeRaster();
269 /* Check whether the time would overlap with the next one */
271 Integer pNextSwitch = switchMap.get(getPositiveSwitch()).get(getActiveDay()).get(actC);
272 /* The negative switch cannot be higher then the next positive switch */
273 if (actTime > (pNextSwitch - getSwitchPointTimeRaster()) && pNextSwitch > 0) {
274 actTime = pNextSwitch - getSwitchPointTimeRaster();
278 daysList.add(actTime);
280 daysList.set(actC - 1, actTime);
289 * This function checks whether the actual cycle have to be removed (Both times set to MAX_TIME)
291 void checkRemovement() {
292 if (getActiveNegativeSwitch().equals(MAX_TIME) && getActivePositiveSwitch().equals(MAX_TIME)
293 && getNbrCycles() > 0) {
294 switchMap.get(getNegativeSwitch()).get(getActiveDay()).remove(getActiveCycle() - 1);
295 switchMap.get(getPositiveSwitch()).get(getActiveDay()).remove(getActiveCycle() - 1);
300 * This function determines the positive and negative switch point names
302 public boolean determineSwitchNames(KM200Device device) {
303 if (!setpointProperty.isEmpty()) {
304 KM200ServiceObject setpObject = device.getServiceObject(setpointProperty);
305 if (null != setpObject) {
306 if (setpObject.serviceTreeMap.keySet().isEmpty()) {
309 for (String key : setpObject.serviceTreeMap.keySet()) {
320 * This function updates objects the switching points
322 public void updateSwitches(JsonObject nodeRoot, KM200Device device) {
323 synchronized (switchMap) {
324 /* Update the list of switching points */
326 JsonArray sPoints = nodeRoot.get("switchPoints").getAsJsonArray();
327 logger.trace("sPoints: {}", nodeRoot);
328 if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
329 /* First start. Determine the positive and negative switching points */
330 if (sPoints.size() > 0) {
331 for (int i = 0; i < sPoints.size(); i++) {
332 JsonObject subJSON = sPoints.get(i).getAsJsonObject();
333 String setpoint = subJSON.get("setpoint").getAsString();
334 if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
335 positiveSwitch = setpoint;
336 negativeSwitch = setpoint;
338 negativeSwitch = setpoint;
340 if (!positiveSwitch.equals(negativeSwitch)) {
345 if (!setpointProperty.isEmpty()) {
346 BigDecimal firstVal = null;
347 KM200ServiceObject setpObject = device.getServiceObject(setpointProperty);
348 if (null != setpObject) {
349 logger.debug("No switch points set. Use alternative way. {}", nodeRoot);
350 for (String key : setpoints) {
351 if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
352 positiveSwitch = key;
353 negativeSwitch = key;
354 firstVal = (BigDecimal) setpObject.serviceTreeMap.get(key).getValue();
356 BigDecimal nextVal = (BigDecimal) setpObject.serviceTreeMap.get(key).getValue();
357 if (null != nextVal && null != firstVal) {
358 if (nextVal.compareTo(firstVal) > 0) {
359 positiveSwitch = key;
361 negativeSwitch = key;
365 if (!positiveSwitch.equalsIgnoreCase(negativeSwitch)) {
373 logger.debug("Positive switch: {}", positiveSwitch);
374 logger.debug("Negative switch: {}", negativeSwitch);
375 Map<String, List<Integer>> weekMap = null;
376 weekMap = switchMap.get(positiveSwitch);
377 if (weekMap == null) {
378 initWeeklist(positiveSwitch);
380 weekMap = switchMap.get(negativeSwitch);
381 if (weekMap == null) {
382 initWeeklist(negativeSwitch);
384 for (int i = 0; i < sPoints.size(); i++) {
385 JsonObject subJSON = sPoints.get(i).getAsJsonObject();
386 String day = subJSON.get("dayOfWeek").getAsString();
387 String setpoint = subJSON.get("setpoint").getAsString();
388 Integer time = subJSON.get("time").getAsInt();
389 addSwitch(day, setpoint, time);
395 * This function updates objects JSONData on the actual set switch points.
397 public @Nullable JsonObject getUpdatedJSONData(KM200ServiceObject parObject) {
398 synchronized (switchMap) {
399 boolean prepareNewOnly = false;
400 JsonArray sPoints = new JsonArray();
401 for (String day : days) {
402 if (switchMap.get(getPositiveSwitch()).containsKey(day)
403 && switchMap.get(getNegativeSwitch()).containsKey(day)) {
405 Integer minDays = Math.min(switchMap.get(getPositiveSwitch()).get(day).size(),
406 switchMap.get(getNegativeSwitch()).get(day).size());
407 for (j = 0; j < minDays; j++) {
408 JsonObject tmpObj = new JsonObject();
409 tmpObj.addProperty("dayOfWeek", day);
410 tmpObj.addProperty("setpoint", getPositiveSwitch());
411 tmpObj.addProperty("time", switchMap.get(getPositiveSwitch()).get(day).get(j));
413 tmpObj = new JsonObject();
414 tmpObj.addProperty("dayOfWeek", day);
415 tmpObj.addProperty("setpoint", getNegativeSwitch());
416 tmpObj.addProperty("time", switchMap.get(getNegativeSwitch()).get(day).get(j));
420 /* Check whether one object for a new cycle is already created */
421 if (switchMap.get(getPositiveSwitch()).get(day).size() > minDays) {
422 JsonObject tmpObj = new JsonObject();
423 tmpObj.addProperty("dayOfWeek", day);
424 tmpObj.addProperty("setpoint", getPositiveSwitch());
425 tmpObj.addProperty("time", switchMap.get(getPositiveSwitch()).get(day).get(j));
427 prepareNewOnly = true;
428 } else if (switchMap.get(getNegativeSwitch()).get(day).size() > minDays) {
429 JsonObject tmpObj = new JsonObject();
430 tmpObj.addProperty("dayOfWeek", day);
431 tmpObj.addProperty("setpoint", getNegativeSwitch());
432 tmpObj.addProperty("time", switchMap.get(getNegativeSwitch()).get(day).get(j));
434 prepareNewOnly = true;
438 logger.debug("New switching points: {}", sPoints);
439 JsonObject switchRoot = parObject.getJSONData();
440 if (null != switchRoot) {
441 switchRoot.remove("switchPoints");
442 switchRoot.add("switchPoints", sPoints);
443 parObject.setJSONData(switchRoot);
445 logger.debug("Jsojnoject switchRoot not found");
447 /* Preparation for are new cycle, don't sent it to the device */
448 if (prepareNewOnly) {
456 int getMaxNbOfSwitchPoints() {
457 return maxNbOfSwitchPoints;
460 int getMaxNbOfSwitchPointsPerDay() {
461 return maxNbOfSwitchPointsPerDay;
464 public int getSwitchPointTimeRaster() {
465 return switchPointTimeRaster;
468 public @Nullable String getSetpointProperty() {
469 return setpointProperty;
472 public @Nullable String getPositiveSwitch() {
473 return positiveSwitch;
476 public @Nullable String getNegativeSwitch() {
477 return negativeSwitch;
481 * This function returns the number of cycles
483 public Integer getNbrCycles() {
484 synchronized (switchMap) {
485 Map<String, List<Integer>> weekP = switchMap.get(getPositiveSwitch());
486 Map<String, List<Integer>> weekN = switchMap.get(getNegativeSwitch());
487 if (weekP.isEmpty() && weekN.isEmpty()) {
490 List<Integer> daysListP = weekP.get(getActiveDay());
491 List<Integer> daysListN = weekN.get(getActiveDay());
492 return Math.min(daysListP.size(), daysListN.size());
497 * This function returns the selected day
499 public String getActiveDay() {
504 * This function returns the selected cycle
506 public Integer getActiveCycle() {
511 * This function returns the positive switch to the selected day and cycle
513 public Integer getActivePositiveSwitch() {
514 synchronized (switchMap) {
515 Map<String, List<Integer>> week = switchMap.get(getPositiveSwitch());
517 List<Integer> daysList = week.get(getActiveDay());
518 if (!daysList.isEmpty()) {
519 Integer cycl = getActiveCycle();
520 if (cycl <= daysList.size()) {
521 return (daysList.get(getActiveCycle() - 1));
530 * This function returns the negative switch to the selected day and cycle
532 public Integer getActiveNegativeSwitch() {
533 synchronized (switchMap) {
534 Map<String, List<Integer>> week = switchMap.get(getNegativeSwitch());
536 List<Integer> daysList = week.get(getActiveDay());
537 if (!daysList.isEmpty()) {
538 Integer cycl = getActiveCycle();
539 if (cycl <= daysList.size()) {
540 return (daysList.get(getActiveCycle() - 1));