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 2018/07/06 11:40:56 UTC

[incubator-plc4x] branch feature/ethernet-ip created (now 735431e)

This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a change to branch feature/ethernet-ip
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git.


      at 735431e  PLC4X-38 - Implement the Ethernet/IP Protocol

This branch includes the following new commits:

     new a66f66d  Refactored the way the maps the enums are built up.
     new 7a2b14b  Added some EtherNet/IP related documentation.
     new 735431e  PLC4X-38 - Implement the Ethernet/IP Protocol

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



[incubator-plc4x] 02/03: Added some EtherNet/IP related documentation.

Posted by cd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch feature/ethernet-ip
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 7a2b14b4fc9ea90f509d8bdbf10f770598aeb473
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Fri Jul 6 13:00:59 2018 +0200

    Added some EtherNet/IP related documentation.
---
 src/site/asciidoc/developers/vpn.adoc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/site/asciidoc/developers/vpn.adoc b/src/site/asciidoc/developers/vpn.adoc
index 9f80ced..af4a8f8 100644
--- a/src/site/asciidoc/developers/vpn.adoc
+++ b/src/site/asciidoc/developers/vpn.adoc
@@ -121,6 +121,7 @@ image::plc4x-vpn-beckhoff.jpg[float=right, width=200]
 This device is able to use the following protocols:
 
 - ADS (Port `48898`)
+- EtherNet/IP (Port `48181`)
 
 It is configured to use the IP: `10.10.64.40`
 
@@ -157,6 +158,10 @@ Next thing, you should ensure, is that the type of `Remote Route` is set to `Non
 
 When clicking on `Add Route`, don't be surprised that the window doesn't close, you have to click on `Close` after that and then you should see your new route in the route list screen.
 
+When planning on using the `EtherNet/IP` communication, the configuration of the TwinCAT device is described here:
+
+https://download.beckhoff.com/download/document/automation/twincat3/TF6280_EtherNet_IP_Slave_EN.pdf
+
 === Requesting an account
 
 The PLC hardware in the `PLC4X IoT Lab` is hosted in the codecentric Frankfurt office.


[incubator-plc4x] 03/03: PLC4X-38 - Implement the Ethernet/IP Protocol

Posted by cd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch feature/ethernet-ip
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit 735431e26b3aa74bb4a502e75446808c58175dc3
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Fri Jul 6 13:40:52 2018 +0200

    PLC4X-38 - Implement the Ethernet/IP Protocol
---
 .../plc4x/java/ethernetip/EtherNetIpPlcDriver.java |  81 ++++
 .../connection/BaseEtherNetIpPlcConnection.java    |  93 +++++
 .../connection/EtherNetIpTcpPlcConnection.java     |  83 ++++
 .../java/ethernetip/model/EtherNetIpAddress.java   |  84 +++++
 .../ethernetip/netty/Plc4XEtherNetIpProtocol.java  | 419 +++++++++++++++++++++
 .../netty/events/EtherNetIpConnectedEvent.java     |  22 ++
 .../ethernetip/src/site/asciidoc/index.adoc        |  74 ++++
 .../java/ethernetip/ManualPlc4XEtherNetIpTest.java |  57 +++
 plc4j/protocols/pom.xml                            |   1 +
 9 files changed, 914 insertions(+)

diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/EtherNetIpPlcDriver.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/EtherNetIpPlcDriver.java
new file mode 100644
index 0000000..69aa03a
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/EtherNetIpPlcDriver.java
@@ -0,0 +1,81 @@
+/*
+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.java.ethernetip;
+
+import org.apache.plc4x.java.api.PlcDriver;
+import org.apache.plc4x.java.api.authentication.PlcAuthentication;
+import org.apache.plc4x.java.api.connection.PlcConnection;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.ethernetip.connection.EtherNetIpTcpPlcConnection;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Implementation of the Ethernet/IP protocol, based on the driver implementation available at:
+ * https://github.com/digitalpetri/ethernet-ip/
+ *
+ * Spec:
+ * http://read.pudn.com/downloads166/ebook/763212/EIP-CIP-V2-1.0.pdf
+ */
+public class EtherNetIpPlcDriver implements PlcDriver {
+
+    private static final Pattern ETHERNETIP_URI_PATTERN = Pattern.compile("^eip://(?<host>[\\w.]+)(:(?<port>\\d*))?(?<params>\\?.*)?");
+
+    @Override
+    public String getProtocolCode() {
+        return "eip";
+    }
+
+    @Override
+    public String getProtocolName() {
+        return "EtherNet/IP (TCP)";
+    }
+
+    @Override
+    public PlcConnection connect(String url) throws PlcConnectionException {
+        Matcher matcher = ETHERNETIP_URI_PATTERN.matcher(url);
+        if (!matcher.matches()) {
+            throw new PlcConnectionException(
+                "Connection url doesn't match the format 'eip//{port|host}'");
+        }
+
+        String host = matcher.group("host");
+        String port = matcher.group("port");
+        String params = matcher.group("params") != null ? matcher.group("params").substring(1) : null;
+        try {
+            InetAddress inetAddress = InetAddress.getByName(host);
+            if (port == null) {
+                return new EtherNetIpTcpPlcConnection(inetAddress, params);
+            } else {
+                return new EtherNetIpTcpPlcConnection(inetAddress, Integer.valueOf(port), params);
+            }
+        } catch (UnknownHostException e) {
+            throw new PlcConnectionException("Unknown host" + host, e);
+        }
+    }
+
+    @Override
+    public PlcConnection connect(String url, PlcAuthentication authentication) throws PlcConnectionException {
+        throw new PlcConnectionException("EtherNet/IP connections don't support authentication.");
+    }
+
+}
diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/BaseEtherNetIpPlcConnection.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/BaseEtherNetIpPlcConnection.java
new file mode 100644
index 0000000..31a6c2d
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/BaseEtherNetIpPlcConnection.java
@@ -0,0 +1,93 @@
+/*
+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.java.ethernetip.connection;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.plc4x.java.api.connection.PlcReader;
+import org.apache.plc4x.java.api.connection.PlcWriter;
+import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.model.Address;
+import org.apache.plc4x.java.base.connection.AbstractPlcConnection;
+import org.apache.plc4x.java.base.connection.ChannelFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.CompletableFuture;
+
+public abstract class BaseEtherNetIpPlcConnection extends AbstractPlcConnection implements PlcReader, PlcWriter {
+
+    private static final Logger logger = LoggerFactory.getLogger(BaseEtherNetIpPlcConnection.class);
+
+    protected BaseEtherNetIpPlcConnection(ChannelFactory channelFactory, String params) {
+        super(channelFactory, true);
+
+        if (!StringUtils.isEmpty(params)) {
+            for (String param : params.split("&")) {
+                String[] paramElements = param.split("=");
+                String paramName = paramElements[0];
+                if (paramElements.length == 2) {
+                    String paramValue = paramElements[1];
+                    switch (paramName) {
+                        default:
+                            logger.debug("Unknown parameter {} with value {}", paramName, paramValue);
+                    }
+                } else {
+                    logger.debug("Unknown no-value parameter {}", paramName);
+                }
+            }
+        }
+    }
+
+    @Override
+    public Address parseAddress(String addressString) {
+        /*if (MaskWriteRegisterModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
+            return MaskWriteRegisterModbusAddress.of(addressString);
+        } else if (ReadDiscreteInputsModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
+            return ReadDiscreteInputsModbusAddress.of(addressString);
+        } else if (ReadHoldingRegistersModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
+            return ReadHoldingRegistersModbusAddress.of(addressString);
+        } else if (ReadInputRegistersModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
+            return ReadInputRegistersModbusAddress.of(addressString);
+        } else if (CoilAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
+            return CoilAddress.of(addressString);
+        } else if (RegisterAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
+            return RegisterAddress.of(addressString);
+        }*/
+        return null;
+    }
+
+    @Override
+    public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
+        CompletableFuture<PlcReadResponse> readFuture = new CompletableFuture<>();
+        PlcRequestContainer<PlcReadRequest, PlcReadResponse> container =
+            new PlcRequestContainer<>(readRequest, readFuture);
+        channel.writeAndFlush(container);
+        return readFuture;
+    }
+
+    @Override
+    public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
+        CompletableFuture<PlcWriteResponse> writeFuture = new CompletableFuture<>();
+        PlcRequestContainer<PlcWriteRequest, PlcWriteResponse> container =
+            new PlcRequestContainer<>(writeRequest, writeFuture);
+        channel.writeAndFlush(container);
+        return writeFuture;
+    }
+
+}
diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/EtherNetIpTcpPlcConnection.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/EtherNetIpTcpPlcConnection.java
new file mode 100644
index 0000000..2ad3448
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/EtherNetIpTcpPlcConnection.java
@@ -0,0 +1,83 @@
+/*
+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.java.ethernetip.connection;
+
+import com.digitalpetri.enip.EnipCodec;
+import io.netty.channel.*;
+import org.apache.plc4x.java.base.connection.ChannelFactory;
+import org.apache.plc4x.java.base.connection.TcpSocketChannelFactory;
+import org.apache.plc4x.java.base.events.ConnectEvent;
+import org.apache.plc4x.java.base.events.ConnectedEvent;
+import org.apache.plc4x.java.ethernetip.netty.Plc4XEtherNetIpProtocol;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.util.concurrent.CompletableFuture;
+
+public class EtherNetIpTcpPlcConnection extends BaseEtherNetIpPlcConnection {
+
+    // Port 44818
+    private static final int ETHERNET_IP_TCP_PORT = 0xAF12;
+
+    private static final Logger logger = LoggerFactory.getLogger(EtherNetIpTcpPlcConnection.class);
+
+    public EtherNetIpTcpPlcConnection(InetAddress address, String params) {
+        this(new TcpSocketChannelFactory(address, ETHERNET_IP_TCP_PORT), params);
+        logger.info("Configured EtherNetIpTcpPlcConnection with: host-name {}", address.getHostAddress());
+    }
+
+    public EtherNetIpTcpPlcConnection(InetAddress address, int port, String params) {
+        this(new TcpSocketChannelFactory(address, port), params);
+        logger.info("Configured EtherNetIpTcpPlcConnection with: host-name {}", address.getHostAddress());
+    }
+
+    public EtherNetIpTcpPlcConnection(ChannelFactory channelFactory, String params) {
+        super(channelFactory, params);
+    }
+
+    @Override
+    protected ChannelHandler getChannelHandler(CompletableFuture<Void> sessionSetupCompleteFuture) {
+        return new ChannelInitializer() {
+            @Override
+            protected void initChannel(Channel channel) {
+                ChannelPipeline pipeline = channel.pipeline();
+                pipeline.addLast(new ChannelInboundHandlerAdapter() {
+                    @Override
+                    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+                        if (evt instanceof ConnectedEvent) {
+                            sessionSetupCompleteFuture.complete(null);
+                        } else {
+                            super.userEventTriggered(ctx, evt);
+                        }
+                    }
+                });
+                pipeline.addLast(new EnipCodec());
+                pipeline.addLast(new Plc4XEtherNetIpProtocol());
+            }
+        };
+    }
+
+    @Override
+    protected void sendChannelCreatedEvent() {
+        // Send an event to the pipeline telling the Protocol filters what's going on.
+        channel.pipeline().fireUserEventTriggered(new ConnectEvent());
+    }
+
+}
diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/model/EtherNetIpAddress.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/model/EtherNetIpAddress.java
new file mode 100644
index 0000000..85ec148
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/model/EtherNetIpAddress.java
@@ -0,0 +1,84 @@
+/*
+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.java.ethernetip.model;
+
+import org.apache.plc4x.java.api.model.Address;
+
+import java.util.Objects;
+import java.util.regex.Pattern;
+
+public abstract class EtherNetIpAddress implements Address {
+
+    public static final Pattern ADDRESS_PATTERN = Pattern.compile("(?<address>\\d+)");
+
+    private final int objectNumber;
+    private final int instanceNumber;
+    private final int attributeNumber;
+
+    private int connectionId;
+
+    public EtherNetIpAddress(int objectNumber, int instanceNumber, int attributeNumber) {
+        this.objectNumber = objectNumber;
+        this.instanceNumber = instanceNumber;
+        this.attributeNumber = attributeNumber;
+
+        this.connectionId = -1;
+    }
+
+    public int getObjectNumber() {
+        return objectNumber;
+    }
+
+    public int getInstanceNumber() {
+        return instanceNumber;
+    }
+
+    public int getAttributeNumber() {
+        return attributeNumber;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof EtherNetIpAddress)) {
+            return false;
+        }
+        EtherNetIpAddress that = (EtherNetIpAddress) o;
+        return objectNumber == that.objectNumber &&
+            instanceNumber == that.instanceNumber &&
+            attributeNumber == that.attributeNumber;
+    }
+
+    @Override
+    public int hashCode() {
+
+        return Objects.hash(objectNumber, instanceNumber, attributeNumber);
+    }
+
+    @Override
+    public String toString() {
+        return "EtherNetIpAddress{" +
+            "object-number=" + objectNumber +
+            "instance-number=" + instanceNumber +
+            "attribute-number=" + attributeNumber +
+            '}';
+    }
+}
diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/Plc4XEtherNetIpProtocol.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/Plc4XEtherNetIpProtocol.java
new file mode 100644
index 0000000..3d6bf3d
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/Plc4XEtherNetIpProtocol.java
@@ -0,0 +1,419 @@
+/*
+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.java.ethernetip.netty;
+
+import com.digitalpetri.enip.EnipPacket;
+import com.digitalpetri.enip.EnipStatus;
+import com.digitalpetri.enip.commands.*;
+import com.digitalpetri.enip.cpf.*;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.MessageToMessageCodec;
+import org.apache.plc4x.java.api.exceptions.PlcProtocolException;
+import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.model.Address;
+import org.apache.plc4x.java.base.events.ConnectEvent;
+import org.apache.plc4x.java.base.events.ConnectedEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+public class Plc4XEtherNetIpProtocol extends MessageToMessageCodec<EnipPacket, PlcRequestContainer<PlcRequest, PlcResponse>> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(Plc4XEtherNetIpProtocol.class);
+
+    private static final int SERVICE_COMMUNICATIONS_TYPE_CODE = 0x0100;
+
+    private long sessionHandle = 0;
+    private static final AtomicLong messageId = new AtomicLong();
+
+    // General information about the remote communication endpoint.
+    private CipIdentityItem identityItem;
+    // Flag to signal, if the remote communication endpoint supports encapsulation of CIP data.
+    private boolean supportsCipEncapsulation = false;
+    // Flag to indicate, if implicit IO (subscription) is generally supported by the remote communication endpoint.
+    // This is handled via separate UDP socket, which would have to be established in parallel.
+    private boolean supportsClass0Or1UdpConnections = false;
+    // Map of non-cip interfaces, that might be used for specialized IO in future versions.
+    private Map<String, Integer> nonCipInterfaces = null;
+    // In CIP we are doing explicit connected messaging, this requires every used address to be registered at the
+    // remote server and to use that Addresses connectionId for accessing data. We are saving the references to
+    // these here.
+    // REMARK: Eventually we should add a timeout to these so we unregister them after not being used
+    // for quire some time. Hereby freeing resources on both client and server.
+    private Map<Address, Long> addressConnectionMap = new ConcurrentHashMap<>();
+
+    private final Map<Long, PlcRequestContainer<PlcRequest, PlcResponse>> requestsMap = new ConcurrentHashMap<>();
+
+    public Plc4XEtherNetIpProtocol() {
+    }
+
+    /**
+     * If the IsoTP protocol is used on top of the ISO on TCP protocol, then as soon as the pipeline receives the
+     * request to connect, an IsoTP connection request TPDU must be sent in order to initialize the connection.
+     *
+     * @param ctx the current protocol layers context
+     * @param evt the event
+     * @throws Exception throws an exception if something goes wrong internally
+     */
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+        // If the connection has just been established, start setting up the connection
+        // by sending a connection request to the plc.
+        if (evt instanceof ConnectEvent) {
+            LOGGER.debug("EtherNet/IP Protocol Sending Connection Request");
+
+            EnipPacket packet = new EnipPacket(CommandCode.RegisterSession, 0, EnipStatus.EIP_SUCCESS,
+                messageId.getAndIncrement(), new RegisterSession());
+
+            ctx.channel().writeAndFlush(packet);
+        } else {
+            super.userEventTriggered(ctx, evt);
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        LOGGER.trace("(-->ERR): {}", ctx, cause);
+        super.exceptionCaught(ctx, cause);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Encoding
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected void encode(ChannelHandlerContext ctx, PlcRequestContainer<PlcRequest, PlcResponse> msg, List<Object> out) {
+        LOGGER.trace("(<--OUT): {}, {}, {}", ctx, msg, out);
+        // Reset transactionId on overflow
+        messageId.compareAndSet(Short.MAX_VALUE + 1, 0);
+        PlcRequest request = msg.getRequest();
+        if (request instanceof PlcReadRequest) {
+            encodeReadRequest(msg, out);
+        } else if (request instanceof PlcWriteRequest) {
+            encodeWriteRequest(msg, out);
+        } /*else if(request instanceof PlcSubscriptionRequest) {
+            encodeSubscriptionRequest(msg, out);
+        } else if(request instanceof PlcUnsubscriptionRequest) {
+            TODO: Implement this and refactor PlcUnsubscriptionRequest first ...
+        }*/
+    }
+
+    private void encodeWriteRequest(PlcRequestContainer<PlcRequest, PlcResponse> msg, List<Object> out) {
+        if(!supportsCipEncapsulation) {
+            LOGGER.warn("CIP Encapsulation not supported by remote, payload encapsulation must be handled by target and originator");
+        }
+
+        PlcWriteRequest request = (PlcWriteRequest) msg.getRequest();
+
+        // Create a ForwardOpen CIP request
+
+        // Create EIP UnconnectedDataItemRequest
+        /*UnconnectedDataItemRequest dataItem = new UnconnectedDataItemRequest(dataEncoder);
+        CpfPacket packet = new CpfPacket(new NullAddressItem(), dataItem);
+
+        // Send that via EIP SendRRData packet
+        CompletableFuture<T> future = new CompletableFuture<>();
+
+        sendRRData(new SendRRData(packet)).whenComplete((command, ex) -> {
+            if (command != null) {
+                CpfItem[] items = command.getPacket().getItems();
+
+                if (items.length == 2 &&
+                    items[0].getTypeId() == NullAddressItem.TYPE_ID &&
+                    items[1].getTypeId() == UnconnectedDataItemResponse.TYPE_ID) {
+
+                    ByteBuf data = ((UnconnectedDataItemResponse) items[1]).getData();
+
+                    future.complete(data);
+                } else {
+                    future.completeExceptionally(new Exception("received unexpected items"));
+                }
+            } else {
+                future.completeExceptionally(ex);
+            }
+        });
+
+        channelManager.getChannel().whenComplete((ch, ex) -> {
+            if (ch != null) writeCommand(ch, command, future);
+            else future.completeExceptionally(ex);
+        });*/
+
+    }
+
+    private void encodeReadRequest(PlcRequestContainer<PlcRequest, PlcResponse> msg, List<Object> out) {
+        if(!supportsCipEncapsulation) {
+            LOGGER.warn("CIP Encapsulation not supported by remote, payload encapsulation must be handled by target and originator");
+        }
+
+        PlcReadRequest request = (PlcReadRequest) msg.getRequest();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Decoding
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected void decode(ChannelHandlerContext ctx, EnipPacket msg, List<Object> out) {
+        LOGGER.trace("(-->IN): {}, {}, {}", ctx, msg, out);
+        LOGGER.debug("{}: session handle: {}, sender context: {}, EtherNetIPPacket:{}", msg, msg.getSessionHandle(), msg.getSenderContext(), msg);
+
+        EnipPacket packet = null;
+        switch (msg.getCommandCode()) {
+            case RegisterSession:
+                handleRegisterSession(ctx, msg);
+
+                // Now try getting some detailed information about the remote.
+                packet = new EnipPacket(CommandCode.ListIdentity, sessionHandle, EnipStatus.EIP_SUCCESS,
+                    messageId.getAndIncrement(), new ListIdentity());
+                break;
+
+            case UnRegisterSession:
+                handleUnregisterSession(ctx, msg);
+
+                // Spec: The receiver shall initiate a close of the underlying
+                // TCP/IP connection when it receives this command.
+                ctx.channel().disconnect();
+                break;
+
+            case ListIdentity:
+                handleListIdentity(ctx, msg);
+
+                // Now try listing the services the remote has to offer.
+                packet = new EnipPacket(CommandCode.ListServices, sessionHandle, EnipStatus.EIP_SUCCESS,
+                    messageId.getAndIncrement(), new ListServices());
+                break;
+
+            case ListInterfaces:
+                handleListInterfaces(ctx, msg);
+
+                // Here we're done connecting.
+                ctx.channel().pipeline().fireUserEventTriggered(new ConnectedEvent());
+                break;
+
+            case ListServices:
+                handleListServices(ctx, msg);
+
+                // Now try listing the interfaces the remote has to offer.
+                packet = new EnipPacket(CommandCode.ListInterfaces, sessionHandle, EnipStatus.EIP_SUCCESS,
+                    messageId.getAndIncrement(), new ListInterfaces());
+                break;
+
+            case Nop:
+                handleNop(ctx, msg);
+                break;
+
+            case SendRRData:
+                handleSendRRDataResponse(ctx, msg);
+                break;
+
+            case SendUnitData:
+                // This might be where the connected data is sent (eventually publish/subscribe communication)
+                break;
+        }
+
+        if(packet != null) {
+            ctx.channel().writeAndFlush(packet);
+        }
+    }
+
+    /**
+     * In order to do explicit connected messaging, the client has to register a session with the server.
+     * In case of a successful session registration the response will contain the sessionHandle, which is
+     * required to be used in all subsequent connected interactions.
+     *
+     * @param ctx the {@link ChannelHandlerContext} instance.
+     * @param msg the packet received from the server.
+     */
+    private void handleRegisterSession(ChannelHandlerContext ctx, EnipPacket msg) {
+        if(msg.getStatus() == EnipStatus.EIP_SUCCESS) {
+            sessionHandle = msg.getSessionHandle();
+
+            LOGGER.info("EtherNet/IP session registered session-handle {}", sessionHandle);
+        } else {
+            ctx.channel().pipeline().fireExceptionCaught(new PlcProtocolException("Got a non-success response."));
+        }
+    }
+
+    /**
+     * As connected operations allocate resources on the server and the client, when receiving a
+     * {@link UnRegisterSession} message (request or response) the locally allocated resources have
+     * to be released again. As the correct response to a UnRegisterSession is the closing of the
+     * connection by the receiving side, this incoming command must be a request sent from the
+     * server.
+     *
+     * @param ctx the {@link ChannelHandlerContext} instance.
+     * @param msg the packet received from the server.
+     */
+    private void handleUnregisterSession(ChannelHandlerContext ctx, EnipPacket msg) {
+        if (msg.getStatus() == EnipStatus.EIP_SUCCESS) {
+            // Reset all internal variables.
+            identityItem = null;
+            supportsCipEncapsulation = false;
+            supportsClass0Or1UdpConnections = false;
+            nonCipInterfaces = null;
+            addressConnectionMap = null;
+        } else {
+            ctx.channel().pipeline().fireExceptionCaught(new PlcProtocolException("Got a non-success response."));
+        }
+    }
+
+    /**
+     * The response to a {@link ListIdentity} command contains a lot of information about the
+     * remote counterpart. In this case we just save this information for further usage.
+     *
+     * @param ctx the {@link ChannelHandlerContext} instance.
+     * @param msg the packet received from the server.
+     */
+    private void handleListIdentity(ChannelHandlerContext ctx, EnipPacket msg) {
+        if(msg.getStatus() == EnipStatus.EIP_SUCCESS) {
+            ListIdentity listIdentityResponse = (ListIdentity) msg.getCommand();
+            if(listIdentityResponse != null) {
+                identityItem = listIdentityResponse.getIdentity().orElse(null);
+                if(identityItem != null) {
+                    LOGGER.info("Connected to: \n - product name: {} \n - serial number: {} ",
+                        identityItem.getProductName().trim(), identityItem.getSerialNumber());
+                }
+            } else {
+                identityItem = null;
+            }
+        } else {
+            ctx.channel().pipeline().fireExceptionCaught(new PlcProtocolException("Got a non-success response."));
+        }
+    }
+
+    /**
+     * Some times EtherNet/IP devices support other devices than the default one.
+     * As we are required to ev eventually reference these interfaces, build a map
+     * of all the devices the remote supports. This way we can check the validity
+     * before actually sending a request.
+     *
+     * @param ctx the {@link ChannelHandlerContext} instance.
+     * @param msg the packet received from the server.
+     */
+    private void handleListInterfaces(ChannelHandlerContext ctx, EnipPacket msg) {
+        if(msg.getStatus() == EnipStatus.EIP_SUCCESS) {
+            ListInterfaces listInterfaces = (ListInterfaces) msg.getCommand();
+            if(listInterfaces != null) {
+                // If the device supports non-CIP interfaces, this array is not empty.
+                // In this case build a map so we can access the information when sending
+                // data in RR-Requests (Request-Response).
+                if(listInterfaces.getInterfaces().length > 0) {
+                    nonCipInterfaces = new HashMap<>();
+                    for (ListInterfaces.InterfaceInformation interfaceInformation : listInterfaces.getInterfaces()) {
+                        String interfaceName = new String(
+                            interfaceInformation.getData(), Charset.forName("US-ASCII")).trim();
+                        nonCipInterfaces.put(interfaceName, interfaceInformation.hashCode());
+                    }
+                } else {
+                    nonCipInterfaces = null;
+                }
+            }
+        } else {
+            ctx.channel().pipeline().fireExceptionCaught(new PlcProtocolException("Got a non-success response."));
+        }
+    }
+
+    /**
+     * Each EtherNet/IP device can support one or more so-called `services`. At least the `Communications`
+     * service is required to be supported by every EtherNet/IP compliant device. This is used for default
+     * IO operations. Usually vendors support custom services which are adjusted to their particular needs,
+     * which might be able to provide better performance than the default. In this case we are ignoring all
+     * these as supporting these would require custom adapters on the PLC4X side. However we do inspect the
+     * capabilities of the `Communications` service to check if encapsulation of CIP data is supported and
+     * if we are able to do connected implicit communication via a parallel UDP channel.
+     *
+     * @param ctx the {@link ChannelHandlerContext} instance.
+     * @param msg the packet received from the server.
+     */
+    private void handleListServices(ChannelHandlerContext ctx, EnipPacket msg) {
+        if (msg.getStatus() == EnipStatus.EIP_SUCCESS) {
+            ListServices listServices = (ListServices) msg.getCommand();
+            if(listServices != null) {
+                for (ListServices.ServiceInformation service : listServices.getServices()) {
+                    // Check if the type code matches the communications service and if bit 5 of the
+                    // capability flags is set.
+                    if (service.getTypeCode() == SERVICE_COMMUNICATIONS_TYPE_CODE) {
+                        supportsCipEncapsulation = (service.getCapabilityFlags() & 32) != 0;
+                        supportsClass0Or1UdpConnections = (service.getCapabilityFlags() & 256) != 0;
+                    }
+                }
+            } else {
+                supportsCipEncapsulation = false;
+                supportsClass0Or1UdpConnections = false;
+            }
+        } else {
+            ctx.channel().pipeline().fireExceptionCaught(new PlcProtocolException("Got a non-success response."));
+        }
+    }
+
+    /**
+     * NOP request/responses are simple no-payload messages used to check if a connection is still
+     * available. Depending on if it's a request or reply, we simply send back a NOP Reply or not.
+     * As no reply is to be generated for an incoming NOP command, this must be a NopRequest.
+     *
+     * @param ctx the {@link ChannelHandlerContext} instance.
+     * @param msg the packet received from the server.
+     */
+    private void handleNop(ChannelHandlerContext ctx, EnipPacket msg) {
+        if(msg.getStatus() == EnipStatus.EIP_SUCCESS) {
+            Nop nop = (Nop) msg.getCommand();
+            // TODO: Reset some sort of timer ...
+        } else {
+            ctx.channel().pipeline().fireExceptionCaught(
+                new PlcProtocolException("Got a non-success flagged request."));
+        }
+    }
+
+    /**
+     * As RR Data is Request Response data and the server will not issue a request to
+     * the client, we can be pretty sure this is a response to a previously issued request.
+     * This contains the actual payload for our requests.
+     *
+     * @param ctx the {@link ChannelHandlerContext} instance.
+     * @param msg the packet received from the server.
+     */
+    private void handleSendRRDataResponse(ChannelHandlerContext ctx, EnipPacket msg) {
+        // This is where the typical request/response stuff is handled.
+        long senderContext = msg.getSenderContext();
+        PlcRequestContainer<PlcRequest, PlcResponse> plcRequestContainer = requestsMap.get(senderContext);
+        if (plcRequestContainer == null) {
+            ctx.channel().pipeline().fireExceptionCaught(
+                new PlcProtocolException("Unrelated payload received for message " + msg));
+        }
+
+        PlcRequest request = plcRequestContainer.getRequest();
+    }
+
+
+    ////////////////////////////////////////////////////////////////////////////////
+    // Encoding helpers.
+    ////////////////////////////////////////////////////////////////////////////////
+
+    ////////////////////////////////////////////////////////////////////////////////
+    // Decoding helpers.
+    ////////////////////////////////////////////////////////////////////////////////
+
+}
diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/events/EtherNetIpConnectedEvent.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/events/EtherNetIpConnectedEvent.java
new file mode 100644
index 0000000..5142299
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/events/EtherNetIpConnectedEvent.java
@@ -0,0 +1,22 @@
+package org.apache.plc4x.java.ethernetip.netty.events;
+/*
+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.
+*/
+
+public class EtherNetIpConnectedEvent {
+}
diff --git a/plc4j/protocols/ethernetip/src/site/asciidoc/index.adoc b/plc4j/protocols/ethernetip/src/site/asciidoc/index.adoc
new file mode 100644
index 0000000..eb169d5
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/site/asciidoc/index.adoc
@@ -0,0 +1,74 @@
+//
+//  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.
+//
+
+== EtherNet/IP
+
+`IP` in `EtherNet/IP` does not relate to the `Internet Protocol` but to `Industrial Protocol`, so EtherNet/IP does not necessarily have to go over `IP` but can be implemented on `ethernet frames`.
+This driver however will utilize `EtherNet/IP` based on `TCP` and `UDP`.
+
+`Explicit messaging`: Read explicitly addressed information (Pull) (CIP Class 3 and 2)
+`Implicit messaging`: Read information distributed by the device configuration (Push) (CIP Class 1 and 0)
+
+TCP used for Explicit Messaging
+UDP used for Implicit Messaging ("Real time" I/O)
+
+In Explicit Messaging (classic Request/Response) there are two different types of access:
+
+- Connected access: A handle 'connectionId' is assigned to an address and this is used to access the resource
+- Unconnected access: No handle is used and the address has to be sent with every request
+
+We'll concentrate on the connected access.
+
+In all cases the payload of the data items is dependent on the encapsulated protocol used.
+In our case we'll concentrate on the default CIP format.
+
+Explicitly addressed object consists of the following data:
+- Object-Number
+- Instance-Number
+- Attribute-Number
+
+Here is the location of the Spec:
+https://www.odva.org/Portals/0/Library/Publications_Numbered/PUB00123R1_Common-Industrial_Protocol_and_Family_of_CIP_Networks.pdf
+
+As EtherNet/IP is used to encapsulate CIP traffic, this spec contains a chapter on the adaption of CIP for EtherNet/IP.
+
+Another documentation I found discussing the explicit messaging thing a little better is this:
+http://www.deltamotion.com/support/webhelp/rmctools/Communications/Ethernet/Supported_Protocols/EtherNetIP/EtherNet_IP_Explicit_Messaging.htm
+https://www.odva.org/Portals/0/Library/Publications_Numbered/PUB00213R0_EtherNetIP_Developers_Guide.pdf
+
+=== Reading
+
+When reading values in a request/response type of communication (explicit), there are two options:
+
+- Unconnected access: Ideal when requesting information irregularly and not frequently. Less performant.
+- Connected access: Ideal when regularly requesting information. Higher performance, but requires multiple phases.
+
+We will concentrate on explicit connected access.
+
+In order to read/write values using connected access, first we have to get a connectionId for the resource we want to access.
+In subsequent read/write operations, we don't have to provide the address, but just provide the connection id and the server will already know what we want to access.
+
+Getting the connection id, is performed by sending an unconnected request to the server referencing the `Forward Open Service`.
+This will register the connection in the Server and this will stay alive until the session is terminated.
+So we have to make sure a session is created before using this service.
+
+Part of this request is a parameter `vendor id`.
+It turns out that this id has to be purchased from the ODVA organization.
+
+https://secure.odva.org/forms/spec-vendor-id-order-form.htm
+
+But probably we can live without a valid and registered id.
\ No newline at end of file
diff --git a/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/ManualPlc4XEtherNetIpTest.java b/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/ManualPlc4XEtherNetIpTest.java
new file mode 100644
index 0000000..f5b332c
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/ManualPlc4XEtherNetIpTest.java
@@ -0,0 +1,57 @@
+/*
+ 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.java.ethernetip;
+
+import org.apache.plc4x.java.PlcDriverManager;
+import org.apache.plc4x.java.api.connection.PlcConnection;
+import org.apache.plc4x.java.api.connection.PlcReader;
+import org.apache.plc4x.java.api.messages.items.ReadResponseItem;
+import org.apache.plc4x.java.api.messages.specific.TypeSafePlcReadRequest;
+import org.apache.plc4x.java.api.messages.specific.TypeSafePlcReadResponse;
+import org.apache.plc4x.java.api.model.Address;
+
+import java.util.concurrent.CompletableFuture;
+
+public class ManualPlc4XEtherNetIpTest {
+
+    public static void main(String... args) {
+        String connectionUrl;
+        System.out.println("Using tcp");
+        connectionUrl = "eip://192.168.42.39:44818";
+        //connectionUrl = "eip://10.10.64.30:44818";
+        try (PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionUrl)) {
+            System.out.println("PlcConnection " + plcConnection);
+
+            PlcReader reader = plcConnection.getReader().orElseThrow(() -> new RuntimeException("No Reader found"));
+
+            Address address = plcConnection.parseAddress("register:7");
+            CompletableFuture<TypeSafePlcReadResponse<Integer>> response = reader
+                .read(new TypeSafePlcReadRequest<>(Integer.class, address));
+            TypeSafePlcReadResponse<Integer> readResponse = response.get();
+            System.out.println("Response " + readResponse);
+            ReadResponseItem<Integer> responseItem = readResponse.getResponseItem().orElseThrow(() -> new RuntimeException("No Item found"));
+            System.out.println("ResponseItem " + responseItem);
+            responseItem.getValues().stream().map(integer -> "Value: " + integer).forEach(System.out::println);
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.exit(1);
+        }
+        System.exit(0);
+    }
+}
diff --git a/plc4j/protocols/pom.xml b/plc4j/protocols/pom.xml
index 292c4ac..0fca4a1 100644
--- a/plc4j/protocols/pom.xml
+++ b/plc4j/protocols/pom.xml
@@ -38,6 +38,7 @@
     <module>driver-bases</module>
 
     <module>ads</module>
+    <module>ethernetip</module>
     <module>modbus</module>
     <module>s7</module>
 


[incubator-plc4x] 01/03: Refactored the way the maps the enums are built up.

Posted by cd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch feature/ethernet-ip
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git

commit a66f66d0f5a8d3e04fdb331939fdb3ac81a0f04c
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Thu Jul 5 10:11:07 2018 +0200

    Refactored the way the maps the enums are built up.
---
 .../plc4x/java/isotp/netty/model/types/DeviceGroup.java | 17 +++++++++--------
 .../java/isotp/netty/model/types/DisconnectReason.java  | 17 +++++++++--------
 .../java/isotp/netty/model/types/ParameterCode.java     | 17 +++++++++--------
 .../java/isotp/netty/model/types/ProtocolClass.java     | 17 +++++++++--------
 .../plc4x/java/isotp/netty/model/types/RejectCause.java | 17 +++++++++--------
 .../plc4x/java/isotp/netty/model/types/TpduCode.java    | 17 +++++++++--------
 .../plc4x/java/isotp/netty/model/types/TpduSize.java    | 17 +++++++++--------
 .../s7/netty/model/types/DataTransportErrorCode.java    | 17 +++++++++--------
 .../java/s7/netty/model/types/DataTransportSize.java    | 17 +++++++++--------
 .../java/s7/netty/model/types/HeaderErrorClass.java     | 17 +++++++++--------
 .../plc4x/java/s7/netty/model/types/MemoryArea.java     | 17 +++++++++--------
 .../plc4x/java/s7/netty/model/types/MessageType.java    | 17 +++++++++--------
 .../plc4x/java/s7/netty/model/types/ParameterError.java | 17 +++++++++--------
 .../plc4x/java/s7/netty/model/types/ParameterType.java  | 17 +++++++++--------
 .../java/s7/netty/model/types/SpecificationType.java    | 17 +++++++++--------
 .../plc4x/java/s7/netty/model/types/TransportSize.java  | 17 +++++++++--------
 .../s7/netty/model/types/VariableAddressingMode.java    | 17 +++++++++--------
 17 files changed, 153 insertions(+), 136 deletions(-)

diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/DeviceGroup.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/DeviceGroup.java
index 64d75c3..f6093c6 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/DeviceGroup.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/DeviceGroup.java
@@ -26,8 +26,6 @@ public enum DeviceGroup {
     OS((byte) 0x02),
     OTHERS((byte) 0x03);
 
-    private static Map<Byte, DeviceGroup> map = null;
-    
     private final byte code;
 
     DeviceGroup(byte code) {
@@ -38,13 +36,16 @@ public enum DeviceGroup {
         return code;
     }
 
-    public static DeviceGroup valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (DeviceGroup deviceGroup : DeviceGroup.values()) {
-                map.put(deviceGroup.code, deviceGroup);
-            }
+    private final static Map<Byte, DeviceGroup> map;
+
+    static {
+        map = new HashMap<>();
+        for (DeviceGroup deviceGroup : DeviceGroup.values()) {
+            map.put(deviceGroup.code, deviceGroup);
         }
+    }
+
+    public static DeviceGroup valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/DisconnectReason.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/DisconnectReason.java
index 1ebaa76..29af0f7 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/DisconnectReason.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/DisconnectReason.java
@@ -39,8 +39,6 @@ public enum DisconnectReason {
     CONNECTION_REQUEST_REFUSED((byte) 0x88),
     HEADER_OR_PARAMETER_LENGTH_INVALID((byte) 0x8A);
 
-    private static Map<Byte, DisconnectReason> map = null;
-    
     private final byte code;
 
     DisconnectReason(byte code) {
@@ -51,13 +49,16 @@ public enum DisconnectReason {
         return code;
     }
 
-    public static DisconnectReason valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (DisconnectReason disconnectReason : DisconnectReason.values()) {
-                map.put(disconnectReason.code, disconnectReason);
-            }
+    private final static Map<Byte, DisconnectReason> map;
+
+    static {
+        map = new HashMap<>();
+        for (DisconnectReason disconnectReason : DisconnectReason.values()) {
+            map.put(disconnectReason.code, disconnectReason);
         }
+    }
+
+    public static DisconnectReason valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/ParameterCode.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/ParameterCode.java
index a8dcd34..2605009 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/ParameterCode.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/ParameterCode.java
@@ -50,8 +50,6 @@ public enum ParameterCode {
     PREFERRED_MAX_PDU_SIZE((byte) 0xF0),
     INACTIVITY_TIMER((byte) 0xF2);
 
-    private static Map<Byte, ParameterCode> map = null;
-    
     private final byte code;
 
     ParameterCode(byte code) {
@@ -62,13 +60,16 @@ public enum ParameterCode {
         return code;
     }
 
-    public static ParameterCode valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (ParameterCode parameterCode : ParameterCode.values()) {
-                map.put(parameterCode.code, parameterCode);
-            }
+    private final static Map<Byte, ParameterCode> map;
+
+    static {
+        map = new HashMap<>();
+        for (ParameterCode parameterCode : ParameterCode.values()) {
+            map.put(parameterCode.code, parameterCode);
         }
+    }
+
+    public static ParameterCode valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/ProtocolClass.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/ProtocolClass.java
index 8871b38..91e8c13 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/ProtocolClass.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/ProtocolClass.java
@@ -28,8 +28,6 @@ public enum ProtocolClass {
     CLASS_3((byte) 0x30),
     CLASS_4((byte) 0x40);
 
-    private static Map<Byte, ProtocolClass> map = null;
-    
     private final byte code;
 
     ProtocolClass(byte code) {
@@ -40,13 +38,16 @@ public enum ProtocolClass {
         return code;
     }
 
-    public static ProtocolClass valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (ProtocolClass protocolClass : ProtocolClass.values()) {
-                map.put(protocolClass.code, protocolClass);
-            }
+    private final static Map<Byte, ProtocolClass> map;
+
+    static {
+        map = new HashMap<>();
+        for (ProtocolClass protocolClass : ProtocolClass.values()) {
+            map.put(protocolClass.code, protocolClass);
         }
+    }
+
+    public static ProtocolClass valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/RejectCause.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/RejectCause.java
index 6437faf..1f2bcf3 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/RejectCause.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/RejectCause.java
@@ -27,8 +27,6 @@ public enum RejectCause {
     INVALID_TPDU_TYPE((byte) 0x02),
     INVALID_PARAMETER_TYPE((byte) 0x03);
 
-    private static Map<Byte, RejectCause> map = null;
-
     private final byte code;
 
     RejectCause(byte code) {
@@ -39,13 +37,16 @@ public enum RejectCause {
         return code;
     }
 
-    public static RejectCause valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (RejectCause rejectCause : RejectCause.values()) {
-                map.put(rejectCause.code, rejectCause);
-            }
+    private final static Map<Byte, RejectCause> map;
+
+    static {
+        map = new HashMap<>();
+        for (RejectCause rejectCause : RejectCause.values()) {
+            map.put(rejectCause.code, rejectCause);
         }
+    }
+
+    public static RejectCause valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/TpduCode.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/TpduCode.java
index 2a568ee..45089ca 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/TpduCode.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/TpduCode.java
@@ -34,8 +34,6 @@ public enum TpduCode {
     TPDU_ERROR((byte) 0x70),
     TPDU_UNKNOWN((byte) 0xFF);
 
-    private static Map<Byte, TpduCode> map = null;
-    
     private final byte code;
 
     TpduCode(byte code) {
@@ -46,13 +44,16 @@ public enum TpduCode {
         return code;
     }
 
-    public static TpduCode valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (TpduCode tpduCode : TpduCode.values()) {
-                map.put(tpduCode.code, tpduCode);
-            }
+    private final static Map<Byte, TpduCode> map;
+
+    static {
+        map = new HashMap<>();
+        for (TpduCode tpduCode : TpduCode.values()) {
+            map.put(tpduCode.code, tpduCode);
         }
+    }
+
+    public static TpduCode valueOf(byte code) {
         if (map.containsKey(code)) {
             return map.get(code);
         }
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/TpduSize.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/TpduSize.java
index 8cdd3b7..f04f2db 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/TpduSize.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/isotp/netty/model/types/TpduSize.java
@@ -30,8 +30,6 @@ public enum TpduSize {
     SIZE_4096((byte) 0x0c, 4096),
     SIZE_8192((byte) 0x0d, 8192);
 
-    private static Map<Byte, TpduSize> map = null;
-    
     private final byte code;
     private final int value;
 
@@ -48,6 +46,15 @@ public enum TpduSize {
         return value;
     }
 
+    private final static Map<Byte, TpduSize> map;
+
+    static {
+        map = new HashMap<>();
+        for (TpduSize tpduSize : TpduSize.values()) {
+            map.put(tpduSize.code, tpduSize);
+        }
+    }
+
     public static TpduSize valueForGivenSize(int pduSize) {
         if(pduSize <= 0) {
             throw new IllegalArgumentException("PduSize has to be greater than 0");
@@ -66,12 +73,6 @@ public enum TpduSize {
     }
 
     public static TpduSize valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (TpduSize tpduSize : TpduSize.values()) {
-                map.put(tpduSize.code, tpduSize);
-            }
-        }
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/DataTransportErrorCode.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/DataTransportErrorCode.java
index 94079a2..8299f79 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/DataTransportErrorCode.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/DataTransportErrorCode.java
@@ -33,8 +33,6 @@ public enum DataTransportErrorCode {
 
     private static final Logger logger = LoggerFactory.getLogger(DataTransportErrorCode.class);
 
-    private static Map<Byte, DataTransportErrorCode> map = null;
-    
     private byte code;
 
     DataTransportErrorCode(byte code) {
@@ -45,13 +43,16 @@ public enum DataTransportErrorCode {
         return code;
     }
 
-    public static DataTransportErrorCode valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (DataTransportErrorCode dataTransportErrorCode : DataTransportErrorCode.values()) {
-                map.put(dataTransportErrorCode.code, dataTransportErrorCode);
-            }
+    private final static Map<Byte, DataTransportErrorCode> map;
+
+    static {
+        map = new HashMap<>();
+        for (DataTransportErrorCode dataTransportErrorCode : DataTransportErrorCode.values()) {
+            map.put(dataTransportErrorCode.code, dataTransportErrorCode);
         }
+    }
+
+    public static DataTransportErrorCode valueOf(byte code) {
         if (!map.containsKey(code)) {
             logger.error("No DataTransportErrorCode for code {}", code);
         }
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/DataTransportSize.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/DataTransportSize.java
index b0da9bb..6a9e46e 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/DataTransportSize.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/DataTransportSize.java
@@ -35,8 +35,6 @@ public enum DataTransportSize {
     REAL((byte) 0x07, false),
     OCTET_STRING((byte) 0x09, false);
 
-    private static Map<Byte, DataTransportSize> map = null;
-    
     private final byte code;
     private final boolean sizeInBits;
 
@@ -53,13 +51,16 @@ public enum DataTransportSize {
         return sizeInBits;
     }
 
-    public static DataTransportSize valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (DataTransportSize dataTransportSize : DataTransportSize.values()) {
-                map.put(dataTransportSize.code, dataTransportSize);
-            }
+    private final static Map<Byte, DataTransportSize> map;
+
+    static {
+        map = new HashMap<>();
+        for (DataTransportSize dataTransportSize : DataTransportSize.values()) {
+            map.put(dataTransportSize.code, dataTransportSize);
         }
+    }
+
+    public static DataTransportSize valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/HeaderErrorClass.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/HeaderErrorClass.java
index b2b5844..aaf6257 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/HeaderErrorClass.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/HeaderErrorClass.java
@@ -30,8 +30,6 @@ public enum HeaderErrorClass {
     ERROR_ON_SUPPLIES((byte) 0x85),
     ACCESS_ERROR((byte) 0x87);
 
-    private static Map<Byte, HeaderErrorClass> map = null;
-
     private final byte code;
 
     HeaderErrorClass(byte code) {
@@ -42,13 +40,16 @@ public enum HeaderErrorClass {
         return code;
     }
 
-    public static HeaderErrorClass valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (HeaderErrorClass headerErrorClass : HeaderErrorClass.values()) {
-                map.put(headerErrorClass.code, headerErrorClass);
-            }
+    private final static Map<Byte, HeaderErrorClass> map;
+
+    static {
+        map = new HashMap<>();
+        for (HeaderErrorClass headerErrorClass : HeaderErrorClass.values()) {
+            map.put(headerErrorClass.code, headerErrorClass);
         }
+    }
+
+    public static HeaderErrorClass valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/MemoryArea.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/MemoryArea.java
index b718ae6..2aa9af9 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/MemoryArea.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/MemoryArea.java
@@ -41,8 +41,6 @@ public enum MemoryArea {
     S7_200_OUTPUTS((byte) 0x07), /* Renamed from "System outputs of 200 family" */
     S7_200_IEC_COUNTERS((byte) 0x1E), /* Renamed from "IEC counters (200 family)" */
     S7_200_IEC_TIMERS((byte) 0x1F); /* Renamed from "IEC timers (200 family)" */
-    
-    private static Map<Byte, MemoryArea> map = null;
 
     private final byte code;
 
@@ -54,13 +52,16 @@ public enum MemoryArea {
         return code;
     }
 
-    public static MemoryArea valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (MemoryArea memoryArea : MemoryArea.values()) {
-                map.put(memoryArea.code, memoryArea);
-            }
+    private final static Map<Byte, MemoryArea> map;
+
+    static {
+        map = new HashMap<>();
+        for (MemoryArea memoryArea : MemoryArea.values()) {
+            map.put(memoryArea.code, memoryArea);
         }
+    }
+
+    public static MemoryArea valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/MessageType.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/MessageType.java
index b372541..7817bcd 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/MessageType.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/MessageType.java
@@ -30,8 +30,6 @@ public enum MessageType {
     ACK_DATA((byte) 0x03),
     USER_DATA((byte) 0x07); /* Renamed from "Userdata" */
 
-    private static Map<Byte, MessageType> map = null;
-    
     private final byte code;
 
     MessageType(byte code) {
@@ -42,13 +40,16 @@ public enum MessageType {
         return code;
     }
 
-    public static MessageType valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (MessageType messageType : MessageType.values()) {
-                map.put(messageType.code, messageType);
-            }
+    private final static Map<Byte, MessageType> map;
+
+    static {
+        map = new HashMap<>();
+        for (MessageType messageType : MessageType.values()) {
+            map.put(messageType.code, messageType);
         }
+    }
+
+    public static MessageType valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/ParameterError.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/ParameterError.java
index 46e8c8e..1bc04ca 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/ParameterError.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/ParameterError.java
@@ -45,8 +45,6 @@ public enum ParameterError {
     L7_UNKNOWN_REQUEST((short) 0xD802),
     L7_INVALID_REQUEST_STATUS((short) 0xD803);
 
-    private static Map<Short, ParameterError> map = null;
-
     private final short code;
 
     ParameterError(short code) {
@@ -57,13 +55,16 @@ public enum ParameterError {
         return code;
     }
 
-    public static ParameterError valueOf(short code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (ParameterError parameterError : ParameterError.values()) {
-                map.put(parameterError.code, parameterError);
-            }
+    private final static Map<Short, ParameterError> map;
+
+    static {
+        map = new HashMap<>();
+        for (ParameterError parameterError : ParameterError.values()) {
+            map.put(parameterError.code, parameterError);
         }
+    }
+
+    public static ParameterError valueOf(short code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/ParameterType.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/ParameterType.java
index 3883be8..4a54d0d 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/ParameterType.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/ParameterType.java
@@ -43,8 +43,6 @@ public enum ParameterType {
 
     private static final Logger logger = LoggerFactory.getLogger(ParameterType.class);
 
-    private static Map<Byte, ParameterType> map = null;
-    
     private final byte code;
 
     ParameterType(byte code) {
@@ -55,13 +53,16 @@ public enum ParameterType {
         return code;
     }
 
-    public static ParameterType valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (ParameterType parameterType : ParameterType.values()) {
-                map.put(parameterType.code, parameterType);
-            }
+    private final static Map<Byte, ParameterType> map;
+
+    static {
+        map = new HashMap<>();
+        for (ParameterType parameterType : ParameterType.values()) {
+            map.put(parameterType.code, parameterType);
         }
+    }
+
+    public static ParameterType valueOf(byte code) {
         if(!map.containsKey(code)) {
             logger.error("ParameterType for code {} not found", code);
         }
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/SpecificationType.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/SpecificationType.java
index 5fd9a18..41e2675 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/SpecificationType.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/SpecificationType.java
@@ -27,8 +27,6 @@ import java.util.Map;
 public enum SpecificationType {
     VARIABLE_SPECIFICATION((byte) 0x12);
 
-    private static Map<Byte, SpecificationType> map = null;
-    
     private final byte code;
 
     SpecificationType(byte code) {
@@ -39,13 +37,16 @@ public enum SpecificationType {
         return code;
     }
 
-    public static SpecificationType valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (SpecificationType specificationType : SpecificationType.values()) {
-                map.put(specificationType.code, specificationType);
-            }
+    private final static Map<Byte, SpecificationType> map;
+
+    static {
+        map = new HashMap<>();
+        for (SpecificationType specificationType : SpecificationType.values()) {
+            map.put(specificationType.code, specificationType);
         }
+    }
+
+    public static SpecificationType valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/TransportSize.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/TransportSize.java
index d31ca11..d6f57e1 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/TransportSize.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/TransportSize.java
@@ -43,8 +43,6 @@ public enum TransportSize {
     IEC_COUNTER((byte) 0x1F, -1),
     HS_COUNTER((byte) 0x20, -1);
 
-    private static Map<Byte, TransportSize> map = null;
-    
     private final byte code;
     private final int sizeInBytes;
 
@@ -61,13 +59,16 @@ public enum TransportSize {
         return sizeInBytes;
     }
 
-    public static TransportSize valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (TransportSize transportSize : TransportSize.values()) {
-                map.put(transportSize.code, transportSize);
-            }
+    private final static Map<Byte, TransportSize> map;
+
+    static {
+        map = new HashMap<>();
+        for (TransportSize transportSize : TransportSize.values()) {
+            map.put(transportSize.code, transportSize);
         }
+    }
+
+    public static TransportSize valueOf(byte code) {
         return map.get(code);
     }
 
diff --git a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/VariableAddressingMode.java b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/VariableAddressingMode.java
index be9fd50..9125d39 100644
--- a/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/VariableAddressingMode.java
+++ b/plc4j/protocols/s7/src/main/java/org/apache/plc4x/java/s7/netty/model/types/VariableAddressingMode.java
@@ -38,8 +38,6 @@ public enum VariableAddressingMode {
     DBREAD((byte) 0xb0),
     SYM1200((byte) 0xb2); /* Renamed from "1200SYM" */
 
-    private static Map<Byte, VariableAddressingMode> map = null;
-    
     private final byte code;
 
     VariableAddressingMode(byte code) {
@@ -50,13 +48,16 @@ public enum VariableAddressingMode {
         return code;
     }
 
-    public static VariableAddressingMode valueOf(byte code) {
-        if (map == null) {
-            map = new HashMap<>();
-            for (VariableAddressingMode variableAddressingMode : VariableAddressingMode.values()) {
-                map.put(variableAddressingMode.code, variableAddressingMode);
-            }
+    private final static Map<Byte, VariableAddressingMode> map;
+
+    static {
+        map = new HashMap<>();
+        for (VariableAddressingMode variableAddressingMode : VariableAddressingMode.values()) {
+            map.put(variableAddressingMode.code, variableAddressingMode);
         }
+    }
+
+    public static VariableAddressingMode valueOf(byte code) {
         return map.get(code);
     }