diff --git a/bundles/org.openhab.binding.goecharger/README.md b/bundles/org.openhab.binding.goecharger/README.md index 1a8317e98..22197fae8 100644 --- a/bundles/org.openhab.binding.goecharger/README.md +++ b/bundles/org.openhab.binding.goecharger/README.md @@ -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`. diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerBindingConstants.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerBindingConstants.java index 614c4b514..cadc624f8 100644 --- a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerBindingConstants.java +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerBindingConstants.java @@ -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%"; } diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerConfiguration.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerConfiguration.java index cde62da04..48163b9cd 100644 --- a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerConfiguration.java +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerConfiguration.java @@ -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; } diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerHandlerFactory.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerHandlerFactory.java index 99c553582..83ffbbf6d 100644 --- a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerHandlerFactory.java +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/GoEChargerHandlerFactory.java @@ -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 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 index 000000000..da7bf4930 --- /dev/null +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseBaseDTO.java @@ -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; +} diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseDTO.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseDTO.java index 7688582f0..839e0f52a 100644 --- a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseDTO.java +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseDTO.java @@ -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("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("ast") + public Integer accessConfiguration; + + @SerializedName("alw") + public Integer allowCharging; + @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 index 000000000..37f903cc6 --- /dev/null +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/api/GoEStatusResponseV2DTO.java @@ -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 index 000000000..9711b7767 --- /dev/null +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerBaseHandler.java @@ -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 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; + } + } +} diff --git a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerHandler.java b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerHandler.java index 7ab496b35..71a7f1aa9 100644 --- a/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerHandler.java +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerHandler.java @@ -12,48 +12,24 @@ */ 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 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) 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) 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) command).toUnit(Units.KILOWATT_HOUR).intValue() * 10); + value = String.valueOf( + ((QuantityType) 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); - - startAutomaticRefresh(); - logger.debug("Finished initializing!"); + super.initialize(); } - private String getUrl(String type) { - return type.replace("%IP%", StringUtils.trimToEmpty(config.ip)); + private String getReadUrl() { + return GoEChargerBindingConstants.API_URL.replace("%IP%", config.ip.toString()); + } + + 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 index 000000000..3ab945448 --- /dev/null +++ b/bundles/org.openhab.binding.goecharger/src/main/java/org/openhab/binding/goecharger/internal/handler/GoEChargerV2Handler.java @@ -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) 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) 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))); + } + } +} diff --git a/bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/i18n/goecharger.properties b/bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/i18n/goecharger.properties index 01e424a12..a5cca35cf 100644 --- a/bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/i18n/goecharger.properties +++ b/bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/i18n/goecharger.properties @@ -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 diff --git a/bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/thing/thing-types.xml index ca60f326f..3759dec36 100644 --- a/bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.goecharger/src/main/resources/OH-INF/thing/thing-types.xml @@ -10,6 +10,7 @@ + @@ -21,15 +22,17 @@ - - - - - + + + + + + + @@ -38,6 +41,10 @@ The IP address of the Go-eCharger network-address + + + The API version of the Go-eCharger + Refresh interval for acquiring data from Go-eCharger in seconds @@ -65,6 +72,12 @@ Maximum current per phase allowed to use for charging + + Number:ElectricCurrent + + Maximum current temporary (not written to EEPROM) + + String @@ -149,6 +162,12 @@ Number Amount of phases currently used for charging + + + + Number + + Force state (Neutral=0, Off=1, On=2) @@ -181,11 +200,19 @@ Specifies the max amps that can be charged with that cable + + Number:Temperature + + Temperature of the Go-eCharger on the type 2 port + + Temperature + Number:Temperature - - Temperature of the Go-eCharger + + Temperature of the Go-eCharger on circuit board + Temperature String