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 Type | OPC UA Type | Description |
|---|---|---|
| INT | Int32 | 16-bit signed integer |
| DINT | Int32 | 32-bit signed integer |
| REAL | Float | 32-bit floating point number |
| LREAL | Double | 64-bit floating point number |
| BOOL | Boolean | 1-bit boolean value |
| BYTE | Byte | 8-bit unsigned integer |
| WORD | UInt16 | 16-bit unsigned integer |
| DWORD | UInt32 | 32-bit unsigned integer |
| STRING | String | String data type (up to 255 characters) |
| TIME | Duration | Time duration in milliseconds |
| DATE | Date | Date value |
| DATE_AND_TIME | DateTime | Date 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
INTfrom the PLC for a UAVariable taht contains aFloatdata type, the binding will convert theINTvalue to aFloatvalue 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,DB2INT<number>: Integer data type, e.g.,INT12REAL<number>: Real data type, e.g.,REAL16X<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; scaledefaults to1,offsetto0, 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
DoubleorFloat— 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:
| Configuration | Why it is rejected |
|---|---|
scale: 0 | every reading would collapse to the offset, and the transform could never be inverted |
scale: .nan, offset: .inf | a non-finite multiplier poisons every sample |
scale on a Boolean or String node | there 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:
| Form | Example | Notes |
|---|---|---|
| raw address | M1:holding:12,float32 | the machine type is required — a register is untyped on the wire |
| declared point | M1:Temperature | address, 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:TemperatureandM1:holding:10,int16are 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.