commit 726936dada0ca2f88840b9f34fa9734115b54f66
Author: holz holz@unicon-gmbh.de <>
Date: Thu Aug 13 07:10:05 2026 +0200
2
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..6445686
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+ Builds, tests, and runs the project kebaAgent.
+
+
+
diff --git a/kemaagent.properties b/kemaagent.properties
new file mode 100644
index 0000000..bf0cf48
--- /dev/null
+++ b/kemaagent.properties
@@ -0,0 +1,17 @@
+# Einstellungen UniCon KemaAgent
+
+aliases: links, rechts
+
+links.port: 502
+#links.ip: 192.168.0.28
+links.ip: localhost
+
+
+rechts.port: 503
+#rechts.ip: 192.168.0.29
+rechts.ip: localhost
+
+webserver.port: 50000
+webserver.defaultUri: /keba
+
+
diff --git a/lib/jlibmodbus-1.2.9.4.jar b/lib/jlibmodbus-1.2.9.4.jar
new file mode 100644
index 0000000..d0d8246
Binary files /dev/null and b/lib/jlibmodbus-1.2.9.4.jar differ
diff --git a/lib/json.jar b/lib/json.jar
new file mode 100644
index 0000000..80a6a80
Binary files /dev/null and b/lib/json.jar differ
diff --git a/manifest.mf b/manifest.mf
new file mode 100644
index 0000000..1574df4
--- /dev/null
+++ b/manifest.mf
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+X-COMMENT: Main-Class will be added automatically by build
+
diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml
new file mode 100644
index 0000000..a0326c3
--- /dev/null
+++ b/nbproject/build-impl.xml
@@ -0,0 +1,1771 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set src.dir
+ Must set test.src.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set build.classes.dir
+ Must set dist.javadoc.dir
+ Must set build.test.classes.dir
+ Must set build.test.results.dir
+ Must set build.classes.excludes
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No tests executed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set JVM to use for profiling in profiler.info.jvm
+ Must set profiler agent JVM arguments in profiler.info.jvmargs.agent
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To run this application from the command line without Ant, try:
+
+ java -jar "${dist.jar.resolved}"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must select one file in the IDE or set debug.class
+
+
+
+
+ Must set fix.includes
+
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set profile.class
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+
+
+
+
+ This target only works when run from inside the NetBeans IDE.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+
+ Must select one file in the IDE or set run.class
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set test.includes
+
+
+
+ Some tests failed; see details above.
+
+
+
+ Must select some files in the IDE or set test.class
+ Must select some method in the IDE or set test.method
+
+
+
+ Some tests failed; see details above.
+
+
+
+
+ Must select one file in the IDE or set test.class
+
+
+
+ Must select one file in the IDE or set test.class
+ Must select some method in the IDE or set test.method
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+ Must select one file in the IDE or set applet.url
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nbproject/configs/Kebaagent.properties b/nbproject/configs/Kebaagent.properties
new file mode 100644
index 0000000..e69de29
diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties
new file mode 100644
index 0000000..83d4633
--- /dev/null
+++ b/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+build.xml.data.CRC32=11b719f8
+build.xml.script.CRC32=606a34af
+build.xml.stylesheet.CRC32=f85dc8f2@1.119.0.48
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+nbproject/build-impl.xml.data.CRC32=11b719f8
+nbproject/build-impl.xml.script.CRC32=b9fa6210
+nbproject/build-impl.xml.stylesheet.CRC32=12e0a6c2@1.119.0.48
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644
index 0000000..5faea40
--- /dev/null
+++ b/nbproject/project.properties
@@ -0,0 +1,101 @@
+annotation.processing.enabled=true
+annotation.processing.enabled.in.editor=false
+annotation.processing.processors.list=
+annotation.processing.run.all.processors=true
+annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
+application.title=kebaAgent
+application.vendor=holz
+build.classes.dir=${build.dir}/classes
+build.classes.excludes=**/*.java,**/*.form
+# This directory is removed when the project is cleaned:
+build.dir=build
+build.generated.dir=${build.dir}/generated
+build.generated.sources.dir=${build.dir}/generated-sources
+# Only compile against the classpath explicitly listed here:
+build.sysclasspath=ignore
+build.test.classes.dir=${build.dir}/test/classes
+build.test.results.dir=${build.dir}/test/results
+# Uncomment to specify the preferred debugger connection transport:
+#debug.transport=dt_socket
+debug.classpath=\
+ ${run.classpath}
+debug.modulepath=\
+ ${run.modulepath}
+debug.test.classpath=\
+ ${run.test.classpath}
+debug.test.modulepath=\
+ ${run.test.modulepath}
+# Files in build.classes.dir which should be excluded from distribution jar
+dist.archive.excludes=
+# This directory is removed when the project is cleaned:
+dist.dir=dist
+dist.jar=${dist.dir}/kebaAgent.jar
+dist.javadoc.dir=${dist.dir}/javadoc
+dist.jlink.dir=${dist.dir}/jlink
+dist.jlink.output=${dist.jlink.dir}/kebaAgent
+endorsed.classpath=
+excludes=
+file.reference.jlibmodbus-1.2.9.4.jar=lib\\jlibmodbus-1.2.9.4.jar
+file.reference.json.jar=lib\\json.jar
+includes=**
+jar.compress=false
+javac.classpath=\
+ ${file.reference.jlibmodbus-1.2.9.4.jar}:\
+ ${file.reference.json.jar}
+# Space-separated list of extra javac options
+javac.compilerargs=
+javac.deprecation=false
+javac.external.vm=true
+javac.modulepath=
+javac.processormodulepath=
+javac.processorpath=\
+ ${javac.classpath}
+javac.source=25
+javac.target=25
+javac.test.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+javac.test.modulepath=\
+ ${javac.modulepath}
+javac.test.processorpath=\
+ ${javac.test.classpath}
+javadoc.additionalparam=
+javadoc.author=false
+javadoc.encoding=${source.encoding}
+javadoc.html5=false
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+# The jlink additional root modules to resolve
+jlink.additionalmodules=
+# The jlink additional command line parameters
+jlink.additionalparam=
+jlink.launcher=true
+jlink.launcher.name=kebaAgent
+main.class=kebaagent.KebaAgent
+manifest.file=manifest.mf
+meta.inf.dir=${src.dir}/META-INF
+mkdist.disabled=false
+platform.active=default_platform
+run.classpath=\
+ ${javac.classpath}:\
+ ${build.classes.dir}
+# Space-separated list of JVM arguments used when running the project.
+# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
+# To set system properties for unit tests define test-sys-prop.name=value:
+run.jvmargs=
+run.modulepath=\
+ ${javac.modulepath}
+run.test.classpath=\
+ ${javac.test.classpath}:\
+ ${build.test.classes.dir}
+run.test.modulepath=\
+ ${javac.test.modulepath}
+source.encoding=UTF-8
+src.dir=src
+test.src.dir=test
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644
index 0000000..5801060
--- /dev/null
+++ b/nbproject/project.xml
@@ -0,0 +1,15 @@
+
+
+ org.netbeans.modules.java.j2seproject
+
+
+ kebaAgent
+
+
+
+
+
+
+
+
+
diff --git a/src/kebaagent/JsonHandler.java b/src/kebaagent/JsonHandler.java
new file mode 100644
index 0000000..07d792e
--- /dev/null
+++ b/src/kebaagent/JsonHandler.java
@@ -0,0 +1,100 @@
+/*
+ */
+package kebaagent;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ *
+ * @author holz
+ */
+class JsonHandler implements HttpHandler
+{
+ private final KebaAgent agent;
+
+ JsonHandler(KebaAgent agent)
+ {
+ this.agent = agent;
+ }
+
+
+ @Override
+ public void handle(HttpExchange httpExchange) throws IOException
+ {
+// nur GET
+ if (!"GET".equals(httpExchange.getRequestMethod()))
+ {
+ return;
+ }
+
+// Parameter ermitteln: path ist Teil hinter Domain: "/t/allowed?..."
+ String uri = httpExchange.getRequestURI().toString();
+ Map params = getParams(uri);
+
+ String callData = agent.getDataAsJson(params);
+// response
+ OutputStream outputStream = httpExchange.getResponseBody();
+// StringBuilder sb = new StringBuilder();
+// sb
+// .append("")
+// .append("")
+// .append("")
+// .append("Hello ")
+// .append(callData)
+// .append("
")
+// .append("")
+// .append("");
+ try
+ {
+// String htmlResponse = sb.toString();
+ String htmlResponse = callData;
+
+// jaho: Header erweitern
+ Headers responseHeaders = httpExchange.getResponseHeaders();
+ responseHeaders.set("Content-Type", "application/json");
+
+// this line is a must (jaho: header senden)
+ httpExchange.sendResponseHeaders(200, htmlResponse.length());
+
+ outputStream.write(htmlResponse.getBytes());
+ outputStream.flush();
+ outputStream.close();
+ }
+ catch (Throwable e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+
+
+ private Map getParams(String uri)
+ {
+// split bei ?
+ List l = new ArrayList<>(List.of(uri.split("\\?")));
+ final Map paramsMap = new HashMap();
+
+ l.removeFirst();
+ l.forEach(xparam ->
+ {
+// key/value extrahieren
+ String[] split = xparam.split("=");
+ String k = split[0];
+ String v = split.length > 1 ? split[1] : "";
+ paramsMap.put(k, v);
+ });
+
+ System.out.println("");
+ return paramsMap;
+ }
+
+}
diff --git a/src/kebaagent/KebaAgent.java b/src/kebaagent/KebaAgent.java
new file mode 100644
index 0000000..e1e3185
--- /dev/null
+++ b/src/kebaagent/KebaAgent.java
@@ -0,0 +1,298 @@
+/*
+Webservice bereitstellen und auf http:GET-Anfrage Werte von KEBA-Wallbox per MODBUS holen
+ */
+package kebaagent;
+
+import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
+import com.intelligt.modbus.jlibmodbus.exception.ModbusNumberException;
+import com.intelligt.modbus.jlibmodbus.exception.ModbusProtocolException;
+import com.intelligt.modbus.jlibmodbus.master.ModbusMaster;
+import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Properties;
+import static kebaagent.KebaModbus.connect;
+import static kebaagent.KebaModbus.readInt32BigEndian;
+import static kebaagent.KebaModbus.sleep;
+import org.json.JSONObject;
+import org.json.JSONArray;
+import org.json.JSONException;
+
+/**
+ *
+ * @author holz
+ */
+public class KebaAgent
+{
+ public static final int WEBSERVER_PORT = 50000;
+
+
+ private Properties prop = new Properties();
+
+
+ public KebaAgent(String[] args)
+ {
+ String propertiesFilename = "kemaagent.properties";
+
+ if (args.length > 0)
+ {
+ List lArgs = List.of(args);
+ if (!lArgs.getLast().startsWith("-"))
+ {
+ propertiesFilename = lArgs.getLast();
+ }
+ }
+
+ System.err.printf("benutzer Einstellungen aus %s\n", propertiesFilename);
+
+ try
+ {
+ prop.load(new FileReader(propertiesFilename));
+ }
+ catch (FileNotFoundException ex)
+ {
+ System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
+ }
+ catch (IOException ex)
+ {
+ System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
+ }
+ }
+
+
+
+/**
+ *
+ * @param alias
+ * @param name
+ * @return
+ */
+ public Optional getPropValue(String alias, String name)
+ {
+ return Optional.of(prop.getProperty(alias + "." + name));
+ }
+
+
+ int getPort()
+ {
+ int defPort = WEBSERVER_PORT;
+ String key = "webserver.port";
+ try
+ {
+ defPort = Integer.parseInt(prop.getProperty(key, ""));
+ }
+ catch (NumberFormatException e)
+ {
+ System.err.printf("Schlüssel %s konnte nicht aus properties eingelesen werden. Es wird Standard %d benutzt.\n", key, defPort);
+ }
+
+ 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
+ * @param params
+ * @return JSON-Daten
+ */
+ String getDataAsJson(Map params)
+ {
+ final int slaveId = 255;
+
+ JSONObject joRoot = new JSONObject();
+ JSONArray jaStations = new JSONArray();
+
+ getAliases()
+ .forEach(name ->
+ {
+
+ KebaModbus kebaModbus = kebaModbusMap.get(name);
+
+ JSONObject joPart = new JSONObject();
+
+ int val;
+ try
+ {
+ if (kebaModbus.initFailed)
+ {
+ joPart.put("error", "initError");
+ return;
+ }
+
+ joPart.put("alias", name);
+
+ sleep(600);
+ String
+ jName = "chargingState";
+ int register = 1000;
+ val = kebaModbus.read(jName, register, slaveId);
+// 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);
+
+ 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);
+
+ sleep(600);
+ jName = "power";
+ register = 1020;
+ val = kebaModbus.read(jName, register, slaveId);
+// 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);
+
+ sleep(600);
+ jName = "RFID";
+ register = 1500;
+ val = kebaModbus.read(jName, register, slaveId);
+// 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);
+
+ sleep(600);
+ jName = "chargedEnergy";
+// angeblich Wh, aber in Wirklichkeit Wh/10
+ register = 1502;
+ val = kebaModbus.read(jName, register, slaveId);
+// 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);
+
+ jaStations.put(joPart);
+ }
+ catch (ModbusProtocolException | ModbusNumberException | ModbusIOException ex)
+ {
+ System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
+// joPart.put("Error", "exception: %s".formatted(ex.getMessage());
+// return "{ error: 'exception: %s'}".formatted(ex.getMessage());
+ }
+ catch (JSONException ex)
+ {
+ System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
+ }
+ });
+
+ try
+ {
+ joRoot.put("stations", jaStations);
+ return joRoot.toString();
+ }
+ catch (JSONException ex)
+ {
+ System.getLogger(KebaAgent.class.getName()).log(System.Logger.Level.ERROR, (String) null, ex);
+ return "{ error: '%s'".formatted(ex.getMessage());
+ }
+ }
+
+
+
+
+
+
+ private void init()
+ {
+ getAliases().forEach(alias ->
+ {
+ getPropValue(alias, "ip").ifPresent(sIP ->
+ {
+ 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);
+ }
+ });
+ });
+
+// KebaModbus km = new KebaModbus("localhost", 502);
+// kebaModbusMap.put("links", km);
+// km.initFailed = true;
+// km.init();
+// km.initFailed = false;
+//
+// km = new KebaModbus("localhost", 503);
+// kebaModbusMap.put("rechts", km);
+// km.initFailed = true;
+// km.init();
+// km.initFailed = false;
+ }
+
+
+/**
+ *
+ * @return Liste von aliases aus den properties
+ */
+ private List getAliases()
+ {
+ String a = prop.getProperty("aliases", "");
+ return List.of(a.split(",")).stream().map(s -> s.trim()).toList();
+ }
+
+
+
+/**
+ * Port aus konfig oder 502
+ * @param alias
+ * @return
+ * @throws NumberFormatException
+ */
+ private int getModbusPort(String alias) throws NumberFormatException
+ {
+ Optional oPort = getPropValue(alias, "port");
+ if (oPort.isPresent())
+ {
+ return Integer.parseInt(oPort.get());
+ }
+ else
+ return 502;
+ }
+
+
+}
diff --git a/src/kebaagent/KebaModbus.java b/src/kebaagent/KebaModbus.java
new file mode 100644
index 0000000..fb944ca
--- /dev/null
+++ b/src/kebaagent/KebaModbus.java
@@ -0,0 +1,341 @@
+/*
+ */
+
+package kebaagent;
+
+import com.intelligt.modbus.jlibmodbus.Modbus;
+import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
+import com.intelligt.modbus.jlibmodbus.exception.ModbusNumberException;
+import com.intelligt.modbus.jlibmodbus.exception.ModbusProtocolException;
+import com.intelligt.modbus.jlibmodbus.master.ModbusMaster;
+import com.intelligt.modbus.jlibmodbus.master.ModbusMasterFactory;
+import com.intelligt.modbus.jlibmodbus.msg.request.ReadHoldingRegistersRequest;
+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.util.HashMap;
+import java.util.Map;
+//import oracle.jrockit.jfr.tools.ConCatRepository;
+
+
+
+/**
+ * Abfrage KEBA-Wallbox
+ * @author jaho
+ */
+public class KebaModbus
+{
+
+ private int port;
+ private String ip;
+ public boolean initFailed;
+ private ModbusMaster modbusMaster;
+
+ private final Map valueMap = new HashMap();
+
+
+
+ void init() throws UnknownHostException, ModbusIOException
+ {
+ 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);
+
+/*
+ // 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);
+*/
+ 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();
+ if (!modbusMaster.isConnected())
+ {
+ System.err.printf("FAILED\n");
+ System.exit(1);
+ }
+ }
+ }
+
+ public ModbusMaster getModbusMaster()
+ {
+ return modbusMaster;
+ }
+
+ public Map getValueMap()
+ {
+ return valueMap;
+ }
+
+
+
+
+ public KebaModbus(String ip, int port)
+ {
+ this.ip = ip;
+ this.port = port;
+ }
+
+
+
+ private static int timeout_ms = 10000;
+
+
+ public static ModbusMaster connect(TcpParameters tcpParameters, int responeTimeout_ms)
+ {
+ // if you would like to set connection parameters separately,
+ // you should use another method: createModbusMasterTCP(String host, int port, boolean keepAlive);
+ ModbusMaster m = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
+ Modbus.setAutoIncrementTransactionId(true);
+ if (responeTimeout_ms > 0)
+ m.setResponseTimeout(responeTimeout_ms);
+
+ return m;
+ }
+
+
+
+ /**
+ * @param args the command line arguments: IP Startadr len [port]
+ */
+ public static void main(String[] args)
+ {
+ if (args.length < 3)
+ {
+ usage();
+ System.exit(1);
+ }
+
+
+ int slaveId = 255; //1;
+
+ int offset = 1022; // Register-offset
+ int quantity = 5; // Anz. Bytes
+ String ipa = "192.168.0.199";
+ int port = Modbus.TCP_PORT; // default 502
+
+// Parameterauswertung
+ try
+ {
+ ipa = args[0];
+ offset = Integer.parseInt(args[1]);
+ quantity = Integer.parseInt(args[2]);
+ if(args.length > 3)
+ port = Integer.parseInt(args[3]);
+
+
+ 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(ipa));
+ tcpParameters.setKeepAlive(true);
+ tcpParameters.setPort(port);
+// tcpParametersSlave.setPort(Modbus.TCP_PORT);
+
+
+ ModbusMaster modbusMaster;
+/*
+ // 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);
+*/
+ System.err.printf("try to connect to %s:%d ...", ipa, 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();
+ if (!modbusMaster.isConnected())
+ {
+ System.err.printf("FAILED\n");
+ System.exit(1);
+ }
+ }
+
+ try
+ {
+ // since 1.2.8
+ if (!modbusMaster.isConnected())
+ {
+ System.err.printf("retry connect to %s:%d ...\n", ipa, port);
+ modbusMaster.connect();
+ }
+
+ System.err.printf("connected to %s:%d, reading Holding Registers %d +%d\n", ipa, port, offset, quantity);
+// Abfrage Holding-Regs
+ // at next string we receive x holdingRegisters from a slave with id of 1 at offset of 0.
+ int[] registerValues = modbusMaster.readHoldingRegisters(slaveId, offset, quantity);
+
+// Ausgabe Adr (dezimal), Wort (dez), h-Byte (dez), L-Byte (dez), Wort(hex), h-Byte(hex), L-Byte(hex)
+ for (int value : registerValues)
+ {
+ System.out.printf("%04d:%05d:%03d:%03d:%04X:%02X:%02X\n", offset++, value, (value & 0xff00) / 256, value & 0xff, value, (value & 0xff00) / 256, value & 0xff);
+ }
+
+
+// Dieser Teil schein nicht zu gehen
+/*
+ // also since 1.2.8.4 you can create your own request and process it with the master
+ offset = 0;
+ ReadHoldingRegistersRequest request = new ReadHoldingRegistersRequest();
+ request.setServerAddress(1);
+ request.setStartAddress(offset);
+ request.setTransactionId(0);
+ ReadHoldingRegistersResponse response = (ReadHoldingRegistersResponse) m.processRequest(request);
+ // you can get either int[] containing register values or byte[] containing raw bytes.
+ System.out.println("-----------------------");
+ for (int value : response.getRegisters())
+ {
+ System.out.println("Address: " + offset++ + ", Value: " + value);
+ }
+*/
+ sleep(600);
+ int register = 1000;
+ int val = readInt32BigEndian(modbusMaster, slaveId, register);
+ System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, val, val);
+
+ sleep(600);
+ register = 1004;
+ val = readInt32BigEndian(modbusMaster, slaveId, register);
+ System.out.printf("Wert für reg %d: %d (0x%04x)\n", register, val, val);
+
+ sleep(600);
+ register = 1014;
+ val = readInt32BigEndian(modbusMaster, slaveId, register);
+ System.out.printf("Wert für reg %d (S/N): %d (0x%04x)\n", register, val, val);
+
+ sleep(600);
+ register = 1008;
+ val = readInt32BigEndian(modbusMaster, slaveId, register);
+ System.out.printf("Wert für reg %d (current 1 [A]): %1.1f (0x%04x)\n", register, val/1000f, val);
+
+ sleep(600);
+ register = 1020;
+ val = readInt32BigEndian(modbusMaster, slaveId, register);
+ System.out.printf("Wert für reg %d (active Power): %1.1f kW (0x%04x)\n", register, val/1e6, val);
+
+ sleep(600);
+ register = 1500;
+ val = readInt32BigEndian(modbusMaster, slaveId, register);
+ System.out.printf("Wert für reg %d (RFID-Card): %d (0x%04x)\n", register, val, val);
+
+ sleep(600);
+// angeblich Wh, aber in Wirklichkeit Wh/10
+ register = 1502;
+ val = readInt32BigEndian(modbusMaster, slaveId, register);
+ System.out.printf("Wert für reg %d (charged energy [kWh]): %1.1f (0x%04x)\n", register, val/10000f, val);
+
+ System.exit(0);
+ } catch (ModbusProtocolException e)
+ {
+ e.printStackTrace();
+ } catch (ModbusNumberException e)
+ {
+ e.printStackTrace();
+ }
+ catch (ModbusIOException e)
+ {
+ e.printStackTrace();
+ }
+ catch (NumberFormatException e)
+ {
+ System.err.println("Parameter ist keine Zahl: " + e.getMessage());
+ }
+ finally
+ {
+ try {
+ modbusMaster.disconnect();
+ } catch (ModbusIOException e) {
+ e.printStackTrace();
+ }
+ System.exit(1);
+ }
+ } catch (RuntimeException e)
+ {
+ throw e;
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ System.exit(1);
+ }
+
+ System.exit(0);
+ } // main
+
+
+
+ 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
+ * @param register
+ * @param slaveId
+ * @throws ModbusProtocolException
+ * @throws ModbusNumberException
+ * @throws ModbusIOException
+ */
+ int read(String name, int register, int slaveId) throws ModbusProtocolException, ModbusNumberException, ModbusIOException
+ {
+ var val = readInt32BigEndian(getModbusMaster(), slaveId, register);
+ valueMap.put(name, val);
+ return val;
+ }
+
+}
diff --git a/src/kebaagent/Webserver.java b/src/kebaagent/Webserver.java
new file mode 100644
index 0000000..37b375c
--- /dev/null
+++ b/src/kebaagent/Webserver.java
@@ -0,0 +1,229 @@
+/*
+ */
+package kebaagent;
+
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpHandlers;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.Request;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+
+/**
+ *
+ * @author holz
+ */
+public class Webserver
+{
+
+ private final int port;
+
+ private KebaAgent agent;
+
+/**
+ * call start()
+ * @see #start()
+ * @param port
+ */
+ public Webserver(int port)
+ {
+ this.port = port;
+ }
+
+
+/**
+ * Start Webserver
+ * @param agent
+ * @throws IOException
+ */
+ public void start(KebaAgent agent) throws IOException
+ {
+ this.agent = agent;
+
+// Create an HttpServer instance on port
+ HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
+
+ String defaultUri = "/keba";
+ Optional pv = agent.getPropValue("webserver", "defaultUri");
+ if (pv.isPresent())
+ defaultUri = pv.get();
+
+// Define a context that serves files from the current directory
+// server.createContext("/", new com.sun.net.httpserver.HttpHandler()
+// {
+// @Override
+// public void handle(com.sun.net.httpserver.HttpExchange exchange) throws IOException
+// {
+// // Handle requests here (simple file serving)
+// System.out.println("recvd: " + exchange);
+//// TODO: müsste jetzt die Response generieren. Wie genau, noch nicht nachvollzogen
+ //// ein komplexeres Bsp unter https://dzone.com/articles/simple-http-server-in-java
+ //// Daraus stammt die Klasse MyHttpHandler unten
+// }
+// });
+
+
+// andere Variante für Handler am Bsp url http://localhost:8000/t/allowed oder http://localhost:8000/t
+// Headers headers = new Headers();
+// headers.set("Allow", "GET");
+// headers.set("Content-Type", "text/plain");
+//
+// HttpHandler allowedResponse = HttpHandlers.of(200, headers, getBody()); // Statuscode, Headers, body
+
+// Trivialhandler für einfache Antwort
+ HttpHandler deniedResponse = HttpHandlers.of(401, Headers.of("Deny", "GET"), "Denied");
+
+ JsonHandler allowedResponse = new JsonHandler(agent);
+
+
+// welche URL ist erlaubt?
+ final String fDefUri = defaultUri;
+ Predicate findAllowedPath = r ->
+// r.getRequestURI().getPath().equals("/t/allowed");
+ r.getRequestURI().getPath().equals(fDefUri);
+
+ HttpHandler handler = HttpHandlers.handleOrElse(findAllowedPath, allowedResponse, deniedResponse);
+// context ist weiter oben angebunden (ab /t)
+// server.createContext("/t", handler);
+ server.createContext(defaultUri, handler);
+
+// ggf. 2. Handler für / bauen (Infoseite)
+
+
+// mit MyHttpHandler, ziemlich buggy und unvollständig von https://dzone.com/articles/simple-http-server-in-java
+// MyHttpHandler myHandler = new MyHttpHandler(agent);
+// server.createContext("/m", myHandler);
+
+
+ // Start the server
+ server.start();
+ System.out.printf("Server started on port %d\n", port);
+
+
+ }
+
+
+// private String getBody()
+// {
+// return "{ 'info': 'hello'}";
+// }
+//
+
+
+
+private static class MyHttpHandler implements HttpHandler
+{
+
+ private KebaAgent agent;
+
+ private MyHttpHandler(KebaAgent a)
+ {
+ agent = a;
+ }
+
+/**
+ * zu implementierende Methode
+ * @param httpExchange
+ * @throws IOException
+ */
+ @Override
+ public void handle(HttpExchange httpExchange) throws IOException
+ {
+ String requestParamValue = null;
+ System.out.println("MyHttpHandler.handle() httpExchange " + httpExchange);
+ String requestMethod = httpExchange.getRequestMethod();
+ System.out.println("MyHttpHandler.handle().getRequestMethod(): " + requestMethod);
+ if ("GET".equals(httpExchange.getRequestMethod()))
+ {
+ requestParamValue = handleGetRequest(httpExchange);
+ System.out.println("MyHttpHandler.handle() GET: requestParamValue = " + requestParamValue);
+ }
+// TODO: nicht im Bsp.
+// else if("POST".equals(httpExchange))
+// {
+// requestParamValue = handlePostRequest(httpExchange);
+// }
+ handleResponse(httpExchange, requestParamValue);
+ }
+
+
+
+/**
+ * hier hat der Autor Schwachsinn produziert
+ * @param httpExchange
+ * @return
+ */
+ private String handleGetRequest(HttpExchange httpExchange)
+ {
+ String path = httpExchange.getRequestURI().toString();
+
+ try
+ {
+// return path.split("\\?")[1]
+ return path.split("/")[1]
+// .split("=")[1];
+ ;
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ return e.getMessage();
+ }
+ }
+
+
+
+ private void handleResponse(HttpExchange httpExchange, String requestParamValue) throws IOException
+ {
+ System.out.println("MyHttpHandler.handleResponse ");
+ OutputStream outputStream = httpExchange.getResponseBody();
+ StringBuilder htmlBuilder = new StringBuilder();
+ htmlBuilder
+ .append("")
+ .append("")
+ .append("")
+ .append("Hello ")
+ .append(requestParamValue)
+ .append("
")
+ .append("")
+ .append("");
+ // encode HTML content
+// apache commons text + lang
+ try
+ {
+// Schwachsinn: Anwendung auf den ganzen HTML-Code
+// String htmlResponse = StringEscapeUtils.escapeHtml4(htmlBuilder.toString());
+// So OK
+ String htmlResponse = htmlBuilder.toString();
+
+// jaho: Header erweitern
+ Headers responseHeaders = httpExchange.getResponseHeaders();
+ responseHeaders.set("Content-Type", "text/plain");
+
+
+// this line is a must (jaho: header senden)
+ httpExchange.sendResponseHeaders(200, htmlResponse.length());
+
+
+ outputStream.write(htmlResponse.getBytes());
+ outputStream.flush();
+ outputStream.close();
+
+ } catch (Throwable e)
+ {
+ e.printStackTrace();
+ }
+
+
+ }
+
+} // MyHttpHandler
+
+
+
+}