added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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>
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<>()));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user