Auto: s7-d1 — TIA Portal CSV + STEP 7 Classic AWL symbol import

Closes #299
This commit is contained in:
Joseph Doherty
2026-04-26 06:32:18 -04:00
parent ac3fd45cc6
commit a908dff7b5
20 changed files with 2526 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
(* Sample STEP 7 Classic AWL file — PR-S7-D1 / #299 fixture.
Carries a VAR_GLOBAL block (M-area sequential offsets) and a
DATA_BLOCK (DB1, sequential DBW/DBD offsets). Comments stripped
before declaration parsing so this preamble does not affect counts.
*)
VAR_GLOBAL
// M-area globals — assigned sequentially: MW0, MW2, MD4
Speed : INT; // motor speed setpoint
Pressure : INT; // pressure transducer
ActualValue : REAL;
END_VAR
DATA_BLOCK DB1
TITLE = 'Sample DB'
VERSION : 0.1
STRUCT
CycleCount : INT; (* runtime cycle counter *)
Setpoint : REAL := 50.0; (* setpoint with init *)
ActualValue : REAL;
RunFlag : BOOL;
Recipe : STRING[20];
END_STRUCT;
BEGIN
END_DATA_BLOCK

View File

@@ -0,0 +1,9 @@
Name,Path,Data type,Logical address,Comment,Hmi accessible,Hmi visible,Hmi writeable,Length
ProbeWord,Default tag table,UInt,%MW0,Probe word for liveness,True,True,True,
SmokeI16,Default tag table,Int,%DB1.DBW10,Signed 16-bit smoke tag,True,True,True,
SmokeI32,Default tag table,DInt,%DB1.DBD20,Signed 32-bit smoke tag,True,True,True,
SmokeF32,Default tag table,Real,%DB1.DBD30,32-bit float smoke tag,True,True,True,
SmokeBool,Default tag table,Bool,%DB1.DBX50.3,Boolean smoke tag,True,True,True,
RecipeName,Default tag table,String,%DB2.DBB0,Recipe name string,True,True,True,32
CookerCfg,Default tag table,"CookerSettings",%DB10.DBB0,UDT placeholder — wait for D2,True,True,True,
HiddenInternal,Default tag table,Int,%MW100,Internal symbol — should be filtered,False,False,False,
1 Name Path Data type Logical address Comment Hmi accessible Hmi visible Hmi writeable Length
2 ProbeWord Default tag table UInt %MW0 Probe word for liveness True True True
3 SmokeI16 Default tag table Int %DB1.DBW10 Signed 16-bit smoke tag True True True
4 SmokeI32 Default tag table DInt %DB1.DBD20 Signed 32-bit smoke tag True True True
5 SmokeF32 Default tag table Real %DB1.DBD30 32-bit float smoke tag True True True
6 SmokeBool Default tag table Bool %DB1.DBX50.3 Boolean smoke tag True True True
7 RecipeName Default tag table String %DB2.DBB0 Recipe name string True True True 32
8 CookerCfg Default tag table CookerSettings %DB10.DBB0 UDT placeholder — wait for D2 True True True
9 HiddenInternal Default tag table Int %MW100 Internal symbol — should be filtered False False False

View File

@@ -0,0 +1,6 @@
Name;Path;Data type;Logical address;Comment;Hmi accessible;Hmi visible;Hmi writeable;Length
ProbeWort;Standard-Variablentabelle;UInt;%MW0;Probe-Wort für Liveness;WAHR;WAHR;WAHR;
SmokeI16;Standard-Variablentabelle;Int;%DB1.DBW10;Vorzeichenbehaftete 16-bit;WAHR;WAHR;WAHR;
SmokeBool;Standard-Variablentabelle;Bool;%DB1.DBX50,3;Boolescher Smoke-Tag;WAHR;WAHR;WAHR;
SmokeReal;Standard-Variablentabelle;Real;%DB1.DBD30;32-bit float;WAHR;WAHR;WAHR;
VerstecktInternal;Standard-Variablentabelle;Int;%MW100;Internes Symbol — herausgefiltert;FALSCH;FALSCH;FALSCH;
1 Name Path Data type Logical address Comment Hmi accessible Hmi visible Hmi writeable Length
2 ProbeWort Standard-Variablentabelle UInt %MW0 Probe-Wort für Liveness WAHR WAHR WAHR
3 SmokeI16 Standard-Variablentabelle Int %DB1.DBW10 Vorzeichenbehaftete 16-bit WAHR WAHR WAHR
4 SmokeBool Standard-Variablentabelle Bool %DB1.DBX50,3 Boolescher Smoke-Tag WAHR WAHR WAHR
5 SmokeReal Standard-Variablentabelle Real %DB1.DBD30 32-bit float WAHR WAHR WAHR
6 VerstecktInternal Standard-Variablentabelle Int %MW100 Internes Symbol — herausgefiltert FALSCH FALSCH FALSCH

View File

@@ -0,0 +1,83 @@
using System.IO;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
using ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests.S7_1500;
using ZB.MOM.WW.OtOpcUa.Driver.S7.SymbolImport;
namespace ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests.SymbolImport;
/// <summary>
/// PR-S7-D1 / #299 — golden-fixture integration test. Loads the canonical TIA Portal
/// CSV export shipped under <c>Fixtures/sample_tia_export.csv</c>, materialises a
/// driver-options object via <c>AddTiaCsvImport</c>, then exercises the runtime read
/// path against the python-snap7 simulator.
/// </summary>
/// <remarks>
/// <para>
/// The fixture's address layout (<c>%MW0</c>, <c>%DB1.DBW10</c>, <c>%DB1.DBD20</c>,
/// <c>%DB1.DBD30</c>, <c>%DB1.DBX50.3</c>) is deliberately aligned with the seed
/// offsets baked into the snap7 S7-1500 profile (<see cref="S7_1500Profile"/>) so
/// a successful round-trip proves the importer's address normalisation lands at
/// exactly the offsets the simulator seeds — a regression in <c>%</c>-stripping or
/// decimal-comma rewriting surfaces here as a read-mismatch, not a flaky timeout.
/// </para>
/// <para>
/// The test is build-only by default (the assertions run only when the simulator
/// fixture reports <c>SkipReason is null</c>) — local dev invokes it with snap7
/// running; CI relies on the fixture's auto-skip when no simulator is reachable.
/// </para>
/// </remarks>
[Collection(Snap7ServerCollection.Name)]
[Trait("Category", "Integration")]
[Trait("Device", "S7_1500")]
public sealed class TiaCsvImportIntegrationTests(Snap7ServerFixture sim)
{
private static string FixturePath(string name) =>
Path.Combine(AppContext.BaseDirectory, "Fixtures", name);
[Fact]
public async Task Driver_imports_csv_then_reads_seeded_tags()
{
if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason);
// Start with an empty options object pinned at the simulator's endpoint, then
// layer the fixture CSV's tags on top via AddTiaCsvImport. The merge keeps the
// endpoint config (Host, Port, CpuType) untouched and adds the imported tags.
var baseOptions = new S7DriverOptions
{
Host = sim.Host,
Port = sim.Port,
CpuType = global::S7.Net.CpuType.S71500,
Timeout = TimeSpan.FromSeconds(5),
Probe = new S7ProbeOptions { Enabled = false },
Tags = [],
};
var options = baseOptions.AddTiaCsvImport(FixturePath("sample_tia_export.csv"), out var importResult);
// Fixture has 8 rows: 6 importable + 1 UDT placeholder + 1 HMI-hidden (skipped).
importResult.ParsedCount.ShouldBe(7);
importResult.SkippedCount.ShouldBe(1);
importResult.UdtPlaceholderCount.ShouldBe(1);
options.Tags.Count.ShouldBe(7);
await using var drv = new S7Driver(options, driverInstanceId: "s7-tia-import");
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
// Read the seed-aligned tags by their browse-name references. ProbeWord at MW0
// doesn't have a snap7 seed (the simulator profile seeds DB1.DBW0 instead), so
// we focus on the DB-anchored tags whose offsets match the S7_1500Profile seeds.
var snapshots = await drv.ReadAsync(
["SmokeI16", "SmokeI32", "SmokeF32", "SmokeBool"],
TestContext.Current.CancellationToken);
snapshots.Count.ShouldBe(4);
foreach (var s in snapshots) s.StatusCode.ShouldBe(0u, "imported-then-read must succeed end-to-end");
Convert.ToInt32(snapshots[0].Value).ShouldBe((int)S7_1500Profile.SmokeI16SeedValue);
Convert.ToInt32(snapshots[1].Value).ShouldBe(S7_1500Profile.SmokeI32SeedValue);
Convert.ToSingle(snapshots[2].Value).ShouldBe(S7_1500Profile.SmokeF32SeedValue, tolerance: 0.0001f);
Convert.ToBoolean(snapshots[3].Value).ShouldBeTrue();
}
}

View File

@@ -25,6 +25,11 @@
<ItemGroup>
<None Update="Docker\**\*" CopyToOutputDirectory="PreserveNewest"/>
<!-- PR-S7-D1 / #299 — TIA Portal CSV + STEP 7 Classic AWL fixtures used by the
symbol-import integration test. -->
<None Update="Fixtures\sample_tia_export.csv" CopyToOutputDirectory="PreserveNewest"/>
<None Update="Fixtures\sample_tia_export_de_locale.csv" CopyToOutputDirectory="PreserveNewest"/>
<None Update="Fixtures\sample_step7_classic.awl" CopyToOutputDirectory="PreserveNewest"/>
</ItemGroup>
<ItemGroup>