2 * Copyright (c) 2010-2020 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 static org.openhab.binding.km200.internal.KM200BindingConstants.*;
17 import java.math.BigDecimal;
18 import java.math.RoundingMode;
19 import java.time.ZonedDateTime;
20 import java.util.HashMap;
21 import java.util.List;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.km200.internal.KM200Device;
27 import org.openhab.binding.km200.internal.KM200ServiceObject;
28 import org.openhab.core.library.CoreItemFactory;
29 import org.openhab.core.library.types.DateTimeType;
30 import org.openhab.core.library.types.DecimalType;
31 import org.openhab.core.library.types.OnOffType;
32 import org.openhab.core.library.types.StringType;
33 import org.openhab.core.types.Command;
34 import org.openhab.core.types.State;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
38 import com.google.gson.JsonArray;
39 import com.google.gson.JsonObject;
40 import com.google.gson.JsonParseException;
41 import com.google.gson.JsonParser;
44 * The KM200DataHandler is managing the data handling between the device and items
46 * @author Markus Eckhardt - Initial contribution
49 public class KM200DataHandler {
50 private final Logger logger = LoggerFactory.getLogger(KM200DataHandler.class);
51 private final JsonParser jsonParser = new JsonParser();
53 private final KM200Device remoteDevice;
55 public KM200DataHandler(KM200Device remoteDevice) {
56 this.remoteDevice = remoteDevice;
60 * This function checks the state of a service on the device
62 public @Nullable State getProvidersState(String service, String itemType, Map<String, String> itemPara) {
63 synchronized (remoteDevice) {
65 KM200ServiceObject object = null;
66 JsonObject jsonNode = null;
68 logger.trace("Check state of: {} item: {}", service, itemType);
69 if (remoteDevice.getBlacklistMap().contains(service)) {
70 logger.warn("Service on blacklist: {}", service);
73 if (remoteDevice.containsService(service)) {
74 object = remoteDevice.getServiceObject(service);
76 logger.warn("Serviceobject does not exist");
79 if (object.getReadable() == 0) {
80 logger.warn("Service is listed as protected (reading is not possible): {}", service);
83 type = object.getServiceType();
85 logger.warn("Service is not in the determined device service list: {}", service);
88 /* Needs to be updated? */
89 if (object.getVirtual() == 0) {
90 if (!object.getUpdated()) {
91 jsonNode = remoteDevice.getServiceNode(service);
92 if (jsonNode == null || jsonNode.isJsonNull()) {
93 logger.warn("Communication is not possible!");
96 object.setJSONData(jsonNode);
97 object.setUpdated(true);
99 /* If already updated then use the saved data */
100 jsonNode = object.getJSONData();
103 /* For using of virtual services only one receive on the parent service is needed */
104 String parent = object.getParent();
105 if (null != parent) {
106 KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
107 if (null != objParent) {
108 if (!objParent.getUpdated()) {
109 /* If it's a virtual service then receive the data from parent service */
110 jsonNode = remoteDevice.getServiceNode(parent);
111 if (jsonNode == null || jsonNode.isJsonNull()) {
112 logger.warn("Communication is not possible!");
115 objParent.setJSONData(jsonNode);
116 objParent.setUpdated(true);
117 object.setUpdated(true);
119 /* If already updated then use the saved data */
120 jsonNode = objParent.getJSONData();
125 if (null != jsonNode) {
126 return parseJSONData(jsonNode, type, service, itemType, itemPara);
134 * This function parses the receviced JSON Data and return the right state
136 public @Nullable State parseJSONData(JsonObject nodeRoot, String type, String service, String itemType,
137 Map<String, String> itemPara) {
139 KM200ServiceObject object = remoteDevice.getServiceObject(service);
140 if (null == object) {
143 String parent = object.getParent();
145 logger.trace("parseJSONData service: {}, data: {}", service, nodeRoot);
146 /* Now parsing of the JSON String depending on its type and the type of binding item */
148 if (nodeRoot.toString().length() == 2) {
149 logger.warn("Get empty reply");
153 case DATA_TYPE_STRING_VALUE: /* Check whether the type is a single value containing a string value */
154 logger.debug("parseJSONData type string value: {} Type: {}", nodeRoot, itemType.toString());
155 String sVal = nodeRoot.get("value").getAsString();
156 object.setValue(sVal);
158 if ("Switch".equals(itemType)) {
159 // type is definitely correct here
160 Map<String, String> switchNames = itemPara;
161 if (switchNames.containsKey("on")) {
162 if (sVal.equals(switchNames.get("off"))) {
163 state = OnOffType.OFF;
164 } else if (sVal.equals(switchNames.get("on"))) {
165 state = OnOffType.ON;
167 } else if (switchNames.isEmpty()) {
168 logger.debug("No switch item configuration");
171 logger.warn("Switch-Item only on configured on/off string values: {}", nodeRoot);
174 /* NumberItem Binding */
175 } else if (CoreItemFactory.NUMBER.equals(itemType)) {
177 state = new DecimalType(Float.parseFloat(sVal));
178 } catch (NumberFormatException e) {
179 logger.warn("Conversion of the string value to Decimal wasn't possible, data: {} error: {}",
180 nodeRoot, e.getMessage());
183 /* DateTimeItem Binding */
184 } else if (CoreItemFactory.DATETIME.equals(itemType)) {
186 state = new DateTimeType(sVal);
187 } catch (IllegalArgumentException e) {
189 "Conversion of the string value to DateTime wasn't possible, data: {} error: {}",
190 nodeRoot, e.getMessage());
193 /* StringItem Binding */
194 } else if (CoreItemFactory.STRING.equals(itemType)) {
195 state = new StringType(sVal);
197 logger.info("Bindingtype not supported for string values: {}", itemType.getClass());
201 case DATA_TYPE_FLOAT_VALUE: /* Check whether the type is a single value containing a float value */
202 logger.trace("state of type float value: {}", nodeRoot);
205 bdVal = new BigDecimal(nodeRoot.get("value").getAsString()).setScale(1, RoundingMode.HALF_UP);
206 } catch (NumberFormatException e) {
209 object.setValue(bdVal);
210 /* NumberItem Binding */
211 if (CoreItemFactory.NUMBER.equals(itemType)) {
212 if (bdVal instanceof Double) { // Checking whether
213 state = new DecimalType((Double) bdVal);
215 state = new DecimalType(((Number) bdVal).doubleValue());
217 /* StringItem Binding */
218 } else if (CoreItemFactory.STRING.equals(itemType)) {
219 state = new StringType(bdVal.toString());
221 logger.info("Bindingtype not supported for float values: {}", itemType.getClass());
225 case DATA_TYPE_SWITCH_PROGRAM: /* Check whether the type is a switchProgram */
226 KM200SwitchProgramServiceHandler sPService = null;
227 logger.trace("state of type switchProgram: {}", nodeRoot);
228 if (null != parent) {
229 KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
230 if (null != objParent) {
231 /* Get the KM200SwitchProgramService class object with all specific parameters */
232 if (object.getVirtual() == 0) {
233 sPService = ((KM200SwitchProgramServiceHandler) object.getValueParameter());
235 sPService = ((KM200SwitchProgramServiceHandler) objParent.getValueParameter());
237 if (null != sPService) {
238 /* Update the switches inside the KM200SwitchProgramService */
239 sPService.updateSwitches(nodeRoot, remoteDevice);
241 /* the parsing of switch program-services have to be outside, using json in strings */
242 if (object.getVirtual() == 1) {
243 return this.getVirtualState(object, itemType, service);
246 * if access to the parent non virtual service the return the switchPoints jsonarray
248 if (CoreItemFactory.STRING.equals(itemType)) {
249 state = new StringType(
250 nodeRoot.get("switchPoints").getAsJsonArray().toString());
253 "Bindingtype not supported for switchProgram, only json over strings supported: {}",
254 itemType.getClass());
263 case DATA_TYPE_ERROR_LIST: /* Check whether the type is a errorList */
264 KM200ErrorServiceHandler eService = null;
265 logger.trace("state of type errorList: {}", nodeRoot);
266 if (null != parent) {
267 KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
268 if (null != objParent) {
269 /* Get the KM200ErrorService class object with all specific parameters */
270 if (object.getVirtual() == 0) {
271 eService = ((KM200ErrorServiceHandler) object.getValueParameter());
273 eService = ((KM200ErrorServiceHandler) objParent.getValueParameter());
275 if (null != eService) {
276 /* Update the switches inside the KM200SwitchProgramService */
277 eService.updateErrors(nodeRoot);
279 /* the parsing of switch program-services have to be outside, using json in strings */
280 if (object.getVirtual() == 1) {
281 return this.getVirtualState(object, itemType, service);
284 * if access to the parent non virtual service the return the switchPoints jsonarray
286 if (CoreItemFactory.STRING.equals(itemType)) {
287 state = new StringType(nodeRoot.get("values").getAsJsonArray().toString());
290 "Bindingtype not supported for error list, only json over strings is supported: {}",
291 itemType.getClass());
298 case DATA_TYPE_Y_RECORDING: /* Check whether the type is a yRecording */
299 logger.info("state of: type yRecording is not supported yet: {}", nodeRoot);
300 /* have to be completed */
302 case DATA_TYPE_SYSTEM_INFO: /* Check whether the type is a systeminfo */
303 logger.info("state of: type systeminfo is not supported yet: {}", nodeRoot);
304 /* have to be completed */
306 case DATA_TYPE_ARRAY_DATA: /* Check whether the type is a arrayData */
307 logger.info("state of: type arrayData is not supported yet: {}", nodeRoot);
308 /* have to be completed */
310 case DATA_TYPE_E_MONITORING_LIST: /* Check whether the type is a eMonitoringList */
311 logger.info("state of: type eMonitoringList is not supported yet: {}", nodeRoot);
312 /* have to be completed */
315 } catch (JsonParseException e) {
316 logger.warn("Parsingexception in JSON, data: {} error: {} ", nodeRoot, e.getMessage());
322 * This function checks the virtual state of a service
324 private @Nullable State getVirtualState(KM200ServiceObject object, String itemType, String service) {
326 String type = object.getServiceType();
327 String parent = object.getParent();
329 if (null != parent) {
330 KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
331 if (null != objParent) {
332 logger.trace("Check virtual state of: {} type: {} item: {}", service, type, itemType);
334 case DATA_TYPE_SWITCH_PROGRAM:
335 KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) objParent
336 .getValueParameter());
337 if (null != sPService) {
338 String[] servicePath = service.split("/");
339 String virtService = servicePath[servicePath.length - 1];
340 if ("weekday".equals(virtService)) {
341 if (CoreItemFactory.STRING.equals(itemType)) {
342 String actDay = sPService.getActiveDay();
343 state = new StringType(actDay);
345 logger.info("Bindingtype not supported for day service: {}", itemType.getClass());
348 } else if ("nbrCycles".equals(virtService)) {
349 if (CoreItemFactory.NUMBER.equals(itemType)) {
350 Integer nbrCycles = sPService.getNbrCycles();
351 state = new DecimalType(nbrCycles);
353 logger.info("Bindingtype not supported for nbrCycles service: {}",
354 itemType.getClass());
357 } else if ("cycle".equals(virtService)) {
358 if (CoreItemFactory.NUMBER.equals(itemType)) {
359 Integer cycle = sPService.getActiveCycle();
360 state = new DecimalType(cycle);
362 logger.info("Bindingtype not supported for cycle service: {}", itemType.getClass());
365 } else if (virtService.equals(sPService.getPositiveSwitch())) {
366 if (CoreItemFactory.NUMBER.equals(itemType)) {
367 Integer minutes = sPService.getActivePositiveSwitch();
368 state = new DecimalType(minutes);
369 } else if (CoreItemFactory.DATETIME.equals(itemType)) {
370 Integer minutes = sPService.getActivePositiveSwitch();
371 ZonedDateTime rightNow = ZonedDateTime.now();
372 rightNow.minusHours(rightNow.getHour());
373 rightNow.minusMinutes(rightNow.getMinute());
374 rightNow.plusSeconds(minutes * 60 - rightNow.getOffset().getTotalSeconds());
375 state = new DateTimeType(rightNow);
377 logger.info("Bindingtype not supported for cycle service: {}", itemType);
380 } else if (virtService.equals(sPService.getNegativeSwitch())) {
381 if (CoreItemFactory.NUMBER.equals(itemType)) {
382 Integer minutes = sPService.getActiveNegativeSwitch();
383 state = new DecimalType(minutes);
384 } else if (CoreItemFactory.DATETIME.equals(itemType)) {
385 Integer minutes = sPService.getActiveNegativeSwitch();
386 ZonedDateTime rightNow = ZonedDateTime.now();
387 rightNow.minusHours(rightNow.getHour());
388 rightNow.minusMinutes(rightNow.getMinute());
389 rightNow.plusSeconds(minutes * 60 - rightNow.getOffset().getTotalSeconds());
390 state = new DateTimeType(rightNow);
392 logger.info("Bindingtype not supported for cycle service: {}", itemType.getClass());
400 case DATA_TYPE_ERROR_LIST:
401 KM200ErrorServiceHandler eService = ((KM200ErrorServiceHandler) objParent.getValueParameter());
402 if (null != eService) {
403 String[] nServicePath = service.split("/");
404 String nVirtService = nServicePath[nServicePath.length - 1];
405 /* Go through the parameters and read the values */
406 switch (nVirtService) {
408 if (CoreItemFactory.NUMBER.equals(itemType)) {
409 Integer nbrErrors = eService.getNbrErrors();
410 state = new DecimalType(nbrErrors);
412 logger.info("Bindingtype not supported for error number service: {}",
413 itemType.getClass());
418 if (CoreItemFactory.NUMBER.equals(itemType)) {
419 Integer actError = eService.getActiveError();
420 state = new DecimalType(actError);
422 logger.info("Bindingtype not supported for error service: {}",
423 itemType.getClass());
428 if (CoreItemFactory.STRING.equals(itemType)) {
429 String errorString = eService.getErrorString();
430 if (errorString == null) {
433 state = new StringType(errorString);
435 logger.info("Bindingtype not supported for error string service: {}",
436 itemType.getClass());
452 * This function sets the state of a service on the device
454 public @Nullable JsonObject sendProvidersState(String service, Command command, String itemType, Object itemPara) {
455 synchronized (remoteDevice) {
457 KM200ServiceObject object;
458 JsonObject newObject;
460 logger.debug("Prepare item for send: {} zitem: {}", service, itemType);
461 if (remoteDevice.getBlacklistMap().contains(service)) {
462 logger.debug("Service on blacklist: {}", service);
465 if (remoteDevice.containsService(service)) {
466 object = remoteDevice.getServiceObject(service);
467 if (null == object) {
468 logger.debug("Object is null");
471 if (object.getWriteable() == 0) {
472 logger.warn("Service is listed as read-only: {}", service);
475 type = object.getServiceType();
477 logger.warn("Service is not in the determined device service list: {}", service);
480 /* The service is availible, set now the values depeding on the item and binding type */
481 logger.trace("state of: {} type: {}", command, type);
482 /* Binding is a NumberItem */
483 if (CoreItemFactory.NUMBER.equals(itemType)) {
484 BigDecimal bdVal = ((DecimalType) command).toBigDecimal();
485 /* Check the capabilities of this service */
486 if (object.getValueParameter() != null) {
487 // type is definitely correct here
488 @SuppressWarnings("unchecked")
489 List<BigDecimal> valParas = (List<BigDecimal>) object.getValueParameter();
490 if (null != valParas) {
491 BigDecimal minVal = valParas.get(0);
492 BigDecimal maxVal = valParas.get(1);
493 if (bdVal.compareTo(minVal) < 0) {
496 if (bdVal.compareTo(maxVal) > 0) {
501 newObject = new JsonObject();
502 if (DATA_TYPE_FLOAT_VALUE.equals(type)) {
503 newObject.addProperty("value", bdVal);
504 } else if (DATA_TYPE_STRING_VALUE.equals(type)) {
505 newObject.addProperty("value", bdVal.toString());
506 } else if (DATA_TYPE_SWITCH_PROGRAM.equals(type) && object.getVirtual() == 1) {
507 /* A switchProgram as NumberItem is always virtual */
508 newObject = sendVirtualState(object, service, command, itemType);
509 } else if (DATA_TYPE_ERROR_LIST.equals(type) && object.getVirtual() == 1) {
510 /* A errorList as NumberItem is always virtual */
511 newObject = sendVirtualState(object, service, command, itemType);
513 logger.info("Not supported type for numberItem: {}", type);
515 /* Binding is a StringItem */
516 } else if (CoreItemFactory.STRING.equals(itemType)) {
517 String val = ((StringType) command).toString();
518 newObject = new JsonObject();
519 /* Check the capabilities of this service */
520 if (object.getValueParameter() != null) {
521 // type is definitely correct here
522 @SuppressWarnings("unchecked")
523 List<String> valParas = (List<String>) object.getValueParameter();
524 if (null != valParas) {
525 if (!valParas.contains(val)) {
526 logger.warn("Parameter is not in the service parameterlist: {}", val);
531 if (DATA_TYPE_STRING_VALUE.equals(type)) {
532 newObject.addProperty("value", val);
533 } else if (DATA_TYPE_FLOAT_VALUE.equals(type)) {
534 newObject.addProperty("value", Float.parseFloat(val));
535 } else if (DATA_TYPE_SWITCH_PROGRAM.equals(type)) {
536 if (object.getVirtual() == 1) {
537 newObject = sendVirtualState(object, service, command, itemType);
539 /* The JSONArray of switch items can be send directly */
541 /* Check whether this input string is a valid JSONArray */
542 JsonArray userArray = (JsonArray) jsonParser.parse(val);
543 newObject = userArray.getAsJsonObject();
544 } catch (JsonParseException e) {
545 logger.warn("The input for the switchProgram is not a valid JSONArray : {}",
551 logger.info("Not supported type for stringItem: {}", type);
553 /* Binding is a DateTimeItem */
554 } else if (CoreItemFactory.DATETIME.equals(itemType)) {
555 String val = ((DateTimeType) command).toString();
556 newObject = new JsonObject();
557 if (DATA_TYPE_STRING_VALUE.equals(type)) {
558 newObject.addProperty("value", val);
559 } else if (DATA_TYPE_SWITCH_PROGRAM.equals(type)) {
560 newObject = sendVirtualState(object, service, command, itemType);
562 logger.info("Not supported type for dateTimeItem: {}", type);
564 } else if ("Switch".equals(itemType)) {
566 newObject = new JsonObject();
567 // type is definitely correct here
568 @SuppressWarnings("unchecked")
569 Map<String, String> switchNames = (HashMap<String, String>) itemPara;
570 if (switchNames.containsKey("on")) {
571 if (command == OnOffType.OFF) {
572 val = switchNames.get("off");
573 } else if (command == OnOffType.ON) {
574 val = switchNames.get("on");
576 // type is definitely correct here
577 @SuppressWarnings("unchecked")
578 List<String> valParas = (List<String>) object.getValueParameter();
579 if (null != valParas) {
580 if (!valParas.contains(val)) {
581 logger.warn("Parameter is not in the service parameterlist: {}", val);
585 } else if (switchNames.isEmpty()) {
586 logger.debug("No switch item configuration");
589 logger.info("Switch-Item only on configured on/off string values {}", command);
592 if (DATA_TYPE_STRING_VALUE.equals(type)) {
593 newObject.addProperty("value", val);
595 logger.info("Not supported type for SwitchItem:{}", type);
598 logger.info("Bindingtype not supported: {}", itemType.getClass());
601 /* If some data is availible then we have to send it to device */
602 if (newObject != null && newObject.toString().length() > 2) {
603 logger.trace("Send Data: {}", newObject);
612 * This function sets the state of a virtual service
614 public @Nullable JsonObject sendVirtualState(KM200ServiceObject object, String service, Command command,
616 JsonObject newObject = null;
618 logger.trace("Check virtual state of: {} type: {} item: {}", service, type, itemType);
619 String parent = object.getParent();
620 if (null != parent) {
621 KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
622 if (null != objParent) {
623 type = object.getServiceType();
624 /* Binding is a StringItem */
625 if (CoreItemFactory.STRING.equals(itemType)) {
626 String val = ((StringType) command).toString();
628 case DATA_TYPE_SWITCH_PROGRAM:
629 KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) objParent
630 .getValueParameter());
631 if (null != sPService) {
632 String[] servicePath = service.split("/");
633 String virtService = servicePath[servicePath.length - 1];
634 if ("weekday".equals(virtService)) {
635 /* Only parameter changing without communication to device */
636 sPService.setActiveDay(val);
641 /* Binding is a NumberItem */
642 } else if (CoreItemFactory.NUMBER.equals(itemType)) {
643 Integer val = ((DecimalType) command).intValue();
645 case DATA_TYPE_SWITCH_PROGRAM:
646 KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) objParent
647 .getValueParameter());
648 if (null != sPService) {
649 String[] servicePath = service.split("/");
650 String virtService = servicePath[servicePath.length - 1];
651 if ("cycle".equals(virtService)) {
652 /* Only parameter changing without communication to device */
653 sPService.setActiveCycle(val);
654 } else if (virtService.equals(sPService.getPositiveSwitch())) {
655 sPService.setActivePositiveSwitch(val);
656 /* Create a JSON Array from current switch configuration */
657 newObject = sPService.getUpdatedJSONData(objParent);
658 } else if (virtService.equals(sPService.getNegativeSwitch())) {
659 sPService.setActiveNegativeSwitch(val);
660 /* Create a JSON Array from current switch configuration */
661 newObject = sPService.getUpdatedJSONData(objParent);
665 case DATA_TYPE_ERROR_LIST:
666 KM200ErrorServiceHandler eService = ((KM200ErrorServiceHandler) objParent
667 .getValueParameter());
668 if (null != eService) {
669 String[] nServicePath = service.split("/");
670 String nVirtService = nServicePath[nServicePath.length - 1];
671 if ("error".equals(nVirtService)) {
672 /* Only parameter changing without communication to device */
673 eService.setActiveError(val);
678 } else if (CoreItemFactory.DATETIME.equals(itemType)) {
679 ZonedDateTime swTime = ((DateTimeType) command).getZonedDateTime();
680 KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) objParent
681 .getValueParameter());
682 if (null != sPService) {
683 String[] servicePath = service.split("/");
684 String virtService = servicePath[servicePath.length - 1];
685 Integer minutes = swTime.getHour() * 60 + swTime.getMinute()
686 + swTime.getOffset().getTotalSeconds() % 60;
687 minutes = (minutes % sPService.getSwitchPointTimeRaster())
688 * sPService.getSwitchPointTimeRaster();
689 if (virtService.equals(sPService.getPositiveSwitch())) {
690 sPService.setActivePositiveSwitch(minutes);
692 if (virtService.equals(sPService.getNegativeSwitch())) {
693 sPService.setActiveNegativeSwitch(minutes);
695 /* Create a JSON Array from current switch configuration */
696 newObject = sPService.getUpdatedJSONData(objParent);