]> git.basschouten.com Git - openhab-addons.git/commitdiff
[goecharger] Add API V2 support (#12400)
authorReinhard Plaim <53980214+ranzen84@users.noreply.github.com>
Tue, 8 Mar 2022 14:37:39 +0000 (15:37 +0100)
committerGitHub <noreply@github.com>
Tue, 8 Mar 2022 14:37:39 +0000 (15:37 +0100)
* add API V2 support
* handle InterrupedException explicitly

Signed-off-by: Reinhard Plaim <reinhardplaim@gmail.com>
12 files changed:
bundles/org.openhab.binding.goecharger/README.md
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerBindingConstants.java
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerConfiguration.java
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerHandlerFactory.java
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseBaseDTO.java [new file with mode: 0644]
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseDTO.java
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseV2DTO.java [new file with mode: 0644]
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerBaseHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerHandler.java
bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerV2Handler.java [new file with mode: 0644]
bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/i18n/goecharger.properties
bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/thing/thing-types.xml

index 1a8317e98a0d53b4a7c6d7230fdac479b7efb0ed..22197fae8a1de1af1f4bbdfa4b283c6d26cb82dc 100644 (file)
@@ -2,10 +2,12 @@
 
 This Binding controls and reads data from the [Go-eCharger](https://go-e.co/).
 It is a mobile wallbox for charging EVs and has an open REST API for reading data and configuration.
+The API must be activated in the Go-eCharger app.
 
 ## Supported Things
 
-This binding supports Go-eCharger HOME+ with 7.4kW or 22kW.
+This binding supports Go-eCharger HOME+ with 7.4kW, 11kW or 22kW.
+The Go-eCharger HOMEfix with 11kW and 22kW is supported too.
 
 ## Thing Configuration
 
@@ -14,34 +16,41 @@ The thing has two configuration parameters:
 | Parameter       | Description                                   | Required |
 |-----------------|-----------------------------------------------|----------|
 | ip              | The IP-address of your Go-eCharger            | yes      |
+| apiVersion      | The API version to use (1=default or 2)       | no       |
 | refreshInterval | Interval to read data, default 5 (in seconds) | no       |
 
+The apiVersion 2 is only available for Go-eCharger with new hardware revision (CM-03).
+
 ## Channels
 
 Currently available channels are 
-| Channel ID               | Item Type                | Description                                                   |
-|--------------------------|--------------------------|---------------------------------------------------------------|
-| maxCurrent               | Number:ElectricCurrent   | Maximum current allowed to use for charging                   |
-| pwmSignal                | String                   | Signal status for PWM signal                                  |
-| error                    | String                   | Error code of charger                                         |
-| voltageL1                | Number:ElectricPotential | Voltage on L1                                                 |
-| voltageL2                | Number:ElectricPotential | Voltage on L2                                                 |
-| voltageL3                | Number:ElectricPotential | Voltage on L3                                                 |
-| currentL1                | Number:ElectricCurrent   | Current on L1                                                 |
-| currentL2                | Number:ElectricCurrent   | Current on L2                                                 |
-| currentL3                | Number:ElectricCurrent   | Current on L3                                                 |
-| powerL1                  | Number:Power             | Power on L1                                                   |
-| powerL2                  | Number:Power             | Power on L2                                                   |
-| powerL3                  | Number:Power             | Power on L2                                                   |
-| phases                   | Number                   | Amount of phases currently used for charging                  |
-| sessionChargeEnergyLimit | Number:Energy            | Wallbox stops charging after defined value, disable with 0    |
-| sessionChargedEnergy     | Number:Energy            | Amount of energy that has been charged in this session        |
-| totalChargedEnergy       | Number:Energy            | Amount of energy that has been charged since installation     |
-| allowCharging            | Switch                   | If `ON` charging is allowed                                   |
-| cableCurrent             | Number:ElectricCurrent   | Specifies the max current that can be charged with that cable |
-| temperature              | Number:Temperature       | Temperature of the Go-eCharger                                |
-| firmware                 | String                   | Firmware Version                                              |
-| accessConfiguration      | String                   | Access configuration, for example OPEN, RFID ...              |
+| Channel ID               | Item Type                | Description                                                   | API version       |
+|--------------------------|--------------------------|---------------------------------------------------------------|-------------------|
+| maxCurrent               | Number:ElectricCurrent   | Maximum current allowed to use for charging                   | 1 (r/w), 2 (r/w)  |
+| maxCurrentTemp           | Number:ElectricCurrent   | Maximum current temporary (not written to EEPROM)             | 1 (r)             |
+| pwmSignal                | String                   | Signal status for PWM signal                                  | 1 (r), 2 (r)      |
+| error                    | String                   | Error code of charger                                         | 1 (r), 2 (r)      |
+| voltageL1                | Number:ElectricPotential | Voltage on L1                                                 | 1 (r), 2 (r)      |
+| voltageL2                | Number:ElectricPotential | Voltage on L2                                                 | 1 (r), 2 (r)      |
+| voltageL3                | Number:ElectricPotential | Voltage on L3                                                 | 1 (r), 2 (r)      |
+| currentL1                | Number:ElectricCurrent   | Current on L1                                                 | 1 (r), 2 (r)      |
+| currentL2                | Number:ElectricCurrent   | Current on L2                                                 | 1 (r), 2 (r)      |
+| currentL3                | Number:ElectricCurrent   | Current on L3                                                 | 1 (r), 2 (r)      |
+| powerL1                  | Number:Power             | Power on L1                                                   | 1 (r), 2 (r)      |
+| powerL2                  | Number:Power             | Power on L2                                                   | 1 (r), 2 (r)      |
+| powerL3                  | Number:Power             | Power on L2                                                   | 1 (r), 2 (r)      |
+| powerAll                 | Number:Power             | Power over all three phases                                   | 1 (r), 2 (r)      |
+| phases                   | Number                   | Amount of phases currently used for charging                  | 1 (r), 2 (r/w)    |
+| sessionChargeEnergyLimit | Number:Energy            | Wallbox stops charging after defined value, disable with 0    | 1 (r/w), 2 (r/w)  |
+| sessionChargedEnergy     | Number:Energy            | Amount of energy that has been charged in this session        | 1 (r), 2 (r)      |
+| totalChargedEnergy       | Number:Energy            | Amount of energy that has been charged since installation     | 1 (r), 2 (r)      |
+| allowCharging            | Switch                   | If `ON` charging is allowed                                   | 1 (r/w), 2 (r)    |
+| cableCurrent             | Number:ElectricCurrent   | Specifies the max current that can be charged with that cable | 1 (r), 2 (r)      |
+| temperature              | Number:Temperature       | Temperature of the curciuit board of the Go-eCharger          | 1 (r), 2 (r)      |
+| temperatureType2Port     | Number:Temperature       | Temperature of the type 2 port of the Go-eCharger             | 2 (r)             |
+| firmware                 | String                   | Firmware Version                                              | 1 (r), 2 (r)      |
+| accessConfiguration      | String                   | Access configuration, for example OPEN, RFID ...              | 1 (r/w)           |
+| forceState               | Number                   | Force state  (Neutral=0, Off=1, On=2)                         | 2 (r/w)           |
 
 ## Full Example
 
@@ -55,6 +64,9 @@ demo.items
 
 ```
 Number:ElectricCurrent     GoEChargerMaxCurrent                 "Maximum current"                       {channel="goecharger:goe:garage:maxCurrent"}
+Number:ElectricCurrent     GoEChargerMaxCurrentTemp             "Maximum current temporary"             {channel="goecharger:goe:garage:maxCurrentTemp"}
+Number                     GoEChargerForceState                 "Force state"                           {channel="goecharger:goe:garage:forceState"}
+Number                     GoEChargerPhases                     "Phases"                                {channel="goecharger:goe:garage:phases"}
 String                     GoEChargerPwmSignal                  "Pwm signal status"                     {channel="goecharger:goe:garage:pwmSignal"}
 String                     GoEChargerError                      "Error code"                            {channel="goecharger:goe:garage:error"}
 Number:ElectricPotential   GoEChargerVoltageL1                  "Voltage l1"                            {channel="goecharger:goe:garage:voltageL1"}
@@ -66,13 +78,14 @@ Number:ElectricCurrent     GoEChargerCurrentL3                  "Current l3"
 Number:Power               GoEChargerPowerL1                    "Power l1"                              {channel="goecharger:goe:garage:powerL1"}
 Number:Power               GoEChargerPowerL2                    "Power l2"                              {channel="goecharger:goe:garage:powerL2"}
 Number:Power               GoEChargerPowerL3                    "Power l3"                              {channel="goecharger:goe:garage:powerL3"}
-Number                     GoEChargerPhases                     "Phases"                                {channel="goecharger:goe:garage:phases"}
+Number:Power               GoEChargerPowerAll                   "Power over All"                        {channel="goecharger:goe:garage:powerAll"}
 Number:Energy              GoEChargerSessionChargeEnergyLimit   "Current session charge energy limit"   {channel="goecharger:goe:garage:sessionChargeEnergyLimit"}
 Number:Energy              GoEChargerSessionChargedEnergy       "Current session charged energy"        {channel="goecharger:goe:garage:sessionChargedEnergy"}
 Number:Energy              GoEChargerTotalChargedEnergy         "Total charged energy"                  {channel="goecharger:goe:garage:totalChargedEnergy"}
 Switch                     GoEChargerAllowCharging              "Allow charging"                        {channel="goecharger:goe:garage:allowCharging"}
 Number:ElectricCurrent     GoEChargerCableCurrent               "Cable encoding"                        {channel="goecharger:goe:garage:cableCurrent"}
-Number:Temperature         GoEChargerTemperature                "Temperature"                           {channel="goecharger:goe:garage:temperature"}
+Number:Temperature         GoEChargerTemperatureType2Port       "Temperature type 2 port"               {channel="goecharger:goe:garage:temperatureType2Port"}
+Number:Temperature         GoEChargerTemperatureCircuitBoard    "Temperature circuit board"             {channel="goecharger:goe:garage:temperature"}
 String                     GoEChargerFirmware                   "Firmware"                              {channel="goecharger:goe:garage:firmware"}
 String                     GoEChargerAccessConfiguration        "Access configuration"                  {channel="goecharger:goe:garage:accessConfiguration"}
 ```
@@ -88,9 +101,86 @@ when
     Item availablePVCurrent received update
 then
     logInfo("Amps available: ", receivedCommand.state)
-    GoEChargerMaxCurrent.sendCommand(receivedCommand.state)
+    GoEChargerMaxCurrentTemp.sendCommand(receivedCommand.state)
+end
+```
+
+Advanced example:
+```
+rule "Set charging limit for go-eCharger"
+when
+    Time cron "*/10 * * ? * *" // Trigger every 10 seconds
+then
+    if (GoEChargerExcessCharge.state == ON) {
+        var totalPowerOutputInWatt = Total_power_fast.state as DecimalType * 1000
+        if (totalPowerOutputInWatt > 0) {
+            totalPowerOutputInWatt = 0
+        }
+
+        totalPowerOutputInWatt = totalPowerOutputInWatt * -1
+
+        var maxAmp3Phases = (totalPowerOutputInWatt / 3) / 230
+        if (maxAmp3Phases > 16.0) {
+            maxAmp3Phases = 16.0
+        }
+        var maxAmp1Phase = totalPowerOutputInWatt / 230;
+
+        if (maxAmp3Phases.intValue >= 6) {
+            // set force state to neutral (Neutral=0, Off=1, On=2)
+            if (GoEChargerForceState.state != 0) {
+                GoEChargerForceState.sendCommand(0);
+            }
+
+            // 3 phases
+            if ((GoEChargerPhases.state as Number) != 3) {
+                GoEChargerPhases.sendCommand(3);
+            }
+
+            if ((GoEChargerMaxCurrent.state as Number).intValue != maxAmp3Phases.intValue) {
+                GoEChargerMaxCurrent.sendCommand(maxAmp3Phases.intValue)
+                // logInfo("eCharger", "Set charging limit 3 Phases: " + maxAmp3Phases.intValue + " A")
+            }
+        } else {
+            if (maxAmp1Phase.intValue >= 6 ) {
+                // set force state to neutral (Neutral=0, Off=1, On=2)
+                if (GoEChargerForceState.state != 0) {
+                    GoEChargerForceState.sendCommand(0);
+                }
+
+                // switch to 1 phase -> check if this is useful
+                if ((GoEChargerPhases.state as Number) != 1) {
+                    GoEChargerPhases.sendCommand(1)
+                }
+
+                if ((GoEChargerMaxCurrent.state as Number).intValue != maxAmp1Phase.intValue) {
+                    GoEChargerMaxCurrent.sendCommand(maxAmp1Phase.intValue)
+                    // logInfo("eCharger", "Set charging limit 1 Phase: " + maxAmp1Phase.intValue + " A")
+                }
+            } else {
+                // switch off
+                if (GoEChargerForceState.state != 1) {
+                    GoEChargerForceState.sendCommand(1);
+                    // logInfo("eCharger", "Switch charging off")
+                }
+            }
+        }
+    } else {
+        // set force state to neutral (Neutral=0, Off=1, On=2)
+        if (GoEChargerForceState.state != 0) {
+            GoEChargerForceState.sendCommand(0);
+        }
+
+        if ((GoEChargerPhases.state as Number) != 3) {
+            GoEChargerPhases.sendCommand(3);
+        }
+
+        if ((GoEChargerMaxCurrent.state as Number).intValue != 16) {
+            GoEChargerMaxCurrent.sendCommand(16)
+        }
+    }
 end
 ```
+
 You can also define more advanced rules if you have multiple cars that charge with a different amount of phases.
 For example if your car charges on one phase only, you can set maxAmps to output of PV power, if your car charges on two phases you can set maxAmps to `pv output / 2`, and for 3 phases `pv output / 3`.
 In general the calculation would be Â´maxAmps = pvOutput / phases`.
index 614c4b5147203ff36935d4943b324eeb7861ee20..cadc624f80691ecbac1af525ac766eada1738bc5 100644 (file)
@@ -31,6 +31,7 @@ public class GoEChargerBindingConstants {
 
     // List of all Channel ids
     public static final String MAX_CURRENT = "maxCurrent";
+    public static final String MAX_CURRENT_TEMPORARY = "maxCurrentTemporary";
     public static final String ACCESS_CONFIGURATION = "accessConfiguration";
     public static final String PWM_SIGNAL = "pwmSignal";
     public static final String ERROR = "error";
@@ -43,10 +44,12 @@ public class GoEChargerBindingConstants {
     public static final String POWER_L1 = "powerL1";
     public static final String POWER_L2 = "powerL2";
     public static final String POWER_L3 = "powerL3";
+    public static final String POWER_ALL = "powerAll";
     public static final String ALLOW_CHARGING = "allowCharging";
     public static final String CABLE_ENCODING = "cableCurrent";
     public static final String PHASES = "phases";
-    public static final String TEMPERATURE = "temperature";
+    public static final String TEMPERATURE_TYPE2_PORT = "temperatureType2Port";
+    public static final String TEMPERATURE_CIRCUIT_BOARD = "temperature";
     public static final String SESSION_CHARGE_CONSUMPTION = "sessionChargedEnergy";
     public static final String SESSION_CHARGE_CONSUMPTION_LIMIT = "sessionChargeEnergyLimit";
     public static final String TOTAL_CONSUMPTION = "totalChargedEnergy";
@@ -54,4 +57,10 @@ public class GoEChargerBindingConstants {
 
     public static final String API_URL = "http://%IP%/status";
     public static final String MQTT_URL = "http://%IP%/mqtt?payload=%KEY%=%VALUE%";
+
+    // API v2 only
+    public static final String FORCE_STATE = "forceState";
+
+    public static final String API_URL_V2 = "http://%IP%/api/status";
+    public static final String SET_URL_V2 = "http://%IP%/api/set?%KEY%=%VALUE%";
 }
index cde62da04b85cd0f2198a698c3272cb201bf7d63..48163b9cd9f941af18ede10ecc8521f466058486 100644 (file)
@@ -19,10 +19,12 @@ import org.eclipse.jdt.annotation.Nullable;
  * The {@link GoEChargerConfiguration} class contains fields mapping thing configuration parameters.
  *
  * @author Samuel Brucksch - Initial contribution
+ * @author Reinhard Plaim - Add apiVersion
  */
 @NonNullByDefault
 public class GoEChargerConfiguration {
 
     public @Nullable String ip;
     public Integer refreshInterval = 5;
+    public Integer apiVersion = 1;
 }
index 99c553582bc2bd39c71c71401ffdf7e25b9ff80f..83ffbbf6d7bdc938decf4e3e14b0a85c67238506 100644 (file)
@@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.jetty.client.HttpClient;
 import org.openhab.binding.goecharger.internal.handler.GoEChargerHandler;
+import org.openhab.binding.goecharger.internal.handler.GoEChargerV2Handler;
 import org.openhab.core.io.net.http.HttpClientFactory;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingTypeUID;
@@ -40,7 +41,6 @@ import org.osgi.service.component.annotations.Reference;
 @NonNullByDefault
 @Component(configurationPid = "binding.goecharger", service = ThingHandlerFactory.class)
 public class GoEChargerHandlerFactory extends BaseThingHandlerFactory {
-
     private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_GOE);
     private final HttpClient httpClient;
 
@@ -57,9 +57,15 @@ public class GoEChargerHandlerFactory extends BaseThingHandlerFactory {
     @Override
     protected @Nullable ThingHandler createHandler(Thing thing) {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+        var apiVersion = thing.getConfiguration().as(GoEChargerConfiguration.class).apiVersion;
 
         if (THING_TYPE_GOE.equals(thingTypeUID)) {
-            return new GoEChargerHandler(thing, httpClient);
+            if (apiVersion == 1) {
+                return new GoEChargerHandler(thing, httpClient);
+            }
+            if (apiVersion == 2) {
+                return new GoEChargerV2Handler(thing, httpClient);
+            }
         }
 
         return null;
diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseBaseDTO.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseBaseDTO.java
new file mode 100644 (file)
index 0000000..da7bf49
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.goecharger.internal.api;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link GoEStatusResponseBaseDTO} class represents a json response from the
+ * charger.
+ *
+ * @author Reinhard Plaim - Initial contribution
+ */
+public class GoEStatusResponseBaseDTO {
+    @SerializedName("car")
+    public Integer pwmSignal;
+
+    @SerializedName("amp")
+    public Integer maxCurrent;
+
+    @SerializedName("nrg")
+    public Integer[] energy;
+
+    @SerializedName("err")
+    public Integer errorCode;
+
+    @SerializedName("cbl")
+    public Integer cableEncoding;
+
+    @SerializedName("eto")
+    public Long totalChargeConsumption;
+
+    @SerializedName("fwv")
+    public String firmware;
+}
index 7688582f0941c9111274ec92445286f0ed9bc94a..839e0f52a7b893934721f5fdfc30ca28b13d2b95 100644 (file)
@@ -19,47 +19,30 @@ import com.google.gson.annotations.SerializedName;
  * charger.
  *
  * @author Samuel Brucksch - Initial contribution
+ * @author Reinhard Plaim - move some properties to base DTO
  */
-public class GoEStatusResponseDTO {
+public class GoEStatusResponseDTO extends GoEStatusResponseBaseDTO {
     @SerializedName("version")
     public String version;
 
-    @SerializedName("car")
-    public Integer pwmSignal;
+    @SerializedName("pha")
+    public Integer phases;
 
     @SerializedName("ast")
     public Integer accessConfiguration;
 
-    @SerializedName("amp")
-    public Integer maxCurrent;
-
-    @SerializedName("nrg")
-    public Integer[] energy;
-
-    @SerializedName("err")
-    public Integer errorCode;
-
     @SerializedName("alw")
     public Integer allowCharging;
 
-    @SerializedName("cbl")
-    public Integer cableEncoding;
-
-    @SerializedName("pha")
-    public Integer phases;
-
     @SerializedName("tmp")
     public Integer temperature;
 
-    @SerializedName("dws")
-    public Long sessionChargeConsumption;
-
     @SerializedName("dwo")
     public Integer sessionChargeConsumptionLimit;
 
-    @SerializedName("eto")
-    public Long totalChargeConsumption;
+    @SerializedName("dws")
+    public Long sessionChargeConsumption;
 
-    @SerializedName("fwv")
-    public String firmware;
+    @SerializedName("amx")
+    public Integer maxCurrentTemporary;
 }
diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseV2DTO.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseV2DTO.java
new file mode 100644 (file)
index 0000000..37f903c
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.goecharger.internal.api;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link GoEStatusResponseV2DTO} class represents a json response from the
+ * charger.
+ *
+ * @author Reinhard Plaim - Initial contribution
+ */
+public class GoEStatusResponseV2DTO extends GoEStatusResponseBaseDTO {
+    @SerializedName("mod")
+    public String version;
+
+    @SerializedName("psm")
+    public Integer phases;
+
+    @SerializedName("alw")
+    public Boolean allowCharging;
+
+    @SerializedName("tma")
+    public Double[] temperatures;
+
+    @SerializedName("wh")
+    public Long sessionChargeConsumption;
+
+    @SerializedName("dwo")
+    public Double sessionChargeConsumptionLimit;
+
+    @SerializedName("frc")
+    public Integer forceState;
+}
diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerBaseHandler.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerBaseHandler.java
new file mode 100644 (file)
index 0000000..9711b77
--- /dev/null
@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.goecharger.internal.handler;
+
+import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.openhab.binding.goecharger.internal.GoEChargerConfiguration;
+import org.openhab.binding.goecharger.internal.api.GoEStatusResponseBaseDTO;
+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;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link GoEChargerBaseHandler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Samuel Brucksch - Initial contribution
+ * @author Reinhard Plaim - Adapt to use API version 2
+ */
+@NonNullByDefault
+public abstract class GoEChargerBaseHandler extends BaseThingHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(GoEChargerBaseHandler.class);
+
+    protected @Nullable GoEChargerConfiguration config;
+
+    protected List<String> allChannels = new ArrayList<>();
+
+    protected final Gson gson = new Gson();
+
+    private @Nullable ScheduledFuture<?> refreshJob;
+
+    protected final HttpClient httpClient;
+
+    public GoEChargerBaseHandler(Thing thing, HttpClient httpClient) {
+        super(thing);
+        this.httpClient = httpClient;
+    }
+
+    protected State getValue(String channelId, GoEStatusResponseBaseDTO goeResponseBase) {
+        switch (channelId) {
+            case MAX_CURRENT:
+                if (goeResponseBase.maxCurrent == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponseBase.maxCurrent, Units.AMPERE);
+            case CABLE_ENCODING:
+                if (goeResponseBase.cableEncoding == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponseBase.cableEncoding, Units.AMPERE);
+            case FIRMWARE:
+                if (goeResponseBase.firmware == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new StringType(goeResponseBase.firmware);
+            case VOLTAGE_L1:
+                if (goeResponseBase.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponseBase.energy[0], Units.VOLT);
+            case VOLTAGE_L2:
+                if (goeResponseBase.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponseBase.energy[1], Units.VOLT);
+            case VOLTAGE_L3:
+                if (goeResponseBase.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponseBase.energy[2], Units.VOLT);
+        }
+        return UnDefType.UNDEF;
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+    }
+
+    @Override
+    public void initialize() {
+        config = getConfigAs(GoEChargerConfiguration.class);
+        allChannels = getThing().getChannels().stream().map(channel -> channel.getUID().getId())
+                .collect(Collectors.toList());
+
+        logger.debug("Number of channels found: {}", allChannels.size());
+
+        updateStatus(ThingStatus.UNKNOWN);
+
+        startAutomaticRefresh();
+        logger.debug("Finished initializing!");
+    }
+
+    @Nullable
+    protected GoEStatusResponseBaseDTO getGoEData()
+            throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
+        return null;
+    }
+
+    protected void updateChannelsAndStatus(@Nullable GoEStatusResponseBaseDTO goeResponse, @Nullable String message) {
+    }
+
+    private void refresh() {
+        synchronized (this) {
+            // Request new GoE data
+            try {
+                GoEStatusResponseBaseDTO goeResponse = getGoEData();
+                updateChannelsAndStatus(goeResponse, null);
+            } catch (InterruptedException ie) {
+                Thread.currentThread().interrupt();
+                updateChannelsAndStatus(null, ie.getMessage());
+            } catch (TimeoutException | ExecutionException e) {
+                updateChannelsAndStatus(null, e.getMessage());
+            }
+        }
+    }
+
+    private void startAutomaticRefresh() {
+        synchronized (this) {
+            if (refreshJob == null || refreshJob.isCancelled()) {
+                GoEChargerConfiguration config = getConfigAs(GoEChargerConfiguration.class);
+                int delay = config.refreshInterval.intValue();
+                logger.debug("Running refresh job with delay {} s", delay);
+                refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, delay, TimeUnit.SECONDS);
+            }
+        }
+    }
+
+    @Override
+    public void dispose() {
+        logger.debug("Disposing the Go-eCharger handler.");
+
+        final ScheduledFuture<?> refreshJob = this.refreshJob;
+        if (refreshJob != null && !refreshJob.isCancelled()) {
+            refreshJob.cancel(true);
+            this.refreshJob = null;
+        }
+    }
+}
index 7ab496b35be0a4eaab6268b00368032ef28a68f2..71a7f1aa944e4548a59f6b7a845bae193f78cd22 100644 (file)
  */
 package org.openhab.binding.goecharger.internal.handler;
 
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.ACCESS_CONFIGURATION;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.ALLOW_CHARGING;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.CABLE_ENCODING;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.CURRENT_L1;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.CURRENT_L2;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.CURRENT_L3;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.ERROR;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.FIRMWARE;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.MAX_CURRENT;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.PHASES;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.POWER_L1;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.POWER_L2;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.POWER_L3;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.PWM_SIGNAL;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.SESSION_CHARGE_CONSUMPTION;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.SESSION_CHARGE_CONSUMPTION_LIMIT;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.TEMPERATURE;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.TOTAL_CONSUMPTION;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.VOLTAGE_L1;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.VOLTAGE_L2;
-import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.VOLTAGE_L3;
+import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.*;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
 
 import javax.measure.quantity.ElectricCurrent;
 import javax.measure.quantity.Energy;
 
-import org.apache.commons.lang3.StringUtils;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.http.HttpMethod;
 import org.openhab.binding.goecharger.internal.GoEChargerBindingConstants;
-import org.openhab.binding.goecharger.internal.GoEChargerConfiguration;
+import org.openhab.binding.goecharger.internal.api.GoEStatusResponseBaseDTO;
 import org.openhab.binding.goecharger.internal.api.GoEStatusResponseDTO;
+import org.openhab.binding.goecharger.internal.api.GoEStatusResponseV2DTO;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.library.types.OnOffType;
 import org.openhab.core.library.types.QuantityType;
@@ -64,7 +40,6 @@ import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
-import org.openhab.core.thing.binding.BaseThingHandler;
 import org.openhab.core.types.Command;
 import org.openhab.core.types.RefreshType;
 import org.openhab.core.types.State;
@@ -72,7 +47,6 @@ import org.openhab.core.types.UnDefType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.gson.Gson;
 import com.google.gson.JsonSyntaxException;
 
 /**
@@ -80,34 +54,31 @@ import com.google.gson.JsonSyntaxException;
  * sent to one of the channels.
  *
  * @author Samuel Brucksch - Initial contribution
+ * @author Reinhard Plaim - Adapt to use API version 2
  */
 @NonNullByDefault
-public class GoEChargerHandler extends BaseThingHandler {
+public class GoEChargerHandler extends GoEChargerBaseHandler {
 
     private final Logger logger = LoggerFactory.getLogger(GoEChargerHandler.class);
 
-    private @Nullable GoEChargerConfiguration config;
-
-    private List<String> allChannels = new ArrayList<>();
-
-    private final Gson gson = new Gson();
-
-    private @Nullable ScheduledFuture<?> refreshJob;
-
-    private final HttpClient httpClient;
-
     public GoEChargerHandler(Thing thing, HttpClient httpClient) {
-        super(thing);
-        this.httpClient = httpClient;
+        super(thing, httpClient);
     }
 
-    private State getValue(String channelId, GoEStatusResponseDTO goeResponse) {
+    @Override
+    protected State getValue(String channelId, GoEStatusResponseBaseDTO goeResponseBase) {
+        var state = super.getValue(channelId, goeResponseBase);
+        if (state != UnDefType.UNDEF) {
+            return state;
+        }
+
+        var goeResponse = (GoEStatusResponseDTO) goeResponseBase;
         switch (channelId) {
-            case MAX_CURRENT:
-                if (goeResponse.maxCurrent == null) {
+            case MAX_CURRENT_TEMPORARY:
+                if (goeResponse.maxCurrentTemporary == null) {
                     return UnDefType.UNDEF;
                 }
-                return new QuantityType<>(goeResponse.maxCurrent, Units.AMPERE);
+                return new QuantityType<>(goeResponse.maxCurrentTemporary, Units.AMPERE);
             case PWM_SIGNAL:
                 if (goeResponse.pwmSignal == null) {
                     return UnDefType.UNDEF;
@@ -178,11 +149,6 @@ public class GoEChargerHandler extends BaseThingHandler {
                     return UnDefType.UNDEF;
                 }
                 return goeResponse.allowCharging == 1 ? OnOffType.ON : OnOffType.OFF;
-            case CABLE_ENCODING:
-                if (goeResponse.cableEncoding == null) {
-                    return UnDefType.UNDEF;
-                }
-                return new QuantityType<>(goeResponse.cableEncoding, Units.AMPERE);
             case PHASES:
                 if (goeResponse.energy == null) {
                     return UnDefType.UNDEF;
@@ -198,7 +164,7 @@ public class GoEChargerHandler extends BaseThingHandler {
                     count++;
                 }
                 return new DecimalType(count);
-            case TEMPERATURE:
+            case TEMPERATURE_CIRCUIT_BOARD:
                 if (goeResponse.temperature == null) {
                     return UnDefType.UNDEF;
                 }
@@ -220,26 +186,6 @@ public class GoEChargerHandler extends BaseThingHandler {
                     return UnDefType.UNDEF;
                 }
                 return new QuantityType<>((Double) (goeResponse.totalChargeConsumption / 10d), Units.KILOWATT_HOUR);
-            case FIRMWARE:
-                if (goeResponse.firmware == null) {
-                    return UnDefType.UNDEF;
-                }
-                return new StringType(goeResponse.firmware);
-            case VOLTAGE_L1:
-                if (goeResponse.energy == null) {
-                    return UnDefType.UNDEF;
-                }
-                return new QuantityType<>(goeResponse.energy[0], Units.VOLT);
-            case VOLTAGE_L2:
-                if (goeResponse.energy == null) {
-                    return UnDefType.UNDEF;
-                }
-                return new QuantityType<>(goeResponse.energy[1], Units.VOLT);
-            case VOLTAGE_L3:
-                if (goeResponse.energy == null) {
-                    return UnDefType.UNDEF;
-                }
-                return new QuantityType<>(goeResponse.energy[2], Units.VOLT);
             case CURRENT_L1:
                 if (goeResponse.energy == null) {
                     return UnDefType.UNDEF;
@@ -272,6 +218,11 @@ public class GoEChargerHandler extends BaseThingHandler {
                     return UnDefType.UNDEF;
                 }
                 return new QuantityType<>(goeResponse.energy[9] * 100, Units.WATT);
+            case POWER_ALL:
+                if (goeResponseBase.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponseBase.energy[11] * 10, Units.WATT);
         }
         return UnDefType.UNDEF;
     }
@@ -286,6 +237,7 @@ public class GoEChargerHandler extends BaseThingHandler {
 
         String key = null;
         String value = null;
+
         switch (channelUID.getId()) {
             case MAX_CURRENT:
                 key = "amp";
@@ -295,13 +247,22 @@ public class GoEChargerHandler extends BaseThingHandler {
                     value = String.valueOf(((QuantityType<ElectricCurrent>) command).toUnit(Units.AMPERE).intValue());
                 }
                 break;
+            case MAX_CURRENT_TEMPORARY:
+                key = "amx";
+                if (command instanceof DecimalType) {
+                    value = String.valueOf(((DecimalType) command).intValue());
+                } else if (command instanceof QuantityType<?>) {
+                    value = String.valueOf(((QuantityType<ElectricCurrent>) command).toUnit(Units.AMPERE).intValue());
+                }
+                break;
             case SESSION_CHARGE_CONSUMPTION_LIMIT:
                 key = "dwo";
+                var multiplier = 10;
                 if (command instanceof DecimalType) {
-                    value = String.valueOf(((DecimalType) command).intValue() * 10);
+                    value = String.valueOf(((DecimalType) command).intValue() * multiplier);
                 } else if (command instanceof QuantityType<?>) {
-                    value = String
-                            .valueOf(((QuantityType<Energy>) command).toUnit(Units.KILOWATT_HOUR).intValue() * 10);
+                    value = String.valueOf(
+                            ((QuantityType<Energy>) command).toUnit(Units.KILOWATT_HOUR).intValue() * multiplier);
                 }
                 break;
             case ALLOW_CHARGING:
@@ -330,8 +291,8 @@ public class GoEChargerHandler extends BaseThingHandler {
                     }
                 }
                 break;
-            default:
         }
+
         if (key != null && value != null) {
             sendData(key, value);
         } else {
@@ -341,96 +302,82 @@ public class GoEChargerHandler extends BaseThingHandler {
 
     @Override
     public void initialize() {
-        config = getConfigAs(GoEChargerConfiguration.class);
-        allChannels = getThing().getChannels().stream().map(channel -> channel.getUID().getId())
-                .collect(Collectors.toList());
-
-        updateStatus(ThingStatus.UNKNOWN);
+        super.initialize();
+    }
 
-        startAutomaticRefresh();
-        logger.debug("Finished initializing!");
+    private String getReadUrl() {
+        return GoEChargerBindingConstants.API_URL.replace("%IP%", config.ip.toString());
     }
 
-    private String getUrl(String type) {
-        return type.replace("%IP%", StringUtils.trimToEmpty(config.ip));
+    private String getWriteUrl(String key, String value) {
+        return GoEChargerBindingConstants.MQTT_URL.replace("%IP%", config.ip.toString()).replace("%KEY%", key)
+                .replace("%VALUE%", value);
     }
 
     private void sendData(String key, String value) {
-        String urlStr = getUrl(GoEChargerBindingConstants.MQTT_URL).replace("%KEY%", key).replace("%VALUE%", value);
-        logger.debug("POST URL = {}", urlStr);
+        String urlStr = getWriteUrl(key, value);
+        logger.trace("GET URL = {}", urlStr);
 
         try {
-            ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.POST)
+            HttpMethod httpMethod = HttpMethod.GET;
+            ContentResponse contentResponse = httpClient.newRequest(urlStr).method(httpMethod)
                     .timeout(5, TimeUnit.SECONDS).send();
             String response = contentResponse.getContentAsString();
-            logger.debug("POST Response: {}", response);
-            GoEStatusResponseDTO result = gson.fromJson(response, GoEStatusResponseDTO.class);
-            updateChannelsAndStatus(result, null);
-        } catch (InterruptedException | TimeoutException | ExecutionException | JsonSyntaxException e) {
-            updateChannelsAndStatus(null, e.getMessage());
+
+            logger.trace("{} Response: {}", httpMethod.toString(), response);
+
+            var statusCode = contentResponse.getStatus();
+            if (!(statusCode == 200 || statusCode == 204)) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        "@text/unsuccessful.communication-error");
+                logger.debug("Could not send data, Response {}, StatusCode: {}", response, statusCode);
+            }
+        } catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ie.toString());
+            logger.debug("Could not send data: {}, {}", urlStr, ie.toString());
+        } catch (TimeoutException | ExecutionException | JsonSyntaxException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
+            logger.debug("Could not send data: {}, {}", urlStr, e.toString());
         }
     }
 
     /**
-     * Request new data from Go-E charger
+     * Request new data from Go-eCharger
      *
-     * @return the Go-E charger object mapping the JSON response or null in case of
+     * @return the Go-eCharger object mapping the JSON response or null in case of
      *         error
      * @throws ExecutionException
      * @throws TimeoutException
      * @throws InterruptedException
      */
     @Nullable
-    private GoEStatusResponseDTO getGoEData()
+    @Override
+    protected GoEStatusResponseBaseDTO getGoEData()
             throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
-        String urlStr = getUrl(GoEChargerBindingConstants.API_URL);
-        logger.debug("GET URL = {}", urlStr);
+        String urlStr = getReadUrl();
+        logger.trace("GET URL = {}", urlStr);
 
         ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
                 .timeout(5, TimeUnit.SECONDS).send();
 
         String response = contentResponse.getContentAsString();
-        logger.debug("GET Response: {}", response);
-        return gson.fromJson(response, GoEStatusResponseDTO.class);
+        logger.trace("GET Response: {}", response);
+
+        if (config.apiVersion == 1) {
+            return gson.fromJson(response, GoEStatusResponseDTO.class);
+        }
+        return gson.fromJson(response, GoEStatusResponseV2DTO.class);
     }
 
-    private void updateChannelsAndStatus(@Nullable GoEStatusResponseDTO goeResponse, @Nullable String message) {
+    @Override
+    protected void updateChannelsAndStatus(@Nullable GoEStatusResponseBaseDTO goeResponse, @Nullable String message) {
         if (goeResponse == null) {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, message);
             allChannels.forEach(channel -> updateState(channel, UnDefType.UNDEF));
         } else {
             updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE);
-            allChannels.forEach(channel -> updateState(channel, getValue(channel, goeResponse)));
-        }
-    }
-
-    private void refresh() {
-        // Request new GoE data
-        try {
-            GoEStatusResponseDTO goeResponse = getGoEData();
-            updateChannelsAndStatus(goeResponse, null);
-        } catch (InterruptedException | TimeoutException | ExecutionException e) {
-            updateChannelsAndStatus(null, e.getMessage());
-        }
-    }
-
-    private void startAutomaticRefresh() {
-        if (refreshJob == null || refreshJob.isCancelled()) {
-            GoEChargerConfiguration config = getConfigAs(GoEChargerConfiguration.class);
-            int delay = config.refreshInterval.intValue();
-            logger.debug("Running refresh job with delay {} s", delay);
-            refreshJob = scheduler.scheduleWithFixedDelay(this::refresh, 0, delay, TimeUnit.SECONDS);
-        }
-    }
-
-    @Override
-    public void dispose() {
-        logger.debug("Disposing the Go-E Charger handler.");
-
-        final ScheduledFuture<?> refreshJob = this.refreshJob;
-        if (refreshJob != null && !refreshJob.isCancelled()) {
-            refreshJob.cancel(true);
-            this.refreshJob = null;
+            allChannels.forEach(channel -> updateState(channel, getValue(channel, (GoEStatusResponseDTO) goeResponse)));
         }
     }
 }
diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerV2Handler.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerV2Handler.java
new file mode 100644 (file)
index 0000000..3ab9454
--- /dev/null
@@ -0,0 +1,360 @@
+/**
+ * Copyright (c) 2010-2022 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.goecharger.internal.handler;
+
+import static org.openhab.binding.goecharger.internal.GoEChargerBindingConstants.*;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.measure.quantity.ElectricCurrent;
+import javax.measure.quantity.Energy;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.http.HttpMethod;
+import org.openhab.binding.goecharger.internal.GoEChargerBindingConstants;
+import org.openhab.binding.goecharger.internal.api.GoEStatusResponseBaseDTO;
+import org.openhab.binding.goecharger.internal.api.GoEStatusResponseDTO;
+import org.openhab.binding.goecharger.internal.api.GoEStatusResponseV2DTO;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.library.unit.SIUnits;
+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;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * The {@link GoEChargerV2Handler} is responsible for handling commands, which are
+ * sent to one of the channels.
+ *
+ * @author Samuel Brucksch - Initial contribution
+ * @author Reinhard Plaim - Adapt to use API version 2
+ */
+@NonNullByDefault
+public class GoEChargerV2Handler extends GoEChargerBaseHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(GoEChargerV2Handler.class);
+
+    private String filter = "";
+
+    public GoEChargerV2Handler(Thing thing, HttpClient httpClient) {
+        super(thing, httpClient);
+    }
+
+    @Override
+    protected State getValue(String channelId, GoEStatusResponseBaseDTO goeResponseBase) {
+        var state = super.getValue(channelId, goeResponseBase);
+        if (state != UnDefType.UNDEF) {
+            return state;
+        }
+
+        var goeResponse = (GoEStatusResponseV2DTO) goeResponseBase;
+        switch (channelId) {
+            case PHASES:
+                if (goeResponse.phases == null) {
+                    return UnDefType.UNDEF;
+                }
+                var phases = "1";
+                if (goeResponse.phases == 2) {
+                    phases = "3";
+                }
+                return new DecimalType(phases);
+            case PWM_SIGNAL:
+                if (goeResponse.pwmSignal == null) {
+                    return UnDefType.UNDEF;
+                }
+                String pwmSignal = null;
+                switch (goeResponse.pwmSignal) {
+                    case 0:
+                        pwmSignal = "UNKNOWN/ERROR";
+                    case 1:
+                        pwmSignal = "IDLE";
+                        break;
+                    case 2:
+                        pwmSignal = "CHARGING";
+                        break;
+                    case 3:
+                        pwmSignal = "WAITING_FOR_CAR";
+                        break;
+                    case 4:
+                        pwmSignal = "COMPLETE";
+                        break;
+                    case 5:
+                        pwmSignal = "ERROR";
+                    default:
+                }
+                return new StringType(pwmSignal);
+            case ERROR:
+                if (goeResponse.errorCode == null) {
+                    return UnDefType.UNDEF;
+                }
+                String error = null;
+                switch (goeResponse.errorCode) {
+                    case 0:
+                        error = "UNKNOWN/ERROR";
+                    case 1:
+                        error = "IDLE";
+                        break;
+                    case 2:
+                        error = "CHARGING";
+                        break;
+                    case 3:
+                        error = "WAITING_FOR_CAR";
+                        break;
+                    case 4:
+                        error = "COMPLETE";
+                        break;
+                    case 5:
+                        error = "ERROR";
+                    default:
+                }
+                return new StringType(error);
+            case ALLOW_CHARGING:
+                return goeResponse.allowCharging == true ? OnOffType.ON : OnOffType.OFF;
+            case TEMPERATURE_TYPE2_PORT:
+                if (goeResponse.temperatures == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponse.temperatures[0], SIUnits.CELSIUS);
+            case TEMPERATURE_CIRCUIT_BOARD:
+                if (goeResponse.temperatures == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponse.temperatures[1], SIUnits.CELSIUS);
+            case SESSION_CHARGE_CONSUMPTION:
+                if (goeResponse.sessionChargeConsumption == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>((Double) (goeResponse.sessionChargeConsumption / 1000d), Units.KILOWATT_HOUR);
+            case SESSION_CHARGE_CONSUMPTION_LIMIT:
+                if (goeResponse.sessionChargeConsumptionLimit == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>((Double) (goeResponse.sessionChargeConsumptionLimit / 1000d),
+                        Units.KILOWATT_HOUR);
+            case TOTAL_CONSUMPTION:
+                if (goeResponse.totalChargeConsumption == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>((Double) (goeResponse.totalChargeConsumption / 1000d), Units.KILOWATT_HOUR);
+            case CURRENT_L1:
+                if (goeResponse.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>((Double) (goeResponse.energy[4] / 1000d), Units.AMPERE);
+            case CURRENT_L2:
+                if (goeResponse.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>((Double) (goeResponse.energy[5] / 1000d), Units.AMPERE);
+            case CURRENT_L3:
+                if (goeResponse.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>((Double) (goeResponse.energy[6] / 1000d), Units.AMPERE);
+            case POWER_L1:
+                if (goeResponse.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponse.energy[7] * 1000, Units.WATT);
+            case POWER_L2:
+                if (goeResponse.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponse.energy[8] * 1000, Units.WATT);
+            case POWER_L3:
+                if (goeResponse.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponse.energy[9] * 1000, Units.WATT);
+            case POWER_ALL:
+                if (goeResponse.energy == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new QuantityType<>(goeResponse.energy[11] * 1000, Units.WATT);
+            case FORCE_STATE:
+                if (goeResponse.forceState == null) {
+                    return UnDefType.UNDEF;
+                }
+                return new DecimalType(goeResponse.forceState.toString());
+        }
+        return UnDefType.UNDEF;
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+        if (command instanceof RefreshType) {
+            // we can not update single channels and refresh is triggered automatically
+            // anyways
+            return;
+        }
+
+        String key = null;
+        String value = null;
+
+        switch (channelUID.getId()) {
+            case MAX_CURRENT:
+                key = "amp";
+                if (command instanceof DecimalType) {
+                    value = String.valueOf(((DecimalType) command).intValue());
+                } else if (command instanceof QuantityType<?>) {
+                    value = String.valueOf(((QuantityType<ElectricCurrent>) command).toUnit(Units.AMPERE).intValue());
+                }
+                break;
+            case SESSION_CHARGE_CONSUMPTION_LIMIT:
+                key = "dwo";
+                var multiplier = 1000;
+                if (command instanceof DecimalType) {
+                    value = String.valueOf(((DecimalType) command).intValue() * multiplier);
+                } else if (command instanceof QuantityType<?>) {
+                    value = String.valueOf(
+                            ((QuantityType<Energy>) command).toUnit(Units.KILOWATT_HOUR).intValue() * multiplier);
+                }
+                break;
+            case PHASES:
+                key = "psm";
+                if (command instanceof DecimalType) {
+                    var phases = 1;
+                    var help = (DecimalType) command;
+                    if (help.intValue() == 3) {
+                        // set value 2 for 3 phases
+                        phases = 2;
+                    }
+                    value = String.valueOf(phases);
+                }
+                break;
+            case FORCE_STATE:
+                key = "frc";
+                if (command instanceof DecimalType) {
+                    value = String.valueOf(((DecimalType) command).intValue());
+                }
+        }
+
+        if (key != null && value != null) {
+            sendData(key, value);
+        } else {
+            logger.warn("Could not update channel {} with key {} and value {}", channelUID.getId(), key, value);
+        }
+    }
+
+    @Override
+    public void initialize() {
+        // only read needed parameters
+        filter = "?filter=";
+        var declaredFields = GoEStatusResponseV2DTO.class.getDeclaredFields();
+        var declaredFieldsBase = GoEStatusResponseV2DTO.class.getSuperclass().getDeclaredFields();
+
+        for (var field : declaredFields) {
+            filter += field.getAnnotation(SerializedName.class).value() + ",";
+        }
+        for (var field : declaredFieldsBase) {
+            filter += field.getAnnotation(SerializedName.class).value() + ",";
+        }
+        filter = filter.substring(0, filter.length() - 1);
+
+        super.initialize();
+    }
+
+    private String getReadUrl() {
+        return GoEChargerBindingConstants.API_URL_V2.replace("%IP%", config.ip.toString()) + filter;
+    }
+
+    private String getWriteUrl(String key, String value) {
+        return GoEChargerBindingConstants.SET_URL_V2.replace("%IP%", config.ip.toString()).replace("%KEY%", key)
+                .replace("%VALUE%", value);
+    }
+
+    private void sendData(String key, String value) {
+        String urlStr = getWriteUrl(key, value);
+        logger.trace("POST URL = {}", urlStr);
+
+        try {
+            HttpMethod httpMethod = HttpMethod.GET;
+            ContentResponse contentResponse = httpClient.newRequest(urlStr).method(httpMethod)
+                    .timeout(5, TimeUnit.SECONDS).send();
+            String response = contentResponse.getContentAsString();
+
+            logger.trace("{} Response: {}", httpMethod.toString(), response);
+
+            var statusCode = contentResponse.getStatus();
+            if (!(statusCode == 200 || statusCode == 204)) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                        "@text/unsuccessful.communication-error");
+                logger.debug("Could not send data, Response {}, StatusCode: {}", response, statusCode);
+            }
+        } catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ie.toString());
+            logger.debug("Could not send data: {}, {}", urlStr, ie.toString());
+        } catch (TimeoutException | ExecutionException | JsonSyntaxException e) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.toString());
+            logger.debug("Could not send data: {}, {}", urlStr, e.toString());
+        }
+    }
+
+    /**
+     * Request new data from Go-eCharger
+     *
+     * @return the Go-eCharger object mapping the JSON response or null in case of
+     *         error
+     * @throws ExecutionException
+     * @throws TimeoutException
+     * @throws InterruptedException
+     */
+    @Nullable
+    @Override
+    protected GoEStatusResponseBaseDTO getGoEData()
+            throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
+        String urlStr = getReadUrl();
+        logger.trace("GET URL = {}", urlStr);
+
+        ContentResponse contentResponse = httpClient.newRequest(urlStr).method(HttpMethod.GET)
+                .timeout(5, TimeUnit.SECONDS).send();
+
+        String response = contentResponse.getContentAsString();
+        logger.trace("GET Response: {}", response);
+
+        if (config.apiVersion == 1) {
+            return gson.fromJson(response, GoEStatusResponseDTO.class);
+        }
+        return gson.fromJson(response, GoEStatusResponseV2DTO.class);
+    }
+
+    protected void updateChannelsAndStatus(@Nullable GoEStatusResponseBaseDTO goeResponse, @Nullable String message) {
+        if (goeResponse == null) {
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, message);
+            allChannels.forEach(channel -> updateState(channel, UnDefType.UNDEF));
+        } else {
+            updateStatus(ThingStatus.ONLINE);
+            allChannels
+                    .forEach(channel -> updateState(channel, getValue(channel, (GoEStatusResponseV2DTO) goeResponse)));
+        }
+    }
+}
index 01e424a126059d0f0172f520c758e407ee1e1b8f..a5cca35cff7fdcefd352a4bf714578834066eab8 100644 (file)
@@ -12,11 +12,21 @@ thing-type.goecharger.goe.description = Go-eCharger thing that represents the wa
 
 thing-type.config.goecharger.goe.ip.label = IP Address
 thing-type.config.goecharger.goe.ip.description = The IP address of the Go-eCharger
+thing-type.config.goecharger.goe.apiVersion.label = API version
+thing-type.config.goecharger.goe.apiVersion.description = The API version of the Go-eCharger
 thing-type.config.goecharger.goe.refreshInterval.label = Refresh Interval
 thing-type.config.goecharger.goe.refreshInterval.description = Refresh interval for acquiring data from Go-eCharger in seconds
 
 # channel types
 
+channel-type.goecharger.current.label = Maximum Current
+channel-type.goecharger.current.description = Maximum current per phase allowed to use for charging
+channel-type.goecharger.maxCurrTmp.label = Maximum Current Temporary
+channel-type.goecharger.maxCurrTmp.description = Maximum current temporary (not written to EEPROM)
+channel-type.goecharger.phs.label = Phases
+channel-type.goecharger.phs.description = Amount of phases currently used for charging
+channel-type.goecharger.fs.label = Force State
+channel-type.goecharger.fs.description = Force state (Neutral=0, Off=1, On=2)
 channel-type.goecharger.alw.label = Allow Charging
 channel-type.goecharger.alw.description = If true charging is allowed
 channel-type.goecharger.ast.label = Access Configuration
@@ -33,20 +43,16 @@ channel-type.goecharger.cl2.label = Current L2
 channel-type.goecharger.cl2.description = Current on L2
 channel-type.goecharger.cl3.label = Current L3
 channel-type.goecharger.cl3.description = Current on L3
-channel-type.goecharger.current.label = Maximum Current
-channel-type.goecharger.current.description = Maximum current per phase allowed to use for charging
 channel-type.goecharger.err.label = Error Code
 channel-type.goecharger.err.description = Error code of Go-eCharger
-channel-type.goecharger.err.state.option.NONE = None
-channel-type.goecharger.err.state.option.RCCB = RCCB
-channel-type.goecharger.err.state.option.NO_GROUND = No ground
-channel-type.goecharger.err.state.option.INTERNAL = Internal
 channel-type.goecharger.eto.label = Total Charged Energy
 channel-type.goecharger.eto.description = Amount of energy that has been charged since installation
 channel-type.goecharger.fmw.label = Firmware
 channel-type.goecharger.fmw.description = Firmware Version
-channel-type.goecharger.pha.label = Phases
-channel-type.goecharger.pha.description = Amount of phases currently used for charging
+channel-type.goecharger.scl.label = Current Session Charge Energy Limit
+channel-type.goecharger.scl.description = Wallbox stops charging after defined value, deactivate with value 0
+channel-type.goecharger.scs.label = Current Session Charged Energy
+channel-type.goecharger.scs.description = Amount of energy that has been charged in this session
 channel-type.goecharger.pl1.label = Power L1
 channel-type.goecharger.pl1.description = Power on L1
 channel-type.goecharger.pl2.label = Power L2
@@ -55,19 +61,16 @@ channel-type.goecharger.pl3.label = Power L3
 channel-type.goecharger.pl3.description = Power on L3
 channel-type.goecharger.pwm.label = PWM signal status
 channel-type.goecharger.pwm.description = Pulse-width modulation signal status
-channel-type.goecharger.pwm.state.option.READY_NO_CAR = Ready (no car)
-channel-type.goecharger.pwm.state.option.CHARGING = Charging
-channel-type.goecharger.pwm.state.option.WAITING_FOR_CAR = Waiting for car
-channel-type.goecharger.pwm.state.option.CHARGING_DONE_CAR_CONNECTED = Charging done (car connected)
-channel-type.goecharger.scl.label = Current Session Charge Energy Limit
-channel-type.goecharger.scl.description = Wallbox stops charging after defined value, deactivate with value 0
-channel-type.goecharger.scs.label = Current Session Charged Energy
-channel-type.goecharger.scs.description = Amount of energy that has been charged in this session
-channel-type.goecharger.tmp.label = Temperature
-channel-type.goecharger.tmp.description = Temperature of the Go-eCharger
+channel-type.goecharger.tmpT2p.label = Temperature type 2 port
+channel-type.goecharger.tmpT2p.description = Temperature on the type 2 port of the Go-eCharger
+channel-type.goecharger.tmp.label = Temperature circuit board
+channel-type.goecharger.tmp.description = Temperature on the circuit board of the Go-eCharger
 channel-type.goecharger.vl1.label = Voltage L1
 channel-type.goecharger.vl1.description = Voltage on L1
 channel-type.goecharger.vl2.label = Voltage L2
 channel-type.goecharger.vl2.description = Voltage on L2
 channel-type.goecharger.vl3.label = Voltage L3
 channel-type.goecharger.vl3.description = Voltage on L3
+
+# Others
+unsuccessful.communication-error=Request response was unsuccessful
index ca60f326f25bc1d186e49c14415cca17af4dc4d0..3759dec36edcbfd9a1429ce5e39f95ebecabfd7b 100644 (file)
@@ -10,6 +10,7 @@
 
                <channels>
                        <channel id="maxCurrent" typeId="current"/>
+                       <channel id="maxCurrentTemp" typeId="maxCurrTmp"/>
                        <channel id="pwmSignal" typeId="pwm"/>
                        <channel id="error" typeId="err"/>
                        <channel id="voltageL1" typeId="vl1"/>
                        <channel id="powerL1" typeId="pl1"/>
                        <channel id="powerL2" typeId="pl2"/>
                        <channel id="powerL3" typeId="pl3"/>
-                       <channel id="phases" typeId="pha"/>
-                       <channel id="sessionChargeEnergyLimit" typeId="scl"/>
-                       <channel id="sessionChargedEnergy" typeId="scs"/>
-                       <channel id="totalChargedEnergy" typeId="eto"/>
                        <channel id="allowCharging" typeId="alw"/>
                        <channel id="cableCurrent" typeId="cbl"/>
-                       <channel id="temperature" typeId="tmp"/>
                        <channel id="firmware" typeId="fmw"/>
                        <channel id="accessConfiguration" typeId="ast"/>
+                       <channel id="phases" typeId="pha"/>
+                       <channel id="forceState" typeId="fs"/>
+                       <channel id="sessionChargedEnergy" typeId="scs"/>
+                       <channel id="sessionChargeEnergyLimit" typeId="scl"/>
+                       <channel id="totalChargedEnergy" typeId="eto"/>
+                       <channel id="temperatureType2Port" typeId="tmpT2p"/>
+                       <channel id="temperature" typeId="tmp"/>
                </channels>
 
                <config-description>
                                <description>The IP address of the Go-eCharger</description>
                                <context>network-address</context>
                        </parameter>
+                       <parameter name="apiVersion" type="integer" required="false" min="1" max="2">
+                               <label>API version</label>
+                               <description>The API version of the Go-eCharger</description>
+                       </parameter>
                        <parameter name="refreshInterval" type="integer" required="false" unit="s">
                                <label>Refresh Interval</label>
                                <description>Refresh interval for acquiring data from Go-eCharger in seconds</description>
                <description>Maximum current per phase allowed to use for charging</description>
                <state pattern="%d %unit%" readOnly="false"/>
        </channel-type>
+       <channel-type id="maxCurrTmp">
+               <item-type>Number:ElectricCurrent</item-type>
+               <label>Maximum Current Temporary</label>
+               <description>Maximum current temporary (not written to EEPROM)</description>
+               <state pattern="%d %unit%" readOnly="false"/>
+       </channel-type>
        <channel-type id="pwm">
                <item-type>String</item-type>
                <label>PWM signal status</label>
                <item-type>Number</item-type>
                <label>Phases</label>
                <description>Amount of phases currently used for charging</description>
+               <state readOnly="false"/>
+       </channel-type>
+       <channel-type id="fs">
+               <item-type>Number</item-type>
+               <label>Force state</label>
+               <description>Force state (Neutral=0, Off=1, On=2)</description>
                <state readOnly="true"/>
        </channel-type>
        <channel-type id="scl">
                <description>Specifies the max amps that can be charged with that cable</description>
                <state pattern="%d %unit%" readOnly="true"/>
        </channel-type>
+       <channel-type id="tmpT2p">
+               <item-type>Number:Temperature</item-type>
+               <label>Temperature type 2 port</label>
+               <description>Temperature of the Go-eCharger on the type 2 port</description>
+               <state pattern="%d %unit%" readOnly="true"/>
+               <category>Temperature</category>
+       </channel-type>
        <channel-type id="tmp">
                <item-type>Number:Temperature</item-type>
-               <label>Temperature</label>
-               <description>Temperature of the Go-eCharger</description>
+               <label>Temperature circuit board</label>
+               <description>Temperature of the Go-eCharger on circuit board</description>
                <state pattern="%d %unit%" readOnly="true"/>
+               <category>Temperature</category>
        </channel-type>
        <channel-type id="fmw">
                <item-type>String</item-type>