Skip to content

Commit

Permalink
Improvements to App-Manager (OpenEMS#1813)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Grill <59126309+michaelgrill@users.noreply.github.com>
  • Loading branch information
sfeilmeier and michaelgrill authored Apr 26, 2022
1 parent b1d60d5 commit 8a1eebb
Show file tree
Hide file tree
Showing 87 changed files with 9,924 additions and 519 deletions.
17 changes: 17 additions & 0 deletions io.openems.common/src/io/openems/common/utils/EnumUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.EnumMap;
import java.util.Optional;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonPrimitive;

Expand Down Expand Up @@ -62,6 +63,21 @@ public static <ENUM extends Enum<ENUM>> JsonPrimitive getAsPrimitive(EnumMap<ENU
return JsonUtils.getAsPrimitive(jSubElement);
}

/**
* Gets the member of the {@link EnumMap} as {@link JsonArray}.
*
* @param <ENUM> the type of the EnumMap key
* @param map the {@link EnumMap}
* @param member the member
* @return the {@link JsonArray} value
* @throws OpenemsNamedException on error
*/
public static <ENUM extends Enum<ENUM>> JsonArray getAsJsonArray(EnumMap<ENUM, JsonElement> map, ENUM member)
throws OpenemsNamedException {
var jSubElement = getSubElement(map, member);
return JsonUtils.getAsJsonArray(jSubElement);
}

/**
* Gets the member of the {@link EnumMap} as {@link Boolean}.
*
Expand Down Expand Up @@ -121,4 +137,5 @@ public static <ENUM extends Enum<ENUM>> int getAsInt(EnumMap<ENUM, JsonElement>
throws OpenemsNamedException {
return JsonUtils.getAsInt(getAsPrimitive(map, member));
}

}
1 change: 1 addition & 0 deletions io.openems.edge.core/bnd.bnd
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Bundle-Version: 1.0.0.${tstamp}
io.openems.edge.common,\
io.openems.edge.controller.api,\
io.openems.edge.ess.api,\
io.openems.edge.io.api,\
io.openems.edge.meter.api,\
io.openems.edge.predictor.api,\
io.openems.edge.scheduler.api,\
Expand Down
8 changes: 8 additions & 0 deletions io.openems.edge.core/readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,11 @@ A service that provides 'OpenemsConstants' as Channels so that they are availabl
== Sum

A service that holds summed up information on the power and energy flows, like aggregated production, consumption and energy storage charge/discharge.

== AppManager

A service for managing Apps. It creates, deletes and updates Components, Network configuration and the execute order of the Components in the scheduler.

== App

The predefined Apps for the AppManager.
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package io.openems.edge.app.api;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Reference;

import com.google.common.collect.Lists;
import com.google.gson.JsonElement;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.function.ThrowingBiFunction;
import io.openems.common.types.EdgeConfig;
import io.openems.common.utils.JsonUtils;
import io.openems.edge.app.api.ModbusTcpApiReadOnly.Property;
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.core.appmanager.AbstractOpenemsApp;
import io.openems.edge.core.appmanager.AppAssistant;
import io.openems.edge.core.appmanager.AppConfiguration;
import io.openems.edge.core.appmanager.AppDescriptor;
import io.openems.edge.core.appmanager.ComponentUtil;
import io.openems.edge.core.appmanager.ConfigurationTarget;
import io.openems.edge.core.appmanager.OpenemsApp;
import io.openems.edge.core.appmanager.OpenemsAppCardinality;
import io.openems.edge.core.appmanager.OpenemsAppCategory;
import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled;
import io.openems.edge.core.appmanager.validator.Validator;
import io.openems.edge.core.appmanager.validator.Validator.Builder;

/**
* Describes a App for ReadOnly Modbus/TCP Api.
*
* <pre>
{
"appId":"App.Api.ModbusTcp.ReadOnly",
"alias":"Modbus/TCP-Api Read-Only",
"instanceId": UUID,
"image": base64,
"properties":{
"CONTROLLER_ID": "ctrlApiModbusTcp0"
},
"appDescriptor": {
"websiteUrl": <a href=
"https://docs.fenecon.de/de/_/latest/fems/apis.html#_fems_app_modbustcp_api_lesend">https://docs.fenecon.de/de/_/latest/fems/apis.html#_fems_app_modbustcp_api_lesend</a>
}
}
* </pre>
*/
@org.osgi.service.component.annotations.Component(name = "App.Api.ModbusTcp.ReadOnly")
public class ModbusTcpApiReadOnly extends AbstractOpenemsApp<Property> implements OpenemsApp {

public static enum Property {
// Components
CONTROLLER_ID;
}

@Activate
public ModbusTcpApiReadOnly(@Reference ComponentManager componentManager, ComponentContext context,
@Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) {
super(componentManager, context, cm, componentUtil);
}

@Override
public AppAssistant getAppAssistant() {
return AppAssistant.create(this.getName()) //
.build();
}

@Override
public AppDescriptor getAppDescriptor() {
return AppDescriptor.create() //
.build();
}

@Override
public OpenemsAppCategory[] getCategorys() {
return new OpenemsAppCategory[] { OpenemsAppCategory.API };
}

@Override
public String getImage() {
return OpenemsApp.FALLBACK_IMAGE;
}

@Override
public String getName() {
return "Modbus/TCP-Api Read-Only";
}

@Override
public OpenemsAppCardinality getCardinality() {
return OpenemsAppCardinality.SINGLE;
}

@Override
protected ThrowingBiFunction<ConfigurationTarget, EnumMap<Property, JsonElement>, AppConfiguration, OpenemsNamedException> appConfigurationFactory() {
return (t, p) -> {

var controllerId = this.getId(t, p, Property.CONTROLLER_ID, "ctrlApiModbusTcp0");

List<EdgeConfig.Component> components = Lists.newArrayList(//
new EdgeConfig.Component(controllerId, this.getName(), "Controller.Api.ModbusTcp.ReadOnly",
JsonUtils.buildJsonObject() //
.build()));

return new AppConfiguration(components);
};
}

@Override
public Builder getValidateBuilder() {
return Validator.create() //
.setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap<String, Map<String, ?>>()) //
.put(CheckAppsNotInstalled.COMPONENT_NAME, //
new Validator.MapBuilder<>(new TreeMap<String, Object>()) //
.put("appIds", new String[] { "App.Api.ModbusTcp.ReadWrite" }) //
.build())
.build());
}

@Override
protected Class<Property> getPropertyClass() {
return Property.class;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package io.openems.edge.app.api;

import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Reference;

import com.google.common.collect.Lists;
import com.google.gson.JsonElement;

import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.function.ThrowingBiFunction;
import io.openems.common.types.EdgeConfig;
import io.openems.common.utils.EnumUtils;
import io.openems.common.utils.JsonUtils;
import io.openems.edge.app.api.ModbusTcpApiReadWrite.Property;
import io.openems.edge.common.component.ComponentManager;
import io.openems.edge.common.component.OpenemsComponent;
import io.openems.edge.core.appmanager.AbstractOpenemsApp;
import io.openems.edge.core.appmanager.AppAssistant;
import io.openems.edge.core.appmanager.AppConfiguration;
import io.openems.edge.core.appmanager.AppDescriptor;
import io.openems.edge.core.appmanager.ComponentUtil;
import io.openems.edge.core.appmanager.ConfigurationTarget;
import io.openems.edge.core.appmanager.JsonFormlyUtil;
import io.openems.edge.core.appmanager.JsonFormlyUtil.InputBuilder.Type;
import io.openems.edge.core.appmanager.OpenemsApp;
import io.openems.edge.core.appmanager.OpenemsAppCardinality;
import io.openems.edge.core.appmanager.OpenemsAppCategory;
import io.openems.edge.core.appmanager.validator.CheckAppsNotInstalled;
import io.openems.edge.core.appmanager.validator.CheckNoComponentInstalledOfFactoryId;
import io.openems.edge.core.appmanager.validator.Validator;
import io.openems.edge.core.appmanager.validator.Validator.Builder;

/**
* Describes a App for ReadWrite Modbus/TCP Api.
*
* <pre>
{
"appId":"App.Api.ModbusTcp.ReadWrite",
"alias":"Modbus/TCP-Api Read-Write",
"instanceId": UUID,
"image": base64,
"properties":{
"CONTROLLER_ID": "ctrlApiModbusTcp0",
"API_TIMEOUT": 60,
"COMPONENT_IDS": ["_sum", ...]
},
"appDescriptor": {
"websiteUrl": <a href=
"https://fenecon.de/fems-2-2/fems-app-modbus-tcp-schreibzugriff-2/">https://fenecon.de/fems-2-2/fems-app-modbus-tcp-schreibzugriff-2/</a>
}
}
* </pre>
*/
@org.osgi.service.component.annotations.Component(name = "App.Api.ModbusTcp.ReadWrite")
public class ModbusTcpApiReadWrite extends AbstractOpenemsApp<Property> implements OpenemsApp {

public static enum Property {
// Components
CONTROLLER_ID, //
// User-Values
API_TIMEOUT, //
COMPONENT_IDS;
}

@Activate
public ModbusTcpApiReadWrite(@Reference ComponentManager componentManager, ComponentContext context,
@Reference ConfigurationAdmin cm, @Reference ComponentUtil componentUtil) {
super(componentManager, context, cm, componentUtil);
}

@Override
public AppAssistant getAppAssistant() {
return AppAssistant.create(this.getName()) //
.fields(JsonUtils.buildJsonArray() //
.add(JsonFormlyUtil.buildInput(Property.API_TIMEOUT) //
.setLabel("Api-Timeout") //
.setDescription("Sets the timeout in seconds for updates on Channels set by this Api.")
.setDefaultValue(60) //
.isRequired(true) //
.setInputType(Type.NUMBER) //
.setMin(30) //
.setMax(120) //
.build())
.add(JsonFormlyUtil.buildSelect(Property.COMPONENT_IDS) //
.isMulti(true) //
.isRequired(true) //
.setLabel("Component-IDs") //
.setDescription("Components that should be made available via Modbus.")
.setOptions(this.componentManager.getAllComponents(), t -> t.id() + ": " + t.alias(),
OpenemsComponent::id)
.setDefaultValue("_sum") //
.build())
.build())
.build();
}

@Override
public AppDescriptor getAppDescriptor() {
return AppDescriptor.create() //
.build();
}

@Override
public OpenemsAppCategory[] getCategorys() {
return new OpenemsAppCategory[] { OpenemsAppCategory.API };
}

@Override
public String getImage() {
return OpenemsApp.FALLBACK_IMAGE;
}

@Override
public String getName() {
return "Modbus/TCP-Api Read-Write";
}

@Override
public OpenemsAppCardinality getCardinality() {
return OpenemsAppCardinality.SINGLE;
}

@Override
protected ThrowingBiFunction<ConfigurationTarget, EnumMap<Property, JsonElement>, AppConfiguration, OpenemsNamedException> appConfigurationFactory() {
return (t, p) -> {

var controllerId = this.getId(t, p, Property.CONTROLLER_ID, "ctrlApiModbusTcp0");
var apiTimeout = EnumUtils.getAsInt(p, Property.API_TIMEOUT);
var controllerIds = EnumUtils.getAsJsonArray(p, Property.COMPONENT_IDS);

// remove self if selected
for (var i = 0; i < controllerIds.size(); i++) {
if (controllerIds.get(i).getAsString().equals(controllerId)) {
controllerIds.remove(i);
break;
}
}

List<EdgeConfig.Component> components = Lists.newArrayList(//
new EdgeConfig.Component(controllerId, this.getName(), "Controller.Api.ModbusTcp.ReadWrite",
JsonUtils.buildJsonObject() //
.addProperty("apiTimeout", apiTimeout) //
.add("component.ids", controllerIds).build()));

return new AppConfiguration(components);
};
}

@Override
public Builder getValidateBuilder() {
return Validator.create() //
.setInstallableCheckableNames(new Validator.MapBuilder<>(new TreeMap<String, Map<String, ?>>()) //
.put(CheckAppsNotInstalled.COMPONENT_NAME, //
new Validator.MapBuilder<>(new TreeMap<String, Object>()) //
.put("appIds", new String[] { "App.Api.ModbusTcp.ReadOnly" }) //
.build())
// TODO remove this if the free apps get created via App-Manager and an actual
// app instance gets created
.put(CheckNoComponentInstalledOfFactoryId.COMPONENT_NAME, //
new Validator.MapBuilder<>(new TreeMap<String, Object>()) //
.put("factorieId", "Controller.Api.ModbusTcp.ReadOnly") //
.build())
.build());
}

@Override
protected Class<Property> getPropertyClass() {
return Property.class;
}

}
Loading

0 comments on commit 8a1eebb

Please sign in to comment.