[keba] Improve connection establishment and stability (#10179)

Signed-off-by: Michael Weger <weger.michael@gmx.net>

* - worked in review findings
- introduced QuantityTypes
- removed redundant pwmpilotcurrent (duplicate of maxpilotcurrent)
- added maxpilotcurrentdutycyle
This commit is contained in:
MikeTheTux 2021-02-25 16:29:40 +01:00 committed by GitHub
parent 6b624f0e1c
commit a9bddff5ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 267 additions and 192 deletions

View File

@ -4,46 +4,48 @@ This binding integrates the [Keba KeContact EV Charging Stations](https://www.ke
## Supported Things ## Supported Things
The Keba KeContact P20 and P30 stations are supported by this binding, the thing type id is `kecontact`. The Keba KeContact P20 and P30 stations which are providing the UDP interface (P20 LSA+ socket, P30 c-series and x-series) are supported by this binding, the thing type id is `kecontact`.
## Thing Configuration ## 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. 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 ## Channels
All devices support the following channels: All devices support the following channels:
| Channel ID | Item Type | Read-only | Description | | Channel ID | Item Type | Read-only | Description |
|--------------------|-----------|-----------|------------------------------------------------------------------------| |---------------------------|---------------------------|-----------|---------------------------------------------------------------------------|
| state | Number | yes | current operational state of the wallbox | | state | Number | yes | current operational state of the wallbox |
| enabled | Switch | no | activation 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 | | maxpresetcurrent | Number:ElectricCurrent | no | maximum current the charging station should deliver to the EV in A |
| power | Number | yes | active power delivered by the charging station | | maxpresetcurrentrange | Number:Dimensionless | no | maximum current the charging station should deliver to the EV in % |
| wallbox | Switch | yes | plug state of wallbox | | power | Number:Power | yes | active power delivered by the charging station |
| vehicle | Switch | yes | plug state of vehicle | | wallbox | Switch | yes | plug state of wallbox |
| locked | Switch | yes | lock state of plug at vehicle | | vehicle | Switch | yes | plug state of vehicle |
| I1/2/3 | Number | yes | current for the given phase | | locked | Switch | yes | lock state of plug at vehicle |
| U1/2/3 | Number | yes | voltage for the given phase | | I1/2/3 | Number:ElectricCurrent | yes | current for the given phase |
| output | Switch | no | state of the X1 relais | | U1/2/3 | Number:ElectricPotential | yes | voltage for the given phase |
| input | Switch | yes | state of the X2 contact | | output | Switch | no | state of the X1 relais |
| display | String | yes | display text on wallbox | | input | Switch | yes | state of the X2 contact |
| error1 | String | yes | error code state 1, if in error (see the KeContact FAQ) | | display | String | no | display text on wallbox |
| error2 | String | yes | error code state 2, if in error (see the KeContact FAQ) | | error1 | String | yes | error code state 1, if in error (see the KeContact FAQ) |
| maxsystemcurrent | Number | yes | maximum current the wallbox can deliver | | error2 | String | yes | error code state 2, if in error (see the KeContact FAQ) |
| failsafecurrent | Number | yes | maximum current the wallbox can deliver, if network is lost | | maxsystemcurrent | Number:ElectricCurrent | yes | maximum current the wallbox can deliver |
| uptime | DateTime | yes | system uptime since the last reset of the wallbox | | failsafecurrent | Number:ElectricCurrent | yes | maximum current the wallbox can deliver, if network is lost |
| sessionconsumption | Number | yes | energy delivered in current session | | uptime | Number:Time | yes | system uptime since the last reset of the wallbox |
| totalconsumption | Number | yes | total energy delivered since the last reset of the wallbox | | sessionconsumption | Number:Energy | yes | energy delivered in current session |
| authreq | Switch | yes | authentication required | | totalconsumption | Number:Energy | yes | total energy delivered since the last reset of the wallbox |
| authon | Switch | yes | authentication enabled | | authreq | Switch | yes | authentication required |
| sessionrfidtag | String | yes | RFID tag used for the last charging session | | authon | Switch | yes | authentication enabled |
| sessionrfidclass | String | yes | RFID tag class used for the last charging session | | sessionrfidtag | String | yes | RFID tag used for the last charging session |
| sessionid | Number | yes | session ID of the last charging session | | sessionrfidclass | String | yes | RFID tag class used for the last charging session |
| setenergylimit | Number | no | set an energy limit for an already running or the next charging session| | sessionid | Number | yes | session ID of the last charging session |
| authenticate | String | no | authenticate and start a session using RFID tag+RFID class | | setenergylimit | Number:Energy | 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 |
| maxpilotcurrent | Number:ElectricCurrent | yes | current offered to the vehicle via control pilot signalization |
| maxpilotcurrentdutycyle | Number:Dimensionless | yes | duty cycle of the control pilot signal |
## Example ## Example
@ -57,28 +59,28 @@ Thing keba:kecontact:1 [ipAddress="192.168.0.64", refreshInterval=30]
demo.items: demo.items:
``` ```
Dimmer KebaCurrentRange {channel="keba:kecontact:1:maxpresetcurrentrange"} Number:Dimensionless KebaCurrentRange "Maximum supply current [%.1f %%]" {channel="keba:kecontact:1:maxpresetcurrentrange"}
Number KebaCurrent {channel="keba:kecontact:1:maxpresetcurrent"} Number:ElectricCurrent KebaCurrent "Maximum supply current [%.3f A]" {channel="keba:kecontact:1:maxpresetcurrent"}
Number KebaSystemCurrent {channel="keba:kecontact:1:maxsystemcurrent"} Number:ElectricCurrent KebaSystemCurrent "Maximum system supply current [%.3f A]" {channel="keba:kecontact:1:maxsystemcurrent"}
Number KebaFailSafeCurrent {channel="keba:kecontact:1:failsafecurrent"} Number:ElectricCurrent KebaFailSafeCurrent "Failsafe supply current [%.3f A]" {channel="keba:kecontact:1:failsafecurrent"}
String KebaState {channel="keba:kecontact:1:state"} String KebaState "Operating State [%s]" {channel="keba:kecontact:1:state"}
Switch KebaSwitch {channel="keba:kecontact:1:enabled"} Switch KebaSwitch "Enabled" {channel="keba:kecontact:1:enabled"}
Switch KebaWallboxPlugged {channel="keba:kecontact:1:wallbox"} Switch KebaWallboxPlugged "Plugged into wallbox" {channel="keba:kecontact:1:wallbox"}
Switch KebaVehiclePlugged {channel="keba:kecontact:1:vehicle"} Switch KebaVehiclePlugged "Plugged into vehicle" {channel="keba:kecontact:1:vehicle"}
Switch KebaPlugLocked {channel="keba:kecontact:1:locked"} Switch KebaPlugLocked "Plug locked" {channel="keba:kecontact:1:locked"}
DateTime KebaUptime "Uptime [%1$tY Y, %1$tm M, %1$td D, %1$tT]" {channel="keba:kecontact:1:uptime"} DateTime KebaUptime "Uptime [%s s]" {channel="keba:kecontact:1:uptime"}
Number KebaI1 {channel="keba:kecontact:1:I1"} Number:ElectricCurrent KebaI1 {channel="keba:kecontact:1:I1"}
Number KebaI2 {channel="keba:kecontact:1:I2"} Number:ElectricCurrent KebaI2 {channel="keba:kecontact:1:I2"}
Number KebaI3 {channel="keba:kecontact:1:I3"} Number:ElectricCurrent KebaI3 {channel="keba:kecontact:1:I3"}
Number KebaU1 {channel="keba:kecontact:1:U1"} Number:ElectricPotential KebaU1 {channel="keba:kecontact:1:U1"}
Number KebaU2 {channel="keba:kecontact:1:U2"} Number:ElectricPotential KebaU2 {channel="keba:kecontact:1:U2"}
Number KebaU3 {channel="keba:kecontact:1:U3"} Number:ElectricPotential KebaU3 {channel="keba:kecontact:1:U3"}
Number KebaPower {channel="keba:kecontact:1:power"} Number:Power KebaPower "Energy during current session [%.1f Wh]" {channel="keba:kecontact:1:power"}
Number KebaSessionEnergy {channel="keba:kecontact:1:sessionconsumption"} Number:Energy KebaSessionEnergy {channel="keba:kecontact:1:sessionconsumption"}
Number KebaTotalEnergy {channel="keba:kecontact:1:totalconsumption"} Number:Energy KebaTotalEnergy "Energy during all sessions [%.1f Wh]" {channel="keba:kecontact:1:totalconsumption"}
Switch KebaInputSwitch {channel="keba:kecontact:1:input"} Switch KebaInputSwitch {channel="keba:kecontact:1:input"}
Switch KebaOutputSwitch {channel="keba:kecontact:1:output"} Switch KebaOutputSwitch {channel="keba:kecontact:1:output"}
Number KebaSetEnergyLimit {channel="keba:kecontact:1:setenergylimit"} Number:Energy KebaSetEnergyLimit "Set charge energy limit [%.1f Wh]" {channel="keba:kecontact:1:setenergylimit"}
``` ```
demo.sitemap: demo.sitemap:
@ -86,20 +88,63 @@ demo.sitemap:
``` ```
sitemap demo label="Main Menu" sitemap demo label="Main Menu"
{ {
Text label="Charging Station" { Text label="Charging Station" {
Text item=KebaState label="Operating State [%s]" Text item=KebaState
Text item=KebaUptime Text item=KebaUptime
Switch item=KebaSwitch label="Enabled" mappings=[ON=ON, OFF=OFF ] Switch item=KebaSwitch
Switch item=KebaWallboxPlugged label="Plugged into wallbox" mappings=[ON=ON, OFF=OFF ] Switch item=KebaWallboxPlugged
Switch item=KebaVehiclePlugged label="Plugged into vehicle" mappings=[ON=ON, OFF=OFF ] Switch item=KebaVehiclePlugged
Switch item=KebaPlugLocked label="Plug locked" mappings=[ON=ON, OFF=OFF ] Switch item=KebaPlugLocked
Slider item=KebaCurrentRange switchSupport label="Maximum supply current [%.1f %%]" Slider item=KebaCurrentRange
Text item=KebaCurrent label="Maximum supply current [%.0f mA]" Text item=KebaCurrent
Text item=KebaSystemCurrent label="Maximum system supply current [%.0f mA]" Text item=KebaSystemCurrent
Text item=KebaFailSafeCurrent label="Failsafe supply current [%.0f mA]" Text item=KebaFailSafeCurrent
Text item=KebaSessionEnergy label="Energy during current session [%.0f Wh]" Text item=KebaSessionEnergy
Text item=KebaTotalEnergy label="Energy during all sessions [%.0f Wh]" Text item=KebaTotalEnergy
Switch item=KebaSetEnergyLimit label="Set charge energy limit" mappings=[0="off", 20000="20kWh"] Switch item=KebaSetEnergyLimit
} }
} }
``` ```
## Troubleshooting
### Enable Verbose Logging
Enable `DEBUG` or `TRACE` (even more verbose) logging for the logger named:
org.openhab.binding.keba
If everything is working fine, you see the cyclic reception of `report 1`, `2` & `3` from the station. The frequency is according to the `refreshInterval` configuration.
### UDP Ports used
Send port = UDP 7090
The Keba station is the server
Receive port = UDP 7090
This binding is providing the server
UDP port 7090 needs to be available/free on the openHAB server.
In order to enable the UDP port 7090 on the Keba station with full functionality, `DIP switch 1.3` must be `ON`.
With `DIP switch 1.3 OFF` only ident-data can be read (`i` and `report 1`) but not the other reports as well as the commands needed for the write access.
After setting the DIP switch, you need to `power OFF` and `ON` the station. SW-reset via WebGUI seems not to be sufficient in order to apply the new configuration.
The right configuration can be validated as follows:
- WebGUI DSW Settings:
- `DIP 1.3 | ON | UDP interface (SmartHome)`
- UDP response of `report 1`:
- `DIP-Sw1` `0x20` Bit is set (enable at least `DEBUG` log-level for the binding)
### Supported stations
- KeContact P20 charging station with network connection (LSA+ socket)
- Product code: `KC-P20-xxxxxx2x-xxx` or `KC-P20-xxxxxx3x-xxx`
- Firmware version: 2.5 or higher
- KeContact P30 charging station (c- or x-series) or BMW wallbox
- Firmware version 3.05 or higher

View File

@ -44,7 +44,7 @@ public class KebaBindingConstants {
public static final String CHANNEL_PLUG_LOCKED = "locked"; public static final String CHANNEL_PLUG_LOCKED = "locked";
public static final String CHANNEL_ENABLED = "enabled"; public static final String CHANNEL_ENABLED = "enabled";
public static final String CHANNEL_PILOT_CURRENT = "maxpilotcurrent"; public static final String CHANNEL_PILOT_CURRENT = "maxpilotcurrent";
public static final String CHANNEL_PILOT_PWM = "pwmpilotcurrent"; public static final String CHANNEL_PILOT_PWM = "maxpilotcurrentdutycyle";
public static final String CHANNEL_MAX_SYSTEM_CURRENT = "maxsystemcurrent"; 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_RANGE = "maxpresetcurrentrange";
public static final String CHANNEL_MAX_PRESET_CURRENT = "maxpresetcurrent"; public static final String CHANNEL_MAX_PRESET_CURRENT = "maxpresetcurrent";
@ -82,7 +82,7 @@ public class KebaBindingConstants {
E('0'), E('0'),
B('1'), B('1'),
C('2', '3'), C('2', '3'),
X('A', 'B', 'C', 'D'); X('A', 'B', 'C', 'D', 'E', 'G', 'H');
private final List<Character> things = new ArrayList<>(); private final List<Character> things = new ArrayList<>();
@ -104,7 +104,7 @@ public class KebaBindingConstants {
} }
} }
throw new IllegalArgumentException("Not a valid series"); throw new IllegalArgumentException("Not a valid series: '" + text + "'");
} }
} }
} }

View File

@ -16,27 +16,33 @@ import static org.openhab.binding.keba.internal.KebaBindingConstants.*;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.net.InetAddress; import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.TimeZone;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.ElectricCurrent;
import javax.measure.quantity.ElectricPotential;
import javax.measure.quantity.Energy;
import javax.measure.quantity.Power;
import javax.measure.quantity.Time;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.openhab.binding.keba.internal.KebaBindingConstants.KebaSeries; import org.openhab.binding.keba.internal.KebaBindingConstants.KebaSeries;
import org.openhab.binding.keba.internal.KebaBindingConstants.KebaType; import org.openhab.binding.keba.internal.KebaBindingConstants.KebaType;
import org.openhab.core.cache.ExpiringCacheMap; import org.openhab.core.cache.ExpiringCacheMap;
import org.openhab.core.config.core.Configuration; 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.DecimalType;
import org.openhab.core.library.types.IncreaseDecreaseType; import org.openhab.core.library.types.IncreaseDecreaseType;
import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType; import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.Units;
import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
@ -63,14 +69,16 @@ public class KeContactHandler extends BaseThingHandler {
public static final String IP_ADDRESS = "ipAddress"; public static final String IP_ADDRESS = "ipAddress";
public static final String POLLING_REFRESH_INTERVAL = "refreshInterval"; public static final String POLLING_REFRESH_INTERVAL = "refreshInterval";
public static final int POLLING_REFRESH_INTERVAL_DEFAULT = 15;
public static final int REPORT_INTERVAL = 3000; 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 BUFFER_SIZE = 1024;
public static final int REMOTE_PORT_NUMBER = 7090; public static final int REMOTE_PORT_NUMBER = 7090;
private static final String CACHE_REPORT_1 = "REPORT_1"; private static final String CACHE_REPORT_1 = "REPORT_1";
private static final String CACHE_REPORT_2 = "REPORT_2"; private static final String CACHE_REPORT_2 = "REPORT_2";
private static final String CACHE_REPORT_3 = "REPORT_3"; private static final String CACHE_REPORT_3 = "REPORT_3";
private static final String CACHE_REPORT_100 = "REPORT_100"; private static final String CACHE_REPORT_100 = "REPORT_100";
public static final int SOCKET_TIME_OUT_MS = 3000;
public static final int SOCKET_CHECK_PORT_NUMBER = 80;
private final Logger logger = LoggerFactory.getLogger(KeContactHandler.class); private final Logger logger = LoggerFactory.getLogger(KeContactHandler.class);
@ -94,32 +102,46 @@ public class KeContactHandler extends BaseThingHandler {
@Override @Override
public void initialize() { public void initialize() {
if (getConfig().get(IP_ADDRESS) != null && !getConfig().get(IP_ADDRESS).equals("")) { try {
transceiver.registerHandler(this); if (isKebaReachable()) {
transceiver.registerHandler(this);
cache = new ExpiringCacheMap<>( int refreshInterval = getRefreshInterval();
Math.max((((BigDecimal) getConfig().get(POLLING_REFRESH_INTERVAL)).intValue()) - 5, 0) * 1000); cache = new ExpiringCacheMap<>(Math.max(refreshInterval - 5, 0) * 1000);
cache.put(CACHE_REPORT_1, () -> transceiver.send("report 1", getHandler())); cache.put(CACHE_REPORT_1, () -> transceiver.send("report 1", getHandler()));
cache.put(CACHE_REPORT_2, () -> transceiver.send("report 2", 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_3, () -> transceiver.send("report 3", getHandler()));
cache.put(CACHE_REPORT_100, () -> transceiver.send("report 100", getHandler())); cache.put(CACHE_REPORT_100, () -> transceiver.send("report 100", getHandler()));
if (pollingJob == null || pollingJob.isCancelled()) { if (pollingJob == null || pollingJob.isCancelled()) {
try { pollingJob = scheduler.scheduleWithFixedDelay(this::pollingRunnable, 0, refreshInterval,
pollingJob = scheduler.scheduleWithFixedDelay(this::pollingRunnable, 0, TimeUnit.SECONDS);
((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");
} }
} else { } catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"IP address or port number not set"); "Exception during initialization of binding: " + e.toString());
} }
} }
private boolean isKebaReachable() throws IOException {
boolean isReachable = false;
SocketAddress sockAddr = new InetSocketAddress(getIPAddress(), SOCKET_CHECK_PORT_NUMBER);
Socket socket = new Socket();
try {
socket.connect(sockAddr, SOCKET_TIME_OUT_MS);
isReachable = true;
} finally {
socket.close();
}
logger.debug("isKebaReachable() returns {}", isReachable);
return isReachable;
}
@Override @Override
public void dispose() { public void dispose() {
if (pollingJob != null && !pollingJob.isCancelled()) { if (pollingJob != null && !pollingJob.isCancelled()) {
@ -134,6 +156,12 @@ public class KeContactHandler extends BaseThingHandler {
return getConfig().get(IP_ADDRESS) != null ? (String) getConfig().get(IP_ADDRESS) : ""; return getConfig().get(IP_ADDRESS) != null ? (String) getConfig().get(IP_ADDRESS) : "";
} }
public int getRefreshInterval() {
return getConfig().get(POLLING_REFRESH_INTERVAL) != null
? ((BigDecimal) getConfig().get(POLLING_REFRESH_INTERVAL)).intValue()
: POLLING_REFRESH_INTERVAL_DEFAULT;
}
private KeContactHandler getHandler() { private KeContactHandler getHandler() {
return this; return this;
} }
@ -150,9 +178,10 @@ public class KeContactHandler extends BaseThingHandler {
private void pollingRunnable() { private void pollingRunnable() {
try { try {
logger.debug("Running pollingRunnable to connect Keba wallbox");
long stamp = System.currentTimeMillis(); long stamp = System.currentTimeMillis();
if (!InetAddress.getByName(((String) getConfig().get(IP_ADDRESS))).isReachable(PING_TIME_OUT)) { if (!isKebaReachable()) {
logger.debug("Ping timed out after '{}' milliseconds", System.currentTimeMillis() - stamp); logger.debug("isKebaReachable() timed out after '{}' milliseconds", System.currentTimeMillis() - stamp);
transceiver.unRegisterHandler(getHandler()); transceiver.unRegisterHandler(getHandler());
} else { } else {
if (getThing().getStatus() == ThingStatus.ONLINE) { if (getThing().getStatus() == ThingStatus.ONLINE) {
@ -300,14 +329,15 @@ public class KeContactHandler extends BaseThingHandler {
case "Curr HW": { case "Curr HW": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
maxSystemCurrent = state; maxSystemCurrent = state;
State newState = new DecimalType(state); State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_MAX_SYSTEM_CURRENT, newState); updateState(CHANNEL_MAX_SYSTEM_CURRENT, newState);
if (maxSystemCurrent != 0) { if (maxSystemCurrent != 0) {
if (maxSystemCurrent < maxPresetCurrent) { if (maxSystemCurrent < maxPresetCurrent) {
transceiver.send("curr " + String.valueOf(maxSystemCurrent), this); transceiver.send("curr " + String.valueOf(maxSystemCurrent), this);
updateState(CHANNEL_MAX_PRESET_CURRENT, new DecimalType(maxSystemCurrent)); updateState(CHANNEL_MAX_PRESET_CURRENT,
updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE, new QuantityType<ElectricCurrent>(maxSystemCurrent / 1000.0, Units.AMPERE));
new PercentType((maxSystemCurrent - 6000) * 100 / (maxSystemCurrent - 6000))); updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE, new QuantityType<Dimensionless>(
(maxSystemCurrent - 6000) * 100 / (maxSystemCurrent - 6000), Units.PERCENT));
} }
} else { } else {
logger.debug("maxSystemCurrent is 0. Ignoring."); logger.debug("maxSystemCurrent is 0. Ignoring.");
@ -317,24 +347,31 @@ public class KeContactHandler extends BaseThingHandler {
case "Curr user": { case "Curr user": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
maxPresetCurrent = state; maxPresetCurrent = state;
updateState(CHANNEL_MAX_PRESET_CURRENT, new DecimalType(state)); State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_MAX_PRESET_CURRENT, newState);
if (maxSystemCurrent != 0) { if (maxSystemCurrent != 0) {
updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE, updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE, new QuantityType<Dimensionless>(
new PercentType(Math.min(100, (state - 6000) * 100 / (maxSystemCurrent - 6000)))); Math.min(100, (state - 6000) * 100 / (maxSystemCurrent - 6000)), Units.PERCENT));
} }
break; break;
} }
case "Curr FS": { case "Curr FS": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
State newState = new DecimalType(state); State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_FAILSAFE_CURRENT, newState); updateState(CHANNEL_FAILSAFE_CURRENT, newState);
break; break;
} }
case "Max curr": { case "Max curr": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
maxPresetCurrent = state; maxPresetCurrent = state;
updateState(CHANNEL_PILOT_CURRENT, new DecimalType(state)); State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_PILOT_PWM, new DecimalType(state)); updateState(CHANNEL_PILOT_CURRENT, newState);
break;
}
case "Max curr %": {
int state = entry.getValue().getAsInt();
State newState = new QuantityType<Dimensionless>(state / 10.0, Units.PERCENT);
updateState(CHANNEL_PILOT_PWM, newState);
break; break;
} }
case "Output": { case "Output": {
@ -367,73 +404,67 @@ public class KeContactHandler extends BaseThingHandler {
} }
case "Sec": { case "Sec": {
long state = entry.getValue().getAsLong(); long state = entry.getValue().getAsLong();
State newState = new QuantityType<Time>(state, Units.SECOND);
Calendar uptime = Calendar.getInstance(); updateState(CHANNEL_UPTIME, newState);
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; break;
} }
case "U1": { case "U1": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
State newState = new DecimalType(state); State newState = new QuantityType<ElectricPotential>(state, Units.VOLT);
updateState(CHANNEL_U1, newState); updateState(CHANNEL_U1, newState);
break; break;
} }
case "U2": { case "U2": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
State newState = new DecimalType(state); State newState = new QuantityType<ElectricPotential>(state, Units.VOLT);
updateState(CHANNEL_U2, newState); updateState(CHANNEL_U2, newState);
break; break;
} }
case "U3": { case "U3": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
State newState = new DecimalType(state); State newState = new QuantityType<ElectricPotential>(state, Units.VOLT);
updateState(CHANNEL_U3, newState); updateState(CHANNEL_U3, newState);
break; break;
} }
case "I1": { case "I1": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
State newState = new DecimalType(state); State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_I1, newState); updateState(CHANNEL_I1, newState);
break; break;
} }
case "I2": { case "I2": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
State newState = new DecimalType(state); State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_I2, newState); updateState(CHANNEL_I2, newState);
break; break;
} }
case "I3": { case "I3": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
State newState = new DecimalType(state); State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_I3, newState); updateState(CHANNEL_I3, newState);
break; break;
} }
case "P": { case "P": {
long state = entry.getValue().getAsLong(); long state = entry.getValue().getAsLong();
State newState = new DecimalType(state / 1000); State newState = new QuantityType<Power>(state / 1000.0, Units.WATT);
updateState(CHANNEL_POWER, newState); updateState(CHANNEL_POWER, newState);
break; break;
} }
case "PF": { case "PF": {
int state = entry.getValue().getAsInt(); int state = entry.getValue().getAsInt();
State newState = new PercentType(state / 10); State newState = new QuantityType<Dimensionless>(state / 10.0, Units.PERCENT);
updateState(CHANNEL_POWER_FACTOR, newState); updateState(CHANNEL_POWER_FACTOR, newState);
break; break;
} }
case "E pres": { case "E pres": {
long state = entry.getValue().getAsLong(); long state = entry.getValue().getAsLong();
State newState = new DecimalType(state / 10); State newState = new QuantityType<Energy>(state / 10.0, Units.WATT_HOUR);
updateState(CHANNEL_SESSION_CONSUMPTION, newState); updateState(CHANNEL_SESSION_CONSUMPTION, newState);
break; break;
} }
case "E total": { case "E total": {
long state = entry.getValue().getAsLong(); long state = entry.getValue().getAsLong();
State newState = new DecimalType(state / 10); State newState = new QuantityType<Energy>(state / 10.0, Units.WATT_HOUR);
updateState(CHANNEL_TOTAL_CONSUMPTION, newState); updateState(CHANNEL_TOTAL_CONSUMPTION, newState);
break; break;
} }
@ -468,8 +499,8 @@ public class KeContactHandler extends BaseThingHandler {
break; break;
} }
case "Setenergy": { case "Setenergy": {
int state = entry.getValue().getAsInt() / 10; int state = entry.getValue().getAsInt();
State newState = new DecimalType(state); State newState = new QuantityType<Energy>(state / 10.0, Units.WATT_HOUR);
updateState(CHANNEL_SETENERGY, newState); updateState(CHANNEL_SETENERGY, newState);
break; break;
} }
@ -488,18 +519,19 @@ public class KeContactHandler extends BaseThingHandler {
} else { } else {
switch (channelUID.getId()) { switch (channelUID.getId()) {
case CHANNEL_MAX_PRESET_CURRENT: { case CHANNEL_MAX_PRESET_CURRENT: {
if (command instanceof DecimalType) { if (command instanceof QuantityType<?>) {
QuantityType<?> value = ((QuantityType<?>) command).toUnit("mA");
transceiver.send( transceiver.send(
"curr " + String.valueOf( "curr " + String.valueOf(Math.min(Math.max(6000, value.intValue()), maxSystemCurrent)),
Math.min(Math.max(6000, ((DecimalType) command).intValue()), maxSystemCurrent)),
this); this);
} }
break; break;
} }
case CHANNEL_MAX_PRESET_CURRENT_RANGE: { case CHANNEL_MAX_PRESET_CURRENT_RANGE: {
if (command instanceof OnOffType || command instanceof IncreaseDecreaseType if (command instanceof OnOffType || command instanceof IncreaseDecreaseType
|| command instanceof PercentType) { || command instanceof QuantityType<?>) {
int newValue = 6000; long newValue = 6000;
if (command == IncreaseDecreaseType.INCREASE) { if (command == IncreaseDecreaseType.INCREASE) {
newValue = Math.min(Math.max(6000, maxPresetCurrent + 1), maxSystemCurrent); newValue = Math.min(Math.max(6000, maxPresetCurrent + 1), maxSystemCurrent);
} else if (command == IncreaseDecreaseType.DECREASE) { } else if (command == IncreaseDecreaseType.DECREASE) {
@ -508,12 +540,12 @@ public class KeContactHandler extends BaseThingHandler {
newValue = maxSystemCurrent; newValue = maxSystemCurrent;
} else if (command == OnOffType.OFF) { } else if (command == OnOffType.OFF) {
newValue = 6000; newValue = 6000;
} else if (command instanceof PercentType) { } else if (command instanceof QuantityType<?>) {
newValue = 6000 + (maxSystemCurrent - 6000) * ((PercentType) command).intValue() / 100; QuantityType<?> value = ((QuantityType<?>) command).toUnit("%");
newValue = Math.round(6000 + (maxSystemCurrent - 6000) * value.doubleValue() / 100.0);
} else { } else {
return; return;
} }
transceiver.send("curr " + String.valueOf(newValue), this); transceiver.send("curr " + String.valueOf(newValue), this);
} }
break; break;
@ -555,10 +587,11 @@ public class KeContactHandler extends BaseThingHandler {
break; break;
} }
case CHANNEL_SETENERGY: { case CHANNEL_SETENERGY: {
if (command instanceof DecimalType) { if (command instanceof QuantityType<?>) {
QuantityType<?> value = ((QuantityType<?>) command).toUnit(Units.WATT_HOUR);
transceiver.send( transceiver.send(
"setenergy " + String.valueOf( "setenergy " + String.valueOf(
Math.min(Math.max(0, ((DecimalType) command).intValue() * 10), 999999999)), Math.min(Math.max(0, Math.round(value.doubleValue() * 10.0)), 999999999)),
this); this);
} }
break; break;

View File

@ -48,11 +48,8 @@ import org.slf4j.LoggerFactory;
public class KeContactTransceiver { public class KeContactTransceiver {
public static final int LISTENER_PORT_NUMBER = 7090; 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 LISTENING_INTERVAL = 100;
public static final int BUFFER_SIZE = 1024; 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 DatagramChannel broadcastChannel;
private SelectionKey broadcastKey; private SelectionKey broadcastKey;
@ -406,8 +403,9 @@ public class KeContactTransceiver {
}; };
private void establishConnection(KeContactHandler handler) { private void establishConnection(KeContactHandler handler) {
String ipAddress = handler.getIPAddress();
if (handler.getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR if (handler.getThing().getStatusInfo().getStatusDetail() != ThingStatusDetail.CONFIGURATION_ERROR
&& handler.getConfig().get(IP_ADDRESS) != null && !handler.getConfig().get(IP_ADDRESS).equals("")) { && !ipAddress.equals("")) {
logger.debug("Establishing the connection to the KEBA KeContact '{}'", handler.getThing().getUID()); logger.debug("Establishing the connection to the KEBA KeContact '{}'", handler.getThing().getUID());
DatagramChannel datagramChannel = null; DatagramChannel datagramChannel = null;
@ -438,8 +436,7 @@ public class KeContactTransceiver {
"An exception occurred while registering a selector"); "An exception occurred while registering a selector");
} }
InetSocketAddress remoteAddress = new InetSocketAddress( InetSocketAddress remoteAddress = new InetSocketAddress(ipAddress, LISTENER_PORT_NUMBER);
(String) handler.getConfig().get(IP_ADDRESS), REMOTE_PORT_NUMBER);
try { try {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
@ -449,8 +446,8 @@ public class KeContactTransceiver {
handler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, ""); handler.updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, "");
} catch (Exception e) { } catch (Exception e) {
logger.debug("An exception occurred while connecting connecting to '{}:{}' : {}", new Object[] { logger.debug("An exception occurred while connecting connecting to '{}:{}' : {}",
(String) handler.getConfig().get(IP_ADDRESS), REMOTE_PORT_NUMBER, e.getMessage() }); new Object[] { ipAddress, LISTENER_PORT_NUMBER, e.getMessage() });
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"An exception occurred while connecting"); "An exception occurred while connecting");
} }

View File

@ -21,7 +21,7 @@
<channel id="vehicle" typeId="plugvehicle"/> <channel id="vehicle" typeId="plugvehicle"/>
<channel id="locked" typeId="locked"/> <channel id="locked" typeId="locked"/>
<channel id="maxpilotcurrent" typeId="pilotcurrent"/> <channel id="maxpilotcurrent" typeId="pilotcurrent"/>
<channel id="pwmpilotcurrent" typeId="pilotrange"/> <channel id="maxpilotcurrentdutycyle" typeId="pilotcurrentdutycyle"/>
<channel id="maxsystemcurrent" typeId="maxcurrent"/> <channel id="maxsystemcurrent" typeId="maxcurrent"/>
<channel id="failsafecurrent" typeId="failsafecurrent"/> <channel id="failsafecurrent" typeId="failsafecurrent"/>
<channel id="output" typeId="x2"/> <channel id="output" typeId="x2"/>
@ -64,7 +64,7 @@
</parameter> </parameter>
<parameter name="refreshInterval" type="integer" required="false"> <parameter name="refreshInterval" type="integer" required="false">
<label>Refresh Interval</label> <label>Refresh Interval</label>
<description>Specifies the refresh interval in seconds.</description> <description>Specifies the refresh interval in seconds</description>
<default>15</default> <default>15</default>
</parameter> </parameter>
</config-description> </config-description>
@ -134,82 +134,82 @@
<state readOnly="false"></state> <state readOnly="false"></state>
</channel-type> </channel-type>
<channel-type id="current_settable"> <channel-type id="current_settable">
<item-type>Number</item-type> <item-type>Number:ElectricCurrent</item-type>
<label>Preset Current</label> <label>Preset Current</label>
<description>Preset Current in mA</description> <description>Preset Current</description>
<state pattern="%d mA" readOnly="false"></state> <state pattern="%.3f %unit%" readOnly="false"></state>
</channel-type> </channel-type>
<channel-type id="current" advanced="true"> <channel-type id="current" advanced="true">
<item-type>Number</item-type> <item-type>Number:ElectricCurrent</item-type>
<label>Current</label> <label>Current</label>
<description>Current in mA</description> <description>Current</description>
<state pattern="%d mA" readOnly="true"></state> <state pattern="%.3f %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="maxcurrent" advanced="true"> <channel-type id="maxcurrent" advanced="true">
<item-type>Number</item-type> <item-type>Number:ElectricCurrent</item-type>
<label>Max. System Current</label> <label>Max. System Current</label>
<description>Maximal System Current in mA</description> <description>Maximal System Current</description>
<state pattern="%d mA" readOnly="true"></state> <state pattern="%.3f %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="failsafecurrent" advanced="true"> <channel-type id="failsafecurrent" advanced="true">
<item-type>Number</item-type> <item-type>Number:ElectricCurrent</item-type>
<label>Failsafe Current</label> <label>Failsafe Current</label>
<description>Failsafe Current in mA (if network is lost)</description> <description>Failsafe Current (if network is lost)</description>
<state pattern="%d mA" readOnly="true"></state> <state pattern="%.3f %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="range" advanced="true"> <channel-type id="range" advanced="true">
<item-type>Dimmer</item-type> <item-type>Number:Dimensionless</item-type>
<label>Rel. Current</label> <label>Rel. Current</label>
<description>Current in % of the 6000-63000 mA range accepted by the wallbox</description> <description>Current in % of the 6-63 A range accepted by the wallbox</description>
<state pattern="%d %%" readOnly="false"></state> <state pattern="%.1f %%" readOnly="false"></state>
</channel-type> </channel-type>
<channel-type id="pilotcurrent" advanced="true"> <channel-type id="pilotcurrent" advanced="true">
<item-type>Number</item-type> <item-type>Number:ElectricCurrent</item-type>
<label>Pilot Current</label> <label>Pilot Current</label>
<description>Current preset value via Control pilot in mA</description> <description>Current value offered to the vehicle via control pilot signalization (PWM)</description>
<state pattern="%d mA" readOnly="true"></state> <state pattern="%.3f %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="pilotrange" advanced="true"> <channel-type id="pilotcurrentdutycyle" advanced="true">
<item-type>Number</item-type> <item-type>Number:Dimensionless</item-type>
<label>Pilot Range</label> <label>Pilot Current Duty Cycle</label>
<description>Current preset value via Control pilot in 0,1% of the PWM value</description> <description>Duty cycle of the control pilot signal</description>
<state pattern="%d" readOnly="true"></state> <state pattern="%.1f %%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="uptime" advanced="true"> <channel-type id="uptime" advanced="true">
<item-type>DateTime</item-type> <item-type>Number:Time</item-type>
<label>System Uptime</label> <label>System Uptime</label>
<description>System uptime since the last reset of the wallbox</description> <description>System uptime since the last reset of the wallbox</description>
<state readOnly="true"></state> <state pattern="%d %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="voltage" advanced="true"> <channel-type id="voltage" advanced="true">
<item-type>Number</item-type> <item-type>Number:ElectricPotential</item-type>
<label>Voltage</label> <label>Voltage</label>
<description>Voltage in V</description> <description>Voltage</description>
<state pattern="%d V" readOnly="true"></state> <state pattern="%d %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="power"> <channel-type id="power">
<item-type>Number</item-type> <item-type>Number:Power</item-type>
<label>Power</label> <label>Power</label>
<description>Active Power in W</description> <description>Active Power</description>
<state pattern="%d W" readOnly="true"></state> <state pattern="%.3f %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="powerfactor" advanced="true"> <channel-type id="powerfactor" advanced="true">
<item-type>Number</item-type> <item-type>Number:Dimensionless</item-type>
<label>Power Factor</label> <label>Power Factor</label>
<description>Power factor (cosphi)</description> <description>Power factor (cosphi)</description>
<state readOnly="true"></state> <state pattern="%.1f %%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="energy" advanced="true"> <channel-type id="energy" advanced="true">
<item-type>Number</item-type> <item-type>Number:Energy</item-type>
<label>Energy</label> <label>Energy Session</label>
<description>Power consumption in Wh.</description> <description>Power consumption</description>
<state pattern="%d Wh" readOnly="true"></state> <state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="totalenergy" advanced="true"> <channel-type id="totalenergy" advanced="true">
<item-type>Number</item-type> <item-type>Number:Energy</item-type>
<label>Energy</label> <label>Energy Total</label>
<description>Total energy consumption is added up after each completed charging session</description> <description>Total energy consumption is added up after each completed charging session</description>
<state pattern="%d Wh" readOnly="true"></state> <state pattern="%.1f %unit%" readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="display" advanced="true"> <channel-type id="display" advanced="true">
<item-type>String</item-type> <item-type>String</item-type>
@ -248,10 +248,10 @@
<state readOnly="true"></state> <state readOnly="true"></state>
</channel-type> </channel-type>
<channel-type id="setenergylimit"> <channel-type id="setenergylimit">
<item-type>Number</item-type> <item-type>Number:Energy</item-type>
<label>Energy Limit</label> <label>Energy Limit</label>
<description>An energy limit for an already running or the next charging session.</description> <description>An energy limit for an already running or the next charging session</description>
<state pattern="%d Wh" readOnly="false"></state> <state pattern="%.1f %unit%" readOnly="false"></state>
</channel-type> </channel-type>
<channel-type id="authenticate"> <channel-type id="authenticate">
<item-type>String</item-type> <item-type>String</item-type>