mirror of
https://github.com/danieldemus/openhab-addons
synced 2026-07-29 12:34:21 +02:00
[evcc] Add channels for grid currents, grid energy and charging currents. (#19228)
* Add channels for grid currents, grid energy and charging currents. Signed-off-by: Marcel Goerentz <57457529+marcelGoerentz@users.noreply.github.com>
This commit is contained in:
@@ -55,20 +55,20 @@ These channels are dynamically added to the Thing during their initialization; t
|
||||
### `demo.things` Example
|
||||
|
||||
```java
|
||||
Bridge evcc:server:demo-server "Demo" [scheme="https", url="demo.evcc.io", port=80, refreshInterval=30] {
|
||||
Bridge evcc:server:demo-server "Demo" [scheme="https", host="demo.evcc.io", port=443, refreshInterval=30] {
|
||||
// This thing will only exist once per evcc instance
|
||||
Thing site demo-site "Site - evcc Demo"
|
||||
// You can define as many Battery things as you have batteries configured in your evcc instance
|
||||
Thing battery demo-battery1 "Battery - evcc Demo Battery 1"
|
||||
Thing battery demo-battery1 "Battery - evcc Demo Battery 1"[index=0]
|
||||
..
|
||||
// You can define as many PV things as you have photovoltaics configured in your evcc instance
|
||||
Thing pv demo-pv1 "PV - evcc Demo Photovoltaic 1"
|
||||
Thing pv demo-pv1 "PV - evcc Demo Photovoltaic 1"[index=0]
|
||||
..
|
||||
// You can define as many Loadpoint things as you have loadpoints configured in your evcc instance
|
||||
Thing loadpoint demo-loadpoint-carport "Loadpoint - evcc Demo Loadpoint 1"
|
||||
Thing loadpoint demo-loadpoint-carport "Loadpoint - evcc Demo Loadpoint 1"[index=0]
|
||||
..
|
||||
// You can define as many Vehicle things as you have vehicles configured in your evcc instance
|
||||
Thing vehicle demo-vehicle1 "Vehicle - evcc Demo Vehicle 1"
|
||||
Thing vehicle demo-vehicle1 "Vehicle - evcc Demo Vehicle 1"[id="vehicle_1"]
|
||||
..
|
||||
}
|
||||
```
|
||||
|
||||
+25
-6
@@ -59,12 +59,31 @@ public class EvccBindingConstants {
|
||||
public static final String PROPERTY_TYPE_STATISTICS = "statistics";
|
||||
public static final String PROPERTY_TYPE_VEHICLE = "vehicle";
|
||||
|
||||
public static final String JSON_MEMBER_BATTERY = "battery";
|
||||
public static final String JSON_MEMBER_LOADPOINTS = "loadpoints";
|
||||
public static final String JSON_MEMBER_PV = "pv";
|
||||
public static final String JSON_MEMBER_STATISTICS = "statistics";
|
||||
public static final String JSON_MEMBER_VEHICLES = "vehicles";
|
||||
public static final String JSON_MEMBER_CHARGER_FEATURE_HEATING = "chargerFeatureHeating";
|
||||
public static final String JSON_KEY_BATTERY = "battery";
|
||||
public static final String JSON_KEY_CHARGE_CURRENT = "chargeCurrent";
|
||||
public static final String JSON_KEY_CHARGE_CURRENTS = "chargeCurrents";
|
||||
public static final String JSON_KEY_CHARGE_VOLTAGES = "chargeVoltages";
|
||||
public static final String JSON_KEY_CHARGER_FEATURE_HEATING = "chargerFeatureHeating";
|
||||
public static final String JSON_KEY_CHARGING = "charging";
|
||||
public static final String JSON_KEY_CONNECTED = "connected";
|
||||
public static final String JSON_KEY_EFFECTIVE_LIMIT_SOC = "effectiveLimitSoc";
|
||||
public static final String JSON_KEY_EFFECTIVE_PLAN_SOC = "effectivePlanSoc";
|
||||
public static final String JSON_KEY_ENABLED = "enabled";
|
||||
public static final String JSON_KEY_GRID = "grid";
|
||||
public static final String JSON_KEY_GRID_CONFIGURED = "gridConfigured";
|
||||
public static final String JSON_KEY_LIMIT_SOC = "limitSoc";
|
||||
public static final String JSON_KEY_LOADPOINTS = "loadpoints";
|
||||
public static final String JSON_KEY_OFFERED_CURRENT = "offeredCurrent";
|
||||
public static final String JSON_KEY_PHASES = "phases";
|
||||
public static final String JSON_KEY_PHASES_CONFIGURED = "phasesConfigured";
|
||||
public static final String JSON_KEY_PV = "pv";
|
||||
public static final String JSON_KEY_SMART_COST_TYPE = "smartCostType";
|
||||
public static final String JSON_KEY_STATISTICS = "statistics";
|
||||
public static final String JSON_KEY_TITLE = "title";
|
||||
public static final String JSON_KEY_VEHICLE_LIMIT_SOC = "vehicleLimitSoc";
|
||||
public static final String JSON_KEY_VEHICLE_PRESENT = "vehiclePresent";
|
||||
public static final String JSON_KEY_VEHICLE_SOC = "vehicleSoc";
|
||||
public static final String JSON_KEY_VEHICLES = "vehicles";
|
||||
|
||||
public static final String NUMBER_CURRENCY = CoreItemFactory.NUMBER + ":Currency";
|
||||
public static final String NUMBER_DIMENSIONLESS = CoreItemFactory.NUMBER + ":Dimensionless";
|
||||
|
||||
+2
-2
@@ -15,13 +15,13 @@ package org.openhab.binding.evcc.internal;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link EvccConfiguration} class contains fields mapping thing configuration parameters.
|
||||
* The {@link EvccBridgeConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Florian Hotze - Initial contribution
|
||||
* @author Marcel Goerentz - Rework the binding
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccConfiguration {
|
||||
public class EvccBridgeConfiguration {
|
||||
public String host = "";
|
||||
public int pollInterval = 30;
|
||||
public int port = 7070;
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class EvccDiscoveryService extends AbstractThingHandlerDiscoveryService<E
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Starting evcc Discover");
|
||||
JsonObject state = thingHandler.getCachedEvccState();
|
||||
JsonObject state = thingHandler.getCachedEvccState().deepCopy();
|
||||
if (!state.isEmpty()) {
|
||||
for (EvccDiscoveryMapper mapper : mappers) {
|
||||
mapper.discover(state, thingHandler).forEach(thing -> {
|
||||
|
||||
+5
-5
@@ -41,21 +41,21 @@ public class BatteryDiscoveryMapper implements EvccDiscoveryMapper {
|
||||
@Override
|
||||
public Collection<DiscoveryResult> discover(JsonObject state, EvccBridgeHandler bridgeHandler) {
|
||||
List<DiscoveryResult> results = new ArrayList<>();
|
||||
JsonArray batteries = state.getAsJsonArray(JSON_MEMBER_BATTERY);
|
||||
JsonArray batteries = state.getAsJsonArray(JSON_KEY_BATTERY);
|
||||
if (batteries == null) {
|
||||
return results;
|
||||
}
|
||||
for (int i = 0; i < batteries.size(); i++) {
|
||||
JsonObject battery = batteries.get(i).getAsJsonObject();
|
||||
String title = battery.has("title") ? battery.get("title").getAsString().toLowerCase(Locale.ROOT)
|
||||
: "battery" + i;
|
||||
String title = battery.has(JSON_KEY_TITLE)
|
||||
? battery.get(JSON_KEY_TITLE).getAsString().toLowerCase(Locale.ROOT)
|
||||
: JSON_KEY_BATTERY + i;
|
||||
|
||||
ThingUID uid = new ThingUID(EvccBindingConstants.THING_TYPE_BATTERY, bridgeHandler.getThing().getUID(),
|
||||
Utils.sanitizeName(title));
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(title)
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperty(PROPERTY_INDEX, i)
|
||||
.withProperty(PROPERTY_TYPE, PROPERTY_TYPE_BATTERY).withProperty(PROPERTY_TITLE, title)
|
||||
.withRepresentationProperty(PROPERTY_TITLE).build();
|
||||
.withProperty(PROPERTY_TITLE, title).withRepresentationProperty(PROPERTY_TITLE).build();
|
||||
results.add(result);
|
||||
}
|
||||
return results;
|
||||
|
||||
+4
-6
@@ -43,28 +43,26 @@ public class LoadpointDiscoveryMapper implements EvccDiscoveryMapper {
|
||||
@Override
|
||||
public Collection<DiscoveryResult> discover(JsonObject state, EvccBridgeHandler bridgeHandler) {
|
||||
List<DiscoveryResult> results = new ArrayList<>();
|
||||
JsonArray loadpoints = state.getAsJsonArray(JSON_MEMBER_LOADPOINTS);
|
||||
JsonArray loadpoints = state.getAsJsonArray(JSON_KEY_LOADPOINTS);
|
||||
if (loadpoints == null) {
|
||||
return results;
|
||||
}
|
||||
for (int i = 0; i < loadpoints.size(); i++) {
|
||||
JsonObject lp = loadpoints.get(i).getAsJsonObject();
|
||||
String title = lp.has("title") ? lp.get("title").getAsString().toLowerCase(Locale.ROOT) : "loadpoint" + i;
|
||||
String title = lp.has(JSON_KEY_TITLE) ? lp.get(JSON_KEY_TITLE).getAsString().toLowerCase(Locale.ROOT)
|
||||
: "loadpoint" + i;
|
||||
|
||||
ThingUID uid = new ThingUID("DUMMY:DUMMY:DUMMY");
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
properties.put(PROPERTY_INDEX, i);
|
||||
properties.put(PROPERTY_TITLE, title);
|
||||
|
||||
if (lp.has(JSON_MEMBER_CHARGER_FEATURE_HEATING)
|
||||
&& lp.get(JSON_MEMBER_CHARGER_FEATURE_HEATING).getAsBoolean()) {
|
||||
if (lp.has(JSON_KEY_CHARGER_FEATURE_HEATING) && lp.get(JSON_KEY_CHARGER_FEATURE_HEATING).getAsBoolean()) {
|
||||
uid = new ThingUID(EvccBindingConstants.THING_TYPE_HEATING, bridgeHandler.getThing().getUID(),
|
||||
Utils.sanitizeName(title));
|
||||
properties.put(PROPERTY_TYPE, PROPERTY_TYPE_HEATING);
|
||||
} else {
|
||||
uid = new ThingUID(EvccBindingConstants.THING_TYPE_LOADPOINT, bridgeHandler.getThing().getUID(),
|
||||
Utils.sanitizeName(title));
|
||||
properties.put(PROPERTY_TYPE, PROPERTY_TYPE_LOADPOINT);
|
||||
}
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(title)
|
||||
|
||||
+4
-4
@@ -41,20 +41,20 @@ public class PvDiscoveryMapper implements EvccDiscoveryMapper {
|
||||
@Override
|
||||
public Collection<DiscoveryResult> discover(JsonObject state, EvccBridgeHandler bridgeHandler) {
|
||||
List<DiscoveryResult> results = new ArrayList<>();
|
||||
JsonArray pvs = state.getAsJsonArray(JSON_MEMBER_PV);
|
||||
JsonArray pvs = state.getAsJsonArray(JSON_KEY_PV);
|
||||
if (pvs == null) {
|
||||
return results;
|
||||
}
|
||||
for (int i = 0; i < pvs.size(); i++) {
|
||||
JsonObject pv = pvs.get(i).getAsJsonObject();
|
||||
String title = pv.has("title") ? pv.get("title").getAsString().toLowerCase(Locale.ROOT) : "pv" + i;
|
||||
String title = pv.has(JSON_KEY_TITLE) ? pv.get(JSON_KEY_TITLE).getAsString().toLowerCase(Locale.ROOT)
|
||||
: JSON_KEY_PV + i;
|
||||
|
||||
ThingUID uid = new ThingUID(EvccBindingConstants.THING_TYPE_PV, bridgeHandler.getThing().getUID(),
|
||||
Utils.sanitizeName(title));
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(title)
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperty(PROPERTY_INDEX, i)
|
||||
.withProperty(PROPERTY_TYPE, PROPERTY_TYPE_PV).withProperty(PROPERTY_TITLE, title)
|
||||
.withRepresentationProperty(PROPERTY_TITLE).build();
|
||||
.withProperty(PROPERTY_TITLE, title).withRepresentationProperty(PROPERTY_TITLE).build();
|
||||
|
||||
results.add(result);
|
||||
}
|
||||
|
||||
+2
-2
@@ -44,8 +44,8 @@ public class SiteDiscoveryMapper implements EvccDiscoveryMapper {
|
||||
String siteTitle = state.get("siteTitle").getAsString();
|
||||
ThingUID uid = new ThingUID(EvccBindingConstants.THING_TYPE_SITE, bridgeHandler.getThing().getUID(), "site");
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(siteTitle)
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperty(PROPERTY_TYPE, PROPERTY_TYPE_SITE)
|
||||
.withProperty(PROPERTY_SITE_TITLE, siteTitle).withRepresentationProperty(PROPERTY_SITE_TITLE).build();
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperty(PROPERTY_SITE_TITLE, siteTitle)
|
||||
.withRepresentationProperty(PROPERTY_SITE_TITLE).build();
|
||||
results.add(result);
|
||||
return results;
|
||||
}
|
||||
|
||||
+2
-2
@@ -38,12 +38,12 @@ public class StatisticsDiscoveryMapper implements EvccDiscoveryMapper {
|
||||
@Override
|
||||
public Collection<DiscoveryResult> discover(JsonObject state, EvccBridgeHandler bridgeHandler) {
|
||||
List<DiscoveryResult> results = new ArrayList<>();
|
||||
JsonObject statistics = state.getAsJsonObject(JSON_MEMBER_STATISTICS);
|
||||
JsonObject statistics = state.getAsJsonObject(JSON_KEY_STATISTICS);
|
||||
if (statistics == null) {
|
||||
return results;
|
||||
}
|
||||
ThingUID uid = new ThingUID(EvccBindingConstants.THING_TYPE_STATISTICS, bridgeHandler.getThing().getUID(),
|
||||
"statistics");
|
||||
JSON_KEY_STATISTICS);
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel("Statistics")
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperty(PROPERTY_TYPE, PROPERTY_TYPE_STATISTICS)
|
||||
.withRepresentationProperty(PROPERTY_TYPE).build();
|
||||
|
||||
+5
-8
@@ -12,10 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.evcc.internal.discovery.mapper;
|
||||
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.JSON_MEMBER_VEHICLES;
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.PROPERTY_ID;
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.PROPERTY_TYPE;
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.PROPERTY_TYPE_VEHICLE;
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -44,20 +41,20 @@ public class VehicleDiscoveryMapper implements EvccDiscoveryMapper {
|
||||
@Override
|
||||
public Collection<DiscoveryResult> discover(JsonObject state, EvccBridgeHandler bridgeHandler) {
|
||||
List<DiscoveryResult> results = new ArrayList<>();
|
||||
JsonObject vehicles = state.getAsJsonObject(JSON_MEMBER_VEHICLES);
|
||||
JsonObject vehicles = state.getAsJsonObject(JSON_KEY_VEHICLES);
|
||||
if (vehicles == null) {
|
||||
return results;
|
||||
}
|
||||
for (Map.Entry<String, JsonElement> entry : vehicles.entrySet()) {
|
||||
JsonObject v = entry.getValue().getAsJsonObject();
|
||||
String id = entry.getKey();
|
||||
String title = v.has("title") ? v.get("title").getAsString() : id;
|
||||
String title = v.has(JSON_KEY_TITLE) ? v.get(JSON_KEY_TITLE).getAsString() : id;
|
||||
|
||||
ThingUID uid = new ThingUID(EvccBindingConstants.THING_TYPE_VEHICLE, bridgeHandler.getThing().getUID(),
|
||||
Utils.sanitizeName(title));
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withLabel(title)
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperty(PROPERTY_TYPE, PROPERTY_TYPE_VEHICLE)
|
||||
.withProperty(PROPERTY_ID, id).withRepresentationProperty(PROPERTY_ID).build();
|
||||
.withBridge(bridgeHandler.getThing().getUID()).withProperty(PROPERTY_ID, id)
|
||||
.withRepresentationProperty(PROPERTY_ID).build();
|
||||
|
||||
results.add(result);
|
||||
}
|
||||
|
||||
+74
-118
@@ -13,6 +13,7 @@
|
||||
package org.openhab.binding.evcc.internal.handler;
|
||||
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
import static org.openhab.core.util.StringUtils.capitalize;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -20,6 +21,9 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -27,8 +31,6 @@ import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
@@ -36,13 +38,6 @@ import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
import org.openhab.core.library.CoreItemFactory;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
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.Units;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@@ -58,11 +53,13 @@ import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.State;
|
||||
import org.osgi.framework.BundleContext;
|
||||
import org.osgi.framework.FrameworkUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
@@ -76,10 +73,13 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EvccBaseThingHandler.class);
|
||||
private final ChannelTypeRegistry channelTypeRegistry;
|
||||
private final Gson gson = new Gson();
|
||||
protected String type = "";
|
||||
protected @Nullable EvccBridgeHandler bridgeHandler;
|
||||
protected boolean isInitialized = false;
|
||||
protected String endpoint = "";
|
||||
protected String smartCostType = "";
|
||||
protected @Nullable StateResolver stateResolver = StateResolver.getInstance();
|
||||
|
||||
public EvccBaseThingHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing);
|
||||
@@ -87,7 +87,7 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
}
|
||||
|
||||
protected void commonInitialize(JsonObject state) {
|
||||
ThingBuilder builder = editThing();
|
||||
List<Channel> newChannels = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<@Nullable String, @Nullable JsonElement> entry : state.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
@@ -102,10 +102,15 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
continue;
|
||||
}
|
||||
|
||||
createChannel(thingKey, builder, value);
|
||||
@Nullable
|
||||
Channel channel = createChannel(thingKey, value);
|
||||
if (null != channel) {
|
||||
newChannels.add(channel);
|
||||
}
|
||||
}
|
||||
|
||||
updateThing(builder.build());
|
||||
newChannels.sort(Comparator.comparing(channel -> channel.getUID().getId()));
|
||||
updateThing(editThing().withChannels(newChannels).build());
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
isInitialized = true;
|
||||
Optional.ofNullable(bridgeHandler).ifPresentOrElse(handler -> handler.register(this),
|
||||
@@ -133,38 +138,48 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
if (command instanceof RefreshType) {
|
||||
String key = Utils.getKeyFromChannelUID(channelUID);
|
||||
Optional.ofNullable(bridgeHandler).ifPresent(handler -> {
|
||||
JsonObject state = getStateFromCachedState(handler.getCachedEvccState());
|
||||
if (!state.isEmpty()) {
|
||||
JsonElement value = state.get(key);
|
||||
ItemTypeUnit typeUnit = getItemType(new ChannelTypeUID(BINDING_ID, channelUID.getId()));
|
||||
if (null != value) {
|
||||
setItemValue(typeUnit, channelUID, value);
|
||||
}
|
||||
JsonObject jsonState = getStateFromCachedState(handler.getCachedEvccState());
|
||||
if (!jsonState.isEmpty()) {
|
||||
JsonElement value = jsonState.get(key);
|
||||
Optional.ofNullable(stateResolver).ifPresent(resolver -> {
|
||||
State state = resolver.resolveState(key, value);
|
||||
if (null != state) {
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void createChannel(String thingKey, ThingBuilder builder, JsonElement value) {
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, thingKey);
|
||||
ItemTypeUnit typeUnit = getItemType(channelTypeUID);
|
||||
String itemType = typeUnit.itemType;
|
||||
private String getItemType(ChannelTypeUID channelTypeUID) {
|
||||
ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID);
|
||||
if (null != channelType) {
|
||||
String itemType = channelType.getItemType();
|
||||
return Objects.requireNonNullElse(itemType, "Unknown");
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Channel createChannel(String thingKey, JsonElement value) {
|
||||
ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, thingKey);
|
||||
String itemType = getItemType(channelTypeUID);
|
||||
if (!"Unknown".equals(itemType)) {
|
||||
String label = getChannelLabel(thingKey);
|
||||
Channel channel = ChannelBuilder.create(new ChannelUID(getThing().getUID(), thingKey)).withLabel(label)
|
||||
.withType(channelTypeUID).withAcceptedItemType(itemType).build();
|
||||
if (getThing().getChannels().stream().noneMatch(c -> c.getUID().equals(channel.getUID()))) {
|
||||
builder.withChannel(channel);
|
||||
return channel;
|
||||
}
|
||||
} else {
|
||||
String valString = Objects.requireNonNullElse(value.toString(), "Null");
|
||||
logUnknownChannelXmlAsync(thingKey, "Hint for type: " + valString);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getChannelLabel(String thingKey) {
|
||||
String returnValue = thingKey;
|
||||
@Nullable
|
||||
String tmp = Optional.ofNullable(bridgeHandler).map(handler -> {
|
||||
String labelKey = "channel-type.evcc." + thingKey + ".label";
|
||||
@@ -173,71 +188,10 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
Locale locale = handler.getLocaleProvider().getLocale();
|
||||
return tp.getText(ctx.getBundle(), labelKey, thingKey, locale);
|
||||
}).orElse(thingKey);
|
||||
if (null != tmp) {
|
||||
returnValue = tmp;
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private ItemTypeUnit getItemType(ChannelTypeUID channelTypeUID) {
|
||||
ChannelType channelType = channelTypeRegistry.getChannelType(channelTypeUID);
|
||||
if (null != channelType) {
|
||||
String itemType = channelType.getItemType();
|
||||
if (null != itemType) {
|
||||
Unit<?> unit = Utils.getUnitFromChannelType(itemType);
|
||||
return new ItemTypeUnit(channelType, unit);
|
||||
}
|
||||
}
|
||||
return new ItemTypeUnit(channelType, Units.ONE);
|
||||
}
|
||||
|
||||
private void setItemValue(ItemTypeUnit itemTypeUnit, ChannelUID channelUID, JsonElement value) {
|
||||
if (value.isJsonNull() || itemTypeUnit.itemType.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
switch (itemTypeUnit.itemType) {
|
||||
case CoreItemFactory.NUMBER:
|
||||
case NUMBER_CURRENCY:
|
||||
case NUMBER_ENERGY_PRICE:
|
||||
updateState(channelUID, new DecimalType(value.getAsDouble()));
|
||||
break;
|
||||
case NUMBER_DIMENSIONLESS:
|
||||
case NUMBER_ELECTRIC_CURRENT:
|
||||
case NUMBER_EMISSION_INTENSITY:
|
||||
case NUMBER_ENERGY:
|
||||
case NUMBER_LENGTH:
|
||||
case NUMBER_POWER:
|
||||
Double finalValue = "%".equals(itemTypeUnit.unitHint) ? value.getAsDouble() / 100 : value.getAsDouble();
|
||||
if (channelUID.getId().contains("capacity") || "km".equals(itemTypeUnit.unitHint)) {
|
||||
updateState(channelUID, new QuantityType<>(finalValue, itemTypeUnit.unit.multiply(1000)));
|
||||
} else if ("Wh".equals(itemTypeUnit.unitHint)) {
|
||||
updateState(channelUID, new QuantityType<>(finalValue, itemTypeUnit.unit.divide(1000)));
|
||||
} else {
|
||||
updateState(channelUID, new QuantityType<>(finalValue, itemTypeUnit.unit));
|
||||
}
|
||||
break;
|
||||
case NUMBER_TIME:
|
||||
updateState(channelUID, QuantityType.valueOf(value.getAsDouble() + " s"));
|
||||
break;
|
||||
case NUMBER_TEMPERATURE:
|
||||
updateState(channelUID, QuantityType.valueOf(value.getAsDouble() + " " + itemTypeUnit.unitHint));
|
||||
break;
|
||||
case CoreItemFactory.DATETIME:
|
||||
updateState(channelUID, new DateTimeType(value.getAsString()));
|
||||
break;
|
||||
case CoreItemFactory.STRING:
|
||||
updateState(channelUID, new StringType(value.getAsString()));
|
||||
break;
|
||||
case CoreItemFactory.SWITCH:
|
||||
updateState(channelUID, value.getAsBoolean() ? OnOffType.ON : OnOffType.OFF);
|
||||
break;
|
||||
default:
|
||||
logUnknownChannelXmlAsync(channelUID.getId(), "Hint for type: " + value.toString());
|
||||
}
|
||||
return null != tmp ? tmp : thingKey;
|
||||
}
|
||||
|
||||
protected String getThingKey(String key) {
|
||||
Map<String, String> props = getThing().getProperties();
|
||||
if ("batteryGridChargeLimit".equals(key) || "smartCostLimit".equals(key)) {
|
||||
if ("co2".equals(smartCostType)) {
|
||||
key += "Co2";
|
||||
@@ -245,17 +199,16 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
key += "Price";
|
||||
}
|
||||
}
|
||||
String type = "heating".equals(props.get("type")) ? "loadpoint" : props.get("type");
|
||||
String type = "heating".equals(this.type) ? "loadpoint" : this.type;
|
||||
return (type + "-" + Utils.sanitizeChannelID(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromEvccState(JsonObject state) {
|
||||
if (!isInitialized || state.isEmpty()) {
|
||||
public void updateStatesFromApiResponse(JsonObject jsonState) {
|
||||
if (!isInitialized || jsonState.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map.Entry<@Nullable String, @Nullable JsonElement> entry : state.entrySet()) {
|
||||
for (Map.Entry<@Nullable String, @Nullable JsonElement> entry : jsonState.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
JsonElement value = entry.getValue();
|
||||
if (null == key || null == value || !value.isJsonPrimitive()) {
|
||||
@@ -267,10 +220,22 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
Channel existingChannel = getThing().getChannel(channelUID.getId());
|
||||
if (existingChannel == null) {
|
||||
ThingBuilder builder = editThing();
|
||||
createChannel(thingKey, builder, value);
|
||||
updateThing(builder.build());
|
||||
List<Channel> channels = new ArrayList<>(getThing().getChannels());
|
||||
builder.withoutChannels(channels);
|
||||
@Nullable
|
||||
Channel newChannel = createChannel(thingKey, value);
|
||||
if (null != newChannel) {
|
||||
channels.add(newChannel);
|
||||
channels.sort(Comparator.comparing(channel -> channel.getUID().getId()));
|
||||
updateThing(builder.withChannels(channels).build());
|
||||
}
|
||||
}
|
||||
setItemValue(getItemType(new ChannelTypeUID(BINDING_ID, channelUID.getId())), channelUID, value);
|
||||
Optional.ofNullable(stateResolver).ifPresent(resolver -> {
|
||||
State state = resolver.resolveState(key, value);
|
||||
if (null != state) {
|
||||
updateState(channelUID, state);
|
||||
}
|
||||
});
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
@@ -285,9 +250,19 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
if (response.getStatus() == 200) {
|
||||
logger.debug("Sending command was successful");
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
logger.debug("Sending command was unsuccessful, got this error:\n {}",
|
||||
response.getContentAsString());
|
||||
@Nullable
|
||||
JsonObject responseJson = gson.fromJson(response.getContentAsString(), JsonObject.class);
|
||||
Optional.ofNullable(responseJson).ifPresent(json -> {
|
||||
if (json.has("error")) {
|
||||
logger.debug("Sending command was unsuccessful, got this error:\n {}",
|
||||
json.get("error").getAsString());
|
||||
updateStatus(getThing().getStatus(), ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
json.get("error").getAsString());
|
||||
} else {
|
||||
updateStatus(getThing().getStatus(), ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
logger.warn("evcc API error: HTTP {}", response.getStatus());
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("evcc bridge couldn't call the API", e);
|
||||
@@ -339,7 +314,7 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
<state readOnly="true"></state>
|
||||
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||
</channel-type>
|
||||
""", key, itemType, Utils.capitalizeWords(key));
|
||||
""", key, itemType, capitalize(key));
|
||||
|
||||
Path filePath = Paths.get(System.getProperty("user.dir"), "evcc", "unknown-channels.xml");
|
||||
|
||||
@@ -366,23 +341,4 @@ public abstract class EvccBaseThingHandler extends BaseThingHandler implements E
|
||||
logger.error("Failed to write unknown channel definition to file", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ItemTypeUnit {
|
||||
private final Unit<?> unit;
|
||||
private final String unitHint;
|
||||
private final String itemType;
|
||||
|
||||
public ItemTypeUnit(@Nullable ChannelType type, @Nullable Unit<?> unit) {
|
||||
if (null == type) {
|
||||
unitHint = "";
|
||||
itemType = "Unknown";
|
||||
} else {
|
||||
String tmp = type.getUnitHint();
|
||||
unitHint = null != tmp ? tmp : "";
|
||||
tmp = type.getItemType();
|
||||
itemType = null != tmp ? tmp : "Unknown";
|
||||
}
|
||||
this.unit = Objects.requireNonNullElse(unit, Units.ONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-10
@@ -14,7 +14,7 @@ package org.openhab.binding.evcc.internal.handler;
|
||||
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@@ -37,35 +37,41 @@ public class EvccBatteryHandler extends EvccBaseThingHandler {
|
||||
|
||||
public EvccBatteryHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing, channelTypeRegistry);
|
||||
Map<String, String> props = thing.getProperties();
|
||||
String indexString = props.getOrDefault(PROPERTY_INDEX, "0");
|
||||
index = Integer.parseInt(indexString);
|
||||
Object index = thing.getConfiguration().get(PROPERTY_INDEX);
|
||||
String indexString;
|
||||
if (index instanceof BigDecimal s) {
|
||||
indexString = s.toString();
|
||||
} else {
|
||||
indexString = thing.getProperties().getOrDefault(PROPERTY_INDEX, "0");
|
||||
}
|
||||
this.index = Integer.parseInt(indexString);
|
||||
type = PROPERTY_TYPE_BATTERY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
Optional.ofNullable(bridgeHandler).ifPresent(handler -> {
|
||||
JsonObject stateOpt = handler.getCachedEvccState();
|
||||
JsonObject stateOpt = handler.getCachedEvccState().deepCopy();
|
||||
if (stateOpt.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject state = stateOpt.getAsJsonArray(JSON_MEMBER_BATTERY).get(index).getAsJsonObject();
|
||||
JsonObject state = stateOpt.getAsJsonArray(JSON_KEY_BATTERY).get(index).getAsJsonObject();
|
||||
commonInitialize(state);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromEvccState(JsonObject state) {
|
||||
state = state.has(JSON_MEMBER_BATTERY) ? state.getAsJsonArray(JSON_MEMBER_BATTERY).get(index).getAsJsonObject()
|
||||
public void prepareApiResponseForChannelStateUpdate(JsonObject state) {
|
||||
state = state.has(JSON_KEY_BATTERY) ? state.getAsJsonArray(JSON_KEY_BATTERY).get(index).getAsJsonObject()
|
||||
: new JsonObject();
|
||||
super.updateFromEvccState(state);
|
||||
updateStatesFromApiResponse(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getStateFromCachedState(JsonObject state) {
|
||||
return state.getAsJsonArray(JSON_MEMBER_BATTERY).get(index).getAsJsonObject();
|
||||
return state.getAsJsonArray(JSON_KEY_BATTERY).get(index).getAsJsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -24,7 +24,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.openhab.binding.evcc.internal.EvccConfiguration;
|
||||
import org.openhab.binding.evcc.internal.EvccBridgeConfiguration;
|
||||
import org.openhab.binding.evcc.internal.discovery.EvccDiscoveryService;
|
||||
import org.openhab.core.i18n.LocaleProvider;
|
||||
import org.openhab.core.i18n.TranslationProvider;
|
||||
@@ -78,7 +78,7 @@ public class EvccBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
EvccConfiguration config = getConfigAs(EvccConfiguration.class);
|
||||
EvccBridgeConfiguration config = getConfigAs(EvccBridgeConfiguration.class);
|
||||
endpoint = config.scheme + "://" + config.host + ":" + config.port + "/api/state";
|
||||
|
||||
startPolling(config.pollInterval);
|
||||
@@ -150,12 +150,12 @@ public class EvccBridgeHandler extends BaseBridgeHandler {
|
||||
private void notifyListeners(JsonObject state) {
|
||||
for (EvccThingLifecycleAware listener : listeners) {
|
||||
try {
|
||||
listener.updateFromEvccState(state);
|
||||
listener.prepareApiResponseForChannelStateUpdate(state);
|
||||
} catch (Exception e) {
|
||||
if (listener instanceof BaseThingHandler handler) {
|
||||
logger.warn("Listener {} couldn't parse evcc state", handler.getThing().getUID(), e);
|
||||
} else {
|
||||
logger.debug("Listener {} is not instance of BaseThingHandlder", listener, e);
|
||||
logger.debug("Listener {} is not instance of BaseThingHandler", listener, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,9 @@ public class EvccBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
public void register(EvccThingLifecycleAware handler) {
|
||||
listeners.addIfAbsent(handler);
|
||||
Optional.of(lastState).ifPresent(handler::updateFromEvccState);
|
||||
if (!lastState.isEmpty()) {
|
||||
handler.prepareApiResponseForChannelStateUpdate(lastState);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister(EvccThingLifecycleAware handler) {
|
||||
|
||||
+10
-8
@@ -12,7 +12,7 @@
|
||||
*/
|
||||
package org.openhab.binding.evcc.internal.handler;
|
||||
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.JSON_MEMBER_LOADPOINTS;
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -38,9 +38,11 @@ public class EvccHeatingHandler extends EvccLoadpointHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(EvccHeatingHandler.class);
|
||||
|
||||
private static final Map<String, String> JSON_KEYS = Map.ofEntries(
|
||||
Map.entry("effectiveLimitTemperature", "effectiveLimitSoc"),
|
||||
Map.entry("effectivePlanTemperature", "effectivePlanSoc"), Map.entry("limitTemperature", "limitSoc"),
|
||||
Map.entry("vehicleLimitTemperature", "vehicleLimitSoc"), Map.entry("vehicleTemperature", "vehicleSoc"));
|
||||
Map.entry("effectiveLimitTemperature", JSON_KEY_EFFECTIVE_LIMIT_SOC),
|
||||
Map.entry("effectivePlanTemperature", JSON_KEY_EFFECTIVE_PLAN_SOC),
|
||||
Map.entry("limitTemperature", JSON_KEY_LIMIT_SOC),
|
||||
Map.entry("vehicleLimitTemperature", JSON_KEY_VEHICLE_LIMIT_SOC),
|
||||
Map.entry("vehicleTemperature", JSON_KEY_VEHICLE_SOC));
|
||||
|
||||
public EvccHeatingHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing, channelTypeRegistry);
|
||||
@@ -69,15 +71,15 @@ public class EvccHeatingHandler extends EvccLoadpointHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromEvccState(JsonObject state) {
|
||||
public void prepareApiResponseForChannelStateUpdate(JsonObject state) {
|
||||
updateJSON(state);
|
||||
super.updateFromEvccState(state);
|
||||
updateStatesFromApiResponse(state);
|
||||
}
|
||||
|
||||
protected void updateJSON(JsonObject state) {
|
||||
JsonObject heatingState = state.getAsJsonArray(JSON_MEMBER_LOADPOINTS).get(index).getAsJsonObject();
|
||||
JsonObject heatingState = state.getAsJsonArray(JSON_KEY_LOADPOINTS).get(index).getAsJsonObject();
|
||||
renameJsonKeys(heatingState); // rename the json keys
|
||||
state.getAsJsonArray(JSON_MEMBER_LOADPOINTS).set(index, heatingState); // Update the keys in the original JSON
|
||||
state.getAsJsonArray(JSON_KEY_LOADPOINTS).set(index, heatingState); // Update the keys in the original JSON
|
||||
}
|
||||
|
||||
private static void renameJsonKeys(JsonObject json) {
|
||||
|
||||
+47
-28
@@ -14,6 +14,7 @@ package org.openhab.binding.evcc.internal.handler;
|
||||
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@@ -29,6 +30,8 @@ import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
@@ -40,14 +43,26 @@ import com.google.gson.JsonObject;
|
||||
public class EvccLoadpointHandler extends EvccBaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(EvccLoadpointHandler.class);
|
||||
|
||||
// JSON keys that need a special treatment, in example for backwards compatibility
|
||||
private static final Map<String, String> JSON_KEYS = Map.ofEntries(
|
||||
Map.entry(JSON_KEY_CHARGE_CURRENT, JSON_KEY_OFFERED_CURRENT),
|
||||
Map.entry(JSON_KEY_VEHICLE_PRESENT, JSON_KEY_CONNECTED),
|
||||
Map.entry(JSON_KEY_PHASES, JSON_KEY_PHASES_CONFIGURED), Map.entry(JSON_KEY_CHARGE_CURRENTS, ""),
|
||||
Map.entry(JSON_KEY_CHARGE_VOLTAGES, ""));
|
||||
protected final int index;
|
||||
private int[] version = {};
|
||||
|
||||
public EvccLoadpointHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing, channelTypeRegistry);
|
||||
Map<String, String> props = thing.getProperties();
|
||||
String indexString = props.getOrDefault(PROPERTY_INDEX, "0");
|
||||
index = Integer.parseInt(indexString);
|
||||
Object index = thing.getConfiguration().get(PROPERTY_INDEX);
|
||||
String indexString;
|
||||
if (index instanceof BigDecimal s) {
|
||||
indexString = s.toString();
|
||||
} else {
|
||||
indexString = thing.getProperties().getOrDefault(PROPERTY_INDEX, "0");
|
||||
}
|
||||
this.index = Integer.parseInt(indexString);
|
||||
type = PROPERTY_TYPE_LOADPOINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -55,7 +70,7 @@ public class EvccLoadpointHandler extends EvccBaseThingHandler {
|
||||
super.initialize();
|
||||
Optional.ofNullable(bridgeHandler).ifPresent(handler -> {
|
||||
endpoint = handler.getBaseURL() + API_PATH_LOADPOINTS + "/" + (index + 1);
|
||||
JsonObject stateOpt = handler.getCachedEvccState();
|
||||
JsonObject stateOpt = handler.getCachedEvccState().deepCopy();
|
||||
if (stateOpt.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
return;
|
||||
@@ -65,7 +80,7 @@ public class EvccLoadpointHandler extends EvccBaseThingHandler {
|
||||
heating.updateJSON(stateOpt.getAsJsonObject());
|
||||
}
|
||||
|
||||
JsonObject state = stateOpt.getAsJsonArray(JSON_MEMBER_LOADPOINTS).get(index).getAsJsonObject();
|
||||
JsonObject state = stateOpt.getAsJsonArray(JSON_KEY_LOADPOINTS).get(index).getAsJsonObject();
|
||||
|
||||
modifyJSON(state);
|
||||
commonInitialize(state);
|
||||
@@ -76,9 +91,9 @@ public class EvccLoadpointHandler extends EvccBaseThingHandler {
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof State) {
|
||||
String datapoint = Utils.getKeyFromChannelUID(channelUID).toLowerCase();
|
||||
// Backwardscompatibility for phasesConfigured
|
||||
if ("configuredPhases".equals(datapoint) && version[0] == 0 && version[1] < 200) {
|
||||
datapoint = "phases";
|
||||
// Correct the datapoint for the API call
|
||||
if ("phasesconfigured".equals(datapoint)) {
|
||||
datapoint = JSON_KEY_PHASES;
|
||||
}
|
||||
// Special Handling for enable and disable endpoints
|
||||
if (datapoint.contains("enable")) {
|
||||
@@ -104,34 +119,38 @@ public class EvccLoadpointHandler extends EvccBaseThingHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromEvccState(JsonObject state) {
|
||||
version = Utils.convertVersionStringToIntArray(state.get("version").getAsString().split(" ")[0]);
|
||||
state = state.getAsJsonArray(JSON_MEMBER_LOADPOINTS).get(index).getAsJsonObject();
|
||||
public void prepareApiResponseForChannelStateUpdate(JsonObject state) {
|
||||
state = state.getAsJsonArray(JSON_KEY_LOADPOINTS).get(index).getAsJsonObject();
|
||||
modifyJSON(state);
|
||||
super.updateFromEvccState(state);
|
||||
updateStatesFromApiResponse(state);
|
||||
}
|
||||
|
||||
private void modifyJSON(JsonObject state) {
|
||||
// This is for backward compatibility with older evcc versions
|
||||
if (state.has("chargeCurrent")) {
|
||||
state.addProperty("offeredCurrent", state.get("chargeCurrent").getAsDouble());
|
||||
state.remove("chargeCurrent");
|
||||
}
|
||||
if (state.has("vehiclePresent")) {
|
||||
state.add("connected", state.get("vehiclePresent"));
|
||||
}
|
||||
if (state.has("enabled")) {
|
||||
state.add("charging", state.get("enabled"));
|
||||
}
|
||||
if (state.has("phases")) {
|
||||
state.add("phasesConfigured", state.get("phases"));
|
||||
JSON_KEYS.forEach((oldKey, newKey) -> {
|
||||
if (state.has(oldKey)) {
|
||||
if (oldKey.equals(JSON_KEY_CHARGE_CURRENTS)) {
|
||||
addMeasurementDatapointToState(state, state.getAsJsonArray(oldKey), "Current");
|
||||
} else if (oldKey.equals(JSON_KEY_CHARGE_VOLTAGES)) {
|
||||
addMeasurementDatapointToState(state, state.getAsJsonArray(oldKey), "Voltage");
|
||||
} else {
|
||||
state.add(newKey, state.get(oldKey));
|
||||
}
|
||||
state.remove(oldKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void addMeasurementDatapointToState(JsonObject state, JsonArray values, String datapoint) {
|
||||
int phase = 1;
|
||||
for (JsonElement value : values) {
|
||||
state.add("charge" + datapoint + "L" + phase, value);
|
||||
phase++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getStateFromCachedState(JsonObject state) {
|
||||
return state.has(JSON_MEMBER_LOADPOINTS)
|
||||
? state.getAsJsonArray(JSON_MEMBER_LOADPOINTS).get(index).getAsJsonObject()
|
||||
return state.has(JSON_KEY_LOADPOINTS) ? state.getAsJsonArray(JSON_KEY_LOADPOINTS).get(index).getAsJsonObject()
|
||||
: new JsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
+16
-10
@@ -14,7 +14,7 @@ package org.openhab.binding.evcc.internal.handler;
|
||||
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@@ -37,35 +37,41 @@ public class EvccPvHandler extends EvccBaseThingHandler {
|
||||
|
||||
public EvccPvHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing, channelTypeRegistry);
|
||||
Map<String, String> props = thing.getProperties();
|
||||
String indexString = props.getOrDefault(PROPERTY_INDEX, "0");
|
||||
index = Integer.parseInt(indexString);
|
||||
Object index = thing.getConfiguration().get(PROPERTY_INDEX);
|
||||
String indexString;
|
||||
if (index instanceof BigDecimal s) {
|
||||
indexString = s.toString();
|
||||
} else {
|
||||
indexString = thing.getProperties().getOrDefault(PROPERTY_INDEX, "0");
|
||||
}
|
||||
this.index = Integer.parseInt(indexString);
|
||||
type = PROPERTY_TYPE_PV;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
Optional.ofNullable(bridgeHandler).ifPresent(handler -> {
|
||||
JsonObject stateOpt = handler.getCachedEvccState();
|
||||
JsonObject stateOpt = handler.getCachedEvccState().deepCopy();
|
||||
if (stateOpt.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject state = stateOpt.getAsJsonArray(JSON_MEMBER_PV).get(index).getAsJsonObject();
|
||||
JsonObject state = stateOpt.getAsJsonArray(JSON_KEY_PV).get(index).getAsJsonObject();
|
||||
commonInitialize(state);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromEvccState(JsonObject state) {
|
||||
state = state.getAsJsonArray(JSON_MEMBER_PV).get(index).getAsJsonObject();
|
||||
super.updateFromEvccState(state);
|
||||
public void prepareApiResponseForChannelStateUpdate(JsonObject state) {
|
||||
state = state.getAsJsonArray(JSON_KEY_PV).get(index).getAsJsonObject();
|
||||
updateStatesFromApiResponse(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getStateFromCachedState(JsonObject state) {
|
||||
return state.has(JSON_MEMBER_PV) ? state.getAsJsonArray(JSON_MEMBER_PV).get(index).getAsJsonObject()
|
||||
return state.has(JSON_KEY_PV) ? state.getAsJsonArray(JSON_KEY_PV).get(index).getAsJsonObject()
|
||||
: new JsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
+33
-10
@@ -12,6 +12,9 @@
|
||||
*/
|
||||
package org.openhab.binding.evcc.internal.handler;
|
||||
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@@ -26,6 +29,8 @@ import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
@@ -40,13 +45,14 @@ public class EvccSiteHandler extends EvccBaseThingHandler {
|
||||
|
||||
public EvccSiteHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing, channelTypeRegistry);
|
||||
type = PROPERTY_TYPE_SITE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof State) {
|
||||
String datapoint = Utils.getKeyFromChannelUID(channelUID).toLowerCase();
|
||||
String value = "";
|
||||
String value;
|
||||
if (command instanceof OnOffType) {
|
||||
value = command == OnOffType.ON ? "true" : "false";
|
||||
} else {
|
||||
@@ -64,11 +70,11 @@ public class EvccSiteHandler extends EvccBaseThingHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromEvccState(JsonObject state) {
|
||||
if (state.has("gridConfigured")) {
|
||||
public void prepareApiResponseForChannelStateUpdate(JsonObject state) {
|
||||
if (state.has(JSON_KEY_GRID_CONFIGURED)) {
|
||||
modifyJSON(state);
|
||||
}
|
||||
super.updateFromEvccState(state);
|
||||
updateStatesFromApiResponse(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,18 +82,18 @@ public class EvccSiteHandler extends EvccBaseThingHandler {
|
||||
super.initialize();
|
||||
Optional.ofNullable(bridgeHandler).ifPresent(handler -> {
|
||||
endpoint = handler.getBaseURL();
|
||||
JsonObject state = handler.getCachedEvccState();
|
||||
JsonObject state = handler.getCachedEvccState().deepCopy();
|
||||
if (state.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the smart cost type
|
||||
if (state.has("smartCostType") && !state.get("smartCostType").isJsonNull()) {
|
||||
smartCostType = state.get("smartCostType").getAsString();
|
||||
if (state.has(JSON_KEY_SMART_COST_TYPE) && !state.get(JSON_KEY_SMART_COST_TYPE).isJsonNull()) {
|
||||
smartCostType = state.get(JSON_KEY_SMART_COST_TYPE).getAsString();
|
||||
}
|
||||
|
||||
if (state.has("gridConfigured")) {
|
||||
if (state.has(JSON_KEY_GRID_CONFIGURED)) {
|
||||
modifyJSON(state);
|
||||
}
|
||||
commonInitialize(state);
|
||||
@@ -95,8 +101,25 @@ public class EvccSiteHandler extends EvccBaseThingHandler {
|
||||
}
|
||||
|
||||
private void modifyJSON(JsonObject state) {
|
||||
state.add("gridPower", state.getAsJsonObject("grid").get("power"));
|
||||
state.remove("gridConfigured");
|
||||
for (Map.Entry<String, JsonElement> entry : state.getAsJsonObject(JSON_KEY_GRID).entrySet()) {
|
||||
if ("currents".equals(entry.getKey())) {
|
||||
addMeasurementDatapointsToState(state, entry.getValue().getAsJsonArray(), "Current");
|
||||
} else if ("voltages".equals(entry.getKey())) {
|
||||
addMeasurementDatapointsToState(state, entry.getValue().getAsJsonArray(), "Voltage");
|
||||
} else {
|
||||
state.add(JSON_KEY_GRID + Utils.capitalizeFirstLetter(entry.getKey()), entry.getValue());
|
||||
}
|
||||
}
|
||||
state.remove(JSON_KEY_GRID);
|
||||
state.remove(JSON_KEY_GRID_CONFIGURED);
|
||||
}
|
||||
|
||||
protected void addMeasurementDatapointsToState(JsonObject state, JsonArray values, String datapoint) {
|
||||
int phase = 1;
|
||||
for (JsonElement value : values) {
|
||||
state.add("grid" + datapoint + "L" + phase, value);
|
||||
phase++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+15
-2
@@ -24,6 +24,7 @@ import org.openhab.core.thing.ChannelGroupUID;
|
||||
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.type.ChannelTypeRegistry;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -43,6 +44,7 @@ public class EvccStatisticsHandler extends EvccBaseThingHandler {
|
||||
|
||||
public EvccStatisticsHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing, channelTypeRegistry);
|
||||
type = PROPERTY_TYPE_STATISTICS;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,6 +54,12 @@ public class EvccStatisticsHandler extends EvccBaseThingHandler {
|
||||
handler.register(this);
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
isInitialized = true;
|
||||
JsonObject stateOpt = handler.getCachedEvccState().deepCopy();
|
||||
if (stateOpt.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
return;
|
||||
}
|
||||
prepareApiResponseForChannelStateUpdate(stateOpt);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -61,8 +69,13 @@ public class EvccStatisticsHandler extends EvccBaseThingHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromEvccState(JsonObject state) {
|
||||
state = state.has(JSON_MEMBER_STATISTICS) ? state.getAsJsonObject(JSON_MEMBER_STATISTICS) : new JsonObject();
|
||||
public void prepareApiResponseForChannelStateUpdate(JsonObject state) {
|
||||
state = state.has(JSON_KEY_STATISTICS) ? state.getAsJsonObject(JSON_KEY_STATISTICS) : new JsonObject();
|
||||
if (!isInitialized || state.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
return;
|
||||
}
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
for (String statisticsKey : state.keySet()) {
|
||||
JsonObject statistic = state.getAsJsonObject(statisticsKey);
|
||||
logger.debug("Extracting statistics for {}", statisticsKey);
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ public interface EvccThingLifecycleAware {
|
||||
*
|
||||
* @param state the responded JSON
|
||||
*/
|
||||
void updateFromEvccState(JsonObject state);
|
||||
void prepareApiResponseForChannelStateUpdate(JsonObject state);
|
||||
|
||||
/**
|
||||
* This method shall return the to the thing corresponding JSON object
|
||||
|
||||
+13
-7
@@ -46,7 +46,13 @@ public class EvccVehicleHandler extends EvccBaseThingHandler {
|
||||
|
||||
public EvccVehicleHandler(Thing thing, ChannelTypeRegistry channelTypeRegistry) {
|
||||
super(thing, channelTypeRegistry);
|
||||
vehicleId = thing.getProperties().get(PROPERTY_ID);
|
||||
Object id = thing.getConfiguration().get(PROPERTY_ID);
|
||||
if (id instanceof String s) {
|
||||
vehicleId = s;
|
||||
} else {
|
||||
vehicleId = thing.getProperties().getOrDefault(PROPERTY_ID, "");
|
||||
}
|
||||
type = PROPERTY_TYPE_VEHICLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,9 +72,9 @@ public class EvccVehicleHandler extends EvccBaseThingHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromEvccState(JsonObject state) {
|
||||
state = state.getAsJsonObject(JSON_MEMBER_VEHICLES).getAsJsonObject(vehicleId);
|
||||
super.updateFromEvccState(state);
|
||||
public void prepareApiResponseForChannelStateUpdate(JsonObject state) {
|
||||
state = state.getAsJsonObject(JSON_KEY_VEHICLES).getAsJsonObject(vehicleId);
|
||||
updateStatesFromApiResponse(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,20 +82,20 @@ public class EvccVehicleHandler extends EvccBaseThingHandler {
|
||||
super.initialize();
|
||||
Optional.ofNullable(bridgeHandler).ifPresent(handler -> {
|
||||
endpoint = handler.getBaseURL() + API_PATH_VEHICLES;
|
||||
JsonObject stateOpt = handler.getCachedEvccState();
|
||||
JsonObject stateOpt = handler.getCachedEvccState().deepCopy();
|
||||
if (stateOpt.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject state = stateOpt.getAsJsonObject(JSON_MEMBER_VEHICLES).getAsJsonObject(vehicleId);
|
||||
JsonObject state = stateOpt.getAsJsonObject(JSON_KEY_VEHICLES).getAsJsonObject(vehicleId);
|
||||
commonInitialize(state);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getStateFromCachedState(JsonObject state) {
|
||||
return state.has(JSON_MEMBER_VEHICLES) ? state.getAsJsonObject(JSON_MEMBER_VEHICLES).getAsJsonObject(vehicleId)
|
||||
return state.has(JSON_KEY_VEHICLES) ? state.getAsJsonObject(JSON_KEY_VEHICLES).getAsJsonObject(vehicleId)
|
||||
: new JsonObject();
|
||||
}
|
||||
}
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
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.MetricPrefix;
|
||||
import org.openhab.core.library.unit.SIUnits;
|
||||
import org.openhab.core.library.unit.Units;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
* The {@link StateResolver} provides a resolver to determine the appropriate State type based on the JSON value and key
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class StateResolver {
|
||||
|
||||
private static final StateResolver INSTANCE = new StateResolver();
|
||||
|
||||
public static StateResolver getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a {@link State} object from a given JSON element based on the provided key.
|
||||
* This method interprets the JSON value heuristically, mapping numeric values to
|
||||
* {@link QuantityType} or {@link DecimalType}, strings to {@link StringType} or {@link DateTimeType},
|
||||
* and booleans to {@link OnOffType}. The key is used to infer the appropriate unit of measurement
|
||||
* or semantic meaning.
|
||||
*
|
||||
* @param key the semantic key associated with the value (e.g. "Odometer", "Timestamp", "Price")
|
||||
* @param value the JSON element to be converted into an openHAB {@link State}
|
||||
* @return the resolved {@link State}, or {@code null} if the value is not a supported primitive
|
||||
*/
|
||||
@Nullable
|
||||
public State resolveState(String key, JsonElement value) {
|
||||
if (value.isJsonNull() || !value.isJsonPrimitive()) {
|
||||
return null;
|
||||
}
|
||||
JsonPrimitive prim = value.getAsJsonPrimitive();
|
||||
if (prim.isNumber()) {
|
||||
String lowerKey = key.toLowerCase();
|
||||
Number raw = prim.getAsNumber();
|
||||
Unit<?> base = determineBaseUnitFromKey(key);
|
||||
if (lowerKey.contains("price") || key.contains("tariff")) {
|
||||
return new DecimalType(raw);
|
||||
} else if (raw.toString().contains(".")) {
|
||||
if ("energy".equals(lowerKey) || "gridenergy".equals(lowerKey) || "chargedenergy".equals(lowerKey)
|
||||
|| "pvenergy".equals(lowerKey) || "limitenergy".equals(lowerKey)
|
||||
|| "chargedkwh".equals(lowerKey) || lowerKey.contains("import")
|
||||
|| lowerKey.contains("capacity")) {
|
||||
return new QuantityType<>(raw, MetricPrefix.KILO(base));
|
||||
}
|
||||
} else if (lowerKey.contains("capacity") || lowerKey.contains("odometer") || lowerKey.contains("range")
|
||||
|| lowerKey.contains("import") || "limitenergy".equals(lowerKey)) {
|
||||
return new QuantityType<>(raw, MetricPrefix.KILO(base));
|
||||
}
|
||||
return new QuantityType<>(raw, base);
|
||||
} else if (prim.isString()) {
|
||||
if (key.contains("Timestamp")) {
|
||||
return new DateTimeType(value.getAsString());
|
||||
} else {
|
||||
return new StringType(value.getAsString());
|
||||
}
|
||||
} else if (prim.isBoolean()) {
|
||||
return value.getAsBoolean() ? OnOffType.ON : OnOffType.OFF;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the most appropriate base {@link Units} for a given key using keyword heuristics.
|
||||
* This method performs a case-insensitive analysis of the key to infer the expected unit
|
||||
* (e.g. "temperature" → °C, "energy" → Wh). It is used to assign meaningful units to numeric values
|
||||
* when constructing {@link QuantityType} instances.
|
||||
*
|
||||
* @param key the semantic key to analyze (e.g. "chargingPower", "batterySoc", "sessionEnergy")
|
||||
* @return the inferred {@link Units}/{@link SIUnits}, or {@link Units#ONE} if no specific unit is matched
|
||||
*/
|
||||
private Unit<?> determineBaseUnitFromKey(String key) {
|
||||
String lower = key.toLowerCase();
|
||||
|
||||
if (lower.contains("soc") || lower.contains("percentage")) {
|
||||
return Units.PERCENT;
|
||||
}
|
||||
if (lower.contains("power") || lower.contains("threshold") || lower.contains("tariffsolar")) {
|
||||
return Units.WATT;
|
||||
}
|
||||
if (lower.contains("energy") || lower.contains("capacity") || lower.contains("import")) {
|
||||
return Units.WATT_HOUR;
|
||||
}
|
||||
if (lower.contains("temp") || lower.contains("temperature") || lower.contains("heating")) {
|
||||
return SIUnits.CELSIUS;
|
||||
}
|
||||
if (lower.contains("voltage")) {
|
||||
return Units.VOLT;
|
||||
}
|
||||
if (lower.contains("current")) {
|
||||
return Units.AMPERE;
|
||||
}
|
||||
if (lower.contains("duration") || lower.contains("time") || lower.contains("delay")
|
||||
|| lower.contains("interval") || lower.contains("remaining") || lower.contains("overrun")
|
||||
|| lower.contains("precondition")) {
|
||||
return Units.SECOND;
|
||||
}
|
||||
if (lower.contains("odometer") || lower.contains("distance") || lower.contains("range")) {
|
||||
return SIUnits.METRE;
|
||||
}
|
||||
if (lower.contains("co2")) {
|
||||
return Units.GRAM_PER_KILOWATT_HOUR;
|
||||
}
|
||||
return Units.ONE;
|
||||
}
|
||||
}
|
||||
+13
-21
@@ -23,7 +23,6 @@ import static org.openhab.binding.evcc.internal.EvccBindingConstants.NUMBER_TIME
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import javax.measure.Unit;
|
||||
|
||||
@@ -55,26 +54,6 @@ public class Utils {
|
||||
return Objects.requireNonNullElse(unit, Units.ONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will capitalize the words of a given string
|
||||
*
|
||||
* @param input string containing hyphenized words
|
||||
* @return A string with spaces instead of hyphens and the first letter of each word is capitalized
|
||||
*/
|
||||
public static String capitalizeWords(String input) {
|
||||
String[] allParts = input.split("-");
|
||||
String[] parts = Arrays.stream(allParts, 1, allParts.length).toArray(String[]::new);
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
|
||||
for (String part : parts) {
|
||||
if (!part.isEmpty()) {
|
||||
joiner.add(Character.toUpperCase(part.charAt(0)) + part.substring(1));
|
||||
}
|
||||
}
|
||||
|
||||
return joiner.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will sanitize a given string for a channel ID
|
||||
*
|
||||
@@ -122,4 +101,17 @@ public class Utils {
|
||||
}
|
||||
return Arrays.stream(input.split("\\.")).mapToInt(Integer::parseInt).toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Capitalizes the first character of a string.
|
||||
*
|
||||
* @param input The input string.
|
||||
* @return The string with the first character in uppercase.
|
||||
*/
|
||||
public static String capitalizeFirstLetter(String input) {
|
||||
if (input.isEmpty()) {
|
||||
return input;
|
||||
}
|
||||
return input.substring(0, 1).toUpperCase() + input.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,14 @@ thing-type.evcc.vehicle.description = A vehicle configured in your evcc instance
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.evcc.battery.index.label = Index
|
||||
thing-type.config.evcc.battery.index.description = The index of the battery in your evcc setup (starting with 0)
|
||||
thing-type.config.evcc.heating.index.label = Index
|
||||
thing-type.config.evcc.heating.index.description = The index of the heating loadpoint in your evcc setup (starting with 0)
|
||||
thing-type.config.evcc.loadpoint.index.label = Index
|
||||
thing-type.config.evcc.loadpoint.index.description = The index of the loadpoint in your evcc setup (starting with 0)
|
||||
thing-type.config.evcc.pv.index.label = Index
|
||||
thing-type.config.evcc.pv.index.description = The index of the photovoltaic system in your evcc setup (starting with 0)
|
||||
thing-type.config.evcc.server.host.label = Hostname or IP
|
||||
thing-type.config.evcc.server.host.description = Address of your evcc instance, for example evcc.local or 192.168.1.2
|
||||
thing-type.config.evcc.server.pollInterval.label = Polling Interval
|
||||
@@ -42,6 +50,8 @@ thing-type.config.evcc.server.scheme.label = Protocol Scheme
|
||||
thing-type.config.evcc.server.scheme.description = The protocol scheme that should be used to connect to your instance
|
||||
thing-type.config.evcc.server.scheme.option.http = HTTP
|
||||
thing-type.config.evcc.server.scheme.option.https = HTTPS
|
||||
thing-type.config.evcc.vehicle.id.label = ID
|
||||
thing-type.config.evcc.vehicle.id.description = The ID of the vehicle in your evcc setup (database ID)
|
||||
|
||||
# channel group types
|
||||
|
||||
@@ -49,7 +59,7 @@ channel-group-type.evcc.statistics-group.label = Statistics Group
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.evcc.avg-co2-type.label = Average C02
|
||||
channel-type.evcc.avg-co2-type.label = Average CO2
|
||||
channel-type.evcc.avg-price-type.label = Average Price
|
||||
channel-type.evcc.battery-capacity.label = Battery Capacity
|
||||
channel-type.evcc.battery-capacity.description = Capacity of this battery
|
||||
@@ -68,6 +78,12 @@ channel-type.evcc.loadpoint-battery-boost.label = Battery Boost
|
||||
channel-type.evcc.loadpoint-battery-boost.description = Controls and indicates whether battery boost is active or not
|
||||
channel-type.evcc.loadpoint-battery-boost.state.option.ON = Activated
|
||||
channel-type.evcc.loadpoint-battery-boost.state.option.OFF = Deactivated
|
||||
channel-type.evcc.loadpoint-charge-current-l1.label = Charge Current 1
|
||||
channel-type.evcc.loadpoint-charge-current-l1.description = Actual charge current of charger phase L1
|
||||
channel-type.evcc.loadpoint-charge-current-l2.label = Charge Current 2
|
||||
channel-type.evcc.loadpoint-charge-current-l2.description = Actual charge current of charger phase L2
|
||||
channel-type.evcc.loadpoint-charge-current-l3.label = Charge Current 3
|
||||
channel-type.evcc.loadpoint-charge-current-l3.description = Actual charge current of charger phase L3
|
||||
channel-type.evcc.loadpoint-charge-duration.label = Charging Duration
|
||||
channel-type.evcc.loadpoint-charge-duration.description = Time passed since start of the charging
|
||||
channel-type.evcc.loadpoint-charge-power.label = Charging Power
|
||||
@@ -78,6 +94,12 @@ channel-type.evcc.loadpoint-charge-remaining-energy.label = Charging Remaining E
|
||||
channel-type.evcc.loadpoint-charge-remaining-energy.description = Remaining energy until limit SoC is reached
|
||||
channel-type.evcc.loadpoint-charge-total-import.label = Charge Total Import
|
||||
channel-type.evcc.loadpoint-charge-total-import.description = Total imported energy
|
||||
channel-type.evcc.loadpoint-charge-voltage-l1.label = Charge Voltage 1
|
||||
channel-type.evcc.loadpoint-charge-voltage-l1.description = Actual charge voltage of charger phase L1
|
||||
channel-type.evcc.loadpoint-charge-voltage-l2.label = Charge Voltage 2
|
||||
channel-type.evcc.loadpoint-charge-voltage-l2.description = Actual charge voltage of charger phase L2
|
||||
channel-type.evcc.loadpoint-charge-voltage-l3.label = Charge Voltage 3
|
||||
channel-type.evcc.loadpoint-charge-voltage-l3.description = Actual charge voltage of charger phase L3
|
||||
channel-type.evcc.loadpoint-charged-energy.label = Charged Energy
|
||||
channel-type.evcc.loadpoint-charged-energy.description = Energy charged since plugged-in
|
||||
channel-type.evcc.loadpoint-charger-feature-heating.label = Heating
|
||||
@@ -139,10 +161,10 @@ channel-type.evcc.loadpoint-enable-delay.label = Enable Delay
|
||||
channel-type.evcc.loadpoint-enable-delay.description = Delay in PV mode before starting the charging when enough power is available
|
||||
channel-type.evcc.loadpoint-enable-threshold.label = Enable Threshold
|
||||
channel-type.evcc.loadpoint-enable-threshold.description = Threshold for the enable delay
|
||||
channel-type.evcc.loadpoint-enabled.label = Loadpoint Enabled
|
||||
channel-type.evcc.loadpoint-enabled.description = Indicates whether the charger is locked or released
|
||||
channel-type.evcc.loadpoint-enabled.state.option.ON = Released
|
||||
channel-type.evcc.loadpoint-enabled.state.option.OFF = Locked
|
||||
channel-type.evcc.loadpoint-enabled.label = Enabled
|
||||
channel-type.evcc.loadpoint-enabled.description = Indicating whether this loadpoint is enabled or not
|
||||
channel-type.evcc.loadpoint-enabled.state.option.ON = Enabled
|
||||
channel-type.evcc.loadpoint-enabled.state.option.OFF = Disabled
|
||||
channel-type.evcc.loadpoint-limit-energy.label = Energy Limit
|
||||
channel-type.evcc.loadpoint-limit-energy.description = Energy limit when charging the vehicle
|
||||
channel-type.evcc.loadpoint-limit-soc.label = SoC Limit
|
||||
@@ -240,7 +262,7 @@ channel-type.evcc.loadpoint-vehicle-range.label = Vehicle Range
|
||||
channel-type.evcc.loadpoint-vehicle-range.description = Current range of the vehicle
|
||||
channel-type.evcc.loadpoint-vehicle-soc.label = Vehicle API SoC
|
||||
channel-type.evcc.loadpoint-vehicle-soc.description = Current state of charge of the vehicle
|
||||
channel-type.evcc.loadpoint-vehicle-temperature.label = Temperature
|
||||
channel-type.evcc.loadpoint-vehicle-temperature.label = Device Temperature
|
||||
channel-type.evcc.loadpoint-vehicle-temperature.description = Current temperature of the heating device
|
||||
channel-type.evcc.loadpoint-vehicle-title.label = Vehicle Title
|
||||
channel-type.evcc.loadpoint-vehicle-title.description = The name of the vehicle as displayed in the UI
|
||||
@@ -304,8 +326,22 @@ channel-type.evcc.site-green-share-home.label = Green Share Home
|
||||
channel-type.evcc.site-green-share-home.description = Indicates whether green power is available for home
|
||||
channel-type.evcc.site-green-share-loadpoints.label = Green Share Loadpoints
|
||||
channel-type.evcc.site-green-share-loadpoints.description = Indicates whether green power is available for loadpoints
|
||||
channel-type.evcc.site-grid-current-l1.label = Grid Current 1
|
||||
channel-type.evcc.site-grid-current-l1.description = Grid phase 1 current
|
||||
channel-type.evcc.site-grid-current-l2.label = Grid Current 2
|
||||
channel-type.evcc.site-grid-current-l2.description = Grid phase 2 current
|
||||
channel-type.evcc.site-grid-current-l3.label = Grid Current 3
|
||||
channel-type.evcc.site-grid-current-l3.description = Grid phase 3 current
|
||||
channel-type.evcc.site-grid-energy.label = Grid Energy
|
||||
channel-type.evcc.site-grid-energy.description = Consumed energy from the grid
|
||||
channel-type.evcc.site-grid-power.label = Grid Power
|
||||
channel-type.evcc.site-grid-power.description = Current power from grid (negative means feed-in)
|
||||
channel-type.evcc.site-grid-voltage-l1.label = Charge Voltage 1
|
||||
channel-type.evcc.site-grid-voltage-l1.description = Actual charge voltage of charger phase L1
|
||||
channel-type.evcc.site-grid-voltage-l2.label = Charge Voltage 2
|
||||
channel-type.evcc.site-grid-voltage-l2.description = Actual charge voltage of charger phase L2
|
||||
channel-type.evcc.site-grid-voltage-l3.label = Charge Voltage 3
|
||||
channel-type.evcc.site-grid-voltage-l3.description = Actual charge voltage of charger phase L3
|
||||
channel-type.evcc.site-home-power.label = Home Power
|
||||
channel-type.evcc.site-home-power.description = Current power consumption by home
|
||||
channel-type.evcc.site-interval.label = Interval
|
||||
@@ -387,8 +423,3 @@ thing-type.config.evcc.server.schema.label = Protocol schema
|
||||
thing-type.config.evcc.server.schema.description = The protocol schema that should be used to connect to your instance
|
||||
thing-type.config.evcc.server.schema.option.http = HTTP
|
||||
thing-type.config.evcc.server.schema.option.https = HTTPS
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.evcc.site-battery-grid-charge-limit.label = Grid Charging Limit
|
||||
channel-type.evcc.site-battery-grid-charge-limit.description = Below this smart cost limit the charging from grid will start (currency or co2)
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
|
||||
<label>Battery</label>
|
||||
<description>A battery configured in your evcc instance</description>
|
||||
<config-description>
|
||||
<parameter name="index" type="integer">
|
||||
<label>Index</label>
|
||||
<description>The index of the battery in your evcc setup (starting with 0)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<channel-type id="battery-capacity">
|
||||
<item-type unitHint="kWh">Number:Energy</item-type>
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
<label>Heating - Loadpoint</label>
|
||||
<description>A heating loadpoint configured in your evcc instance</description>
|
||||
<representation-property>index</representation-property>
|
||||
<config-description>
|
||||
<parameter name="index" type="integer">
|
||||
<label>Index</label>
|
||||
<description>The index of the heating loadpoint in your evcc setup (starting with 0)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<channel-type id="loadpoint-effective-limit-temperature">
|
||||
<item-type unitHint="°C">Number:Temperature</item-type>
|
||||
@@ -58,7 +64,7 @@
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-vehicle-temperature">
|
||||
<item-type unitHint="°C">Number:Temperature</item-type>
|
||||
<label>Temperature</label>
|
||||
<label>Device Temperature</label>
|
||||
<description>Current temperature of the heating device</description>
|
||||
<category>Temperature</category>
|
||||
<tags>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<label>Loadpoint</label>
|
||||
<description>A loadpoint configured in your evcc instance</description>
|
||||
<representation-property>index</representation-property>
|
||||
<config-description>
|
||||
<parameter name="index" type="integer">
|
||||
<label>Index</label>
|
||||
<description>The index of the loadpoint in your evcc setup (starting with 0)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<channel-type id="loadpoint-battery-boost">
|
||||
<item-type>Switch</item-type>
|
||||
@@ -188,7 +194,7 @@
|
||||
<tag>Status</tag>
|
||||
<tag>Mode</tag>
|
||||
</tags>
|
||||
<state pattern="%f %unit%" readOnly="true">
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="ON">Charging</option>
|
||||
<option value="OFF">Not charging</option>
|
||||
@@ -284,6 +290,21 @@
|
||||
</tags>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-enabled">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Enabled</label>
|
||||
<description>Indicating whether this loadpoint is enabled or not</description>
|
||||
<tags>
|
||||
<tag>Status</tag>
|
||||
<tag>Enabled</tag>
|
||||
</tags>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="ON">Enabled</option>
|
||||
<option value="OFF">Disabled</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-plan-projected-end">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Plan Projected End</label>
|
||||
@@ -429,6 +450,72 @@
|
||||
</tags>
|
||||
<state pattern="%d" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-charge-current-l1">
|
||||
<item-type unitHint="A">Number:ElectricCurrent</item-type>
|
||||
<label>Charge Current 1</label>
|
||||
<description>Actual charge current of charger phase L1</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-charge-current-l2">
|
||||
<item-type unitHint="A">Number:ElectricCurrent</item-type>
|
||||
<label>Charge Current 2</label>
|
||||
<description>Actual charge current of charger phase L2</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-charge-current-l3">
|
||||
<item-type unitHint="A">Number:ElectricCurrent</item-type>
|
||||
<label>Charge Current 3</label>
|
||||
<description>Actual charge current of charger phase L3</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-charge-voltage-l1">
|
||||
<item-type unitHint="V">Number:ElectricPotential</item-type>
|
||||
<label>Charge Voltage 1</label>
|
||||
<description>Actual charge voltage of charger phase L1</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-charge-voltage-l2">
|
||||
<item-type unitHint="V">Number:ElectricPotential</item-type>
|
||||
<label>Charge Voltage 2</label>
|
||||
<description>Actual charge voltage of charger phase L2</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-charge-voltage-l3">
|
||||
<item-type unitHint="V">Number:ElectricPotential</item-type>
|
||||
<label>Charge Voltage 3</label>
|
||||
<description>Actual charge voltage of charger phase L3</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="loadpoint-phases-active">
|
||||
<item-type>Number</item-type>
|
||||
<label>Active Phases</label>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
|
||||
<label>Photovoltaic</label>
|
||||
<description>A photovoltaic system configured in your evcc instance</description>
|
||||
<config-description>
|
||||
<parameter name="index" type="integer">
|
||||
<label>Index</label>
|
||||
<description>The index of the photovoltaic system in your evcc setup (starting with 0)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<!-- Already declared in site.xml maybe evcc is going to change it in the future -->
|
||||
<channel-type id="pv-power">
|
||||
|
||||
@@ -195,6 +195,83 @@
|
||||
<description>Indicates whether green power is available for loadpoints</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-grid-current-l1">
|
||||
<item-type unitHint="A">Number:ElectricCurrent</item-type>
|
||||
<label>Grid Current 1</label>
|
||||
<description>Grid phase 1 current</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-grid-current-l2">
|
||||
<item-type unitHint="A">Number:ElectricCurrent</item-type>
|
||||
<label>Grid Current 2</label>
|
||||
<description>Grid phase 2 current</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-grid-current-l3">
|
||||
<item-type unitHint="A">Number:ElectricCurrent</item-type>
|
||||
<label>Grid Current 3</label>
|
||||
<description>Grid phase 3 current</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Current</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-grid-voltage-l1">
|
||||
<item-type unitHint="V">Number:ElectricPotential</item-type>
|
||||
<label>Charge Voltage 1</label>
|
||||
<description>Actual charge voltage of charger phase L1</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-grid-voltage-l2">
|
||||
<item-type unitHint="V">Number:ElectricPotential</item-type>
|
||||
<label>Charge Voltage 2</label>
|
||||
<description>Actual charge voltage of charger phase L2</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-grid-voltage-l3">
|
||||
<item-type unitHint="V">Number:ElectricPotential</item-type>
|
||||
<label>Charge Voltage 3</label>
|
||||
<description>Actual charge voltage of charger phase L3</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Voltage</tag>
|
||||
</tags>
|
||||
<state pattern="%.1f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-grid-energy">
|
||||
<item-type unitHint="kWh">Number:Energy</item-type>
|
||||
<label>Grid Energy</label>
|
||||
<description>Consumed energy from the grid</description>
|
||||
<category>Energy</category>
|
||||
<tags>
|
||||
<tag>Measurement</tag>
|
||||
<tag>Energy</tag>
|
||||
</tags>
|
||||
<state pattern="%.2f %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-grid-power">
|
||||
<item-type unitHint="W">Number:Power</item-type>
|
||||
<label>Grid Power</label>
|
||||
@@ -237,14 +314,14 @@
|
||||
<state min="0" step="0.1" max="100" pattern="%.0f %unit%"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-interval">
|
||||
<item-type>Number</item-type>
|
||||
<item-type unitHint="s">Number:Time</item-type>
|
||||
<label>Interval</label>
|
||||
<description>Refresh interval configured for evcc</description>
|
||||
<category>Settings</category>
|
||||
<state pattern="%d" readOnly="true"/>
|
||||
<state pattern="%d %unit%" readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="site-pv-energy">
|
||||
<item-type unitHint="Wh">Number:Energy</item-type>
|
||||
<item-type unitHint="kWh">Number:Energy</item-type>
|
||||
<label>PV Energy</label>
|
||||
<description>Accumulated energy provided through photovoltaic systems</description>
|
||||
<category>Energy</category>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
<channel-type id="avg-co2-type">
|
||||
<item-type unitHint="g/kWh">Number:EmissionIntensity</item-type>
|
||||
<label>Average C02</label>
|
||||
<label>Average CO2</label>
|
||||
<tags>
|
||||
<tag>Calculation</tag>
|
||||
<tag>Info</tag>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<label>Vehicle</label>
|
||||
<description>A vehicle configured in your evcc instance</description>
|
||||
<representation-property>id</representation-property>
|
||||
<config-description>
|
||||
<parameter name="id" type="text">
|
||||
<label>ID</label>
|
||||
<description>The ID of the vehicle in your evcc setup (database ID)</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
<channel-type id="vehicle-capacity">
|
||||
<item-type unitHint="kWh">Number:Energy</item-type>
|
||||
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
/**
|
||||
* Abstract base test for EvccBaseThingHandler implementations.
|
||||
* Extend this class in your handler tests and override createHandler().
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@SuppressWarnings("null")
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractThingHandlerTestClass<T extends EvccBaseThingHandler> {
|
||||
|
||||
protected Thing thing = mock(Thing.class);
|
||||
protected ChannelTypeRegistry channelTypeRegistry = mock(ChannelTypeRegistry.class);
|
||||
@Nullable
|
||||
protected T handler = null;
|
||||
|
||||
protected ThingStatus lastThingStatus = ThingStatus.UNKNOWN;
|
||||
protected ThingStatusDetail lastThingStatusDetail = ThingStatusDetail.NONE;
|
||||
|
||||
protected static JsonObject exampleResponse = new JsonObject();
|
||||
protected static JsonObject verifyObject = new JsonObject();
|
||||
|
||||
/**
|
||||
* Implement this to provide a handler instance for testing.
|
||||
*/
|
||||
protected abstract T createHandler();
|
||||
|
||||
@BeforeAll
|
||||
static void setUpOnce() {
|
||||
try (InputStream is = EvccBatteryHandlerTest.class.getClassLoader()
|
||||
.getResourceAsStream("responses/example_response.json")) {
|
||||
if (is == null) {
|
||||
throw new IllegalArgumentException("Couldn't find response file");
|
||||
}
|
||||
String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||
exampleResponse = JsonParser.parseString(json).getAsJsonObject();
|
||||
verifyObject = exampleResponse.deepCopy();
|
||||
|
||||
} catch (IOException e) {
|
||||
fail("Failed to read example response file", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class InitializeTests {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
handler = spy(createHandler());
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("index", "0", "type", "battery"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(configuration.get("index")).thenReturn("0");
|
||||
when(configuration.get("id")).thenReturn("vehicle_1");
|
||||
when(thing.getConfiguration()).thenReturn(configuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializeWithoutBridgeHandler() {
|
||||
handler.initialize();
|
||||
assertEquals(ThingStatus.OFFLINE, lastThingStatus);
|
||||
assertEquals(ThingStatusDetail.BRIDGE_UNINITIALIZED, lastThingStatusDetail);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initializeWithBridgeHandlerWithoutCachedState() {
|
||||
EvccBridgeHandler bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.bridgeHandler = bridgeHandler;
|
||||
when(bridgeHandler.getCachedEvccState()).thenReturn(new JsonObject());
|
||||
|
||||
handler.initialize();
|
||||
assertEquals(ThingStatus.OFFLINE, lastThingStatus);
|
||||
assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, lastThingStatusDetail);
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link BaseThingHandlerTestClass} is responsible for creating a subclass for testing the BaseThingHandler
|
||||
* implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class BaseThingHandlerTestClass extends EvccBaseThingHandler {
|
||||
public boolean createChannelCalled = false;
|
||||
public boolean updateThingCalled = false;
|
||||
public boolean updateStatusCalled = false;
|
||||
public boolean prepareApiResponseForChannelStateUpdateCalled = true;
|
||||
public boolean logUnknownChannelXmlCalled = false;
|
||||
public ThingStatus lastUpdatedStatus = ThingStatus.UNKNOWN;
|
||||
public boolean updateStateCalled = false;
|
||||
public State lastState = UnDefType.UNDEF;
|
||||
public ChannelUID lastChannelUID = new ChannelUID("dummy:dummy:dummy:dummy");
|
||||
|
||||
public BaseThingHandlerTestClass(Thing thing, ChannelTypeRegistry registry) {
|
||||
super(thing, registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateThing(Thing thing) {
|
||||
updateThingCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status) {
|
||||
lastUpdatedStatus = status;
|
||||
updateStatusCalled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Channel createChannel(String thingKey, JsonElement value) {
|
||||
createChannelCalled = true;
|
||||
return super.createChannel(thingKey, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareApiResponseForChannelStateUpdate(JsonObject state) {
|
||||
prepareApiResponseForChannelStateUpdateCalled = true;
|
||||
super.updateStatesFromApiResponse(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getStateFromCachedState(JsonObject state) {
|
||||
return new JsonObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(ChannelUID uid, State state) {
|
||||
updateStateCalled = true;
|
||||
lastState = state;
|
||||
lastChannelUID = uid;
|
||||
}
|
||||
|
||||
// Make sure no files are getting created
|
||||
@Override
|
||||
protected void logUnknownChannelXml(String key, String itemType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUnknownChannelXmlAsync(String key, String itemType) {
|
||||
logUnknownChannelXmlCalled = true;
|
||||
super.logUnknownChannelXmlAsync(key, itemType);
|
||||
}
|
||||
}
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.NUMBER_ENERGY;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.thing.type.ChannelType;
|
||||
import org.openhab.core.thing.type.ChannelTypeRegistry;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
|
||||
/**
|
||||
* The {@link EvccBaseThingHandlerTest} is responsible for testing the EvccBaseThingHandler implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccBaseThingHandlerTest {
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private final Thing thing = mock(Thing.class);
|
||||
|
||||
@SuppressWarnings("null")
|
||||
private final ChannelTypeRegistry channelTypeRegistry = mock(ChannelTypeRegistry.class);
|
||||
|
||||
private BaseThingHandlerTestClass handler = new BaseThingHandlerTestClass(thing, channelTypeRegistry);
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
handler = spy(new BaseThingHandlerTestClass(thing, channelTypeRegistry));
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("index", "0", "type", "battery"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(configuration.get("index")).thenReturn("0");
|
||||
when(configuration.get("id")).thenReturn("vehicle_1");
|
||||
when(thing.getConfiguration()).thenReturn(configuration);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class UpdateStatesFromApiResponseTests {
|
||||
@Test
|
||||
public void updateFromEvccStateNotInitializedDoesNothing() {
|
||||
handler.isInitialized = false;
|
||||
JsonObject state = new JsonObject();
|
||||
handler.updateStatesFromApiResponse(state);
|
||||
assertTrue(handler.prepareApiResponseForChannelStateUpdateCalled);
|
||||
assertFalse(handler.createChannelCalled);
|
||||
assertFalse(handler.updateThingCalled);
|
||||
assertFalse(handler.updateStatusCalled);
|
||||
assertEquals(ThingStatus.UNKNOWN, handler.lastUpdatedStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateFromEvccStateEmptyStateDoesNothing() {
|
||||
handler.isInitialized = true;
|
||||
JsonObject state = new JsonObject();
|
||||
handler.updateStatesFromApiResponse(state);
|
||||
assertTrue(handler.prepareApiResponseForChannelStateUpdateCalled);
|
||||
assertFalse(handler.createChannelCalled);
|
||||
assertFalse(handler.updateThingCalled);
|
||||
// Status should not be updated for empty state
|
||||
assertFalse(handler.updateStatusCalled);
|
||||
assertEquals(ThingStatus.UNKNOWN, handler.lastUpdatedStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void updateFromEvccStateWithPrimitiveValueCreatesChannelAndSetsItemValue() {
|
||||
handler.isInitialized = true;
|
||||
JsonObject state = new JsonObject();
|
||||
state.add("capacity", new JsonPrimitive(5.5));
|
||||
// Channel does not exist
|
||||
when(thing.getChannel(anyString())).thenReturn(null);
|
||||
ChannelType mockChannelType = mock(ChannelType.class);
|
||||
when(mockChannelType.getItemType()).thenReturn(NUMBER_ENERGY);
|
||||
when(channelTypeRegistry.getChannelType(any())).thenReturn(mockChannelType);
|
||||
|
||||
handler.updateStatesFromApiResponse(state);
|
||||
assertTrue(handler.prepareApiResponseForChannelStateUpdateCalled);
|
||||
assertTrue(handler.createChannelCalled);
|
||||
assertTrue(handler.updateThingCalled);
|
||||
assertTrue(handler.updateStatusCalled);
|
||||
assertEquals(ThingStatus.ONLINE, handler.lastUpdatedStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateFromEvccStateWithExistingChannelDoesNotCreateChannel() {
|
||||
handler.isInitialized = true;
|
||||
JsonObject state = new JsonObject();
|
||||
state.add("capacity", new JsonPrimitive(5.5));
|
||||
@SuppressWarnings("null")
|
||||
Channel mockChannel = mock(Channel.class);
|
||||
when(thing.getChannel(anyString())).thenReturn(mockChannel);
|
||||
|
||||
handler.updateStatesFromApiResponse(state);
|
||||
|
||||
assertTrue(handler.prepareApiResponseForChannelStateUpdateCalled);
|
||||
assertFalse(handler.createChannelCalled);
|
||||
assertFalse(handler.updateThingCalled); // Should not update thing if channel exists
|
||||
assertTrue(handler.updateStatusCalled);
|
||||
assertEquals(ThingStatus.ONLINE, handler.lastUpdatedStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateFromEvccStateSkipsNonPrimitiveValues() {
|
||||
handler.isInitialized = true;
|
||||
JsonObject state = new JsonObject();
|
||||
JsonObject nonPrimitive = new JsonObject();
|
||||
nonPrimitive.addProperty("foo", "bar");
|
||||
state.add("complexKey", nonPrimitive);
|
||||
|
||||
handler.updateStatesFromApiResponse(state);
|
||||
|
||||
assertTrue(handler.prepareApiResponseForChannelStateUpdateCalled);
|
||||
assertFalse(handler.createChannelCalled);
|
||||
assertFalse(handler.updateThingCalled);
|
||||
assertTrue(handler.updateStatusCalled); // Status is updated even if nothing else happens
|
||||
assertEquals(ThingStatus.ONLINE, handler.lastUpdatedStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateStatesFromApiResponseWithNullValueDoesNothing() {
|
||||
handler.isInitialized = true;
|
||||
JsonObject state = new JsonObject();
|
||||
state.add("capacity", null); // Null value
|
||||
handler.updateStatesFromApiResponse(state);
|
||||
assertFalse(handler.createChannelCalled);
|
||||
assertFalse(handler.updateThingCalled);
|
||||
assertTrue(handler.updateStatusCalled);
|
||||
assertEquals(ThingStatus.ONLINE, handler.lastUpdatedStatus);
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class HandleCommandTests {
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void handleCommandWithNumberItemType() {
|
||||
ChannelUID channelUID = new ChannelUID("test:thing:uid:battery-capacity");
|
||||
RefreshType command = RefreshType.REFRESH;
|
||||
|
||||
JsonObject cachedState = new JsonObject();
|
||||
cachedState.add("capacity", new JsonPrimitive(42.0));
|
||||
|
||||
ChannelType mockChannelType = mock(ChannelType.class);
|
||||
when(mockChannelType.getItemType()).thenReturn(NUMBER_ENERGY);
|
||||
when(channelTypeRegistry.getChannelType(any())).thenReturn(mockChannelType);
|
||||
|
||||
doReturn(cachedState).when(handler).getStateFromCachedState(any());
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
|
||||
handler.handleCommand(channelUID, command);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void handleCommandWithRefreshTypeAndValidValue() {
|
||||
ChannelUID channelUID = new ChannelUID("test:thing:uid:battery-capacity");
|
||||
RefreshType command = RefreshType.REFRESH;
|
||||
|
||||
JsonObject cachedState = new JsonObject();
|
||||
cachedState.add("capacity", new JsonPrimitive(42.0));
|
||||
|
||||
ChannelType mockChannelType = mock(ChannelType.class);
|
||||
when(mockChannelType.getItemType()).thenReturn(NUMBER_ENERGY);
|
||||
when(channelTypeRegistry.getChannelType(any())).thenReturn(mockChannelType);
|
||||
|
||||
doReturn(cachedState).when(handler).getStateFromCachedState(any());
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
|
||||
handler.handleCommand(channelUID, command);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void handleCommandWithRefreshTypeAndMissingValue() {
|
||||
ChannelUID channelUID = new ChannelUID("test:thing:uid:battery-capacity");
|
||||
RefreshType command = RefreshType.REFRESH;
|
||||
|
||||
JsonObject cachedState = new JsonObject(); // no "capacity" key
|
||||
doReturn(cachedState).when(handler).getStateFromCachedState(any());
|
||||
|
||||
handler.handleCommand(channelUID, command);
|
||||
assertFalse(handler.logUnknownChannelXmlCalled);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
void handleCommandWithNonRefreshTypeDoesNothing() {
|
||||
ChannelUID channelUID = new ChannelUID("test:thing:uid:battery-capacity");
|
||||
Command command = mock(org.openhab.core.types.Command.class);
|
||||
handler.handleCommand(channelUID, command);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateChannelWithUnknownItemType() {
|
||||
JsonElement value = new JsonPrimitive(5.5);
|
||||
@SuppressWarnings("null")
|
||||
ThingBuilder builder = mock(ThingBuilder.class);
|
||||
|
||||
@SuppressWarnings("null")
|
||||
ChannelType mockChannelType = mock(ChannelType.class);
|
||||
when(mockChannelType.getItemType()).thenReturn("Unknown");
|
||||
when(channelTypeRegistry.getChannelType(any())).thenReturn(mockChannelType);
|
||||
|
||||
handler.createChannel("capacity", value);
|
||||
|
||||
assertTrue(handler.createChannelCalled);
|
||||
verify(builder, never()).withChannel(any());
|
||||
}
|
||||
|
||||
@Nested
|
||||
class GetThingKeyTests {
|
||||
@Test
|
||||
public void getThingKeyWithBatteryTypeAndSpecialKey() {
|
||||
handler.type = "battery";
|
||||
|
||||
String key = "soc";
|
||||
String result = handler.getThingKey(key);
|
||||
|
||||
assertEquals("battery-soc", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getThingKeyWithHeatingType() {
|
||||
handler.type = "loadpoint";
|
||||
|
||||
String key = "capacity";
|
||||
String result = handler.getThingKey(key);
|
||||
|
||||
assertEquals("loadpoint-capacity", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getThingKeyWithDefaultType() {
|
||||
handler.type = "loadpoint";
|
||||
|
||||
String key = "someKey";
|
||||
String result = handler.getThingKey(key);
|
||||
|
||||
assertEquals("loadpoint-some-key", result);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
void logUnknownChannelXmlAsyncIsCalledAsynchronously() {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
doAnswer(invocation -> {
|
||||
future.complete(true);
|
||||
return null;
|
||||
}).when(handler).logUnknownChannelXml(anyString(), anyString());
|
||||
|
||||
handler.logUnknownChannelXmlAsync("testKey", "testType");
|
||||
|
||||
try {
|
||||
future.get(2, SECONDS);
|
||||
} catch (Exception e) {
|
||||
fail("logUnknownChannelXml was not called asynchronously");
|
||||
}
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link EvccBatteryHandlerTest} is responsible for testing the EvccBatteryHandler implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccBatteryHandlerTest extends AbstractThingHandlerTestClass<EvccBatteryHandler> {
|
||||
|
||||
private static JsonObject batteryState = new JsonObject();
|
||||
|
||||
@Override
|
||||
protected EvccBatteryHandler createHandler() {
|
||||
return new EvccBatteryHandler(thing, channelTypeRegistry) {
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail detail) {
|
||||
lastThingStatus = status;
|
||||
lastThingStatusDetail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status) {
|
||||
lastThingStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUnknownChannelXmlAsync(String key, String itemType) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Bridge getBridge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThing(Thing thing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("index", "0", "type", "battery"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(configuration.get("index")).thenReturn("0");
|
||||
when(configuration.get("id")).thenReturn("vehicle_1");
|
||||
when(thing.getConfiguration()).thenReturn(configuration);
|
||||
handler = spy(createHandler());
|
||||
|
||||
EvccBridgeHandler bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.bridgeHandler = bridgeHandler;
|
||||
when(bridgeHandler.getCachedEvccState()).thenReturn(exampleResponse);
|
||||
batteryState = exampleResponse.getAsJsonArray("battery").get(0).getAsJsonObject();
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testPrepareApiResponseForChannelStateUpdateIsNotInitialized() {
|
||||
handler.isInitialized = false;
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
verify(handler).updateStatesFromApiResponse(batteryState);
|
||||
assertSame(ThingStatus.UNKNOWN, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInitializeWithBridgeHandlerWithValidState() {
|
||||
handler.initialize();
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testPrepareApiResponseForChannelStateUpdateIsInitialized() {
|
||||
handler.isInitialized = true;
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testGetStateFromCachedState() {
|
||||
JsonObject result = handler.getStateFromCachedState(exampleResponse);
|
||||
assertSame(batteryState, result);
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link EvccHeatingHandlerTest} is responsible for testing the EvccHeatingHandler implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccHeatingHandlerTest extends AbstractThingHandlerTestClass<EvccHeatingHandler> {
|
||||
|
||||
private JsonObject heatingObject = new JsonObject();
|
||||
|
||||
@Override
|
||||
protected EvccHeatingHandler createHandler() {
|
||||
return new EvccHeatingHandler(thing, channelTypeRegistry) {
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail detail) {
|
||||
lastThingStatus = status;
|
||||
lastThingStatusDetail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status) {
|
||||
lastThingStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUnknownChannelXmlAsync(String key, String itemType) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Bridge getBridge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThing(Thing thing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("index", "0", "type", "heating"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(configuration.get("index")).thenReturn("0");
|
||||
when(configuration.get("id")).thenReturn("vehicle_1");
|
||||
when(thing.getConfiguration()).thenReturn(configuration);
|
||||
handler = spy(createHandler());
|
||||
EvccBridgeHandler bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.bridgeHandler = bridgeHandler;
|
||||
when(bridgeHandler.getCachedEvccState()).thenReturn(exampleResponse);
|
||||
|
||||
heatingObject = verifyObject.getAsJsonArray(JSON_KEY_LOADPOINTS).get(0).getAsJsonObject();
|
||||
heatingObject.remove(JSON_KEY_EFFECTIVE_LIMIT_SOC);
|
||||
heatingObject.remove(JSON_KEY_VEHICLE_SOC);
|
||||
heatingObject.remove(JSON_KEY_LIMIT_SOC);
|
||||
heatingObject.remove(JSON_KEY_EFFECTIVE_PLAN_SOC);
|
||||
heatingObject.remove(JSON_KEY_VEHICLE_LIMIT_SOC);
|
||||
heatingObject.addProperty("effectiveLimitTemperature", 80);
|
||||
heatingObject.addProperty("vehicleTemperature", 48.3);
|
||||
heatingObject.addProperty("limitTemperature", 80);
|
||||
heatingObject.addProperty("effectivePlanTemperature", 20);
|
||||
heatingObject.addProperty("vehicleLimitTemperature", 80);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInitializeWithBridgeHandlerWithValidState() {
|
||||
handler.initialize();
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testPrepareApiResponseForChannelStateUpdateIsInitialized() {
|
||||
handler.isInitialized = true;
|
||||
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testPrepareApiResponseForChannelStateUpdateIsNotInitialized() {
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertSame(ThingStatus.UNKNOWN, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testJsonGetsModifiedCorrectly() {
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertEquals(heatingObject, exampleResponse.getAsJsonArray("loadpoints").get(0));
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testGetStateFromCachedState() {
|
||||
JsonObject result = handler.getStateFromCachedState(exampleResponse);
|
||||
assertSame(exampleResponse.getAsJsonArray("loadpoints").get(0), result);
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.openhab.binding.evcc.internal.EvccBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link EvccLoadpointHandlerTest} is responsible for testing the EvccLoadpointHandler implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccLoadpointHandlerTest extends AbstractThingHandlerTestClass<EvccLoadpointHandler> {
|
||||
|
||||
private final JsonObject modifiedTestState = exampleResponse.deepCopy();
|
||||
private final JsonObject testObject = exampleResponse.getAsJsonArray("loadpoints").get(0).getAsJsonObject();
|
||||
private final JsonObject modifiedVerifyObject = verifyObject.deepCopy().getAsJsonArray("loadpoints").get(0)
|
||||
.getAsJsonObject();
|
||||
|
||||
@Override
|
||||
protected EvccLoadpointHandler createHandler() {
|
||||
return new EvccLoadpointHandler(thing, channelTypeRegistry) {
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail detail) {
|
||||
lastThingStatus = status;
|
||||
lastThingStatusDetail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status) {
|
||||
lastThingStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUnknownChannelXmlAsync(String key, String itemType) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Bridge getBridge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThing(Thing thing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("index", "0", "type", "loadpoint"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(configuration.get("index")).thenReturn("0");
|
||||
when(configuration.get("id")).thenReturn("vehicle_1");
|
||||
when(thing.getConfiguration()).thenReturn(configuration);
|
||||
handler = spy(createHandler());
|
||||
|
||||
modifiedVerifyObject.addProperty("chargedEnergy", 50);
|
||||
modifiedVerifyObject.addProperty(JSON_KEY_OFFERED_CURRENT, 6);
|
||||
modifiedVerifyObject.addProperty(JSON_KEY_CONNECTED, true);
|
||||
modifiedVerifyObject.addProperty(JSON_KEY_PHASES_CONFIGURED, "3");
|
||||
modifiedVerifyObject.addProperty("chargeCurrentL1", 6);
|
||||
modifiedVerifyObject.addProperty("chargeCurrentL2", 7);
|
||||
modifiedVerifyObject.addProperty("chargeCurrentL3", 8);
|
||||
modifiedVerifyObject.addProperty("chargeVoltageL1", 230.0);
|
||||
modifiedVerifyObject.addProperty("chargeVoltageL2", 231.0);
|
||||
modifiedVerifyObject.addProperty("chargeVoltageL3", 229.0);
|
||||
modifiedVerifyObject.remove(JSON_KEY_CHARGE_CURRENT);
|
||||
modifiedVerifyObject.remove(JSON_KEY_VEHICLE_PRESENT);
|
||||
modifiedVerifyObject.remove(JSON_KEY_PHASES);
|
||||
|
||||
testObject.addProperty("chargedEnergy", 50);
|
||||
testObject.addProperty(JSON_KEY_CHARGE_CURRENT, 6);
|
||||
testObject.addProperty(JSON_KEY_VEHICLE_PRESENT, true);
|
||||
testObject.addProperty(JSON_KEY_PHASES, "3");
|
||||
JsonArray currents = new JsonArray();
|
||||
currents.add(6);
|
||||
currents.add(7);
|
||||
currents.add(8);
|
||||
testObject.add(JSON_KEY_CHARGE_CURRENTS, currents);
|
||||
JsonArray voltages = new JsonArray();
|
||||
voltages.add(230.0);
|
||||
voltages.add(231.0);
|
||||
voltages.add(229.0);
|
||||
testObject.add(JSON_KEY_CHARGE_VOLTAGES, voltages);
|
||||
JsonArray loadpointArray = exampleResponse.getAsJsonArray("loadpoints");
|
||||
loadpointArray.set(0, testObject);
|
||||
modifiedTestState.add("loadpoints", loadpointArray);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInitializeWithBridgeHandlerWithValidState() {
|
||||
EvccBridgeHandler bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.bridgeHandler = bridgeHandler;
|
||||
when(bridgeHandler.getCachedEvccState()).thenReturn(exampleResponse);
|
||||
|
||||
handler.initialize();
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testPrepareApiResponseForChannelStateUpdateIsInitialized() {
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.isInitialized = true;
|
||||
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testPrepareApiResponseForChannelStateUpdateIsNotInitialized() {
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertSame(ThingStatus.UNKNOWN, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testJsonGetsModifiedCorrectly() {
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertEquals(modifiedVerifyObject, modifiedTestState.getAsJsonArray("loadpoints").get(0));
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testGetStateFromCachedState() {
|
||||
JsonObject result = handler.getStateFromCachedState(exampleResponse);
|
||||
assertSame(exampleResponse.getAsJsonArray("loadpoints").get(0), result);
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link EvccSiteHandlerTest} is responsible for testing the EvccPvHandler implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccPvHandlerTest extends AbstractThingHandlerTestClass<EvccPvHandler> {
|
||||
|
||||
private final JsonObject testState = new JsonObject();
|
||||
private final JsonObject testObject = new JsonObject();
|
||||
private final JsonObject verifyObject = new JsonObject();
|
||||
|
||||
@Override
|
||||
protected EvccPvHandler createHandler() {
|
||||
return new EvccPvHandler(thing, channelTypeRegistry) {
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail detail) {
|
||||
lastThingStatus = status;
|
||||
lastThingStatusDetail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status) {
|
||||
lastThingStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUnknownChannelXmlAsync(String key, String itemType) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Bridge getBridge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThing(Thing thing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("index", "0", "type", "pv"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(configuration.get("index")).thenReturn("0");
|
||||
when(configuration.get("id")).thenReturn("vehicle_1");
|
||||
when(thing.getConfiguration()).thenReturn(configuration);
|
||||
handler = spy(createHandler());
|
||||
|
||||
verifyObject.addProperty("power", 2000);
|
||||
|
||||
testObject.addProperty("power", 2000);
|
||||
JsonArray loadpointArray = new JsonArray();
|
||||
loadpointArray.add(testObject);
|
||||
testState.add("pv", loadpointArray);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInitializeWithBridgeHandlerWithValidState() {
|
||||
EvccBridgeHandler bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.bridgeHandler = bridgeHandler;
|
||||
when(bridgeHandler.getCachedEvccState()).thenReturn(testState);
|
||||
|
||||
handler.initialize();
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testPrepareApiResponseForChannelStateUpdateIsInitialized() {
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.isInitialized = true;
|
||||
|
||||
handler.prepareApiResponseForChannelStateUpdate(testState);
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testPrepareApiResponseForChannelStateUpdateIsNotInitialized() {
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
|
||||
handler.prepareApiResponseForChannelStateUpdate(testState);
|
||||
assertSame(ThingStatus.UNKNOWN, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testGetStateFromCachedState() {
|
||||
JsonObject result = handler.getStateFromCachedState(testState);
|
||||
assertSame(testState.getAsJsonArray("pv").get(0), result);
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* The {@link EvccSiteHandlerTest} is responsible for testing the EvccSiteHandler implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccSiteHandlerTest extends AbstractThingHandlerTestClass<EvccSiteHandler> {
|
||||
|
||||
private final JsonObject gridConfigured = new JsonObject();
|
||||
private final JsonObject modifiedVerifyObject = verifyObject.deepCopy();
|
||||
|
||||
@Override
|
||||
protected EvccSiteHandler createHandler() {
|
||||
return new EvccSiteHandler(thing, channelTypeRegistry) {
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail detail) {
|
||||
lastThingStatus = status;
|
||||
lastThingStatusDetail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status) {
|
||||
lastThingStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUnknownChannelXmlAsync(String key, String itemType) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Bridge getBridge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThing(Thing thing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("index", "0", "type", "pv"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
handler = spy(createHandler());
|
||||
|
||||
modifiedVerifyObject.addProperty("gridPower", 2000);
|
||||
modifiedVerifyObject.addProperty("gridEnergy", 10000);
|
||||
modifiedVerifyObject.addProperty("gridCurrentL1", 6);
|
||||
modifiedVerifyObject.addProperty("gridCurrentL2", 7);
|
||||
modifiedVerifyObject.addProperty("gridCurrentL3", 8);
|
||||
modifiedVerifyObject.addProperty("gridVoltageL1", 230.0);
|
||||
modifiedVerifyObject.addProperty("gridVoltageL2", 231.0);
|
||||
modifiedVerifyObject.addProperty("gridVoltageL3", 229.0);
|
||||
modifiedVerifyObject.remove("gridConfigured");
|
||||
modifiedVerifyObject.remove("grid");
|
||||
|
||||
gridConfigured.addProperty("power", 2000);
|
||||
gridConfigured.addProperty("energy", 10000);
|
||||
JsonArray currents = new JsonArray();
|
||||
currents.add(6);
|
||||
currents.add(7);
|
||||
currents.add(8);
|
||||
gridConfigured.add("currents", currents);
|
||||
JsonArray voltages = new JsonArray();
|
||||
voltages.add(230.0);
|
||||
voltages.add(231.0);
|
||||
voltages.add(229.0);
|
||||
gridConfigured.add("voltages", voltages);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInitializeWithBridgeHandlerWithValidState() {
|
||||
EvccBridgeHandler bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.bridgeHandler = bridgeHandler;
|
||||
when(bridgeHandler.getCachedEvccState()).thenReturn(exampleResponse);
|
||||
|
||||
handler.initialize();
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Nested
|
||||
public class TestPrepareApiResponseForChannelStateUpdate {
|
||||
|
||||
@Test
|
||||
public void handlerIsInitialized() {
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.isInitialized = true;
|
||||
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlerIsNotInitialized() {
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertSame(ThingStatus.UNKNOWN, lastThingStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stateContainsGridConfigured() {
|
||||
handler.bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
|
||||
exampleResponse.addProperty("gridConfigured", true);
|
||||
exampleResponse.add("grid", gridConfigured);
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertEquals(modifiedVerifyObject, exampleResponse);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testGetStateFromCachedState() {
|
||||
JsonObject result = handler.getStateFromCachedState(exampleResponse);
|
||||
assertSame(exampleResponse, result);
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link EvccStatisticsHandlerTest} is responsible for testing the EvccSiteHandler implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccStatisticsHandlerTest extends AbstractThingHandlerTestClass<EvccStatisticsHandler> {
|
||||
|
||||
private boolean updateStateCalled = false;
|
||||
private int updateStateCounter = 0;
|
||||
|
||||
@Override
|
||||
protected EvccStatisticsHandler createHandler() {
|
||||
return new EvccStatisticsHandler(thing, channelTypeRegistry) {
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail detail) {
|
||||
lastThingStatus = status;
|
||||
lastThingStatusDetail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status) {
|
||||
lastThingStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUnknownChannelXmlAsync(String key, String itemType) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Bridge getBridge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateState(ChannelUID uid, State state) {
|
||||
updateStateCalled = true;
|
||||
updateStateCounter++;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("index", "0", "type", "statistics"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
handler = spy(createHandler());
|
||||
EvccBridgeHandler bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.bridgeHandler = bridgeHandler;
|
||||
when(bridgeHandler.getCachedEvccState()).thenReturn(exampleResponse);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Nested
|
||||
public class TestPrepareApiResponseForChannelStateUpdate {
|
||||
|
||||
@Test
|
||||
public void handlerIsInitialized() {
|
||||
handler.isInitialized = true;
|
||||
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertTrue(updateStateCalled);
|
||||
assertEquals(16, updateStateCounter);
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handlerIsNotInitialized() {
|
||||
handler.prepareApiResponseForChannelStateUpdate(exampleResponse);
|
||||
assertSame(ThingStatus.OFFLINE, lastThingStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2025 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.evcc.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.config.core.Configuration;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
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.ThingUID;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link EvccVehicleHandlerTest} is responsible for testing the EvccSiteHandler implementation
|
||||
*
|
||||
* @author Marcel Goerentz - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EvccVehicleHandlerTest extends AbstractThingHandlerTestClass<EvccVehicleHandler> {
|
||||
|
||||
@Override
|
||||
protected EvccVehicleHandler createHandler() {
|
||||
return new EvccVehicleHandler(thing, channelTypeRegistry) {
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status, ThingStatusDetail detail) {
|
||||
lastThingStatus = status;
|
||||
lastThingStatusDetail = detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStatus(ThingStatus status) {
|
||||
lastThingStatus = status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logUnknownChannelXmlAsync(String key, String itemType) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected Bridge getBridge() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateThing(Thing thing) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateState(ChannelUID channelUID, State state) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
when(thing.getUID()).thenReturn(new ThingUID("test:thing:uid"));
|
||||
when(thing.getProperties()).thenReturn(Map.of("id", "vehicle_1", "type", "vehicle"));
|
||||
when(thing.getChannels()).thenReturn(new ArrayList<>());
|
||||
Configuration configuration = mock(Configuration.class);
|
||||
when(configuration.get("index")).thenReturn("0");
|
||||
when(configuration.get("id")).thenReturn("vehicle_1");
|
||||
when(thing.getConfiguration()).thenReturn(configuration);
|
||||
handler = spy(createHandler());
|
||||
EvccBridgeHandler bridgeHandler = mock(EvccBridgeHandler.class);
|
||||
handler.bridgeHandler = bridgeHandler;
|
||||
when(bridgeHandler.getCachedEvccState()).thenReturn(exampleResponse);
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testInitializeWithBridgeHandlerWithValidState() {
|
||||
handler.initialize();
|
||||
assertSame(ThingStatus.ONLINE, lastThingStatus);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user