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 + + + +}