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.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.Nullable;
24 import org.openhab.binding.km200.internal.KM200Device;
25 import org.openhab.binding.km200.internal.KM200ServiceObject;
26 import org.openhab.core.types.StateOption;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
30 import com.google.gson.JsonArray;
31 import com.google.gson.JsonObject;
34 * The KM200SwitchProgramService representing a switch program service with its all capabilities
36 * @author Markus Eckhardt - Initial contribution
37 * @implNote {@code @NonNullByDefault} is not working here because of the switchMap array handling
40 public class KM200SwitchProgramServiceHandler {
41 private final Logger logger = LoggerFactory.getLogger(KM200SwitchProgramServiceHandler.class);
43 private int maxNbOfSwitchPoints = 8;
44 private int maxNbOfSwitchPointsPerDay = 8;
45 private int switchPointTimeRaster = 10;
46 private String setpointProperty = "";
47 private String positiveSwitch = "";
48 private String negativeSwitch = "";
50 protected final Integer MIN_TIME = 0;
51 protected final Integer MAX_TIME = 1430;
52 protected static final String TYPE_MONDAY = "Mo";
53 protected static final String TYPE_TUESDAY = "Tu";
54 protected static final String TYPE_WEDNESDAY = "We";
55 protected static final String TYPE_THURSDAY = "Th";
56 protected static final String TYPE_FRIDAY = "Fr";
57 protected static final String TYPE_SATURDAY = "Sa";
58 protected static final String TYPE_SUNDAY = "Su";
60 private String activeDay = TYPE_MONDAY;
61 private Integer activeCycle = 1;
63 /* Night- and daylist for all weekdays */
64 public Map<String, Map<String, List<Integer>>> switchMap = new HashMap<>();
66 /* List with all days */
67 private static List<String> days = new ArrayList<>(Arrays.asList(TYPE_MONDAY, TYPE_TUESDAY, TYPE_WEDNESDAY,
68 TYPE_THURSDAY, TYPE_FRIDAY, TYPE_SATURDAY, TYPE_SUNDAY));
70 public static List<StateOption> daysList = List.of(new StateOption(TYPE_MONDAY, "Monday"),
71 new StateOption(TYPE_TUESDAY, "Tuesday"), new StateOption(TYPE_WEDNESDAY, "Wednesday"),
72 new StateOption(TYPE_THURSDAY, "Thursday"), new StateOption(TYPE_FRIDAY, "Friday"),
73 new StateOption(TYPE_SATURDAY, "Saturday"), new StateOption(TYPE_SUNDAY, "Sunday"));
75 /* List with setpoints */
76 private List<String> setpoints = new ArrayList<>();
79 * This function inits the week list
81 void initWeeklist(String setpoint) {
82 Map<String, List<Integer>> weekMap = switchMap.get(setpoint);
83 if (weekMap == null) {
84 weekMap = new HashMap<>();
85 for (String day : days) {
86 weekMap.put(day, new ArrayList<>());
88 switchMap.put(setpoint, weekMap);
93 * This function adds a switch to the switchmap
95 void addSwitch(String day, String setpoint, int time) {
96 logger.trace("Adding day: {} setpoint: {} time: {}", day, setpoint, time);
97 if (!days.contains(day)) {
98 logger.warn("This type of weekday is not supported, get day: {}", day);
99 throw new IllegalArgumentException("This type of weekday is not supported, get day: " + day);
101 if (!setpoints.contains(setpoint)) {
102 if (setpoints.size() == 2 && "on".compareTo(setpoint) == 0) {
103 if ("high".compareTo(setpoints.get(0)) == 0 && "off".compareTo(setpoints.get(1)) == 0) {
104 if (("on".compareTo(positiveSwitch) == 0 && "off".compareTo(negativeSwitch) == 0)
105 || ("off".compareTo(positiveSwitch) == 0 && "on".compareTo(negativeSwitch) == 0)) {
107 "!!! Wrong configuration on device. 'on' instead of 'high' in switch program. It seems that's a firmware problem-> ignoring it !!!");
109 throw new IllegalArgumentException(
110 "This type of setpoint is not supported, get setpoint: " + setpoint);
115 Map<String, List<Integer>> weekMap = switchMap.get(setpoint);
116 if (weekMap == null) {
117 initWeeklist(setpoint);
118 weekMap = switchMap.get(setpoint);
120 if (weekMap != null) {
121 List<Integer> dayList = weekMap.get(day);
122 if (dayList != null) {
124 Collections.sort(dayList);
130 * This function removes all switches from the switchmap
133 void removeAllSwitches() {
137 public void setMaxNbOfSwitchPoints(Integer nbr) {
138 maxNbOfSwitchPoints = nbr;
141 public void setMaxNbOfSwitchPointsPerDay(Integer nbr) {
142 maxNbOfSwitchPointsPerDay = nbr;
145 public void setSwitchPointTimeRaster(Integer raster) {
146 switchPointTimeRaster = raster;
149 public void setSetpointProperty(String property) {
150 setpointProperty = property;
154 * This function sets the day
156 public void setActiveDay(String day) {
157 if (!days.contains(day)) {
158 logger.warn("This type of weekday is not supported, get day: {}", day);
165 * This function sets the cycle
167 public void setActiveCycle(Integer cycle) {
168 if (cycle > this.getMaxNbOfSwitchPoints() / 2 || cycle > this.getMaxNbOfSwitchPointsPerDay() / 2 || cycle < 1) {
169 logger.warn("The value of cycle is not valid, get cycle: {}", cycle);
172 /* limit the cycle to the next one after last (for creating a new one) */
173 if (cycle > (getNbrCycles() + 1) || getNbrCycles() == 0) {
174 activeCycle = getNbrCycles() + 1;
181 * This function sets the positive switch to the selected day and cycle
183 public void setActivePositiveSwitch(Integer time) {
185 if (time < MIN_TIME) {
187 } else if (time > MAX_TIME) {
192 synchronized (switchMap) {
193 Map<String, List<Integer>> week = switchMap.get(getPositiveSwitch());
195 List<Integer> daysList = week.get(getActiveDay());
196 if (daysList != null) {
197 Integer actC = getActiveCycle();
198 Integer nbrC = getNbrCycles();
199 Integer nSwitch = null;
200 boolean newS = false;
205 if (switchMap.get(getNegativeSwitch()).get(getActiveDay()).size() < actC) {
208 nSwitch = switchMap.get(getNegativeSwitch()).get(getActiveDay()).get(actC - 1);
210 /* The positiv switch cannot be higher then the negative */
211 if (actTime > (nSwitch - getSwitchPointTimeRaster()) && nSwitch > 0) {
213 if (nSwitch < MAX_TIME) {
214 actTime -= getSwitchPointTimeRaster();
217 /* Check whether the time would overlap with the previous one */
219 Integer nPrevSwitch = switchMap.get(getNegativeSwitch()).get(getActiveDay()).get(actC - 2);
220 /* The positiv switch cannot be lower then the previous negative */
221 if (actTime < (nPrevSwitch + getSwitchPointTimeRaster())) {
222 actTime = nPrevSwitch + getSwitchPointTimeRaster();
226 daysList.add(actTime);
228 daysList.set(actC - 1, actTime);
237 * This function sets the negative switch to the selected day and cycle
239 public void setActiveNegativeSwitch(Integer time) {
241 if (time < MIN_TIME) {
243 } else if (time > MAX_TIME) {
248 synchronized (switchMap) {
249 Map<String, List<Integer>> week = switchMap.get(getNegativeSwitch());
251 List<Integer> daysList = week.get(getActiveDay());
252 if (daysList != null) {
253 Integer nbrC = getNbrCycles();
254 Integer actC = getActiveCycle();
255 Integer pSwitch = null;
256 boolean newS = false;
261 /* Check whether the positive switch is existing too */
262 if (switchMap.get(getPositiveSwitch()).get(getActiveDay()).size() < actC) {
263 /* No -> new Switch */
266 pSwitch = switchMap.get(getPositiveSwitch()).get(getActiveDay()).get(actC - 1);
268 /* The negative switch cannot be lower then the positive */
269 if (actTime < (pSwitch + getSwitchPointTimeRaster())) {
270 actTime = pSwitch + getSwitchPointTimeRaster();
272 /* Check whether the time would overlap with the next one */
274 Integer pNextSwitch = switchMap.get(getPositiveSwitch()).get(getActiveDay()).get(actC);
275 /* The negative switch cannot be higher then the next positive switch */
276 if (actTime > (pNextSwitch - getSwitchPointTimeRaster()) && pNextSwitch > 0) {
277 actTime = pNextSwitch - getSwitchPointTimeRaster();
281 daysList.add(actTime);
283 daysList.set(actC - 1, actTime);
292 * This function checks whether the actual cycle have to be removed (Both times set to MAX_TIME)
294 void checkRemovement() {
295 if (getActiveNegativeSwitch().equals(MAX_TIME) && getActivePositiveSwitch().equals(MAX_TIME)
296 && getNbrCycles() > 0) {
297 switchMap.get(getNegativeSwitch()).get(getActiveDay()).remove(getActiveCycle() - 1);
298 switchMap.get(getPositiveSwitch()).get(getActiveDay()).remove(getActiveCycle() - 1);
303 * This function determines the positive and negative switch point names
305 public boolean determineSwitchNames(KM200Device device) {
306 if (!setpointProperty.isEmpty()) {
307 KM200ServiceObject setpObject = device.getServiceObject(setpointProperty);
308 if (null != setpObject) {
309 if (setpObject.serviceTreeMap.keySet().isEmpty()) {
312 for (String key : setpObject.serviceTreeMap.keySet()) {
323 * This function updates objects the switching points
325 public void updateSwitches(JsonObject nodeRoot, KM200Device device) {
326 synchronized (switchMap) {
327 /* Update the list of switching points */
329 JsonArray sPoints = nodeRoot.get("switchPoints").getAsJsonArray();
330 logger.trace("sPoints: {}", nodeRoot);
331 if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
332 /* First start. Determine the positive and negative switching points */
333 if (sPoints.size() > 0) {
334 for (int i = 0; i < sPoints.size(); i++) {
335 JsonObject subJSON = sPoints.get(i).getAsJsonObject();
336 String setpoint = subJSON.get("setpoint").getAsString();
337 if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
338 positiveSwitch = setpoint;
339 negativeSwitch = setpoint;
341 negativeSwitch = setpoint;
343 if (!positiveSwitch.equals(negativeSwitch)) {
348 if (!setpointProperty.isEmpty()) {
349 BigDecimal firstVal = null;
350 KM200ServiceObject setpObject = device.getServiceObject(setpointProperty);
351 if (null != setpObject) {
352 logger.debug("No switch points set. Use alternative way. {}", nodeRoot);
353 for (String key : setpoints) {
354 if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
355 positiveSwitch = key;
356 negativeSwitch = key;
357 firstVal = (BigDecimal) setpObject.serviceTreeMap.get(key).getValue();
359 BigDecimal nextVal = (BigDecimal) setpObject.serviceTreeMap.get(key).getValue();
360 if (null != nextVal && null != firstVal) {
361 if (nextVal.compareTo(firstVal) > 0) {
362 positiveSwitch = key;
364 negativeSwitch = key;
368 if (!positiveSwitch.equalsIgnoreCase(negativeSwitch)) {
376 logger.debug("Positive switch: {}", positiveSwitch);
377 logger.debug("Negative switch: {}", negativeSwitch);
378 Map<String, List<Integer>> weekMap = null;
379 weekMap = switchMap.get(positiveSwitch);
380 if (weekMap == null) {
381 initWeeklist(positiveSwitch);
383 weekMap = switchMap.get(negativeSwitch);
384 if (weekMap == null) {
385 initWeeklist(negativeSwitch);
387 for (int i = 0; i < sPoints.size(); i++) {
388 JsonObject subJSON = sPoints.get(i).getAsJsonObject();
389 String day = subJSON.get("dayOfWeek").getAsString();
390 String setpoint = subJSON.get("setpoint").getAsString();
391 Integer time = subJSON.get("time").getAsInt();
392 addSwitch(day, setpoint, time);
398 * This function updates objects JSONData on the actual set switch points.
400 public @Nullable JsonObject getUpdatedJSONData(KM200ServiceObject parObject) {
401 synchronized (switchMap) {
402 boolean prepareNewOnly = false;
403 JsonArray sPoints = new JsonArray();
404 for (String day : days) {
405 if (switchMap.get(getPositiveSwitch()).containsKey(day)
406 && switchMap.get(getNegativeSwitch()).containsKey(day)) {
408 Integer minDays = Math.min(switchMap.get(getPositiveSwitch()).get(day).size(),
409 switchMap.get(getNegativeSwitch()).get(day).size());
410 for (j = 0; j < minDays; j++) {
411 JsonObject tmpObj = new JsonObject();
412 tmpObj.addProperty("dayOfWeek", day);
413 tmpObj.addProperty("setpoint", getPositiveSwitch());
414 tmpObj.addProperty("time", switchMap.get(getPositiveSwitch()).get(day).get(j));
416 tmpObj = new JsonObject();
417 tmpObj.addProperty("dayOfWeek", day);
418 tmpObj.addProperty("setpoint", getNegativeSwitch());
419 tmpObj.addProperty("time", switchMap.get(getNegativeSwitch()).get(day).get(j));
423 /* Check whether one object for a new cycle is already created */
424 if (switchMap.get(getPositiveSwitch()).get(day).size() > minDays) {
425 JsonObject tmpObj = new JsonObject();
426 tmpObj.addProperty("dayOfWeek", day);
427 tmpObj.addProperty("setpoint", getPositiveSwitch());
428 tmpObj.addProperty("time", switchMap.get(getPositiveSwitch()).get(day).get(j));
430 prepareNewOnly = true;
431 } else if (switchMap.get(getNegativeSwitch()).get(day).size() > minDays) {
432 JsonObject tmpObj = new JsonObject();
433 tmpObj.addProperty("dayOfWeek", day);
434 tmpObj.addProperty("setpoint", getNegativeSwitch());
435 tmpObj.addProperty("time", switchMap.get(getNegativeSwitch()).get(day).get(j));
437 prepareNewOnly = true;
441 logger.debug("New switching points: {}", sPoints);
442 JsonObject switchRoot = parObject.getJSONData();
443 if (null != switchRoot) {
444 switchRoot.remove("switchPoints");
445 switchRoot.add("switchPoints", sPoints);
446 parObject.setJSONData(switchRoot);
448 logger.debug("Jsojnoject switchRoot not found");
450 /* Preparation for are new cycle, don't sent it to the device */
451 if (prepareNewOnly) {
459 int getMaxNbOfSwitchPoints() {
460 return maxNbOfSwitchPoints;
463 int getMaxNbOfSwitchPointsPerDay() {
464 return maxNbOfSwitchPointsPerDay;
467 public int getSwitchPointTimeRaster() {
468 return switchPointTimeRaster;
471 public @Nullable String getSetpointProperty() {
472 return setpointProperty;
475 public @Nullable String getPositiveSwitch() {
476 return positiveSwitch;
479 public @Nullable String getNegativeSwitch() {
480 return negativeSwitch;
484 * This function returns the number of cycles
486 public Integer getNbrCycles() {
487 synchronized (switchMap) {
488 Map<String, List<Integer>> weekP = switchMap.get(getPositiveSwitch());
489 Map<String, List<Integer>> weekN = switchMap.get(getNegativeSwitch());
490 if (weekP != null && weekN != null) {
491 if (weekP.isEmpty() && weekN.isEmpty()) {
494 List<Integer> daysListP = weekP.get(getActiveDay());
495 List<Integer> daysListN = weekN.get(getActiveDay());
496 if (daysListP != null && daysListN != null) {
497 return Math.min(daysListP.size(), daysListN.size());
508 * This function returns the selected day
510 public String getActiveDay() {
515 * This function returns the selected cycle
517 public Integer getActiveCycle() {
522 * This function returns the positive switch to the selected day and cycle
524 public Integer getActivePositiveSwitch() {
525 synchronized (switchMap) {
526 Map<String, List<Integer>> week = switchMap.get(getPositiveSwitch());
528 List<Integer> daysList = week.get(getActiveDay());
529 if (daysList != null && !daysList.isEmpty()) {
530 Integer cycl = getActiveCycle();
531 if (cycl <= daysList.size()) {
532 return (daysList.get(getActiveCycle() - 1));
541 * This function returns the negative switch to the selected day and cycle
543 public Integer getActiveNegativeSwitch() {
544 synchronized (switchMap) {
545 Map<String, List<Integer>> week = switchMap.get(getNegativeSwitch());
547 List<Integer> daysList = week.get(getActiveDay());
548 if (daysList != null && !daysList.isEmpty()) {
549 Integer cycl = getActiveCycle();
550 if (cycl <= daysList.size()) {
551 return (daysList.get(getActiveCycle() - 1));