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();