Version: WATS.Client 6.1+
Namespace: Virinco.WATS.Interface
For: .NET Converter Development
📘 Note for AI Agents: This is the user-facing guide. For complete, structured API references optimized for agent consumption, see:
- ../api/UUT_REFERENCE.md - Test reports (UUTReport)
- ../api/UUR_REFERENCE.md - Repair reports (UURReport)
- Getting Started
- Converter Architecture
- Creating UUT Reports
- Building Test Sequences
- Step Types Reference
- Validation & Submission
- Best Practices
- .NET SDK 8.0 or .NET Framework 4.8
- WATS.Client NuGet package (6.1+)
- Visual Studio Code or Visual Studio
IMPORTANT: There are two different NuGet packages depending on your target framework:
- WATS.Client - For .NET Framework 4.x (version 6.1.*)
- Virinco.WATS.ClientAPI - For .NET Core/6/8/10+ (version 7.0.*)
For multi-targeting projects (net8.0 and net48), add to your .csproj:
<ItemGroup Condition="'$(TargetFramework)' == 'net48'">
<PackageReference Include="WATS.Client" Version="6.1.*" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Virinco.WATS.ClientAPI" Version="7.0.*" />
</ItemGroup>Or via command line:
# For .NET Framework 4.8
dotnet add package WATS.Client
# For .NET 8.0+
dotnet add package Virinco.WATS.ClientAPIusing Virinco.WATS.Interface;All converters implement IReportConverter_v2:
using System;
using System.Collections.Generic;
using System.IO;
using Virinco.WATS.Interface;
public class MyConverter : IReportConverter_v2
{
private readonly Dictionary<string, string> _parameters;
// Default constructor
public MyConverter()
{
_parameters = new Dictionary<string, string>
{
{ "operationTypeCode", "30" }, // Default operation type
{ "stationName", Environment.MachineName }
};
}
// Constructor with parameters (called by WATS Client)
public MyConverter(IDictionary<string, string> parameters)
: this()
{
if (parameters != null)
{
foreach (var kvp in parameters)
{
_parameters[kvp.Key] = kvp.Value;
}
}
}
// Required property - exposes parameters to WATS Client
public Dictionary<string, string> ConverterParameters => _parameters;
// Main conversion method
public Report ImportReport(TDM api, Stream file)
{
// ⚠️ CRITICAL: Initialize API first
api.InitializeAPI(true);
// Set validation mode (see Validation & Submission section)
api.ValidationMode = ValidationModeType.AutoTruncate;
// Your conversion logic here
UUTReport uut = CreateUUTFromFile(api, file);
// Submit to WATS
api.Submit(uut);
return null; // Return null for single UUT, or Report for batch
}
// Optional cleanup (called when converter is unloaded)
public void CleanUp()
{
// Release resources if needed
}
private UUTReport CreateUUTFromFile(TDM api, Stream file)
{
// Implementation here
throw new NotImplementedException();
}
}The most important thing to know:
var api = new TDM();
api.InitializeAPI(true); // That's it! No username/password/token needed!Why no authentication?
- Authentication happens via the installed WATS Client
- The converter runs in the WATS Client context
- NEVER try to authenticate with username/password in converter code
public Report ImportReport(TDM api, Stream file)
{
api.InitializeAPI(true);
api.ValidationMode = ValidationModeType.AutoTruncate;
// Parse your file to extract data
string serialNumber = "SN12345";
string partNumber = "PN-001";
DateTime testTime = DateTime.Now;
// Get operation type from YOUR server
OperationType opType = api.GetOperationTypes()
.Where(o => o.Name.Equals("In-Circuit Test", StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
if (opType == null)
{
// Fallback to code from parameters
string opCode = _parameters.GetValueOrDefault("operationTypeCode", "30");
opType = api.GetOperationType(opCode);
}
// Create UUT report
UUTReport uut = api.CreateUUTReport(
operatorName: "Auto", // Test operator name ("Auto" for automated, or operator ID)
partNumber: partNumber, // Product/part identifier (required)
partRevisionNumber: "A", // Part revision ("A", "Rev 1", etc. - use "A" if unknown)
serialNumber: serialNumber, // Unique unit serial number (required)
operationType: opType, // Operation type from server (ICT, FCT, etc.)
sequenceFileName: "TestProgram.seq", // Test sequence/program filename (helps track test software changes)
sequenceFileVersion: "2.4.1" // Test sequence version (important for correlating issues to test changes)
);
// Set timing
uut.StartDateTime = testTime;
uut.ExecutionTime = 45.5; // Seconds
// Set station info
uut.StationName = _parameters.GetValueOrDefault("stationName", Environment.MachineName);
// Build test sequence (see next section)
BuildTestSequence(uut, file);
// Submit
api.Submit(uut);
return null;
}| Property | Type | Description | Example |
|---|---|---|---|
SerialNumber |
string | Unique unit identifier | "SN12345" |
PartNumber |
string | Product identifier | "PN-001" |
PartRevisionNumber |
string | Product revision | "A", "Rev 2" |
OperatorName |
string | Test operator | "John Doe", "Auto" |
OperationType |
OperationType | Test operation | ICT, FCT, etc. |
// ✅ Preferred: Use UTC time
uut.StartDateTimeUTC = DateTime.UtcNow;
// Alternative: Local time (will be converted)
uut.StartDateTime = DateTime.Now;
// Alternative: With timezone offset
uut.StartDateTimeOffset = DateTimeOffset.Now;// Duration in SECONDS (double)
uut.ExecutionTime = 45.5; // 45.5 seconds// Overall UUT status
uut.Status = UUTStatusType.Passed;
// Available values:
// - UUTStatusType.Passed
// - UUTStatusType.Failed
// - UUTStatusType.Error
// - UUTStatusType.Terminated
// - UUTStatusType.Running// Station information
uut.StationName = "Line1-Station3";
uut.Location = "Factory A";
uut.Purpose = "Final Test";
uut.FixtureId = "Fixture_01";
uut.TestSocketIndex = 0; // short
// Batch information
uut.BatchSerialNumber = "BATCH-20260206-001";// Free-form comment
uut.Comment = "First article inspection completed";
// Error information (if failed)
uut.ErrorCode = 0;
uut.ErrorMessage = "No errors";AddMiscUUTInfo() for key-value pairs:
// ✅ CORRECT
uut.AddMiscUUTInfo("PCB Serial Number", "PCB12345");
uut.AddMiscUUTInfo("Temperature", "25");
uut.AddMiscUUTInfo("Firmware Version", "1.2.3");
// ❌ WRONG - Misc property is read-only
// uut.Misc["Key"] = "Value"; // This will not work!Best Practice: Also add as test steps for visibility:
// Add to header
uut.AddMiscUUTInfo("PCB Serial Number", pcbSerial);
// Also add to sequence for visibility in charts/reports
var root = uut.GetRootSequenceCall();
root.AddStringValueStep("PCB Serial Number").AddTest(pcbSerial);Operation types are retrieved from YOUR WATS server:
// Option 1: Get by name from file content
OperationType opType = api.GetOperationTypes()
.Where(o => o.Name.Equals("ICT Test", StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
// Option 2: Get by code from parameters
OperationType opType = api.GetOperationType("30");
// Option 3: Get by integer code
OperationType opType = api.GetOperationType(30);
// Option 4: List all available operation types
var operationTypes = api.GetOperationTypes();
foreach (var op in operationTypes)
{
Console.WriteLine($"Code: {op.Code}, Name: {op.Name}");
}api.GetOperationType() or api.GetOperationTypes() - do not pass operation type as a string directly to CreateUUTReport.
Using PowerShell utility (recommended for quick reference):
# List all operation types from your WATS server
.\Tools\GetOperationTypes.ps1
# Filter by name or code
.\Tools\GetOperationTypes.ps1 -Filter "ICT"
# Export to CSV for creating mapping tables
.\Tools\GetOperationTypes.ps1 -ExportPath "operations.csv"Using C# helper class (in your converter code):
// List all available operation types
OperationTypeHelper.ListAvailableOperationTypes(api);
// Validate operation code exists
if (!OperationTypeHelper.OperationCodeExists(api, "30"))
{
throw new Exception("Operation code not found!");
}
// Get with detailed error message
var opType = OperationTypeHelper.GetOperationTypeOrThrow(api, "30");When your test files contain operation identifiers, create a mapping function:
// Map source file values to WATS operation codes
private string MapToOperationCode(string sourceValue)
{
return sourceValue switch
{
"ICT" => "30", // In-Circuit Test
"FUNCTIONAL" => "40", // Functional Test
"BURN-IN" => "50", // Burn-In
"FINAL" => "60", // Final Test
_ => "30" // Default
};
}
// Use in your converter
string opCode = MapToOperationCode(testType);
var opType = api.GetOperationType(opCode);See OperationTypeHelper.cs in ExampleConverters for more mapping examples.
To see YOUR server's operation types:
- Open WATS Web Application
- Navigate to: Control Panel → Process & Production → Processes
- Note the Process Name and Process Code for each
Example operation types (your server configuration will vary):
- In-Circuit Test - Code: 30
- Programming - Code: 10
- Functional Test - Code: 40
- Final Inspection - Code: 50
- Burn-In - Code: 60
SequenceCall rootSequence = uut.GetRootSequenceCall();
// Set sequence metadata (important for tracking test software changes)
rootSequence.SequenceFileName = "ICT_TestProgram.seq";
rootSequence.SequenceFileVersion = "2.4.1";Best Practice: Always set SequenceFileName and SequenceFileVersion when available from your source file. This helps correlate test failures to specific test software versions.
// TestModeType.Import - Trust source file pass/fail status (default for converters)
api.TestMode = TestModeType.Import;
// TestModeType.Active - WATS evaluates limits and determines pass/fail
api.TestMode = TestModeType.Active;
// TestModeType.TestStand - For native TestStand XML
api.TestMode = TestModeType.TestStand;When to use:
- Import - Trust the pass/fail status from source file (most converters). WATS does NOT recalculate status.
- Active - WATS calculates step status from limits and propagates status up the sequence hierarchy. Use when source file has measurements but unreliable pass/fail.
- TestStand - Processing NI TestStand XML files (special handling)
For measurements with numeric values and optional limits.
var step = rootSequence.AddNumericLimitStep("Voltage Test");
step.AddTest(
numericValue: 5.0,
units: "V"
);var step = rootSequence.AddNumericLimitStep("Voltage Test");
step.AddTest(
numericValue: 5.0,
compOperator: CompOperatorType.GELE, // Greater-or-Equal AND Less-or-Equal
lowLimit: 4.9,
highLimit: 5.1,
units: "V",
status: StepStatusType.Passed
);// Less Than (use lowLimit parameter for single-sided limits)
var step = rootSequence.AddNumericLimitStep("Temperature");
step.AddTest(
numericValue: 23.5,
compOperator: CompOperatorType.LT,
lowLimit: 25.0, // Note: use lowLimit parameter even for upper limit comparisons
units: "°C",
status: StepStatusType.Passed
);
// Greater Than or Equal
var step = rootSequence.AddNumericLimitStep("Voltage");
step.AddTest(
numericValue: 5.1,
compOperator: CompOperatorType.GE,
lowLimit: 5.0,
units: "V",
status: StepStatusType.Passed
);| Operator | Description | Usage |
|---|---|---|
EQ |
Equal | Exact match |
NE |
Not Equal | Value must differ |
LT |
Less Than | value < limit |
LE |
Less or Equal | value ≤ limit |
GT |
Greater Than | value > limit |
GE |
Greater or Equal | value ≥ limit |
GELE |
Greater-or-Equal AND Less-or-Equal | lowLimit ≤ value ≤ highLimit |
GTLT |
Greater-Than AND Less-Than | lowLimit < value < highLimit |
LOG |
Log value only | No pass/fail comparison - just record the value |
StepStatusType.Passed // Test passed
StepStatusType.Failed // Test failed
StepStatusType.Error // Test error
StepStatusType.Terminated // Test terminated
StepStatusType.Skipped // Test skipped
StepStatusType.Running // Test running (rare)
StepStatusType.Done // Test done (status unknown)For tests with only PASS/FAIL result (no measurement).
var step = rootSequence.AddPassFailStep("Continuity Test");
// ✅ CORRECT: Must call AddTest with boolean value
step.AddTest(
measurementValue: true, // true = passed, false = failed
status: StepStatusType.Passed
);
// Alternative: Just set status (simpler for Import mode)
step.Status = StepStatusType.Passed;AddTest() or set Status - never leave empty!
For steps with text/string measurements.
var step = rootSequence.AddStringValueStep("Barcode Scan");
// ✅ CORRECT: Add the string value
step.AddTest(
stringValue: "BC123456789",
status: StepStatusType.Passed
);
// With string comparison
step.AddTest(
stringValue: "BC123456789",
compOperator: CompOperatorType.EQ,
limit: "BC123456789",
status: StepStatusType.Passed
);Common Uses:
- Barcode/serial number verification
- Version strings
- Configuration values
- Error messages
For grouping steps into hierarchies.
// Create parent sequence
var subAssembly = rootSequence.AddSequenceCall("Power Supply Tests");
// Add steps to the sub-sequence
var voltageStep = subAssembly.AddNumericLimitStep("5V Rail");
voltageStep.AddTest(5.02, CompOperatorType.GELE, 4.9, 5.1, "V", StepStatusType.Passed);
var currentStep = subAssembly.AddNumericLimitStep("5V Current");
currentStep.AddTest(0.5, CompOperatorType.LE, 1.0, "A", StepStatusType.Passed);
// Nested sub-sequences
var diagnostics = subAssembly.AddSequenceCall("Diagnostics");
diagnostics.AddPassFailStep("Self-Test").Status = StepStatusType.Passed;Best Practice: Use nested sequences to match the structure of your source file or test program.
For steps that measure multiple related values simultaneously.
// Use NumericLimitStep and AddMultipleTest() for multiple measurements
var step = rootSequence.AddNumericLimitStep("Multi-Channel Voltage");
// First call to AddMultipleTest() locks the step as MULTIPLE
step.AddMultipleTest(5.01, CompOperatorType.GELE, 4.9, 5.1, "V", "Channel_0");
step.AddMultipleTest(3.32, CompOperatorType.GELE, 3.2, 3.4, "V", "Channel_1");
step.AddMultipleTest(12.05, CompOperatorType.GELE, 11.8, 12.2, "V", "Channel_2");When to use:
- Multi-channel measurements
- Array/vector measurements
- Related measurements that logically belong together
Important: Once you call AddMultipleTest(), you cannot call AddTest() on the same step (and vice versa)
For repeated test steps (e.g., stress tests, cycles).
var step = rootSequence.AddNumericLimitStep("Cycle Test");
// Set loop properties
step.StepGroup = "Stress Test";
step.LoopIndex = 1; // Current iteration
step.LoopCount = 100; // Total iterations
// Add measurement for this iteration
step.AddTest(5.01, "V", StepStatusType.Passed);// AutoTruncate - Automatically truncate strings that exceed field limits (recommended)
api.ValidationMode = ValidationModeType.AutoTruncate;
// Strict - Throw exceptions if data exceeds limits (use for debugging)
api.ValidationMode = ValidationModeType.Strict;
// Import - No validation, accept data as-is (not recommended)
api.ValidationMode = ValidationModeType.Import;When to use:
- AutoTruncate (recommended) - Truncates long string values to fit database field limits. Best for production converters.
- Strict - Throws exceptions if data violates limits. Use during development to catch data issues.
- Import - No validation. Use only if you're certain your data is clean.
// Submit single UUT
api.Submit(uut);
// Or return null from ImportReport (same result)
return null;
// For batch processing (multiple UUTs in one file)
Report report = new Report();
report.TotalUUTsReported = 3;
// Add UUTs to report...
return report;// ✅ Initialize API first thing
api.InitializeAPI(true);
// ✅ Use auto-truncation
api.ValidationMode = ValidationModeType.AutoTruncate;
// ✅ Get operation types from server
var opType = api.GetOperationTypes()
.FirstOrDefault(o => o.Name.Equals("In-Circuit Test", StringComparison.OrdinalIgnoreCase));
// ✅ Always set status or call AddTest
step.AddTest(5.0, "V", StepStatusType.Passed);
// ✅ Use UTC time
uut.StartDateTimeUTC = DateTime.UtcNow;
// ✅ Add misc info with AddMiscUUTInfo
uut.AddMiscUUTInfo("PCB Serial", pcbSerial);
// ✅ Handle missing optional fields gracefully
string operator = ExtractOperator(file) ?? "Unknown";// ❌ Don't try to authenticate (it's automatic!)
// api.Authenticate(username, password); // WRONG!
// api.SetupAPI(url, token); // WRONG!
// ❌ Don't use properties that don't exist
// step.SetNumericValue(5.0, "V"); // Method doesn't exist!
// step.SetLimits(4.9, 5.1); // Method doesn't exist!
// ❌ Don't access Misc property directly
// uut.Misc["Key"] = "Value"; // Misc is read-only!
// ❌ Don't create steps without measurements
var step = root.AddPassFailStep("Test");
// step.Status = ... // WRONG - must call AddTest()
// ❌ Don't pass operation type as string to CreateUUTReport
// Use api.GetOperationType() or api.GetOperationTypes() insteadusing System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Virinco.WATS.Interface;
public class ExampleConverter : IReportConverter_v2
{
private readonly Dictionary<string, string> _parameters;
public ExampleConverter()
{
_parameters = new Dictionary<string, string>
{
{ "operationTypeCode", "30" }
};
}
public ExampleConverter(IDictionary<string, string> parameters) : this()
{
if (parameters != null)
{
foreach (var kvp in parameters)
_parameters[kvp.Key] = kvp.Value;
}
}
public Dictionary<string, string> ConverterParameters => _parameters;
public Report ImportReport(TDM api, Stream file)
{
// Initialize API
api.InitializeAPI(true);
api.ValidationMode = ValidationModeType.AutoTruncate;
api.TestMode = TestModeType.Import;
// Parse file (simplified)
using (var reader = new StreamReader(file))
{
var lines = reader.ReadToEnd().Split('\n');
string serialNumber = ExtractValue(lines, "Serial:");
string partNumber = ExtractValue(lines, "Part:");
DateTime testTime = DateTime.Parse(ExtractValue(lines, "Date:"));
// Get operation type
var opType = api.GetOperationType(_parameters["operationTypeCode"]);
// Create UUT
UUTReport uut = api.CreateUUTReport(
operatorName: "Auto",
partNumber: partNumber,
partRevisionNumber: "A",
serialNumber: serialNumber,
operationType: opType,
sequenceFileName: "TestProgram.seq",
sequenceFileVersion: "2.4.1"
);
uut.StartDateTime = testTime;
uut.StationName = Environment.MachineName;
// Build sequence
var root = uut.GetRootSequenceCall();
var voltageStep = root.AddNumericLimitStep("Voltage Test");
voltageStep.AddTest(5.02, CompOperatorType.GELE, 4.9, 5.1, "V", StepStatusType.Passed);
var continuityStep = root.AddPassFailStep("Continuity");
continuityStep.AddTest(true, StepStatusType.Passed);
// Set overall status
uut.Status = UUTStatusType.Passed;
// Submit
api.Submit(uut);
}
return null;
}
public void CleanUp() { }
private string ExtractValue(string[] lines, string key)
{
var line = lines.FirstOrDefault(l => l.StartsWith(key));
return line?.Substring(key.Length).Trim();
}
}"OperationType not found"
- Check that operation type exists on YOUR server
- Use
api.GetOperationTypes()to see available types - Verify code/name matches exactly
"Step has no measurement"
- Always call
AddTest()for all step types - Or set
Statusproperty directly for PassFailStep
"Misc property is read-only"
- Use
uut.AddMiscUUTInfo(key, value)instead - Don't try to set
uut.Misc[key] = value
"Invalid DateTime"
- Use DateTime.TryParse with multiple formats
- Provide fallback to DateTime.Now
For more examples, see:
- DevKit/Converters/ExampleConverters/
- Repository API_KNOWLEDGE/DotNet/UUTReport_API_Quick_Reference.md