added migrated 2.x add-ons

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

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.vitotronic</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,133 @@
# Vitotronic Binding
Viessmann heating systems with Vitotronic has an optolink Interface for maintenance.
This interface can use for get/set data in the heating system. [see on openv](https://github.com/openv/openv/wiki/)
The Vitotronic binding is a solution to bind this interface into openHAB.
It supports the separation of the heating adaption from the integration in [openHAB](https://www.openhab.org/).
![Architecture](doc/architecture_vitotronic.jpg)
The adapter transforms the address oriented raw interface of the Vitotronic to an abstract format.
The adapter itself is not a part of the binding.
[A alpha version is available here](https://github.com/steand/optolink)
[More Information about the adapter](https://github.com/steand/optolink/wiki)
## Supported Things
For easy using are the main things of a heating system are already define in this binding:
* heating (Vitotronic core system)
* pelletburner (Pellet Fireplace, works for wood also)
* oilburner (Oil Fireplace)
* gasburner (Gas Fireplace)
* storagetank (Storage Tank, stores heat in a water tank on 3 levels: bottom, middle, top=hot water)
* circuit (Heating circuit controls the flow between the heating system and the radiators in the rooms)
* solar (Solar water heating (SWH): Convert sunlight into energy for water heating)
For advanced used 3 basic things of a headingsystem define also.
* temperaturesensor (Single temperature sensor)
* pump (Single pump)
* valve (Single valve)
Note: The mapping of things and channels to the heating system addresses must be done in the adapter.
## Discovery
The binding discovers the adapter with broadcast and put the found `vitotronic:bridge` into the inbox.
For automatic detection the adapter and **openHAB** must be on the same LAN.
The discovery itself must be start in the Paper UI.
If the bridge isn't on the same LAN the bridge can also add manually.
In this case the `IP-Address` and the `adapterID` is required.
Íf the `vitotronic:bridge` added a second discovery will be start.
It discovers all things, define in the adapter and put found things into the inbox.
## Binding Configuration
Binding itself has 4 configuration parameters:
* ipAddress (The IP address of the Optolink adapter)
* port (Port of the LAN gateway. Default: 31113)
* adapterID (The ID/Name of the adapter)
* refreshInterval (Refresh time for data in seconds. Default: 600 seconds)
If the adapter is automatic discovered the ipAddress, and adapterID will be set by discovery.
The rereshInterval can be set between 60 and 600 seconds.
The minimal setting is dependent of the performance of the adapter.
## Thing Configuration
There is no configuration of Things necessary.
Only some channels are set active by default.
If this channels are defined in the adapter and will be used in **openHAB** it must set active manually.
Don't change the Thing Name. It is the reference to the name in the adapter.
## Channels
The follow channels are implemented:
| Channel Type ID | Item Type | Description |
|------------------------|-----------|-------------------------------------------------------|
| systemtime | DateTime | DateTime of the heating system |
| outside_temp | Number | Outside temperature sensor |
| boiler_temp | Number | Temperature sensor of boiler (fireplace) |
| malfunction | Switch | General malfunction state of the heating system |
| exhaust_temp | Number | Exhaust temperature |
| flowuprating | Switch | Pump state of flow up rating |
| flame_temp | Number | Temperature of flame |
| airshutter_prim | Number | Position of the primary air shutter |
| airshutter_sec | Number | Position of the secondary air shutter |
| lambdasensor | Number | Oxygen content of the exhaust air |
| fanspeed | Number | Fan Speed in rpm |
| fanspeed_target | Number | Fan Speed in rpm |
| error | Switch | |
| starts | Number | Count of starts |
| ontime | Number | Ontime in hours |
| consumedpellets | Number | Consumed Pellets since start of heating in tons |
| power | Number | Power of the pellet burner |
| powerlevel | Number | Power of the oil/gas burner |
| actualpower | Number | Actual power of the burner |
| ontimelevel1 | Number | Ontime in hours |
| ontimelevel2 | Number | Ontime in hours |
| consumedoil | Number | Consumed Oil since start of heating in Liter |
| hotwater_temp | Number | Temperature sensor of the hot water |
| hotwater_temp_setpoint | Number | Hot water temperature setpoint (target) |
| middle_temp | Number | Temperature sensor in the middle of the storage tank |
| bottom_temp | Number | Temperature sensor at the bottom of the storage tank |
| circuitpump | Switch | Circuit pump state |
| flowtemperature | Number | Temperature sensor of the ciruit flow |
| pump | Switch | Pump state |
| operationmode | Number | Operationmode |
| savemode | Switch | Savemode on/off |
| partymode | Switch | Partymode on/off |
| party_temp_setpoint | Number | Party mode temperature setpoint (target) |
| room_temp | Number | Target temperature of rooms |
| room_temp_setpoint | Number | Room temperature setpoint (target) |
| save_temp_setpoint | Number | Save mode temperature setpoint (target) |
| gradient | Number | The gradient relativ to outside temperature |
| niveau | Number | The niveau relativ to outside temperature |
| timer_MO | String | Heating timer for Monday |
| timer_TU | String | Heating timer for Tuesday |
| timer_WE | String | Heating timer for Wednesday |
| timer_TH | String | Heating timer for Thursday |
| timer_FR | String | Heating timer for Friday |
| timer_SA | String | Heating timer for Saturday |
| timer_SU | String | Heating timer for Sunday |
| timer_ww_MO | String | Hot water timer for Monday |
| timer_ww_TU | String | Hot water timer for Tuesday |
| timer_ww_WE | String | Hot water timer for Wednesday |
| timer_ww_TH | String | Hot water timer for Thursday |
| timer_ww_FR | String | Hot water timer for Friday |
| timer_ww_SA | String | Hot water timer for Saturday |
| timer_ww_SU | String | Hot water timer for Sunday |
| collector_temp | Number | Actual temperature of the collector |
| storagetank_temp | Number | Actual temperature of the storage tank (solar sensor) |
| bufferload | Switch | State of the pump (on/off) |
| loadsuppression | Switch | State of the load suppression (on/off) |
| producedheat | Number | Produced heat since starting solar system |
| temperature | Number | Generic temperature sensor |
| valve | Number | Value of a generic valve |

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.vitotronic</artifactId>
<name>openHAB Add-ons :: Bundles :: Vitotronic Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.vitotronic-${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-vitotronic" description="Vitotronic Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.vitotronic/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,28 @@
/**
* 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.vitotronic.internal;
/**
* The {@link VitotronicBindingConfiguration} class defines variables which are
* used for the binding configuration.
*
* @author Stefan Andres - Initial contribution
*/
public class VitotronicBindingConfiguration {
public String ipAddress;
public Integer port;
public String adapterId;
public Integer refreshInterval;
}

View File

@@ -0,0 +1,79 @@
/**
* 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.vitotronic.internal;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link VitotronicBinding} class defines common constants, which are
* used across the whole binding.
*
* @author Stefan Andres - Initial contribution
*/
@NonNullByDefault
public class VitotronicBindingConstants {
public static final String BROADCAST_MESSAGE = "@@@@VITOTRONIC@@@@/";
public static final int BROADCAST_PORT = 31113;
public static final String BINDING_ID = "vitotronic";
public static final String IP_ADDRESS = "ipAddress";
public static final String PORT = "port";
public static final String ADAPTER_ID = "adapterID";
public static final String REFRESH_INTERVAL = "refreshInterval";
public static final String DISCOVERY_INTERVAL = "discoveryInterval";
// List of main device types
public static final String BRIDGE_VITOTRONIC = "bridge";
// List of all Thing Type
public static final String THING_ID_HEATING = "heating";
public static final String THING_ID_GASBURNER = "gasburner";
public static final String THING_ID_PELLETBURNER = "pelletburner";
public static final String THING_ID_OILBURNER = "oilburner";
public static final String THING_ID_STORAGETANK = "storagetank";
public static final String THING_ID_CIRCUIT = "circuit";
public static final String THING_ID_SOLAR = "solar";
public static final String THING_ID_TEMPERATURESENSOR = "temperaturesensor";
public static final String THING_ID_PUMP = "pump";
public static final String THING_ID_VALVE = "valve";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_UID_BRIDGE = new ThingTypeUID(BINDING_ID, BRIDGE_VITOTRONIC);
public static final ThingTypeUID THING_TYPE_UID_HEATING = new ThingTypeUID(BINDING_ID, THING_ID_HEATING);
public static final ThingTypeUID THING_TYPE_UID_GASBURNER = new ThingTypeUID(BINDING_ID, THING_ID_GASBURNER);
public static final ThingTypeUID THING_TYPE_UID_PELLETBURNER = new ThingTypeUID(BINDING_ID, THING_ID_PELLETBURNER);
public static final ThingTypeUID THING_TYPE_UID_OILBURNER = new ThingTypeUID(BINDING_ID, THING_ID_OILBURNER);
public static final ThingTypeUID THING_TYPE_UID_STORAGETANK = new ThingTypeUID(BINDING_ID, THING_ID_STORAGETANK);
public static final ThingTypeUID THING_TYPE_UID_CIRCUIT = new ThingTypeUID(BINDING_ID, THING_ID_CIRCUIT);
public static final ThingTypeUID THING_TYPE_UID_SOLAR = new ThingTypeUID(BINDING_ID, THING_ID_SOLAR);
public static final ThingTypeUID THING_TYPE_UID_TEMPERATURESENSOR = new ThingTypeUID(BINDING_ID,
THING_ID_TEMPERATURESENSOR);
public static final ThingTypeUID THING_TYPE_UID_PUMP = new ThingTypeUID(BINDING_ID, THING_ID_PUMP);
public static final ThingTypeUID THING_TYPE_UID_VALVE = new ThingTypeUID(BINDING_ID, THING_ID_VALVE);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(Stream
.of(THING_TYPE_UID_BRIDGE, THING_TYPE_UID_GASBURNER, THING_TYPE_UID_HEATING, THING_TYPE_UID_PELLETBURNER,
THING_TYPE_UID_OILBURNER, THING_TYPE_UID_STORAGETANK, THING_TYPE_UID_CIRCUIT, THING_TYPE_UID_SOLAR,
THING_TYPE_UID_TEMPERATURESENSOR, THING_TYPE_UID_PUMP, THING_TYPE_UID_VALVE)
.collect(Collectors.toSet()));
public static final Set<ThingTypeUID> SUPPORTED_BRIDGE_THING_TYPES_UIDS = Collections
.singleton(THING_TYPE_UID_BRIDGE);
}

View File

@@ -0,0 +1,100 @@
/**
* 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.vitotronic.internal;
import java.util.Hashtable;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.vitotronic.internal.discovery.VitotronicDiscoveryService;
import org.openhab.binding.vitotronic.internal.handler.VitotronicBridgeHandler;
import org.openhab.binding.vitotronic.internal.handler.VitotronicThingHandler;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryService;
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.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VitotronicHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Stefan Andres - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.vitotronic")
public class VitotronicHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(VitotronicHandlerFactory.class);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
logger.trace("Ask Handler for Supported Thing {}",
VitotronicBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID));
return VitotronicBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
logger.trace("Install Handler for Thing {}", thing.getUID());
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(VitotronicBindingConstants.THING_TYPE_UID_BRIDGE)) {
VitotronicBridgeHandler handler = new VitotronicBridgeHandler((Bridge) thing);
registerThingDiscovery(handler);
return handler;
}
if (supportsThingType(thingTypeUID)) {
return new VitotronicThingHandler(thing);
}
return null;
}
private synchronized void registerThingDiscovery(VitotronicBridgeHandler bridgeHandler) {
VitotronicDiscoveryService discoveryService = new VitotronicDiscoveryService(bridgeHandler);
logger.trace("Try to register Discovery service on BundleID: {} Service: {}",
bundleContext.getBundle().getBundleId(), DiscoveryService.class.getName());
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>());
discoveryService.activate();
}
@Override
public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration,
@Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) {
logger.trace("Create Thing for Type {}", thingUID);
String adapterID = (String) configuration.get(VitotronicBindingConstants.ADAPTER_ID);
if (VitotronicBindingConstants.THING_TYPE_UID_BRIDGE.equals(thingTypeUID)) {
logger.trace("Create Bridge: {}", adapterID);
return super.createThing(thingTypeUID, configuration, thingUID, null);
} else {
if (supportsThingType(thingTypeUID)) {
logger.trace("Create Thing: {}", adapterID);
return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID);
}
}
return null;
}
}

View File

@@ -0,0 +1,107 @@
/**
* 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.vitotronic.internal.discovery;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.vitotronic.internal.VitotronicBindingConstants;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VitotronicBridgeDiscovery} class handles the discovery of optolink adapter
* with broadcasting and put it to inbox, if found.
*
*
* @author Stefan Andres - Initial contribution
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.vitotronic")
public class VitotronicBridgeDiscovery extends AbstractDiscoveryService {
private int adapterPort = 31113;
private final Logger logger = LoggerFactory.getLogger(VitotronicBridgeDiscovery.class);
public VitotronicBridgeDiscovery() throws IllegalArgumentException {
super(VitotronicBindingConstants.SUPPORTED_BRIDGE_THING_TYPES_UIDS, 15, false);
}
@Override
protected void startScan() {
logger.trace("Start discovery of Vitotronic Optolink Adapter (VOP)");
adapterPort = VitotronicBindingConstants.BROADCAST_PORT;
scheduler.execute(searchRunnable);
}
// Runnable for search adapter
private Runnable searchRunnable = () -> {
logger.trace("Start adapter discovery ");
logger.debug("Send broadcast message");
try (DatagramSocket localSocket = new DatagramSocket()) {
localSocket.setBroadcast(true);
localSocket.setSoTimeout(10000); // Listen 10 seconds
String broadcastMsg = VitotronicBindingConstants.BROADCAST_MESSAGE + "*";
byte[] sendData = broadcastMsg.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,
InetAddress.getByName("255.255.255.255"), adapterPort);
localSocket.send(sendPacket);
byte[] receiveBuffer = new byte[255];
// Listen for answer
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
localSocket.receive(receivePacket);
String receiveMessage = new String(receivePacket.getData()).trim();
String receiveIP = receivePacket.getAddress().getHostAddress();
int receivePort = receivePacket.getPort();
logger.debug("Received Message: {} ", receiveMessage);
logger.debug("Received from Host: {}", receiveIP);
logger.debug("Received from Port: {}", receivePort);
if (receiveMessage.startsWith(VitotronicBindingConstants.BROADCAST_MESSAGE)) {
// register bridge
String adapterID = receiveMessage.substring(VitotronicBindingConstants.BROADCAST_MESSAGE.length())
.toUpperCase();
addAdapter(receiveIP, receivePort, adapterID);
}
} catch (IOException e) {
logger.debug("No optolink adapter found!");
}
};
private void addAdapter(String remoteIP, int remotePort, String adapterID) {
Map<String, Object> properties = new HashMap<>(3);
properties.put(VitotronicBindingConstants.IP_ADDRESS, remoteIP);
properties.put(VitotronicBindingConstants.PORT, remotePort);
properties.put(VitotronicBindingConstants.ADAPTER_ID, adapterID);
ThingUID uid = new ThingUID(VitotronicBindingConstants.THING_TYPE_UID_BRIDGE, adapterID);
thingDiscovered(DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(adapterID).build());
}
}

View File

@@ -0,0 +1,98 @@
/**
* 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.vitotronic.internal.discovery;
import static org.openhab.binding.vitotronic.internal.VitotronicBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.vitotronic.internal.VitotronicBindingConstants;
import org.openhab.binding.vitotronic.internal.handler.VitotronicBridgeHandler;
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.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VitotronicBridgeDiscovery} class handles the discovery of things.
* with broadcasting and put it to inbox, if found.
*
*
* @author Stefan Andres - Initial contribution
*/
@NonNullByDefault
public class VitotronicDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(VitotronicDiscoveryService.class);
private VitotronicBridgeHandler vitotronicBridgeHandler;
public VitotronicDiscoveryService(VitotronicBridgeHandler vitotronicBridgeHandler) throws IllegalArgumentException {
super(VitotronicBindingConstants.SUPPORTED_THING_TYPES_UIDS, 10, false);
this.vitotronicBridgeHandler = vitotronicBridgeHandler;
}
private void addThing(ThingUID bridgeUID, String thingType, String thingID) {
logger.trace("Adding new Vitotronic thing: {}", thingID);
ThingUID thingUID = null;
switch (thingType) {
case THING_ID_HEATING:
thingUID = new ThingUID(THING_TYPE_UID_HEATING, bridgeUID, thingID);
break;
case THING_ID_GASBURNER:
thingUID = new ThingUID(THING_TYPE_UID_GASBURNER, bridgeUID, thingID);
break;
case THING_ID_PELLETBURNER:
thingUID = new ThingUID(THING_TYPE_UID_PELLETBURNER, bridgeUID, thingID);
break;
case THING_ID_STORAGETANK:
thingUID = new ThingUID(THING_TYPE_UID_STORAGETANK, bridgeUID, thingID);
break;
case THING_ID_CIRCUIT:
thingUID = new ThingUID(THING_TYPE_UID_CIRCUIT, bridgeUID, thingID);
break;
case THING_ID_SOLAR:
thingUID = new ThingUID(THING_TYPE_UID_SOLAR, bridgeUID, thingID);
break;
}
if (thingUID != null) {
logger.trace("Adding new Discovery thingType: {} bridgeType: {}", thingUID, bridgeUID);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeUID)
.withLabel(thingID).build();
logger.trace("call register: {} label: {}", discoveryResult.getBindingId(), discoveryResult.getLabel());
thingDiscovered(discoveryResult);
} else {
logger.debug("Discovered Thing is unsupported: type '{}'", thingID);
}
}
public void addVitotronicThing(String thingType, String thingID) {
addThing(vitotronicBridgeHandler.getThing().getUID(), thingType, thingID);
}
public void activate() {
vitotronicBridgeHandler.registerDiscoveryService(this);
}
@Override
public void deactivate() {
vitotronicBridgeHandler.unregisterDiscoveryService();
}
@Override
protected void startScan() {
// Scan will be done by bridge
}
}

View File

@@ -0,0 +1,420 @@
/**
* 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.vitotronic.internal.handler;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.openhab.binding.vitotronic.internal.VitotronicBindingConfiguration;
import org.openhab.binding.vitotronic.internal.discovery.VitotronicDiscoveryService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
/**
* The {@link VitotronicBridgeHandler} class handles the connection to the
* optolink adapter.
*
* @author Stefan Andres - Initial contribution
*/
public class VitotronicBridgeHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(VitotronicBridgeHandler.class);
private String ipAddress;
private int port;
private int refreshInterval = 300;
private Socket socket;
private PrintStream out;
private InputStream inStream;
private boolean isConnect = false;
private boolean isDiscover = false;
public VitotronicBridgeHandler(Bridge bridge) {
super(bridge);
}
@Override
public void updateStatus(ThingStatus status) {
super.updateStatus(status);
updateThingHandlersStatus(status);
}
public void updateStatus() {
if (isConnect) {
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE);
}
}
// Managing Thing Discovery Service
private VitotronicDiscoveryService discoveryService = null;
public void registerDiscoveryService(VitotronicDiscoveryService discoveryService) {
if (discoveryService == null) {
throw new IllegalArgumentException("It's not allowed to pass a null ThingDiscoveryListener.");
} else {
this.discoveryService = discoveryService;
logger.trace("register Discovery Service");
}
}
public void unregisterDiscoveryService() {
discoveryService = null;
logger.trace("unregister Discovery Service");
}
// Handles Thing discovery
private void createThing(String thingType, String thingID) {
logger.trace("Create thing Type='{}' id='{}'", thingType, thingID);
if (discoveryService != null) {
discoveryService.addVitotronicThing(thingType, thingID);
}
}
// Managing ThingHandler
private Map<String, VitotronicThingHandler> thingHandlerMap = new HashMap<>();
public void registerVitotronicThingListener(VitotronicThingHandler thingHandler) {
if (thingHandler == null) {
throw new IllegalArgumentException("It's not allowed to pass a null ThingHandler.");
} else {
String thingID = thingHandler.getThing().getUID().getId();
if (thingHandlerMap.get(thingID) == null) {
thingHandlerMap.put(thingID, thingHandler);
logger.trace("register thingHandler for thing: {}", thingID);
updateThingHandlerStatus(thingHandler, this.getStatus());
sendSocketData("get " + thingID);
} else {
logger.trace("thingHandler for thing: '{}' already registered", thingID);
}
}
}
public void unregisterThingListener(VitotronicThingHandler thingHandler) {
if (thingHandler != null) {
String thingID = thingHandler.getThing().getUID().getId();
if (thingHandlerMap.remove(thingID) == null) {
logger.trace("thingHandler for thing: {} not registered", thingID);
}
}
}
private void updateThingHandlerStatus(VitotronicThingHandler thingHandler, ThingStatus status) {
thingHandler.updateStatus(status);
}
private void updateThingHandlersStatus(ThingStatus status) {
for (Map.Entry<String, VitotronicThingHandler> entry : thingHandlerMap.entrySet()) {
updateThingHandlerStatus(entry.getValue(), status);
}
}
// Background Runables
private ScheduledFuture<?> pollingJob;
private Runnable pollingRunnable = () -> {
logger.trace("Polling job called");
if (!isConnect) {
startSocketReceiver();
try {
Thread.sleep(5000); // Wait for connection .
} catch (InterruptedException e) {
}
}
if (isConnect) {
scanThings();
refreshData();
}
};
private synchronized void startAutomaticRefresh() {
if (pollingJob == null || pollingJob.isCancelled()) {
pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0, refreshInterval, TimeUnit.SECONDS);
}
}
private void refreshData() {
logger.trace("Job: refresh Data...");
for (Map.Entry<String, VitotronicThingHandler> entry : thingHandlerMap.entrySet()) {
String channelList = entry.getValue().getActiveChannelListAsString();
String thingId = entry.getValue().getThing().getUID().getId();
if (isConnect && (channelList.length() > 0)) {
logger.trace("Get Data for '{}'", thingId);
sendSocketData("get " + thingId + " " + channelList);
}
}
}
// Methods for ThingHandler
public void scanThings() {
logger.trace("Job: Discover Things...");
if (!isDiscover) {
sendSocketData("list");
isDiscover = true;
}
}
public ThingStatus getStatus() {
return getThing().getStatus();
}
public void updateChannel(String thingId, String channelId, String value) {
sendSocketData("set " + thingId + ":" + channelId + " " + value);
}
// internal Methods
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
// No channels - nothing to do
}
@Override
public void initialize() {
logger.debug("Initializing Vitotronic bridge handler {}", getThing().getUID());
updateStatus();
VitotronicBindingConfiguration configuration = getConfigAs(VitotronicBindingConfiguration.class);
ipAddress = configuration.ipAddress;
port = configuration.port;
refreshInterval = configuration.refreshInterval;
isDiscover = false;
startAutomaticRefresh();
}
@Override
public void dispose() {
logger.debug("Dispose Vitotronic bridge handler {}", getThing().getUID());
if (pollingJob != null && !pollingJob.isCancelled()) {
pollingJob.cancel(true);
pollingJob = null;
}
}
// Connection to adapter
private void openSocket() {
logger.trace("Try to open connection to Optolink Adapter {}:{}", ipAddress, port);
try {
socket = new Socket(ipAddress, port);
out = new PrintStream(socket.getOutputStream());
inStream = socket.getInputStream();
} catch (UnknownHostException e) {
logger.error("Can't find Host: {}:{}", ipAddress, port);
} catch (IOException e) {
logger.debug("Error in communication to Host: {}:{}", ipAddress, port);
logger.trace("Diagnostic: ", e);
}
}
Runnable socketReceiverRunnable = () -> {
logger.trace("Start Background Thread for recieving data from adapter");
try {
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
xmlReader.setContentHandler(new XmlHandler());
logger.trace("Start Parser for optolink adapter");
xmlReader.parse(new InputSource(inStream));
} catch (IOException e) {
logger.trace("Connection error from optolink adapter");
} catch (SAXException e) {
logger.trace("XML Parser Error");
}
updateStatus(ThingStatus.OFFLINE);
isConnect = false;
try {
if (!socket.isClosed()) {
socket.close();
}
} catch (Exception e) {
}
logger.trace("Connection to optolink adapter is died ... wait for restart");
};
private void startSocketReceiver() {
if (!isConnect) {
openSocket();
Thread thread = new Thread(socketReceiverRunnable);
thread.setName("VitotronicSocketThread");
thread.start();
}
}
private void sendSocketData(String message) {
try {
logger.trace("Send Message {}", message);
if (isConnect) {
if (message.matches("^set.*REFRESH$")) {
String[] msgParts = message.split(" ");
String[] thingChannel = msgParts[1].split(":");
message = "get " + thingChannel[0] + " " + thingChannel[1];
}
out.write((message + "\n").getBytes());
}
} catch (IOException e) {
logger.error("Error in sending data to optolink adapter");
logger.trace("Diagnostic: ", e);
}
}
// Handles all data what received from optolink adapter
public class XmlHandler implements ContentHandler {
boolean isData;
boolean isDefine;
boolean isThing;
boolean isChannel;
boolean isDescription;
String thingID;
String thingType;
String channelID;
String description;
VitotronicThingHandler thingHandler;
Set<String> channels = new HashSet<>();
@Override
public void startElement(String uri, String localName, String pName, Attributes attr) throws SAXException {
try {
switch (localName) {
case "optolink":
isConnect = true;
updateStatus(ThingStatus.ONLINE);
break;
case "data":
isDefine = false;
break;
case "define":
isDefine = true;
break;
case "description":
isDescription = true;
break;
case "thing":
isThing = true;
if (isDefine) {
thingType = attr.getValue("type");
}
thingID = attr.getValue("id");
channels.clear();
thingHandler = thingHandlerMap.get(thingID);
break;
case "channel":
isChannel = true;
channelID = attr.getValue("id");
if (isDefine) {
channels.add(channelID);
} else { // is data
if (thingHandler != null) {
logger.trace("Set Data for channel '{}' value '{}'", channelID, attr.getValue("value"));
thingHandler.setChannelValue(channelID, attr.getValue("value"));
}
}
break;
}
} catch (Exception e) {
logger.error("Error in parsing data");
logger.trace("Diagnostic: ", e);
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
if (isDescription) {
description = new String(ch, start, length);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
switch (localName) {
case "description":
isDescription = false;
break;
case "thing":
if (isDefine) {
createThing(thingType, thingID);
}
isThing = false;
thingHandler = null;
break;
case "channel":
isChannel = false;
break;
}
}
// Unused function of xmlReader
@Override
public void endDocument() throws SAXException {
}
@Override
public void ignorableWhitespace(char[] arg0, int arg1, int arg2) throws SAXException {
}
@Override
public void processingInstruction(String arg0, String arg1) throws SAXException {
}
@Override
public void setDocumentLocator(Locator arg0) {
}
@Override
public void skippedEntity(String arg0) throws SAXException {
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void startPrefixMapping(String arg0, String arg1) throws SAXException {
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
}
}

View File

@@ -0,0 +1,153 @@
/**
* 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.vitotronic.internal.handler;
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.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link VitotronicHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Stefan Andres - Initial contribution
*/
public class VitotronicThingHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(VitotronicThingHandler.class);
private VitotronicBridgeHandler bridgeHandler;
public VitotronicThingHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
bridgeHandler = getBridgeHandler();
logger.debug("Thing Handler for {} started", getThing().getUID().getId());
registerVitotronicThingListener(bridgeHandler);
}
@Override
public void dispose() {
logger.debug("Thing Handler for {} stop", getThing().getUID().getId());
unregisterVitotronicThingListener(bridgeHandler);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Handle command for channel '{}' command '{}'", channelUID.getId(), command);
bridgeHandler.updateChannel(getThing().getUID().getId(), channelUID.getId(), command.toString());
}
@Override
public void updateStatus(ThingStatus status) {
super.updateStatus(status);
}
private synchronized VitotronicBridgeHandler getBridgeHandler() {
Bridge bridge = getBridge();
if (bridge == null) {
logger.debug("Required bridge not defined for device {}.", getThing().getUID());
return null;
} else {
return getBridgeHandler(bridge);
}
}
private synchronized VitotronicBridgeHandler getBridgeHandler(Bridge bridge) {
VitotronicBridgeHandler bridgeHandler = null;
ThingHandler handler = bridge.getHandler();
if (handler instanceof VitotronicBridgeHandler) {
bridgeHandler = (VitotronicBridgeHandler) handler;
} else {
logger.debug("No available bridge handler found yet. Bridge: {} .", bridge.getUID());
bridgeHandler = null;
}
return bridgeHandler;
}
private void registerVitotronicThingListener(VitotronicBridgeHandler bridgeHandler) {
if (bridgeHandler != null) {
bridgeHandler.registerVitotronicThingListener(this);
} else {
logger.debug("Can't register {} at bridge bridgeHandler is null.", this.getThing().getUID());
}
}
private void unregisterVitotronicThingListener(VitotronicBridgeHandler bridgeHandler) {
if (bridgeHandler != null) {
bridgeHandler.unregisterThingListener(this);
} else {
logger.debug("Can't unregister {} at bridge bridgeHandler is null.", this.getThing().getUID());
}
}
public void setChannelValue(String channelId, String value) {
Channel channel = getThing().getChannel(channelId);
if (channel == null) {
logger.trace("Cannel '{}:{}' not implemented", getThing().getUID().getId(), channelId);
return;
}
logger.trace("Set {}:{}:{} = {}", getThing().getUID().getId(), channelId, channel.getAcceptedItemType(), value);
switch (channel.getAcceptedItemType()) {
case "Number":
this.updateState(channelId, new DecimalType(value));
break;
case "String":
this.updateState(channelId, new StringType(value));
break;
case "Switch":
if (value.toUpperCase().contains("ON")) {
this.updateState(channelId, OnOffType.ON);
} else {
this.updateState(channelId, OnOffType.OFF);
}
break;
case "DateTime":
this.updateState(channelId, new DateTimeType(value));
break;
default:
logger.trace("Type '{}' for channel '{}' not implemented", channel.getAcceptedItemType(), channelId);
}
}
public String getActiveChannelListAsString() {
String channelList = "";
for (Channel channel : getThing().getChannels()) {
if (isLinked(channel.getUID().getId())) {
if (channelList.length() > 0) {
channelList = channelList + "," + channel.getUID().getId();
} else {
channelList = channel.getUID().getId();
}
}
}
return channelList;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="vitotronic" 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>Vitotronic Binding</name>
<description>This is the binding for Vitotronic.</description>
<author>Stefan Andres</author>
</binding:binding>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="vitotronic"
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="bridge">
<label>Bridge Vitotronic Optolink Adapter</label>
<description>This bridge represents the Vitotronic Optolink adapter
</description>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<context>network-address</context>
<label>IP Address</label>
<description>The IP address of the Optolink adapter</description>
</parameter>
<parameter name="port" type="integer" required="false" min="1024" max="49151">
<context>network-address</context>
<label>Adapter Port</label>
<description>Port of the LAN gateway</description>
<default>31113</default>
</parameter>
<parameter name="adapterID" type="text" required="true">
<label>Adapter ID</label>
<description>The ID of the adapter.</description>
</parameter>
<parameter name="refreshInterval" type="integer" required="false" min="60" max="600">
<context>refresh</context>
<label>Refresh Interval</label>
<description>Refreshtime in seconds.</description>
<default>300</default>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,652 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="vitotronic"
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 -->
<thing-type id="heating">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Vitotronic</label>
<description>Vitotronic core system</description>
<channels>
<channel id="systemtime" typeId="systemtime"/>
<channel id="outside_temp" typeId="outside_temp"/>
<channel id="boiler_temp" typeId="boiler_temp"/>
<channel id="malfunction" typeId="malfunction"/>
</channels>
</thing-type>
<thing-type id="gasburner">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Gas Burner</label>
<description>Condensing Technology Gas Burner</description>
<channels>
<channel id="powerlevel" typeId="powerlevel">
<description>Power of the gas burner</description>
</channel>
<channel id="exhaust_temp" typeId="exhaust_temp"/>
</channels>
</thing-type>
<thing-type id="pelletburner">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Pellet Burner</label>
<description>Pellet Fireplace, works for wood also </description>
<channels>
<channel id="flame_temp" typeId="flame_temp"/>
<channel id="airshutter_prim" typeId="airshutter_prim"/>
<channel id="airshutter_sec" typeId="airshutter_sec"/>
<channel id="lambdasensor" typeId="lambdasensor"/>
<channel id="fanspeed" typeId="fanspeed"/>
<channel id="fanspeed_target" typeId="fanspeed_target"/>
<channel id="error" typeId="error"/>
<channel id="starts" typeId="starts"/>
<channel id="ontime" typeId="ontime"/>
<channel id="consumedpellets" typeId="consumedpellets"/>
<channel id="powerlevel" typeId="powerlevel">
<description>Power of the pellet burner</description>
</channel>
<channel id="flowuprating" typeId="flowuprating"/>
</channels>
</thing-type>
<thing-type id="oilburner">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Oil Burner</label>
<description>Oil Fireplace</description>
<channels>
<channel id="powerlevel" typeId="powerlevel">
<description>Power of the oil burner</description>
</channel>
<channel id="actualpower" typeId="actualpower"/>
<channel id="error" typeId="error"/>
<channel id="starts" typeId="starts"/>
<channel id="ontimelevel1" typeId="ontimelevel1"/>
<channel id="ontimelevel2" typeId="ontimelevel2"/>
<channel id="consumedoil" typeId="consumedoil"/>
</channels>
</thing-type>
<thing-type id="storagetank">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Hotwater Storagetank</label>
<description>Storage Tank, stores heat in a water tank on 3 levels: bottom, middle, top=hot water</description>
<channels>
<channel id="hotwater_temp" typeId="hotwater_temp"/>
<channel id="hotwater_temp_setpoint" typeId="hotwater_temp_setpoint"/>
<channel id="middle_temp" typeId="middle_temp"/>
<channel id="bottom_temp" typeId="bottom_temp"/>
<channel id="circuitpump" typeId="circuitpump"/>
</channels>
</thing-type>
<thing-type id="circuit">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Circuit</label>
<description>Heating circuit controls the flow of heating water between the heating system and the radiators in the
rooms</description>
<channels>
<channel id="flowtemperature" typeId="flow_temp"/>
<channel id="pump" typeId="pump"/>
<channel id="operationmode" typeId="operationmode"/>
<channel id="savemode" typeId="savemode"/>
<channel id="partymode" typeId="partymode"/>
<channel id="party_temp_setpoint" typeId="party_temp_setpoint"/>
<channel id="room_temp" typeId="room_temp"/>
<channel id="room_temp_setpoint" typeId="room_temp_setpoint"/>
<channel id="save_temp_setpoint" typeId="save_temp_setpoint"/>
<channel id="gradient" typeId="gradient"/>
<channel id="niveau" typeId="niveau"/>
<channel id="timer_MO" typeId="timer">
<label>Monday Heating Timer</label>
<description>On/Off timer for Monday. Up to 4 timers can be set</description>
<properties>
<property name="address">3000</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_TU" typeId="timer">
<label>Tuesday Heating Timer</label>
<description>On/Off timer for Tuesday. Up to 4 timers can be set</description>
<properties>
<property name="address">3008</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_WE" typeId="timer">
<label>Wednesday Heating Timer</label>
<description>On/Off timer for Wednesday. Up to 4 timers can be set</description>
<properties>
<property name="address">3010</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_TH" typeId="timer">
<label>Thursday Heating Timer</label>
<description>On/Off timer for Thursday. Up to 4 timers can be set</description>
<properties>
<property name="address">3018</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_FR" typeId="timer">
<label>Friday Heating Timer</label>
<description>On/Off timer for Friday. Up to 4 timers can be set</description>
<properties>
<property name="address">3020</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_SA" typeId="timer">
<label>Saturday Heating Timer</label>
<description>On/Off timer for Saturday. Up to 4 timers can be set</description>
<properties>
<property name="address">3028</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_SU" typeId="timer">
<label>Sunday Heating Timer</label>
<description>On/Off timer for Sunday. Up to 4 timers can be set</description>
<properties>
<property name="address">3030</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_ww_MO" typeId="timer">
<label>Monday Hot Water Timer</label>
<description>On/Off timer for Monday. Up to 4 timers can be set</description>
<properties>
<property name="address">2100</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_ww_TU" typeId="timer">
<label>Tuesday Hot Water Timer</label>
<description>On/Off timer for Tuesday. Up to 4 timers can be set</description>
<properties>
<property name="address">2108</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_ww_WE" typeId="timer">
<label>Wednesday Hot Water Timer</label>
<description>On/Off timer for Wednesday. Up to 4 timers can be set</description>
<properties>
<property name="address">2110</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_ww_TH" typeId="timer">
<label>Thursday Hot Water Timer</label>
<description>On/Off timer for Thursday. Up to 4 timers can be set</description>
<properties>
<property name="address">2118</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_ww_FR" typeId="timer">
<label>Friday Hot Water Timer</label>
<description>On/Off timer for Friday. Up to 4 timers can be set</description>
<properties>
<property name="address">2120</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_ww_SA" typeId="timer">
<label>Saturday Hot Water Timer</label>
<description>On/Off timer for Saturday. Up to 4 timers can be set</description>
<properties>
<property name="address">2128</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
<channel id="timer_ww_SU" typeId="timer">
<label>Sunday Hot Water Timer</label>
<description>On/Off timer for Sunday. Up to 4 timers can be set</description>
<properties>
<property name="address">2130</property>
<property name="type">timer</property>
<property name="divider">1</property>
</properties>
</channel>
</channels>
</thing-type>
<thing-type id="solar">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Solar Heating</label>
<description>Solar water heating (SWH): Convert sunlight into energy for water heating</description>
<channels>
<channel id="collector_temp" typeId="collector_temp"/>
<channel id="storagetank_temp" typeId="storagetank_temp"/>
<channel id="bufferload" typeId="bufferload"/>
<channel id="loadsuppression" typeId="loadsuppression"/>
<channel id="ontime" typeId="ontime"/>
<channel id="producedheat" typeId="producedheat"/>
</channels>
</thing-type>
<!-- Things for single use -->
<thing-type id="temperaturesensor">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Temperature Sensor</label>
<description>Single temperature sensor</description>
<channels>
<channel id="value" typeId="temperature"/>
</channels>
</thing-type>
<thing-type id="pump">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Pump</label>
<description>Single pump</description>
<channels>
<channel id="value" typeId="pump"/>
</channels>
</thing-type>
<thing-type id="valve">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Valve</label>
<description>Single valve</description>
<channels>
<channel id="valve" typeId="valve"/>
</channels>
</thing-type>
<!-- Channel Type -->
<!-- Channel Types for heating -->
<channel-type id="systemtime">
<item-type>DateTime</item-type>
<label>DateTime of the Heating System</label>
<state readOnly="true"/>
</channel-type>
<channel-type id="outside_temp">
<item-type>Number</item-type>
<label>Outside Temperature</label>
<description>Outside temperature sensor</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="boiler_temp">
<item-type>Number</item-type>
<label>Boiler</label>
<description>Temperature sensor of boiler (fireplace)</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="malfunction">
<item-type>Switch</item-type>
<label>Heating Malfunction</label>
<description>General Malfunction state of the heating</description>
<state readOnly="true"/>
</channel-type>
<!-- Channel Types for Gas Burner -->
<channel-type id="powerlevel">
<item-type>Number</item-type>
<label>Power Level</label>
<state pattern="%d %%" readOnly="true" max="100" min="0"/>
</channel-type>
<channel-type id="exhaust_temp">
<item-type>Number</item-type>
<label>Exhaust Temperature</label>
<description>Actual temperature of the exhaust</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<!-- Channel Types for Pellet Burner -->
<channel-type id="flame_temp">
<item-type>Number</item-type>
<label>Flame</label>
<description>Temperature of flame</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="lambdasensor" advanced="true">
<item-type>Number</item-type>
<label>Lambdasensor (O2)</label>
<description>Oxygen content of the exhaust air</description>
<state pattern="%.2f %%" readOnly="true"/>
</channel-type>
<channel-type id="airshutter_prim" advanced="true">
<item-type>Number</item-type>
<label>Primary Airshutter</label>
<description>Position of the primary air shutter</description>
<state pattern="%d %%" readOnly="true"/>
</channel-type>
<channel-type id="airshutter_sec" advanced="true">
<item-type>Number</item-type>
<label>Secondary Airshutter</label>
<description>Position of the secondary air shutter</description>
<state pattern="%d %%" readOnly="true"/>
</channel-type>
<channel-type id="starts" advanced="true">
<item-type>Number</item-type>
<label>Starts</label>
<description>Count of starts</description>
<state pattern="%d" readOnly="true"/>
</channel-type>
<channel-type id="fanspeed">
<item-type>Number</item-type>
<label>Fan Speed</label>
<description>Fan Speed in rpm</description>
<state pattern="%d rpm" readOnly="true"/>
</channel-type>
<channel-type id="fanspeed_target" advanced="true">
<item-type>Number</item-type>
<label>Target Fan Speed</label>
<description>Fan Speed in rpm</description>
<state pattern="%d rpm" readOnly="true"/>
</channel-type>
<channel-type id="ontime" advanced="true">
<item-type>Number</item-type>
<label>On Time</label>
<description>Ontime in hours</description>
<state pattern="%.2f h" readOnly="true"/>
</channel-type>
<channel-type id="consumedpellets" advanced="true">
<item-type>Number</item-type>
<label>Consumed Pellets</label>
<description>Consumed Pellets since start of heating in tons</description>
<state pattern="%.2f t" readOnly="true"/>
</channel-type>
<channel-type id="flowuprating" advanced="true">
<item-type>Switch</item-type>
<label>Pump</label>
<description>Pump state</description>
<state readOnly="true"/>
</channel-type>
<!-- Channel Types for Oil Burner -->
<channel-type id="actualpower">
<item-type>Number</item-type>
<label>Actual Power Level</label>
<description>Actual power of the burner</description>
<state pattern="%d %%" readOnly="true" max="100" min="0"/>
</channel-type>
<channel-type id="error" advanced="true">
<item-type>Switch</item-type>
<label>Errors</label>
<description>True, if errors for the burner exists</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="ontimelevel1" advanced="true">
<item-type>Number</item-type>
<label>On Time Level 1</label>
<description>Ontime in hours</description>
<state pattern="%.2f h" readOnly="true"/>
</channel-type>
<channel-type id="ontimelevel2" advanced="true">
<item-type>Number</item-type>
<label>On Time Level 2</label>
<description>Ontime in hours</description>
<state pattern="%.2f h" readOnly="true"/>
</channel-type>
<channel-type id="consumedoil" advanced="true">
<item-type>Number</item-type>
<label>Consumed Oil</label>
<description>Consumed Oil since start of heating in Liter</description>
<state pattern="%f l" readOnly="true"/>
</channel-type>
<!-- Channel Types for storagetank -->
<channel-type id="circuitpump">
<item-type>Switch</item-type>
<label>Circuit Pump</label>
<description>Circuit pump state</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="middle_temp">
<item-type>Number</item-type>
<label>Middle</label>
<description>Temperature sensor in the middle of the storage tank</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="bottom_temp">
<item-type>Number</item-type>
<label>Bottom</label>
<description>Temperature sensor at the bottom of the storage tank</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="hotwater_temp">
<item-type>Number</item-type>
<label>Hot Water</label>
<description>Temperature sensor of the hot water</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="hotwater_temp_setpoint">
<item-type>Number</item-type>
<label>Hotwater TempSet</label>
<description>Set the hot water temperature</description>
<category>Temperature</category>
<state pattern="%d °C" readOnly="false" max="60" min="30" step="1"/>
</channel-type>
<!-- Channel Types for circuit -->
<channel-type id="flow_temp">
<item-type>Number</item-type>
<label>Flow</label>
<description>Temperature sensor of the circuit flow</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="pump">
<item-type>Switch</item-type>
<label>Pump</label>
<description>Pump state</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="operationmode">
<item-type>Number</item-type>
<label>Operationmode</label>
<description>Operationmode</description>
<state pattern="%d" readOnly="false" max="4" min="0" step="1"/>
</channel-type>
<channel-type id="partymode">
<item-type>Switch</item-type>
<label>Partymode</label>
<description>Partymode on/off</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="savemode">
<item-type>Switch</item-type>
<label>Savemode</label>
<description>Savemode on/off</description>
<state readOnly="false"/>
</channel-type>
<channel-type id="party_temp_setpoint" advanced="true">
<item-type>Number</item-type>
<label>Partymode TempSet</label>
<description>Target temperature of party mode</description>
<category>Temperature</category>
<state min="6" step="1" pattern="%.1f °C" readOnly="false"/>
</channel-type>
<channel-type id="save_temp_setpoint" advanced="true">
<item-type>Number</item-type>
<label>Savemode TempSet</label>
<description>Target temperature of save mode</description>
<category>Temperature</category>
<state min="6" step="1" pattern="%.1f °C" readOnly="false"/>
</channel-type>
<channel-type id="room_temp" advanced="true">
<item-type>Number</item-type>
<label>Room Temp</label>
<description>Temperature of rooms</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="room_temp_setpoint">
<item-type>Number</item-type>
<label>Room TempSet</label>
<description>Target temperature of rooms</description>
<category>Temperature</category>
<state min="6" step="1" pattern="%.1f °C" readOnly="false"/>
</channel-type>
<channel-type id="gradient" advanced="true">
<item-type>Number</item-type>
<label>Gradient</label>
<description>The gradient relative to outside temperature</description>
<state pattern="%.1f" readOnly="true"/>
</channel-type>
<channel-type id="niveau" advanced="true">
<item-type>Number</item-type>
<label>Niveau</label>
<description>The niveau relative to outside temperature</description>
<state pattern="%.1f" readOnly="true"/>
</channel-type>
<channel-type id="timer" advanced="true">
<item-type>String</item-type>
<label>Timer for Heating or Warm Water</label>
<state pattern="%s" readOnly="true"/>
</channel-type>
<!-- Channel Type for solar -->
<channel-type id="collector_temp">
<item-type>Number</item-type>
<label>Collector</label>
<description>Actual temperature of the collector</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="storagetank_temp">
<item-type>Number</item-type>
<label>Storagetank</label>
<description>Actual temperature of the storage tank (solar sensor)</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="bufferload">
<item-type>Switch</item-type>
<label>Buffer Load Pump</label>
<description>State of the pump (on/off)</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="loadsuppression" advanced="true">
<item-type>Switch</item-type>
<label>Load Suppression</label>
<description>State of the load suppression (on/off)</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="producedheat" advanced="true">
<item-type>Number</item-type>
<label>Produced Heat</label>
<description>Produced heat since starting solar system</description>
<state pattern="%.2f kWh" readOnly="true"/>
</channel-type>
<!-- single types -->
<channel-type id="temperature">
<item-type>Number</item-type>
<label>Temperature</label>
<description>Generic temperature sensor</description>
<category>Temperature</category>
<state pattern="%.1f °C" readOnly="true"/>
</channel-type>
<channel-type id="valve">
<item-type>Number</item-type>
<label>Valve</label>
<description>Value of a generic valve</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>