From 9bf9b9a66f8df61a37db7eae4a0c2d7b95eff63d Mon Sep 17 00:00:00 2001 From: "holz holz@unicon-gmbh.de" Date: Thu, 13 Aug 2026 09:13:16 +0200 Subject: [PATCH] =?UTF-8?q?Optimierung=20der=20Abfragezeiten,=20Teilcachin?= =?UTF-8?q?g=20zus=C3=A4tzlich=20Timestamp=20pro=20alias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nbproject/project.properties | 1 + src/kebaagent/KebaAgent.java | 163 ++++++++++++++++----------- src/kebaagent/KebaModbus.java | 203 ++++++++++++++++++++++------------ 3 files changed, 230 insertions(+), 137 deletions(-) diff --git a/nbproject/project.properties b/nbproject/project.properties index 5faea40..abab306 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -81,6 +81,7 @@ main.class=kebaagent.KebaAgent manifest.file=manifest.mf meta.inf.dir=${src.dir}/META-INF mkdist.disabled=false +no.dependencies=true platform.active=default_platform run.classpath=\ ${javac.classpath}:\ diff --git a/src/kebaagent/KebaAgent.java b/src/kebaagent/KebaAgent.java index e1e3185..00c275c 100644 --- a/src/kebaagent/KebaAgent.java +++ b/src/kebaagent/KebaAgent.java @@ -13,6 +13,7 @@ import java.io.FileReader; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.time.LocalDateTime; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -26,7 +27,7 @@ import org.json.JSONArray; import org.json.JSONException; /** - * + * In der Konfigurationsdatei können mehrere Wallbox-Ports aka alias definiert werden. Über die wird die Konfig separiert. * @author holz */ public class KebaAgent @@ -35,8 +36,18 @@ public class KebaAgent private Properties prop = new Properties(); - +/** + * Liste von Instanzen über Namen + */ + private final Map kebaModbusMap = new HashMap<>(); + + +/** + * Instanz erstellen und properties einlesen + * @see #init() + * @param args + */ public KebaAgent(String[] args) { String propertiesFilename = "kemaagent.properties"; @@ -80,6 +91,10 @@ public class KebaAgent } +/** + * Webserver Port aus config ermitteln und liefern + * @return + */ int getPort() { int defPort = WEBSERVER_PORT; @@ -96,36 +111,8 @@ public class KebaAgent return defPort; } - -/** - * Liste von Instanzen über Namen - */ - private final Map kebaModbusMap = new HashMap<>(); - - /** - * @param args the command line arguments - */ - public static void main(String[] args) - { - - KebaAgent agent = new KebaAgent(args); - agent.init(); - - - Webserver webserver = new Webserver(agent.getPort()); - try - { - webserver.start(agent); - } - catch (IOException ex) - { - System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex); - } - System.out.println("webserver gestartet"); - } - - + /** * alle wichtigen modbus-Daten beschaffen @@ -139,6 +126,7 @@ public class KebaAgent JSONObject joRoot = new JSONObject(); JSONArray jaStations = new JSONArray(); +// über alle Wallboxen ... getAliases() .forEach(name -> { @@ -147,58 +135,67 @@ public class KebaAgent JSONObject joPart = new JSONObject(); - int val; + int intVal; try { if (kebaModbus.initFailed) { joPart.put("error", "initError"); + jaStations.put(joPart); return; } joPart.put("alias", name); + int pause_ms = 600; - sleep(600); +// nach keba-Doku muss zwischen den Abfragen 500ms Pause sein und es kann immer nur ein Register gelesen werden! +// sleep(600); String jName = "chargingState"; int register = 1000; - val = kebaModbus.read(jName, register, slaveId); + intVal = kebaModbus.read(jName, register, slaveId, pause_ms); // val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register); - System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, val, val); - joPart.put(jName, val); + System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, intVal, intVal); + joPart.put(jName, intVal); - sleep(600); +// cache-Zeit für statische Daten (S/N etc) + int maxAgeMinutes = 60; + +// sleep(600); jName = "s/n"; register = 1014; - val = kebaModbus.read(jName, register, slaveId); - System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, val, val); - joPart.put(jName, val); + intVal = kebaModbus.readCached(jName, register, slaveId, pause_ms, maxAgeMinutes); + System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, intVal, intVal); + joPart.put(jName, intVal); - sleep(600); +// akt. Leistung +// sleep(600); jName = "power"; register = 1020; - val = kebaModbus.read(jName, register, slaveId); + intVal = kebaModbus.read(jName, register, slaveId, pause_ms); // val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register); - System.out.printf("Wert für reg %d (active Power): %1.1f kW (0x%04x)\n", register, val/1e6, val); - joPart.put(jName, val/1e6); + System.out.printf("Wert für reg %d (active Power): %1.1f kW (0x%04x)\n", register, intVal/1e6, intVal); + joPart.put(jName, intVal/1e6); - sleep(600); +// sleep(600); jName = "RFID"; register = 1500; - val = kebaModbus.read(jName, register, slaveId); + intVal = kebaModbus.read(jName, register, slaveId, pause_ms); // val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register); - System.out.printf("Wert für reg %d (RFID-Card): %d (0x%04x)\n", register, val, val); - joPart.put(jName, val); + System.out.printf("Wert für reg %d (RFID-Card): %d (0x%04x)\n", register, intVal, intVal); + joPart.put(jName, intVal); - sleep(600); +// sleep(600); jName = "chargedEnergy"; // angeblich Wh, aber in Wirklichkeit Wh/10 register = 1502; - val = kebaModbus.read(jName, register, slaveId); + intVal = kebaModbus.read(jName, register, slaveId, pause_ms); // val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register); - System.out.printf("Wert für reg %d (charged energy [kWh]): %1.1f (0x%04x)\n", register, val/10000f, val); - joPart.put(jName, val/10000f); + System.out.printf("Wert für reg %d (charged energy [kWh]): %1.1f (0x%04x)\n", register, intVal/10000f, intVal); + joPart.put(jName, intVal/10000f); + + joPart.put("ts", LocalDateTime.now().withNano(0).toString()); jaStations.put(joPart); } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException ex) @@ -229,26 +226,28 @@ public class KebaAgent - +/** + * + */ private void init() { getAliases().forEach(alias -> { - getPropValue(alias, "ip").ifPresent(sIP -> + getPropValue(alias, "ip").ifPresent(sIP -> + { + KebaModbus km = new KebaModbus(sIP, getModbusPort(alias)); + km.initFailed = true; + try { - KebaModbus km = new KebaModbus(sIP, getModbusPort(alias)); - km.initFailed = true; - try - { - km.init(); - km.initFailed = false; - kebaModbusMap.put(alias, km); - } - catch (UnknownHostException | ModbusIOException | NumberFormatException ex) - { - System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex); - } - }); + km.init(); + km.initFailed = false; + kebaModbusMap.put(alias, km); + } + catch (UnknownHostException | ModbusIOException | NumberFormatException ex) + { + System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, "init-Fehler", ex); + } + }); }); // KebaModbus km = new KebaModbus("localhost", 502); @@ -294,5 +293,35 @@ public class KebaAgent return 502; } + + + + + + + /** + * @param args the command line arguments + */ + public static void main(String[] args) + { + + KebaAgent agent = new KebaAgent(args); + agent.init(); + + + Webserver webserver = new Webserver(agent.getPort()); + try + { + webserver.start(agent); + } + catch (IOException ex) + { + System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex); + } + System.out.println("webserver gestartet"); + } + + + } diff --git a/src/kebaagent/KebaModbus.java b/src/kebaagent/KebaModbus.java index fb944ca..4d5aebc 100644 --- a/src/kebaagent/KebaModbus.java +++ b/src/kebaagent/KebaModbus.java @@ -14,6 +14,8 @@ import com.intelligt.modbus.jlibmodbus.msg.response.ReadHoldingRegistersResponse import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters; import java.net.InetAddress; import java.net.UnknownHostException; +import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; //import oracle.jrockit.jfr.tools.ConCatRepository; @@ -34,49 +36,67 @@ public class KebaModbus private final Map valueMap = new HashMap(); + private static int timeout_ms = 10000; + record RCacheEntry(String name, LocalDateTime ts, Integer value){}; + private Map cache = new HashMap<>(); + +/** + * + * @throws UnknownHostException + * @throws ModbusIOException + */ void init() throws UnknownHostException, ModbusIOException { - TcpParameters tcpParameters = new TcpParameters(); -// TcpParameters tcpParametersSlave = new TcpParameters(); + TcpParameters tcpParameters = new TcpParameters(); +// TcpParameters tcpParametersSlave = new TcpParameters(); + + //tcp parameters have already set by default as in example +// tcpParameters.setHost(InetAddress.getLocalHost()); + tcpParameters.setHost(InetAddress.getByName(ip)); + tcpParameters.setKeepAlive(true); + tcpParameters.setPort(port); +// tcpParametersSlave.setPort(Modbus.TCP_PORT); - //tcp parameters have already set by default as in example -// tcpParameters.setHost(InetAddress.getLocalHost()); - tcpParameters.setHost(InetAddress.getByName(ip)); - tcpParameters.setKeepAlive(true); - tcpParameters.setPort(port); -// tcpParametersSlave.setPort(Modbus.TCP_PORT); - /* - // if you would like to set connection parameters separately, - // you should use another method: createModbusMasterTCP(String host, int port, boolean keepAlive); - m = ModbusMasterFactory.createModbusMasterTCP(tcpParameters); - Modbus.setAutoIncrementTransactionId(true); + // if you would like to set connection parameters separately, + // you should use another method: createModbusMasterTCP(String host, int port, boolean keepAlive); + m = ModbusMasterFactory.createModbusMasterTCP(tcpParameters); + Modbus.setAutoIncrementTransactionId(true); // jaho - m.setResponseTimeout(60000); + m.setResponseTimeout(60000); */ - System.err.printf("try to connect to %s:%d ...", ip, port); + System.err.printf("try to connect to %s:%d ...", ip, port); // ! diese Methode kommt immer sofort zurück. Man muss Ergebnis prüfen! - modbusMaster = connect(tcpParameters, timeout_ms); - if (modbusMaster.isConnected()) - System.err.printf("done\n"); - else + modbusMaster = connect(tcpParameters, timeout_ms); + if (modbusMaster.isConnected()) + System.err.printf("done\n"); + else + { + modbusMaster.connect(); + if (!modbusMaster.isConnected()) { - modbusMaster.connect(); - if (!modbusMaster.isConnected()) - { - System.err.printf("FAILED\n"); - System.exit(1); - } + System.err.printf("FAILED\n"); + System.exit(1); } + } } + +/** + * + * @return + */ public ModbusMaster getModbusMaster() { return modbusMaster; } +/** + * + * @return + */ public Map getValueMap() { return valueMap; @@ -84,7 +104,11 @@ public class KebaModbus - +/** + * Keba-Wallbox über MODBUS verbinden + * @param ip + * @param port + */ public KebaModbus(String ip, int port) { this.ip = ip; @@ -93,9 +117,12 @@ public class KebaModbus - private static int timeout_ms = 10000; - - +/** + * Verbindung herstellen + * @param tcpParameters + * @param responeTimeout_ms + * @return + */ public static ModbusMaster connect(TcpParameters tcpParameters, int responeTimeout_ms) { // if you would like to set connection parameters separately, @@ -108,9 +135,65 @@ public class KebaModbus return m; } + + private static void usage() + { + System.err.println("Reading MODBUS-Register from IPAdr\n\nusage:\nSwgModbus [port]\n"); + } + + +/** + * 4 Bytes lesen. Das geht hier nur mit dem Abruf von 2 16-Bit-Registern + * @param slaveId + * @param register Startregister + * @return + */ + static int readInt32BigEndian(ModbusMaster modbusMaster, int slaveId, int register) throws ModbusProtocolException, ModbusNumberException, ModbusIOException + { + int[] registerValues = modbusMaster.readHoldingRegisters(slaveId, register, 2); + return registerValues[0] << 16 | registerValues[1]; + } + + + + static void sleep(int dur_ms) + { + try + { + Thread.sleep(dur_ms); + } + catch (InterruptedException ex) + { + System.getLogger(KebaModbus.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex); + } + } + + +/** + * MODBUS-Register lesen und Wert in Map speichern + * @param name Name, unter dem der Wert gespeichert wird + * @param register + * @param slaveId + * @param pause_ms Pause vor Abfrage + * @return gelesener Wert + * @throws ModbusProtocolException + * @throws ModbusNumberException + * @throws ModbusIOException + */ + int read(String name, int register, int slaveId, int pause_ms) throws ModbusProtocolException, ModbusNumberException, ModbusIOException + { + sleep(pause_ms); + var val = readInt32BigEndian(getModbusMaster(), slaveId, register); + valueMap.put(name, val); + return val; + } + + + /** + * Testcode * @param args the command line arguments: IP Startadr len [port] */ public static void main(String[] args) @@ -289,53 +372,33 @@ public class KebaModbus - private static void usage() - { - System.err.println("Reading MODBUS-Register from IPAdr\n\nusage:\nSwgModbus [port]\n"); - } - - /** - * 4 Bytes lesen. Das geht hier nur mit dem Abruf von 2 16-Bit-Registern - * @param slaveId - * @param register Startregister - * @return - */ - static int readInt32BigEndian(ModbusMaster modbusMaster, int slaveId, int register) throws ModbusProtocolException, ModbusNumberException, ModbusIOException - { - int[] registerValues = modbusMaster.readHoldingRegisters(slaveId, register, 2); - return registerValues[0] << 16 | registerValues[1]; - } - - - - static void sleep(int dur_ms) - { - try - { - Thread.sleep(dur_ms); - } - catch (InterruptedException ex) - { - System.getLogger(KebaModbus.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex); - } - } - - -/** - * Lesen und in Map speichern - * @param status + * MODBUS-Wert aus cache liefern oder neu abfragen, wenn maxAgeMinutes überschritten ist + * @param jName * @param register * @param slaveId + * @param pause_ms + * @param maxAgeMinutes + * @return + * @throws ModbusIOException * @throws ModbusProtocolException - * @throws ModbusNumberException - * @throws ModbusIOException + * @throws ModbusNumberException */ - int read(String name, int register, int slaveId) throws ModbusProtocolException, ModbusNumberException, ModbusIOException + int readCached(String jName, int register, int slaveId, int pause_ms, int maxAgeMinutes) throws ModbusIOException, ModbusProtocolException, ModbusNumberException { - var val = readInt32BigEndian(getModbusMaster(), slaveId, register); - valueMap.put(name, val); - return val; +// test, ob cache existiert + if (cache.containsKey(jName)) + { +// noch gültig? + if (cache.get(jName).ts.plusMinutes(maxAgeMinutes).isAfter(LocalDateTime.now())) + return cache.get(jName).value; + } + + int intVal = read(jName, register, slaveId, pause_ms); + cache.put(jName, new RCacheEntry(jName, LocalDateTime.now(), intVal)); + return intVal; } + + }