Extract historian into a runtime-loaded plugin so hosts without the Wonderware SDK can run with Historian.Enabled=false

The aahClientManaged SDK is now isolated in ZB.MOM.WW.LmxOpcUa.Historian.Aveva and loaded via HistorianPluginLoader from a Historian/ subfolder only when enabled, removing the SDK from Host's compile-time and deploy-time surface.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-12 15:16:07 -04:00
parent 9e1a180ce3
commit 9b42b61eb6
40 changed files with 739 additions and 15855 deletions

View File

@@ -1,9 +1,9 @@
using System;
using ArchestrA;
using ZB.MOM.WW.LmxOpcUa.Historian.Aveva;
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
using ZB.MOM.WW.LmxOpcUa.Host.Historian;
namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers
namespace ZB.MOM.WW.LmxOpcUa.Historian.Aveva.Tests
{
/// <summary>
/// Fake Historian connection factory for tests. Controls whether connections
@@ -11,20 +11,10 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Helpers
/// </summary>
internal sealed class FakeHistorianConnectionFactory : IHistorianConnectionFactory
{
/// <summary>
/// When set, <see cref="CreateAndConnect"/> throws this exception.
/// </summary>
public Exception? ConnectException { get; set; }
/// <summary>
/// Number of times <see cref="CreateAndConnect"/> has been called.
/// </summary>
public int ConnectCallCount { get; private set; }
/// <summary>
/// When set, called on each <see cref="CreateAndConnect"/> to determine behavior.
/// Receives the call count (1-based). Return null to succeed, or throw to fail.
/// </summary>
public Action<int>? OnConnect { get; set; }
public HistorianAccess CreateAndConnect(HistorianConfiguration config, HistorianConnectionType type)

View File

@@ -1,12 +1,11 @@
using System;
using System.Threading;
using Shouldly;
using Xunit;
using ZB.MOM.WW.LmxOpcUa.Historian.Aveva;
using ZB.MOM.WW.LmxOpcUa.Host.Configuration;
using ZB.MOM.WW.LmxOpcUa.Host.Historian;
using ZB.MOM.WW.LmxOpcUa.Tests.Helpers;
namespace ZB.MOM.WW.LmxOpcUa.Tests.Historian
namespace ZB.MOM.WW.LmxOpcUa.Historian.Aveva.Tests
{
/// <summary>
/// Verifies Historian data source lifecycle behavior: dispose safety,
@@ -76,9 +75,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Historian
}
[Fact]
public void ExtractAggregateValue_UnknownColumn_ReturnsNull()
public void HistorianAggregateMap_UnknownColumn_ReturnsNull()
{
HistorianDataSource.MapAggregateToColumn(new Opc.Ua.NodeId(99999)).ShouldBeNull();
HistorianAggregateMap.MapAggregateToColumn(new Opc.Ua.NodeId(99999)).ShouldBeNull();
}
[Fact]

View File

@@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net48</TargetFramework>
<PlatformTarget>x86</PlatformTarget>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>ZB.MOM.WW.LmxOpcUa.Historian.Aveva.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/>
<PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Shouldly" Version="4.2.1"/>
<PackageReference Include="OPCFoundation.NetStandard.Opc.Ua.Server" Version="1.5.374.126"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ZB.MOM.WW.LmxOpcUa.Historian.Aveva\ZB.MOM.WW.LmxOpcUa.Historian.Aveva.csproj"/>
</ItemGroup>
<ItemGroup>
<!-- Tests reference aahClientManaged so the FakeHistorianConnectionFactory can
implement the SDK-typed IHistorianConnectionFactory interface. -->
<Reference Include="aahClientManaged">
<HintPath>..\..\lib\aahClientManaged.dll</HintPath>
<EmbedInteropTypes>false</EmbedInteropTypes>
</Reference>
<Reference Include="aahClientCommon">
<HintPath>..\..\lib\aahClientCommon.dll</HintPath>
<EmbedInteropTypes>false</EmbedInteropTypes>
</Reference>
</ItemGroup>
</Project>

View File

@@ -5,55 +5,55 @@ using ZB.MOM.WW.LmxOpcUa.Host.Historian;
namespace ZB.MOM.WW.LmxOpcUa.Tests.Historian
{
public class HistorianDataSourceTests
public class HistorianAggregateMapTests
{
[Fact]
public void MapAggregateToColumn_Average_ReturnsAverage()
{
HistorianDataSource.MapAggregateToColumn(ObjectIds.AggregateFunction_Average).ShouldBe("Average");
HistorianAggregateMap.MapAggregateToColumn(ObjectIds.AggregateFunction_Average).ShouldBe("Average");
}
[Fact]
public void MapAggregateToColumn_Minimum_ReturnsMinimum()
{
HistorianDataSource.MapAggregateToColumn(ObjectIds.AggregateFunction_Minimum).ShouldBe("Minimum");
HistorianAggregateMap.MapAggregateToColumn(ObjectIds.AggregateFunction_Minimum).ShouldBe("Minimum");
}
[Fact]
public void MapAggregateToColumn_Maximum_ReturnsMaximum()
{
HistorianDataSource.MapAggregateToColumn(ObjectIds.AggregateFunction_Maximum).ShouldBe("Maximum");
HistorianAggregateMap.MapAggregateToColumn(ObjectIds.AggregateFunction_Maximum).ShouldBe("Maximum");
}
[Fact]
public void MapAggregateToColumn_Count_ReturnsValueCount()
{
HistorianDataSource.MapAggregateToColumn(ObjectIds.AggregateFunction_Count).ShouldBe("ValueCount");
HistorianAggregateMap.MapAggregateToColumn(ObjectIds.AggregateFunction_Count).ShouldBe("ValueCount");
}
[Fact]
public void MapAggregateToColumn_Start_ReturnsFirst()
{
HistorianDataSource.MapAggregateToColumn(ObjectIds.AggregateFunction_Start).ShouldBe("First");
HistorianAggregateMap.MapAggregateToColumn(ObjectIds.AggregateFunction_Start).ShouldBe("First");
}
[Fact]
public void MapAggregateToColumn_End_ReturnsLast()
{
HistorianDataSource.MapAggregateToColumn(ObjectIds.AggregateFunction_End).ShouldBe("Last");
HistorianAggregateMap.MapAggregateToColumn(ObjectIds.AggregateFunction_End).ShouldBe("Last");
}
[Fact]
public void MapAggregateToColumn_StdDev_ReturnsStdDev()
{
HistorianDataSource.MapAggregateToColumn(ObjectIds.AggregateFunction_StandardDeviationPopulation)
HistorianAggregateMap.MapAggregateToColumn(ObjectIds.AggregateFunction_StandardDeviationPopulation)
.ShouldBe("StdDev");
}
[Fact]
public void MapAggregateToColumn_Unsupported_ReturnsNull()
{
HistorianDataSource.MapAggregateToColumn(new NodeId(99999)).ShouldBeNull();
HistorianAggregateMap.MapAggregateToColumn(new NodeId(99999)).ShouldBeNull();
}
}
}

View File

@@ -34,14 +34,6 @@
<HintPath>..\..\lib\ArchestrA.MxAccess.dll</HintPath>
<EmbedInteropTypes>false</EmbedInteropTypes>
</Reference>
<Reference Include="aahClientManaged">
<HintPath>..\..\lib\aahClientManaged.dll</HintPath>
<EmbedInteropTypes>false</EmbedInteropTypes>
</Reference>
<Reference Include="aahClientCommon">
<HintPath>..\..\lib\aahClientCommon.dll</HintPath>
<EmbedInteropTypes>false</EmbedInteropTypes>
</Reference>
</ItemGroup>
<ItemGroup>