[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
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
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
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 |
| 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:ElectricCurrent | no | maximum current the charging station should deliver to the EV in A |
| maxpresetcurrentrange | Number:Dimensionless | no | maximum current the charging station should deliver to the EV in % |
| power | Number:Power | 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:ElectricCurrent | yes | current for the given phase |
| U1/2/3 | Number:ElectricPotential | yes | voltage for the given phase |
| output | Switch | no | state of the X1 relais |
| input | Switch | yes | state of the X2 contact |
| display | String | no | 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:ElectricCurrent | yes | maximum current the wallbox can deliver |
| failsafecurrent | Number:ElectricCurrent | yes | maximum current the wallbox can deliver, if network is lost |
| uptime | Number:Time | yes | system uptime since the last reset of the wallbox |
| sessionconsumption | Number:Energy | yes | energy delivered in current session |
| totalconsumption | Number:Energy | 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: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
@ -57,28 +59,28 @@ 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"}
Number:Dimensionless KebaCurrentRange "Maximum supply current [%.1f %%]" {channel="keba:kecontact:1:maxpresetcurrentrange"}
Number:ElectricCurrent KebaCurrent "Maximum supply current [%.3f A]" {channel="keba:kecontact:1:maxpresetcurrent"}
Number:ElectricCurrent KebaSystemCurrent "Maximum system supply current [%.3f A]" {channel="keba:kecontact:1:maxsystemcurrent"}
Number:ElectricCurrent KebaFailSafeCurrent "Failsafe supply current [%.3f A]" {channel="keba:kecontact:1:failsafecurrent"}
String KebaState "Operating State [%s]" {channel="keba:kecontact:1:state"}
Switch KebaSwitch "Enabled" {channel="keba:kecontact:1:enabled"}
Switch KebaWallboxPlugged "Plugged into wallbox" {channel="keba:kecontact:1:wallbox"}
Switch KebaVehiclePlugged "Plugged into vehicle" {channel="keba:kecontact:1:vehicle"}
Switch KebaPlugLocked "Plug locked" {channel="keba:kecontact:1:locked"}
DateTime KebaUptime "Uptime [%s s]" {channel="keba:kecontact:1:uptime"}
Number:ElectricCurrent KebaI1 {channel="keba:kecontact:1:I1"}
Number:ElectricCurrent KebaI2 {channel="keba:kecontact:1:I2"}
Number:ElectricCurrent KebaI3 {channel="keba:kecontact:1:I3"}
Number:ElectricPotential KebaU1 {channel="keba:kecontact:1:U1"}
Number:ElectricPotential KebaU2 {channel="keba:kecontact:1:U2"}
Number:ElectricPotential KebaU3 {channel="keba:kecontact:1:U3"}
Number:Power KebaPower "Energy during current session [%.1f Wh]" {channel="keba:kecontact:1:power"}
Number:Energy KebaSessionEnergy {channel="keba:kecontact:1:sessionconsumption"}
Number:Energy KebaTotalEnergy "Energy during all sessions [%.1f Wh]" {channel="keba:kecontact:1:totalconsumption"}
Switch KebaInputSwitch {channel="keba:kecontact:1:input"}
Switch KebaOutputSwitch {channel="keba:kecontact:1:output"}
Number:Energy KebaSetEnergyLimit "Set charge energy limit [%.1f Wh]" {channel="keba:kecontact:1:setenergylimit"}
```
demo.sitemap:
@ -86,20 +88,63 @@ 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"]
}
Text label="Charging Station" {
Text item=KebaState
Text item=KebaUptime
Switch item=KebaSwitch
Switch item=KebaWallboxPlugged
Switch item=KebaVehiclePlugged
Switch item=KebaPlugLocked
Slider item=KebaCurrentRange
Text item=KebaCurrent
Text item=KebaSystemCurrent
Text item=KebaFailSafeCurrent
Text item=KebaSessionEnergy
Text item=KebaTotalEnergy
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_ENABLED = "enabled";
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_PRESET_CURRENT_RANGE = "maxpresetcurrentrange";
public static final String CHANNEL_MAX_PRESET_CURRENT = "maxpresetcurrent";
@ -82,7 +82,7 @@ public class KebaBindingConstants {
E('0'),
B('1'),
C('2', '3'),
X('A', 'B', 'C', 'D');
X('A', 'B', 'C', 'D', 'E', 'G', 'H');
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.math.BigDecimal;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
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 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.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.QuantityType;
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.Thing;
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 POLLING_REFRESH_INTERVAL = "refreshInterval";
public static final int POLLING_REFRESH_INTERVAL_DEFAULT = 15;
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";
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);
@ -94,32 +102,46 @@ public class KeContactHandler extends BaseThingHandler {
@Override
public void initialize() {
if (getConfig().get(IP_ADDRESS) != null && !getConfig().get(IP_ADDRESS).equals("")) {
transceiver.registerHandler(this);
try {
if (isKebaReachable()) {
transceiver.registerHandler(this);
cache = new ExpiringCacheMap<>(
Math.max((((BigDecimal) getConfig().get(POLLING_REFRESH_INTERVAL)).intValue()) - 5, 0) * 1000);
int refreshInterval = getRefreshInterval();
cache = new ExpiringCacheMap<>(Math.max(refreshInterval - 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()));
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(this::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");
if (pollingJob == null || pollingJob.isCancelled()) {
pollingJob = scheduler.scheduleWithFixedDelay(this::pollingRunnable, 0, refreshInterval,
TimeUnit.SECONDS);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"IP address or port number not set");
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"IP address or port number not set");
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"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
public void dispose() {
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) : "";
}
public int getRefreshInterval() {
return getConfig().get(POLLING_REFRESH_INTERVAL) != null
? ((BigDecimal) getConfig().get(POLLING_REFRESH_INTERVAL)).intValue()
: POLLING_REFRESH_INTERVAL_DEFAULT;
}
private KeContactHandler getHandler() {
return this;
}
@ -150,9 +178,10 @@ public class KeContactHandler extends BaseThingHandler {
private void pollingRunnable() {
try {
logger.debug("Running pollingRunnable to connect Keba wallbox");
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);
if (!isKebaReachable()) {
logger.debug("isKebaReachable() timed out after '{}' milliseconds", System.currentTimeMillis() - stamp);
transceiver.unRegisterHandler(getHandler());
} else {
if (getThing().getStatus() == ThingStatus.ONLINE) {
@ -300,14 +329,15 @@ public class KeContactHandler extends BaseThingHandler {
case "Curr HW": {
int state = entry.getValue().getAsInt();
maxSystemCurrent = state;
State newState = new DecimalType(state);
State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
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)));
updateState(CHANNEL_MAX_PRESET_CURRENT,
new QuantityType<ElectricCurrent>(maxSystemCurrent / 1000.0, Units.AMPERE));
updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE, new QuantityType<Dimensionless>(
(maxSystemCurrent - 6000) * 100 / (maxSystemCurrent - 6000), Units.PERCENT));
}
} else {
logger.debug("maxSystemCurrent is 0. Ignoring.");
@ -317,24 +347,31 @@ public class KeContactHandler extends BaseThingHandler {
case "Curr user": {
int state = entry.getValue().getAsInt();
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) {
updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE,
new PercentType(Math.min(100, (state - 6000) * 100 / (maxSystemCurrent - 6000))));
updateState(CHANNEL_MAX_PRESET_CURRENT_RANGE, new QuantityType<Dimensionless>(
Math.min(100, (state - 6000) * 100 / (maxSystemCurrent - 6000)), Units.PERCENT));
}
break;
}
case "Curr FS": {
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);
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));
State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
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;
}
case "Output": {
@ -367,73 +404,67 @@ public class KeContactHandler extends BaseThingHandler {
}
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())));
State newState = new QuantityType<Time>(state, Units.SECOND);
updateState(CHANNEL_UPTIME, newState);
break;
}
case "U1": {
int state = entry.getValue().getAsInt();
State newState = new DecimalType(state);
State newState = new QuantityType<ElectricPotential>(state, Units.VOLT);
updateState(CHANNEL_U1, newState);
break;
}
case "U2": {
int state = entry.getValue().getAsInt();
State newState = new DecimalType(state);
State newState = new QuantityType<ElectricPotential>(state, Units.VOLT);
updateState(CHANNEL_U2, newState);
break;
}
case "U3": {
int state = entry.getValue().getAsInt();
State newState = new DecimalType(state);
State newState = new QuantityType<ElectricPotential>(state, Units.VOLT);
updateState(CHANNEL_U3, newState);
break;
}
case "I1": {
int state = entry.getValue().getAsInt();
State newState = new DecimalType(state);
State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_I1, newState);
break;
}
case "I2": {
int state = entry.getValue().getAsInt();
State newState = new DecimalType(state);
State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_I2, newState);
break;
}
case "I3": {
int state = entry.getValue().getAsInt();
State newState = new DecimalType(state);
State newState = new QuantityType<ElectricCurrent>(state / 1000.0, Units.AMPERE);
updateState(CHANNEL_I3, newState);
break;
}
case "P": {
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);
break;
}
case "PF": {
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);
break;
}
case "E pres": {
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);
break;
}
case "E total": {
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);
break;
}
@ -468,8 +499,8 @@ public class KeContactHandler extends BaseThingHandler {
break;
}
case "Setenergy": {
int state = entry.getValue().getAsInt() / 10;
State newState = new DecimalType(state);
int state = entry.getValue().getAsInt();
State newState = new QuantityType<Energy>(state / 10.0, Units.WATT_HOUR);
updateState(CHANNEL_SETENERGY, newState);
break;
}
@ -488,18 +519,19 @@ public class KeContactHandler extends BaseThingHandler {
} else {
switch (channelUID.getId()) {
case CHANNEL_MAX_PRESET_CURRENT: {
if (command instanceof DecimalType) {
if (command instanceof QuantityType<?>) {
QuantityType<?> value = ((QuantityType<?>) command).toUnit("mA");
transceiver.send(
"curr " + String.valueOf(
Math.min(Math.max(6000, ((DecimalType) command).intValue()), maxSystemCurrent)),
"curr " + String.valueOf(Math.min(Math.max(6000, value.intValue()), maxSystemCurrent)),
this);
}
break;
}
case CHANNEL_MAX_PRESET_CURRENT_RANGE: {
if (command instanceof OnOffType || command instanceof IncreaseDecreaseType
|| command instanceof PercentType) {
int newValue = 6000;
|| command instanceof QuantityType<?>) {
long newValue = 6000;
if (command == IncreaseDecreaseType.INCREASE) {
newValue = Math.min(Math.max(6000, maxPresetCurrent + 1), maxSystemCurrent);
} else if (command == IncreaseDecreaseType.DECREASE) {
@ -508,12 +540,12 @@ public class KeContactHandler extends BaseThingHandler {
newValue = maxSystemCurrent;
} else if (command == OnOffType.OFF) {
newValue = 6000;
} else if (command instanceof PercentType) {
newValue = 6000 + (maxSystemCurrent - 6000) * ((PercentType) command).intValue() / 100;
} else if (command instanceof QuantityType<?>) {
QuantityType<?> value = ((QuantityType<?>) command).toUnit("%");
newValue = Math.round(6000 + (maxSystemCurrent - 6000) * value.doubleValue() / 100.0);
} else {
return;
}
transceiver.send("curr " + String.valueOf(newValue), this);
}
break;
@ -555,10 +587,11 @@ public class KeContactHandler extends BaseThingHandler {
break;
}
case CHANNEL_SETENERGY: {
if (command instanceof DecimalType) {
if (command instanceof QuantityType<?>) {
QuantityType<?> value = ((QuantityType<?>) command).toUnit(Units.WATT_HOUR);
transceiver.send(
"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);
}
break;

View File

@ -48,11 +48,8 @@ import org.slf4j.LoggerFactory;
public 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;
@ -406,8 +403,9 @@ public class KeContactTransceiver {
};
private void establishConnection(KeContactHandler handler) {
String ipAddress = handler.getIPAddress();
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());
DatagramChannel datagramChannel = null;
@ -438,8 +436,7 @@ public class KeContactTransceiver {
"An exception occurred while registering a selector");
}
InetSocketAddress remoteAddress = new InetSocketAddress(
(String) handler.getConfig().get(IP_ADDRESS), REMOTE_PORT_NUMBER);
InetSocketAddress remoteAddress = new InetSocketAddress(ipAddress, LISTENER_PORT_NUMBER);
try {
if (logger.isTraceEnabled()) {
@ -449,8 +446,8 @@ public class KeContactTransceiver {
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() });
logger.debug("An exception occurred while connecting connecting to '{}:{}' : {}",
new Object[] { ipAddress, LISTENER_PORT_NUMBER, e.getMessage() });
handler.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"An exception occurred while connecting");
}

View File

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