Compare commits
4 Commits
main
..
124330db9a
| Author | SHA1 | Date | |
|---|---|---|---|
| 124330db9a | |||
| 9bf9b9a66f | |||
| c560160dd0 | |||
| 726936dada |
-10
@@ -1,10 +0,0 @@
|
|||||||
# ---> NetBeans
|
|
||||||
**/nbproject/private/
|
|
||||||
**/nbproject/Makefile-*.mk
|
|
||||||
**/nbproject/Package-*.bash
|
|
||||||
build/
|
|
||||||
nbbuild/
|
|
||||||
dist/
|
|
||||||
nbdist/
|
|
||||||
.nb-gradle/
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Copyright (C) 2026 by UniCon
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- You may freely edit this file. See commented blocks below for -->
|
||||||
|
<!-- some examples of how to customize the build. -->
|
||||||
|
<!-- (If you delete it and reopen the project it will be recreated.) -->
|
||||||
|
<!-- By default, only the Clean and Build commands use this build script. -->
|
||||||
|
<!-- Commands such as Run, Debug, and Test only use this build script if -->
|
||||||
|
<!-- the Compile on Save feature is turned off for the project. -->
|
||||||
|
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
|
||||||
|
<!-- in the project's Project Properties dialog box.-->
|
||||||
|
<project name="kebaAgent" default="default" basedir=".">
|
||||||
|
<description>Builds, tests, and runs the project kebaAgent.</description>
|
||||||
|
<import file="nbproject/build-impl.xml"/>
|
||||||
|
<!--
|
||||||
|
|
||||||
|
There exist several targets which are by default empty and which can be
|
||||||
|
used for execution of your tasks. These targets are usually executed
|
||||||
|
before and after some main targets. They are:
|
||||||
|
|
||||||
|
-pre-init: called before initialization of project properties
|
||||||
|
-post-init: called after initialization of project properties
|
||||||
|
-pre-compile: called before javac compilation
|
||||||
|
-post-compile: called after javac compilation
|
||||||
|
-pre-compile-single: called before javac compilation of single file
|
||||||
|
-post-compile-single: called after javac compilation of single file
|
||||||
|
-pre-compile-test: called before javac compilation of JUnit tests
|
||||||
|
-post-compile-test: called after javac compilation of JUnit tests
|
||||||
|
-pre-compile-test-single: called before javac compilation of single JUnit test
|
||||||
|
-post-compile-test-single: called after javac compilation of single JUunit test
|
||||||
|
-pre-jar: called before JAR building
|
||||||
|
-post-jar: called after JAR building
|
||||||
|
-post-clean: called after cleaning build products
|
||||||
|
|
||||||
|
(Targets beginning with '-' are not intended to be called on their own.)
|
||||||
|
|
||||||
|
Example of inserting an obfuscator after compilation could look like this:
|
||||||
|
|
||||||
|
<target name="-post-compile">
|
||||||
|
<obfuscate>
|
||||||
|
<fileset dir="${build.classes.dir}"/>
|
||||||
|
</obfuscate>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
For list of available properties check the imported
|
||||||
|
nbproject/build-impl.xml file.
|
||||||
|
|
||||||
|
|
||||||
|
Another way to customize the build is by overriding existing main targets.
|
||||||
|
The targets of interest are:
|
||||||
|
|
||||||
|
-init-macrodef-javac: defines macro for javac compilation
|
||||||
|
-init-macrodef-junit: defines macro for junit execution
|
||||||
|
-init-macrodef-debug: defines macro for class debugging
|
||||||
|
-init-macrodef-java: defines macro for class execution
|
||||||
|
-do-jar: JAR building
|
||||||
|
run: execution of project
|
||||||
|
-javadoc-build: Javadoc generation
|
||||||
|
test-report: JUnit report generation
|
||||||
|
|
||||||
|
An example of overriding the target for project execution could look like this:
|
||||||
|
|
||||||
|
<target name="run" depends="kebaAgent-impl.jar">
|
||||||
|
<exec dir="bin" executable="launcher.exe">
|
||||||
|
<arg file="${dist.jar}"/>
|
||||||
|
</exec>
|
||||||
|
</target>
|
||||||
|
|
||||||
|
Notice that the overridden target depends on the jar target and not only on
|
||||||
|
the compile target as the regular run target does. Again, for a list of available
|
||||||
|
properties which you can use, check the target you are overriding in the
|
||||||
|
nbproject/build-impl.xml file.
|
||||||
|
|
||||||
|
-->
|
||||||
|
</project>
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
|
X-COMMENT: Main-Class will be added automatically by build
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
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
|
||||||
|
no.dependencies=true
|
||||||
|
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
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://www.netbeans.org/ns/project/1">
|
||||||
|
<type>org.netbeans.modules.java.j2seproject</type>
|
||||||
|
<configuration>
|
||||||
|
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
|
||||||
|
<name>kebaAgent</name>
|
||||||
|
<source-roots>
|
||||||
|
<root id="src.dir"/>
|
||||||
|
</source-roots>
|
||||||
|
<test-roots>
|
||||||
|
<root id="test.src.dir"/>
|
||||||
|
</test-roots>
|
||||||
|
</data>
|
||||||
|
</configuration>
|
||||||
|
</project>
|
||||||
@@ -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<String, String> params = getParams(uri);
|
||||||
|
|
||||||
|
String callData = agent.getDataAsJson(params);
|
||||||
|
// response
|
||||||
|
OutputStream outputStream = httpExchange.getResponseBody();
|
||||||
|
// StringBuilder sb = new StringBuilder();
|
||||||
|
// sb
|
||||||
|
// .append("<html>")
|
||||||
|
// .append("<body>")
|
||||||
|
// .append("<h1>")
|
||||||
|
// .append("Hello ")
|
||||||
|
// .append(callData)
|
||||||
|
// .append("</h1>")
|
||||||
|
// .append("</body>")
|
||||||
|
// .append("</html>");
|
||||||
|
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<String, String> getParams(String uri)
|
||||||
|
{
|
||||||
|
// split bei ?
|
||||||
|
List<String> l = new ArrayList<>(List.of(uri.split("\\?")));
|
||||||
|
final Map<String, String> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
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.time.LocalDateTime;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In der Konfigurationsdatei können mehrere Wallbox-Ports aka alias definiert werden. Über die wird die Konfig separiert.
|
||||||
|
* @author holz
|
||||||
|
*/
|
||||||
|
public class KebaAgent
|
||||||
|
{
|
||||||
|
public static final int WEBSERVER_PORT = 50000;
|
||||||
|
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
if (args.length > 0)
|
||||||
|
{
|
||||||
|
List<String> 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<String> getPropValue(String alias, String name)
|
||||||
|
{
|
||||||
|
return Optional.of(prop.getProperty(alias + "." + name));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Webserver Port aus config ermitteln und liefern
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* alle wichtigen modbus-Daten beschaffen
|
||||||
|
* @param params
|
||||||
|
* @return JSON-Daten
|
||||||
|
*/
|
||||||
|
String getDataAsJson(Map<String, String> params)
|
||||||
|
{
|
||||||
|
final int slaveId = 255;
|
||||||
|
|
||||||
|
JSONObject joRoot = new JSONObject();
|
||||||
|
JSONArray jaStations = new JSONArray();
|
||||||
|
|
||||||
|
// über alle Wallboxen ...
|
||||||
|
getAliases()
|
||||||
|
.forEach(name ->
|
||||||
|
{
|
||||||
|
|
||||||
|
KebaModbus kebaModbus = kebaModbusMap.get(name);
|
||||||
|
|
||||||
|
JSONObject joPart = new JSONObject();
|
||||||
|
|
||||||
|
int intVal;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (kebaModbus.initFailed)
|
||||||
|
{
|
||||||
|
joPart.put("error", "initError");
|
||||||
|
jaStations.put(joPart);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
joPart.put("alias", name);
|
||||||
|
int pause_ms = 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;
|
||||||
|
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, intVal, intVal);
|
||||||
|
joPart.put(jName, intVal);
|
||||||
|
|
||||||
|
// cache-Zeit für statische Daten (S/N etc)
|
||||||
|
int maxAgeMinutes = 60;
|
||||||
|
|
||||||
|
// sleep(600);
|
||||||
|
jName = "s/n";
|
||||||
|
register = 1014;
|
||||||
|
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);
|
||||||
|
|
||||||
|
// akt. Leistung
|
||||||
|
// sleep(600);
|
||||||
|
jName = "power";
|
||||||
|
register = 1020;
|
||||||
|
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, intVal/1e6, intVal);
|
||||||
|
joPart.put(jName, intVal/1e6);
|
||||||
|
|
||||||
|
// sleep(600);
|
||||||
|
jName = "RFID";
|
||||||
|
register = 1500;
|
||||||
|
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, intVal, intVal);
|
||||||
|
joPart.put(jName, intVal);
|
||||||
|
|
||||||
|
// sleep(600);
|
||||||
|
jName = "chargedEnergy";
|
||||||
|
// angeblich Wh, aber in Wirklichkeit Wh/10
|
||||||
|
register = 1502;
|
||||||
|
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, intVal/10000f, intVal);
|
||||||
|
joPart.put(jName, intVal/10000f);
|
||||||
|
|
||||||
|
|
||||||
|
joPart.put("ts", LocalDateTime.now().withNano(0).toString());
|
||||||
|
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, "init-Fehler", 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<String> 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<String> oPort = getPropValue(alias, "port");
|
||||||
|
if (oPort.isPresent())
|
||||||
|
{
|
||||||
|
return Integer.parseInt(oPort.get());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
/*
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
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<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();
|
||||||
|
|
||||||
|
//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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public ModbusMaster getModbusMaster()
|
||||||
|
{
|
||||||
|
return modbusMaster;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Map<String, Integer> getValueMap()
|
||||||
|
{
|
||||||
|
return valueMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keba-Wallbox über MODBUS verbinden
|
||||||
|
* @param ip
|
||||||
|
* @param port
|
||||||
|
*/
|
||||||
|
public KebaModbus(String ip, int port)
|
||||||
|
{
|
||||||
|
this.ip = ip;
|
||||||
|
this.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
int readCached(String jName, int register, int slaveId, int pause_ms, int maxAgeMinutes) throws ModbusIOException, ModbusProtocolException, ModbusNumberException
|
||||||
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<String> 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<irgendwas anderes>
|
||||||
|
// 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<Request> 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("<html>")
|
||||||
|
.append("<body>")
|
||||||
|
.append("<h1>")
|
||||||
|
.append("Hello ")
|
||||||
|
.append(requestParamValue)
|
||||||
|
.append("</h1>")
|
||||||
|
.append("</body>")
|
||||||
|
.append("</html>");
|
||||||
|
// 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user