Implement EnsureTagAsync (live-verified) + DeleteTagAsync (DelT semantics partial)
New SDK surface:
HistorianClient.EnsureTagAsync(HistorianTagDefinition)
HistorianClient.DeleteTagAsync(string tagName)
Plumbing:
src/AVEVA.Historian.Client/Models/HistorianTagDefinition.cs
Public input model — TagName/Description/EngineeringUnit/DataType/MinEU/MaxEU.
Currently only HistorianDataType.Float is live-verified.
src/AVEVA.Historian.Client/Wcf/HistorianTagWriteProtocol.cs
SerializeAnalogCTagMetadata produces 146-byte payload byte-for-byte
identical to the captured native EnsT2(Float) request.
SerializeDeleteTagNames produces ushort 0x6751 + ushort 1 + uint count
+ per-tag (uint charCount + UTF-16 chars).
src/AVEVA.Historian.Client/Wcf/HistorianWcfTagWriteOrchestrator.cs
Both EnsT2 and DelT run the full Stat-priming chain captured for the
analog flow (UpdC3 + Stat.GetV ×3 + Stat.GETHI ×2 + 7× GetSystemParameter
+ Trx.GetV + Retr.GetV).
src/AVEVA.Historian.Client/Wcf/HistorianWcfTagClient.cs
MapDataType extended to accept tag-origin marker 0xC7 (SDK-created tags).
Tests:
5 golden-byte tests (HistorianTagWriteProtocolTests):
SerializeAnalogCTagMetadata byte-for-byte match against captured 146-byte fixture
SerializeAnalogCTagMetadata produces different bytes for different inputs
SerializeDeleteTagNames single-tag matches captured shape
SerializeDeleteTagNames multi-tag appends each
SerializeDeleteTagNames empty list throws
1 live integration test (gated by HISTORIAN_WRITE_SANDBOX_TAG):
EnsureTagAsync_AndDeleteTagAsync_RoundTrip_AgainstLocalHistorian
EnsureTagAsync creates the sandbox tag, GetTagMetadataAsync reads it
back. 130/130 tests pass.
Harness improvements:
--write-delete-after now runs DelT independently of AddStreamedValue
outcome.
HistorianTagStatusList constructed correctly for DeleteTags reflection
call (previous StringCollection attempt failed with TypeMismatch).
Known DelT gap: SDK's DeleteTagAsync returns true but server-side
cascading deletion does not always complete (the row remains in
Runtime.dbo.Tag). The captured native flow's DelT removes the tag
cleanly (verified via harness --write-delete-after), so something
around the WCF DelT call is missing from our orchestrator. Documented
as known issue with SMC-based cleanup as workaround.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -366,25 +366,34 @@ internal static class Program
|
||||
ErrorDescription = GetPropertyText(addValueError, "ErrorDescription"),
|
||||
});
|
||||
|
||||
// Optionally delete the tag for clean rollback.
|
||||
if (HasFlag(args, "--write-delete-after"))
|
||||
}
|
||||
|
||||
// DeleteTags runs independently of AddStreamedValue success (write-RE
|
||||
// sandbox cleanup); guarded by --write-delete-after to keep the default
|
||||
// run non-destructive.
|
||||
if (HasFlag(args, "--write-delete-after"))
|
||||
{
|
||||
Type tagStatusType = GetType(assembly, "ArchestrA.HistorianTagStatus");
|
||||
Type tagStatusListType = GetType(assembly, "ArchestrA.HistorianTagStatusList");
|
||||
object tagsToDelete = Activator.CreateInstance(tagStatusListType)!;
|
||||
object tagStatus = Activator.CreateInstance(tagStatusType)!;
|
||||
SetProperty(tagStatus, "TagName", sandboxTag);
|
||||
MethodInfo addItem = tagStatusListType.GetMethod("Add", new[] { tagStatusType })
|
||||
?? throw new MissingMethodException("HistorianTagStatusList.Add");
|
||||
addItem.Invoke(tagsToDelete, [tagStatus]);
|
||||
|
||||
object deleteError = Activator.CreateInstance(errorType)!;
|
||||
MethodInfo deleteMethod = accessType.GetMethods().First(m =>
|
||||
m.Name == "DeleteTags" && m.GetParameters().Length == 2);
|
||||
object?[] deleteArgs = [tagsToDelete, deleteError];
|
||||
bool deleteSuccess = (bool)deleteMethod.Invoke(access, deleteArgs)!;
|
||||
deleteError = deleteArgs[1]!;
|
||||
rows.Add(new
|
||||
{
|
||||
object deleteError = Activator.CreateInstance(errorType)!;
|
||||
MethodInfo deleteMethod = accessType.GetMethods().FirstOrDefault(m =>
|
||||
m.Name == "DeleteTags" && m.GetParameters().Length == 2)
|
||||
?? throw new MissingMethodException("HistorianAccess.DeleteTags");
|
||||
StringCollection tagsToDelete = [];
|
||||
tagsToDelete.Add(sandboxTag);
|
||||
object?[] deleteArgs = [tagsToDelete, deleteError];
|
||||
bool deleteSuccess = (bool)deleteMethod.Invoke(access, deleteArgs)!;
|
||||
deleteError = deleteArgs[1]!;
|
||||
rows.Add(new
|
||||
{
|
||||
Kind = "DeleteTags",
|
||||
Success = deleteSuccess,
|
||||
ErrorDescription = GetPropertyText(deleteError, "ErrorDescription"),
|
||||
});
|
||||
}
|
||||
Kind = "DeleteTags",
|
||||
Success = deleteSuccess,
|
||||
ErrorDescription = GetPropertyText(deleteError, "ErrorDescription"),
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (openSuccess && status.ConnectedToServer && IsTagScenario(scenario))
|
||||
|
||||
Reference in New Issue
Block a user