//
//  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
//
//      https://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.
//
:imagesdir: ../../images/
:icons: image
:iconsdir: ../_images/users/protocols
:source-highlighter: rouge
//:coderay-linenums-mode: inline
//:coderay-css: class

= S7 (Step7)

image::users/protocols/s7_banner.png[banner,512,167]

The S7 driver talks to Siemens Simatic S7 controllers over the S7comm protocol carried on
ISO-on-TCP (COTP). It exploits the advanced features of the S7-300 and S7-400 families -
device status (RUN/STOP) events, system/user diagnostic events, alarms, cyclic (CYC) push
subscriptions and SZL/SSL diagnostics - and provides basic PUT/GET read and write access to
the S7-1200 and S7-1500 families, whose firmware does not expose the same event and
diagnostics services. S7-200 and S7-200 Smart controllers are reachable too, through a small
set of connection and addressing conventions documented below. The driver is also reported to
work against VIPA hardware, a Siemens-compatible clone of the S7-300 with extended memory.

Most closed-source S7 stacks, and even the best-known open source one, Snap7, are built
around a simple request/response pattern. PLC4X's S7 driver goes further by giving open,
non-Siemens applications access to the asynchronous, timestamped data that would otherwise
require the Siemens ecosystem itself - SOFTNET OPC-DA/OPC-UA, the low-level SAPI-S7 library,
or a DCS/SCADA product such as PCS7, Braumat or WinCC.

The current stable release of the driver is 0.10.0 (2022-10-04). Community support is
available on the PLC4X development list (dev@plc4x.apache.org); companies offering
commercial support for PLC4X drivers are listed on the project website.

== Supported Operations

[cols="2,2a,5a"]
|===
|Name |Value |Description

|
| `read`
| C, Go and Java.

|
| `write`
| C, Go and Java.

|
| `subscribe`
| Go and Java. Two independent flavors: cyclic (`CYC`) push of ordinary tags at a
requested interval, and alarm subscriptions (`ALM` for a live push feed, `QUERY:ALARM_S` /
`QUERY:ALARM_8` for a one-shot fetch of what is currently buffered). Both ride the S7Comm
"UserData" services that the driver probes for at connect time; a device that does not answer
that probe - and S7-1200/S7-1500 controllers, which do not implement the alarm/event
subsystem the S7-300/S7-400 family does - rejects a subscription with `UNSUPPORTED`. The C
driver does not subscribe.

|
| `browse`
| Go and Java, under the same UserData-service condition as `subscribe`. Enumerates the
static memory areas plus the data blocks (`DB`) currently present on the connected device -
this is a live inventory of what can be addressed, not a network scan.

|
| `discover`
| Java only, and only when the optional `pcap4j` runtime and a system `libpcap` are
installed. Sends a PROFINET DCP broadcast and collects device announcements - this is the
network-discovery API, not `browse`; it finds devices, it does not enumerate tags on one.

|===

== Connection String

The connection string has the layout

----
s7://{ip-address}?{options}
----

`SCHEMA` is always `s7`; `cotp` is the only transport and is therefore omitted. The `ip
address` names the PLC or the CP that fronts it, and `options` is an ampersand-separated list
of key/value parameters, documented in full below.

----
s7://10.10.1.33?cotp.remote-rack=0&cotp.remote-slot=3&controller-type=S7_400
----

NOTE: When connecting to a Siemens `LOGO` device, add `?controller-type=LOGO` to the
connection string. Siemens seems to have only partially implemented the protocol on these
devices: without pinning the type, the device terminates the connection as soon as the driver
tries to read the SZL table it normally uses to auto-detect which S7 device family it is
talking to.

== Connection String Options

include::partial$s7.adoc[]

== Tag Addresses

Addressing is implemented in C, Go and Java. See the
xref:protocols/index.adoc[protocol support matrix] for what each implementation does.

=== General Format

The PLC4X S7 driver addresses TIA Portal's own address notation, so addresses can generally
be copied directly out of TIA Portal.

In general all S7 addresses have this format:

----
%{Memory-Area}{start-address}[{selection}]:{Data-Type}
----

If the selection is omitted, a single element is read.
The selection in brackets is the shared array notation - a single index, an inclusive
range, and optionally the array's declared lower bound. See
xref:array-notation.adoc[Addressing arrays] for the full set of forms and what this
driver can express.

Generally there are two types of addresses:

----
Bit-Addresses  {Memory-Area-Code}{Start-Byte-Address}.{Bit-Offset}:BOOL[{Count}]
Byte-Addresses {Memory-Area-Code}{Start-Byte-Address}:{Data-Type-Code}[{count}]
----

Bit addresses are only used if the datatype `BOOL` is used. The array notation of these can
be omitted, in which case a `Count` of 1 is used by default. `Start-Byte-Address` and
`Bit-Offset` both represent unsigned integer values.

In case of accessing data in the `data block` memory area, the syntax is a bit more complex:

----
DB{Data-Block-Number}.DB{Short-Data-Type-Code}{Start-Byte-Address}.{Bit-Offset}:BOOL[{Count}]
DB{Data-Block-Number}.DB{Short-Data-Type-Code}{Start-Byte-Address}:{Data-Type-Code}[{Count}]
----

When reading a `STRING` datatype, currently 254 characters are automatically fetched from the
PLC. To limit the amount of data transferred, the `STRING` type declaration syntax was
extended to allow declaring a shorter length:

----
DB{Data-Block-Number}.DB{Short-Data-Type-Code}{Start-Byte-Address}:STRING({string-length})[{Count}]
----

The data-block forms above carry some unnecessary boilerplate (the `.DB` in the middle, as
well as the `Short-Data-Type-Code`), so a shorter syntax is also accepted, and handled
identically to the long form:

----
DB{Data-Block-Number}:{Start-Byte-Address}.{Bit-Offset}:BOOL[{Count}]
DB{Data-Block-Number}:{Start-Byte-Address}:{Data-Type-Code}[{Count}]
DB{Data-Block-Number}:{Start-Byte-Address}:STRING({string-length})[{Count}]
----

=== Memory Areas

The S7 driver currently allows access to the following memory areas.

The `Code` column represents the code that is used in above general address syntax:

Not all S7 device types support the same full set of memory areas, so the last column gives more information on which types a given memory area is supported on.

[cols="2,2,5a,2"]
|===
|Code |Name |Description |Supported PLC Types

|C
|COUNTERS
|TODO: Document this
|TODO: Document this

|T
|TIMERS
|TODO: Document this
|TODO: Document this

|D
|DIRECT_PERIPHERAL_ACCESS
|TODO: Document this
|TODO: Document this

|I
|INPUTS
|Inputs (Digital and Analog ... usually Analog Inputs just have a start-address offset to separate them from the digital ones)
|All

|Q
|OUTPUTS
|Outputs (Digital and Analog ... usually Analog Outputs just have a start-address offset to separate them from the digital ones)
|All

|M
|FLAGS_MARKERS
|TODO: Document this
|TODO: Document this

|DB
|DATA_BLOCKS
|Memory areas containing user-defined data structures usually accessed by the integer data block number. Please note that data block addresses have a little more complex address format.
|All

|DBI
|INSTANCE_DATA_BLOCKS
|TODO: Document this
|TODO: Document this

|LD
|LOCAL_DATA
|TODO: Document this
|TODO: Document this

|===

=== Data Types

[cols="1,1,2,4,1,1"]
|===
|Code | Short-Code |Name |Description |Size in bits | Supported PLC Types

6+|Bit-Strings (Will all interpreted as sequence of boolean values in PLC4X)
|BOOL           |X |Bit                     |Single boolean value       |1  |All
|BYTE           |B |Byte                    |Array of 8 boolean values  |1  |All
|WORD           |W |Word                    |Array of 16 boolean values |2  |All
|DWORD          |D |Double-Word             |Array of 32 boolean values |4  |All
|LWORD          |X |Long-Word               |Array of 64 boolean values |8  |S7_1500

6+|Integer values
|SINT           |B |Small int               |8 bit integer (signed)     |1  |S7_1200, S7_1500
|USINT          |B |Small unsigned int      |8 bit integer (unsigned)   |1  |S7_1200, S7_1500
|INT            |W |Integer                 |16 bit integer (signed)    |2  |All
|UINT           |W |Unsigned integer        |16 bit integer (unsigned)  |2  |S7_1200, S7_1500
|DINT           |D |Double integer          |32 bit integer (signed)    |4  |All
|UDINT          |D |Unsigned Double Integer |32 bit integer (unsigned)  |4  |S7_1200, S7_1500
|LINT           |X |Long integer            |64 bit integer (signed)    |8  |S7_1500
|ULINT          |X |Unsigned long integer   |64 bit integer (unsigned)  |8  |S7_1500

6+|Floating point values
|REAL           |D |Real                    |32 bit IEEE 754 full precision floating point value (signed)                           |4  |All
|LREAL          |X |Long Real               |64 bit IEEE 754 double precision floating point value (signed)                         |8  |S7_1200, S7_1500

6+|Character values
|CHAR           |B |Character               |8 bit character                                                                        |1  |All
|WCHAR          |X |Double byte character   |16 bit character value                                                                 |2  |S7_1200, S7_1500
|STRING         |X |String                  |String 2 + n bytes                                                                     |1  |All
|WSTRING        |X |Double byte String      |String of 16 bit characters 2 + n bytes                                                |1  |S7_1200, S7_1500

6+|Temporal values
|S5TIME          |X |S5 Time            |S5 Time (like in duration)                                                                 |2  |S7_300, S7_400, S7_1500
|TIME            |X |Time               |Time (like in duration) (Minutes, Seconds, Milliseconds)                                   |4  |All
|LTIME           |X |Long Time          |Long Time (like in duration) (Minutes, Seconds, Milliseconds, Microseconds, Nanoseconds)   |8  |S7_1500
|DATE            |X |Date               |Date                                                                                       |2  |All
|TIME_OF_DAY     |X |Time of day        |Time (like in 4:40PM)                                                                      |4  |All
|DATE_AND_TIME   |X |Date and Time      |Date and time (like in 03.05.2020 4:40 PM)                                                 |8  |S7_300, S7_400, S7_1500
|===

=== Examples

.Examples
[cols="2,3" ,options="header"]
|===
|Address |Meaning
|`%DB1.DBX0.0:BOOL` |the single bit at data block 1, byte 0, bit 0
|`%DB10.DBW20:INT` |a 16-bit signed integer at data block 10, byte 20
|`%I0.0:BOOL` |the single bit at input byte 0, bit 0
|`%MD100:DINT` |a 32-bit signed integer at flag/marker byte 100
|`%DB1.DBB0[0..9]:BYTE` |10 bytes starting at data block 1, byte 0, returned as a list
|`%DB5:10:INT` |a 16-bit signed integer at data block 5, byte 10, using the shorter data-block syntax
|`%DB1.DBB0[0..15]:RAW_BYTE_ARRAY` |16 raw bytes starting at data block 1, byte 0, decoded as `BYTE` - `RAW_BYTE_ARRAY` is accepted as an alias for `BYTE`
|`%DB1.DB0:STRING(80)` |an `STRING` of up to 80 characters at data block 1, byte 0 - fewer than the 254-character default
|`%DB1.DB0:STRING` |a `STRING` at data block 1, byte 0, read at its default maximum length
|`ALM` |subscribes to the live alarm push feed (see Protocol Details)
|`QUERY:ALARM_S` |one-shot read of the alarms currently buffered on the PLC, `ALARM_S` layout
|===

== Notes and Tips

Especially when it comes to the input- and output addresses for analog channels, the start
addresses are configurable and hereby don't always start at the same address.
In order to find out what addresses these ports have, please go to the `device setting` of
your PLC in `TIA Portal`.

image::users/protocols/s7-tia-device-settings.png[devsettings,640,480,,align="center"]

Especially pay attention to this part:

image::users/protocols/s7-tia-io-settings.png[iosettings,600,144,,align="center"]

In above image you can see that this device has 8 digital inputs (`DI 8`) and 2 analog inputs
(`AI 2_1`) as well as 6 digital outputs (`DQ 6`).

The start addresses of the digital inputs and outputs start directly at `0`. The analog
inputs however start at address `64`.

Each digital input and output can be addressed by a single bit-address (start-address and
offset) or can be read in a block by reading a full byte starting at the given start address
without providing a bit offset.

== Protocol Details

=== Siemens S7-200 / S7-200 Smart

The S7-200 and the newer S7-200 Smart are lower-end controllers that behave a little differently from the S7-300/400/1200/1500 family. They can still be reached with the S7 driver over Ethernet (ISO-on-TCP), but they need a couple of specific connection settings and use a different convention for addressing their variable memory.

This section collects what the community has found to work (see PLC4X issues #1925 and #2641).

==== Connecting

Two things are important when connecting to an S7-200 (Smart):

. Disable the automatic controller-type detection by passing `controller-type=S7_200`. The S7-200 does not answer the SZL/SSL identification request the driver normally uses to auto-detect the CPU family, so — as with the `LOGO` device mentioned above — the type has to be provided explicitly.
. Provide the TSAP (Transport Service Access Point) values explicitly. The S7-200 (Smart) expects both the local and the remote TSAP to be `0x0200`, which is `512` in decimal. Set them with `cotp.local-tsap=512` and `cotp.remote-tsap=512`; a non-zero TSAP overrides the rack/slot based addressing.

A minimal, working connection string therefore looks like this:

----
s7://192.168.12.100?cotp.local-tsap=512&cotp.remote-tsap=512&controller-type=S7_200
----

`cotp.remote-rack=0` and `cotp.remote-slot=0` are the defaults, so they are optional, but may be added for clarity:

----
s7://192.168.12.100?cotp.remote-rack=0&cotp.remote-slot=0&cotp.local-tsap=512&cotp.remote-tsap=512&controller-type=S7_200
----

Both reading and writing have been confirmed to work against an S7-200 Smart with this configuration.

==== Addressing the V (variable) memory area

The S7-200 keeps its user variables in a `V` (variable) memory area, addressed in the Siemens tooling (STEP 7-Micro/WIN) as, for example, `VW310`, `VD732` or `V350.0`. The S7 driver does not have a dedicated `V` memory-area code.

On the S7-200, however, the `V` area is simply an alias for `Data Block 1` (`DB1`). Any `V` address can therefore be read and written by addressing `DB1` instead, using the normal data-block syntax described above:

[cols="2,3,3"]
|===
|S7-200 notation |PLC4X address |Meaning

|`VB100`  |`%DB1.DBB100:BYTE`   |single byte at V100
|`VW310`  |`%DB1.DBW310:INT`    |16-bit word at V310
|`VD732`  |`%DB1.DBD732:UDINT`  |32-bit double word at V732
|`V350.0` |`%DB1.DBX350.0:BOOL` |single bit V350.0
|===

The shorter data-block notation works as well, for example `%DB1:732:UDINT` for `VD732`.

The `M` (flags/markers) memory area is addressed as usual, for example `%M10.0:BOOL` or `%MW20:INT`.

[NOTE,icon=s7_note.png]
Native support for a dedicated `V` memory-area code may be added in a future release. Until then, please use the `DB1` alias shown above.

=== Actors participating in the communication process

PLC programming in general is a Pandora's box!

Here we will assume that you use standard technological functions/libraries within your development cycle, therefore, at this point it is important to point out the actors that participate in this dialogue between the driver and the PLC and how they affect the communication cycle.

The different actors involved in communication are shown in image 1.


[plantuml,s7h_image01,svg,opts="inline",svg-type="inline",align="center"]
....
autonumber "<b>(00)"
title Participants in the communication model of the S7 driver.
footer Image 1

actor App
participant PLC4X
box "PLC (AS)" #LightBlue
participant OS
participant PCS7
participant S7App
endbox

box "CP" #LightGray
participant CP
endbox

....


. `PLC (AS)`, the controller. `AS` is the reference used in PCS7.
. `App`,  your application.
. `PLC4X`, implementation of the S7 driver.
. `OS`, PLC operating system.
. `PCS7`, represents the technological functions used in the PLC. PCS7 are Siemens DCS libraries.
. `S7App`, your application that runs on the PLC.
. `CP`, the communications CP will depend on your architecture and requirements, for an S7-300 it will be a CP 343-1 or a CP 443-1 for an S7-400.


==== S7 Read/Write

[plantuml,s7h_image02,svg,opts="inline",svg-type="inline",align="center"]
....
autonumber "<b>(00)"
title PLC4X Simatic S7 <b>MODE</b> Suscription.
footer Image 2

actor App
participant PLC4X
box "PLC (AS)" #LightBlue
participant OS
participant PCS7
participant S7App
endbox

box "CP" #LightGray
participant CP
endbox

App -> PLC4X : subscription("MODE")
PLC4X -> OS
OS -> PLC4X
PLC4X -> App : OK
App -> PLC4X : Register the consumer
OS -> OS : STOP
OS --> PLC4X
PLC4X --> App : to consumer
...latter...
OS -> OS : WARM_RESTART
OS --> PLC4X
PLC4X --> App : to consumer
OS -> OS : RUN
OS --> PLC4X
PLC4X --> App : to consumer
....

==== S7 Event Subscription

[NOTE,icon=s7_note.png]
`onSubscribe` accepts exactly two kinds of tag: an `S7AlarmTag` (`ALM`, `QUERY:ALARM_S`
or `QUERY:ALARM_8`) subscribed as an `EVENT`, or an ordinary `S7Tag` subscribed as
`CYCLIC`; anything else is rejected with `INVALID_ADDRESS`. The `MODE`, `SYS` and `USR`
subscriptions this section once documented were built on an `addEventField()` /
`S7ModeEvent` / `S7SysEvent` / `S7UserEvent` API that is no longer present in the source
tree; those subsections have been commented out in this page's source rather than deleted.
`ALM` and cyclic (`CYC`) subscriptions are what actually works today.

The S7 driver allows the subscription to asynchronous events generated in the PLC.

This type of event is generated by S7-300, S7-400, G120C-PN, S120-PN controllers and VIPA devices. Unfortunately for the S7-1200 and S7-1500 series this functionality has been superseded.

[NOTE,icon=s7_note.png]
For a complete list of compatibility between the S7-300,400 and S7-1200 & S7-1500, you can see the document in [1] provided by Siemens.

These services have the following advantages:

. Report the status of the CPUs and other components within the control architecture that support it (CP, IM, DI, etc).
. Transfer of values when a change occurs.
. Associate values to the events sent.
. A better handling of the TimeStamp of the associated values.

The messages are classified into two groups depending on how they are generated:

. SCAN: All those events generated by the system or preset in Step7 (TIA Portal). The change of state of the configured signals is carried out by the operating system at specific intervals (500 ms, 100ms or 16 ms).
. ALARM: These are events generated by the user application using the alarm blocks (ALARM_S, ALARM_SQ, NOTIFY, ALARM, ALARM_8). In addition to user applications, these events can be generated from technological functions such as PCS7 or Braumat.

The data associated with the events is represented in a HashMap in order to facilitate its transfer to other applications based on a standard such as JMS, MQTT or other messaging technology.

The handling of the TimeStamp of the SCAN type events is generated in the computer. In ALARM type messages the TimeStamps are generated in the PLC. It is extremely important that the date and time synchronization is done between both computers and PLC.

The values associated with the events can have different types of representation, so their interpretation must be agreed upon during the programming of the application in the PLC and your application.

For each type of event, the particular fields of type <String, T> will be arranged within the Map. These will be documented for each type of event.

To maximize the use of the data fields associated with the events, the use of the intra-area pointer system and the ANY type pointer is recommended in the PLC, As well as the recommendations for the management of the time stamp [2].


At the user application level `App`, you can use the PLC4X API to subscribe SCAN or ALARM type events by selecting any of the following fields according to the requirement:

. `MODE`: Change of operating state in the controller, change from STOP to RUN and vice versa.
. `SYS`: System events, associated with internal events of the controller or events previously parameterized for their indication.
. `USR`: Events programmed by the user and that are registered in the internal diagnostic buffer.
. `ALM`: Alarm events generated by the user program, ALARM_S, ALARM_8, NOTIFY.

In the following sections we will describe in more detail the functionalities of each field.

==== SCAN Events

[NOTE,icon=s7_note.png]
The `MODE`, `SYS` and `USR` subscriptions that this section used to describe are not
available in the current driver: the `S7ModeEvent`, `S7SysEvent`, `S7UserEvent` and
`addEventField()` API they were written against no longer exists in the source tree. Those
subsections are retained in this page's source as a comment block rather than deleted, so
the protocol-level description can be restored if the API returns or rewritten against the
current one. What the driver implements today is the `ALM` subscription described below,
and cyclic (`CYC`) subscriptions covered further on.

////
COMMENTED OUT - dead API, retained for reference only.

The MODE and SYS/USR subscription sections below document an API that no longer exists:
S7ModeEvent, S7SysEvent, S7UserEvent and the addEventField() call are absent from the
source tree. S7CotpConnection.onSubscribe accepts only an S7AlarmTag (EVENT) or an
S7Tag (CYCLIC). The protocol-level description of what MODE/SYS/USR events mean on the
wire is preserved here rather than deleted, so it can be restored if the API returns or
rewritten against the current one.
////

////
===== Subscription to MODE events (S7ModeEvent).

By subscribing to controller status changes or `MODE` events, the PLC status changes can be tracked.

Depending on the CPU model, these state changes are followed in the user application (PLC program), OB100 and OB101, allowing these applications to be brought to a safe state.

Now, how do these state changes affect external applications, for example HMI or custom user applications?

In the use of a unified Siemens architecture, the operator panels (HMI) and WinCC (Scada) detect the status of the CPU and pass the quality of the points in the database in real time to poor quality.

In the case of an application developed with PLC4X, the use of MODE events will allow your application to indicate to users the quality of the points used, and that by design the quality is not updated in the controller.


[plantuml,s7h_image03,svg,opts="inline",svg-type="inline",align="center"]
....
autonumber "<b>(00)"
title PLC4X Simatic S7 <b>MODE</b> Suscription.
footer Image 3

actor App
participant PLC4X
box "PLC (AS)" #LightBlue
participant OS
participant PCS7
participant S7App
endbox

box "CP" #LightGray
participant CP
endbox


App -> PLC4X : subscription("MODE")
PLC4X -> OS
OS -> PLC4X
PLC4X -> App : OK
App -> PLC4X : Register the consumer
OS -> OS : STOP
OS --> PLC4X
PLC4X --> App : to consumer
...latter...
OS -> OS : WARM_RESTART
OS --> PLC4X
PLC4X --> App : to consumer
OS -> OS : RUN
OS --> PLC4X
PLC4X --> App : to consumer
....


From image 2, we can describe the sequence of actions that can be followed for subscription. In the first place, the subscription process occurs from the *App* of the user (1)(2)(3)(4), having a positive response the application is ready to receive the events asynchronously from the *PLC (AS)*.

Suppose that the manager for a reason passes the controller to *STOP* (06) through the front switch or from the engineering station, then *OS* proceeds to send a notification (07)(08) to all consoles that are registered to receive this event.

Subsequently, the manager decides to switch the controller to execution mode, through the front switch or the engineering console, at this time the *OS* is in charge of generating the startup events, initially it indicates the hot start *WARN_RESTART* (09)(10)(11) and if the startup is successful, indicate that the controller is in execution mode or *RUN* (12)(13)(14).

The information received in (08)(11)(14) is included in the attached table.

|===
|Field |Type |Description

|TYPE |STRING |Fixed value.
|TIMESTAMP |Instant |Instant.now () value assigned when receiving the event from the PLC.
|MAP |HashMap |The HashMap with all fields.
|METHOD |byte |Value of "method" as defined in S7Parameter.
|FUNCTION |byte |Value of "function" as defined in S7Parameter.
|CURRENT_MODE |short |Status value reported in the event. Check the ModeTransitionType enum.
|===

With the sequence diagram and the data structures that will be received by the application, we can analyze the Java code for this specific function. We think this should serve as a pseudocode for the other languages.


[source,java]
----
public class PLCEventModeSubscription {

   public static void main(String[] args) throws Exception {
    try (PlcConnection connection  new PlcDriverManager()
			.getConnection("s7://192.168.1.51?cotp.remote-rack=0&cotp.remote-slot=3&controller-type=S7_400")) {

      final PlcSubscriptionRequest.Builder subscription  connection.subscriptionRequestBuilder(); // <01>

      subscription.addEventField("myMODE", "MODE");
      final PlcSubscriptionRequest sub  subscription.build();

      System.out.println("Query: " + sub.toString());

      final PlcSubscriptionResponse subresponse  sub.execute().get();

      if (subresponse.getResponseCode("myMODE") = PlcResponseCode.OK) { //<04>
				PlcConsumerRegistration registerMode =
        	subresponse
          	.getSubscriptionHandle("myMODE") //<05>
          	.register(msg -> { //<08><11><14>
           		System.out.println("******** S7ModeEvent ********");
            	Map<String, Object> map = ((S7ModeEvent) msg).getMap();
            	map.forEach((x, y) -> {
              	System.out.println(x + " : " + y);
            	});
            	short currentmode = (short)
              map.get(S7ModeEvent.Fields.CURRENT_MODE.name());
            	System.out.println("CURRENT_MODE MSG: " + ModeTransitionType.enumForValue(currentmode).name());
            	System.out.println("****************************");
          	});
			}
          System.out.println("Waiting for the messages.");
          Thread.sleep(120000);
          connection.close();
          System.out.println("Ending the connection.");
        }
    }
}
----


===== Subscription to SYS events (S7SysEvent) and USER events (S7UserEvent).

System events allow to receive asynchronously any event that affects the operation of the controller, or any of its peripheral equipment that is capable of sending events through a PROFIBUS or Profinet fieldbus.

[plantuml,s7h_image04,svg,opts="inline",svg-type="inline",align="center"]
....
autonumber "<b>(00)"
title PLC4X Simatic S7 System Event <b>SYS</b> subscription.
footer Image 4

actor App
participant PLC4X
box "PLC (AS)" #LightBlue
participant OS
participant PCS7
participant S7App
endbox

box "CP" #LightGray
participant CP
endbox

App -> PLC4X : subscription("MODE")
PLC4X -> OS
OS -> PLC4X
PLC4X -> App : OK
App -> PLC4X : Register the consumer

CP -> OS : STOP
OS ->  OS : To Diagnostic buffer
OS --> PLC4X
PLC4X --> App : to consumer
....

A first example of its use is the change of state of a CP, IM or FM within the architecture of the controller. This will allow the application to indicate that there is an effect on the system that may affect the quality of the signals used, allowing preventive or corrective actions to be taken as required.

[plantuml,s7h_image05,svg,opts="inline",svg-type="inline",align="center"]
....
autonumber "<b>(00)"
title PLC4X User Event <b>USR</b> subscription.
footer Image 5

actor App
participant PLC4X
box "PLC (AS)" #LightBlue
participant OS
participant PCS7
participant S7App
endbox

box "CP" #LightGray
participant CP
endbox

App -> PLC4X : subscription("USR")
PLC4X -> OS
OS -> PLC4X
PLC4X -> App : OK
App -> PLC4X : Register the consumer

S7App -> OS : To diagnostic buffer
S7App -> OS : To registered console
OS --> PLC4X
PLC4X --> App : to consumer
....

In general, system and user events are part of the same group of events, but they are differentiated to facilitate their processing.

From the sequence diagrams after subscribing to the required event type (01)(02)(03)(04), the consumer (05) is registered to start receiving the events either from the *SYS* system or from the user *USR*.

When the event is generated, it is sent to the diagnostic buffer (06) and an image of it is sent to all consoles registered to receive this type of event (07) distributed by the OS (08).

Since at the protocol level the events are not differentiated, the PLC4X driver (08) is in charge of classifying the events in *SYS* or *USR* and transferring them to the registered consumer (09).

[TIP,icon=s7_tip.png]
For didactic purposes, a step-by-step explanation has been carried out, but in general the *App* application can be subscribed to the four types of events simultaneously.


The following table shows the fields available for each message.



|===
|Field |Type |Description

|TYPE |STRING |Fixed value.
|TIMESTAMP |Instant |Instant.now () value assigned when receiving the event from the PLC.
|EVENT_ID |short |OS generated event ID.
|PRIORITY_CLASS |byte |Value of "method" as defined in S7Parameter.
|OB_NUMBER |byte |Value of "function" as defined in S7Parameter.
|DAT_ID |short |Status value reported in the event. Check the ModeTransitionType enum.
|INFO1 |WORD |System information 1 word long.
|INFO2 |DWORD |System information 2 words l ng.
|===

For SYS events, the EVENT_ID is generated automatically by the *OS*, and basically they are constant in the different families of controllers.

For the USER or User-defined events follow the same pattern as system events. They have the particularity that the value of EVENT_ID must be between the values 0xAXXX and 0xBYYY.

This programming of the user-defined events is carried out at the level of the *PLC(AS)* controller, so we recommend the technical note [3] of the Siemens portal.

[TIP,icon="s7_tip.png"]
In the case of user-defined messages, it is important to take into account that these are reported to the diagnostic buffer, which has a limited capacity depending on the CPU model used. Also take into account that the diagnostic buffer works like a circular buffer, so the oldest messages will be lost.

....
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   |15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1|
   +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
   \__________/\__________/\____________________/
    Event class     IDs         Event number

    Event Class:
       1   Standard OB Events
       2   Synchronous errors
       3   Asynchronous errors
       4   Mode transition
       5   Run-time events
       6   Communications events
       7   Events for fail-safe and fault tolerant systems
       8   Standardized diagnostic data on modules
       9   Predefined user events
     A,B   Freely definable events
   C,D,E   Reserved
       F   Events for modules other than CPUs (for example, CPs, FMs)

   IDs (Bit)
       8   0:Event leaving state, 1:Event entering state
       9   1:Entry in diagnostic buffer
      10   1:Internal error
      11   1:External error
....

In the previous table we can see how the event classes are coded, and how they are classified. If you require detailed information on each event, the user's *App* must interpret the indicated bits.

In the INFO1 and INFO2 fields, specific diagnostic information associated with the event is generally attached, or some information that needs to be recorded in the case of user events.

The INFO1 field contains information that can be stored in a word, namely, WORD, INT of ARRAY [0..1] OF CHAR.

The INFO2 field contains information that can be stored in a double word, namely, DWORD, DINT, REAL, TIME, ARRAY [0..3] OF CHAR.

Below is an example code for the subscription of events type *SYS*.

[source,java]
----
public static void main(String[] args) throws Exception {
 try (PlcConnection connection  new PlcDriverManager().
  getConnection("s7://192.168.1.51?cotp.remote-rack=0&cotp.remote-slot=3&controller-type=S7_400")) {

   final PlcSubscriptionRequest.Builder subscription  connection.subscriptionRequestBuilder(); //<01>

   subscription.addEventField("mySYS", "SYS");
   final PlcSubscriptionRequest sub  subscription.build();

   System.out.println("Query: " + sub.toString());

   final PlcSubscriptionResponse subresponse  sub.execute().get();

   PlcConsumerRegistration registerSys =
    subresponse
     .getSubscriptionHandle("mySYS") //<05>
     .register(msg -> { //<09>
      System.out.println("******** S7SysEvent ********");
      Map<String, Object> map = ((S7SysEvent) msg).getMap();
      map.forEach((x, y) -> {
       System.out.println(x + " : " + y);
      });
      Integer eventid = (Integer) map.get(S7SysEvent.Fields.EVENT_ID.name());
      System.out.println("DIAGNOSTIC: " + S7DiagnosticEventId.
      valueOf(eventid.shortValue()).getDescription()); //<10>
      System.out.println("****************************");
     });

   System.out.println("Waiting for the messages.");
   Thread.sleep(120000);
   connection.close();
   System.out.println("Ending the connection.");
  }
 }
----

And below is an example code for the subscription of events type *USR*.

[source,java]
----
public static void main(String[] args) throws Exception {
 try (PlcConnection connection  new PlcDriverManager().
  getConnection("s7://192.168.1.51?cotp.remote-rack=0&cotp.remote-slot=3&controller-type=S7_400")) {

   final PlcSubscriptionRequest.Builder subscription  connection.subscriptionRequestBuilder();

   subscription.addEventField("myUSR", "USR");
   final PlcSubscriptionRequest sub  subscription.build();

   System.out.println("Query: " + sub.toString());

   final PlcSubscriptionResponse subresponse  sub.execute().get();

   PlcConsumerRegistration registerUsr =
    subresponse
    .getSubscriptionHandle("myUSR") //<05>
    .register(msg -> {
     System.out.println("******** S7UserEvent *******");
     Map<String, Object> map = ((S7UserEvent) msg).getMap();
     map.forEach((x, y) -> { //<09>
      System.out.println(x + " : " + y);
     });
     System.out.println("****************************");
    });

   System.out.println("Waiting for the messages.");
   Thread.sleep(120000);
   connection.close();
   System.out.println("Ending the connection.");
  }
 }
----

The Java code shows how to detect the type of event in an event type *SYS*. In the S7 driver, there is an enum object _S7DiagnosticEventId_(10) that allows us to identify which internal event of the *PLC(AS)* generated it and thus, through the interpretation of the INFO1 and INFO2 fields, determine the root cause of the event.

[NOTE,icon=s7_note.png]
To date, the enum object _S7DiagnosticEventId_ contains a considerable amount of diagnostic values, it must be updated according to the new CPUs or firmware versions available.

Unlike *SYS* events, *USR* events must be interpreted directly by the *App* application, so they are generally scheduled during the development phase of the *S7App* application.

By having INFO1 and INFO2 in the *S7App* program, the user can transfer data associated with events, such as transitions between phases, events of diagnostic routines such as firts-out or the start or end of a batch process, all asynchronously.

////
////
END of commented-out dead MODE and SYS/USR API sections.
////

==== ALARM Events

===== Subscription to ALM type events

[NOTE,icon=s7_note.png]
This is the one event class of the four originally described in this section that the
current driver actually implements, as a real `PlcSubscriptionRequest` on an `S7AlarmTag`
(`ALM` for the live push feed shown here; `QUERY:ALARM_S` / `QUERY:ALARM_8` for a one-shot
fetch of what is currently buffered - see `S7CotpConnection.onSubscribe`). The diagram
below illustrates the message flow at the protocol level. Note that the older
`S7AlarmEvent` class is not part of the current API - a subscription delivers a standard
`PlcSubscriptionEvent`.

[plantuml,s7h_image06,svg,opts="inline",svg-type="inline",align="center"]
....
autonumber "<b>(00)"
title PLC4X Simatic S7 Alarm Event <b>ALM</b> suscription.
footer Image 6

actor App
participant PLC4X
box "PLC (AS)" #LightBlue
participant OS
participant PCS7
participant S7App
endbox

box "CP" #LightGray
participant CP
endbox

App -> PLC4X : subscription("ALM")
PLC4X -> OS
OS -> PLC4X
PLC4X -> App : OK
App -> PLC4X : Register the consumer

App -> PLC4X : Request alarms
PLC4X -> OS
OS -> PLC4X : Alarm_1,Alarm_2,
PLC4X -> OS : Next seq
OS -> PLC4X : Alarm_3,Alarm_4,Alarm_9000
PLC4X -> OS : Next seq
OS -> PLC4X : Alarm_5,Alarm_6,
PLC4X -> OS : Next seq
OS -> PLC4X : Alarm_7,Alarm_8.

PLC4X -> App : Alarm_1,Alarm_2,Alarm_3,Alarm_4,Alarm_9000,Alarm_5,Alarm_6,Alarm_7,Alarm_8
PCS7 --> OS : Alarm_1000 to console
OS --> PLC4X
PLC4X --> App : Alarm_1000 to consumer

S7App --> OS : Alarm_9000 to console
OS --> PLC4X
PLC4X --> App : Alarm_9000 to consumer

S7App --> OS : Alarm_6 User defined alarm to console
activate S7App
OS --> PLC4X
PLC4X --> App : Alarm_6 to consumer

S7App -> S7App : Wait for Alarm_6 ack

App -> PLC4X : ACK Alarm_6
PLC4X -> OS : ACK Alarm_6
OS -> PLC4X : OK
PLC4X -> App : OK
deactivate S7App
OS --> PLC4X : Alarm_6 status update
PLC4X --> App : to consumer

....

The registration sequence for subscription is the typical one carried out so far (01)(02)(03)(04)(05). From that moment on, you can start receiving alarm events asynchronously.

Depending on your application, you can make a request for the currently active alarms in the alarm buffer of the *PLC(AS)*, in this way you can prepare a reception buffer or establish the correct state of a state machine that depends on the Active events in the controller.

You must take into account that when making the request (06), from a few to hundreds of alarms can be stored depending on the complexity of your application and the capacity of the *PLC (AS)*.

In this scenario, the *PLC4X* driver maintains the dialogue with the *OS* to receive sequentially (07)(08)(09)(10)(11)(12)(13)(14) the alarms stored on the controller, to later transfer them to the user application *App* (15).

At the end of the subscription process, it will begin to receive the events generated by the system, such as high precision time signals (16)(17)(18)(19) or events generated by the user application (20)(21)(22).

This simple sequence of events is used by process applications based on PCS7, for the handling of alarms, events and logging of practically all the events of the distributed control system (DCS).

Another important feature of the driver is the ability to recognize the alarms generated from the *PLC(AS)*. In (23)(24)(25) the *S7App* application generates an alarm/event that is required to be acknowledged by the user to continue with the execution of a specific routine. The user applications *App* generates the acknowledgment (27)(28) using the corresponding alarm identifier, the *OS* is responsible for making the confirmation (29)(30) and asynchronously generating an event for the update of the state machine in the *App*(31)(32).

Within the cyclical execution of the application *S7App* waits for the confirmation of the alarm (26) to continue with some specific routine.

TODO: Field description

|===
|Field |Type |Description

|TYPE  | |
|TIMESTAMP | |
|TIMESTAMP_GOING | |
|TIMESTAMP_COMING | |
|ASSOCIATED_VALUES | |
|MAP | |
|EVENT_ID | |
|EVENT_STATE | |
|STATE | |
|ACKSTATE_GOING | |
|ACKSTATE_COMING | |
|EVENT_GOING | |
|EVENT_COMING | |
|EVENT_LAST_CHANGE | |
|SIG | |
|SIG_[1...8] | |
|SIG_STATE | |
|SIG_[1...8]_STATE | |
|SIG_DATA | |
|SIG_[1...8]_DATA | |
|SIG_[1...8]_DATA_GOING | |
|SIG_[1...8]_DATA_CO | |
|SIG_[1..8]_DATA_STATUS | |
|SIG_[1...8]_DATA_SIZE | |
|SIG_[1...8]_DATA_LENGTH | |
|===


TODO: Example code

=== Cyclic subscription (CYC)

The cyclical subscription allows the acquisition of data in passive mode, that is, the data is sent from the PLC in a cyclical and synchronous way. Unlike the `MODE`/`SYS`/`USR` event
classes above, this one *is* implemented by the current driver: subscribe an ordinary
`S7Tag` with subscription type `CYCLIC` and a requested `Duration`, and the driver picks a
matching hardware time base and factor for you.

[plantuml,s7h_image07,svg,opts="inline",svg-type="inline",align="center"]
....
autonumber "<b>(00)"
title Participants in the communication model of the S7 driver.
footer Image 7

actor App
participant PLC4X
box "PLC (AS)" #LightBlue
participant OS
participant PCS7
participant S7App
endbox

box "CP" #LightGray
participant CP
endbox

....


The data transfer has three time bases:

. *B01SEC*: Time base 0.1 Sec. (100 mSec.).
. *B1SEC*: Time base 1.0 Sec.
. *B10SEC*: Time base 10 Sec.

The driver prefers the coarsest base whose factor still fits in a byte, because S7-300
firmware from V3.2 onward rejects `B01SEC` (100 ms) subscription requests outright
(`errCode=0xD804`), honoring only `B1SEC` and `B10SEC`; sub-second requests still fall back to
`B01SEC` since it is the only base able to express them, and will be rejected by that
firmware.

=== SZL System Status List

The system status list gives access to the operating data of the PLC, such as memory space, operating status, status of the control switches, as well as diagnostic data of expansion cards or decentralized peripherals, PROFIBUS or PROFINET .

This is fundamental data to determine the quality of the data supplied by the PLC.

By initiating the connection with the PLC you can determine its operating status, which will allow you to define the quality of the data taken and what the implemented application can do or not, eventually this is the procedure carried out by the Siemens CPs.

[NOTE,icon=s7_note.png]
Why SZL and not SSL? Well, creative freedom. The translation of the manuals from German to Spanish use SZL and German to English use SSL. Both the source code and the documentation use this reduction to keep the text uniform.

Due to the fact that the data structures are so varied, basically one per type of diagnosis, the decision was made to return these as an array of bytes, leaving the developer to implement the parser according to their requirements.

For a first approach to using system state lists a byte array to JSON notation parser is available at "org.apache.plc4x.java.s7.readwrite.utils.StaticHelper.SZL" .

[NOTE,icon=s7_tip.png]
Make use of the XXX document for a detailed explanation of each SZL, since as indicated, everything will depend on the hardware you have installed.

[NOTE,icon=s7_note.png]
The driver itself reads SZLs internally - to auto-detect the controller type at connect
time, and to back `browse` - through `S7SzlService`/`S7CotpConnection`, not through a
user-addressable `PlcTag`. The `SZL_ID=...;INDEX=...` tag address used in the walkthrough
below does not match any pattern `S7PlcTagHandler` accepts today (it does not start with the
`%` every other S7 address requires), so `addTagAddress` with that string throws
`PlcInvalidTagException` against the current driver. The SZL_ID/INDEX encoding itself and the
JSON shape of a response remain accurate and are kept for reference; only the specific
"read it as a normal PlcTag" mechanism shown here is out of date.

=== Notation for SZL request

The access to the SZL of the PLC is done as a read request, where the PLCTag is formed by two fields "SZL_ID" and "INDEX".

. SZL_ID: Number assigned to the SZL list. There are variants of the SZL_ID depending on whether the request is total, partial or just the header. To facilitate its use, the representation will always be in hexadecimal with the format 0xxyID.

....

  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  |15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1|
  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
  \__________/\__________/\____________________/
   Module      Number of   Number of the partial
   class       the partial list
               list
               extract
....

.. Number of the partial list : The number of the partial SZL list you want to read.


.. Number of the partial list extract: Defines which part of the partial list you want to extract. It varies from list to list.

.. Module Class: The requests for the partial list depend on the hardware that is being used, generally the IM (communication cards), FM (special functions) and CP (periphery controller) have their own diagnostic system, which can be consulted through of the SZL_ID/INDEX PlcTag.

....
  Module Class:
  +--------------+-----------------+
  | Module class | Coding (Binary) |
  +--------------|-----------------+
  |     CPU      |      0000       |
  +--------------|-----------------+
  |     IM       |      0100       |
  +--------------|-----------------+
  |     FM       |      1000       |
  +--------------|-----------------+
  |     CP       |      1100       |
  +--------------|-----------------+
....

. INDEX: Number of the required sublist. To facilitate its use, the representation will always be in hexadecimal with the format 0xhhhh.



image::users/protocols/mi_001.png[mi_001]


[source,java]
----
public static void main(String[] args) throws Exception {
    System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "trace");

    System.out.println("******************************************************************************************");
    System.out.println("Before using, take a look at:");
    System.out.println("System Software for S7-300/400.\r\nSystem and Standard Functions - Volume 1/2");
    System.out.println("Document: A5E02789976-01");
    System.out.println("Chapter 34 System Status Lists (SSL).");
    System.out.println("URL: https://cache.industry.siemens.com/dl/files/604/44240604/att_67003/v1/s7sfc_en-EN.pdf");
    System.out.println("******************************************************************************************");

    try (PlcConnection connection  new DefaultPlcDriverManager().getConnection("s7://10.10.1.33?cotp.remote-rack=0&cotp.remote-slot=3&controller-type=S7_400")) { //(01)

        final PlcReadRequest.Builder readrequest  connection.readRequestBuilder(); //(02)

        readrequest.addTagAddress("MySZL", "SZL_ID=16#0012;INDEX=16#0000"); //(03)

        final PlcReadRequest rr  readrequest.build(); //(04)
        final PlcReadResponse szlresponse  rr.execute().get(); //(05)

        if (szlresponse.getResponseCode("MySZL") = PlcResponseCode.OK){ //(06)

            Collection<Byte>  data  szlresponse.getAllBytes("MySZL"); //(07)
            byte[] dbytes  ArrayUtils.toPrimitive(data.toArray(new Byte[data.size()])); //(08)

            SZL szl  SZL.valueOf(0x0012); //(09)
            ByteBuf wb  wrappedBuffer(dbytes); //(10)
            StringBuilder sb =  szl.execute(wb); //(11)
            System.out.println(sb.toString());  //(12)

        } else if (szlresponse.getResponseCode("MySZL") = PlcResponseCode.NOT_FOUND){ //(13)
                System.out.println("SZL is not supported.");
        }

            Thread.sleep(2000);
            System.out.println("Bye...");

      }
    }
----

The request for the SZL lists follows the same pattern of variable readings, for each request a response, unlike the request for process variables where several can be grouped in a single request, the SZL request must correspond to one request to one petition.

Like other requests, the connection URL (01) is established and the request constructor instance (02) is created. The associated PLCTag is added to the diagnostic list (one per request), in this case the SZL_ID=0x0012 and INDEX=0x0000 (03) which allows obtaining the identification and firmware of the PLC.

In (04) and (05), we prepare and execute the request to the PLC. If we have a valid response (06) we can perform the processing of the data stream obtained, which as indicated is an array of bytes which is obtained in (07) and (08).

As we pointed out in the support libraries, we have an "SZL" object (an enum), which allows us to select the appropriate parser based on the numerical index SLZ_ID (09). In (10) we make a wrapper in a ByteBuf type (from the Netty library) in order to pass it to the "szl" instance through the "execute" method (11).

When processing the data buffer we must obtain in (12) a StringBuilder with the JSON representation.

....
{"RECORDS":[{"AUSBG2":0,"AUSBG1":61,"BGTYP":130,"INDEX":1,"MIFB":"6ES7 417-4XT05-0AB0 "},{"AUSBG2":0,"AUSBG1":2,"BGTYP":130,"INDEX":6,"MIFB":"6ES7 417-4XT05-0AB0 "},{"AUSBG2":768,"AUSBG1":22021,"BGTYP":0,"INDEX":7,"MIFB":"                    "},{"AUSBG2":3,"AUSBG1":22021,"BGTYP":0,"INDEX":129,"MIFB":"Boot Loader         "}],"LENGTHDR":28,"SZL-ID":17,"INDEX":0,"N_DR":4}
....

As noted above, the parser performed on the SZL enum is not complete, so the missing information must be obtained from the returned fields. For further details you should consult [].


In case of not being able to process the request, it is detected in (13) to take the necessary measures.

The following diagram represents the information in JSON format.


[plantuml,s7h_szlxy11demo,svg,opts="inline",svg-type="inline",align="center"]
....
@startjson
{"RECORDS":[{"AUSBG2":0,"AUSBG1":61,"BGTYP":130,"INDEX":1,"MIFB":"6ES7 417-4XT05-0AB0 "},{"AUSBG2":0,"AUSBG1":2,"BGTYP":130,"INDEX":6,"MIFB":"6ES7 417-4XT05-0AB0 "},{"AUSBG2":768,"AUSBG1":22021,"BGTYP":0,"INDEX":7,"MIFB":"                    "},{"AUSBG2":3,"AUSBG1":22021,"BGTYP":0,"INDEX":129,"MIFB":"Boot Loader         "}],"LENGTHDR":28,"SZL-ID":17,"INDEX":0,"N_DR":4}
@endjson
....


From the obtained StringBuilder, you can use the JSON processor of your choice to access the different fields.



|===
|*Module class* |*SZL-ID* |*Implemented*
|SZL List  |16#xy00 | Tested with S7-400, JSON
|Module identification |16#xy11 |Tested with S7-400, JSON
|CPU characteristics |16#xy12 |Tested with S7-400, JSON
|User memory areas |16#xy13   |Tested with S7-400, JSON
|System areas |16#xy14   |Tested with S7-400, JSON
|Block types  |16#xy15   |Tested with S7-400, JSON
|Interrupt status |16#xy22   |Tested with S7-400, JSON
|Assignment between process image partitions and OBs |16#xy25   |Tested with S7-400, JSON
|Communication status data |16#xy32   |Tested with S7-400, JSON
|H CPU group information |16#xy71   |
|Status of the module LEDs |16#xy74   |Tested with S7-400, JSON
|Switched DP slaves in the H-system |16#xy75   |
|Module status information |16#xy91   |Tested with S7-400, JSON
|Rack / station status information |16#xy92   |Tested with S7-400, JSON
|Rack / station status information |16#xy94   |Tested with S7-400, JSON
|Extended DP master system / PROFINET IO system information |16#xy95   |Tested with S7-400, JSON
|Module status information, PROFINET IO and PROFIBUS DP |16#xy96   |Tested with S7-400, JSON
|Tool changer information (PROFINET IO) |16#xy9C   |Tested with S7-400, JSON
|Diagnostic buffer of the CPU |16#xyA0   |Tested with S7-400, JSON
|Module diagnostic information (data record 0) |16#xyB1   |Tested with S7-400, JSON
|Module diagnostic information (data record 1), geographical address|16#xyB2   |Tested with S7-400, JSON
|Module diagnostic information (data record 1), local address |16#xyB3   |Tested with S7-400, JSON
|Diagnostic data of a DP slave |16#xyB4   |Tested with S7-400, JSON
|===

== More Information
1. https://snap7.sourceforge.net/
2. https://support.industry.siemens.com/cs/document/13649203/simatic-net-pc-software-s7-programming-interface?dti=0&dl=en&lc=es-ES
3. https://support.industry.siemens.com/cs/document/109797648/simatic-comparison-list-for-s7-300-s7-400-s7-1200-s7-1500?dti=0&lc=en-WW
4. https://support.industry.siemens.com/cs/mdm/109746537?c=98956468747&lc=es-DO
5. https://support.industry.siemens.com/cs/document/15166942/writing-user-specific-information-to-the-diagnostic-buffer-of-a-cpu-(sfc-52)?dti=0&lc=en-ES
6. https://support.industry.siemens.com/cs/document/24013249/how-do-you-display-alarm_s-or-alarm_d-messages-with-process-values-(so-called-associated-values)-on-a-wincc-flexible-operator-panel-?dti=0&lc=en-WW
7. https://support.industry.siemens.com/cs/document/109481157/comunicaci%C3%B3n-entre-paneles-de-operador-simatic-hmi-y-convertidores-de-frecuencia-sinamics-g120-para-el-manejo-y-la-visualizaci%C3%B3n-de-avisos-de-fallo-y-de-advertencia-(enlace-directo-sin-controlador)?dti=0&lc=es-WW
8. https://support.industry.siemens.com/cs/document/97550333/sinamics-g-s-hmi-direct-access?dti=0&lc=en-WW
9. https://support.industry.siemens.com/cs/document/21402122/technology-cpus-technology-template-error-messages-?dti=0&lc=en-WW
10. https://support.industry.siemens.com/cs/document/77467239/transforming-warning-and-error-messages-of-a-sinamics-drive-with-the-aid-of-the-xml-parser-and-integrating-them-into-a-step-7-hmi-project?dti=0&lc=en-WW
11. https://support.industry.siemens.com/cs/document/24037531/%C2%BFqu%C3%A9-pasos-de-configuraci%C3%B3n-hay-que-realizar-en-el-simotion-scout-y-el-wincc-flexible-para-que-se-muestren-los-avisos-alarm_s-y-las-alarmas-tecnol%C3%B3gicas-dentro-del-wincc-flexible-runtime-?dti=0&lc=es-ES
