added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.keba/.classpath
Normal file
32
bundles/org.openhab.binding.keba/.classpath
Normal 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>
|
||||
23
bundles/org.openhab.binding.keba/.project
Normal file
23
bundles/org.openhab.binding.keba/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.keba</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>
|
||||
13
bundles/org.openhab.binding.keba/NOTICE
Normal file
13
bundles/org.openhab.binding.keba/NOTICE
Normal 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
|
||||
105
bundles/org.openhab.binding.keba/README.md
Normal file
105
bundles/org.openhab.binding.keba/README.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Keba Binding
|
||||
|
||||
This binding integrates the [Keba KeContact EV Charging Stations](https://www.keba.com).
|
||||
|
||||
## Supported Things
|
||||
|
||||
The Keba KeContact P20 and P30 stations are supported by this binding, the thing type id is `kecontact`.
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The Keba KeContact P20/30 requires the ip address as the configuration parameter `ipAddress`. Optionally, a refresh interval (in seconds) can be defined as parameter `refreshInterval` that defines the polling of values from the charging station.
|
||||
|
||||
|
||||
## Channels
|
||||
|
||||
All devices support the following channels:
|
||||
|
||||
| Channel ID | Item Type | Read-only | Description |
|
||||
|--------------------|-----------|-----------|------------------------------------------------------------------------|
|
||||
| state | Number | yes | current operational state of the wallbox |
|
||||
| enabled | Switch | no | activation state of the wallbox |
|
||||
| maxpresetcurrent | Number | no | maximum current the charging station should deliver to the EV |
|
||||
| power | Number | yes | active power delivered by the charging station |
|
||||
| wallbox | Switch | yes | plug state of wallbox |
|
||||
| vehicle | Switch | yes | plug state of vehicle |
|
||||
| locked | Switch | yes | lock state of plug at vehicle |
|
||||
| I1/2/3 | Number | yes | current for the given phase |
|
||||
| U1/2/3 | Number | yes | voltage for the given phase |
|
||||
| output | Switch | no | state of the X1 relais |
|
||||
| input | Switch | yes | state of the X2 contact |
|
||||
| display | String | yes | display text on wallbox |
|
||||
| error1 | String | yes | error code state 1, if in error (see the KeContact FAQ) |
|
||||
| error2 | String | yes | error code state 2, if in error (see the KeContact FAQ) |
|
||||
| maxsystemcurrent | Number | yes | maximum current the wallbox can deliver |
|
||||
| failsafecurrent | Number | yes | maximum current the wallbox can deliver, if network is lost |
|
||||
| uptime | DateTime | yes | system uptime since the last reset of the wallbox |
|
||||
| sessionconsumption | Number | yes | energy delivered in current session |
|
||||
| totalconsumption | Number | yes | total energy delivered since the last reset of the wallbox |
|
||||
| authreq | Switch | yes | authentication required |
|
||||
| authon | Switch | yes | authentication enabled |
|
||||
| sessionrfidtag | String | yes | RFID tag used for the last charging session |
|
||||
| sessionrfidclass | String | yes | RFID tag class used for the last charging session |
|
||||
| sessionid | Number | yes | session ID of the last charging session |
|
||||
| setenergylimit | Number | no | set an energy limit for an already running or the next charging session|
|
||||
| authenticate | String | no | authenticate and start a session using RFID tag+RFID class |
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
demo.Things:
|
||||
|
||||
```
|
||||
Thing keba:kecontact:1 [ipAddress="192.168.0.64", refreshInterval=30]
|
||||
```
|
||||
|
||||
demo.items:
|
||||
|
||||
```
|
||||
Dimmer KebaCurrentRange {channel="keba:kecontact:1:maxpresetcurrentrange"}
|
||||
Number KebaCurrent {channel="keba:kecontact:1:maxpresetcurrent"}
|
||||
Number KebaSystemCurrent {channel="keba:kecontact:1:maxsystemcurrent"}
|
||||
Number KebaFailSafeCurrent {channel="keba:kecontact:1:failsafecurrent"}
|
||||
String KebaState {channel="keba:kecontact:1:state"}
|
||||
Switch KebaSwitch {channel="keba:kecontact:1:enabled"}
|
||||
Switch KebaWallboxPlugged {channel="keba:kecontact:1:wallbox"}
|
||||
Switch KebaVehiclePlugged {channel="keba:kecontact:1:vehicle"}
|
||||
Switch KebaPlugLocked {channel="keba:kecontact:1:locked"}
|
||||
DateTime KebaUptime "Uptime [%1$tY Y, %1$tm M, %1$td D, %1$tT]" {channel="keba:kecontact:1:uptime"}
|
||||
Number KebaI1 {channel="keba:kecontact:1:I1"}
|
||||
Number KebaI2 {channel="keba:kecontact:1:I2"}
|
||||
Number KebaI3 {channel="keba:kecontact:1:I3"}
|
||||
Number KebaU1 {channel="keba:kecontact:1:U1"}
|
||||
Number KebaU2 {channel="keba:kecontact:1:U2"}
|
||||
Number KebaU3 {channel="keba:kecontact:1:U3"}
|
||||
Number KebaPower {channel="keba:kecontact:1:power"}
|
||||
Number KebaSessionEnergy {channel="keba:kecontact:1:sessionconsumption"}
|
||||
Number KebaTotalEnergy {channel="keba:kecontact:1:totalconsumption"}
|
||||
Switch KebaInputSwitch {channel="keba:kecontact:1:input"}
|
||||
Switch KebaOutputSwitch {channel="keba:kecontact:1:output"}
|
||||
Number KebaSetEnergyLimit {channel="keba:kecontact:1:setenergylimit"}
|
||||
```
|
||||
|
||||
demo.sitemap:
|
||||
|
||||
```
|
||||
sitemap demo label="Main Menu"
|
||||
{
|
||||
Text label="Charging Station" {
|
||||
Text item=KebaState label="Operating State [%s]"
|
||||
Text item=KebaUptime
|
||||
Switch item=KebaSwitch label="Enabled" mappings=[ON=ON, OFF=OFF ]
|
||||
Switch item=KebaWallboxPlugged label="Plugged into wallbox" mappings=[ON=ON, OFF=OFF ]
|
||||
Switch item=KebaVehiclePlugged label="Plugged into vehicle" mappings=[ON=ON, OFF=OFF ]
|
||||
Switch item=KebaPlugLocked label="Plug locked" mappings=[ON=ON, OFF=OFF ]
|
||||
Slider item=KebaCurrentRange switchSupport label="Maximum supply current [%.1f %%]"
|
||||
Text item=KebaCurrent label="Maximum supply current [%.0f mA]"
|
||||
Text item=KebaSystemCurrent label="Maximum system supply current [%.0f mA]"
|
||||
Text item=KebaFailSafeCurrent label="Failsafe supply current [%.0f mA]"
|
||||
Text item=KebaSessionEnergy label="Energy during current session [%.0f Wh]"
|
||||
Text item=KebaTotalEnergy label="Energy during all sessions [%.0f Wh]"
|
||||
Switch item=KebaSetEnergyLimit label="Set charge energy limit" mappings=[0="off", 20000="20kWh"]
|
||||
}
|
||||
}
|
||||
```
|
||||
17
bundles/org.openhab.binding.keba/pom.xml
Normal file
17
bundles/org.openhab.binding.keba/pom.xml
Normal 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.keba</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Keba Binding</name>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.keba-${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-keba" description="Keba Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.keba/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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.keba.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link KebaBinding} class defines common constants, which are used across
|
||||
* the whole binding.
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class KebaBindingConstants {
|
||||
|
||||
public static final String BINDING_ID = "keba";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_KECONTACTP20 = new ThingTypeUID(BINDING_ID, "kecontact");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_MODEL = "model";
|
||||
public static final String CHANNEL_FIRMWARE = "firmware";
|
||||
public static final String CHANNEL_STATE = "state";
|
||||
public static final String CHANNEL_ERROR_1 = "error1";
|
||||
public static final String CHANNEL_ERROR_2 = "error2";
|
||||
public static final String CHANNEL_WALLBOX = "wallbox";
|
||||
public static final String CHANNEL_VEHICLE = "vehicle";
|
||||
public static final String CHANNEL_PLUG_LOCKED = "locked";
|
||||
public static final String CHANNEL_ENABLED = "enabled";
|
||||
public static final String CHANNEL_PILOT_CURRENT = "maxpilotcurrent";
|
||||
public static final String CHANNEL_PILOT_PWM = "pwmpilotcurrent";
|
||||
public static final String CHANNEL_MAX_SYSTEM_CURRENT = "maxsystemcurrent";
|
||||
public static final String CHANNEL_MAX_PRESET_CURRENT_RANGE = "maxpresetcurrentrange";
|
||||
public static final String CHANNEL_MAX_PRESET_CURRENT = "maxpresetcurrent";
|
||||
public static final String CHANNEL_FAILSAFE_CURRENT = "failsafecurrent";
|
||||
public static final String CHANNEL_INPUT = "input";
|
||||
public static final String CHANNEL_OUTPUT = "output";
|
||||
public static final String CHANNEL_SERIAL = "serial";
|
||||
public static final String CHANNEL_UPTIME = "uptime";
|
||||
public static final String CHANNEL_I1 = "I1";
|
||||
public static final String CHANNEL_I2 = "I2";
|
||||
public static final String CHANNEL_I3 = "I3";
|
||||
public static final String CHANNEL_U1 = "U1";
|
||||
public static final String CHANNEL_U2 = "U2";
|
||||
public static final String CHANNEL_U3 = "U3";
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_POWER_FACTOR = "powerfactor";
|
||||
public static final String CHANNEL_SESSION_CONSUMPTION = "sessionconsumption";
|
||||
public static final String CHANNEL_TOTAL_CONSUMPTION = "totalconsumption";
|
||||
public static final String CHANNEL_DISPLAY = "display";
|
||||
public static final String CHANNEL_AUTHON = "authon";
|
||||
public static final String CHANNEL_AUTHREQ = "authreq";
|
||||
public static final String CHANNEL_SESSION_RFID_TAG = "sessionrfidtag";
|
||||
public static final String CHANNEL_SESSION_RFID_CLASS = "sessionrfidclass";
|
||||
public static final String CHANNEL_SESSION_SESSION_ID = "sessionid";
|
||||
public static final String CHANNEL_SETENERGY = "setenergylimit";
|
||||
public static final String CHANNEL_AUTHENTICATE = "authenticate";
|
||||
|
||||
public enum KebaType {
|
||||
P20,
|
||||
P30
|
||||
}
|
||||
|
||||
public enum KebaSeries {
|
||||
|
||||
E('0'),
|
||||
B('1'),
|
||||
C('2', '3'),
|
||||
X('A', 'B', 'C', 'D');
|
||||
|
||||
private final List<Character> things = new ArrayList<>();
|
||||
|
||||
KebaSeries(char... e) {
|
||||
Character[] cArray = ArrayUtils.toObject(e);
|
||||
for (char c : cArray) {
|
||||
things.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean matchesSeries(char c) {
|
||||
return things.contains(c);
|
||||
}
|
||||
|
||||
public static KebaSeries getSeries(char text) throws IllegalArgumentException {
|
||||
for (KebaSeries c : KebaSeries.values()) {
|
||||
if (c.matchesSeries(text)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Not a valid series");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.keba.internal;
|
||||
|
||||
import static org.openhab.binding.keba.internal.KebaBindingConstants.THING_TYPE_KECONTACTP20;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.openhab.binding.keba.internal.handler.KeContactHandler;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link KebaHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution
|
||||
*/
|
||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.keba")
|
||||
public class KebaHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_KECONTACTP20);
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (thingTypeUID.equals(THING_TYPE_KECONTACTP20)) {
|
||||
return new KeContactHandler(thing);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
/**
|
||||
* 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.keba.internal.handler;
|
||||
|
||||
import static org.openhab.binding.keba.internal.KebaBindingConstants.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.openhab.binding.keba.internal.KebaBindingConstants.KebaSeries;
|
||||
import org.openhab.binding.keba.internal.KebaBindingConstants.KebaType;
|
||||
import org.openhab.core.cache.ExpiringCacheMap;
|
||||
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.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
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.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* The {@link KeContactHandler} is responsible for handling commands, which
|
||||
* are sent to one of the channels.
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution
|
||||
*/
|
||||
public class KeContactHandler extends BaseThingHandler {
|
||||
|
||||
public static final String IP_ADDRESS = "ipAddress";
|
||||
public static final String POLLING_REFRESH_INTERVAL = "refreshInterval";
|
||||
public static final int REPORT_INTERVAL = 3000;
|
||||
public static final int PING_TIME_OUT = 3000;
|
||||
public static final int BUFFER_SIZE = 1024;
|
||||
public static final int REMOTE_PORT_NUMBER = 7090;
|
||||
private static final String CACHE_REPORT_1 = "REPORT_1";
|
||||
private static final String CACHE_REPORT_2 = "REPORT_2";
|
||||
private static final String CACHE_REPORT_3 = "REPORT_3";
|
||||
private static final String CACHE_REPORT_100 = "REPORT_100";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KeContactHandler.class);
|
||||
|
||||
protected JsonParser parser = new JsonParser();
|
||||
|
||||
private ScheduledFuture<?> pollingJob;
|
||||
private static KeContactTransceiver transceiver = new KeContactTransceiver();
|
||||
private ExpiringCacheMap<String, ByteBuffer> cache;
|
||||
|
||||
private int maxPresetCurrent = 0;
|
||||
private int maxSystemCurrent = 63000;
|
||||
private KebaType type;
|
||||
private KebaSeries series;
|
||||
private int lastState = -1; // trigger a report100 at startup
|
||||
private boolean isReport100needed = true;
|
||||
|
||||
public KeContactHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
if (getConfig().get(IP_ADDRESS) != null && !getConfig().get(IP_ADDRESS).equals("")) {
|
||||
transceiver.registerHandler(this);
|
||||
|
||||
cache = new ExpiringCacheMap<>(
|
||||
Math.max((((BigDecimal) getConfig().get(POLLING_REFRESH_INTERVAL)).intValue()) - 5, 0) * 1000);
|
||||
|
||||
cache.put(CACHE_REPORT_1, () -> transceiver.send("report 1", getHandler()));
|
||||
cache.put(CACHE_REPORT_2, () -> transceiver.send("report 2", getHandler()));
|
||||
cache.put(CACHE_REPORT_3, () -> transceiver.send("report 3", getHandler()));
|
||||
cache.put(CACHE_REPORT_100, () -> transceiver.send("report 100", getHandler()));
|
||||
|
||||
if (pollingJob == null || pollingJob.isCancelled()) {
|
||||
try {
|
||||
pollingJob = scheduler.scheduleWithFixedDelay(pollingRunnable, 0,
|
||||
((BigDecimal) getConfig().get(POLLING_REFRESH_INTERVAL)).intValue(), TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
|
||||
"An exception occurred while scheduling the polling job");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"IP address or port number not set");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (pollingJob != null && !pollingJob.isCancelled()) {
|
||||
pollingJob.cancel(true);
|
||||
pollingJob = null;
|
||||
}
|
||||
|
||||
transceiver.unRegisterHandler(this);
|
||||
}
|
||||
|
||||
public String getIPAddress() {
|
||||
return getConfig().get(IP_ADDRESS) != null ? (String) getConfig().get(IP_ADDRESS) : "";
|
||||
}
|
||||
|
||||
private KeContactHandler getHandler() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatus(ThingStatus status, ThingStatusDetail statusDetail, String description) {
|
||||
super.updateStatus(status, statusDetail, description);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Configuration getConfig() {
|
||||
return super.getConfig();
|
||||
}
|
||||
|
||||
private Runnable pollingRunnable = () -> {
|
||||
try {
|
||||
long stamp = System.currentTimeMillis();
|
||||
if (!InetAddress.getByName(((String) getConfig().get(IP_ADDRESS))).isReachable(PING_TIME_OUT)) {
|
||||
logger.debug("Ping timed out after '{}' milliseconds", System.currentTimeMillis() - stamp);
|
||||
transceiver.unRegisterHandler(getHandler());
|
||||
} else {
|
||||
if (getThing().getStatus() == ThingStatus.ONLINE) {
|
||||
ByteBuffer response = cache.get(CACHE_REPORT_1);
|
||||
if (response != null) {
|
||||
onData(response);
|
||||
}
|
||||
|
||||
Thread.sleep(REPORT_INTERVAL);
|
||||
|
||||
response = cache.get(CACHE_REPORT_2);
|
||||
if (response != null) {
|
||||
onData(response);
|
||||
}
|
||||
|
||||
Thread.sleep(REPORT_INTERVAL);
|
||||
|
||||
response = cache.get(CACHE_REPORT_3);
|
||||
if (response != null) {
|
||||
onData(response);
|
||||
}
|
||||
|
||||
if (isReport100needed) {
|
||||
Thread.sleep(REPORT_INTERVAL);
|
||||
|
||||
response = cache.get(CACHE_REPORT_100);
|
||||
if (response != null) {
|
||||
onData(response);
|
||||
}
|
||||
isReport100needed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException | IOException e) {
|
||||
logger.debug("An exception occurred while polling the KEBA KeContact '{}': {}", getThing().getUID(),
|
||||
e.getMessage(), e);
|
||||
Thread.currentThread().interrupt();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"An exception occurred while while polling the charging station");
|
||||
} catch (InterruptedException e) {
|
||||
logger.debug("Polling job has been interrupted for handler of thing '{}'.", getThing().getUID());
|
||||
}
|
||||
};
|
||||
|
||||
protected void onData(ByteBuffer byteBuffer) {
|
||||
String response = new String(byteBuffer.array(), 0, byteBuffer.limit());
|
||||
response = StringUtils.chomp(response);
|
||||
|
||||
if (response.contains("TCH-OK")) {
|
||||
// ignore confirmation messages which are not JSON
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
JsonObject readObject = parser.parse(response).getAsJsonObject();
|
||||
|
||||
for (Entry<String, JsonElement> entry : readObject.entrySet()) {
|
||||
switch (entry.getKey()) {
|
||||
case "Product": {
|
||||
Map<String, String> properties = editProperties();
|
||||
String product = entry.getValue().getAsString().trim();
|
||||
properties.put(CHANNEL_MODEL, product);
|
||||
updateProperties(properties);
|
||||
if (product.contains("P20")) {
|
||||
type = KebaType.P20;
|
||||
} else if (product.contains("P30")) {
|
||||
type = KebaType.P30;
|
||||
}
|
||||
series = KebaSeries.getSeries(product.substring(13, 14).charAt(0));
|
||||
break;
|
||||
}
|
||||
case "Serial": {
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(CHANNEL_SERIAL, entry.getValue().getAsString());
|
||||
updateProperties(properties);
|
||||
break;
|
||||
}
|
||||
case "Firmware": {
|
||||
Map<String, String> properties = editProperties();
|
||||
properties.put(CHANNEL_FIRMWARE, entry.getValue().getAsString());
|
||||
updateProperties(properties);
|
||||
break;
|
||||
}
|
||||
case "Plug": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
switch (state) {
|
||||
case 0: {
|
||||
updateState(CHANNEL_WALLBOX, OnOffType.OFF);
|
||||
updateState(CHANNEL_VEHICLE, OnOffType.OFF);
|
||||
updateState(CHANNEL_PLUG_LOCKED, OnOffType.OFF);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
updateState(CHANNEL_WALLBOX, OnOffType.ON);
|
||||
updateState(CHANNEL_VEHICLE, OnOffType.OFF);
|
||||
updateState(CHANNEL_PLUG_LOCKED, OnOffType.OFF);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
updateState(CHANNEL_WALLBOX, OnOffType.ON);
|
||||
updateState(CHANNEL_VEHICLE, OnOffType.OFF);
|
||||
updateState(CHANNEL_PLUG_LOCKED, OnOffType.ON);
|
||||
break;
|
||||
}
|
||||
case 5: {
|
||||
updateState(CHANNEL_WALLBOX, OnOffType.ON);
|
||||
updateState(CHANNEL_VEHICLE, OnOffType.ON);
|
||||
updateState(CHANNEL_PLUG_LOCKED, OnOffType.OFF);
|
||||
break;
|
||||
}
|
||||
case 7: {
|
||||
updateState(CHANNEL_WALLBOX, OnOffType.ON);
|
||||
updateState(CHANNEL_VEHICLE, OnOffType.ON);
|
||||
updateState(CHANNEL_PLUG_LOCKED, OnOffType.ON);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "State": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_STATE, newState);
|
||||
if (lastState != state) {
|
||||
// the state is different from the last one, so we will trigger a report100
|
||||
isReport100needed = true;
|
||||
lastState = state;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Enable sys": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
switch (state) {
|
||||
case 1: {
|
||||
updateState(CHANNEL_ENABLED, OnOffType.ON);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
updateState(CHANNEL_ENABLED, OnOffType.OFF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Curr HW": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
maxSystemCurrent = state;
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_MAX_SYSTEM_CURRENT, newState);
|
||||
if (maxSystemCurrent != 0) {
|
||||
if (maxSystemCurrent < maxPresetCurrent) {
|
||||
transceiver.send("curr " + String.valueOf(maxSystemCurrent), this);
|
||||
updateState(CHANNEL_MAX_PRESET_CURRENT, new DecimalType(maxSystemCurrent));
|
||||
updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE,
|
||||
new PercentType((maxSystemCurrent - 6000) * 100 / (maxSystemCurrent - 6000)));
|
||||
}
|
||||
} else {
|
||||
logger.debug("maxSystemCurrent is 0. Ignoring.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Curr user": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
maxPresetCurrent = state;
|
||||
updateState(CHANNEL_MAX_PRESET_CURRENT, new DecimalType(state));
|
||||
if (maxSystemCurrent != 0) {
|
||||
updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE,
|
||||
new PercentType(Math.min(100, (state - 6000) * 100 / (maxSystemCurrent - 6000))));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Curr FS": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_FAILSAFE_CURRENT, newState);
|
||||
break;
|
||||
}
|
||||
case "Max curr": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
maxPresetCurrent = state;
|
||||
updateState(CHANNEL_PILOT_CURRENT, new DecimalType(state));
|
||||
updateState(CHANNEL_PILOT_PWM, new DecimalType(state));
|
||||
break;
|
||||
}
|
||||
case "Output": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
switch (state) {
|
||||
case 1: {
|
||||
updateState(CHANNEL_OUTPUT, OnOffType.ON);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
updateState(CHANNEL_OUTPUT, OnOffType.OFF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Input": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
switch (state) {
|
||||
case 1: {
|
||||
updateState(CHANNEL_INPUT, OnOffType.ON);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
updateState(CHANNEL_INPUT, OnOffType.OFF);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "Sec": {
|
||||
long state = entry.getValue().getAsLong();
|
||||
|
||||
Calendar uptime = Calendar.getInstance();
|
||||
uptime.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
uptime.setTimeInMillis(state * 1000);
|
||||
SimpleDateFormat pFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
||||
pFormatter.setTimeZone(TimeZone.getTimeZone("GMT"));
|
||||
|
||||
updateState(CHANNEL_UPTIME, new DateTimeType(pFormatter.format(uptime.getTime())));
|
||||
break;
|
||||
}
|
||||
case "U1": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_U1, newState);
|
||||
break;
|
||||
}
|
||||
case "U2": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_U2, newState);
|
||||
break;
|
||||
}
|
||||
case "U3": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_U3, newState);
|
||||
break;
|
||||
}
|
||||
case "I1": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_I1, newState);
|
||||
break;
|
||||
}
|
||||
case "I2": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_I2, newState);
|
||||
break;
|
||||
}
|
||||
case "I3": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_I3, newState);
|
||||
break;
|
||||
}
|
||||
case "P": {
|
||||
long state = entry.getValue().getAsLong();
|
||||
State newState = new DecimalType(state / 1000);
|
||||
updateState(CHANNEL_POWER, newState);
|
||||
break;
|
||||
}
|
||||
case "PF": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new PercentType(state / 10);
|
||||
updateState(CHANNEL_POWER_FACTOR, newState);
|
||||
break;
|
||||
}
|
||||
case "E pres": {
|
||||
long state = entry.getValue().getAsLong();
|
||||
State newState = new DecimalType(state / 10);
|
||||
updateState(CHANNEL_SESSION_CONSUMPTION, newState);
|
||||
break;
|
||||
}
|
||||
case "E total": {
|
||||
long state = entry.getValue().getAsLong();
|
||||
State newState = new DecimalType(state / 10);
|
||||
updateState(CHANNEL_TOTAL_CONSUMPTION, newState);
|
||||
break;
|
||||
}
|
||||
case "AuthON": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_AUTHON, newState);
|
||||
break;
|
||||
}
|
||||
case "Authreq": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_AUTHREQ, newState);
|
||||
break;
|
||||
}
|
||||
case "RFID tag": {
|
||||
String state = entry.getValue().getAsString().trim();
|
||||
State newState = new StringType(state);
|
||||
updateState(CHANNEL_SESSION_RFID_TAG, newState);
|
||||
break;
|
||||
}
|
||||
case "RFID class": {
|
||||
String state = entry.getValue().getAsString().trim();
|
||||
State newState = new StringType(state);
|
||||
updateState(CHANNEL_SESSION_RFID_CLASS, newState);
|
||||
break;
|
||||
}
|
||||
case "Session ID": {
|
||||
int state = entry.getValue().getAsInt();
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_SESSION_SESSION_ID, newState);
|
||||
break;
|
||||
}
|
||||
case "Setenergy": {
|
||||
int state = entry.getValue().getAsInt() / 10;
|
||||
State newState = new DecimalType(state);
|
||||
updateState(CHANNEL_SETENERGY, newState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (JsonParseException e) {
|
||||
logger.debug("Invalid JSON data will be ignored: '{}'", response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if ((command instanceof RefreshType)) {
|
||||
// let's assume we do frequent enough polling and ignore the REFRESH request here
|
||||
// in order to prevent too many channel state updates
|
||||
} else {
|
||||
switch (channelUID.getId()) {
|
||||
case CHANNEL_MAX_PRESET_CURRENT: {
|
||||
if (command instanceof DecimalType) {
|
||||
transceiver.send(
|
||||
"curr " + String.valueOf(
|
||||
Math.min(Math.max(6000, ((DecimalType) command).intValue()), maxSystemCurrent)),
|
||||
this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHANNEL_MAX_PRESET_CURRENT_RANGE: {
|
||||
if (command instanceof OnOffType || command instanceof IncreaseDecreaseType
|
||||
|| command instanceof PercentType) {
|
||||
int newValue = 6000;
|
||||
if (command == IncreaseDecreaseType.INCREASE) {
|
||||
newValue = Math.min(Math.max(6000, maxPresetCurrent + 1), maxSystemCurrent);
|
||||
} else if (command == IncreaseDecreaseType.DECREASE) {
|
||||
newValue = Math.min(Math.max(6000, maxPresetCurrent - 1), maxSystemCurrent);
|
||||
} else if (command == OnOffType.ON) {
|
||||
newValue = maxSystemCurrent;
|
||||
} else if (command == OnOffType.OFF) {
|
||||
newValue = 6000;
|
||||
} else if (command instanceof PercentType) {
|
||||
newValue = 6000 + (maxSystemCurrent - 6000) * ((PercentType) command).intValue() / 100;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
transceiver.send("curr " + String.valueOf(newValue), this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHANNEL_ENABLED: {
|
||||
if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
transceiver.send("ena 1", this);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
transceiver.send("ena 0", this);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHANNEL_OUTPUT: {
|
||||
if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
transceiver.send("output 1", this);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
transceiver.send("output 0", this);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHANNEL_DISPLAY: {
|
||||
if (command instanceof StringType) {
|
||||
if (type == KebaType.P30 && (series == KebaSeries.C || series == KebaSeries.X)) {
|
||||
String cmd = command.toString();
|
||||
int maxLength = (cmd.length() < 23) ? cmd.length() : 23;
|
||||
transceiver.send("display 0 0 0 0 " + cmd.substring(0, maxLength), this);
|
||||
} else {
|
||||
logger.warn("'Display' is not supported on a KEBA KeContact {}:{}", type, series);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHANNEL_SETENERGY: {
|
||||
if (command instanceof DecimalType) {
|
||||
transceiver.send(
|
||||
"setenergy " + String.valueOf(
|
||||
Math.min(Math.max(0, ((DecimalType) command).intValue() * 10), 999999999)),
|
||||
this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CHANNEL_AUTHENTICATE: {
|
||||
if (command instanceof StringType) {
|
||||
String cmd = command.toString();
|
||||
// cmd must contain ID + CLASS (works only if the RFID TAG is in the whitelist of the Keba
|
||||
// station)
|
||||
transceiver.send("start " + cmd, this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
/**
|
||||
* 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.keba.internal.handler;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.PortUnreachableException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.CancelledKeyException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.channels.ClosedSelectorException;
|
||||
import java.nio.channels.DatagramChannel;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link KeContactTransceiver} is responsible for receiving UDP broadcast messages sent by the KEBA Charging
|
||||
* Stations. {@link KeContactHandler} willing to receive these messages have to register themselves with the
|
||||
* {@link KeContactTransceiver}
|
||||
*
|
||||
* @author Karel Goderis - Initial contribution
|
||||
*/
|
||||
|
||||
class KeContactTransceiver {
|
||||
|
||||
public static final int LISTENER_PORT_NUMBER = 7090;
|
||||
public static final int REMOTE_PORT_NUMBER = 7090;
|
||||
public static final int LISTENING_INTERVAL = 100;
|
||||
public static final int BUFFER_SIZE = 1024;
|
||||
public static final String IP_ADDRESS = "ipAddress";
|
||||
public static final String POLLING_REFRESH_INTERVAL = "refreshInterval";
|
||||
|
||||
private DatagramChannel broadcastChannel;
|
||||
private SelectionKey broadcastKey;
|
||||
private Selector selector;
|
||||
private Thread transceiverThread;
|
||||
private boolean isStarted = false;
|
||||
private Set<KeContactHandler> handlers = Collections.synchronizedSet(new HashSet<>());
|
||||
private Map<KeContactHandler, DatagramChannel> datagramChannels = Collections.synchronizedMap(new HashMap<>());
|
||||
private Map<KeContactHandler, ByteBuffer> buffers = Collections.synchronizedMap(new HashMap<>());
|
||||
private Map<KeContactHandler, ReentrantLock> locks = Collections.synchronizedMap(new HashMap<>());
|
||||
private Map<KeContactHandler, Boolean> flags = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(KeContactTransceiver.class);
|
||||
|
||||
public void start() {
|
||||
if (!isStarted) {
|
||||
logger.debug("Starting the the KEBA KeContact transceiver");
|
||||
try {
|
||||
selector = Selector.open();
|
||||
|
||||
if (transceiverThread == null) {
|
||||
transceiverThread = new Thread(transceiverRunnable, "openHAB-Keba-Transceiver");
|
||||
transceiverThread.start();
|
||||
}
|
||||
|
||||
broadcastChannel = DatagramChannel.open();
|
||||
broadcastChannel.socket().bind(new InetSocketAddress(LISTENER_PORT_NUMBER));
|
||||
broadcastChannel.configureBlocking(false);
|
||||
|
||||
logger.info("Listening for incoming data on {}", broadcastChannel.getLocalAddress());
|
||||
|
||||
synchronized (selector) {
|
||||
selector.wakeup();
|
||||
broadcastKey = broadcastChannel.register(selector, broadcastChannel.validOps());
|
||||
}
|
||||
|
||||
for (KeContactHandler listener : handlers) {
|
||||
establishConnection(listener);
|
||||
}
|
||||
|
||||
isStarted = true;
|
||||
} catch (ClosedSelectorException | CancelledKeyException | IOException e) {
|
||||
logger.error("An exception occurred while registering the selector: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (isStarted) {
|
||||
for (KeContactHandler listener : handlers) {
|
||||
this.removeConnection(listener);
|
||||
}
|
||||
|
||||
try {
|
||||
broadcastChannel.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("An exception occurred while closing the broadcast channel on port number {} : '{}'",
|
||||
LISTENER_PORT_NUMBER, e.getMessage(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
selector.close();
|
||||
} catch (IOException e) {
|
||||
logger.error("An exception occurred while closing the selector: '{}'", e.getMessage(), e);
|
||||
}
|
||||
|
||||
logger.debug("Stopping the the KEBA KeContact transceiver");
|
||||
if (transceiverThread != null) {
|
||||
transceiverThread.interrupt();
|
||||
try {
|
||||
transceiverThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
transceiverThread = null;
|
||||
}
|
||||
|
||||
locks.clear();
|
||||
flags.clear();
|
||||
|
||||
isStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
stop();
|
||||
isStarted = false;
|
||||
start();
|
||||
}
|
||||
|
||||
public void registerHandler(KeContactHandler handler) {
|
||||
if (handler != null) {
|
||||
handlers.add(handler);
|
||||
locks.put(handler, new ReentrantLock());
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("There are now {} KEBA KeContact handlers registered with the transceiver",
|
||||
handlers.size());
|
||||
}
|
||||
|
||||
if (handlers.size() == 1) {
|
||||
start();
|
||||
}
|
||||
|
||||
if (!isConnected(handler)) {
|
||||
establishConnection(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unRegisterHandler(KeContactHandler handler) {
|
||||
if (handler != null) {
|
||||
locks.remove(handler);
|
||||
handlers.remove(handler);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("There are now {} KEBA KeContact handlers registered with the transceiver",
|
||||
handlers.size());
|
||||
}
|
||||
|
||||
if (handlers.isEmpty()) {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected ByteBuffer send(String message, KeContactHandler handler) {
|
||||
ReentrantLock handlerLock = locks.get(handler);
|
||||
|
||||
if (handlerLock != null) {
|
||||
handlerLock.lock();
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(message.getBytes().length);
|
||||
buffer.put(message.getBytes("ASCII"));
|
||||
|
||||
flags.put(handler, Boolean.TRUE);
|
||||
buffers.put(handler, buffer);
|
||||
|
||||
synchronized (handlerLock) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("{} waiting on handerLock {}", Thread.currentThread().getName(),
|
||||
handlerLock.toString());
|
||||
}
|
||||
handlerLock.wait(KeContactHandler.REPORT_INTERVAL);
|
||||
}
|
||||
|
||||
return buffers.remove(handler);
|
||||
} catch (UnsupportedEncodingException | InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} finally {
|
||||
handlerLock.unlock();
|
||||
}
|
||||
} else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("The handler for '{}' is not yet registered with the KeContactTransceiver",
|
||||
handler.getThing().getUID());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Runnable transceiverRunnable = () -> {
|
||||
while (true) {
|
||||
try {
|
||||
synchronized (selector) {
|
||||
try {
|
||||
selector.selectNow();
|
||||
} catch (IOException e) {
|
||||
logger.error("An exception occurred while selecting: {}", e.getMessage());
|
||||
}
|
||||
|
||||
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
|
||||
while (it.hasNext()) {
|
||||
SelectionKey selKey = it.next();
|
||||
it.remove();
|
||||
|
||||
if (selKey.isValid() && selKey.isWritable()) {
|
||||
DatagramChannel theChannel = (DatagramChannel) selKey.channel();
|
||||
KeContactHandler theHandler = null;
|
||||
boolean error = false;
|
||||
|
||||
for (KeContactHandler handler : handlers) {
|
||||
if (theChannel.equals(datagramChannels.get(handler))) {
|
||||
theHandler = handler;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (theHandler != null) {
|
||||
ReentrantLock theLock = locks.get(theHandler);
|
||||
Boolean theFlag = flags.get(theHandler);
|
||||
if (theLock != null && theLock.isLocked() && theFlag != null
|
||||
&& theFlag.equals(Boolean.TRUE)) {
|
||||
ByteBuffer theBuffer = buffers.remove(theHandler);
|
||||
flags.put(theHandler, Boolean.FALSE);
|
||||
|
||||
if (theBuffer != null) {
|
||||
try {
|
||||
theBuffer.rewind();
|
||||
logger.debug("Sending '{}' on the channel '{}'->'{}'",
|
||||
new Object[] { new String(theBuffer.array()),
|
||||
theChannel.getLocalAddress(),
|
||||
theChannel.getRemoteAddress() });
|
||||
int byteswritten = theChannel.write(theBuffer);
|
||||
} catch (NotYetConnectedException e) {
|
||||
theHandler.updateStatus(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"The remote host is not yet connected");
|
||||
error = true;
|
||||
} catch (ClosedChannelException e) {
|
||||
theHandler.updateStatus(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"The connection to the remote host is closed");
|
||||
error = true;
|
||||
} catch (IOException e) {
|
||||
theHandler.updateStatus(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.COMMUNICATION_ERROR, "An IO exception occurred");
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
removeConnection(theHandler);
|
||||
establishConnection(theHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selKey.isValid() && selKey.isReadable()) {
|
||||
int numberBytesRead = 0;
|
||||
InetSocketAddress clientAddress = null;
|
||||
ByteBuffer readBuffer = null;
|
||||
boolean error = false;
|
||||
|
||||
if (selKey.equals(broadcastKey)) {
|
||||
try {
|
||||
readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
|
||||
clientAddress = (InetSocketAddress) broadcastChannel.receive(readBuffer);
|
||||
logger.debug("Received {} from {} on the transceiver listener port ",
|
||||
new String(readBuffer.array()), clientAddress);
|
||||
numberBytesRead = readBuffer.position();
|
||||
} catch (IOException e) {
|
||||
logger.error(
|
||||
"An exception occurred while receiving data on the transceiver listener port: '{}'",
|
||||
e.getMessage(), e);
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (numberBytesRead == -1) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
readBuffer.flip();
|
||||
if (readBuffer.remaining() > 0) {
|
||||
for (KeContactHandler handler : handlers) {
|
||||
if (clientAddress != null && handler.getIPAddress()
|
||||
.equals(clientAddress.getAddress().getHostAddress())) {
|
||||
ReentrantLock theLock = locks.get(handler);
|
||||
if (theLock != null && theLock.isLocked()) {
|
||||
buffers.put(handler, readBuffer);
|
||||
synchronized (theLock) {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("{} notifyall on handerLock {}",
|
||||
Thread.currentThread().getName(),
|
||||
theLock.toString());
|
||||
}
|
||||
theLock.notifyAll();
|
||||
}
|
||||
} else {
|
||||
handler.onData(readBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handlers.forEach(listener -> listener.updateStatus(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.COMMUNICATION_ERROR, "The transceiver is offline"));
|
||||
reset();
|
||||
}
|
||||
} else {
|
||||
DatagramChannel theChannel = (DatagramChannel) selKey.channel();
|
||||
KeContactHandler theHandler = null;
|
||||
|
||||
for (KeContactHandler handlers : handlers) {
|
||||
if (datagramChannels.get(handlers).equals(theChannel)) {
|
||||
theHandler = handlers;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (theHandler != null) {
|
||||
try {
|
||||
readBuffer = ByteBuffer.allocate(BUFFER_SIZE);
|
||||
numberBytesRead = theChannel.read(readBuffer);
|
||||
logger.debug("Received {} from {} on the transceiver listener port ",
|
||||
new String(readBuffer.array()), theChannel.getRemoteAddress());
|
||||
} catch (NotYetConnectedException e) {
|
||||
theHandler.updateStatus(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"The remote host is not yet connected");
|
||||
error = true;
|
||||
} catch (PortUnreachableException e) {
|
||||
theHandler.updateStatus(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"The remote host is probably not a KEBA KeContact");
|
||||
error = true;
|
||||
} catch (IOException e) {
|
||||
theHandler.updateStatus(ThingStatus.OFFLINE,
|
||||
ThingStatusDetail.COMMUNICATION_ERROR, "An IO exception occurred");
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (numberBytesRead == -1) {
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
readBuffer.flip();
|
||||
if (readBuffer.remaining() > 0) {
|
||||
ReentrantLock theLock = locks.get(theHandler);
|
||||
if (theLock != null && theLock.isLocked()) {
|
||||
buffers.put(theHandler, readBuffer);
|
||||
synchronized (theLock) {
|
||||
theLock.notifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
removeConnection(theHandler);
|
||||
establishConnection(theHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Thread.currentThread().isInterrupted()) {
|
||||
Thread.sleep(LISTENING_INTERVAL);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException | ClosedSelectorException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void establishConnection(KeContactHandler handler) {
|
||||
if (handler.getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR
|
||||
&& handler.getConfig().get(IP_ADDRESS) != null && !handler.getConfig().get(IP_ADDRESS).equals("")) {
|
||||
logger.debug("Establishing the connection to the KEBA KeContact '{}'", handler.getThing().getUID());
|
||||
|
||||
DatagramChannel datagramChannel = null;
|
||||
try {
|
||||
datagramChannel = DatagramChannel.open();
|
||||
} catch (Exception e2) {
|
||||
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"An exception occurred while opening a datagram channel");
|
||||
}
|
||||
|
||||
if (datagramChannel != null) {
|
||||
datagramChannels.put(handler, datagramChannel);
|
||||
|
||||
try {
|
||||
datagramChannel.configureBlocking(false);
|
||||
} catch (IOException e2) {
|
||||
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"An exception occurred while configuring a datagram channel");
|
||||
}
|
||||
|
||||
synchronized (selector) {
|
||||
selector.wakeup();
|
||||
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
|
||||
try {
|
||||
datagramChannel.register(selector, interestSet);
|
||||
} catch (ClosedChannelException e1) {
|
||||
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"An exception occurred while registering a selector");
|
||||
}
|
||||
|
||||
InetSocketAddress remoteAddress = new InetSocketAddress(
|
||||
(String) handler.getConfig().get(IP_ADDRESS), REMOTE_PORT_NUMBER);
|
||||
|
||||
try {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Connecting the channel for {} ", remoteAddress);
|
||||
}
|
||||
datagramChannel.connect(remoteAddress);
|
||||
|
||||
handler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "");
|
||||
} catch (Exception e) {
|
||||
logger.debug("An exception occurred while connecting connecting to '{}:{}' : {}", new Object[] {
|
||||
(String) handler.getConfig().get(IP_ADDRESS), REMOTE_PORT_NUMBER, e.getMessage() });
|
||||
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"An exception occurred while connecting");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
handler.getThing().getStatusInfo().getDescription());
|
||||
}
|
||||
}
|
||||
|
||||
private void removeConnection(KeContactHandler handler) {
|
||||
logger.debug("Tearing down the connection to the KEBA KeContact '{}'", handler.getThing().getUID());
|
||||
DatagramChannel datagramChannel = datagramChannels.remove(handler);
|
||||
|
||||
if (datagramChannel != null) {
|
||||
synchronized (selector) {
|
||||
try {
|
||||
datagramChannel.keyFor(selector).cancel();
|
||||
datagramChannel.close();
|
||||
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "");
|
||||
} catch (Exception e) {
|
||||
logger.debug("An exception occurred while closing the datagramchannel for '{}': {}",
|
||||
handler.getThing().getUID(), e.getMessage());
|
||||
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"An exception occurred while closing the datagramchannel");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isConnected(KeContactHandler handler) {
|
||||
return datagramChannels.get(handler) != null ? true : false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="keba" 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>Keba Binding</name>
|
||||
<description>This is the binding for Keba EV Charging Stations</description>
|
||||
<author>Karel Goderis</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,262 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="keba"
|
||||
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="kecontact">
|
||||
<label>KeContact EV Charging Station</label>
|
||||
<description>A KeContact EV Charging Station</description>
|
||||
|
||||
<channels>
|
||||
<channel id="enabled" typeId="enabled"/>
|
||||
<channel id="state" typeId="state"/>
|
||||
<channel id="maxpresetcurrent" typeId="current_settable"/>
|
||||
<channel id="maxpresetcurrentrange" typeId="range"/>
|
||||
<channel id="power" typeId="power"/>
|
||||
<channel id="powerfactor" typeId="powerfactor"/>
|
||||
<channel id="error1" typeId="error1"/>
|
||||
<channel id="error2" typeId="error2"/>
|
||||
<channel id="wallbox" typeId="plugwallbox"/>
|
||||
<channel id="vehicle" typeId="plugvehicle"/>
|
||||
<channel id="locked" typeId="locked"/>
|
||||
<channel id="maxpilotcurrent" typeId="pilotcurrent"/>
|
||||
<channel id="pwmpilotcurrent" typeId="pilotrange"/>
|
||||
<channel id="maxsystemcurrent" typeId="maxcurrent"/>
|
||||
<channel id="failsafecurrent" typeId="failsafecurrent"/>
|
||||
<channel id="output" typeId="x2"/>
|
||||
<channel id="input" typeId="x1"/>
|
||||
<channel id="uptime" typeId="uptime"/>
|
||||
<channel id="I1" typeId="current">
|
||||
<label>Current Phase 1</label>
|
||||
</channel>
|
||||
<channel id="I2" typeId="current">
|
||||
<label>Current Phase 2</label>
|
||||
</channel>
|
||||
<channel id="I3" typeId="current">
|
||||
<label>Current Phase 3</label>
|
||||
</channel>
|
||||
<channel id="U1" typeId="voltage">
|
||||
<label>Voltage Phase 1</label>
|
||||
</channel>
|
||||
<channel id="U2" typeId="voltage">
|
||||
<label>Voltage Phase 2</label>
|
||||
</channel>
|
||||
<channel id="U3" typeId="voltage">
|
||||
<label>Voltage Phase 3</label>
|
||||
</channel>
|
||||
<channel id="sessionconsumption" typeId="energy"/>
|
||||
<channel id="totalconsumption" typeId="totalenergy"/>
|
||||
<channel id="display" typeId="display"/>
|
||||
<channel id="authreq" typeId="authreq"/>
|
||||
<channel id="authon" typeId="authon"/>
|
||||
<channel id="sessionrfidtag" typeId="sessionrfidtag"/>
|
||||
<channel id="sessionrfidclass" typeId="sessionrfidclass"/>
|
||||
<channel id="sessionid" typeId="sessionid"/>
|
||||
<channel id="setenergylimit" typeId="setenergylimit"/>
|
||||
<channel id="authenticate" typeId="authenticate"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<label>Network Address</label>
|
||||
<description>Network address of the wallbox</description>
|
||||
</parameter>
|
||||
<parameter name="refreshInterval" type="integer" required="false">
|
||||
<label>Refresh Interval</label>
|
||||
<description>Specifies the refresh interval in seconds.</description>
|
||||
<default>15</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="state">
|
||||
<item-type>Number</item-type>
|
||||
<label>Operation State</label>
|
||||
<description>Current operational state of the wallbox</description>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="0">Starting</option>
|
||||
<option value="1">Not Ready</option>
|
||||
<option value="2">Ready</option>
|
||||
<option value="3">Charging</option>
|
||||
<option value="4">Error</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="error1" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Error Code 1</label>
|
||||
<description>Error code state, if in error. See the KeContact FAQ</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="error2" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Error Code 2</label>
|
||||
<description>Error code state, if in error. See the KeContact FAQ</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="plugwallbox" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Wallbox Plugged</label>
|
||||
<description>State of the wallbox plug, e.g. ON if plugged in, OFF if unplugged</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="plugvehicle">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Vehicle Plugged</label>
|
||||
<description>State of the vehicle plug, e.g. ON if plugged in, OFF if unplugged</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="locked" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Plug Lock</label>
|
||||
<description>Indicator if the plug is locked by the electrical vehicle</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="enabled">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Enabled</label>
|
||||
<description>Activation state of the wallbox</description>
|
||||
<state readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="x1" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>X1</label>
|
||||
<description>State of the X1 input</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="x2" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>X2</label>
|
||||
<description>State of the X2 output</description>
|
||||
<state readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="current_settable">
|
||||
<item-type>Number</item-type>
|
||||
<label>Preset Current</label>
|
||||
<description>Preset Current in mA</description>
|
||||
<state pattern="%d mA" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="current" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Current</label>
|
||||
<description>Current in mA</description>
|
||||
<state pattern="%d mA" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="maxcurrent" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Max. System Current</label>
|
||||
<description>Maximal System Current in mA</description>
|
||||
<state pattern="%d mA" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="failsafecurrent" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Failsafe Current</label>
|
||||
<description>Failsafe Current in mA (if network is lost)</description>
|
||||
<state pattern="%d mA" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="range" advanced="true">
|
||||
<item-type>Dimmer</item-type>
|
||||
<label>Rel. Current</label>
|
||||
<description>Current in % of the 6000-63000 mA range accepted by the wallbox</description>
|
||||
<state pattern="%d %%" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="pilotcurrent" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Pilot Current</label>
|
||||
<description>Current preset value via Control pilot in mA</description>
|
||||
<state pattern="%d mA" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="pilotrange" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Pilot Range</label>
|
||||
<description>Current preset value via Control pilot in 0,1% of the PWM value</description>
|
||||
<state pattern="%d" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="uptime" advanced="true">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>System Uptime</label>
|
||||
<description>System uptime since the last reset of the wallbox</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="voltage" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Voltage</label>
|
||||
<description>Voltage in V</description>
|
||||
<state pattern="%d V" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="power">
|
||||
<item-type>Number</item-type>
|
||||
<label>Power</label>
|
||||
<description>Active Power in W</description>
|
||||
<state pattern="%d W" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="powerfactor" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Power Factor</label>
|
||||
<description>Power factor (cosphi)</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="energy" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Energy</label>
|
||||
<description>Power consumption in Wh.</description>
|
||||
<state pattern="%d Wh" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="totalenergy" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Energy</label>
|
||||
<description>Total energy consumption is added up after each completed charging session</description>
|
||||
<state pattern="%d Wh" readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="display" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>Display</label>
|
||||
<description>Text to show on the P30 Series C or X display</description>
|
||||
<state readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="authreq" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Authentication Required</label>
|
||||
<description>Authentication required</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="authon" advanced="true">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Authentication Enabled</label>
|
||||
<description>Authentication enabled</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="sessionrfidtag" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>RFID Tag</label>
|
||||
<description>RFID Tag used for the last charging session</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="sessionrfidclass" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>RFID Tag Class</label>
|
||||
<description>RFID Tag class used for the last charging session</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="sessionid" advanced="true">
|
||||
<item-type>Number</item-type>
|
||||
<label>Session ID</label>
|
||||
<description>Session ID of the last charging session</description>
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
<channel-type id="setenergylimit">
|
||||
<item-type>Number</item-type>
|
||||
<label>Energy Limit</label>
|
||||
<description>An energy limit for an already running or the next charging session.</description>
|
||||
<state pattern="%d Wh" readOnly="false"></state>
|
||||
</channel-type>
|
||||
<channel-type id="authenticate">
|
||||
<item-type>String</item-type>
|
||||
<label>Authenticate</label>
|
||||
<description>Authenticate and start a charging session</description>
|
||||
<state readOnly="false"></state>
|
||||
</channel-type>
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user