Skip to content

Commit

Permalink
FEMS Backports 2024-11-01 (#2863)
Browse files Browse the repository at this point in the history
- UI: Label correction in SoC widget in history.
  - Fixed lables shown below.
  - Energy is represented as Power.
  - Entladung/Beladung is represented as Charge/Discharge power. replaced with Just Charge/Discharge.
- Keba ReadWorker: fix possible Exception
  - Added Tests to identify when and how the exception can occur.
  - Tests for the "calculateCycleTime" work for multiple cases without throwing an exception
  - Tests could still be kept for code coverage
- GoodWe: improve serial number filter
  - Change the serial number filter according to the goowe serial number rule.
  - Each GoodWe Type is now detected individually by its rated power in combination with its internal model series code.
- GoodWeGridMeter: adjust CURRENT sign current if needed
  - Added Listener to ActivePowerLX Channels
  - Each Listeners checks if the nextValue for the power channel if the current and power have the same sign
  - If the sign is different the current channel is set with * -1
- JUnit Test Framework: add possibility to test raw modbus reads
  - `DummyModbusBridge` can now be used to test raw modbus reads
  - Apply new test for changes in GoodWe GridMeter and SunSpec
  - Also: Improve and apply `ReflectionUtils`
- Replace exchangerate.host with Eurpean Central Bank API
- `_host/OsVersion`: read os version as string
  - Added OS_VERSION Channel in Host Component
  - Write e. g. "11 (bullseye)" in channel for linux and for windows "Windows 11"
- UI: replace currentUser observable with signal and solve login caching
  - Start recommended restructuring observables to signals
  - Fix `UserComponent` caching user information after logout
- UI: Smaller Improvements
  - Auto capitalize ibn-installer key input field to show only upper case letters in mobile view (keyboard)
  - Fix wrong service number in offline manuals
  - adjust time of use chart in live with replacing the upper most left yAxis tick with the chart title
- UI: remove app center app icon
  - Improving app center readability of icons
- Time-of-use-Tariff Swisspower

---------

Co-authored-by: Lukas Rieger <[email protected]>
Co-authored-by: Sagar Venu <[email protected]>
Co-authored-by: Sebastian Asen <[email protected]>
Co-authored-by: Stefan Feilmeier <[email protected]>
Co-authored-by: Johann Kaufmann <[email protected]>
Co-authored-by: Michael Grill <[email protected]>
  • Loading branch information
7 people authored Nov 1, 2024
1 parent d58852d commit ae1dbf8
Show file tree
Hide file tree
Showing 112 changed files with 2,517 additions and 617 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public SystemUpdateParams getSystemUpdateParams() {
.put("App.TimeOfUseTariff.Hassfurt", "") //
.put("App.TimeOfUseTariff.RabotCharge", "") //
.put("App.TimeOfUseTariff.Stromdao", "") //
.put("App.TimeOfUseTariff.Swisspower", "") //
.put("App.TimeOfUseTariff.Tibber", "") //
.put("App.Api.ModbusTcp.ReadOnly", "") //
.put("App.Api.ModbusTcp.ReadWrite", "") //
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,13 +133,4 @@ public default String getEntsoeToken() {
return null;
}

/**
* Gets the OEM Access-Key for Exchangerate.host (used by
* TimeOfUseTariff.ENTSO-E).
*
* @return the value
*/
public default String getExchangeRateAccesskey() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.openems.common.test;

import static io.openems.common.utils.ReflectionUtils.invokeMethodViaReflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Expand Down Expand Up @@ -110,7 +112,7 @@ public Dictionary<String, Object> getAsProperties()
}

var key = method.getName().replace("_", ".");
var value = method.invoke(this);
var value = invokeMethodViaReflection(this, method);
if (value == null) {
throw new IllegalArgumentException("Configuration for [" + key + "] is null");
}
Expand Down
128 changes: 120 additions & 8 deletions io.openems.common/src/io/openems/common/utils/ReflectionUtils.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,143 @@
package io.openems.common.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

import io.openems.common.function.ThrowingRunnable;
import io.openems.common.function.ThrowingSupplier;

public class ReflectionUtils {

public static class ReflectionException extends RuntimeException {
private static final long serialVersionUID = -8001364348945297741L;

protected static ReflectionException from(Exception e) {
return new ReflectionException(e.getClass().getSimpleName() + ": " + e.getMessage());
}

public ReflectionException(String message) {
super(message);
}
}

private ReflectionUtils() {
// no instance needed
}

protected static void callGuarded(ThrowingRunnable<Exception> runnable) throws ReflectionException {
try {
runnable.run();
} catch (Exception e) {
throw ReflectionException.from(e);
}
}

protected static <T> T callGuarded(ThrowingSupplier<T, Exception> supplier) throws ReflectionException {
try {
return supplier.get();
} catch (Exception e) {
throw ReflectionException.from(e);
}
}

/**
* Sets the value of a Field via Java Reflection.
*
* @param object the target object
* @param memberName the name the declared field
* @param value the value to be set
* @throws Exception on error
*/
public static void setAttributeViaReflection(Object object, String memberName, Object value)
throws ReflectionException {
var field = getField(object.getClass(), memberName);
callGuarded(() -> field.set(object, value));
}

/**
* Sets the value of a static Field via Java Reflection.
*
* @param clazz the {@link Class}
* @param memberName the name the declared field
* @param value the value to be set
* @throws Exception on error
*/
public static void setStaticAttributeViaReflection(Class<?> clazz, String memberName, Object value)
throws ReflectionException {
var field = getField(clazz, memberName);
callGuarded(() -> field.set(null, value));
}

/**
* Gets the value of a Field via Java Reflection.
*
* @param <T> the type of the value
* @param object the target object
* @param memberName the name the declared field
* @return the value
* @throws Exception on error
*/
@SuppressWarnings("unchecked")
public static <T> boolean setAttribute(Class<? extends T> clazz, T object, String memberName, Object value)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
public static <T> T getValueViaReflection(Object object, String memberName) throws ReflectionException {
var field = getField(object.getClass(), memberName);
return (T) callGuarded(() -> field.get(object));
}

/**
* Invokes a {@link Method} that takes no arguments via Java Reflection.
*
* @param <T> the type of the result
* @param object the target object
* @param memberName the name of the method
* @return the result of the method
* @throws Exception on error
*/
public static <T> T invokeMethodWithoutArgumentsViaReflection(Object object, String memberName)
throws ReflectionException {
var method = callGuarded(() -> object.getClass().getDeclaredMethod(memberName));
return invokeMethodViaReflection(object, method);
}

/**
* Invokes a {@link Method} via Java Reflection.
*
* @param <T> the type of the result
* @param object the target object
* @param method the {@link Method}
* @param args the arguments to be set
* @return the result of the method
* @throws Exception on error
*/
@SuppressWarnings("unchecked")
public static <T> T invokeMethodViaReflection(Object object, Method method, Object... args)
throws ReflectionException {
method.setAccessible(true);
return (T) callGuarded(() -> method.invoke(object, args));
}

/**
* Gets the {@link Class#getDeclaredField(String)} in the given {@link Class} or
* any of its superclasses.
*
* @param clazz the given {@link Class}
* @param memberName the name of the declared field
* @return a {@link Field}
* @throws ReflectionException if there is no such field
*/
public static Field getField(Class<?> clazz, String memberName) throws ReflectionException {
try {
var field = clazz.getDeclaredField(memberName);
field.setAccessible(true);
field.set(object, value);
return true;
return field;
} catch (NoSuchFieldException e) {
// Ignore.
}
// If we are here, no matching field or method was found. Search in parent
// classes.
Class<?> parent = clazz.getSuperclass();
if (parent == null) {
return false; // reached 'java.lang.Object'
throw new ReflectionException("Reached java.lang.Object");
}
return setAttribute((Class<T>) parent, object, memberName, value);
return getField(parent, memberName);
}

}
30 changes: 30 additions & 0 deletions io.openems.common/src/io/openems/common/utils/XmlUtils.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package io.openems.common.utils;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.DOMException;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import io.openems.common.exceptions.OpenemsError;
import io.openems.common.exceptions.OpenemsError.OpenemsNamedException;
Expand Down Expand Up @@ -254,4 +262,26 @@ public static Stream<Node> stream(final Node node) {
var childNodes = node.getChildNodes();
return IntStream.range(0, childNodes.getLength()).boxed().map(childNodes::item);
}

/**
* Parses the provided XML string and returns the root {@link Element} of the
* XML document.
*
* @param xml the XML string to parse
* @return the root {@link Element} of the parsed XML document
* @throws ParserConfigurationException if a DocumentBuilder cannot be created
* which satisfies the configuration
* requested
* @throws SAXException if any parse errors occur while
* processing the XML
* @throws IOException if an I/O error occurs during parsing
*/
public static Element getXmlRootDocument(String xml)
throws ParserConfigurationException, SAXException, IOException {
var dbFactory = DocumentBuilderFactory.newInstance();
var dBuilder = dbFactory.newDocumentBuilder();
var is = new InputSource(new StringReader(xml));
var doc = dBuilder.parse(is);
return doc.getDocumentElement();
}
}
2 changes: 2 additions & 0 deletions io.openems.edge.application/EdgeApp.bndrun
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@
bnd.identity;id='io.openems.edge.timeofusetariff.groupe',\
bnd.identity;id='io.openems.edge.timeofusetariff.hassfurt',\
bnd.identity;id='io.openems.edge.timeofusetariff.rabotcharge',\
bnd.identity;id='io.openems.edge.timeofusetariff.swisspower',\
bnd.identity;id='io.openems.edge.timeofusetariff.tibber',\

-runbundles: \
Expand Down Expand Up @@ -373,6 +374,7 @@
io.openems.edge.timeofusetariff.groupe;version=snapshot,\
io.openems.edge.timeofusetariff.hassfurt;version=snapshot,\
io.openems.edge.timeofusetariff.rabotcharge;version=snapshot,\
io.openems.edge.timeofusetariff.swisspower;version=snapshot,\
io.openems.edge.timeofusetariff.tibber;version=snapshot,\
io.openems.oem.openems;version=snapshot,\
io.openems.shared.influxdb;version=snapshot,\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -636,4 +636,26 @@ public void testBatteryProtectionSocLimitations() throws Exception {
.output(BP_CHARGE_MAX_SOC, round(40 * 0.2F)) //
);
}

@Test
public void testReadModbus() throws Exception {
var sut = new BatteryFeneconHomeImpl();
new ComponentTest(sut) //
.addReference("cm", new DummyConfigurationAdmin()) //
.addReference("componentManager", new DummyComponentManager()) //
.addReference("setModbus", new DummyModbusBridge("modbus0") //
.withRegister(18000, (byte) 0x00, (byte) 0x00)) // TOWER_4_BMS_SOFTWARE_VERSION
.activate(MyConfig.create() //
.setId("battery0") //
.setModbusId("modbus0") //
.setModbusUnitId(0) //
.setStartStop(StartStopConfig.START) //
.setBatteryStartUpRelay("io0/InputOutput4")//
.build()) //

.next(new TestCase() //
.output(BatteryFeneconHome.ChannelId.TOWER_4_BMS_SOFTWARE_VERSION, 0)) //

.deactivate();
}
}
Loading

0 comments on commit ae1dbf8

Please sign in to comment.