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
meta.inf.dir=${src.dir}/META-INF
mkdist.disabled=false
no.dependencies=true
platform.active=default_platform
run.classpath=\
${javac.classpath}:\
+96 -67
View File
@@ -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<String, KebaModbus> 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<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");
}
/**
* 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");
}
}
+133 -70
View File
@@ -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<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
{
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<String, Integer> 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 <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]
*/
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
* @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;
}
}