@@ -966,6 +966,84 @@ Two surface options:
|
||||
Full reference: [`docs/drivers/S7-TIA-Import.md`](../drivers/S7-TIA-Import.md).
|
||||
CLI flag table: [`docs/Driver.S7.Cli.md` "import-symbols"](../Driver.S7.Cli.md#import-symbols).
|
||||
|
||||
## UDT / STRUCT support
|
||||
|
||||
PR-S7-D2 / #300 — UDT-typed DBs are exposed via per-member fan-out at driver
|
||||
init time. The driver reads / writes / subscribes only ever target scalar
|
||||
leaves; the parent UDT pointer never reaches the wire. This keeps the rest of
|
||||
the driver pipeline (address parser, block-coalescing planner, scan-group
|
||||
partitioner, deadband filter) UDT-unaware.
|
||||
|
||||
### `S7UdtDefinition`
|
||||
|
||||
A UDT is declared once in `S7DriverOptions.Udts` and referenced by tags whose
|
||||
`UdtName` is set:
|
||||
|
||||
```csharp
|
||||
new S7UdtDefinition(
|
||||
Name: "Pump",
|
||||
Members: [
|
||||
new S7UdtMember("Pressure", Offset: 0, S7DataType.Float32),
|
||||
new S7UdtMember("Status", Offset: 4, S7DataType.Int16),
|
||||
new S7UdtMember("Enabled", Offset: 6, S7DataType.Bool),
|
||||
],
|
||||
SizeBytes: 7);
|
||||
```
|
||||
|
||||
Tags adopt the UDT layout via `UdtName`:
|
||||
|
||||
```csharp
|
||||
new S7TagDefinition("Pump1", "DB1.DBX0.0", S7DataType.Byte, UdtName: "Pump");
|
||||
```
|
||||
|
||||
### Fan-out semantics
|
||||
|
||||
At `InitializeAsync` time the driver:
|
||||
|
||||
1. Walks `_options.Tags`. For each tag with `UdtName`, looks up the UDT in
|
||||
`_options.Udts` (case-insensitive).
|
||||
2. For each UDT member, computes `parent.Address.ByteOffset + member.Offset`
|
||||
and emits one scalar `S7TagDefinition` per leaf with name
|
||||
`Parent.Member` (dot-separated).
|
||||
3. Array members emit `Member[0]`, `Member[1]`, ... at stride `elementBytes`.
|
||||
4. Nested UDT members recurse — array-of-UDT walks at stride `inner.SizeBytes`.
|
||||
5. The fanned-out leaves replace the parent UDT tag in the driver's tag map.
|
||||
|
||||
Reads / writes / subscribes that target the parent name surface
|
||||
`BadNodeIdUnknown` — clients must address the leaves directly.
|
||||
|
||||
### 4-level nesting cap
|
||||
|
||||
UDT-of-UDT is supported up to 4 levels deep. Anything deeper throws
|
||||
`InvalidOperationException("UDT nesting depth exceeds 4 levels…")` at Init.
|
||||
This catches accidentally-recursive declarations early; real industrial UDTs
|
||||
rarely go beyond 2 layers.
|
||||
|
||||
### Optimized block access — must be off
|
||||
|
||||
The static-offset model assumes member byte offsets in the declaration match
|
||||
the runtime layout exactly. TIA Portal's "Optimized block access" flag lets
|
||||
the runtime reorder members for memory alignment, breaking that assumption.
|
||||
Same prerequisite as general absolute-offset DB addressing on S7-1200 / 1500:
|
||||
**Optimized block access must be disabled** on any DB that the driver
|
||||
addresses by absolute offset, including UDT-typed DBs.
|
||||
|
||||
If a customer can't disable Optimized access (e.g., shared-DB constraints),
|
||||
the workaround is to expose the UDT through the symbolic-tag path once that
|
||||
ships — not in PR-S7-D2.
|
||||
|
||||
### Validation
|
||||
|
||||
The fan-out rejects, with clear errors:
|
||||
|
||||
- UDT name not found in `Udts` collection
|
||||
- Member offsets not in ascending order
|
||||
- Member offsets that overlap (a primitive's `[offset, offset+width)` range
|
||||
intersects the next member's offset)
|
||||
- Total members extending past `SizeBytes`
|
||||
- Tag with `UdtName` AND `ElementCount > 1` (array-of-UDT belongs in the UDT
|
||||
layout, not at the parent-tag level)
|
||||
|
||||
## References
|
||||
|
||||
1. Siemens Industry Online Support, *Modbus/TCP Communication between SIMATIC S7-1500 / S7-1200 and Modbus/TCP Controllers with Instructions `MB_CLIENT` and `MB_SERVER`*, Entry ID 102020340, V6 (Feb 2021). https://cache.industry.siemens.com/dl/files/340/102020340/att_118119/v6/net_modbus_tcp_s7-1500_s7-1200_en.pdf
|
||||
|
||||
Reference in New Issue
Block a user