You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2019/10/16 09:36:42 UTC
[plc4x] branch feature/plc-simulator updated: - Added inputs and
outputs to the context - Refactored the Simulator and Server APIs - Made
the WaterTankSimulationModule do something a little more complicated
This is an automated email from the ASF dual-hosted git repository.
cdutz pushed a commit to branch feature/plc-simulator
in repository https://gitbox.apache.org/repos/asf/plc4x.git
The following commit(s) were added to refs/heads/feature/plc-simulator by this push:
new 9a17312 - Added inputs and outputs to the context - Refactored the Simulator and Server APIs - Made the WaterTankSimulationModule do something a little more complicated
9a17312 is described below
commit 9a1731286b1b6f4f772ff669b11adb9aca567b54
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Wed Oct 16 09:44:32 2019 +0200
- Added inputs and outputs to the context
- Refactored the Simulator and Server APIs
- Made the WaterTankSimulationModule do something a little more complicated
---
sandbox/plc-simulator/pom.xml | 5 +
.../org/apache/plc4x/simulator/PlcSimulator.java | 27 +++--
.../org/apache/plc4x/simulator/model/Context.java | 112 +++++++++++++++++++++
.../plc4x/simulator/server/ServerModule.java | 5 +
.../plc4x/simulator/server/s7/S7ServerModule.java | 13 ++-
.../server/s7/protocol/S7Step7ServerAdapter.java | 13 ++-
.../simulator/simulation/SimulationModule.java | 5 +-
.../watertank/WaterTankSimulationModule.java | 65 ++++++++++--
8 files changed, 221 insertions(+), 24 deletions(-)
diff --git a/sandbox/plc-simulator/pom.xml b/sandbox/plc-simulator/pom.xml
index fcb7a7a..238c59d 100644
--- a/sandbox/plc-simulator/pom.xml
+++ b/sandbox/plc-simulator/pom.xml
@@ -77,6 +77,11 @@
<version>0.5.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-collections4</artifactId>
+ </dependency>
+
<!-- Explicitly override the scope to compile to include these -->
<dependency>
<groupId>ch.qos.logback</groupId>
diff --git a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/PlcSimulator.java b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/PlcSimulator.java
index aa55608..26aab3a 100644
--- a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/PlcSimulator.java
+++ b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/PlcSimulator.java
@@ -18,6 +18,7 @@ under the License.
*/
package org.apache.plc4x.simulator;
+import org.apache.plc4x.simulator.model.Context;
import org.apache.plc4x.simulator.server.ServerModule;
import org.apache.plc4x.simulator.simulation.SimulationModule;
import org.slf4j.Logger;
@@ -41,17 +42,7 @@ public class PlcSimulator {
}
private PlcSimulator(ClassLoader classLoader) {
-
- // Initialize all the server modules.
- LOGGER.info("Initializing Server Modules:");
- serverModules = new TreeMap<>();
- ServiceLoader<ServerModule> serverModuleLoader = ServiceLoader.load(ServerModule.class, classLoader);
- for (ServerModule serverModule : serverModuleLoader) {
- LOGGER.info(String.format("Initializing server module: %s ...", serverModule.getName()));
- serverModules.put(serverModule.getName(), serverModule);
- LOGGER.info("Initialized");
- }
- LOGGER.info("Finished Initializing Server Modules\n");
+ Map<String, Context> contexts = new TreeMap<>();
// Initialize all the simulation modules.
LOGGER.info("Initializing Simulation Modules:");
@@ -60,10 +51,24 @@ public class PlcSimulator {
for (SimulationModule simulationModule : simulationModuleLoader) {
LOGGER.info(String.format("Initializing simulation module: %s ...", simulationModule.getName()));
simulationModules.put(simulationModule.getName(), simulationModule);
+ contexts.put(simulationModule.getName(), simulationModule.getContext());
LOGGER.info("Initialized");
}
LOGGER.info("Finished Initializing Simulation Modules\n");
+ // Initialize all the server modules.
+ LOGGER.info("Initializing Server Modules:");
+ serverModules = new TreeMap<>();
+ ServiceLoader<ServerModule> serverModuleLoader = ServiceLoader.load(ServerModule.class, classLoader);
+ for (ServerModule serverModule : serverModuleLoader) {
+ LOGGER.info(String.format("Initializing server module: %s ...", serverModule.getName()));
+ serverModules.put(serverModule.getName(), serverModule);
+ // Inject the contexts.
+ serverModule.setContexts(contexts);
+ LOGGER.info("Initialized");
+ }
+ LOGGER.info("Finished Initializing Server Modules\n");
+
running = true;
}
diff --git a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/model/Context.java b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/model/Context.java
new file mode 100644
index 0000000..be8faad
--- /dev/null
+++ b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/model/Context.java
@@ -0,0 +1,112 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+*/
+package org.apache.plc4x.simulator.model;
+
+import org.apache.commons.collections4.list.FixedSizeList;
+import org.apache.commons.collections4.map.FixedSizeMap;
+
+import java.util.*;
+
+public class Context {
+
+ private final FixedSizeMap<String, Object> memory;
+ private final FixedSizeList<Boolean> digitalInputs;
+ private final FixedSizeList<Long> analogInputs;
+ private final FixedSizeList<Boolean> digitalOutputs;
+ private final FixedSizeList<Long> analogOutputs;
+
+ public Context(FixedSizeMap<String, Object> memory,
+ FixedSizeList<Boolean> digitalInputs, FixedSizeList<Long> analogInputs,
+ FixedSizeList<Boolean> digitalOutputs, FixedSizeList<Long> analogOutputs) {
+ this.memory = memory;
+ this.digitalInputs = digitalInputs;
+ this.analogInputs = analogInputs;
+ this.digitalOutputs = digitalOutputs;
+ this.analogOutputs = analogOutputs;
+ }
+
+ public Map<String, Object> getMemory() {
+ return memory;
+ }
+
+ public List<Boolean> getDigitalInputs() {
+ return digitalInputs;
+ }
+
+ public List<Long> getAnalogInputs() {
+ return analogInputs;
+ }
+
+ public List<Boolean> getDigitalOutputs() {
+ return digitalOutputs;
+ }
+
+ public List<Long> getAnalogOutputs() {
+ return analogOutputs;
+ }
+
+ public static class ContextBuilder {
+ private final Map<String, Object> memory;
+ private final List<Boolean> digitalInputs;
+ private final List<Long> analogInputs;
+ private final List<Boolean> digitalOutputs;
+ private final List<Long> analogOutputs;
+
+ public ContextBuilder() {
+ memory = new TreeMap<>();
+ digitalInputs = new LinkedList<>();
+ analogInputs = new LinkedList<>();
+ digitalOutputs = new LinkedList<>();
+ analogOutputs = new LinkedList<>();
+ }
+
+ public ContextBuilder addMemoryVariable(String name, Object defaultValue) {
+ memory.put(name, defaultValue);
+ return this;
+ }
+
+ public ContextBuilder addDigitalInput(Boolean defaultValue) {
+ digitalInputs.add(defaultValue);
+ return this;
+ }
+
+ public ContextBuilder addAnalogInput(Long defaultValue) {
+ analogInputs.add(defaultValue);
+ return this;
+ }
+
+ public ContextBuilder addDigitalOutput(Boolean defaultValue) {
+ digitalOutputs.add(defaultValue);
+ return this;
+ }
+
+ public ContextBuilder addAnalogOutput(Long defaultValue) {
+ analogOutputs.add(defaultValue);
+ return this;
+ }
+
+ public Context build() {
+ return new Context(FixedSizeMap.fixedSizeMap(memory),
+ FixedSizeList.fixedSizeList(digitalInputs), FixedSizeList.fixedSizeList(analogInputs),
+ FixedSizeList.fixedSizeList(digitalOutputs), FixedSizeList.fixedSizeList(analogOutputs));
+ }
+
+ }
+
+}
diff --git a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/ServerModule.java b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/ServerModule.java
index da67ef6..97bf1c0 100644
--- a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/ServerModule.java
+++ b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/ServerModule.java
@@ -19,6 +19,9 @@ under the License.
package org.apache.plc4x.simulator.server;
import org.apache.plc4x.simulator.exceptions.SimulatorExcepiton;
+import org.apache.plc4x.simulator.model.Context;
+
+import java.util.Map;
public interface ServerModule {
@@ -27,6 +30,8 @@ public interface ServerModule {
*/
String getName();
+ void setContexts(Map<String, Context> contexts);
+
void start() throws SimulatorExcepiton;
void stop() throws SimulatorExcepiton;
diff --git a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/S7ServerModule.java b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/S7ServerModule.java
index ed1b160..483acc6 100644
--- a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/S7ServerModule.java
+++ b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/S7ServerModule.java
@@ -28,15 +28,21 @@ import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.apache.plc4x.java.s7.protocol.S7Step7Protocol;
import org.apache.plc4x.simulator.exceptions.SimulatorExcepiton;
+import org.apache.plc4x.simulator.model.Context;
import org.apache.plc4x.simulator.server.ServerModule;
import org.apache.plc4x.simulator.server.s7.protocol.S7Step7ServerAdapter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
public class S7ServerModule implements ServerModule {
private static final int ISO_ON_TCP_PORT = 102;
private EventLoopGroup loopGroup;
private EventLoopGroup workerGroup;
+ private List<Context> contexts;
@Override
public String getName() {
@@ -44,6 +50,11 @@ public class S7ServerModule implements ServerModule {
}
@Override
+ public void setContexts(Map<String, Context> contexts) {
+ this.contexts = new ArrayList<>(contexts.values());
+ }
+
+ @Override
public void start() throws SimulatorExcepiton {
if(loopGroup != null) {
return;
@@ -61,7 +72,7 @@ public class S7ServerModule implements ServerModule {
public void initChannel(SocketChannel channel) {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new S7Step7Protocol());
- pipeline.addLast(new S7Step7ServerAdapter());
+ pipeline.addLast(new S7Step7ServerAdapter(contexts));
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
diff --git a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/protocol/S7Step7ServerAdapter.java b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/protocol/S7Step7ServerAdapter.java
index 46a4191..1ab07b5 100644
--- a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/protocol/S7Step7ServerAdapter.java
+++ b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/protocol/S7Step7ServerAdapter.java
@@ -21,14 +21,20 @@ package org.apache.plc4x.simulator.server.s7.protocol;
import io.netty.channel.*;
import org.apache.plc4x.java.s7.readwrite.*;
import org.apache.plc4x.java.s7.readwrite.types.*;
+import org.apache.plc4x.simulator.model.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.List;
+import java.util.Map;
+
public class S7Step7ServerAdapter extends ChannelInboundHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(S7Step7ServerAdapter.class);
- private State state = State.INITIAL;
+ private List<Context> contexts;
+
+ private State state;
// COTP parameters
private static final int localReference = 42;
@@ -48,6 +54,11 @@ public class S7Step7ServerAdapter extends ChannelInboundHandlerAdapter {
private static final int maxPduLength = 240;
private int pduLength;
+ public S7Step7ServerAdapter(List<Context> contexts) {
+ this.contexts = contexts;
+ state = State.INITIAL;
+ }
+
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof TPKTPacket) {
diff --git a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/simulation/SimulationModule.java b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/simulation/SimulationModule.java
index f08b082..c79ee22 100644
--- a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/simulation/SimulationModule.java
+++ b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/simulation/SimulationModule.java
@@ -18,7 +18,7 @@ under the License.
*/
package org.apache.plc4x.simulator.simulation;
-import java.util.Map;
+import org.apache.plc4x.simulator.model.Context;
public interface SimulationModule {
@@ -29,10 +29,9 @@ public interface SimulationModule {
/**
* Gives access to the internal simulations context.
- * This is an immutable map of named properties that should contain only simple data-types.
* @return reference to the simulations context
*/
- Map<String, Object> getContext();
+ Context getContext();
/**
* Method for doing the actual processing inside the simulation.
diff --git a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/simulation/watertank/WaterTankSimulationModule.java b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/simulation/watertank/WaterTankSimulationModule.java
index 8b5ba9c..e49b949 100644
--- a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/simulation/watertank/WaterTankSimulationModule.java
+++ b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/simulation/watertank/WaterTankSimulationModule.java
@@ -18,21 +18,47 @@ under the License.
*/
package org.apache.plc4x.simulator.simulation.watertank;
+import org.apache.plc4x.simulator.model.Context;
import org.apache.plc4x.simulator.simulation.SimulationModule;
-import java.util.Map;
-import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
+/**
+ * This is a little simulation that simulates a Water tank.
+ * This tank has a capacity who's "waterLevel" is represented as a Long value.
+ * Water can flow into the tank if the input valve is opened and it can flow
+ * out of the tank if the output valve is open.
+ *
+ * The capacity of the output is slightly smaller than that of the input, so
+ * opening both valves will result in the tank filling.
+ *
+ * To prevent the tank from bursting, there's an emergency valve which is opened
+ * as soon as the water-level reaches a critical maximum.
+ */
public class WaterTankSimulationModule implements SimulationModule {
+ private static final long MAX_WATER_LEVEL = 27648L;
+ private static final long EMERGENCY_VALVE_WATER_LEVEL = 27500L;
+
+ private static final int NUM_INPUT_VALVE_INPUT = 0;
+ private static final int NUM_OUTPUT_VALVE_INPUT = 1;
+
+ private static final int EMERGENCY_VALVE_OUTPUT = 0;
+
private static final String PROP_WATER_LEVEL = "waterLevel";
- private final Map<String, Object> context;
+ private final Context context;
public WaterTankSimulationModule() {
- context = new TreeMap<>();
- context.put(PROP_WATER_LEVEL, 0);
+ context = new Context.ContextBuilder()
+ // The input valve
+ .addDigitalInput(false)
+ // The output valve
+ .addDigitalInput(false)
+ // The emergency valve
+ .addDigitalOutput(false)
+ // The water level
+ .addMemoryVariable(PROP_WATER_LEVEL, 0L).build();
}
@Override
@@ -41,16 +67,39 @@ public class WaterTankSimulationModule implements SimulationModule {
}
@Override
- public Map<String, Object> getContext() {
+ public Context getContext() {
return context;
}
@Override
public void loop() {
// TODO: Do something sensible ;-)
+ // Just a POC for now ... to be replaced by a "real" simulation ...
try {
- // Just increase the level by 1 (Whatever this means ...
- context.put(PROP_WATER_LEVEL, ((Integer) context.get(PROP_WATER_LEVEL)) + 1);
+ // Get the current value for the water tank level.
+ Long value = (Long) context.getMemory().get(PROP_WATER_LEVEL);
+
+ // If the input valve is open, add 10.
+ if(context.getDigitalInputs().get(NUM_INPUT_VALVE_INPUT)) {
+ value += 10;
+ value = Math.min(MAX_WATER_LEVEL, value);
+ }
+
+ // If the output valve is open, subtract 8 (It's slightly less throughput than the input)
+ if(context.getDigitalInputs().get(NUM_OUTPUT_VALVE_INPUT)) {
+ value -= 8;
+ value = Math.max(0, value);
+ }
+
+ // Calculate if the emergency valve should be open
+ boolean emergencyValveOpen = value > EMERGENCY_VALVE_WATER_LEVEL;
+
+ // Update the memory.
+ context.getMemory().put(PROP_WATER_LEVEL, value);
+ // Update the state of the emergency valve.
+ context.getDigitalOutputs().set(EMERGENCY_VALVE_OUTPUT, emergencyValveOpen);
+
+ // Sleep for a little while (Should probably be handled by the simulator)
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();