]> git.basschouten.com Git - openhab-addons.git/commitdiff
[keba] Improve connection establishment and stability (#10179)
authorMikeTheTux <44850211+MikeTheTux@users.noreply.github.com>
Thu, 25 Feb 2021 15:29:40 +0000 (16:29 +0100)
committerGitHub <noreply@github.com>
Thu, 25 Feb 2021 15:29:40 +0000 (16:29 +0100)
Signed-off-by: Michael Weger <weger.michael@gmx.net>
* - worked in review findings
- introduced QuantityTypes
- removed redundant pwmpilotcurrent (duplicate of maxpilotcurrent)
- added maxpilotcurrentdutycyle

bundles/org.openhab.binding.keba/README.md
bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/KebaBindingConstants.java
bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/handler/KeContactHandler.java
bundles/org.openhab.binding.keba/src/main/java/org/openhab/binding/keba/internal/handler/KeContactTransceiver.java
bundles/org.openhab.binding.keba/src/main/resources/OH-INF/thing/kecontact.xml

index 2c1c5bae994b505b717c7aedb835cfa676d4e8c8..e6037cfebc3f0f185f1b01a46a4db7ef019bb94b 100644 (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
index fa4b0d227ccfc09c39f4ec6913ad8e1c23d7bf33..42f48c577ec0f0cca858a5989a12e5b83e016236 100644 (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 + "'");
         }
     }
 }
index 478c49bf9c24b09bc084cd01d9954c659b2878df..36f789e4962f0ca165b25a0974b3ec5d94573b94 100644 (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);
-
-            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(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");
+        try {
+            if (isKebaReachable()) {
+                transceiver.registerHandler(this);
+
+                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()));
+
+                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;
index c123190843cb18dd1d85849274af80a73f32547e..61139dfe0f3a17f1d35a80d0286fae286b1533be 100644 (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");
                     }
index 1739420b74679a1d0a44a97ee733991d0cd65701..cccbe07bc666a34044d4f07e33423d26f8642608 100644 (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>
                <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>
                <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>