added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.km200-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-km200" description="KM200 Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-mdns</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.km200/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link km200Binding} class defines common constants, which are
* used across the whole binding.
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200BindingConstants {
public static final String BINDING_ID = "km200";
public static final String CONFIG_DESCRIPTION_URI_CHANNEL = "channel-type:km200:config";
public static final String CONFIG_DESCRIPTION_URI_THING = "thing-type:km200:config";
// Bridge UID
public static final ThingTypeUID THING_TYPE_KMDEVICE = new ThingTypeUID(BINDING_ID, "kmdevice");
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_GATEWAY = new ThingTypeUID(BINDING_ID, "gateway");
public static final ThingTypeUID THING_TYPE_DHW_CIRCUIT = new ThingTypeUID(BINDING_ID, "dhwCircuit");
public static final ThingTypeUID THING_TYPE_HEATING_CIRCUIT = new ThingTypeUID(BINDING_ID, "heatingCircuit");
public static final ThingTypeUID THING_TYPE_SOLAR_CIRCUIT = new ThingTypeUID(BINDING_ID, "solarCircuit");
public static final ThingTypeUID THING_TYPE_HEAT_SOURCE = new ThingTypeUID(BINDING_ID, "heatSource");
public static final ThingTypeUID THING_TYPE_SYSTEM = new ThingTypeUID(BINDING_ID, "system");
public static final ThingTypeUID THING_TYPE_SYSTEM_APPLIANCE = new ThingTypeUID(BINDING_ID, "appliance");
public static final ThingTypeUID THING_TYPE_SYSTEM_HOLIDAYMODES = new ThingTypeUID(BINDING_ID, "holidayMode");
public static final ThingTypeUID THING_TYPE_SYSTEM_SENSOR = new ThingTypeUID(BINDING_ID, "sensor");
public static final ThingTypeUID THING_TYPE_NOTIFICATION = new ThingTypeUID(BINDING_ID, "notification");
public static final ThingTypeUID THING_TYPE_SWITCH_PROGRAM = new ThingTypeUID(BINDING_ID, "switchProgram");
public static final ThingTypeUID THING_TYPE_SYSTEMSTATES = new ThingTypeUID(BINDING_ID, "systemStates");
// KM200 DataTypes
public static final String DATA_TYPE_STRING_VALUE = "stringValue";
public static final String DATA_TYPE_FLOAT_VALUE = "floatValue";
public static final String DATA_TYPE_SWITCH_PROGRAM = "switchProgram";
public static final String DATA_TYPE_ERROR_LIST = "errorList";
public static final String DATA_TYPE_Y_RECORDING = "yRecording";
public static final String DATA_TYPE_SYSTEM_INFO = "systeminfo";
public static final String DATA_TYPE_ARRAY_DATA = "arrayData";
public static final String DATA_TYPE_E_MONITORING_LIST = "eMonitoringList";
public static final String DATA_TYPE_MODULE_LIST = "moduleList";
public static final String DATA_TYPE_REF_ENUM = "refEnum";
public static final String DATA_TYPE_PROTECTED = "$$PROTECTED$$";
// Other constants
public static final String SWITCH_PROGRAM_REPLACEMENT = "__current__";
public static final String SWITCH_PROGRAM_PATH_NAME = "switchPrograms";
public static final String SWITCH_PROGRAM_CURRENT_PATH_NAME = "activeSwitchProgram";
public static final String SWITCH_PROGRAM_POSITIVE = "activeSwitchProgramPos";
public static final String SWITCH_PROGRAM_NEGATIVE = "activeSwitchProgramNeg";
}

View File

@@ -0,0 +1,92 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.type.ChannelGroupType;
import org.openhab.core.thing.type.ChannelGroupTypeProvider;
import org.openhab.core.thing.type.ChannelGroupTypeUID;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeProvider;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.osgi.service.component.annotations.Component;
/**
* Extends the ChannelTypeProvider for user defined channel and channel group types.
*
* @author Markus Eckhardt - Initial contribution
*/
@Component(service = { ChannelTypeProvider.class, ChannelGroupTypeProvider.class,
KM200ChannelTypeProvider.class }, immediate = true)
@NonNullByDefault
public class KM200ChannelTypeProvider implements ChannelTypeProvider, ChannelGroupTypeProvider {
private final List<ChannelType> channelTypes = new CopyOnWriteArrayList<>();
private final List<ChannelGroupType> channelGroupTypes = new CopyOnWriteArrayList<>();
@Override
public Collection<ChannelType> getChannelTypes(@Nullable Locale locale) {
return channelTypes;
}
@Override
public @Nullable ChannelType getChannelType(ChannelTypeUID channelTypeUID, @Nullable Locale locale) {
for (ChannelType channelType : channelTypes) {
if (channelType.getUID().equals(channelTypeUID)) {
return channelType;
}
}
return null;
}
@Override
public Collection<ChannelGroupType> getChannelGroupTypes(@Nullable Locale locale) {
return channelGroupTypes;
}
@Override
public @Nullable ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID,
@Nullable Locale locale) {
for (ChannelGroupType channelGroupType : channelGroupTypes) {
if (channelGroupType.getUID().equals(channelGroupTypeUID)) {
return channelGroupType;
}
}
return null;
}
public void addChannelType(ChannelType type) {
channelTypes.add(type);
}
public void removeChannelType(ChannelType type) {
channelTypes.remove(type);
}
public void removeChannelTypesForThing(ThingUID uid) {
List<ChannelType> removes = new ArrayList<>();
for (ChannelType c : channelTypes) {
if (c.getUID().getAsString().startsWith(uid.getAsString())) {
removes.add(c);
}
}
channelTypes.removeAll(removes);
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The KM200Comm class does the communication to the device and does any encryption/decryption/converting jobs
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200Comm<KM200BindingProvider> {
private final Logger logger = LoggerFactory.getLogger(KM200Comm.class);
private final HttpClient httpClient;
private final KM200Device remoteDevice;
private Integer maxNbrRepeats;
public KM200Comm(KM200Device device, HttpClient httpClient) {
this.remoteDevice = device;
maxNbrRepeats = Integer.valueOf(10);
this.httpClient = httpClient;
}
/**
* This function sets the maximum number of repeats.
*/
public void setMaxNbrRepeats(Integer maxNbrRepeats) {
this.maxNbrRepeats = maxNbrRepeats;
}
/**
* This function does the GET http communication to the device
*/
public byte @Nullable [] getDataFromService(String service) {
byte[] responseBodyB64 = null;
int statusCode = 0;
ContentResponse contentResponse = null;
logger.debug("Starting receive connection...");
try {
// Create an instance of HttpClient.
for (int i = 0; i < maxNbrRepeats.intValue() && statusCode != HttpStatus.OK_200; i++) {
contentResponse = httpClient.newRequest(remoteDevice.getIP4Address() + service, 80).scheme("http")
.agent("TeleHeater/2.2.3").accept("application/json").method(HttpMethod.GET)
.timeout(5, TimeUnit.SECONDS).send();
// Execute the method.
statusCode = contentResponse.getStatus();
// Release the connection.
switch (statusCode) {
case HttpStatus.OK_200:
remoteDevice.setCharSet(StandardCharsets.UTF_8.name());
responseBodyB64 = contentResponse.getContent();
break;
case HttpStatus.INTERNAL_SERVER_ERROR_500:
/* Unknown problem with the device, wait and try again */
logger.debug("HTTP GET failed: 500, internal server error, repeating.. ");
Thread.sleep(100L * i + 1);
continue;
case HttpStatus.FORBIDDEN_403:
/* Service is available but not readable, return a byte array with a size of 1 as code */
byte[] serviceIsProtected = new byte[1];
responseBodyB64 = serviceIsProtected;
break;
case HttpStatus.NOT_FOUND_404:
/* Should only happen on discovery */
responseBodyB64 = null;
break;
default:
logger.debug("HTTP GET failed: {}", contentResponse.getReason());
responseBodyB64 = null;
break;
}
}
} catch (InterruptedException e) {
logger.debug("Sleep was interrupted: {}", e.getMessage());
} catch (TimeoutException e) {
logger.debug("Call to {} {} timed out", remoteDevice.getIP4Address(), service);
} catch (ExecutionException e) {
logger.debug("Fatal transport error: {}", e.getMessage());
}
return responseBodyB64;
}
/**
* This function does the SEND http communication to the device
*/
public Integer sendDataToService(String service, byte[] data) {
int rCode = 0;
ContentResponse contentResponse = null;
logger.debug("Starting send connection...");
try {
for (int i = 0; i < maxNbrRepeats.intValue() && rCode != HttpStatus.NO_CONTENT_204; i++) {
// Create a method instance.
contentResponse = httpClient.newRequest("http://" + remoteDevice.getIP4Address() + service)
.method(HttpMethod.POST).agent("TeleHeater/2.2.3").accept("application/json")
.content(new BytesContentProvider(data)).timeout(5, TimeUnit.SECONDS).send();
rCode = contentResponse.getStatus();
switch (rCode) {
case HttpStatus.NO_CONTENT_204: // The default return value
break;
case HttpStatus.LOCKED_423:
/* Unknown problem with the device, wait and try again */
logger.debug("HTTP POST failed: 423, locked, repeating.. ");
Thread.sleep(1000L * i + 1);
continue;
default:
logger.debug("HTTP POST failed: {}", contentResponse.getReason());
rCode = 0;
break;
}
}
} catch (InterruptedException e) {
logger.debug("Sleep was interrupted: {}", e.getMessage());
} catch (ExecutionException e) {
logger.debug("Fatal transport error: {}", e.getMessage());
} catch (TimeoutException e) {
logger.debug("Call to {} {} timed out.", remoteDevice.getIP4Address(), service);
}
return rCode;
}
}

View File

@@ -0,0 +1,184 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The KM200Cryption is managing the en- and decription of the communication to the device
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200Cryption {
private final Logger logger = LoggerFactory.getLogger(KM200Cryption.class);
private final KM200Device remoteDevice;
public KM200Cryption(KM200Device remoteDevice) {
this.remoteDevice = remoteDevice;
}
/**
* This function removes zero padding from a byte array.
*
*/
private 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.
*
*/
private byte[] addZeroPadding(byte[] bdata, int bSize, String cSet) throws UnsupportedEncodingException {
int encryptPadchar = bSize - (bdata.length % bSize);
byte[] padchars = new String(new char[encryptPadchar]).getBytes(cSet);
byte[] paddedData = new byte[bdata.length + padchars.length];
System.arraycopy(bdata, 0, paddedData, 0, bdata.length);
System.arraycopy(padchars, 0, paddedData, bdata.length, padchars.length);
return paddedData;
}
/**
* This function does the decoding for a new message from the device
*
*/
public @Nullable String decodeMessage(byte[] encoded) {
String retString = null;
byte[] decodedB64 = null;
// MimeDecoder was the only working decoder.
decodedB64 = Base64.getMimeDecoder().decode(encoded);
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, remoteDevice.getCharSet());
logger.debug("Did NOT decrypt message");
return retString;
}
// --- create cipher
final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(remoteDevice.getCryptKeyPriv(), "AES"));
byte[] decryptedData = cipher.doFinal(decodedB64);
byte[] decryptedDataWOZP = removeZeroPadding(decryptedData);
return (new String(decryptedDataWOZP, remoteDevice.getCharSet()));
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
logger.warn("Exception on encoding", e);
return null;
}
}
/**
* This function does the encoding for a new message to the device
*
*/
public byte @Nullable [] encodeMessage(String data) {
try {
// --- create cipher
byte[] bdata = data.getBytes(remoteDevice.getCharSet());
final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(remoteDevice.getCryptKeyPriv(), "AES"));
int bsize = cipher.getBlockSize();
/* Add Padding, encrypt AES and B64 */
byte[] encryptedData = cipher.doFinal(addZeroPadding(bdata, bsize, remoteDevice.getCharSet()));
try {
return (Base64.getMimeEncoder().encode(encryptedData));
} catch (IllegalArgumentException e) {
logger.debug("Base64encoding not possible: {}", e.getMessage());
}
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
logger.warn("Exception on encoding", e);
}
return null;
}
/**
* This function creates the private key from the MD5Salt, the device and the private password
*
* @author Markus Eckhardt
*/
public void recreateKeys() {
if (StringUtils.isNotBlank(remoteDevice.getGatewayPassword())
&& StringUtils.isNotBlank(remoteDevice.getPrivatePassword()) && remoteDevice.getMD5Salt().length > 0) {
byte[] md5K1 = null;
byte[] md5K2Init = null;
byte[] md5K2Private = null;
byte[] bytesOfGatewayPassword = null;
byte[] bytesOfPrivatePassword = null;
/* Needed keys for the communication */
byte[] cryptKeyInit;
byte[] cryptKeyPriv;
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
logger.warn("No such algorithm, MD5: {}", e.getMessage());
return;
}
/* First half of the key: MD5 of (GatewayPassword . Salt) */
bytesOfGatewayPassword = remoteDevice.getGatewayPassword().getBytes(StandardCharsets.UTF_8);
byte[] combParts1 = new byte[bytesOfGatewayPassword.length + remoteDevice.getMD5Salt().length];
System.arraycopy(bytesOfGatewayPassword, 0, combParts1, 0, bytesOfGatewayPassword.length);
System.arraycopy(remoteDevice.getMD5Salt(), 0, combParts1, bytesOfGatewayPassword.length,
remoteDevice.getMD5Salt().length);
md5K1 = md.digest(combParts1);
/* Second half of the key: - Initial: MD5 of ( Salt) */
md5K2Init = md.digest(remoteDevice.getMD5Salt());
/* Second half of the key: - private: MD5 of ( Salt . PrivatePassword) */
bytesOfPrivatePassword = remoteDevice.getPrivatePassword().getBytes(StandardCharsets.UTF_8);
byte[] combParts2 = new byte[bytesOfPrivatePassword.length + remoteDevice.getMD5Salt().length];
System.arraycopy(remoteDevice.getMD5Salt(), 0, combParts2, 0, remoteDevice.getMD5Salt().length);
System.arraycopy(bytesOfPrivatePassword, 0, combParts2, remoteDevice.getMD5Salt().length,
bytesOfPrivatePassword.length);
md5K2Private = md.digest(combParts2);
/* Create Keys */
cryptKeyInit = new byte[md5K1.length + md5K2Init.length];
System.arraycopy(md5K1, 0, cryptKeyInit, 0, md5K1.length);
System.arraycopy(md5K2Init, 0, cryptKeyInit, md5K1.length, md5K2Init.length);
remoteDevice.setCryptKeyInit(cryptKeyInit);
cryptKeyPriv = new byte[md5K1.length + md5K2Private.length];
System.arraycopy(md5K1, 0, cryptKeyPriv, 0, md5K1.length);
System.arraycopy(md5K2Private, 0, cryptKeyPriv, md5K1.length, md5K2Private.length);
remoteDevice.setCryptKeyPriv(cryptKeyPriv);
}
}
}

View File

@@ -0,0 +1,396 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.core.util.HexUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
/**
* The KM200Device representing the device with its all capabilities
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200Device {
private final Logger logger = LoggerFactory.getLogger(KM200Device.class);
private final JsonParser jsonParser = new JsonParser();
private final KM200Cryption comCryption;
private final KM200Comm<KM200Device> deviceCommunicator;
/* valid IPv4 address of the KMxxx. */
protected String ip4Address = "";
/* The gateway password which is provided on the type sign of the KMxxx. */
protected String gatewayPassword = "";
/* The private password which has been defined by the user via EasyControl. */
protected String privatePassword = "";
/* The returned device charset for communication */
protected String charSet = "";
/* Needed keys for the communication */
protected byte[] cryptKeyInit = ArrayUtils.EMPTY_BYTE_ARRAY;
protected byte[] cryptKeyPriv = ArrayUtils.EMPTY_BYTE_ARRAY;
/* Buderus_MD5Salt */
protected byte[] md5Salt = ArrayUtils.EMPTY_BYTE_ARRAY;
/* Device services */
public Map<String, KM200ServiceObject> serviceTreeMap;
/* Device services blacklist */
private List<String> blacklistMap = new ArrayList<>();
/* List of virtual services */
public List<KM200ServiceObject> virtualList;
/* Is the first INIT done */
protected boolean isIited;
public KM200Device(HttpClient httpClient) {
serviceTreeMap = new HashMap<>();
getBlacklistMap().add("/gateway/firmware");
virtualList = new ArrayList<>();
comCryption = new KM200Cryption(this);
deviceCommunicator = new KM200Comm<>(this, httpClient);
}
public Boolean isConfigured() {
return StringUtils.isNotBlank(ip4Address) && cryptKeyPriv.length > 0;
}
public String getIP4Address() {
return ip4Address;
}
public String getGatewayPassword() {
return gatewayPassword;
}
public String getPrivatePassword() {
return privatePassword;
}
public byte[] getMD5Salt() {
return md5Salt;
}
public byte[] getCryptKeyInit() {
return cryptKeyInit;
}
public byte[] getCryptKeyPriv() {
return cryptKeyPriv;
}
public String getCharSet() {
return charSet;
}
public boolean getInited() {
return isIited;
}
public List<String> getBlacklistMap() {
return blacklistMap;
}
public void setBlacklistMap(List<String> blacklistMap) {
this.blacklistMap = blacklistMap;
}
public void setIP4Address(String ip) {
ip4Address = ip;
}
public void setGatewayPassword(String password) {
gatewayPassword = password;
comCryption.recreateKeys();
}
public void setPrivatePassword(String password) {
privatePassword = password;
comCryption.recreateKeys();
}
public void setMD5Salt(String salt) {
if (!salt.isEmpty()) {
md5Salt = HexUtils.hexToBytes(salt);
comCryption.recreateKeys();
} else {
md5Salt = new byte[] { 0 };
}
}
public void setCryptKeyPriv(String key) {
if (!key.isEmpty()) {
cryptKeyPriv = HexUtils.hexToBytes(key);
} else {
cryptKeyPriv = new byte[] { 0 };
}
}
public void setCryptKeyPriv(byte[] key) {
cryptKeyPriv = key;
}
public void setCryptKeyInit(byte[] key) {
cryptKeyInit = key;
}
public void setCharSet(String charset) {
charSet = charset;
}
public void setInited(boolean inited) {
isIited = inited;
}
public void setMaxNbrRepeats(Integer maxNbrRepeats) {
this.deviceCommunicator.setMaxNbrRepeats(maxNbrRepeats);
}
/**
* This function prepares a list of all on the device available services with its capabilities
*
*/
public void listAllServices() {
logger.debug("##################################################################");
logger.debug("List of avalible services");
logger.debug("readable;writeable;recordable;virtual;type;service;value;allowed;min;max;unit");
printAllServices(serviceTreeMap);
logger.debug("##################################################################");
}
/**
* This function outputs a ";" separated list of all on the device available services with its capabilities
*
* @param actTreeMap
*/
public void printAllServices(Map<String, KM200ServiceObject> actTreeMap) {
if (logger.isDebugEnabled()) {
for (KM200ServiceObject object : actTreeMap.values()) {
String val = "", type;
StringBuilder valPara = new StringBuilder();
logger.debug("List type: {} service: {}", object.getServiceType(), object.getFullServiceName());
type = object.getServiceType();
if (DATA_TYPE_STRING_VALUE.equals(type) || DATA_TYPE_FLOAT_VALUE.equals(type)) {
Object valObject = object.getValue();
if (null != valObject) {
val = valObject.toString();
if (object.getValueParameter() != null) {
if (DATA_TYPE_STRING_VALUE.equals(type)) {
// Type is definitely correct here
@SuppressWarnings("unchecked")
List<String> valParas = (List<String>) object.getValueParameter();
if (null != valParas) {
for (int i = 0; i < valParas.size(); i++) {
if (i > 0) {
valPara.append("|");
}
valPara.append(valParas.get(i));
}
valPara.append(";;;");
}
}
if (DATA_TYPE_FLOAT_VALUE.equals(type)) {
// Type is definitely correct here
@SuppressWarnings("unchecked")
List<Object> valParas = (List<Object>) object.getValueParameter();
if (null != valParas) {
valPara.append(";");
valPara.append(valParas.get(0));
valPara.append(";");
valPara.append(valParas.get(1));
valPara.append(";");
if (valParas.size() == 3) {
valPara.append(valParas.get(2));
}
}
}
} else {
valPara.append(";;;");
}
} else {
val = "";
valPara.append(";");
}
}
logger.debug("{};{};{};{};{};{};{};{}", object.getReadable(), object.getWriteable(),
object.getRecordable(), object.getVirtual(), type, object.getFullServiceName(), val, valPara);
printAllServices(object.serviceTreeMap);
}
}
}
/**
* This function resets the update state on all service objects
*
* @param actTreeMap
*/
public void resetAllUpdates(Map<String, KM200ServiceObject> actTreeMap) {
for (KM200ServiceObject stmObject : actTreeMap.values()) {
stmObject.setUpdated(false);
resetAllUpdates(stmObject.serviceTreeMap);
}
}
/**
* This function checks whether a service is available
*
* @param service
*/
public Boolean containsService(String service) {
String[] servicePath = service.split("/");
KM200ServiceObject object = null;
int len = servicePath.length;
if (len == 0) {
return false;
}
if (!serviceTreeMap.containsKey(servicePath[1])) {
return false;
} else {
if (len == 2) {
return true;
}
object = serviceTreeMap.get(servicePath[1]);
}
for (int i = 2; i < len; i++) {
if (object.serviceTreeMap.containsKey(servicePath[i])) {
object = object.serviceTreeMap.get(servicePath[i]);
continue;
} else {
return false;
}
}
return true;
}
/**
* This function return the KM200CommObject of a service
*
* @param service
*/
public @Nullable KM200ServiceObject getServiceObject(String service) {
String[] servicePath = service.split("/");
KM200ServiceObject object = null;
int len = servicePath.length;
if (len == 0) {
return null;
}
if (!serviceTreeMap.containsKey(servicePath[1])) {
return null;
} else {
object = serviceTreeMap.get(servicePath[1]);
if (len == 2) {
return object;
}
}
for (int i = 2; i < len; i++) {
if (object.serviceTreeMap.containsKey(servicePath[i])) {
object = object.serviceTreeMap.get(servicePath[i]);
continue;
} else {
return null;
}
}
return object;
}
/**
* This function checks the communication to a service on the device. It returns null if the communication is not
* possible and a JSON node in opposide case.
*
*/
public @Nullable JsonObject getServiceNode(String service) {
String decodedData = null;
JsonObject nodeRoot = null;
byte[] recData = deviceCommunicator.getDataFromService(service.toString());
try {
if (recData == null) {
logger.debug("Communication to {} is not possible!", service);
return null;
}
if (recData.length == 0) {
logger.debug("No reply from KM200!");
return null;
}
/* Look whether the communication was forbidden */
if (recData.length == 1) {
logger.debug("{}: recData.length == 1", service);
nodeRoot = new JsonObject();
decodedData = "";
return nodeRoot;
} else {
decodedData = comCryption.decodeMessage(recData);
if (decodedData == null) {
logger.warn("Decoding of the KM200 message is not possible!");
return null;
}
}
if (decodedData.length() > 0) {
if ("SERVICE NOT AVAILABLE".equals(decodedData)) {
logger.debug("{}: SERVICE NOT AVAILABLE", service);
return null;
} else {
nodeRoot = (JsonObject) jsonParser.parse(decodedData);
}
} else {
logger.debug("Get empty reply");
return null;
}
} catch (JsonParseException e) {
logger.warn("Parsingexception in JSON: {} service: {}", e.getMessage(), service);
return null;
}
return nodeRoot;
}
/**
* This function checks the communication to a service on the device. It returns null if the communication is not
* possible and a JSON node in opposide case.
*
*/
public void setServiceNode(String service, JsonObject newObject) {
int retVal;
byte[] encData = comCryption.encodeMessage(newObject.toString());
if (encData == null) {
logger.warn("Couldn't encrypt data");
return;
} else {
logger.debug("Send: {}", service);
retVal = deviceCommunicator.sendDataToService(service, encData);
if (retVal == 0) {
logger.debug("Send to device failed: {}: {}", service, newObject);
}
}
}
}

View File

@@ -0,0 +1,123 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.km200.internal.discovery.KM200GatewayDiscoveryService;
import org.openhab.binding.km200.internal.handler.KM200GatewayHandler;
import org.openhab.binding.km200.internal.handler.KM200ThingHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KM200HandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.km200")
public class KM200HandlerFactory extends BaseThingHandlerFactory {
public final Set<ThingTypeUID> SUPPORTED_ALL_THING_TYPES_UIDS = Collections
.unmodifiableSet(Stream.concat(KM200GatewayHandler.SUPPORTED_THING_TYPES_UIDS.stream(),
KM200ThingHandler.SUPPORTED_THING_TYPES_UIDS.stream()).collect(Collectors.toSet()));
private Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
private final Logger logger = LoggerFactory.getLogger(KM200HandlerFactory.class);
private final KM200ChannelTypeProvider channelTypeProvider;
/**
* shared instance of HTTP client for asynchronous calls
*/
private final HttpClient httpClient;
@Activate
public KM200HandlerFactory(@Reference HttpClientFactory httpClientFactory,
@Reference KM200ChannelTypeProvider channelTypeProvider) {
this.httpClient = httpClientFactory.getCommonHttpClient();
this.channelTypeProvider = channelTypeProvider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_ALL_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, ThingUID thingUID) {
return createThing(thingTypeUID, configuration, thingUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (KM200GatewayHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
logger.debug("It's a gataway: {}", thingTypeUID.getAsString());
KM200GatewayHandler gatewayHandler = new KM200GatewayHandler((Bridge) thing, httpClient);
registerKM200GatewayDiscoveryService(gatewayHandler);
return gatewayHandler;
} else if (KM200ThingHandler.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID)) {
logger.debug("It's a thing: {}", thingTypeUID.getAsString());
return new KM200ThingHandler(thing, channelTypeProvider);
} else {
return null;
}
}
@Override
protected synchronized void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof KM200GatewayHandler) {
ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.get(thingHandler.getThing().getUID());
serviceReg.unregister();
discoveryServiceRegs.remove(thingHandler.getThing().getUID());
}
}
/**
* Adds KM200GatewayHandler to the discovery service to find the KMXXX device
*
* @param gatewayHandler
*/
private synchronized void registerKM200GatewayDiscoveryService(KM200GatewayHandler gatewayHandler) {
KM200GatewayDiscoveryService discoveryService = new KM200GatewayDiscoveryService(gatewayHandler);
this.discoveryServiceRegs.put(gatewayHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
}

View File

@@ -0,0 +1,119 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonObject;
/**
* The KM200CommObject representing a service on a device with its all capabilities
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200ServiceObject {
private int readable;
private int writeable;
private int recordable;
private int virtual;
private boolean updated;
private @Nullable String parent;
private String fullServiceName;
private String serviceType;
private @Nullable JsonObject jsonData;
private @Nullable Object value;
private @Nullable Object valueParameter;
/* Device services */
public Map<String, KM200ServiceObject> serviceTreeMap;
public KM200ServiceObject(String fullServiceName, String serviceType, int readable, int writeable, int recordable,
int virtual, @Nullable String parent) {
serviceTreeMap = new HashMap<>();
this.fullServiceName = fullServiceName;
this.serviceType = serviceType;
this.readable = readable;
this.writeable = writeable;
this.recordable = recordable;
this.virtual = virtual;
this.parent = parent;
updated = false;
}
/* Sets */
public void setValue(Object val) {
value = val;
}
public void setUpdated(boolean updt) {
updated = updt;
}
public void setValueParameter(Object val) {
valueParameter = val;
}
public void setJSONData(JsonObject data) {
jsonData = data;
}
/* gets */
public int getReadable() {
return readable;
}
public int getWriteable() {
return writeable;
}
public int getRecordable() {
return recordable;
}
public String getServiceType() {
return serviceType;
}
public String getFullServiceName() {
return fullServiceName;
}
public @Nullable Object getValue() {
return value;
}
public @Nullable Object getValueParameter() {
return valueParameter;
}
public @Nullable String getParent() {
return parent;
}
public int getVirtual() {
return virtual;
}
public boolean getUpdated() {
return updated;
}
public @Nullable JsonObject getJSONData() {
return jsonData;
}
}

View File

@@ -0,0 +1,140 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.openhab.core.thing.ThingTypeUID;
/**
* The KM200ThingType enum is representing the things
*
* @author Markus Eckhardt - Initial contribution
*
*/
public enum KM200ThingType {
GATEWAY("/gateway", KM200BindingConstants.THING_TYPE_GATEWAY) {
@Override
public List<String> asBridgeProperties() {
List<String> asProperties = new ArrayList<>();
asProperties.add("versionFirmware");
asProperties.add("instAccess");
asProperties.add("versionHardware");
asProperties.add("uuid");
asProperties.add("instWriteAccess");
asProperties.add("openIPAccess");
return asProperties;
}
},
DHWCIRCUIT("/dhwCircuits", KM200BindingConstants.THING_TYPE_DHW_CIRCUIT) {
@Override
public List<String> ignoreSubService() {
List<String> subServices = new ArrayList<>();
subServices.add("switchPrograms");
return subServices;
}
@Override
public String getActiveCheckSubPath() {
return "status";
}
},
HEATINGCIRCUIT("/heatingCircuits", KM200BindingConstants.THING_TYPE_HEATING_CIRCUIT) {
@Override
public List<String> ignoreSubService() {
List<String> subServices = new ArrayList<>();
subServices.add("switchPrograms");
return subServices;
}
@Override
public String getActiveCheckSubPath() {
return "status";
}
},
HEATSOURCE("/heatSources", KM200BindingConstants.THING_TYPE_HEAT_SOURCE),
SOLARCIRCUIT("/solarCircuits", KM200BindingConstants.THING_TYPE_SOLAR_CIRCUIT) {
@Override
public String getActiveCheckSubPath() {
return "status";
}
},
APPLIANCE("/system/appliance", KM200BindingConstants.THING_TYPE_SYSTEM_APPLIANCE),
HOLIDAYMODES("/system/holidayModes", KM200BindingConstants.THING_TYPE_SYSTEM_HOLIDAYMODES),
NOTIFICATIONS("/notifications", KM200BindingConstants.THING_TYPE_NOTIFICATION),
SENSOR("/system/sensors", KM200BindingConstants.THING_TYPE_SYSTEM_SENSOR),
SYSTEM("/system", KM200BindingConstants.THING_TYPE_SYSTEM) {
@Override
public List<String> ignoreSubService() {
List<String> subServices = new ArrayList<>();
subServices.add("sensors");
subServices.add("appliance");
subServices.add("holidayModes");
return subServices;
}
@Override
public List<String> asBridgeProperties() {
List<String> asProperties = new ArrayList<>();
asProperties.add("bus");
asProperties.add("systemType");
asProperties.add("brand");
asProperties.add("info");
return asProperties;
}
},
SWITCHPROGRAM("", KM200BindingConstants.THING_TYPE_SWITCH_PROGRAM),
SYSTEMSTATES("/systemStates", KM200BindingConstants.THING_TYPE_SYSTEMSTATES);
public final String rootPath;
public final ThingTypeUID thingTypeUID;
KM200ThingType(String rootPath, ThingTypeUID thingTypeUID) {
this.rootPath = rootPath;
this.thingTypeUID = thingTypeUID;
}
public String getRootPath() {
return rootPath;
}
public ThingTypeUID getThingTypeUID() {
return thingTypeUID;
}
public List<String> ignoreSubService() {
return Collections.emptyList();
}
public String getActiveCheckSubPath() {
return null;
}
public List<String> asBridgeProperties() {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal;
import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The KM200Utils is a class with common utilities.
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200Utils {
private static final Logger LOGGER = LoggerFactory.getLogger(KM200Utils.class);
/**
* Translates a service name to a service path (Replaces # through /)
*
*/
public static String translatesNameToPath(String name) {
return name.replace("#", "/");
}
/**
* Translates a service path to a service name (Replaces / through #)
*
*/
public static String translatesPathToName(String path) {
return path.replace("/", "#");
}
/**
* This function checks whether the service has a replacement parameter
*
*/
public static String checkParameterReplacement(Channel channel, KM200Device device) {
String service = KM200Utils.translatesNameToPath(channel.getProperties().get("root"));
if (service.contains(SWITCH_PROGRAM_REPLACEMENT)) {
String currentService = KM200Utils
.translatesNameToPath(channel.getProperties().get(SWITCH_PROGRAM_CURRENT_PATH_NAME));
if (device.containsService(currentService)) {
KM200ServiceObject curSerObj = device.getServiceObject(currentService);
if (null != curSerObj) {
if (DATA_TYPE_STRING_VALUE.equals(curSerObj.getServiceType())) {
String val = (String) curSerObj.getValue();
service = service.replace(SWITCH_PROGRAM_REPLACEMENT, val);
return service;
}
}
}
}
return service;
}
/**
* This function checks whether the channel has channel parameters
*
*/
public static Map<String, String> getChannelConfigurationStrings(Channel channel) {
Map<String, String> paraNames = new HashMap<>();
if (channel.getConfiguration().containsKey("on")) {
paraNames.put("on", channel.getConfiguration().get("on").toString());
LOGGER.debug("Added ON: {}", channel.getConfiguration().get("on"));
}
if (channel.getConfiguration().containsKey("off")) {
paraNames.put("off", channel.getConfiguration().get("off").toString());
LOGGER.debug("Added OFF: {}", channel.getConfiguration().get("off"));
}
return paraNames;
}
}

View File

@@ -0,0 +1,114 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.discovery;
import static org.openhab.binding.km200.internal.KM200BindingConstants.THING_TYPE_KMDEVICE;
import java.net.InetAddress;
import java.util.Random;
import java.util.Set;
import javax.jmdns.ServiceInfo;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.km200.internal.handler.KM200GatewayHandler;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KM200GatewayDiscoveryParticipant} class discovers gateways and adds the results to the inbox.
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
@Component(immediate = true, configurationPid = "binding.km200")
public class KM200GatewayDiscoveryParticipant implements MDNSDiscoveryParticipant {
private final Logger logger = LoggerFactory.getLogger(KM200GatewayDiscoveryParticipant.class);
public static final Set<ThingTypeUID> SUPPORTED_ALL_THING_TYPES_UIDS = KM200GatewayHandler.SUPPORTED_THING_TYPES_UIDS;
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return SUPPORTED_ALL_THING_TYPES_UIDS;
}
@Override
public @Nullable DiscoveryResult createResult(ServiceInfo info) {
DiscoveryResult discoveryResult = null;
ThingUID uid = getThingUID(info);
logger.debug("MDNS info: {}, uid: {}", info, uid);
if (uid != null) {
InetAddress[] addrs = info.getInetAddresses();
logger.debug("ip: {} id:{}", addrs[0].getHostAddress(), uid.getId());
discoveryResult = DiscoveryResultBuilder.create(uid).withProperty("ip4Address", addrs[0].getHostAddress())
.withProperty("deviceId", uid.getId()).withRepresentationProperty(addrs[0].getHostAddress())
.withLabel("KM50/100/200 Gateway (" + addrs[0].getHostAddress() + ")").build();
return discoveryResult;
}
return null;
}
@Override
public @Nullable ThingUID getThingUID(ServiceInfo info) {
ThingTypeUID typeUID = getThingTypeUID(info);
if (typeUID != null) {
logger.debug("getType: {}", info.getType());
if (info.getType() != null) {
if (info.getType().equalsIgnoreCase(getServiceType())) {
String devId = info.getPropertyString("uuid");
if (null != devId) {
logger.info("Discovered a KMXXX gateway with name: '{}' id: '{}'", info.getName(), devId);
if (devId.isEmpty()) {
/* If something is wrong then we are generating a random UUID */
logger.debug("Error in automatic device-id detection. Using random value");
Random rnd = new Random();
devId = String.valueOf(rnd.nextLong());
}
ThingUID thinguid = new ThingUID(typeUID, devId);
return thinguid;
} else {
logger.debug("No uuid property found");
}
}
}
}
return null;
}
@Override
public String getServiceType() {
return "_http._tcp.local.";
}
private @Nullable ThingTypeUID getThingTypeUID(ServiceInfo info) {
InetAddress[] addrs = info.getInetAddresses();
if (addrs.length > 0) {
String hardwareID;
hardwareID = info.getPropertyString("hwversion");
logger.debug("hardwareID: {}", hardwareID);
if (hardwareID != null && hardwareID.contains("iCom_Low")) {
return THING_TYPE_KMDEVICE;
}
}
return null;
}
}

View File

@@ -0,0 +1,191 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.discovery;
import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.km200.internal.KM200ServiceObject;
import org.openhab.binding.km200.internal.KM200ThingType;
import org.openhab.binding.km200.internal.KM200Utils;
import org.openhab.binding.km200.internal.handler.KM200GatewayHandler;
import org.openhab.binding.km200.internal.handler.KM200GatewayStatusListener;
import org.openhab.binding.km200.internal.handler.KM200SwitchProgramServiceHandler;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KM200GatewayDiscoveryService} class discovers things through a gateway
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200GatewayDiscoveryService extends AbstractDiscoveryService implements KM200GatewayStatusListener {
private final Logger logger = LoggerFactory.getLogger(KM200GatewayDiscoveryService.class);
private static int timeOut = 120;
KM200GatewayHandler gateway;
public KM200GatewayDiscoveryService(KM200GatewayHandler gateway) {
super(KM200GatewayHandler.SUPPORTED_THING_TYPES_UIDS, timeOut, true);
this.gateway = gateway;
this.gateway.addGatewayStatusListener(this);
}
@Override
protected void startScan() {
discoverDevices();
}
@Override
protected void startBackgroundDiscovery() {
discoverDevices();
}
@Override
public void gatewayStatusChanged(ThingStatus status) {
if (status.equals(ThingStatus.ONLINE)) {
discoverDevices();
}
}
@Override
protected void deactivate() {
gateway.removeHubStatusListener(this);
super.deactivate();
}
/**
* Discovers devices connected to a hub
*/
private void discoverDevices() {
if (!gateway.getDevice().getInited()) {
logger.debug("Gateway not configured, scanning postponed.");
return;
}
ThingUID thingUID = null;
ThingUID bridgeUID = gateway.getThing().getUID();
for (KM200ThingType tType : KM200ThingType.values()) {
String root = tType.getRootPath();
if (root.isEmpty()) {
continue;
}
String checkService = tType.getActiveCheckSubPath();
if (gateway.getDevice().containsService(root)) {
boolean enumOnly = true;
KM200ServiceObject object = gateway.getDevice().getServiceObject(root);
if (null == object) {
logger.warn("No root service object found");
return;
}
Set<String> keys = object.serviceTreeMap.keySet();
/* Check whether all sub services are refEnum */
for (String key : keys) {
if (!DATA_TYPE_REF_ENUM.equals(object.serviceTreeMap.get(key).getServiceType())) {
enumOnly = false;
break;
}
}
/* If there are refEnum only, then create for every one an own thing */
if (enumOnly) {
for (String key : keys) {
/* Check whether this part of heating system is inactive. If its then ignore it */
if (checkService != null) {
String checkServicePath = root + "/" + key + "/" + checkService;
if (gateway.getDevice().containsService(checkServicePath)) {
KM200ServiceObject serviceObject = gateway.getDevice()
.getServiceObject(checkServicePath);
if (null != serviceObject) {
Object val = serviceObject.getValue();
if (null != val && "INACTIVE".equals(val)) {
continue;
}
}
}
}
thingUID = new ThingUID(tType.getThingTypeUID(), bridgeUID, key);
Map<String, Object> properties = new HashMap<>(1);
properties.put("root", KM200Utils.translatesPathToName(root) + "#" + key);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withLabel(key).withProperties(properties).build();
thingDiscovered(discoveryResult);
if (object.serviceTreeMap.get(key).serviceTreeMap.containsKey(SWITCH_PROGRAM_PATH_NAME)) {
String currentPathName = root + "/" + key + "/" + SWITCH_PROGRAM_CURRENT_PATH_NAME;
String currParaRepl = SWITCH_PROGRAM_REPLACEMENT;
boolean currExists = object.serviceTreeMap.get(key).serviceTreeMap
.containsKey(SWITCH_PROGRAM_CURRENT_PATH_NAME);
KM200ServiceObject switchObject = object.serviceTreeMap.get(key).serviceTreeMap
.get(SWITCH_PROGRAM_PATH_NAME);
if (switchObject.serviceTreeMap.isEmpty()) {
continue;
}
/*
* if the device has only one switching program then the "activeSwitchProgram" service is
* not existing. In this case we are using a fix path to this one service.
*/
if (!currExists) {
if (switchObject.serviceTreeMap.keySet().size() == 1) {
currParaRepl = switchObject.serviceTreeMap.entrySet().iterator().next().getKey();
}
}
KM200SwitchProgramServiceHandler valPara = (KM200SwitchProgramServiceHandler) switchObject.serviceTreeMap
.entrySet().iterator().next().getValue().getValueParameter();
if (null != valPara) {
String posName = valPara.getPositiveSwitch();
String negName = valPara.getNegativeSwitch();
if (null == posName || null == negName) {
logger.warn("Service switches not found!");
return;
}
ThingUID subThingUID = new ThingUID(tType.getThingTypeUID(), bridgeUID,
key + "-switchprogram");
Map<String, Object> subProperties = new HashMap<>(4);
subProperties.put("root", KM200Utils.translatesPathToName(
root + "/" + key + "/" + SWITCH_PROGRAM_PATH_NAME + "/" + currParaRepl));
subProperties.put(SWITCH_PROGRAM_CURRENT_PATH_NAME,
KM200Utils.translatesPathToName(currentPathName));
subProperties.put(SWITCH_PROGRAM_POSITIVE, posName);
subProperties.put(SWITCH_PROGRAM_NEGATIVE, negName);
DiscoveryResult subDiscoveryResult = DiscoveryResultBuilder.create(subThingUID)
.withBridge(bridgeUID).withLabel(key + " switch program")
.withProperties(subProperties).build();
thingDiscovered(subDiscoveryResult);
}
}
}
} else {
String[] sParts = root.split("/");
String key = sParts[sParts.length - 1];
thingUID = new ThingUID(tType.getThingTypeUID(), bridgeUID, key);
Map<String, Object> properties = new HashMap<>(1);
properties.put("root", KM200Utils.translatesPathToName(root));
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withLabel(key).withProperties(properties).build();
thingDiscovered(discoveryResult);
}
}
}
}
}

View File

@@ -0,0 +1,704 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.handler;
import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.km200.internal.KM200Device;
import org.openhab.binding.km200.internal.KM200ServiceObject;
import org.openhab.core.library.CoreItemFactory;
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.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
/**
* The KM200DataHandler is managing the data handling between the device and items
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200DataHandler {
private final Logger logger = LoggerFactory.getLogger(KM200DataHandler.class);
private final JsonParser jsonParser = new JsonParser();
private final KM200Device remoteDevice;
public KM200DataHandler(KM200Device remoteDevice) {
this.remoteDevice = remoteDevice;
}
/**
* This function checks the state of a service on the device
*/
public @Nullable State getProvidersState(String service, String itemType, Map<String, String> itemPara) {
synchronized (remoteDevice) {
String type = null;
KM200ServiceObject object = null;
JsonObject jsonNode = null;
logger.trace("Check state of: {} item: {}", service, itemType);
if (remoteDevice.getBlacklistMap().contains(service)) {
logger.warn("Service on blacklist: {}", service);
return null;
}
if (remoteDevice.containsService(service)) {
object = remoteDevice.getServiceObject(service);
if (null == object) {
logger.warn("Serviceobject does not exist");
return null;
}
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;
}
/* Needs to be updated? */
if (object.getVirtual() == 0) {
if (!object.getUpdated()) {
jsonNode = remoteDevice.getServiceNode(service);
if (jsonNode == null || jsonNode.isJsonNull()) {
logger.warn("Communication is not possible!");
return null;
}
object.setJSONData(jsonNode);
object.setUpdated(true);
} else {
/* If already updated then use the saved data */
jsonNode = object.getJSONData();
}
} else {
/* For using of virtual services only one receive on the parent service is needed */
String parent = object.getParent();
if (null != parent) {
KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
if (null != objParent) {
if (!objParent.getUpdated()) {
/* If it's a virtual service then receive the data from parent service */
jsonNode = remoteDevice.getServiceNode(parent);
if (jsonNode == null || jsonNode.isJsonNull()) {
logger.warn("Communication is not possible!");
return null;
}
objParent.setJSONData(jsonNode);
objParent.setUpdated(true);
object.setUpdated(true);
} else {
/* If already updated then use the saved data */
jsonNode = objParent.getJSONData();
}
}
}
}
if (null != jsonNode) {
return parseJSONData(jsonNode, type, service, itemType, itemPara);
} else {
return null;
}
}
}
/**
* This function parses the receviced JSON Data and return the right state
*/
public @Nullable State parseJSONData(JsonObject nodeRoot, String type, String service, String itemType,
Map<String, String> itemPara) {
State state = null;
KM200ServiceObject object = remoteDevice.getServiceObject(service);
if (null == object) {
return null;
}
String parent = object.getParent();
logger.trace("parseJSONData service: {}, data: {}", service, nodeRoot);
/* Now parsing of the JSON String depending on its type and the type of binding item */
try {
if (nodeRoot.toString().length() == 2) {
logger.warn("Get empty reply");
return null;
}
switch (type) {
case DATA_TYPE_STRING_VALUE: /* Check whether the type is a single value containing a string value */
logger.debug("parseJSONData type string value: {} Type: {}", nodeRoot, itemType.toString());
String sVal = nodeRoot.get("value").getAsString();
object.setValue(sVal);
/* Switch Binding */
if ("Switch".equals(itemType)) {
// type is definitely correct here
Map<String, String> switchNames = itemPara;
if (switchNames.containsKey("on")) {
if (sVal.equals(switchNames.get("off"))) {
state = OnOffType.OFF;
} else if (sVal.equals(switchNames.get("on"))) {
state = OnOffType.ON;
}
} else if (switchNames.isEmpty()) {
logger.debug("No switch item configuration");
return null;
} else {
logger.warn("Switch-Item only on configured on/off string values: {}", nodeRoot);
return null;
}
/* NumberItem Binding */
} else if (CoreItemFactory.NUMBER.equals(itemType)) {
try {
state = new DecimalType(Float.parseFloat(sVal));
} catch (NumberFormatException e) {
logger.warn("Conversion of the string value to Decimal wasn't possible, data: {} error: {}",
nodeRoot, e.getMessage());
return null;
}
/* DateTimeItem Binding */
} else if (CoreItemFactory.DATETIME.equals(itemType)) {
try {
state = new DateTimeType(sVal);
} catch (IllegalArgumentException e) {
logger.warn(
"Conversion of the string value to DateTime wasn't possible, data: {} error: {}",
nodeRoot, e.getMessage());
return null;
}
/* StringItem Binding */
} else if (CoreItemFactory.STRING.equals(itemType)) {
state = new StringType(sVal);
} else {
logger.info("Bindingtype not supported for string values: {}", itemType.getClass());
return null;
}
return state;
case DATA_TYPE_FLOAT_VALUE: /* Check whether the type is a single value containing a float value */
logger.trace("state of type float value: {}", nodeRoot);
Object bdVal = null;
try {
bdVal = new BigDecimal(nodeRoot.get("value").getAsString()).setScale(1, RoundingMode.HALF_UP);
} catch (NumberFormatException e) {
bdVal = Double.NaN;
}
object.setValue(bdVal);
/* NumberItem Binding */
if (CoreItemFactory.NUMBER.equals(itemType)) {
if (bdVal instanceof Double) { // Checking whether
state = new DecimalType((Double) bdVal);
} else {
state = new DecimalType(((Number) bdVal).doubleValue());
}
/* StringItem Binding */
} else if (CoreItemFactory.STRING.equals(itemType)) {
state = new StringType(bdVal.toString());
} else {
logger.info("Bindingtype not supported for float values: {}", itemType.getClass());
return null;
}
return state;
case DATA_TYPE_SWITCH_PROGRAM: /* Check whether the type is a switchProgram */
KM200SwitchProgramServiceHandler sPService = null;
logger.trace("state of type switchProgram: {}", nodeRoot);
if (null != parent) {
KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
if (null != objParent) {
/* Get the KM200SwitchProgramService class object with all specific parameters */
if (object.getVirtual() == 0) {
sPService = ((KM200SwitchProgramServiceHandler) object.getValueParameter());
} else {
sPService = ((KM200SwitchProgramServiceHandler) objParent.getValueParameter());
}
if (null != sPService) {
/* Update the switches inside the KM200SwitchProgramService */
sPService.updateSwitches(nodeRoot, remoteDevice);
/* 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 (CoreItemFactory.STRING.equals(itemType)) {
state = new StringType(
nodeRoot.get("switchPoints").getAsJsonArray().toString());
} else {
logger.info(
"Bindingtype not supported for switchProgram, only json over strings supported: {}",
itemType.getClass());
return null;
}
return state;
}
}
}
}
return null;
case DATA_TYPE_ERROR_LIST: /* Check whether the type is a errorList */
KM200ErrorServiceHandler eService = null;
logger.trace("state of type errorList: {}", nodeRoot);
if (null != parent) {
KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
if (null != objParent) {
/* Get the KM200ErrorService class object with all specific parameters */
if (object.getVirtual() == 0) {
eService = ((KM200ErrorServiceHandler) object.getValueParameter());
} else {
eService = ((KM200ErrorServiceHandler) objParent.getValueParameter());
}
if (null != eService) {
/* Update the switches inside 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 (CoreItemFactory.STRING.equals(itemType)) {
state = new StringType(nodeRoot.get("values").getAsJsonArray().toString());
} else {
logger.info(
"Bindingtype not supported for error list, only json over strings is supported: {}",
itemType.getClass());
return null;
}
}
}
}
}
case DATA_TYPE_Y_RECORDING: /* Check whether the type is a yRecording */
logger.info("state of: type yRecording is not supported yet: {}", nodeRoot);
/* have to be completed */
break;
case DATA_TYPE_SYSTEM_INFO: /* Check whether the type is a systeminfo */
logger.info("state of: type systeminfo is not supported yet: {}", nodeRoot);
/* have to be completed */
break;
case DATA_TYPE_ARRAY_DATA: /* Check whether the type is a arrayData */
logger.info("state of: type arrayData is not supported yet: {}", nodeRoot);
/* have to be completed */
break;
case DATA_TYPE_E_MONITORING_LIST: /* Check whether the type is a eMonitoringList */
logger.info("state of: type eMonitoringList is not supported yet: {}", nodeRoot);
/* have to be completed */
break;
}
} catch (JsonParseException e) {
logger.warn("Parsingexception in JSON, data: {} error: {} ", nodeRoot, e.getMessage());
}
return null;
}
/**
* This function checks the virtual state of a service
*/
private @Nullable State getVirtualState(KM200ServiceObject object, String itemType, String service) {
State state = null;
String type = object.getServiceType();
String parent = object.getParent();
if (null != parent) {
KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
if (null != objParent) {
logger.trace("Check virtual state of: {} type: {} item: {}", service, type, itemType);
switch (type) {
case DATA_TYPE_SWITCH_PROGRAM:
KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) objParent
.getValueParameter());
if (null != sPService) {
String[] servicePath = service.split("/");
String virtService = servicePath[servicePath.length - 1];
if ("weekday".equals(virtService)) {
if (CoreItemFactory.STRING.equals(itemType)) {
String actDay = sPService.getActiveDay();
state = new StringType(actDay);
} else {
logger.info("Bindingtype not supported for day service: {}", itemType.getClass());
return null;
}
} else if ("nbrCycles".equals(virtService)) {
if (CoreItemFactory.NUMBER.equals(itemType)) {
Integer nbrCycles = sPService.getNbrCycles();
state = new DecimalType(nbrCycles);
} else {
logger.info("Bindingtype not supported for nbrCycles service: {}",
itemType.getClass());
return null;
}
} else if ("cycle".equals(virtService)) {
if (CoreItemFactory.NUMBER.equals(itemType)) {
Integer cycle = sPService.getActiveCycle();
state = new DecimalType(cycle);
} else {
logger.info("Bindingtype not supported for cycle service: {}", itemType.getClass());
return null;
}
} else if (virtService.equals(sPService.getPositiveSwitch())) {
if (CoreItemFactory.NUMBER.equals(itemType)) {
Integer minutes = sPService.getActivePositiveSwitch();
state = new DecimalType(minutes);
} else if (CoreItemFactory.DATETIME.equals(itemType)) {
Integer minutes = sPService.getActivePositiveSwitch();
ZonedDateTime rightNow = ZonedDateTime.now();
rightNow.minusHours(rightNow.getHour());
rightNow.minusMinutes(rightNow.getMinute());
rightNow.plusSeconds(minutes * 60 - rightNow.getOffset().getTotalSeconds());
state = new DateTimeType(rightNow);
} else {
logger.info("Bindingtype not supported for cycle service: {}", itemType);
return null;
}
} else if (virtService.equals(sPService.getNegativeSwitch())) {
if (CoreItemFactory.NUMBER.equals(itemType)) {
Integer minutes = sPService.getActiveNegativeSwitch();
state = new DecimalType(minutes);
} else if (CoreItemFactory.DATETIME.equals(itemType)) {
Integer minutes = sPService.getActiveNegativeSwitch();
ZonedDateTime rightNow = ZonedDateTime.now();
rightNow.minusHours(rightNow.getHour());
rightNow.minusMinutes(rightNow.getMinute());
rightNow.plusSeconds(minutes * 60 - rightNow.getOffset().getTotalSeconds());
state = new DateTimeType(rightNow);
} else {
logger.info("Bindingtype not supported for cycle service: {}", itemType.getClass());
return null;
}
}
return state;
} else {
return null;
}
case DATA_TYPE_ERROR_LIST:
KM200ErrorServiceHandler eService = ((KM200ErrorServiceHandler) objParent.getValueParameter());
if (null != eService) {
String[] nServicePath = service.split("/");
String nVirtService = nServicePath[nServicePath.length - 1];
/* Go through the parameters and read the values */
switch (nVirtService) {
case "nbrErrors":
if (CoreItemFactory.NUMBER.equals(itemType)) {
Integer nbrErrors = eService.getNbrErrors();
state = new DecimalType(nbrErrors);
} else {
logger.info("Bindingtype not supported for error number service: {}",
itemType.getClass());
return null;
}
break;
case "error":
if (CoreItemFactory.NUMBER.equals(itemType)) {
Integer actError = eService.getActiveError();
state = new DecimalType(actError);
} else {
logger.info("Bindingtype not supported for error service: {}",
itemType.getClass());
return null;
}
break;
case "errorString":
if (CoreItemFactory.STRING.equals(itemType)) {
String errorString = eService.getErrorString();
if (errorString == null) {
return null;
}
state = new StringType(errorString);
} else {
logger.info("Bindingtype not supported for error string service: {}",
itemType.getClass());
return null;
}
break;
}
return state;
} else {
return null;
}
}
}
}
return null;
}
/**
* This function sets the state of a service on the device
*/
public @Nullable JsonObject sendProvidersState(String service, Command command, String itemType, Object itemPara) {
synchronized (remoteDevice) {
String type;
KM200ServiceObject object;
JsonObject newObject;
logger.debug("Prepare item for send: {} zitem: {}", service, itemType);
if (remoteDevice.getBlacklistMap().contains(service)) {
logger.debug("Service on blacklist: {}", service);
return null;
}
if (remoteDevice.containsService(service)) {
object = remoteDevice.getServiceObject(service);
if (null == object) {
logger.debug("Object is null");
return null;
}
if (object.getWriteable() == 0) {
logger.warn("Service is listed as read-only: {}", service);
return null;
}
type = object.getServiceType();
} else {
logger.warn("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.trace("state of: {} type: {}", command, type);
/* Binding is a NumberItem */
if (CoreItemFactory.NUMBER.equals(itemType)) {
BigDecimal bdVal = ((DecimalType) command).toBigDecimal();
/* Check the capabilities of this service */
if (object.getValueParameter() != null) {
// type is definitely correct here
@SuppressWarnings("unchecked")
List<BigDecimal> valParas = (List<BigDecimal>) object.getValueParameter();
if (null != valParas) {
BigDecimal minVal = valParas.get(0);
BigDecimal maxVal = valParas.get(1);
if (bdVal.compareTo(minVal) < 0) {
bdVal = minVal;
}
if (bdVal.compareTo(maxVal) > 0) {
bdVal = maxVal;
}
}
}
newObject = new JsonObject();
if (DATA_TYPE_FLOAT_VALUE.equals(type)) {
newObject.addProperty("value", bdVal);
} else if (DATA_TYPE_STRING_VALUE.equals(type)) {
newObject.addProperty("value", bdVal.toString());
} else if (DATA_TYPE_SWITCH_PROGRAM.equals(type) && object.getVirtual() == 1) {
/* A switchProgram as NumberItem is always virtual */
newObject = sendVirtualState(object, service, command, itemType);
} else if (DATA_TYPE_ERROR_LIST.equals(type) && object.getVirtual() == 1) {
/* A errorList as NumberItem is always virtual */
newObject = sendVirtualState(object, service, command, itemType);
} else {
logger.info("Not supported type for numberItem: {}", type);
}
/* Binding is a StringItem */
} else if (CoreItemFactory.STRING.equals(itemType)) {
String val = ((StringType) command).toString();
newObject = new JsonObject();
/* Check the capabilities of this service */
if (object.getValueParameter() != null) {
// type is definitely correct here
@SuppressWarnings("unchecked")
List<String> valParas = (List<String>) object.getValueParameter();
if (null != valParas) {
if (!valParas.contains(val)) {
logger.warn("Parameter is not in the service parameterlist: {}", val);
return null;
}
}
}
if (DATA_TYPE_STRING_VALUE.equals(type)) {
newObject.addProperty("value", val);
} else if (DATA_TYPE_FLOAT_VALUE.equals(type)) {
newObject.addProperty("value", Float.parseFloat(val));
} else if (DATA_TYPE_SWITCH_PROGRAM.equals(type)) {
if (object.getVirtual() == 1) {
newObject = sendVirtualState(object, service, command, itemType);
} else {
/* The JSONArray of switch items can be send directly */
try {
/* Check whether this input string is a valid JSONArray */
JsonArray userArray = (JsonArray) jsonParser.parse(val);
newObject = userArray.getAsJsonObject();
} catch (JsonParseException e) {
logger.warn("The input for the switchProgram is not a valid JSONArray : {}",
e.getMessage());
return null;
}
}
} else {
logger.info("Not supported type for stringItem: {}", type);
}
/* Binding is a DateTimeItem */
} else if (CoreItemFactory.DATETIME.equals(itemType)) {
String val = ((DateTimeType) command).toString();
newObject = new JsonObject();
if (DATA_TYPE_STRING_VALUE.equals(type)) {
newObject.addProperty("value", val);
} else if (DATA_TYPE_SWITCH_PROGRAM.equals(type)) {
newObject = sendVirtualState(object, service, command, itemType);
} else {
logger.info("Not supported type for dateTimeItem: {}", type);
}
} else if ("Switch".equals(itemType)) {
String val = null;
newObject = new JsonObject();
// type is definitely correct here
@SuppressWarnings("unchecked")
Map<String, String> switchNames = (HashMap<String, String>) itemPara;
if (switchNames.containsKey("on")) {
if (command == OnOffType.OFF) {
val = switchNames.get("off");
} else if (command == OnOffType.ON) {
val = switchNames.get("on");
}
// type is definitely correct here
@SuppressWarnings("unchecked")
List<String> valParas = (List<String>) object.getValueParameter();
if (null != valParas) {
if (!valParas.contains(val)) {
logger.warn("Parameter is not in the service parameterlist: {}", val);
return null;
}
}
} else if (switchNames.isEmpty()) {
logger.debug("No switch item configuration");
return null;
} else {
logger.info("Switch-Item only on configured on/off string values {}", command);
return null;
}
if (DATA_TYPE_STRING_VALUE.equals(type)) {
newObject.addProperty("value", val);
} else {
logger.info("Not supported type for SwitchItem:{}", type);
}
} else {
logger.info("Bindingtype not supported: {}", itemType.getClass());
return null;
}
/* If some data is availible then we have to send it to device */
if (newObject != null && newObject.toString().length() > 2) {
logger.trace("Send Data: {}", newObject);
return newObject;
} else {
return null;
}
}
}
/**
* This function sets the state of a virtual service
*/
public @Nullable JsonObject sendVirtualState(KM200ServiceObject object, String service, Command command,
String itemType) {
JsonObject newObject = null;
String type = null;
logger.trace("Check virtual state of: {} type: {} item: {}", service, type, itemType);
String parent = object.getParent();
if (null != parent) {
KM200ServiceObject objParent = remoteDevice.getServiceObject(parent);
if (null != objParent) {
type = object.getServiceType();
/* Binding is a StringItem */
if (CoreItemFactory.STRING.equals(itemType)) {
String val = ((StringType) command).toString();
switch (type) {
case DATA_TYPE_SWITCH_PROGRAM:
KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) objParent
.getValueParameter());
if (null != sPService) {
String[] servicePath = service.split("/");
String virtService = servicePath[servicePath.length - 1];
if ("weekday".equals(virtService)) {
/* Only parameter changing without communication to device */
sPService.setActiveDay(val);
}
}
break;
}
/* Binding is a NumberItem */
} else if (CoreItemFactory.NUMBER.equals(itemType)) {
Integer val = ((DecimalType) command).intValue();
switch (type) {
case DATA_TYPE_SWITCH_PROGRAM:
KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) objParent
.getValueParameter());
if (null != sPService) {
String[] servicePath = service.split("/");
String virtService = servicePath[servicePath.length - 1];
if ("cycle".equals(virtService)) {
/* 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 */
newObject = sPService.getUpdatedJSONData(objParent);
} else if (virtService.equals(sPService.getNegativeSwitch())) {
sPService.setActiveNegativeSwitch(val);
/* Create a JSON Array from current switch configuration */
newObject = sPService.getUpdatedJSONData(objParent);
}
}
break;
case DATA_TYPE_ERROR_LIST:
KM200ErrorServiceHandler eService = ((KM200ErrorServiceHandler) objParent
.getValueParameter());
if (null != eService) {
String[] nServicePath = service.split("/");
String nVirtService = nServicePath[nServicePath.length - 1];
if ("error".equals(nVirtService)) {
/* Only parameter changing without communication to device */
eService.setActiveError(val);
}
}
break;
}
} else if (CoreItemFactory.DATETIME.equals(itemType)) {
ZonedDateTime swTime = ((DateTimeType) command).getZonedDateTime();
KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) objParent
.getValueParameter());
if (null != sPService) {
String[] servicePath = service.split("/");
String virtService = servicePath[servicePath.length - 1];
Integer minutes = swTime.getHour() * 60 + swTime.getMinute()
+ swTime.getOffset().getTotalSeconds() % 60;
minutes = (minutes % sPService.getSwitchPointTimeRaster())
* sPService.getSwitchPointTimeRaster();
if (virtService.equals(sPService.getPositiveSwitch())) {
sPService.setActivePositiveSwitch(minutes);
}
if (virtService.equals(sPService.getNegativeSwitch())) {
sPService.setActiveNegativeSwitch(minutes);
}
/* Create a JSON Array from current switch configuration */
newObject = sPService.getUpdatedJSONData(objParent);
}
}
return newObject;
}
}
return null;
}
}

View File

@@ -0,0 +1,140 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.handler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* The KM200ErrorService representing a error service with its all capabilities
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200ErrorServiceHandler {
private final Logger logger = LoggerFactory.getLogger(KM200ErrorServiceHandler.class);
private Integer activeError = 1;
/* List for all errors */
private final List<Map<String, String>> errorMap;
public KM200ErrorServiceHandler() {
errorMap = new ArrayList<>();
}
/**
* This function removes all errors from the list
*/
void removeAllErrors() {
synchronized (errorMap) {
errorMap.clear();
}
}
/**
* This function updates the errors
*/
public void updateErrors(JsonObject nodeRoot) {
synchronized (errorMap) {
/* Update the list of errors */
removeAllErrors();
JsonArray sPoints = nodeRoot.get("values").getAsJsonArray();
for (int i = 0; i < sPoints.size(); i++) {
JsonObject subJSON = sPoints.get(i).getAsJsonObject();
Map<String, String> valMap = new HashMap<>();
Set<Map.Entry<String, JsonElement>> oMap = subJSON.entrySet();
oMap.forEach(item -> {
logger.trace("Set: {} val: {}", item.getKey(), item.getValue().getAsString());
valMap.put(item.getKey(), item.getValue().getAsString());
});
errorMap.add(valMap);
}
}
}
/**
* This function returns the number of errors
*/
public int getNbrErrors() {
synchronized (errorMap) {
return errorMap.size();
}
}
/**
* This function sets the actual errors
*/
public void setActiveError(int error) {
int actError;
if (error < 1) {
actError = 1;
} else if (error > getNbrErrors()) {
actError = getNbrErrors();
} else {
actError = error;
}
synchronized (activeError) {
activeError = actError;
}
}
/**
* This function returns the selected error
*/
public int getActiveError() {
synchronized (activeError) {
return activeError;
}
}
/**
* This function returns a error string with all parameters
*/
public @Nullable String getErrorString() {
String value = "";
synchronized (errorMap) {
int actN = getActiveError();
if (errorMap.size() < actN || errorMap.isEmpty()) {
return null;
}
/* is the time value existing ("t") then use it on the begin */
if (errorMap.get(actN - 1).containsKey("t")) {
value = errorMap.get(actN - 1).get("t");
for (String para : errorMap.get(actN - 1).keySet()) {
if (!"t".equals(para)) {
value += " " + para + ":" + errorMap.get(actN - 1).get(para);
}
}
} else {
for (String para : errorMap.get(actN - 1).keySet()) {
value += para + ":" + errorMap.get(actN - 1).get(para) + " ";
}
}
return value;
}
}
}

View File

@@ -0,0 +1,627 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.handler;
import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.km200.internal.KM200Device;
import org.openhab.binding.km200.internal.KM200ServiceObject;
import org.openhab.binding.km200.internal.KM200ThingType;
import org.openhab.binding.km200.internal.KM200Utils;
import org.openhab.core.common.NamedThreadFactory;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingStatusInfo;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* The {@link KM200GatewayHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200GatewayHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(KM200GatewayHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_KMDEVICE);
private final Map<Channel, JsonObject> sendMap = Collections.synchronizedMap(new LinkedHashMap<>());
private List<KM200GatewayStatusListener> listeners = new CopyOnWriteArrayList<>();
/**
* shared instance of HTTP client for (a)synchronous calls
*/
private ScheduledExecutorService executor;
private final KM200Device remoteDevice;
private final KM200DataHandler dataHandler;
private int readDelay;
private int refreshInterval;
public KM200GatewayHandler(Bridge bridge, HttpClient httpClient) {
super(bridge);
refreshInterval = 120;
readDelay = 100;
updateStatus(ThingStatus.UNINITIALIZED, ThingStatusDetail.CONFIGURATION_PENDING);
remoteDevice = new KM200Device(httpClient);
dataHandler = new KM200DataHandler(remoteDevice);
executor = Executors.newScheduledThreadPool(2, new NamedThreadFactory("org.openhab.binding.km200", true));
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Channel channel = getThing().getChannel(channelUID.getId());
if (null != channel) {
if (command instanceof DateTimeType || command instanceof DecimalType || command instanceof StringType) {
prepareMessage(thing, channel, command);
} else {
logger.warn("Unsupported Command: {} Class: {}", command.toFullString(), command.getClass());
}
}
}
@Override
public void initialize() {
if (!getDevice().getInited()) {
logger.info("Update KM50/100/200 gateway configuration, it takes a minute....");
getConfiguration();
if (getDevice().isConfigured()) {
if (!checkConfiguration()) {
return;
}
/* configuration and communication seems to be ok */
readCapabilities();
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
logger.debug("The KM50/100/200 gateway configuration is not complete");
return;
}
SendKM200Runnable sendRunnable = new SendKM200Runnable(sendMap, getDevice());
GetKM200Runnable receivingRunnable = new GetKM200Runnable(sendMap, this, getDevice());
if (!executor.isTerminated()) {
executor = Executors.newScheduledThreadPool(2,
new NamedThreadFactory("org.openhab.binding.km200", true));
executor.scheduleWithFixedDelay(receivingRunnable, 30, refreshInterval, TimeUnit.SECONDS);
executor.scheduleWithFixedDelay(sendRunnable, 60, refreshInterval * 2, TimeUnit.SECONDS);
}
}
}
@Override
public void dispose() {
executor.shutdown();
try {
if (!executor.awaitTermination(60000, TimeUnit.SECONDS)) {
logger.debug("Services didn't finish in 60000 seconds!");
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
synchronized (getDevice()) {
getDevice().setInited(false);
getDevice().setIP4Address("");
getDevice().setCryptKeyPriv("");
getDevice().setMD5Salt("");
getDevice().setGatewayPassword("");
getDevice().setPrivatePassword("");
getDevice().serviceTreeMap.clear();
}
updateStatus(ThingStatus.OFFLINE);
}
@Override
public void handleRemoval() {
for (Thing actThing : getThing().getThings()) {
actThing.setStatusInfo(new ThingStatusInfo(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, ""));
}
this.updateStatus(ThingStatus.REMOVED);
}
/**
* Gets bridges configuration
*/
private void getConfiguration() {
Configuration configuration = getConfig();
for (String key : configuration.keySet()) {
logger.trace("initialize Key: {} Value: {}", key, configuration.get(key));
switch (key) {
case "ip4Address":
String ip = (String) configuration.get("ip4Address");
if (StringUtils.isNotBlank(ip)) {
try {
InetAddress.getByName(ip);
} catch (UnknownHostException e) {
logger.warn("IP4_address is not valid!: {}", ip);
}
getDevice().setIP4Address(ip);
} else {
logger.debug("No ip4_address configured!");
}
break;
case "privateKey":
String privateKey = (String) configuration.get("privateKey");
if (StringUtils.isNotBlank(privateKey)) {
getDevice().setCryptKeyPriv(privateKey);
}
break;
case "md5Salt":
String md5Salt = (String) configuration.get("md5Salt");
if (StringUtils.isNotBlank(md5Salt)) {
getDevice().setMD5Salt(md5Salt);
}
break;
case "gatewayPassword":
String gatewayPassword = (String) configuration.get("gatewayPassword");
if (StringUtils.isNotBlank(gatewayPassword)) {
getDevice().setGatewayPassword(gatewayPassword);
}
break;
case "privatePassword":
String privatePassword = (String) configuration.get("privatePassword");
if (StringUtils.isNotBlank(privatePassword)) {
getDevice().setPrivatePassword(privatePassword);
}
break;
case "refreshInterval":
refreshInterval = ((BigDecimal) configuration.get("refreshInterval")).intValue();
logger.debug("Set refresh interval to: {} seconds.", refreshInterval);
break;
case "readDelay":
readDelay = ((BigDecimal) configuration.get("readDelay")).intValue();
logger.debug("Set read delay to: {} seconds.", readDelay);
break;
case "maxNbrRepeats":
Integer maxNbrRepeats = ((BigDecimal) configuration.get("maxNbrRepeats")).intValue();
logger.debug("Set max. number of repeats to: {} seconds.", maxNbrRepeats);
remoteDevice.setMaxNbrRepeats(maxNbrRepeats);
break;
}
}
}
/**
* Checks bridges configuration
*/
private boolean checkConfiguration() {
/* Get HTTP Data from device */
JsonObject nodeRoot = remoteDevice.getServiceNode("/gateway/DateTime");
if (nodeRoot == null || nodeRoot.isJsonNull()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"No communication possible with gateway");
return false;
}
logger.debug("Test of the communication to the gateway was successful..");
/* Testing the received data, is decryption working? */
try {
nodeRoot.get("type").getAsString();
nodeRoot.get("id").getAsString();
} catch (JsonParseException e) {
logger.debug("The data is not readable, check the key and password configuration! {}", e.getMessage());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Wrong gateway configuration");
return false;
}
return true;
}
/**
* Reads the devices capabilities and sets the data structures
*/
private void readCapabilities() {
KM200VirtualServiceHandler virtualServiceHandler;
/* Checking of the device specific services and creating of a service list */
for (KM200ThingType thing : KM200ThingType.values()) {
String rootPath = thing.getRootPath();
if (!rootPath.isEmpty() && (rootPath.indexOf("/", 0) == rootPath.lastIndexOf("/", rootPath.length() - 1))) {
if (remoteDevice.getBlacklistMap().contains(thing.getRootPath())) {
logger.debug("Service on blacklist: {}", thing.getRootPath());
return;
}
KM200ServiceHandler serviceHandler = new KM200ServiceHandler(thing.getRootPath(), null, remoteDevice);
serviceHandler.initObject();
}
}
/* Now init the virtual services */
virtualServiceHandler = new KM200VirtualServiceHandler(remoteDevice);
virtualServiceHandler.initVirtualObjects();
/* Output all available services in the log file */
getDevice().listAllServices();
updateBridgeProperties();
getDevice().setInited(true);
}
/**
* Adds a GatewayConnectedListener
*/
public void addGatewayStatusListener(KM200GatewayStatusListener listener) {
listeners.add(listener);
listener.gatewayStatusChanged(getThing().getStatus());
}
/**
* Removes a GatewayConnectedListener
*/
public void removeHubStatusListener(KM200GatewayStatusListener listener) {
listeners.remove(listener);
}
/**
* Refreshes a channel
*/
public void refreshChannel(Channel channel) {
GetSingleKM200Runnable runnable = new GetSingleKM200Runnable(sendMap, this, getDevice(), channel);
logger.debug("starting single runnable.");
scheduler.submit(runnable);
}
/**
* Updates bridges properties
*/
private void updateBridgeProperties() {
List<String> propertyServices = new ArrayList<>();
propertyServices.add(KM200ThingType.GATEWAY.getRootPath());
propertyServices.add(KM200ThingType.SYSTEM.getRootPath());
Map<String, String> bridgeProperties = editProperties();
for (KM200ThingType tType : KM200ThingType.values()) {
List<String> asProperties = tType.asBridgeProperties();
String rootPath = tType.getRootPath();
if (rootPath.isEmpty()) {
continue;
}
KM200ServiceObject serObj = getDevice().getServiceObject(rootPath);
if (null != serObj) {
for (String subKey : asProperties) {
if (serObj.serviceTreeMap.containsKey(subKey)) {
KM200ServiceObject subKeyObj = serObj.serviceTreeMap.get(subKey);
String subKeyType = subKeyObj.getServiceType();
if (!DATA_TYPE_STRING_VALUE.equals(subKeyType) && !DATA_TYPE_FLOAT_VALUE.equals(subKeyType)) {
continue;
}
if (bridgeProperties.containsKey(subKey)) {
bridgeProperties.remove(subKey);
}
Object value = subKeyObj.getValue();
logger.trace("Add Property: {} :{}", subKey, value);
if (null != value) {
bridgeProperties.put(subKey, value.toString());
}
}
}
}
}
updateProperties(bridgeProperties);
}
/**
* Prepares a message for sending
*/
public void prepareMessage(Thing thing, Channel channel, Command command) {
if (getDevice().getInited()) {
JsonObject newObject = null;
State state = null;
String service = KM200Utils.checkParameterReplacement(channel, getDevice());
String chTypes = channel.getAcceptedItemType();
if (null == chTypes) {
logger.warn("Channel {} has not accepted item types", channel.getLabel());
return;
}
logger.trace("handleCommand channel: {} service: {}", channel.getLabel(), service);
newObject = dataHandler.sendProvidersState(service, command, chTypes,
KM200Utils.getChannelConfigurationStrings(channel));
synchronized (getDevice()) {
KM200ServiceObject serObjekt = getDevice().getServiceObject(service);
if (null != serObjekt) {
if (newObject != null) {
sendMap.put(channel, newObject);
} else if (getDevice().containsService(service) && serObjekt.getVirtual() == 1) {
String parent = serObjekt.getParent();
for (Thing actThing : getThing().getThings()) {
logger.trace("Checking: {}", actThing.getUID().getAsString());
for (Channel tmpChannel : actThing.getChannels()) {
String tmpChTypes = tmpChannel.getAcceptedItemType();
if (null == tmpChTypes) {
logger.warn("Channel {} has not accepted item types", tmpChannel.getLabel());
return;
}
String actService = KM200Utils.checkParameterReplacement(tmpChannel, getDevice());
KM200ServiceObject actSerObjekt = getDevice().getServiceObject(actService);
if (null != actSerObjekt) {
String actParent = actSerObjekt.getParent();
if (actParent != null && actParent.equals(parent)) {
state = dataHandler.getProvidersState(actService, tmpChTypes,
KM200Utils.getChannelConfigurationStrings(tmpChannel));
if (state != null) {
try {
updateState(tmpChannel.getUID(), state);
} catch (IllegalStateException e) {
logger.warn("Could not get updated item state", e);
}
}
}
}
}
}
} else {
logger.debug("Service is not availible: {}", service);
}
}
}
}
}
/**
* Update the children
*/
// Every thing has here a handler
private void updateChildren(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
KM200Device remoteDevice, @Nullable String parent) {
State state;
synchronized (remoteDevice) {
if (parent != null) {
KM200ServiceObject serParObjekt = remoteDevice.getServiceObject(parent);
if (null != serParObjekt) {
serParObjekt.setUpdated(false);
}
}
for (Thing actThing : gatewayHandler.getThing().getThings()) {
for (Channel actChannel : actThing.getChannels()) {
String actChTypes = actChannel.getAcceptedItemType();
if (null == actChTypes) {
logger.warn("Channel {} has not accepted item types", actChannel.getLabel());
return;
}
logger.trace("Checking: {} Root: {}", actChannel.getUID().getAsString(),
actChannel.getProperties().get("root"));
KM200ThingHandler actHandler = (KM200ThingHandler) actThing.getHandler();
if (actHandler != null) {
if (!actHandler.checkLinked(actChannel)) {
continue;
}
} else {
continue;
}
String tmpService = KM200Utils.checkParameterReplacement(actChannel, remoteDevice);
KM200ServiceObject tmpSerObjekt = remoteDevice.getServiceObject(tmpService);
if (null != tmpSerObjekt) {
if (parent == null || parent.equals(tmpSerObjekt.getParent())) {
synchronized (sendMap) {
if (sendMap.containsKey(actChannel)) {
state = dataHandler.parseJSONData(sendMap.get(actChannel),
tmpSerObjekt.getServiceType(), tmpService, actChTypes,
KM200Utils.getChannelConfigurationStrings(actChannel));
} else {
state = dataHandler.getProvidersState(tmpService, actChTypes,
KM200Utils.getChannelConfigurationStrings(actChannel));
}
}
if (state != null) {
try {
gatewayHandler.updateState(actChannel.getUID(), state);
} catch (IllegalStateException e) {
logger.warn("Could not get updated item state", e);
}
}
}
try {
Thread.sleep(readDelay);
} catch (InterruptedException e) {
continue;
}
}
}
}
}
}
/**
* Return the device instance.
*/
public KM200Device getDevice() {
return remoteDevice;
}
/**
* The GetKM200Runnable class get the data from device to all items.
*/
private class GetKM200Runnable implements Runnable {
private final KM200GatewayHandler gatewayHandler;
private final KM200Device remoteDevice;
private final Logger logger = LoggerFactory.getLogger(GetKM200Runnable.class);
private final Map<Channel, JsonObject> sendMap;
public GetKM200Runnable(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
KM200Device remoteDevice) {
this.sendMap = sendMap;
this.gatewayHandler = gatewayHandler;
this.remoteDevice = remoteDevice;
}
@Override
public void run() {
logger.debug("GetKM200Runnable");
synchronized (remoteDevice) {
if (remoteDevice.getInited()) {
remoteDevice.resetAllUpdates(remoteDevice.serviceTreeMap);
updateChildren(sendMap, gatewayHandler, remoteDevice, null);
}
}
}
}
/**
* The GetKM200Runnable class get the data from device for one channel.
*/
private class GetSingleKM200Runnable implements Runnable {
private final Logger logger = LoggerFactory.getLogger(GetSingleKM200Runnable.class);
private final KM200GatewayHandler gatewayHandler;
private final KM200Device remoteDevice;
private final Channel channel;
private final Map<Channel, JsonObject> sendMap;
public GetSingleKM200Runnable(Map<Channel, JsonObject> sendMap, KM200GatewayHandler gatewayHandler,
KM200Device remoteDevice, Channel channel) {
this.gatewayHandler = gatewayHandler;
this.remoteDevice = remoteDevice;
this.channel = channel;
this.sendMap = sendMap;
}
@Override
public void run() {
logger.debug("GetKM200Runnable");
State state = null;
synchronized (remoteDevice) {
synchronized (sendMap) {
if (sendMap.containsKey(channel)) {
return;
}
}
if (remoteDevice.getInited()) {
logger.trace("Checking: {} Root: {}", channel.getUID().getAsString(),
channel.getProperties().get("root"));
String chTypes = channel.getAcceptedItemType();
if (null == chTypes) {
logger.warn("Channel {} has not accepted item types", channel.getLabel());
return;
}
String service = KM200Utils.checkParameterReplacement(channel, remoteDevice);
KM200ServiceObject object = remoteDevice.getServiceObject(service);
if (null != object) {
if (object.getVirtual() == 1) {
String parent = object.getParent();
updateChildren(sendMap, gatewayHandler, remoteDevice, parent);
} else {
object.setUpdated(false);
synchronized (sendMap) {
KM200ServiceObject serObjekt = remoteDevice.getServiceObject(service);
if (null != serObjekt) {
if (sendMap.containsKey(channel)) {
state = dataHandler.parseJSONData(sendMap.get(channel),
serObjekt.getServiceType(), service, chTypes,
KM200Utils.getChannelConfigurationStrings(channel));
} else {
state = dataHandler.getProvidersState(service, chTypes,
KM200Utils.getChannelConfigurationStrings(channel));
}
}
if (state != null) {
try {
gatewayHandler.updateState(channel.getUID(), state);
} catch (IllegalStateException e) {
logger.warn("Could not get updated item state", e);
}
}
}
}
}
}
}
}
}
/**
* The sendKM200Thread class sends the data to the device.
*/
private class SendKM200Runnable implements Runnable {
private final Logger logger = LoggerFactory.getLogger(SendKM200Runnable.class);
private final Map<Channel, JsonObject> newObject;
private final KM200Device remoteDevice;
public SendKM200Runnable(Map<Channel, JsonObject> newObject, KM200Device remoteDevice) {
this.newObject = newObject;
this.remoteDevice = remoteDevice;
}
@Override
public void run() {
logger.debug("Send-Executor started");
Map.Entry<Channel, JsonObject> nextEntry;
/* Check whether a new entry is availible, if yes then take and remove it */
do {
nextEntry = null;
synchronized (remoteDevice) {
synchronized (newObject) {
Iterator<Entry<Channel, JsonObject>> i = newObject.entrySet().iterator();
if (i.hasNext()) {
nextEntry = i.next();
i.remove();
}
}
if (nextEntry != null) {
/* Now send the data to the device */
Channel channel = nextEntry.getKey();
JsonObject newObject = nextEntry.getValue();
String service = KM200Utils.checkParameterReplacement(channel, remoteDevice);
KM200ServiceObject object = remoteDevice.getServiceObject(service);
if (null != object) {
if (object.getVirtual() == 0) {
remoteDevice.setServiceNode(service, newObject);
} else {
String parent = object.getParent();
if (null != parent) {
logger.trace("Sending: {} to : {}", newObject, service);
remoteDevice.setServiceNode(parent, newObject);
}
}
}
}
}
} while (nextEntry != null);
}
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.handler;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingStatus;
/**
* the {@link KM200GatewayHandler} interface is for classes wishing to register
* to be called back when a gateway status changes
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public interface KM200GatewayStatusListener {
public void gatewayStatusChanged(ThingStatus status);
}

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.handler;
import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.km200.internal.KM200Device;
import org.openhab.binding.km200.internal.KM200ServiceObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
* The KM200DataHandler is representing one service on the device
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200ServiceHandler {
private final Logger logger = LoggerFactory.getLogger(KM200ServiceHandler.class);
private final String service;
private final @Nullable KM200ServiceObject parent;
private final KM200Device remoteDevice;
public KM200ServiceHandler(String service, @Nullable KM200ServiceObject parent, KM200Device remoteDevice) {
this.service = service;
this.parent = parent;
this.remoteDevice = remoteDevice;
}
/**
* This function starts the object's initialization
*/
public void initObject() {
JsonObject nodeRoot;
if (remoteDevice.getBlacklistMap().contains(service)) {
logger.debug("Blacklisted: {}", service);
return;
}
if (null == remoteDevice.getServiceNode(service)) {
logger.debug("initDevice: nodeRoot == null for service: {}", service);
return;
}
nodeRoot = remoteDevice.getServiceNode(service);
if (null != nodeRoot) {
determineServiceObject(createServiceObject(nodeRoot), nodeRoot);
}
}
/**
* This function checks the flags of a service on the device and creates a KM200CommObject
*/
public KM200ServiceObject createServiceObject(JsonObject nodeRoot) {
KM200ServiceObject serviceObject;
String id = null, type = null;
Integer writeable = 0;
Integer recordable = 0;
Integer readable = 1;
/* check whether the node is an empty one */
if (nodeRoot.toString().length() == 2) {
readable = 0;
id = service;
type = DATA_TYPE_PROTECTED;
} else {
type = nodeRoot.get("type").getAsString();
id = nodeRoot.get("id").getAsString();
}
/* Check the service features and set the flags */
if (nodeRoot.has("writeable")) {
Integer val = nodeRoot.get("writeable").getAsInt();
logger.trace("writable: {}", val);
writeable = val;
}
if (nodeRoot.has("recordable")) {
Integer val = nodeRoot.get("recordable").getAsInt();
logger.trace("recordable: {}", val);
recordable = val;
}
logger.trace("Typ: {}", type);
serviceObject = new KM200ServiceObject(id, type, readable, writeable, recordable, 0, null);
serviceObject.setJSONData(nodeRoot);
return serviceObject;
}
/**
* This function determines the service's capabilities
*/
public void determineServiceObject(KM200ServiceObject serviceObject, JsonObject nodeRoot) {
/* Check the service features and set the flags */
String id = null;
Object valObject = null;
JsonObject dataObject = serviceObject.getJSONData();
if (null != dataObject) {
switch (serviceObject.getServiceType()) {
case DATA_TYPE_STRING_VALUE: /*
* Check whether the type is a single value containing a
* string value
*/
logger.trace("initDevice: type string value: {}", dataObject);
valObject = new String(nodeRoot.get("value").getAsString());
serviceObject.setValue(valObject);
if (nodeRoot.has("allowedValues")) {
List<String> valParas = new ArrayList<>();
JsonArray paras = nodeRoot.get("allowedValues").getAsJsonArray();
for (int i = 0; i < paras.size(); i++) {
String subJSON = paras.get(i).getAsString();
valParas.add(subJSON);
}
serviceObject.setValueParameter(valParas);
}
break;
case DATA_TYPE_FLOAT_VALUE: /* Check whether the type is a single value containing a float value */
logger.trace("initDevice: type float value: {}", dataObject);
valObject = nodeRoot.get("value");
try {
valObject = nodeRoot.get("value").getAsBigDecimal();
serviceObject.setValue(valObject);
} catch (NumberFormatException e) {
Double tmpObj = Double.NaN;
serviceObject.setValue(tmpObj);
}
if (nodeRoot.has("minValue") && nodeRoot.has("maxValue")) {
List<Object> valParas = new ArrayList<>();
valParas.add(nodeRoot.get("minValue").getAsBigDecimal());
valParas.add(nodeRoot.get("maxValue").getAsBigDecimal());
if (nodeRoot.has("unitOfMeasure")) {
valParas.add(nodeRoot.get("unitOfMeasure").getAsString());
}
serviceObject.setValueParameter(valParas);
}
break;
case DATA_TYPE_SWITCH_PROGRAM: /* Check whether the type is a switchProgram */
logger.trace("initDevice: type switchProgram {}", dataObject);
KM200SwitchProgramServiceHandler sPService = new KM200SwitchProgramServiceHandler();
sPService.setMaxNbOfSwitchPoints(nodeRoot.get("maxNbOfSwitchPoints").getAsInt());
sPService.setMaxNbOfSwitchPointsPerDay(nodeRoot.get("maxNbOfSwitchPointsPerDay").getAsInt());
sPService.setSwitchPointTimeRaster(nodeRoot.get("switchPointTimeRaster").getAsInt());
JsonObject propObject = nodeRoot.get("setpointProperty").getAsJsonObject();
sPService.setSetpointProperty(propObject.get("id").getAsString());
serviceObject.setValueParameter(sPService);
serviceObject.setJSONData(dataObject);
remoteDevice.virtualList.add(serviceObject);
break;
case DATA_TYPE_ERROR_LIST: /* Check whether the type is a errorList */
logger.trace("initDevice: type errorList: {}", dataObject);
KM200ErrorServiceHandler eService = new KM200ErrorServiceHandler();
eService.updateErrors(nodeRoot);
serviceObject.setValueParameter(eService);
serviceObject.setJSONData(dataObject);
remoteDevice.virtualList.add(serviceObject);
break;
case DATA_TYPE_REF_ENUM: /* Check whether the type is a refEnum */
logger.trace("initDevice: type refEnum: {}", dataObject);
JsonArray refers = nodeRoot.get("references").getAsJsonArray();
for (int i = 0; i < refers.size(); i++) {
JsonObject subJSON = refers.get(i).getAsJsonObject();
id = subJSON.get("id").getAsString();
KM200ServiceHandler serviceHandler = new KM200ServiceHandler(id, serviceObject, remoteDevice);
serviceHandler.initObject();
}
break;
case DATA_TYPE_MODULE_LIST: /* Check whether the type is a moduleList */
logger.trace("initDevice: type moduleList: {}", dataObject);
JsonArray vals = nodeRoot.get("values").getAsJsonArray();
for (int i = 0; i < vals.size(); i++) {
JsonObject subJSON = vals.get(i).getAsJsonObject();
id = subJSON.get("id").getAsString();
KM200ServiceHandler serviceHandler = new KM200ServiceHandler(id, serviceObject, remoteDevice);
serviceHandler.initObject();
}
break;
case DATA_TYPE_Y_RECORDING: /* Check whether the type is a yRecording */
logger.trace("initDevice: type yRecording: {}", dataObject);
/* have to be completed */
break;
case DATA_TYPE_SYSTEM_INFO: /* Check whether the type is a systeminfo */
logger.trace("initDevice: type systeminfo: {}", dataObject);
JsonArray sInfo = nodeRoot.get("values").getAsJsonArray();
serviceObject.setValue(sInfo);
/* have to be completed */
break;
case DATA_TYPE_ARRAY_DATA:
logger.trace("initDevice: type arrayData: {}", dataObject);
serviceObject.setJSONData(dataObject);
/* have to be completed */
break;
case DATA_TYPE_E_MONITORING_LIST:
logger.trace("initDevice: type eMonitoringList: {}", dataObject);
serviceObject.setJSONData(dataObject);
/* have to be completed */
break;
case DATA_TYPE_PROTECTED:
logger.trace("initDevice: readonly");
serviceObject.setJSONData(dataObject);
break;
default: /* Unknown type */
logger.info("initDevice: type: {} unknown for service: {} Data: {}", serviceObject.getServiceType(),
service, dataObject);
}
}
String[] servicePath = service.split("/");
if (null != parent) {
parent.serviceTreeMap.put(servicePath[servicePath.length - 1], serviceObject);
} else {
remoteDevice.serviceTreeMap.put(servicePath[servicePath.length - 1], serviceObject);
}
}
}

View File

@@ -0,0 +1,547 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.handler;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.km200.internal.KM200Device;
import org.openhab.binding.km200.internal.KM200ServiceObject;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
/**
* The KM200SwitchProgramService representing a switch program service with its all capabilities
*
* @author Markus Eckhardt - Initial contribution
* @NonNullByDefault is not working here because of the switchMap array handling
*/
public class KM200SwitchProgramServiceHandler {
private final Logger logger = LoggerFactory.getLogger(KM200SwitchProgramServiceHandler.class);
private int maxNbOfSwitchPoints = 8;
private int maxNbOfSwitchPointsPerDay = 8;
private int switchPointTimeRaster = 10;
private String setpointProperty = "";
private String positiveSwitch = "";
private String negativeSwitch = "";
protected final Integer MIN_TIME = 0;
protected final Integer MAX_TIME = 1430;
protected final static String TYPE_MONDAY = "Mo";
protected final static String TYPE_TUESDAY = "Tu";
protected final static String TYPE_WEDNESDAY = "We";
protected final static String TYPE_THURSDAY = "Th";
protected final static String TYPE_FRIDAY = "Fr";
protected final static String TYPE_SATURDAY = "Sa";
protected final static String TYPE_SUNDAY = "Su";
private String activeDay = TYPE_MONDAY;
private Integer activeCycle = 1;
/* Night- and daylist for all weekdays */
public Map<String, Map<String, List<Integer>>> switchMap = new HashMap<>();
/* List with all days */
private static List<String> days = new ArrayList<>(Arrays.asList(TYPE_MONDAY, TYPE_TUESDAY, TYPE_WEDNESDAY,
TYPE_THURSDAY, TYPE_FRIDAY, TYPE_SATURDAY, TYPE_SUNDAY));
public static List<@NonNull StateOption> daysList = new ArrayList<>(
Arrays.asList(new StateOption(TYPE_MONDAY, "Monday"), new StateOption(TYPE_TUESDAY, "Tuesday"),
new StateOption(TYPE_WEDNESDAY, "Wednesday"), new StateOption(TYPE_THURSDAY, "Thursday"),
new StateOption(TYPE_FRIDAY, "Friday"), new StateOption(TYPE_SATURDAY, "Saturday"),
new StateOption(TYPE_SUNDAY, "Sunday")));
/* List with setpoints */
private List<String> setpoints = new ArrayList<>();
/**
* This function inits the week list
*/
void initWeeklist(String setpoint) {
Map<String, List<Integer>> weekMap = switchMap.get(setpoint);
if (weekMap == null) {
weekMap = new HashMap<>();
for (String day : days) {
weekMap.put(day, new ArrayList<>());
}
switchMap.put(setpoint, weekMap);
}
}
/**
* This function adds a switch to the switchmap
*/
void addSwitch(String day, String setpoint, int time) {
logger.trace("Adding day: {} setpoint: {} time: {}", day, setpoint, time);
if (!days.contains(day)) {
logger.warn("This type of weekday is not supported, get day: {}", day);
throw new IllegalArgumentException("This type of weekday is not supported, get day: " + day);
}
if (!setpoints.contains(setpoint)) {
if (setpoints.size() == 2 && "on".compareTo(setpoint) == 0) {
if ("high".compareTo(setpoints.get(0)) == 0 && "off".compareTo(setpoints.get(1)) == 0) {
if ("on".compareTo(positiveSwitch) == 0 && "off".compareTo(negativeSwitch) == 0) {
logger.info(
"!!! Wrong configuration on device. 'on' instead of 'high' in switch program. It seems that's a firmware problem-> ignoring it !!!");
} else {
throw new IllegalArgumentException(
"This type of setpoint is not supported, get setpoint: " + setpoint);
}
}
}
}
Map<String, List<Integer>> weekMap = switchMap.get(setpoint);
if (weekMap == null) {
initWeeklist(setpoint);
weekMap = switchMap.get(setpoint);
}
List<Integer> dayList = weekMap.get(day);
dayList.add(time);
Collections.sort(dayList);
}
/**
* This function removes all switches from the switchmap
*
*/
void removeAllSwitches() {
switchMap.clear();
}
public void setMaxNbOfSwitchPoints(Integer nbr) {
maxNbOfSwitchPoints = nbr;
}
public void setMaxNbOfSwitchPointsPerDay(Integer nbr) {
maxNbOfSwitchPointsPerDay = nbr;
}
public void setSwitchPointTimeRaster(Integer raster) {
switchPointTimeRaster = raster;
}
public void setSetpointProperty(String property) {
setpointProperty = property;
}
/**
* This function sets the day
*/
public void setActiveDay(String day) {
if (!days.contains(day)) {
logger.warn("This type of weekday is not supported, get day: {}", day);
return;
}
activeDay = day;
}
/**
* This function sets the cycle
*/
public void setActiveCycle(Integer cycle) {
if (cycle > this.getMaxNbOfSwitchPoints() / 2 || cycle > this.getMaxNbOfSwitchPointsPerDay() / 2 || cycle < 1) {
logger.warn("The value of cycle is not valid, get cycle: {}", cycle);
return;
}
/* limit the cycle to the next one after last (for creating a new one) */
if (cycle > (getNbrCycles() + 1) || getNbrCycles() == 0) {
activeCycle = getNbrCycles() + 1;
} else {
activeCycle = cycle;
}
}
/**
* This function sets the positive switch to the selected day and cycle
*/
public void setActivePositiveSwitch(Integer time) {
Integer actTime;
if (time < MIN_TIME) {
actTime = MIN_TIME;
} else if (time > MAX_TIME) {
actTime = MAX_TIME;
} else {
actTime = time;
}
synchronized (switchMap) {
Map<String, List<Integer>> week = switchMap.get(getPositiveSwitch());
if (week != null) {
List<Integer> daysList = week.get(getActiveDay());
if (daysList != null) {
Integer actC = getActiveCycle();
Integer nbrC = getNbrCycles();
Integer nSwitch = null;
boolean newS = false;
if (nbrC < actC) {
/* new Switch */
newS = true;
}
if (switchMap.get(getNegativeSwitch()).get(getActiveDay()).size() < actC) {
nSwitch = 0;
} else {
nSwitch = switchMap.get(getNegativeSwitch()).get(getActiveDay()).get(actC - 1);
}
/* The positiv switch cannot be higher then the negative */
if (actTime > (nSwitch - getSwitchPointTimeRaster()) && nSwitch > 0) {
actTime = nSwitch;
if (nSwitch < MAX_TIME) {
actTime -= getSwitchPointTimeRaster();
}
}
/* Check whether the time would overlap with the previous one */
if (actC > 1) {
Integer nPrevSwitch = switchMap.get(getNegativeSwitch()).get(getActiveDay()).get(actC - 2);
/* The positiv switch cannot be lower then the previous negative */
if (actTime < (nPrevSwitch + getSwitchPointTimeRaster())) {
actTime = nPrevSwitch + getSwitchPointTimeRaster();
}
}
if (newS) {
daysList.add(actTime);
} else {
daysList.set(actC - 1, actTime);
}
checkRemovement();
}
}
}
}
/**
* This function sets the negative switch to the selected day and cycle
*/
public void setActiveNegativeSwitch(Integer time) {
Integer actTime;
if (time < MIN_TIME) {
actTime = MIN_TIME;
} else if (time > MAX_TIME) {
actTime = MAX_TIME;
} else {
actTime = time;
}
synchronized (switchMap) {
Map<String, List<Integer>> week = switchMap.get(getNegativeSwitch());
if (week != null) {
List<Integer> daysList = week.get(getActiveDay());
if (daysList != null) {
Integer nbrC = getNbrCycles();
Integer actC = getActiveCycle();
Integer pSwitch = null;
boolean newS = false;
if (nbrC < actC) {
/* new Switch */
newS = true;
}
/* Check whether the positive switch is existing too */
if (switchMap.get(getPositiveSwitch()).get(getActiveDay()).size() < actC) {
/* No -> new Switch */
pSwitch = 0;
} else {
pSwitch = switchMap.get(getPositiveSwitch()).get(getActiveDay()).get(actC - 1);
}
/* The negative switch cannot be lower then the positive */
if (actTime < (pSwitch + getSwitchPointTimeRaster())) {
actTime = pSwitch + getSwitchPointTimeRaster();
}
/* Check whether the time would overlap with the next one */
if (nbrC > actC) {
Integer pNextSwitch = switchMap.get(getPositiveSwitch()).get(getActiveDay()).get(actC);
/* The negative switch cannot be higher then the next positive switch */
if (actTime > (pNextSwitch - getSwitchPointTimeRaster()) && pNextSwitch > 0) {
actTime = pNextSwitch - getSwitchPointTimeRaster();
}
}
if (newS) {
daysList.add(actTime);
} else {
daysList.set(actC - 1, actTime);
}
checkRemovement();
}
}
}
}
/**
* This function checks whether the actual cycle have to be removed (Both times set to MAX_TIME)
*/
void checkRemovement() {
if (getActiveNegativeSwitch().equals(MAX_TIME) && getActivePositiveSwitch().equals(MAX_TIME)
&& getNbrCycles() > 0) {
switchMap.get(getNegativeSwitch()).get(getActiveDay()).remove(getActiveCycle() - 1);
switchMap.get(getPositiveSwitch()).get(getActiveDay()).remove(getActiveCycle() - 1);
}
}
/**
* This function determines the positive and negative switch point names
*/
public boolean determineSwitchNames(KM200Device device) {
if (!setpointProperty.isEmpty()) {
KM200ServiceObject setpObject = device.getServiceObject(setpointProperty);
if (null != setpObject) {
if (setpObject.serviceTreeMap.keySet().isEmpty()) {
return false;
}
for (String key : setpObject.serviceTreeMap.keySet()) {
setpoints.add(key);
}
} else {
return false;
}
}
return true;
}
/**
* This function updates objects the switching points
*/
public void updateSwitches(JsonObject nodeRoot, KM200Device device) {
synchronized (switchMap) {
/* Update the list of switching points */
removeAllSwitches();
JsonArray sPoints = nodeRoot.get("switchPoints").getAsJsonArray();
logger.trace("sPoints: {}", nodeRoot);
if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
/* First start. Determine the positive and negative switching points */
if (sPoints.size() > 0) {
for (int i = 0; i < sPoints.size(); i++) {
JsonObject subJSON = sPoints.get(i).getAsJsonObject();
String setpoint = subJSON.get("setpoint").getAsString();
if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
positiveSwitch = setpoint;
negativeSwitch = setpoint;
} else {
negativeSwitch = setpoint;
}
if (!positiveSwitch.equals(negativeSwitch)) {
break;
}
}
} else {
if (!setpointProperty.isEmpty()) {
BigDecimal firstVal = null;
KM200ServiceObject setpObject = device.getServiceObject(setpointProperty);
if (null != setpObject) {
logger.debug("No switch points set. Use alternative way. {}", nodeRoot);
for (String key : setpoints) {
if (positiveSwitch.isEmpty() || negativeSwitch.isEmpty()) {
positiveSwitch = key;
negativeSwitch = key;
firstVal = (BigDecimal) setpObject.serviceTreeMap.get(key).getValue();
} else {
BigDecimal nextVal = (BigDecimal) setpObject.serviceTreeMap.get(key).getValue();
if (null != nextVal) {
if (nextVal.compareTo(firstVal) > 0) {
positiveSwitch = key;
} else {
negativeSwitch = key;
}
}
}
if (!positiveSwitch.equalsIgnoreCase(negativeSwitch)) {
break;
}
}
}
}
}
}
logger.debug("Positive switch: {}", positiveSwitch);
logger.debug("Negative switch: {}", negativeSwitch);
Map<String, List<Integer>> weekMap = null;
weekMap = switchMap.get(positiveSwitch);
if (weekMap == null) {
initWeeklist(positiveSwitch);
}
weekMap = switchMap.get(negativeSwitch);
if (weekMap == null) {
initWeeklist(negativeSwitch);
}
for (int i = 0; i < sPoints.size(); i++) {
JsonObject subJSON = sPoints.get(i).getAsJsonObject();
String day = subJSON.get("dayOfWeek").getAsString();
String setpoint = subJSON.get("setpoint").getAsString();
Integer time = subJSON.get("time").getAsInt();
addSwitch(day, setpoint, time);
}
}
}
/**
* This function updates objects JSONData on the actual set switch points.
*/
public @Nullable JsonObject getUpdatedJSONData(KM200ServiceObject parObject) {
synchronized (switchMap) {
boolean prepareNewOnly = false;
JsonArray sPoints = new JsonArray();
for (String day : days) {
if (switchMap.get(getPositiveSwitch()).containsKey(day)
&& switchMap.get(getNegativeSwitch()).containsKey(day)) {
Integer j;
Integer minDays = Math.min(switchMap.get(getPositiveSwitch()).get(day).size(),
switchMap.get(getNegativeSwitch()).get(day).size());
for (j = 0; j < minDays; j++) {
JsonObject tmpObj = new JsonObject();
tmpObj.addProperty("dayOfWeek", day);
tmpObj.addProperty("setpoint", getPositiveSwitch());
tmpObj.addProperty("time", switchMap.get(getPositiveSwitch()).get(day).get(j));
sPoints.add(tmpObj);
tmpObj = new JsonObject();
tmpObj.addProperty("dayOfWeek", day);
tmpObj.addProperty("setpoint", getNegativeSwitch());
tmpObj.addProperty("time", switchMap.get(getNegativeSwitch()).get(day).get(j));
sPoints.add(tmpObj);
}
/* Check whether one object for a new cycle is already created */
if (switchMap.get(getPositiveSwitch()).get(day).size() > minDays) {
JsonObject tmpObj = new JsonObject();
tmpObj.addProperty("dayOfWeek", day);
tmpObj.addProperty("setpoint", getPositiveSwitch());
tmpObj.addProperty("time", switchMap.get(getPositiveSwitch()).get(day).get(j));
sPoints.add(tmpObj);
prepareNewOnly = true;
} else if (switchMap.get(getNegativeSwitch()).get(day).size() > minDays) {
JsonObject tmpObj = new JsonObject();
tmpObj.addProperty("dayOfWeek", day);
tmpObj.addProperty("setpoint", getNegativeSwitch());
tmpObj.addProperty("time", switchMap.get(getNegativeSwitch()).get(day).get(j));
sPoints.add(tmpObj);
prepareNewOnly = true;
}
}
}
logger.debug("New switching points: {}", sPoints);
JsonObject switchRoot = parObject.getJSONData();
if (null != switchRoot) {
switchRoot.remove("switchPoints");
switchRoot.add("switchPoints", sPoints);
parObject.setJSONData(switchRoot);
} else {
logger.debug("Jsojnoject switchRoot not found");
}
/* Preparation for are new cycle, don't sent it to the device */
if (prepareNewOnly) {
return null;
} else {
return switchRoot;
}
}
}
int getMaxNbOfSwitchPoints() {
return maxNbOfSwitchPoints;
}
int getMaxNbOfSwitchPointsPerDay() {
return maxNbOfSwitchPointsPerDay;
}
public int getSwitchPointTimeRaster() {
return switchPointTimeRaster;
}
public @Nullable String getSetpointProperty() {
return setpointProperty;
}
public @Nullable String getPositiveSwitch() {
return positiveSwitch;
}
public @Nullable String getNegativeSwitch() {
return negativeSwitch;
}
/**
* This function returns the number of cycles
*/
public Integer getNbrCycles() {
synchronized (switchMap) {
Map<String, List<Integer>> weekP = switchMap.get(getPositiveSwitch());
Map<String, List<Integer>> weekN = switchMap.get(getNegativeSwitch());
if (weekP.isEmpty() && weekN.isEmpty()) {
return 0;
}
List<Integer> daysListP = weekP.get(getActiveDay());
List<Integer> daysListN = weekN.get(getActiveDay());
return Math.min(daysListP.size(), daysListN.size());
}
}
/**
* This function returns the selected day
*/
public String getActiveDay() {
return activeDay;
}
/**
* This function returns the selected cycle
*/
public Integer getActiveCycle() {
return activeCycle;
}
/**
* This function returns the positive switch to the selected day and cycle
*/
public Integer getActivePositiveSwitch() {
synchronized (switchMap) {
Map<String, List<Integer>> week = switchMap.get(getPositiveSwitch());
if (week != null) {
List<Integer> daysList = week.get(getActiveDay());
if (!daysList.isEmpty()) {
Integer cycl = getActiveCycle();
if (cycl <= daysList.size()) {
return (daysList.get(getActiveCycle() - 1));
}
}
}
}
return 0;
}
/**
* This function returns the negative switch to the selected day and cycle
*/
public Integer getActiveNegativeSwitch() {
synchronized (switchMap) {
Map<String, List<Integer>> week = switchMap.get(getNegativeSwitch());
if (week != null) {
List<Integer> daysList = week.get(getActiveDay());
if (!daysList.isEmpty()) {
Integer cycl = getActiveCycle();
if (cycl <= daysList.size()) {
return (daysList.get(getActiveCycle() - 1));
}
}
}
}
return 0;
}
}

View File

@@ -0,0 +1,493 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.handler;
import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.km200.internal.KM200ChannelTypeProvider;
import org.openhab.binding.km200.internal.KM200ServiceObject;
import org.openhab.binding.km200.internal.KM200ThingType;
import org.openhab.binding.km200.internal.KM200Utils;
import org.openhab.core.library.CoreItemFactory;
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.thing.Bridge;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeBuilder;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.StateDescriptionFragment;
import org.openhab.core.types.StateDescriptionFragmentBuilder;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KM200ThingHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200ThingHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(KM200ThingHandler.class);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(THING_TYPE_DHW_CIRCUIT, THING_TYPE_HEATING_CIRCUIT, THING_TYPE_SOLAR_CIRCUIT, THING_TYPE_HEAT_SOURCE,
THING_TYPE_SYSTEM_APPLIANCE, THING_TYPE_SYSTEM_HOLIDAYMODES, THING_TYPE_SYSTEM_SENSOR,
THING_TYPE_GATEWAY, THING_TYPE_NOTIFICATION, THING_TYPE_SYSTEM, THING_TYPE_SYSTEMSTATES)
.collect(Collectors.toSet()));
private final KM200ChannelTypeProvider channelTypeProvider;
public KM200ThingHandler(Thing thing, KM200ChannelTypeProvider channelTypeProvider) {
super(thing);
this.channelTypeProvider = channelTypeProvider;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Bridge bridge = this.getBridge();
if (bridge == null) {
return;
}
KM200GatewayHandler gateway = (KM200GatewayHandler) bridge.getHandler();
if (gateway == null) {
return;
}
Channel channel = getThing().getChannel(channelUID.getId());
if (null != channel) {
if (command instanceof DateTimeType || command instanceof DecimalType || command instanceof StringType
|| command instanceof OnOffType) {
gateway.prepareMessage(this.getThing(), channel, command);
} else if (command instanceof RefreshType) {
gateway.refreshChannel(channel);
} else {
logger.warn("Unsupported Command: {} Class: {}", command.toFullString(), command.getClass());
}
}
}
/**
* Choose a tag for a channel
*/
Set<String> checkTags(String unitOfMeasure, @Nullable Boolean readOnly) {
Set<String> tags = new HashSet<>();
if (unitOfMeasure.indexOf("°C") == 0 || unitOfMeasure.indexOf("K") == 0) {
if (null != readOnly) {
if (readOnly) {
tags.add("CurrentTemperature");
} else {
tags.add("TargetTemperature");
}
}
}
return tags;
}
/**
* Choose a category for a channel
*/
String checkCategory(String unitOfMeasure, String topCategory, @Nullable Boolean readOnly) {
String category = null;
if (unitOfMeasure.indexOf("°C") == 0 || unitOfMeasure.indexOf("K") == 0) {
if (null == readOnly) {
category = topCategory;
} else {
if (readOnly) {
category = "Temperature";
} else {
category = "Heating";
}
}
} else if (unitOfMeasure.indexOf("kW") == 0 || unitOfMeasure.indexOf("kWh") == 0) {
category = "Energy";
} else if (unitOfMeasure.indexOf("l/min") == 0 || unitOfMeasure.indexOf("l/h") == 0) {
category = "Flow";
} else if (unitOfMeasure.indexOf("Pascal") == 0 || unitOfMeasure.indexOf("bar") == 0) {
category = "Pressure";
} else if (unitOfMeasure.indexOf("rpm") == 0) {
category = "Flow";
} else if (unitOfMeasure.indexOf("mins") == 0 || unitOfMeasure.indexOf("minutes") == 0) {
category = "Time";
} else if (unitOfMeasure.indexOf("kg/l") == 0) {
category = "Oil";
} else if (unitOfMeasure.indexOf("%%") == 0) {
category = "Number";
} else {
category = topCategory;
}
return category;
}
/**
* Creates a new channel
*/
@Nullable
Channel createChannel(ChannelTypeUID channelTypeUID, ChannelUID channelUID, String root, String type,
@Nullable String currentPathName, String description, String label, boolean addProperties,
boolean switchProgram, StateDescriptionFragment state, String unitOfMeasure) {
URI configDescriptionUriChannel = null;
Channel newChannel = null;
ChannelType channelType = null;
Map<String, String> chProperties = new HashMap<>();
String itemType = "";
String category = null;
if (CoreItemFactory.NUMBER.equals(type)) {
itemType = "NumberType";
category = "Number";
} else if (CoreItemFactory.STRING.equals(type)) {
itemType = "StringType";
category = "Text";
} else {
logger.info("Channeltype {} not supported", type);
return null;
}
try {
configDescriptionUriChannel = new URI(CONFIG_DESCRIPTION_URI_CHANNEL);
channelType = ChannelTypeBuilder.state(channelTypeUID, label, itemType) //
.withDescription(description) //
.withCategory(checkCategory(unitOfMeasure, category, state.isReadOnly())) //
.withTags(checkTags(unitOfMeasure, state.isReadOnly())) //
.withStateDescription(state.toStateDescription()) //
.withConfigDescriptionURI(configDescriptionUriChannel).build();
} catch (URISyntaxException ex) {
logger.warn("Can't create ConfigDescription URI '{}', ConfigDescription for channels not avilable!",
CONFIG_DESCRIPTION_URI_CHANNEL);
channelType = ChannelTypeBuilder.state(channelTypeUID, label, itemType) //
.withDescription(description) //
.withCategory(checkCategory(unitOfMeasure, category, state.isReadOnly())) //
.withTags(checkTags(unitOfMeasure, state.isReadOnly())).build();
}
channelTypeProvider.addChannelType(channelType);
chProperties.put("root", KM200Utils.translatesPathToName(root));
if (null != currentPathName && switchProgram) {
chProperties.put(SWITCH_PROGRAM_CURRENT_PATH_NAME, currentPathName);
}
if (addProperties) {
newChannel = ChannelBuilder.create(channelUID, type).withType(channelTypeUID).withDescription(description)
.withLabel(label).withKind(ChannelKind.STATE).withProperties(chProperties).build();
} else {
newChannel = ChannelBuilder.create(channelUID, type).withType(channelTypeUID).withDescription(description)
.withLabel(label).withKind(ChannelKind.STATE).build();
}
return newChannel;
}
@Override
public void initialize() {
Bridge bridge = this.getBridge();
if (bridge == null) {
logger.debug("Bridge not existing");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
return;
}
logger.debug("initialize, Bridge: {}", bridge);
KM200GatewayHandler gateway = (KM200GatewayHandler) bridge.getHandler();
if (gateway == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
logger.debug("Gateway not existing: {}", bridge);
return;
}
String service = KM200Utils.translatesNameToPath(thing.getProperties().get("root"));
synchronized (gateway.getDevice()) {
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.CONFIGURATION_PENDING);
if (!gateway.getDevice().getInited()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
logger.debug("Bridge: not initialized: {}", bridge);
return;
}
List<Channel> subChannels = new ArrayList<>();
if (gateway.getDevice().containsService(service)) {
KM200ServiceObject serObj = gateway.getDevice().getServiceObject(service);
if (null != serObj) {
addChannels(serObj, thing, subChannels, "");
}
} else if (service.contains(SWITCH_PROGRAM_PATH_NAME)) {
String currentPathName = thing.getProperties().get(SWITCH_PROGRAM_CURRENT_PATH_NAME);
StateDescriptionFragment state = StateDescriptionFragmentBuilder.create().withPattern("%s")
.withOptions(KM200SwitchProgramServiceHandler.daysList).build();
Channel newChannel = createChannel(new ChannelTypeUID(thing.getUID().getAsString() + ":" + "weekday"),
new ChannelUID(thing.getUID(), "weekday"), service + "/" + "weekday", CoreItemFactory.STRING,
currentPathName, "Current selected weekday for cycle selection", "Weekday", true, true, state,
"");
if (null == newChannel) {
logger.warn("Creation of the channel {} was not possible", thing.getUID());
} else {
subChannels.add(newChannel);
}
state = StateDescriptionFragmentBuilder.create().withMinimum(BigDecimal.ZERO).withStep(BigDecimal.ONE)
.withPattern("%d").withReadOnly(true).build();
newChannel = createChannel(new ChannelTypeUID(thing.getUID().getAsString() + ":" + "nbrCycles"),
new ChannelUID(thing.getUID(), "nbrCycles"), service + "/" + "nbrCycles",
CoreItemFactory.NUMBER, currentPathName, "Number of switching cycles", "Number", true, true,
state, "");
if (null == newChannel) {
logger.warn("Creation of the channel {} was not possible", thing.getUID());
} else {
subChannels.add(newChannel);
}
state = StateDescriptionFragmentBuilder.create().withMinimum(BigDecimal.ZERO).withStep(BigDecimal.ONE)
.withPattern("%d").build();
newChannel = createChannel(new ChannelTypeUID(thing.getUID().getAsString() + ":" + "cycle"),
new ChannelUID(thing.getUID(), "cycle"), service + "/" + "cycle", CoreItemFactory.NUMBER,
currentPathName, "Current selected cycle", "Cycle", true, true, state, "");
if (null == newChannel) {
logger.warn("Creation of the channel {} was not possible", thing.getUID());
} else {
subChannels.add(newChannel);
}
state = StateDescriptionFragmentBuilder.create().withMinimum(BigDecimal.ZERO).withStep(BigDecimal.ONE)
.withPattern("%d minutes").build();
String posName = thing.getProperties().get(SWITCH_PROGRAM_POSITIVE);
newChannel = createChannel(new ChannelTypeUID(thing.getUID().getAsString() + ":" + posName),
new ChannelUID(thing.getUID(), posName), service + "/" + posName, CoreItemFactory.NUMBER,
currentPathName, "Positive switch of the cycle, like 'Day' 'On'", posName, true, true, state,
"minutes");
if (null == newChannel) {
logger.warn("Creation of the channel {} was not possible", thing.getUID());
} else {
subChannels.add(newChannel);
}
String negName = thing.getProperties().get(SWITCH_PROGRAM_NEGATIVE);
newChannel = createChannel(new ChannelTypeUID(thing.getUID().getAsString() + ":" + negName),
new ChannelUID(thing.getUID(), negName), service + "/" + negName, CoreItemFactory.NUMBER,
currentPathName, "Negative switch of the cycle, like 'Night' 'Off'", negName, true, true, state,
"minutes");
if (null == newChannel) {
logger.warn("Creation of the channel {} was not possible", thing.getUID());
} else {
subChannels.add(newChannel);
}
}
ThingBuilder thingBuilder = editThing();
List<Channel> actChannels = thing.getChannels();
for (Channel channel : actChannels) {
thingBuilder.withoutChannel(channel.getUID());
}
thingBuilder.withChannels(subChannels);
updateThing(thingBuilder.build());
updateStatus(ThingStatus.ONLINE);
}
}
@Override
public void dispose() {
channelTypeProvider.removeChannelTypesForThing(getThing().getUID());
}
/**
* Checks whether a channel is linked to an item
*/
public boolean checkLinked(Channel channel) {
return isLinked(channel.getUID().getId());
}
/**
* Search for services and add them to a list
*/
private void addChannels(KM200ServiceObject serObj, Thing thing, List<Channel> subChannels, String subNameAddon) {
String service = serObj.getFullServiceName();
Set<String> subKeys = serObj.serviceTreeMap.keySet();
List<String> asProperties = null;
/* Some defines for dummy values, we will ignore such services */
final BigDecimal maxInt16AsFloat = new BigDecimal(+3276.8).setScale(6, RoundingMode.HALF_UP);
final BigDecimal minInt16AsFloat = new BigDecimal(-3276.8).setScale(6, RoundingMode.HALF_UP);
final BigDecimal maxInt16AsInt = new BigDecimal(3200).setScale(4, RoundingMode.HALF_UP);
for (KM200ThingType tType : KM200ThingType.values()) {
String root = tType.getRootPath();
if (root.compareTo(service) == 0) {
asProperties = tType.asBridgeProperties();
}
}
for (String subKey : subKeys) {
if (asProperties != null) {
if (asProperties.contains(subKey)) {
continue;
}
}
Map<String, String> properties = new HashMap<>(1);
String root = service + "/" + subKey;
properties.put("root", KM200Utils.translatesPathToName(root));
String subKeyType = serObj.serviceTreeMap.get(subKey).getServiceType();
boolean readOnly;
String unitOfMeasure = "";
StateDescriptionFragment state = null;
ChannelTypeUID channelTypeUID = new ChannelTypeUID(
thing.getUID().getAsString() + ":" + subNameAddon + subKey);
Channel newChannel = null;
ChannelUID channelUID = new ChannelUID(thing.getUID(), subNameAddon + subKey);
if (serObj.serviceTreeMap.get(subKey).getWriteable() > 0) {
readOnly = false;
} else {
readOnly = true;
}
if ("temperatures".compareTo(thing.getUID().getId()) == 0) {
unitOfMeasure = "°C";
}
logger.trace("Create things: {} id: {} channel: {}", thing.getUID(), subKey, thing.getUID().getId());
switch (subKeyType) {
case DATA_TYPE_STRING_VALUE:
/* Creating an new channel type with capabilities from service */
List<StateOption> options = null;
if (serObj.serviceTreeMap.get(subKey).getValueParameter() != null) {
options = new ArrayList<>();
// The type is definitely correct here
@SuppressWarnings("unchecked")
List<String> subValParas = (List<String>) serObj.serviceTreeMap.get(subKey).getValueParameter();
if (null != subValParas) {
for (String para : subValParas) {
StateOption stateOption = new StateOption(para, para);
options.add(stateOption);
}
}
}
StateDescriptionFragmentBuilder builder = StateDescriptionFragmentBuilder.create().withPattern("%s")
.withReadOnly(readOnly);
if (options != null && !options.isEmpty()) {
builder.withOptions(options);
}
state = builder.build();
newChannel = createChannel(channelTypeUID, channelUID, root, CoreItemFactory.STRING, null, subKey,
subKey, true, false, state, unitOfMeasure);
break;
case DATA_TYPE_FLOAT_VALUE:
/*
* Check whether the value is a NaN. Usually all floats are BigDecimal. If it's a double then it's
* Double.NaN. In this case we are ignoring this channel.
*/
BigDecimal minVal = null;
BigDecimal maxVal = null;
BigDecimal step = null;
final BigDecimal val;
Object tmpVal = serObj.serviceTreeMap.get(subKey).getValue();
if (tmpVal instanceof Double) {
continue;
}
/* Check whether the value is a dummy (e.g. not connected sensor) */
val = (BigDecimal) serObj.serviceTreeMap.get(subKey).getValue();
if (val != null) {
if (val.setScale(6, RoundingMode.HALF_UP).equals(maxInt16AsFloat)
|| val.setScale(6, RoundingMode.HALF_UP).equals(minInt16AsFloat)
|| val.setScale(4, RoundingMode.HALF_UP).equals(maxInt16AsInt)) {
continue;
}
}
/* Check the capabilities of this service */
if (serObj.serviceTreeMap.get(subKey).getValueParameter() != null) {
/* Creating an new channel type with capabilities from service */
// The type is definitely correct here
@SuppressWarnings("unchecked")
List<Object> subValParas = (List<Object>) serObj.serviceTreeMap.get(subKey).getValueParameter();
if (null != subValParas) {
minVal = (BigDecimal) subValParas.get(0);
maxVal = (BigDecimal) subValParas.get(1);
if (subValParas.size() > 2) {
unitOfMeasure = (String) subValParas.get(2);
if ("C".equals(unitOfMeasure)) {
unitOfMeasure = "°C";
}
}
step = BigDecimal.valueOf(0.5);
}
}
builder = StateDescriptionFragmentBuilder.create().withPattern("%.1f " + unitOfMeasure)
.withReadOnly(readOnly);
if (minVal != null) {
builder.withMinimum(minVal);
}
if (maxVal != null) {
builder.withMaximum(maxVal);
}
if (step != null) {
builder.withStep(step);
}
state = builder.build();
newChannel = createChannel(channelTypeUID, channelUID, root, CoreItemFactory.NUMBER, null, subKey,
subKey, true, false, state, unitOfMeasure);
break;
case DATA_TYPE_REF_ENUM:
/* Check whether the sub service should be ignored */
boolean ignoreIt = false;
for (KM200ThingType tType : KM200ThingType.values()) {
if (tType.getThingTypeUID().equals(thing.getThingTypeUID())) {
for (String ignore : tType.ignoreSubService()) {
if (ignore.equals(subKey)) {
ignoreIt = true;
}
}
}
}
if (ignoreIt) {
continue;
}
/* Search for new services in sub path */
addChannels(serObj.serviceTreeMap.get(subKey), thing, subChannels, subKey + "_");
break;
case DATA_TYPE_ERROR_LIST:
if ("nbrErrors".equals(subKey) || "error".equals(subKey)) {
state = StateDescriptionFragmentBuilder.create().withPattern("%.0f").withReadOnly(readOnly)
.build();
newChannel = createChannel(new ChannelTypeUID(thing.getUID().getAsString() + ":" + subKey),
channelUID, root, CoreItemFactory.NUMBER, null, subKey, subKey, true, false, state,
unitOfMeasure);
} else if ("errorString".equals(subKey)) {
state = StateDescriptionFragmentBuilder.create().withPattern("%s").withReadOnly(readOnly)
.build();
newChannel = createChannel(new ChannelTypeUID(thing.getUID().getAsString() + ":" + subKey),
channelUID, root, CoreItemFactory.STRING, null, "Error message", "Text", true, false,
state, unitOfMeasure);
}
break;
}
if (newChannel != null && state != null) {
subChannels.add(newChannel);
}
}
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.km200.internal.handler;
import static org.openhab.binding.km200.internal.KM200BindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.km200.internal.KM200Device;
import org.openhab.binding.km200.internal.KM200ServiceObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* The KM200DataHandler is representing the virtual services inside this binding
*
* @author Markus Eckhardt - Initial contribution
*/
@NonNullByDefault
public class KM200VirtualServiceHandler {
private final Logger logger = LoggerFactory.getLogger(KM200VirtualServiceHandler.class);
private final KM200Device remoteDevice;
public KM200VirtualServiceHandler(KM200Device remoteDevice) {
this.remoteDevice = remoteDevice;
}
/**
* This function creates the virtual services
*/
public void initVirtualObjects() {
KM200ServiceObject newObject = null;
try {
for (KM200ServiceObject object : remoteDevice.virtualList) {
logger.debug("Full Servicename: {}", object.getFullServiceName());
String id = object.getFullServiceName();
String type = object.getServiceType();
switch (type) {
case DATA_TYPE_SWITCH_PROGRAM:
KM200SwitchProgramServiceHandler sPService = ((KM200SwitchProgramServiceHandler) object
.getValueParameter());
if (null != sPService) {
if (!sPService.determineSwitchNames(remoteDevice)) {
logger.info("No references for switch service: {}, this is not supported",
object.getFullServiceName());
continue;
}
JsonObject nodeRoot = object.getJSONData();
if (null != nodeRoot) {
sPService.updateSwitches(nodeRoot, remoteDevice);
newObject = new KM200ServiceObject(id + "/weekday", type, 1, 1, 0, 1, id);
object.serviceTreeMap.put("weekday", newObject);
newObject = new KM200ServiceObject(id + "/nbrCycles", type, 1, 0, 0, 1, id);
object.serviceTreeMap.put("nbrCycles", newObject);
newObject = new KM200ServiceObject(id + "/cycle", type, 1, 1, 0, 1, id);
object.serviceTreeMap.put("cycle", newObject);
logger.debug("On: {} Of: {}", id + "/" + sPService.getPositiveSwitch(),
id + "/" + sPService.getNegativeSwitch());
newObject = new KM200ServiceObject(id + "/" + sPService.getPositiveSwitch(), type, 1,
object.getWriteable(), object.getRecordable(), 1, id);
String posSwitch = sPService.getPositiveSwitch();
if (null != posSwitch) {
object.serviceTreeMap.put(posSwitch, newObject);
}
newObject = new KM200ServiceObject(id + "/" + sPService.getNegativeSwitch(), type, 1,
object.getWriteable(), object.getRecordable(), 1, id);
String negSwitch = sPService.getNegativeSwitch();
if (null != negSwitch) {
object.serviceTreeMap.put(negSwitch, newObject);
}
}
}
break;
case DATA_TYPE_ERROR_LIST:
newObject = new KM200ServiceObject(id + "/nbrErrors", type, 1, 0, 0, 1, id);
object.serviceTreeMap.put("nbrErrors", newObject);
newObject = new KM200ServiceObject(id + "/error", type, 1, 1, 0, 1, id);
object.serviceTreeMap.put("error", newObject);
newObject = new KM200ServiceObject(id + "/errorString", type, 1, 0, 0, 1, id);
object.serviceTreeMap.put("errorString", newObject);
break;
}
}
} catch (JsonParseException e) {
logger.warn("Parsingexception in JSON: {}", e.getMessage());
}
}
}

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="km200" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>KM200 Binding</name>
<description>The KM200 Binding is communicating with a Buderus Logamatic web KM200 / KM100 / KM50 gateway.
These devices
are gateways for heating systems and allows to control them. It is possible to receive and send parameters.
</description>
<author>Markus Eckhardt</author>
</binding:binding>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="appliance" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Appliance</label>
<description>This thing is representing the appliance (The heater inside of this heating system).</description>
<properties>
<property name="root">#system#appliance</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<bridge-type id="kmdevice">
<label>KM200/100/50</label>
<description>The KM200 binding is communicating with a Buderus Logamatic web KM200 / KM100 / KM50. It is possible to
receive and send parameters like string or float values.</description>
<config-description>
<parameter name="ip4Address" type="text" required="true">
<label>IP4 Address</label>
<context>network-address</context>
<description>IP4 Address of the KMXXX device</description>
</parameter>
<parameter name="privateKey" type="text" required="true">
<label>Private Key</label>
<context>password</context>
<description>Private en-/decryption key built from MD5Salt, GatewayPassword and PrivatePassword</description>
</parameter>
<parameter name="refreshInterval" type="integer" required="true">
<label>Auto Refresh Interval</label>
<description>Auto refresh interval in seconds</description>
<default>30</default>
</parameter>
<parameter name="readDelay" type="integer" required="true">
<label>Read Delay</label>
<description>Delay between two read attempts in ms</description>
<default>100</default>
</parameter>
<parameter name="maxNbrRepeats" type="integer" required="true">
<label>Maximum Number of Repeats</label>
<description>Maximum number of repeats in case of a communication error (like HTTP 500 error)</description>
<default>10</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="dhwCircuit" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Hot Water Circuit</label>
<description>This thing is representing a hot water circuit.</description>
<properties>
<property name="root">#dhwCircuits</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="gateway" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Gateway</label>
<description>This thing is representing the gateway. (The connected KM200/100/50 device)</description>
<properties>
<property name="root">#gateway</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="heatSource" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Heat Source</label>
<description>This thing is representing the heat source.</description>
<properties>
<property name="root">#heatSources</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="heatingCircuit" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Heating Circuit</label>
<description>This thing is representing a heating circuit.</description>
<properties>
<property name="root">#heatingCircuits</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="holidayMode" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Holiday Mode</label>
<description>This thing is representing the holiday modes configuration.</description>
<properties>
<property name="root">#system#holidayModes</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="notification" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Notifications</label>
<description>This thing is representing the notifications.</description>
<properties>
<property name="root">#notifications</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="sensor" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Sensors</label>
<description>This thing is representing the sensors.</description>
<properties>
<property name="root">#system#sensors</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="solarCircuit" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Solar Circuit</label>
<description>This thing is representing a solar circuit.</description>
<properties>
<property name="root">#solarCircuits</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="switchProgram" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>Switch Program</label>
<description>This thing is representing a switch program.</description>
<properties>
<property name="root"/>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="system" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>System</label>
<description>This thing is representing the system without sensors and appliance.</description>
<properties>
<property name="root">#system</property>
</properties>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="km200"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="systemStates" listed="false">
<supported-bridge-type-refs>
<bridge-type-ref id="kmdevice"/>
</supported-bridge-type-refs>
<label>System States</label>
<description>This thing is representing the systems states.</description>
<properties>
<property name="root">#systemStates</property>
</properties>
</thing-type>
</thing:thing-descriptions>