Skip to main content

Brownfield Siemens S7 Devices

Siemens S7 binding​

The Siemens S7 binding allows you to connect to Siemens S7 PLCs (like S7-1200, S7-1500) using the TCP/IP protocol. This binding supports reading and data from/to the PLC. For more advanced use case, we recommend using the OPC UA binding, which provides a more comprehensive interface to the PLC. This binding is useful for legacy system integration or when the OPC UA server is not available on the PLC.

Configuration​

brownfieldDevices:
s7:
- name: S7-1200
type: TCP
address: 10.20.30.40
port: 102

Mapping S7 data to your OPC UA Variable​

You can use the mapping section in the configuration file to map S7 data to OPC UA Variables.

mapping:
- node: /di:DeviceSet/own:MyObject/own:MyInt32
s7: plc1:DB1,INT12

- node: /di:DeviceSet/own:MyObject/own:MyFloat1
s7: plc1:DB2,REAL16

- node: /di:DeviceSet/own:MyObject/own:MyFloat2
value: |
(
$mb := $s7("plc1:DB2,REAL16");
{
"value": $mb.value / 10.0 + 3.14,
"statusCode": $mb.statusCode
};
)

- node: /di:DeviceSet/own:MyObject/own:MyFloatArray
s7: plc1:DB1,REAL24.4

- node: /di:DeviceSet/own:MyObject/own:MyBoolArray
s7: plc1:DB3,X0.4.4

- node: /di:DeviceSet/own:MyObject/own:MyBool
s7: plc1:DB3,X0.2

S7 Data Types​

The S7 binding supports various data types used in Siemens S7 PLCs. The following table lists the S7 data types and their corresponding OPC UA types:

S7 TypeOPC UA TypeDescription
INTInt3216-bit signed integer
DINTInt3232-bit signed integer
REALFloat32-bit floating point number
LREALDouble64-bit floating point number
BOOLBoolean1-bit boolean value
BYTEByte8-bit unsigned integer
WORDUInt1616-bit unsigned integer
DWORDUInt3232-bit unsigned integer
STRINGStringString data type (up to 255 characters)
TIMEDurationTime duration in milliseconds
DATEDateDate value
DATE_AND_TIMEDateTimeDate and time value

automatic typ coercion​

  • We recommend using the correct S7 data type in the mapping to ensure that the data is correctly interpreted by the binding.
  • However, the S7 binding automatically converts the S7 data types to the corresponding OPC UA data types when reading or writing data. For example, if you read an INT from the PLC for a UAVariable taht contains a Float data type, the binding will convert the INT value to a Float value before storing it in the OPC UA Variable.

S7 Addressing​

The S7 binding uses the following addressing scheme:

  • DB<number>: Data block, e.g., DB1, DB2
  • INT<number>: Integer data type, e.g., INT12
  • REAL<number>: Real data type, e.g., REAL16
  • X<number>.<byte>.<bit>: Bit addressing, e.g., X0.4.4 (byte 0, bit 4)
  • M<number>: Memory area, e.g., M0.0 (memory byte 0, bit 0)
  • I<number>: Input area, e.g., I0.0 (input byte 0, bit 0)
  • Q<number>: Output area, e.g., Q0.0 (output byte 0, bit 0)
  • P<number>: Peripheral area, e.g., P0.0 (peripheral byte 0, bit 0)

Reading Array of Values​

You can read arrays of values from the PLC by specifying the data type and the starting address. The binding will automatically convert the array elements to the corresponding OPC UA data types.

mapping:
- node: /di:DeviceSet/own:MyObject/own:MyIntArray
s7: plc1:DB1,INT12[0..9] # Read an array of 10 INT values from DB1 starting at address INT12
  • automatic coercion of the array elements to the corresponding OPC UA data types is supported.
  • the corresponding UA variable must be an array or a matrix type with valid dimensions.

Scaling a raw value (scale / offset)​

PLCs commonly store engineering values as scaled integers — a temperature in tenths of a degree, a pressure in hundredths of a bar. A direct link can apply the conversion itself, with no expression:

mapping:
- node: /di:DeviceSet/own:MyObject/own:BoilerTempCelsius
s7: plc1:DB1,INT2
scale: 0.1 # engineering = raw * scale + offset
offset: 0

A raw 214 is published as 21.4.

  • the formula is always raw * scale + offset — the offset is applied after the multiplication;
  • scale defaults to 1, offset to 0, so omitting both leaves the value untouched;
  • arrays are scaled element-wise;
  • the target OPC UA Variable must be a numeric type, and should usually be a Double or Float — scaling into an integer node truncates the fraction you introduced the scale to obtain.

The server refuses to start on a transform that cannot work:

ConfigurationWhy it is rejected
scale: 0every reading would collapse to the offset, and the transform could never be inverted
scale: .nan, offset: .infa non-finite multiplier poisons every sample
scale on a Boolean or String nodethere is no numeric value to scale

:::note Modbus is different Modbus declares scaling on the point, not on the binding:

points:
- name: Temperature
at: holding:10
type: int16
scale: 0.1

That is where an imported vendor mapping file already puts it. Declaring scale: on a modbus: mapping entry is rejected rather than silently multiplied a second time. :::

For anything beyond a linear transform — combining two tags, a conditional, a non-linear curve — use a value: expression instead (below).

Flexibility of the mapping​

Notes​

  • The S7 binding supports reading and writing data to the PLC.

Declared inputs (inputs:)​

When a value comes from more than one tag, or needs more than a linear transform, declare the inputs by name and let the expression refer to them. The formula then contains no addresses at all:

mapping:
- node: /di:DeviceSet/own:MyObject/own:MixedIndex
inputs:
raw: plc1:DB2,REAL16 # S7
power: M1:holding:12,float32 # Modbus, raw address + machine type
temp: M1:Temperature # Modbus, by declared point name
amb: eip0:Temperature # EtherNet/IP
value: $raw.value / 10 + $power.value

Each input is bound as $name, and carries { value, statusCode, sourceTimestamp } exactly like the $s7() accessor does.

Addressing. An input is always <connection>:<address>. The protocol is deduced from the connection name, so a name used by two connections is a load-time error. Modbus accepts two forms:

FormExampleNotes
raw addressM1:holding:12,float32the machine type is required — a register is untyped on the wire
declared pointM1:Temperatureaddress, type and endianness come from the point

A coil or discrete input takes no machine type: M1:coil:3.

Why not just call $s7(...) in the formula? Because inputs written into the expression are invisible: nothing outside the text knows they are inputs, the same address can appear twice under different local names, and the editor cannot draw or edit them. Declared inputs are data — enumerable, nameable, and checkable.

What declaring inputs buys you​

  • One read per address. Two inputs naming the same register cost one device read, not two. M1:Temperature and M1:holding:10,int16 are the same reading and are deduplicated accordingly.
  • Reads happen in parallel. Two $s7() calls in a hand-written formula are two sequential round-trips; declared inputs are fetched concurrently.
  • A bad input never becomes a good-looking number. If any input is not Good, the formula is skipped and the first non-Good status — in declaration order — is published.

Two limits worth knowing​

:::caution Inputs are read eagerly Every declared input is fetched on every evaluation, even if the formula would not have used it. A genuinely conditional read — $cond ? $a() : $b() — should stay a raw accessor call inside value:, which keeps working exactly as before and is not deprecated. :::

:::caution Modbus inputs must be inside the poll plan A Modbus value is served from the polling tables, and those are derived from declared points:. Naming a raw address that no table covers is a load-time error (INPUT-E015) that tells you what is polled. Naming a declared point always works. :::

using value JSondata functions​

You can use the $s7 Josonata function to read data from the PLC in a more flexible way. This allows you to perform calculations, transformations, and other operations on the S7 data before storing it in the OPC UA Variable.

mapping:
- node: /di:DeviceSet/own:MyObject/own:MyFloat2
value: |
(
$mb := $s7("plc1:DB2,REAL16");
{
"value": $mb.value / 10.0 + 3.14,
"statusCode": $mb.statusCode
};
)
```


The `$s7` function takes a string argument that specifies the PLC name, data block, and data type. The function returns a JSON object with the `value` and `statusCode` fields.