Skip to content

Commit

Permalink
FEMS Backports (#1721)
Browse files Browse the repository at this point in the history
- Continuous improvement of Odoo Backend and FENECON Home implementation
- Cleanup JsonUtils and EdgeConfig
- Add EnumUtils
- JsonrpcRequest: improve log messages on error
- JUnit tests
- Timeslot-Peakshaving ("Hochlastzeitfenster")
- Draft for App-Manager
- EssDcCharger and PV-Inverters: add modbus slave definition
- GoodWe:
  - add config setting to disable MPP Tracking with external optimizers
  - improvements to "SMART"-Mode
  • Loading branch information
sfeilmeier authored Jan 31, 2022
1 parent b985a92 commit ff27485
Show file tree
Hide file tree
Showing 73 changed files with 2,622 additions and 590 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@ public enum Partner implements Field {
CITY("city", true), //
COUNTRY("country_id", true), //
ADDRESS_TYPE("type", true), //
LANGUAGE("lang", true);
LANGUAGE("lang", true), //
CATEGORY_ID("category_id", true);

public static final String ODOO_MODEL = "res.partner";
public static final String ODOO_TABLE = Partner.ODOO_MODEL.replace(".", "_");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ public byte[] getOdooSetupProtocolReport(int setupProtocolId) throws OpenemsName
public int submitSetupProtocol(MyUser user, JsonObject setupProtocolJson) throws OpenemsNamedException {
var userJson = JsonUtils.getAsJsonObject(setupProtocolJson, "customer");
var edgeJson = JsonUtils.getAsJsonObject(setupProtocolJson, "edge");
var installerJson = JsonUtils.getAsJsonObject(setupProtocolJson, "installer");

var edgeId = JsonUtils.getAsString(edgeJson, "id");
int[] foundEdge = OdooUtils.search(this.credentials, Field.EdgeDevice.ODOO_MODEL,
Expand All @@ -416,7 +417,7 @@ public int submitSetupProtocol(MyUser user, JsonObject setupProtocolJson) throws
throw new OpenemsException("Edge not found for id [" + edgeId + "]");
}

var password = PasswordUtils.generateRandomPassword(24);
var password = PasswordUtils.generateRandomPassword(8);
var odooUserId = this.createOdooUser(userJson, password);

var customerId = this.getOdooPartnerId(odooUserId);
Expand All @@ -425,6 +426,20 @@ public int submitSetupProtocol(MyUser user, JsonObject setupProtocolJson) throws

var protocolId = this.createSetupProtocol(setupProtocolJson, foundEdge[0], customerId, installerId);

var installer = OdooUtils.readOne(credentials, Field.Partner.ODOO_MODEL, installerId, Field.Partner.IS_COMPANY);
boolean isCompany = (boolean) installer.get("is_company");
if (!isCompany) {
Map<String, Object> fieldsToUpdate = new HashMap<>();
JsonUtils.getAsOptionalString(installerJson, "firstname") //
.ifPresent(firstname -> fieldsToUpdate.put(Field.Partner.FIRSTNAME.id(), firstname));
JsonUtils.getAsOptionalString(installerJson, "lastname") //
.ifPresent(lastname -> fieldsToUpdate.put(Field.Partner.LASTNAME.id(), lastname));

if (!fieldsToUpdate.isEmpty()) {
OdooUtils.write(credentials, Field.Partner.ODOO_MODEL, new Integer[] { installerId }, fieldsToUpdate);
}
}

try {
this.sendSetupProtocolMail(user, protocolId, edgeId);
} catch (OpenemsNamedException ex) {
Expand Down Expand Up @@ -481,20 +496,42 @@ private int createOdooUser(JsonObject userJson, String password) throws OpenemsN
new Domain(Field.User.LOGIN, "=", email));

if (userFound.length == 1) {
// update existing user
var userId = userFound[0];
OdooUtils.write(this.credentials, Field.User.ODOO_MODEL, new Integer[] { userId }, customerFields);
return userId;
}

customerFields.put(Field.User.LOGIN.id(), email);
customerFields.put(Field.User.PASSWORD.id(), password);
customerFields.put(Field.User.GLOBAL_ROLE.id(), OdooUserRole.OWNER.getOdooRole());
customerFields.put(Field.User.GROUPS.id(), OdooUserRole.OWNER.toOdooIds());
var createdUserId = OdooUtils.create(this.credentials, Field.User.ODOO_MODEL, customerFields);

try {
this.addTagToPartner(createdUserId);
} catch (OpenemsException e) {
this.log.warn("Unable to add tag for Odoo user id [" + createdUserId + "]", e);
}

this.sendRegistrationMail(createdUserId, password);
return createdUserId;
}

/**
* Add the "Created via IBN" tag to the referenced partner for given user id.
*
* @param userId to get Odoo partner
* @throws OpenemsException on error
*/
private void addTagToPartner(int userId) throws OpenemsException {
var tagId = OdooUtils.getObjectReference(credentials, "edge", "res_partner_category_created_via_ibn");
var partnerId = this.getOdooPartnerId(userId);

OdooUtils.write(this.credentials, Field.Partner.ODOO_MODEL, new Integer[] { partnerId },
new FieldValue<>(Field.Partner.CATEGORY_ID, new Integer[] { tagId }));
}

/**
* Create a setup protocol in Odoo.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,34 @@ protected static Map<String, Object>[] searchRead(Credentials credentials, Strin
}
}

/**
* Executes a get object reference from Odoo.
*
* @param credentials the Odoo credentials
* @param module the Odoo module
* @param name the external identifier
* @return internal id of external identifier
* @throws OpenemsException on error
*/
protected static int getObjectReference(Credentials credentials, String module, String name)
throws OpenemsException {
// Create request params
Object[] params = { credentials.getDatabase(), credentials.getUid(), credentials.getPassword(), "ir.model.data",
"get_object_reference", new Object[] { module, name } };
try {
// Execute XML request
var resultObj = (Object[]) executeKw(credentials.getUrl(), params);
if (resultObj == null) {
throw new OpenemsException(
"No matching entry found for module [" + module + "] and name [" + name + "]");
}

return (int) resultObj[1];
} catch (Throwable e) {
throw new OpenemsException("Unable to read from Odoo: " + e.getMessage());
}
}

/**
* Adds a message in Odoo Chatter ('mail.thread').
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.openems.backend.metadata.odoo.Field.EdgeDevice;
import io.openems.common.types.EdgeConfig;
import io.openems.common.types.EdgeConfig.Component.JsonFormat;
import io.openems.common.utils.JsonUtils;
import io.openems.common.utils.StringUtils;

public class UpdateEdgeConfig extends DatabaseTask {
Expand All @@ -19,7 +20,7 @@ public class UpdateEdgeConfig extends DatabaseTask {

public UpdateEdgeConfig(int odooId, EdgeConfig config) {
this.odooId = odooId;
this.fullConfig = new GsonBuilder().setPrettyPrinting().create().toJson(config.toJson());
this.fullConfig = JsonUtils.prettyToString(config.toJson());
this.componentsConfig = new GsonBuilder().setPrettyPrinting().create()
.toJson(config.componentsToJson(JsonFormat.WITHOUT_CHANNELS));
}
Expand Down
4 changes: 2 additions & 2 deletions io.openems.common/src/io/openems/common/OpenemsConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public class OpenemsConstants {
* <p>
* This is the month of the release.
*/
public static final short VERSION_MINOR = 2;
public final static short VERSION_MINOR = 2;

/**
* The patch version of OpenEMS.
Expand All @@ -31,7 +31,7 @@ public class OpenemsConstants {
* This is always `0` for OpenEMS open source releases and reserved for private
* distributions.
*/
public static final short VERSION_PATCH = 0;
public final static short VERSION_PATCH = 0;

/**
* The additional version string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,10 @@ public enum OpenemsError {
JSON_NO_STRING(5009, "JSON [%s] is not a String"), //
JSON_NO_STRING_MEMBER(5010, "JSON [%s:%s] is not a String"), //
JSON_NO_BOOLEAN(5011, "JSON [%s] is not a Boolean"), //
JSON_NO_BOOLEAN_MEMBER(5012, "JSON [%s:%s] is not a Boolean"), //
JSON_NO_NUMBER(5013, "JSON [%s] is not a Number"), //
JSON_NO_NUMBER_MEMBER(5014, "JSON [%s:%s] is not a Number"), //
JSON_PARSE_ELEMENT_FAILED(5015, "JSON failed to parse [%s]. %s: %s"), //
JSON_PARSE_FAILED(5016, "JSON failed to parse [%s]: %s"), //
JSON_NO_FLOAT_MEMBER(5017, "JSON [%s:%s] is not a Float"), //
JSON_NO_ENUM_MEMBER(5018, "JSON [%s:%s] is not an Enum"), //
JSON_NO_INET4ADDRESS(5020, "JSON [%s] is not an IPv4 address"), //
JSON_NO_ENUM(5021, "JSON [%s] is not an Enum"), //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public abstract class JsonrpcRequest extends AbstractJsonrpcRequest {
public static final int DEFAULT_TIMEOUT_SECONDS = 60;
public static final int NO_TIMEOUT = -1;

private final UUID id;
private final Optional<Integer> timeoutOpt;
public final UUID id;
public final Optional<Integer> timeoutOpt;

/**
* Creates a {@link JsonrpcRequest} with random {@link UUID} as id and
Expand Down Expand Up @@ -115,6 +115,7 @@ public JsonrpcRequest(UUID id, String method, int timeout) {
*
* @return the {@link UUID} id
*/
// TODO remove this method and directly use the public final 'id'.
public UUID getId() {
return this.id;
}
Expand Down
68 changes: 64 additions & 4 deletions io.openems.common/src/io/openems/common/types/EdgeConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.stream.Collectors;

Expand All @@ -26,6 +27,7 @@
import io.openems.common.channel.ChannelCategory;
import io.openems.common.channel.Level;
import io.openems.common.channel.Unit;
import io.openems.common.exceptions.InvalidValueException;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
import io.openems.common.exceptions.OpenemsException;
import io.openems.common.types.EdgeConfig.Component.JsonFormat;
Expand All @@ -43,6 +45,8 @@ public class EdgeConfig {
*/
public static class Component {

public static final String NO_SERVICE_PID = "NO_SERVICE_PID";

/**
* Represents a Channel of an OpenEMS Component.
*/
Expand Down Expand Up @@ -300,11 +304,11 @@ public JsonObject toJson() {
private final String id;
private final String alias;
private final String factoryId;
private final TreeMap<String, JsonElement> properties;
private final TreeMap<String, Channel> channels;
private final SortedMap<String, JsonElement> properties;
private final SortedMap<String, Channel> channels;

public Component(String servicePid, String id, String alias, String factoryId,
TreeMap<String, JsonElement> properties, TreeMap<String, Channel> channels) {
SortedMap<String, JsonElement> properties, SortedMap<String, Channel> channels) {
this.servicePid = servicePid;
this.id = id;
this.alias = alias;
Expand All @@ -313,6 +317,34 @@ public Component(String servicePid, String id, String alias, String factoryId,
this.channels = channels;
}

/**
* Constructor with NO_SERVICE_PID.
*
* @param id the Component-ID
* @param alias the Alias
* @param factoryId the Factory-ID
* @param properties the configuration properties
* @param channels the channels
*/
public Component(String id, String alias, String factoryId, SortedMap<String, JsonElement> properties,
SortedMap<String, Channel> channels) {
this(NO_SERVICE_PID, id, alias, factoryId, properties, channels);
}

/**
* Constructor with NO_SERVICE_PID, properties as JsonObject and no channels
*
* @param id the Component-ID
* @param factoryId the Factory-ID
* @param properties the configuration properties
*/
public Component(String id, String alias, String factoryId, JsonObject properties) {
this(NO_SERVICE_PID, id, alias, factoryId,
properties.entrySet().stream()
.collect(Collectors.toMap(Entry::getKey, Entry::getValue, (o1, o2) -> o1, TreeMap::new)),
new TreeMap<>());
}

/**
* Gets the PID of the {@link Component}.
*
Expand Down Expand Up @@ -368,6 +400,21 @@ public Optional<JsonElement> getProperty(String propertyId) {
return Optional.ofNullable(this.properties.get(propertyId));
}

/**
* Gets the Property with the given ID or throws an Exception if it does not
* exist.
*
* @param propertyId the Property-ID
* @return the Property
*/
public JsonElement getPropertyOrError(String propertyId) throws InvalidValueException {
JsonElement property = this.properties.get(propertyId);
if (property != null) {
return property;
}
throw new InvalidValueException("Property with ID [" + propertyId + "] does not exist.");
}

/**
* Gets a map of {@link Channel}s of the {@link Component}.
*
Expand Down Expand Up @@ -509,7 +556,6 @@ public static Component fromJson(String componentId, JsonElement json) throws Op
}
}
return new Component(//
"NO_SERVICE_PID", //
componentId, //
alias, //
factoryId, //
Expand Down Expand Up @@ -1055,6 +1101,20 @@ public Optional<Component> getComponent(String componentId) {
return Optional.ofNullable(this.components.get(componentId));
}

/**
* Gets the {@link Component} or throws an Exception if it does not exist.
*
* @param componentId the Component-ID
* @return the {@link Component}
*/
public Component getComponentOrError(String componentId) throws InvalidValueException {
Component component = this.components.get(componentId);
if (component != null) {
return component;
}
throw new InvalidValueException("Component with ID [" + componentId + "] does not exist.");
}

/**
* Add a Factory.
*
Expand Down
Loading

0 comments on commit ff27485

Please sign in to comment.