/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.km200.internal;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.openhab.binding.km200.KM200BindingProvider;
import org.openhab.core.items.Item;
import org.openhab.core.library.items.DateTimeItem;
import org.openhab.core.library.items.NumberItem;
import org.openhab.core.library.items.StringItem;
import org.openhab.core.library.items.SwitchItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.io.ByteStreams;
/**
* The KM200Comm class does the communication to the device and does any encryption/decryption/converting jobs
*
* @author Markus Eckhardt
*
* @since 1.9.0
*/
class KM200Comm {
private static final Logger logger = LoggerFactory.getLogger(KM200Comm.class);
private HttpClient client = null;
private KM200Device device = null;
public KM200Comm(KM200Device device) {
this.device = device;
}
/**
* This function removes zero padding from a byte array.
*
*/
public static byte[] removeZeroPadding(byte[] bytes) {
int i = bytes.length - 1;
while (i >= 0 && bytes[i] == 0) {
--i;
}
return Arrays.copyOf(bytes, i + 1);
}
/**
* This function adds zero padding to a byte array.
*
*/
public static byte[] addZeroPadding(byte[] bdata, int bSize, String cSet) throws UnsupportedEncodingException {
int encrypt_padchar = bSize - (bdata.length % bSize);
byte[] padchars = new String(new char[encrypt_padchar]).getBytes(cSet);
byte[] padded_data = new byte[bdata.length + padchars.length];
System.arraycopy(bdata, 0, padded_data, 0, bdata.length);
System.arraycopy(padchars, 0, padded_data, bdata.length, padchars.length);
return padded_data;
}
/**
* This function converts a hex string to a byte array
*
*/
public static byte[] hexToBytes(String str) {
if (str == null) {
return null;
} else if (str.length() < 2) {
return null;
} else {
int len = str.length() / 2;
byte[] buffer = new byte[len];
for (int i = 0; i < len; i++) {
buffer[i] = (byte) Integer.parseInt(str.substring(i * 2, i * 2 + 2), 16);
}
return buffer;
}
}
/**
* This function converts a byte array to a hex string
*
*/
public static String bytesToHex(byte[] data) {
if (data == null) {
return null;
}
int len = data.length;
String str = "";
for (int i = 0; i < len; i++) {
if ((data[i] & 0xFF) < 16) {
str = str + "0" + Integer.toHexString(data[i] & 0xFF);
} else {
str = str + Integer.toHexString(data[i] & 0xFF);
}
}
return str;
}
/**
* This function does the GET http communication to the device
*
*/
public byte[] getDataFromService(String service) {
byte[] responseBodyB64 = null;
int maxNbrGets = 3;
int statusCode = 0;
// Create an instance of HttpClient.
if (client == null) {
client = new HttpClient();
}
synchronized (client) {
// Create a method instance.
GetMethod method = new GetMethod("http://" + device.getIP4Address() + service);
// Provide custom retry handler is necessary
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false));
// Set the right header
method.setRequestHeader("Accept", "application/json");
method.addRequestHeader("User-Agent", "TeleHeater/2.2.3");
try {
for (int i = 0; i < maxNbrGets && statusCode != HttpStatus.SC_OK; i++) {
// Execute the method.
statusCode = client.executeMethod(method);
// Check the status
switch (statusCode) {
case HttpStatus.SC_OK:
break;
case HttpStatus.SC_INTERNAL_SERVER_ERROR:
/* Unknown problem with the device, wait and try again */
logger.warn("HTTP GET failed: 500, internal server error, repeating.. ");
Thread.sleep(2000L);
continue;
case HttpStatus.SC_FORBIDDEN:
/* Service is available but not readable */
byte[] test = new byte[1];
return test;
default:
logger.error("HTTP GET failed: {}", method.getStatusLine());
return null;
}
}
device.setCharSet(method.getResponseCharSet());
// Read the response body.
responseBodyB64 = ByteStreams.toByteArray(method.getResponseBodyAsStream());
} catch (HttpException e) {
logger.error("Fatal protocol violation: {}", e.getMessage());
} catch (InterruptedException e) {
logger.error("Sleep was interrupted: {}", e.getMessage());
} catch (IOException e) {
logger.error("Fatal transport error: {}", e.getMessage());
} finally {
// Release the connection.
method.releaseConnection();
}
return responseBodyB64;
}
}
/**
* This function does the SEND http communication to the device
*
*/
public Integer sendDataToService(String service, byte[] data) {
// Create an instance of HttpClient.
Integer rCode = null;
if (client == null) {
client = new HttpClient();
}
synchronized (client) {
// Create a method instance.
PostMethod method = new PostMethod("http://" + device.getIP4Address() + service);
// Provide custom retry handler is necessary
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false));
// Set the right header
method.setRequestHeader("Accept", "application/json");
method.addRequestHeader("User-Agent", "TeleHeater/2.2.3");
method.setRequestEntity(new ByteArrayRequestEntity(data));
try {
rCode = client.executeMethod(method);
} catch (Exception e) {
logger.error("Failed to send data {}", e);
} finally {
// Release the connection.
method.releaseConnection();
}
return rCode;
}
}
/**
* This function does the decoding for a new message from the device
*
*/
public String decodeMessage(byte[] encoded) {
String retString = null;
byte[] decodedB64 = null;
try {
decodedB64 = Base64.decodeBase64(encoded);
} catch (Exception e) {
logger.error("Message is not in valid Base64 scheme: {}", e.getMessage());
return null;
}
try {
/* Check whether the length of the decryptData is NOT multiplies of 16 */
if ((decodedB64.length & 0xF) != 0) {
/* Return the data */
retString = new String(decodedB64, device.getCharSet());
return retString;
}
// --- create cipher
final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(device.getCryptKeyPriv(), "AES"));
final byte[] decryptedData = cipher.doFinal(decodedB64);
byte[] decryptedDataWOZP = removeZeroPadding(decryptedData);
retString = new String(decryptedDataWOZP, device.getCharSet());
return retString;
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
// failure to authenticate
logger.error("Exception on encoding: {}", e);
return null;
}
}
/**
* This function does the encoding for a new message to the device
*
*/
public byte[] encodeMessage(String data) {
byte[] encryptedDataB64 = null;
try {
// --- create cipher
byte[] bdata = data.getBytes(device.getCharSet());
final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(device.getCryptKeyPriv(), "AES"));
logger.debug("Create padding..");
int bsize = cipher.getBlockSize();
logger.debug("Add Padding and Encrypt AES..");
final byte[] encryptedData = cipher.doFinal(addZeroPadding(bdata, bsize, device.getCharSet()));
logger.debug("Encrypt B64..");
try {
encryptedDataB64 = Base64.encodeBase64(encryptedData);
} catch (Exception e) {
logger.error("Base64encoding not possible: {}", e.getMessage());
}
return encryptedDataB64;
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
// failure to authenticate
logger.error("Exception on encoding: {}", e);
return null;
}
}
/**
* This function checks the capabilities of a service on the device
*
*/
public void initObjects(String service) {
String id = null, type = null, decodedData = null;
Integer writeable = 0;
Integer recordable = 0;
JSONObject nodeRoot = null;
KM200CommObject newObject = null;
logger.debug("Init: {}", service);
if (device.blacklistMap.contains(service)) {
logger.debug("Service on blacklist: {}", service);
return;
}
byte[] recData = getDataFromService(service.toString());
try {
if (recData == null) {
throw new RuntimeException("Communication is not possible!");
}
if (recData.length == 0) {
throw new RuntimeException("No reply from KM200!");
}
/* Look whether the communication was forbidden */
if (recData.length == 1) {
newObject = new KM200CommObject(service, "", 0, 0, 0);
device.serviceMap.put(service, newObject);
return;
}
decodedData = decodeMessage(recData);
if (decodedData == null) {
throw new RuntimeException("Decoding of the KM200 message is not possible!");
}
if (decodedData.length() > 0) {
nodeRoot = new JSONObject(decodedData);
type = nodeRoot.getString("type");
id = nodeRoot.getString("id");
} else {
logger.warn("Get empty reply");
return;
}
/* Check the service features and set the flags */
if (nodeRoot.has("writeable")) {
Integer val = nodeRoot.getInt("writeable");
logger.debug(val.toString());
writeable = val;
}
if (nodeRoot.has("recordable")) {
Integer val = nodeRoot.getInt("recordable");
logger.debug(val.toString());
recordable = val;
}
logger.debug("Typ: {}", type);
newObject = new KM200CommObject(id, type, writeable, recordable);
newObject.setJSONData(decodedData);
Object valObject = null;
switch (type) {
case "stringValue": /* Check whether the type is a single value containing a string value */
logger.debug("initDevice: type string value: {}", decodedData);
valObject = new String(nodeRoot.getString("value"));
newObject.setValue(valObject);
if (nodeRoot.has("allowedValues")) {
List<String> valParas = new ArrayList<String>();
JSONArray paras = nodeRoot.getJSONArray("allowedValues");
for (int i = 0; i < paras.length(); i++) {
String subJSON = (String) paras.get(i);
valParas.add(subJSON);
}
newObject.setValueParameter(valParas);
}
device.serviceMap.put(id, newObject);
break;
case "floatValue": /* Check whether the type is a single value containing a float value */
logger.debug("initDevice: type float value: {}", decodedData);
valObject = nodeRoot.getBigDecimal("value");
newObject.setValue(valObject);
if (nodeRoot.has("minValue") && nodeRoot.has("maxValue")) {
List<BigDecimal> valParas = new ArrayList<BigDecimal>();
valParas.add(nodeRoot.getBigDecimal("minValue"));
valParas.add(nodeRoot.getBigDecimal("maxValue"));
newObject.setValueParameter(valParas);
}
device.serviceMap.put(id, newObject);
break;
case "switchProgram": /* Check whether the type is a switchProgram */
logger.debug("initDevice: type switchProgram {}", decodedData);
KM200SwitchProgramService sPService = new KM200SwitchProgramService();
sPService.setMaxNbOfSwitchPoints(nodeRoot.getInt("maxNbOfSwitchPoints"));
sPService.setMaxNbOfSwitchPointsPerDay(nodeRoot.getInt("maxNbOfSwitchPointsPerDay"));
sPService.setSwitchPointTimeRaster(nodeRoot.getInt("switchPointTimeRaster"));
JSONObject propObject = nodeRoot.getJSONObject("setpointProperty");
sPService.setSetpointProperty(propObject.getString("id"));
sPService.updateSwitches(nodeRoot);
newObject.setValueParameter(sPService);
newObject.setJSONData(decodedData);
device.serviceMap.put(id, newObject);
device.virtualList.add(newObject);
break;
case "errorList": /* Check whether the type is a errorList */
logger.debug("initDevice: type errorList: {}", decodedData);
KM200ErrorService eService = new KM200ErrorService();
eService.updateErrors(nodeRoot);
newObject.setValueParameter(eService);
newObject.setJSONData(decodedData);
device.serviceMap.put(id, newObject);
device.virtualList.add(newObject);
break;
case "refEnum": /* Check whether the type is a refEnum */
logger.debug("initDevice: type refEnum: {}", decodedData);
device.serviceMap.put(id, newObject);
JSONArray refers = nodeRoot.getJSONArray("references");
for (int i = 0; i < refers.length(); i++) {
JSONObject subJSON = refers.getJSONObject(i);
id = subJSON.getString("id");
initObjects(id);
}
break;
case "moduleList": /* Check whether the type is a moduleList */
logger.debug("initDevice: type moduleList: {}", decodedData);
device.serviceMap.put(id, newObject);
JSONArray vals = nodeRoot.getJSONArray("values");
for (int i = 0; i < vals.length(); i++) {
JSONObject subJSON = vals.getJSONObject(i);
id = subJSON.getString("id");
initObjects(id);
}
break;
case "yRecording": /* Check whether the type is a yRecording */
logger.debug("initDevice: type yRecording: {}", decodedData);
device.serviceMap.put(id, newObject);
/* have to be completed */
break;
case "systeminfo": /* Check whether the type is a systeminfo */
logger.debug("initDevice: type systeminfo: {}", decodedData);
JSONArray sInfo = nodeRoot.getJSONArray("values");
newObject.setValue(sInfo);
device.serviceMap.put(id, newObject);
/* have to be completed */
break;
case "arrayData":
logger.debug("initDevice: type arrayData: {}", decodedData);
newObject.setJSONData(decodedData);
device.serviceMap.put(id, newObject);
/* have to be completed */
break;
default: /* Unknown type */
logger.info("initDevice: type unknown for service: {} Data: {}", service, decodedData);
device.serviceMap.put(id, newObject);
}
} catch (
JSONException e) {
logger.error("Parsingexception in JSON: {} data: {}", e.getMessage(), decodedData);
}
}
/**
* This function creates the virtual services
*
*/
public void initVirtualObjects() {
KM200CommObject newObject = null;
for (KM200CommObject object : device.virtualList) {
logger.debug(object.getFullServiceName());
String id = object.getFullServiceName();
String type = object.getServiceType();
switch (type) {
case "switchProgram":
KM200SwitchProgramService sPService = ((KM200SwitchProgramService) object.getValueParameter());
sPService.determineSwitchNames(device);
newObject = new KM200CommObject(id + "/weekday", type, 1, 0, 1, id);
device.serviceMap.put(id + "/weekday", newObject);
newObject = new KM200CommObject(id + "/nbrCycles", type, 0, 0, 1, id);
device.serviceMap.put(id + "/nbrCycles", newObject);
newObject = new KM200CommObject(id + "/cycle", type, 1, 0, 1, id);
device.serviceMap.put(id + "/cycle", newObject);
logger.debug("On: {} Of: {}", id + "/" + sPService.getPositiveSwitch(),
id + "/" + sPService.getNegativeSwitch());
newObject = new KM200CommObject(id + "/" + sPService.getPositiveSwitch(), type,
object.getWriteable(), object.getRecordable(), 1, id);
device.serviceMap.put(id + "/" + sPService.getPositiveSwitch(), newObject);
newObject = new KM200CommObject(id + "/" + sPService.getNegativeSwitch(), type,
object.getWriteable(), object.getRecordable(), 1, id);
device.serviceMap.put(id + "/" + sPService.getNegativeSwitch(), newObject);
break;
case "errorList":
newObject = new KM200CommObject(id + "/nbrErrors", type, 0, 0, 1, id);
device.serviceMap.put(id + "/nbrErrors", newObject);
newObject = new KM200CommObject(id + "/error", type, 1, 0, 1, id);
device.serviceMap.put(id + "/error", newObject);
newObject = new KM200CommObject(id + "/errorString", type, 0, 0, 1, id);
device.serviceMap.put(id + "/errorString", newObject);
break;
}
}
}
/**
* This function checks whether the service has a replacement parameter
*
*/
public String checkParameterReplacement(KM200BindingProvider provider, String item) {
String service = provider.getService(item);
if (provider.getParameter(item).containsKey("current")) {
String currentService = provider.getParameter(item).get("current");
if (device.serviceMap.containsKey(currentService)) {
if (device.serviceMap.get(currentService).getServiceType().equals("stringValue")) {
String val = (String) device.serviceMap.get(currentService).getValue();
return (service.replace("__current__", val));
}
}
}
return service;
}
/**
* This function checks the state of a service on the device
*
*/
public State getProvidersState(KM200BindingProvider provider, String item) {
synchronized (device) {
String decodedData = null;
String type = null;
byte[] recData = null;
KM200CommObject object = null;
String service = checkParameterReplacement(provider, item);
Class<? extends Item> itemType = provider.getItemType(item);
logger.debug("Check state of: {} type: {} item: {}", service, type, itemType.getName());
if (device.blacklistMap.contains(service)) {
logger.debug("Service on blacklist: {}", service);
return null;
}
if (device.serviceMap.containsKey(service)) {
object = device.serviceMap.get(service);
if (object.getReadable() == 0) {
logger.warn("Service is listed as protected (reading is not possible): {}", service);
return null;
}
type = object.getServiceType();
} else {
logger.warn("Service is not in the determined device service list: {}", service);
return null;
}
/* For using of virtual services only one receive on the parent service is needed */
if (!object.getUpdated()
|| (object.getVirtual() == 1 && !device.serviceMap.get(object.getParent()).getUpdated())) {
if (object.getVirtual() == 1) {
/* If it's a virtual service then receive the data from parent service */
recData = getDataFromService(object.getParent());
} else {
recData = getDataFromService(service);
}
if (recData == null) {
throw new RuntimeException("Communication is not possible!");
}
if (recData.length == 0) {
throw new RuntimeException("No reply from KM200!");
}
/* Look whether the communication was forbidden */
if (recData.length == 1) {
logger.error("Service is listed as readable but communication is forbidden: {}", service);
return null;
}
decodedData = decodeMessage(recData);
logger.debug("Check state of data: {}", decodedData);
if (decodedData == null) {
throw new RuntimeException("Decoding of the KM200 message is not possible!");
}
if (object.getVirtual() == 1) {
device.serviceMap.get(object.getParent()).setJSONData(decodedData);
device.serviceMap.get(object.getParent()).setUpdated(true);
} else {
object.setJSONData(decodedData);
}
object.setUpdated(true);
} else {
/* If already updated then use the saved data */
if (object.getVirtual() == 1) {
decodedData = device.serviceMap.get(object.getParent()).getJSONData();
} else {
decodedData = object.getJSONData();
}
}
/* Data is received, now parsing it */
return parseJSONData(decodedData, type, item, provider);
}
}
/**
* This function parses the receviced JSON Data and return the right state
*
*/
public State parseJSONData(String decodedData, String type, String item, KM200BindingProvider provider) {
JSONObject nodeRoot = null;
State state = null;
Class<? extends Item> itemType = provider.getItemType(item);
String service = checkParameterReplacement(provider, item);
KM200CommObject object = device.serviceMap.get(service);
logger.debug("parseJSONData service: {}, data: {}", service, decodedData);
/* Now parsing of the JSON String depending on its type and the type of binding item */
try {
if (decodedData.length() > 0) {
nodeRoot = new JSONObject(decodedData);
} else {
logger.warn("Get empty reply");
return null;
}
switch (type) {
case "stringValue": /* Check whether the type is a single value containing a string value */
logger.debug("initDevice: type string value: {}", decodedData);
String sVal = nodeRoot.getString("value");
device.serviceMap.get(service).setValue(sVal);
/* SwitchItem Binding */
if (itemType.isAssignableFrom(SwitchItem.class)) {
if (provider.getParameter(item).containsKey("on")) {
if (sVal.equals(provider.getParameter(item).get("off"))) {
state = OnOffType.OFF;
} else if (sVal.equals(provider.getParameter(item).get("on"))) {
state = OnOffType.ON;
}
} else {
logger.warn("Switch-Item only on configured on/off string values: {}", decodedData);
return null;
}
/* NumberItem Binding */
} else if (itemType.isAssignableFrom(NumberItem.class)) {
try {
state = new DecimalType(Float.parseFloat(sVal));
} catch (NumberFormatException e) {
logger.error(
"Conversion of the string value to Decimal wasn't possible, data: {} error: {}",
decodedData, e);
return null;
}
/* DateTimeItem Binding */
} else if (itemType.isAssignableFrom(DateTimeItem.class)) {
try {
state = new DateTimeType(sVal);
} catch (IllegalArgumentException e) {
logger.error(
"Conversion of the string value to DateTime wasn't possible, data: {} error: {}",
decodedData, e);
return null;
}
/* StringItem Binding */
} else if (itemType.isAssignableFrom(StringItem.class)) {
state = new StringType(sVal);
} else {
logger.warn("Bindingtype not supported for string values: {}", itemType.getClass());
return null;
}
return state;
case "floatValue": /* Check whether the type is a single value containing a float value */
logger.debug("state of type float value: {}", decodedData);
BigDecimal bdVal = nodeRoot.getBigDecimal("value");
device.serviceMap.get(service).setValue(bdVal);
/* NumberItem Binding */
if (itemType.isAssignableFrom(NumberItem.class)) {
state = new DecimalType(bdVal.floatValue());
/* StringItem Binding */
} else if (itemType.isAssignableFrom(StringItem.class)) {
state = new StringType(bdVal.toString());
} else {
logger.warn("Bindingtype not supported for float values: {}", itemType.getClass());
return null;
}
return state;
case "switchProgram": /* Check whether the type is a switchProgram */
KM200SwitchProgramService sPService = null;
logger.debug("state of type switchProgram: {}", decodedData);
/* Get the KM200SwitchProgramService class object with all specific parameters */
if (object.getVirtual() == 0) {
sPService = ((KM200SwitchProgramService) object.getValueParameter());
} else {
sPService = ((KM200SwitchProgramService) device.serviceMap.get(object.getParent())
.getValueParameter());
}
/* Update the switches insode the KM200SwitchProgramService */
sPService.updateSwitches(nodeRoot);
/* the parsing of switch program-services have to be outside, using json in strings */
if (object.getVirtual() == 1) {
return this.getVirtualState(object, itemType, service);
} else {
/* if access to the parent non virtual service the return the switchPoints jsonarray */
if (itemType.isAssignableFrom(StringItem.class)) {
state = new StringType(nodeRoot.getJSONArray("switchPoints").toString());
} else {
logger.warn(
"Bindingtype not supported for switchProgram, only json over strings supported: {}",
itemType.getClass());
return null;
}
return state;
}
case "errorList": /* Check whether the type is a errorList */
KM200ErrorService eService = null;
logger.debug("state of type errorList: {}", decodedData);
/* Get the KM200ErrorService class object with all specific parameters */
if (object.getVirtual() == 0) {
eService = ((KM200ErrorService) object.getValueParameter());
} else {
eService = ((KM200ErrorService) device.serviceMap.get(object.getParent()).getValueParameter());
}
/* Update the switches insode the KM200SwitchProgramService */
eService.updateErrors(nodeRoot);
/* the parsing of switch program-services have to be outside, using json in strings */
if (object.getVirtual() == 1) {
return this.getVirtualState(object, itemType, service);
} else {
/* if access to the parent non virtual service the return the switchPoints jsonarray */
if (itemType.isAssignableFrom(StringItem.class)) {
state = new StringType(nodeRoot.getJSONArray("values").toString());
} else {
logger.warn(
"Bindingtype not supported for error list, only json over strings is supported: {}",
itemType.getClass());
return null;
}
return state;
}
case "yRecording": /* Check whether the type is a yRecording */
logger.info("state of: type yRecording is not supported yet: {}", decodedData);
/* have to be completed */
break;
case "systeminfo": /* Check whether the type is a systeminfo */
logger.info("state of: type systeminfo is not supported yet: {}", decodedData);
/* have to be completed */
break;
case "arrayData": /* Check whether the type is a arrayData */
logger.info("state of: type arrayData is not supported yet: {}", decodedData);
/* have to be completed */
break;
}
} catch (JSONException e) {
logger.error("Parsingexception in JSON, data: {} error: {} ", decodedData, e.getMessage());
}
return null;
}
/**
* This function checks the virtual state of a service
*
*/
public State getVirtualState(KM200CommObject object, Class<? extends Item> itemType, String service) {
State state = null;
String type = object.getServiceType();
logger.debug("Check virtual state of: {} type: {} item: {}", service, type, itemType.getName());
switch (type) {
case "switchProgram":
KM200SwitchProgramService sPService = ((KM200SwitchProgramService) device.serviceMap
.get(object.getParent()).getValueParameter());
String[] servicePath = service.split("/");
String virtService = servicePath[servicePath.length - 1];
if (virtService.equals("weekday")) {
if (itemType.isAssignableFrom(StringItem.class)) {
String val = sPService.getActiveDay();
if (val == null) {
return null;
}
state = new StringType(val);
} else {
logger.warn("Bindingtype not supported for day service: {}", itemType.getClass());
return null;
}
} else if (virtService.equals("nbrCycles")) {
if (itemType.isAssignableFrom(NumberItem.class)) {
Integer val = sPService.getNbrCycles();
if (val == null) {
return null;
}
state = new DecimalType(val);
} else {
logger.warn("Bindingtype not supported for nbrCycles service: {}", itemType.getClass());
return null;
}
} else if (virtService.equals("cycle")) {
if (itemType.isAssignableFrom(NumberItem.class)) {
Integer val = sPService.getActiveCycle();
if (val == null) {
return null;
}
state = new DecimalType(val);
} else {
logger.warn("Bindingtype not supported for cycle service: {}", itemType.getClass());
return null;
}
} else if (virtService.equals(sPService.getPositiveSwitch())) {
if (itemType.isAssignableFrom(NumberItem.class)) {
Integer val = sPService.getActivePositiveSwitch();
if (val == null) {
return null;
}
state = new DecimalType(val);
} else if (itemType.isAssignableFrom(DateTimeItem.class)) {
Integer val = sPService.getActivePositiveSwitch();
if (val == null) {
return null;
}
Calendar rightNow = Calendar.getInstance();
Integer hour = val % 60;
Integer minute = val - (hour * 60);
rightNow.set(Calendar.HOUR_OF_DAY, hour);
rightNow.set(Calendar.MINUTE, minute);
state = new DateTimeType(rightNow);
} else {
logger.warn("Bindingtype not supported for cycle service: {}", itemType.getClass());
return null;
}
} else if (virtService.equals(sPService.getNegativeSwitch())) {
if (itemType.isAssignableFrom(NumberItem.class)) {
Integer val = sPService.getActiveNegativeSwitch();
if (val == null) {
return null;
}
state = new DecimalType(val);
} else if (itemType.isAssignableFrom(DateTimeItem.class)) {
Integer val = sPService.getActiveNegativeSwitch();
if (val == null) {
return null;
}
Calendar rightNow = Calendar.getInstance();
Integer hour = val % 60;
Integer minute = val - (hour * 60);
rightNow.set(Calendar.HOUR_OF_DAY, hour);
rightNow.set(Calendar.MINUTE, minute);
state = new DateTimeType(rightNow);
} else {
logger.warn("Bindingtype not supported for cycle service: {}", itemType.getClass());
return null;
}
}
break;
case "errorList":
KM200ErrorService eService = ((KM200ErrorService) device.serviceMap.get(object.getParent())
.getValueParameter());
String[] nServicePath = service.split("/");
String nVirtService = nServicePath[nServicePath.length - 1];
/* Go through the parameters and read the values */
switch (nVirtService) {
case "nbrErrors":
if (itemType.isAssignableFrom(NumberItem.class)) {
Integer val = eService.getNbrErrors();
if (val == null) {
return null;
}
state = new DecimalType(val);
} else {
logger.warn("Bindingtype not supported for error number service: {}", itemType.getClass());
return null;
}
break;
case "error":
if (itemType.isAssignableFrom(NumberItem.class)) {
Integer val = eService.getActiveError();
if (val == null) {
return null;
}
state = new DecimalType(val);
} else {
logger.warn("Bindingtype not supported for error service: {}", itemType.getClass());
return null;
}
break;
case "errorString":
if (itemType.isAssignableFrom(StringItem.class)) {
String val = eService.getErrorString();
if (val == null) {
return null;
}
state = new StringType(val);
} else {
logger.warn("Bindingtype not supported for error string service: {}", itemType.getClass());
return null;
}
break;
}
break;
}
return state;
}
/**
* This function sets the state of a service on the device
*
*/
public byte[] sendProvidersState(KM200BindingProvider provider, String item, Command command) {
synchronized (device) {
String type = null;
String dataToSend = null;
KM200CommObject object = null;
Class<? extends Item> itemType = provider.getItemType(item);
String service = checkParameterReplacement(provider, item);
logger.debug("Prepare item for send: {} type: {} item: {}", service, type, itemType.getName());
if (device.blacklistMap.contains(service)) {
logger.debug("Service on blacklist: {}", service);
return null;
}
if (device.serviceMap.containsKey(service)) {
if (device.serviceMap.get(service).getWriteable() == 0) {
logger.error("Service is listed as read-only: {}", service);
return null;
}
object = device.serviceMap.get(service);
type = object.getServiceType();
} else {
logger.error("Service is not in the determined device service list: {}", service);
return null;
}
/* The service is availible, set now the values depeding on the item and binding type */
logger.debug("state of: {} type: {}", command, type);
/* Binding is a NumberItem */
if (itemType.isAssignableFrom(NumberItem.class)) {
BigDecimal bdVal = ((DecimalType) command).toBigDecimal();
/* Check the capabilities of this service */
if (object.getValueParameter() != null) {
@SuppressWarnings("unchecked")
List<BigDecimal> valParas = (List<BigDecimal>) object.getValueParameter();
BigDecimal minVal = valParas.get(0);
BigDecimal maxVal = valParas.get(1);
if (bdVal.compareTo(minVal) < 0) {
bdVal = minVal;
}
if (bdVal.compareTo(maxVal) > 0) {
bdVal = maxVal;
}
}
if (type.equals("floatValue")) {
dataToSend = new JSONObject().put("value", bdVal).toString();
} else if (type.equals("stringValue")) {
dataToSend = new JSONObject().put("value", bdVal.toString()).toString();
} else if (type.equals("switchProgram") && object.getVirtual() == 1) {
/* A switchProgram as NumberItem is always virtual */
dataToSend = sendVirtualState(object, itemType, service, command);
} else if (type.equals("errorList") && object.getVirtual() == 1) {
/* A errorList as NumberItem is always virtual */
dataToSend = sendVirtualState(object, itemType, service, command);
} else {
logger.warn("Not supported type for numberItem: {}", type);
}
/* Binding is a StringItem */
} else if (itemType.isAssignableFrom(StringItem.class)) {
String val = ((StringType) command).toString();
/* Check the capabilities of this service */
if (object.getValueParameter() != null) {
@SuppressWarnings("unchecked")
List<String> valParas = (List<String>) object.getValueParameter();
if (!valParas.contains(val)) {
logger.warn("Parameter is not in the service parameterlist: {}", val);
return null;
}
}
if (type.equals("stringValue")) {
dataToSend = new JSONObject().put("value", val).toString();
} else if (type.equals("floatValue")) {
dataToSend = new JSONObject().put("value", Float.parseFloat(val)).toString();
} else if (type.equals("switchProgram")) {
if (object.getVirtual() == 1) {
dataToSend = sendVirtualState(object, itemType, service, command);
} else {
/* The JSONArray of switch items can be sended directly */
try {
/* Check whether ths input string is a valid JSONArray */
JSONArray userArray = new JSONArray(val);
dataToSend = userArray.toString();
} catch (Exception e) {
logger.warn("The input for the switchProgram is not a valid JSONArray : {}",
e.getMessage());
return null;
}
}
} else {
logger.warn("Not supported type for stringItem: {}", type);
}
/* Binding is a DateTimeItem */
} else if (itemType.isAssignableFrom(DateTimeItem.class)) {
String val = ((DateTimeType) command).toString();
if (type.equals("stringValue")) {
dataToSend = new JSONObject().put("value", val).toString();
} else if (type.equals("switchProgram")) {
dataToSend = sendVirtualState(object, itemType, service, command);
} else {
logger.warn("Not supported type for dateTimeItem: {}", type);
}
/* Binding is a SwitchItem */
} else if (itemType.isAssignableFrom(SwitchItem.class)) {
String val = null;
if (provider.getParameter(item).containsKey("on")) {
if (command == OnOffType.OFF) {
val = provider.getParameter(item).get("off");
} else if (command == OnOffType.ON) {
val = provider.getParameter(item).get("on");
}
} else {
logger.warn("Switch-Item only on configured on/off string values {}", command);
return null;
}
if (type.equals("stringValue")) {
dataToSend = new JSONObject().put("value", val).toString();
} else {
logger.warn("Not supported type for SwitchItem:{}", type);
}
} else {
logger.warn("Bindingtype not supported: {}", itemType.getClass());
return null;
}
/* If some data is availible then we have to send it to device */
if (dataToSend != null) {
/* base64 + encoding */
logger.debug("Encoding: {}", dataToSend);
byte[] encData = encodeMessage(dataToSend);
if (encData == null) {
logger.error("Couldn't encrypt data");
return null;
}
return encData;
} else {
return null;
}
}
}
/**
* This function sets the state of a virtual service
*
*/
public String sendVirtualState(KM200CommObject object, Class<? extends Item> itemType, String service,
Command command) {
String dataToSend = null;
String type = null;
logger.debug("Check virtual state of: {} type: {} item: {}", service, type, itemType.getName());
object = device.serviceMap.get(service);
KM200CommObject parObject = device.serviceMap.get(object.getParent());
type = object.getServiceType();
/* Binding is a StringItem */
if (itemType.isAssignableFrom(StringItem.class)) {
String val = ((StringType) command).toString();
switch (type) {
case "switchProgram":
KM200SwitchProgramService sPService = ((KM200SwitchProgramService) parObject.getValueParameter());
String[] servicePath = service.split("/");
String virtService = servicePath[servicePath.length - 1];
if (virtService.equals("weekday")) {
/* Only parameter changing without communication to device */
sPService.setActiveDay(val);
}
break;
}
/* Binding is a NumberItem */
} else if (itemType.isAssignableFrom(NumberItem.class)) {
Integer val = ((DecimalType) command).intValue();
switch (type) {
case "switchProgram":
KM200SwitchProgramService sPService = ((KM200SwitchProgramService) parObject.getValueParameter());
String[] servicePath = service.split("/");
String virtService = servicePath[servicePath.length - 1];
if (virtService.equals("cycle")) {
/* Only parameter changing without communication to device */
sPService.setActiveCycle(val);
} else if (virtService.equals(sPService.getPositiveSwitch())) {
sPService.setActivePositiveSwitch(val);
/* Create a JSON Array from current switch configuration */
dataToSend = sPService.getUpdatedJSONData(parObject);
} else if (virtService.equals(sPService.getNegativeSwitch())) {
sPService.setActiveNegativeSwitch(val);
/* Create a JSON Array from current switch configuration */
dataToSend = sPService.getUpdatedJSONData(parObject);
}
break;
case "errorList":
KM200ErrorService eService = ((KM200ErrorService) device.serviceMap.get(object.getParent())
.getValueParameter());
String[] nServicePath = service.split("/");
String nVirtService = nServicePath[nServicePath.length - 1];
if (nVirtService.equals("error")) {
/* Only parameter changing without communication to device */
eService.setActiveError(val);
}
break;
}
} else if (itemType.isAssignableFrom(DateTimeItem.class)) {
Calendar cal = ((DateTimeType) command).getCalendar();
KM200SwitchProgramService sPService = ((KM200SwitchProgramService) parObject.getValueParameter());
String[] servicePath = service.split("/");
String virtService = servicePath[servicePath.length - 1];
Integer minutes;
if (virtService.equals(sPService.getPositiveSwitch())) {
minutes = cal.get(Calendar.HOUR_OF_DAY) * 60 + cal.get(Calendar.MINUTE);
minutes = (minutes % sPService.getSwitchPointTimeRaster()) * sPService.getSwitchPointTimeRaster();
sPService.setActivePositiveSwitch(minutes);
/* Create a JSON Array from current switch configuration */
dataToSend = sPService.getUpdatedJSONData(parObject);
}
if (virtService.equals(sPService.getNegativeSwitch())) {
minutes = cal.get(Calendar.HOUR_OF_DAY) * 60 + cal.get(Calendar.MINUTE);
minutes = (minutes % sPService.getSwitchPointTimeRaster()) * sPService.getSwitchPointTimeRaster();
sPService.setActiveNegativeSwitch(minutes);
/* Create a JSON Array from current switch configuration */
dataToSend = sPService.getUpdatedJSONData(parObject);
}
}
return dataToSend;
}
}