Compare commits

..

2 Commits

Author SHA1 Message Date
holz holz@unicon-gmbh.de 124330db9a Merge kebaAgent/master 2026-08-13 09:19:14 +02:00
holz holz@unicon-gmbh.de 9bf9b9a66f Optimierung der Abfragezeiten, Teilcaching
zusätzlich Timestamp pro alias
2026-08-13 09:13:16 +02:00
3 changed files with 230 additions and 137 deletions
+1
View File
@@ -81,6 +81,7 @@ main.class=kebaagent.KebaAgent
manifest.file=manifest.mf manifest.file=manifest.mf
meta.inf.dir=${src.dir}/META-INF meta.inf.dir=${src.dir}/META-INF
mkdist.disabled=false mkdist.disabled=false
no.dependencies=true
platform.active=default_platform platform.active=default_platform
run.classpath=\ run.classpath=\
${javac.classpath}:\ ${javac.classpath}:\
+81 -52
View File
@@ -13,6 +13,7 @@ import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@@ -26,7 +27,7 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
/** /**
* * In der Konfigurationsdatei können mehrere Wallbox-Ports aka alias definiert werden. Über die wird die Konfig separiert.
* @author holz * @author holz
*/ */
public class KebaAgent public class KebaAgent
@@ -35,8 +36,18 @@ public class KebaAgent
private Properties prop = new Properties(); private Properties prop = new Properties();
/**
* Liste von Instanzen über Namen
*/
private final Map<String, KebaModbus> kebaModbusMap = new HashMap<>();
/**
* Instanz erstellen und properties einlesen
* @see #init()
* @param args
*/
public KebaAgent(String[] args) public KebaAgent(String[] args)
{ {
String propertiesFilename = "kemaagent.properties"; String propertiesFilename = "kemaagent.properties";
@@ -80,6 +91,10 @@ public class KebaAgent
} }
/**
* Webserver Port aus config ermitteln und liefern
* @return
*/
int getPort() int getPort()
{ {
int defPort = WEBSERVER_PORT; int defPort = WEBSERVER_PORT;
@@ -97,34 +112,6 @@ public class KebaAgent
} }
/**
* Liste von Instanzen über Namen
*/
private final Map<String, KebaModbus> 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");
}
/** /**
@@ -139,6 +126,7 @@ public class KebaAgent
JSONObject joRoot = new JSONObject(); JSONObject joRoot = new JSONObject();
JSONArray jaStations = new JSONArray(); JSONArray jaStations = new JSONArray();
// über alle Wallboxen ...
getAliases() getAliases()
.forEach(name -> .forEach(name ->
{ {
@@ -147,58 +135,67 @@ public class KebaAgent
JSONObject joPart = new JSONObject(); JSONObject joPart = new JSONObject();
int val; int intVal;
try try
{ {
if (kebaModbus.initFailed) if (kebaModbus.initFailed)
{ {
joPart.put("error", "initError"); joPart.put("error", "initError");
jaStations.put(joPart);
return; return;
} }
joPart.put("alias", name); 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 String
jName = "chargingState"; jName = "chargingState";
int register = 1000; int register = 1000;
val = kebaModbus.read(jName, register, slaveId); intVal = kebaModbus.read(jName, register, slaveId, pause_ms);
// val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register); // val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register);
System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, val, val); System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, intVal, intVal);
joPart.put(jName, val); joPart.put(jName, intVal);
sleep(600); // cache-Zeit für statische Daten (S/N etc)
int maxAgeMinutes = 60;
// sleep(600);
jName = "s/n"; jName = "s/n";
register = 1014; register = 1014;
val = kebaModbus.read(jName, register, slaveId); intVal = kebaModbus.readCached(jName, register, slaveId, pause_ms, maxAgeMinutes);
System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, val, val); System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, intVal, intVal);
joPart.put(jName, val); joPart.put(jName, intVal);
sleep(600); // akt. Leistung
// sleep(600);
jName = "power"; jName = "power";
register = 1020; register = 1020;
val = kebaModbus.read(jName, register, slaveId); intVal = kebaModbus.read(jName, register, slaveId, pause_ms);
// val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register); // 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); System.out.printf("Wert für reg %d (active Power): %1.1f kW (0x%04x)\n", register, intVal/1e6, intVal);
joPart.put(jName, val/1e6); joPart.put(jName, intVal/1e6);
sleep(600); // sleep(600);
jName = "RFID"; jName = "RFID";
register = 1500; register = 1500;
val = kebaModbus.read(jName, register, slaveId); intVal = kebaModbus.read(jName, register, slaveId, pause_ms);
// val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register); // val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register);
System.out.printf("Wert für reg %d (RFID-Card): %d (0x%04x)\n", register, val, val); System.out.printf("Wert für reg %d (RFID-Card): %d (0x%04x)\n", register, intVal, intVal);
joPart.put(jName, val); joPart.put(jName, intVal);
sleep(600); // sleep(600);
jName = "chargedEnergy"; jName = "chargedEnergy";
// angeblich Wh, aber in Wirklichkeit Wh/10 // angeblich Wh, aber in Wirklichkeit Wh/10
register = 1502; register = 1502;
val = kebaModbus.read(jName, register, slaveId); intVal = kebaModbus.read(jName, register, slaveId, pause_ms);
// val = readInt32BigEndian(kebaModbus.getModbusMaster(), slaveId, register); // 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); System.out.printf("Wert für reg %d (charged energy [kWh]): %1.1f (0x%04x)\n", register, intVal/10000f, intVal);
joPart.put(jName, val/10000f); joPart.put(jName, intVal/10000f);
joPart.put("ts", LocalDateTime.now().withNano(0).toString());
jaStations.put(joPart); jaStations.put(joPart);
} }
catch (ModbusProtocolException | ModbusNumberException | ModbusIOException ex) catch (ModbusProtocolException | ModbusNumberException | ModbusIOException ex)
@@ -229,7 +226,9 @@ public class KebaAgent
/**
*
*/
private void init() private void init()
{ {
getAliases().forEach(alias -> getAliases().forEach(alias ->
@@ -246,7 +245,7 @@ public class KebaAgent
} }
catch (UnknownHostException | ModbusIOException | NumberFormatException ex) catch (UnknownHostException | ModbusIOException | NumberFormatException ex)
{ {
System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex); System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, "init-Fehler", ex);
} }
}); });
}); });
@@ -295,4 +294,34 @@ public class KebaAgent
} }
/**
* @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");
}
} }
+107 -44
View File
@@ -14,6 +14,8 @@ import com.intelligt.modbus.jlibmodbus.msg.response.ReadHoldingRegistersResponse
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters; import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
//import oracle.jrockit.jfr.tools.ConCatRepository; //import oracle.jrockit.jfr.tools.ConCatRepository;
@@ -34,8 +36,17 @@ public class KebaModbus
private final Map<String, Integer> valueMap = new HashMap(); private final Map<String, Integer> valueMap = new HashMap();
private static int timeout_ms = 10000;
record RCacheEntry(String name, LocalDateTime ts, Integer value){};
private Map<String, RCacheEntry> cache = new HashMap<>();
/**
*
* @throws UnknownHostException
* @throws ModbusIOException
*/
void init() throws UnknownHostException, ModbusIOException void init() throws UnknownHostException, ModbusIOException
{ {
TcpParameters tcpParameters = new TcpParameters(); TcpParameters tcpParameters = new TcpParameters();
@@ -72,11 +83,20 @@ public class KebaModbus
} }
} }
/**
*
* @return
*/
public ModbusMaster getModbusMaster() public ModbusMaster getModbusMaster()
{ {
return modbusMaster; return modbusMaster;
} }
/**
*
* @return
*/
public Map<String, Integer> getValueMap() public Map<String, Integer> getValueMap()
{ {
return valueMap; return valueMap;
@@ -84,7 +104,11 @@ public class KebaModbus
/**
* Keba-Wallbox über MODBUS verbinden
* @param ip
* @param port
*/
public KebaModbus(String ip, int port) public KebaModbus(String ip, int port)
{ {
this.ip = ip; 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) public static ModbusMaster connect(TcpParameters tcpParameters, int responeTimeout_ms)
{ {
// if you would like to set connection parameters separately, // if you would like to set connection parameters separately,
@@ -110,7 +137,63 @@ public class KebaModbus
private static void usage()
{
System.err.println("Reading MODBUS-Register from IPAdr\n\nusage:\nSwgModbus <IPAdr> <AdrOffset> <count> [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] * @param args the command line arguments: IP Startadr len [port]
*/ */
public static void main(String[] args) 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 <IPAdr> <AdrOffset> <count> [port]\n");
}
/** /**
* 4 Bytes lesen. Das geht hier nur mit dem Abruf von 2 16-Bit-Registern * MODBUS-Wert aus cache liefern oder neu abfragen, wenn maxAgeMinutes überschritten ist
* @param slaveId * @param jName
* @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
* @param register * @param register
* @param slaveId * @param slaveId
* @param pause_ms
* @param maxAgeMinutes
* @return
* @throws ModbusIOException
* @throws ModbusProtocolException * @throws ModbusProtocolException
* @throws ModbusNumberException * @throws ModbusNumberException
* @throws ModbusIOException
*/ */
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); // test, ob cache existiert
valueMap.put(name, val); if (cache.containsKey(jName))
return val; {
// 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;
}
} }