2
This commit is contained in:
@@ -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,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
|
||||
@@ -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,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<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));
|
||||
}
|
||||
|
||||
|
||||
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<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
|
||||
* @param params
|
||||
* @return JSON-Daten
|
||||
*/
|
||||
String getDataAsJson(Map<String, String> 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<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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<String, Integer> 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<String, Integer> 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 <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
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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