[goecharger] Add cloud API support (#18781)

* [goecharger] add go-e charger cloud api support (#18770)

Signed-off-by: Stefan Fussenegger <stefan.fussenegger+git@gmail.com>
main
Stefan Fussenegger 2025-06-16 21:37:01 +02:00 committed by GitHub
parent 3e24c2c978
commit 75b5db9928
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1513 additions and 37 deletions

View File

@ -15,15 +15,19 @@ Please note that v1 is the default, but more functions (channels) are supported
## Thing Configuration
The thing has three configuration parameters:
The thing has these configuration parameters:
| Parameter | Description | Required |
|-----------------|-----------------------------------------------|----------|
| ip | The IP-address of your go-e Charger | yes |
| apiVersion | The API version to use (1=default or 2) | no |
| refreshInterval | Interval to read data, default 5 (in seconds) | no |
| Parameter | Description | Required |
|-----------------|------------------------------------------------|----------|
| ip | The IP-address of your go-e Charger | yes* |
| serial | The serial number of the Go-eCharger | yes* |
| token | The access token for the Go-eCharger Cloud API | 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-e Charger with new hardware revision (CM-03, GM-10 and potentially others), which can be recognized with the serial number on the back of the device.
*) Configure ip for the Local API or serial and token for Cloud API. If both are configured the local API will be used.
The apiVersion 2 is only available for go-e Charger with new hardware revision (CM-03, GM-10 and potentially others), which can be recognized with the serial number on the back of the device. It's also mandatory for use with the Go-eCharger Cloud API.
## Channels

View File

@ -65,4 +65,7 @@ public class GoEChargerBindingConstants {
public static final String API_URL_V2 = "http://%IP%/api/status";
public static final String SET_URL_V2 = "http://%IP%/api/set?%KEY%=%VALUE%";
public static final String API_URL_CLOUD_V2 = "https://%SERIAL%.api.v3.go-e.io/api/status?token=%TOKEN%";
public static final String SET_URL_CLOUD_V2 = "https://%SERIAL%.api.v3.go-e.io/api/set?token=%TOKEN%&%KEY%=%VALUE%";
}

View File

@ -25,6 +25,8 @@ import org.eclipse.jdt.annotation.Nullable;
public class GoEChargerConfiguration {
public @Nullable String ip;
public @Nullable String serial;
public @Nullable String token;
public Integer refreshInterval = 5;
public Integer apiVersion = 1;
}

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.goecharger.internal.api;
import static org.openhab.binding.goecharger.internal.api.GoEStatusV2ApiKeys.*;
import com.google.gson.annotations.SerializedName;
/**
@ -21,21 +23,21 @@ import com.google.gson.annotations.SerializedName;
* @author Reinhard Plaim - Initial contribution
*/
public class GoEStatusResponseBaseDTO {
@SerializedName("car")
@SerializedName(CAR)
public Integer pwmSignal;
@SerializedName("amp")
@SerializedName(AMP)
public Integer maxCurrent;
@SerializedName("err")
@SerializedName(ERR)
public Integer errorCode;
@SerializedName("cbl")
@SerializedName(CBL)
public Integer cableEncoding;
@SerializedName("eto")
@SerializedName(ETO)
public Long totalChargeConsumption;
@SerializedName("fwv")
@SerializedName(FWV)
public String firmware;
}

View File

@ -12,6 +12,8 @@
*/
package org.openhab.binding.goecharger.internal.api;
import static org.openhab.binding.goecharger.internal.api.GoEStatusV2ApiKeys.*;
import com.google.gson.annotations.SerializedName;
/**
@ -21,33 +23,35 @@ import com.google.gson.annotations.SerializedName;
* @author Reinhard Plaim - Initial contribution
*/
public class GoEStatusResponseV2DTO extends GoEStatusResponseBaseDTO {
@Deprecated
@SerializedName("mod")
public String version;
@SerializedName("psm")
@SerializedName(PSM)
public Integer phases;
@SerializedName("trx")
@SerializedName(TRX)
public Integer transaction;
@SerializedName("alw")
@SerializedName(ALW)
public Boolean allowCharging;
@SerializedName("tma")
@SerializedName(TMA)
public Double[] temperatures;
@SerializedName("wh")
@SerializedName(WH)
public Double sessionChargeConsumption;
@SerializedName("dwo")
@SerializedName(DWO)
public Double sessionChargeConsumptionLimit;
@SerializedName("frc")
@SerializedName(FRC)
public Integer forceState;
@SerializedName("nrg")
@SerializedName(NRG)
public Double[] energy;
@SerializedName("awp")
@SerializedName(AWP)
public Double awattarMaxPrice;
}

View File

@ -33,6 +33,7 @@ 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.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
@ -110,8 +111,8 @@ public abstract class GoEChargerBaseHandler extends BaseThingHandler {
}
@Nullable
protected GoEStatusResponseBaseDTO getGoEData()
throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
protected GoEStatusResponseBaseDTO getGoEData() throws InterruptedException, TimeoutException, ExecutionException,
JsonSyntaxException, IllegalArgumentException {
return null;
}
@ -129,6 +130,9 @@ public abstract class GoEChargerBaseHandler extends BaseThingHandler {
updateChannelsAndStatus(null, ie.getMessage());
} catch (TimeoutException | ExecutionException | JsonSyntaxException e) {
updateChannelsAndStatus(null, e.getMessage());
} catch (IllegalArgumentException e) {
logger.debug("Invalid configuration getting data: {}", e.toString());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
}
}
}

View File

@ -366,8 +366,8 @@ public class GoEChargerHandler extends GoEChargerBaseHandler {
*/
@Nullable
@Override
protected GoEStatusResponseBaseDTO getGoEData()
throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
protected GoEStatusResponseBaseDTO getGoEData() throws InterruptedException, TimeoutException, ExecutionException,
JsonSyntaxException, IllegalArgumentException {
String urlStr = getReadUrl();
logger.trace("GET URL = {}", urlStr);

View File

@ -148,6 +148,9 @@ public class GoEChargerV2Handler extends GoEChargerBaseHandler {
}
return new DecimalType(goeResponse.awattarMaxPrice);
case ALLOW_CHARGING:
if (goeResponse.allowCharging == null) {
return UnDefType.UNDEF;
}
return OnOffType.from(goeResponse.allowCharging);
case TEMPERATURE_TYPE2_PORT:
// It was reported that the temperature is invalid when only one value is returned
@ -306,7 +309,7 @@ public class GoEChargerV2Handler extends GoEChargerBaseHandler {
@Override
public void initialize() {
// only read needed parameters
filter = "?filter=";
filter = "filter=";
var declaredFields = GoEStatusResponseV2DTO.class.getDeclaredFields();
var declaredFieldsBase = GoEStatusResponseV2DTO.class.getSuperclass().getDeclaredFields();
@ -321,21 +324,43 @@ public class GoEChargerV2Handler extends GoEChargerBaseHandler {
super.initialize();
}
private String getReadUrl() {
return GoEChargerBindingConstants.API_URL_V2.replace("%IP%", config.ip.toString()) + filter;
private String getReadUrl() throws IllegalArgumentException {
if (config.ip != null) {
return GoEChargerBindingConstants.API_URL_V2.replace("%IP%", config.ip.toString()) + "?" + filter;
} else if (config.serial != null && config.token != null) {
return GoEChargerBindingConstants.API_URL_CLOUD_V2.replace("%SERIAL%", config.serial.toString())
.replace("%TOKEN%", config.token.toString()) + "&" + filter;
} else {
throw new IllegalArgumentException("either ip or token+serial must be configured");
}
}
private String getWriteUrl(String key, String value) {
return GoEChargerBindingConstants.SET_URL_V2.replace("%IP%", config.ip.toString()).replace("%KEY%", key)
.replace("%VALUE%", value);
private String getWriteUrl(String key, String value) throws IllegalArgumentException {
if (config.ip != null) {
return GoEChargerBindingConstants.SET_URL_V2.replace("%IP%", config.ip.toString()).replace("%KEY%", key)
.replace("%VALUE%", value);
} else if (config.serial != null && config.token != null) {
return GoEChargerBindingConstants.SET_URL_CLOUD_V2.replace("%SERIAL%", config.serial.toString())
.replace("%TOKEN%", config.token.toString()).replace("%KEY%", key).replace("%VALUE%", value);
} else {
throw new IllegalArgumentException("either ip or token+serial must be configured");
}
}
private void sendData(String key, String value) {
String urlStr = getWriteUrl(key, value);
logger.trace("POST URL = {}", urlStr);
String urlStr;
try {
urlStr = getWriteUrl(key, value);
} catch (IllegalArgumentException e) {
logger.debug("Invalid configuration writing data: {}", e.toString());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, e.getMessage());
return;
}
HttpMethod httpMethod = HttpMethod.GET;
logger.trace("{} URL = {}", httpMethod, urlStr);
try {
HttpMethod httpMethod = HttpMethod.GET;
ContentResponse contentResponse = httpClient.newRequest(urlStr).method(httpMethod)
.timeout(5, TimeUnit.SECONDS).send();
String response = contentResponse.getContentAsString();
@ -343,7 +368,7 @@ public class GoEChargerV2Handler extends GoEChargerBaseHandler {
logger.trace("{} Response: {}", httpMethod.toString(), response);
var statusCode = contentResponse.getStatus();
if (!(statusCode == 200 || statusCode == 204)) {
if (!(statusCode == 200 || statusCode == 202 || statusCode == 204)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"@text/unsuccessful.communication-error");
logger.debug("Could not send data, Response {}, StatusCode: {}", response, statusCode);

View File

@ -43,11 +43,19 @@
</properties>
<config-description>
<parameter name="ip" type="text" required="true">
<parameter name="ip" type="text" required="false">
<label>IP Address</label>
<description>The IP address of the Go-eCharger</description>
<context>network-address</context>
</parameter>
<parameter name="serial" type="text" required="false">
<label>Serial Number</label>
<description>The serial number of the Go-eCharger</description>
</parameter>
<parameter name="token" type="text" required="false">
<label>Cloud Access Token</label>
<description>The access token for the Go-eCharger Cloud API</description>
</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>