Using Measurement Data Services
The NI Measurement Data Services provide a comprehensive solution for storing, organizing, and querying test data and metadata. This document explains the typical workflow for using these services, from initial setup through data analysis.
Services Overview:
MetadataStoreService - Manages test context metadata (who, what, where, how)
DataStoreService - Stores and retrieves measurement data and test execution results
Complete Workflow Overview
The typical measurement data workflow follows this sequence:
**Setup Phase** - Register metadata entities and schemas
**Test Execution Phase** - Create test sessions and publish data
**Analysis Phase** - Query and analyze results
Required Imports
Before using the services, import the necessary classes and types:
from datetime import datetime
from ni.datastore.data import DataStoreClient, TestResult, Step, Outcome
from ni.datastore.metadata import MetadataStoreClient
from ni.datastore.metadata import (
Operator, TestStation, HardwareItem, SoftwareItem,
Uut, UutInstance, TestDescription, Test, TestAdapter
)
from nitypes.scalar import Scalar
from nitypes.vector import Vector
from nitypes.waveform import AnalogWaveform
Setup Phase
Before running tests, establish the metadata foundation that describes your test environment and processes.
1. Create Core Metadata Entities
Start by creating the fundamental metadata entities that will be referenced throughout your testing:
Operators
Register the people who will be running tests:
# Create operators
sarah = Operator(
name="Sarah Johnson",
role="Test Engineer",
schema_id=schema_id,
extension={
"department": "Quality Assurance",
"certification": "Level 2 Test Technician"
}
)
sarah_id = metadata_store_client.create_operator(sarah)
mike_id = metadata_store_client.create_operator(Operator(
name="Mike Chen",
role="Senior Technician",
schema_id=schema_id,
extension={
"department": "Manufacturing",
"specialization": "RF Testing"
}
))
Test Stations
Define the physical locations where testing occurs:
# Create test stations
station_a1_id = metadata_store_client.create_test_station(TestStation(
name="Station_A1",
asset_identifier="STA-001",
schema_id=schema_id,
extension={
"location": "Building A, Floor 1",
"station_type": "Production Line"
}
))
rf_lab_id = metadata_store_client.create_test_station(TestStation(
name="RF_Lab_Bench_1",
asset_identifier="RFL-001",
schema_id=schema_id,
extension={
"location": "R&D Lab, Building B",
"station_type": "Development"
}
))
Hardware Items
Register test equipment and instruments:
# Create hardware items (test equipment)
dmm = HardwareItem(
manufacturer="NI",
model="PXIe-4081",
serial_number="DMM-001",
part_number="781061-01",
calibration_due_date="2025-06-15",
schema_id=schema_id,
extension={
"accuracy": "7.5 digits",
"asset_tag": "NI-DMM-001"
}
)
dmm_id = metadata_store_client.create_hardware_item(dmm)
scope_id = metadata_store_client.create_hardware_item(HardwareItem(
manufacturer="NI",
model="PXIe-5171",
serial_number="SCOPE-001",
part_number="783513-01",
calibration_due_date="2025-08-20",
schema_id=schema_id,
extension={
"bandwidth": "1 GHz",
"sample_rate": "1.25 GS/s"
}
))
Software Items
Document the software environment:
# Create software items
python_id = metadata_store_client.create_software_item(SoftwareItem(
product="Python",
version="3.11.5"
))
nidaqmx_id = metadata_store_client.create_software_item(SoftwareItem(
product="NI-DAQmx",
version="23.3.0"
))
custom_app_id = metadata_store_client.create_software_item(SoftwareItem(
product="PowerSupply Test Suite",
version="v2.1.4",
schema_id=schema_id,
extension={
"build_date": "2024-09-15",
"git_commit": "a1b2c3d4"
}
))
2. Define Products Under Test
Create UUT definitions and instances:
UUT (Product Definitions)
# Define the product being tested
power_supply_uut = Uut(
model_name="PowerSupply v2.1",
family="Power",
manufacturers=["ACME Corp"],
part_number="PS-v2.1-001",
schema_id=schema_id,
extension={
"max_output": "24V, 10A",
"efficiency": ">90%"
}
)
power_supply_uut_id = metadata_store_client.create_uut(power_supply_uut)
UUT Instances (Physical Devices)
# Create specific device instances
uut_instance_id = metadata_store_client.create_uut_instance(UutInstance(
uut_id=power_supply_uut_id,
serial_number="PS-2024-001456",
manufacture_date="2024-10-01",
schema_id=schema_id,
extension={
"lot_number": "L2024-Q4-001",
"assembly_line": "Line 3"
}
))
3. Define Test Procedures
Create test specifications and procedures:
Test Descriptions
# Create comprehensive test suites
power_test_desc_id = metadata_store_client.create_test_description(TestDescription(
uut_id=power_supply_uut_id,
name="Power Supply Validation Suite",
schema_id=schema_id,
extension={
"version": "v2.1",
"compliance": "IEC 62368-1"
}
))
Individual Tests
# Create specific test procedures
voltage_test_id = metadata_store_client.create_test(Test(
name="DC Voltage Accuracy Test",
description="Measures DC voltage accuracy across 5V, 12V, and 24V outputs",
schema_id=schema_id,
extension={
"test_limits": "±0.1% of reading",
"test_duration": "~5 minutes"
}
))
load_test_id = metadata_store_client.create_test(Test(
name="Load Regulation Test",
description="Tests voltage stability under varying load conditions",
schema_id=schema_id,
extension={
"load_range": "0% to 100% rated current",
"regulation_limit": "±0.5%"
}
))
4. Register Extension Schemas (Optional)
Define validation schemas for custom extension fields:
# Register a schema for power supply testing
power_supply_schema = """
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"hardware_item": {
"type": "object",
"properties": {
"asset_tag": {"type": "string"},
"calibration_cert": {"type": "string"},
"bandwidth": {"type": "string"}
},
"required": ["asset_tag"]
}
}
}
"""
schema_id = metadata_store_client.register_schema(power_supply_schema)
5. Create Aliases (Optional)
Set up human-readable names for frequently used entities:
# Create aliases for easy reference
metadata_store_client.create_alias("Primary_DMM", dmm)
metadata_store_client.create_alias("Lead_Test_Engineer", sarah)
metadata_store_client.create_alias("Current_PowerSupply_Design", power_supply_uut)
Test Execution Phase
With metadata established, execute tests and publish measurement data.
1. Create Test Result Session
Start each test session by creating a TestResult:
# Create a test result for this test session
test_result_id = data_store_client.create_test_result(TestResult(
name="PowerSupply PS-2024-001456 Validation",
uut_instance_id=uut_instance_id,
operator_id=sarah_id, # or use alias: "Lead_Test_Engineer"
test_station_id=station_a1_id,
test_description_id=power_test_desc_id,
software_item_ids=[python_id, nidaqmx_id, custom_app_id],
hardware_item_ids=[dmm_id, scope_id], # or use aliases
schema_id=schema_id,
extension={
"test_operator_notes": "First production unit validation",
"ambient_temperature": "23°C"
}
))
2. Create Test Steps
Organize measurements into logical Steps:
# Create test steps within the test result
voltage_step_id = data_store_client.create_step(Step(
name="DC Voltage Accuracy Check",
test_result_id=test_result_id,
test_id=voltage_test_id,
type="Measurement",
notes="Testing 5V, 12V, and 24V outputs under no load"
))
load_step_id = data_store_client.create_step(Step(
name="Load Regulation Test",
test_result_id=test_result_id,
test_id=load_test_id,
type="Measurement",
notes="Variable load from 0% to 100% rated current"
))
3. Publish Test Conditions
Record environmental and setup conditions during testing:
# Publish conditions (environmental/setup parameters)
voltage = Scalar(value=120.0, units="V")
data_store_client.publish_condition(
condition_name="Supply Voltage",
type="Input Parameter",
value=voltage,
step_id=voltage_step_id
)
temperature = Scalar(value=23.5, units="DegC")
data_store_client.publish_condition(
condition_name="Temperature",
type="Environment",
value=temperature,
step_id=voltage_step_id
)
humidity = Scalar(value=45.2, units="%RH")
data_store_client.publish_condition(
condition_name="Humidity",
type="Environment",
value=humidity,
step_id=voltage_step_id
)
4. Publish Measurements
Capture actual measurement data:
Single Measurements
# Publish individual measurements
published_measurement = data_store_client.publish_measurement(
name="5V Output Voltage",
value=5.023, # Measured 5.023V
timestamp=datetime.now(),
outcome=Outcome.PASSED,
step_id=voltage_step_id,
hardware_item_ids=[dmm_id],
notes="DMM reading at no load"
)
Waveform Measurements
# Publish complex data (waveforms, spectra, etc.)
waveform_data = AnalogWaveform(
samples=[1.0, 2.0, 3.0, 2.0, 1.0, 0.0, -1.0],
sample_interval=1e-6, # 1 µs per sample
start_time=0.0
)
data_store_client.publish_measurement(
name="Output Ripple Waveform",
value=waveform_data,
timestamp=datetime.now(),
outcome=Outcome.PASSED,
step_id=voltage_step_id,
hardware_item_ids=[scope_id],
notes="Ripple measurement at full load"
)
Batch Measurements
# Publish multiple related measurements efficiently
load_currents = [0.0, 2.5, 5.0, 7.5, 10.0] # Load current sweep
output_voltages = [5.025, 5.023, 5.021, 5.019, 5.018] # Corresponding voltages
data_store_client.publish_measurement_batch(
name="Load Regulation Sweep",
values=output_voltages,
timestamps=[datetime.now()] * len(output_voltages),
outcomes=[Outcome.PASSED] * len(output_voltages),
step_id=load_step_id,
hardware_item_ids=[dmm_id]
)
# Publish corresponding load conditions
data_store_client.publish_condition_batch(
name="Load Current",
condition_type="Test Parameter",
values=load_currents,
step_id=load_step_id
)
Analysis Phase
Query and analyze the stored measurement data and metadata.
1. Query Measurements
Use OData queries to find and filter measurement data:
Basic Queries
# Find all measurements from a specific test result
measurements = data_store_client.query_measurements(
odata_query=f"$filter=TestResultId eq {test_result_id}"
)
# Find failed measurements
failed_measurements = data_store_client.query_measurements(
odata_query="$filter=Outcome eq 'Failed'"
)
# Find measurements by name
voltage_measurements = data_store_client.query_measurements(
odata_query="$filter=contains(name, 'Voltage')"
)
Complex Queries
# Find measurements from specific equipment that failed
equipment_failures = data_store_client.query_measurements(
odata_query=f"$filter=outcome eq 'Failed' and contains(HardwareItemIds, {dmm_id})"
)
# Find recent measurements
recent_measurements = data_store_client.query_measurements(
odata_query="$filter=StartTime gt 2024-10-01T00:00:00Z&$orderby=StartTime"
)
# Find measurements from specific operator
operator_measurements = data_store_client.query_measurements(
odata_query=f"$filter=TestResultId eq {test_result_id} and OperatorId eq {sarah_id}"
)
2. Query Test Context
Analyze test metadata to understand patterns:
Test Results Analysis
# Find all steps for a particular test results
test_steps = data_store_client.query_steps(
odata_query=f"$filter=TestResultId eq {test_result_id}"
)
# Find operator named 'Sarah Johnson'
sarah_tests = metadata_store_client.query_operators(
odata_query="$filter=Name eq 'Sarah Johnson'"
)
Equipment Usage Tracking
# Find measurements using specific equipment
equipment_usage = data_store_client.query_measurements(
odata_query=f"$filter=HardwareItems/any(h: h/Id eq {scope_id})"
)
3. Retrieve Measurement Data
Access the actual measured values:
# Get measurement data
for measurement in measurements:
if measurement.data_type == "type.googleapis.com/ni.protobuf.types.Vector":
value = data_store_client.read_data(measurement, expected_type=Vector)
print(f"{measurement.name}: {value}")
elif measurement.data_type == "type.googleapis.com/ni.protobuf.types.DoubleAnalogWaveform":
waveform = data_store_client.read_data(measurement, expected_type=AnalogWaveform)
print(f"{measurement.name}: {len(waveform.raw_data)} samples")
4. Cross-Reference with Metadata
Combine measurement data with metadata for comprehensive analysis:
# Analyze test results with full context
for measurement in measurements:
# Get associated metadata
test_result = data_store_client.get_test_result(measurement.test_result_id)
step = data_store_client.get_step(measurement.step_id)
# Get UUT instance and model info
uut_instance = metadata_store_client.get_uut_instance(test_result.uut_instance_id)
uut = metadata_store_client.get_uut(uut_instance.uut_id)
# Get operator info
operator = metadata_store_client.get_operator(test_result.operator_id)
# Get equipment info
hardware_items = [
metadata_store_client.get_hardware_item(hw_id)
for hw_id in measurement.hardware_item_ids
]
print(f"Measurement: {measurement.name}")
print(f" UUT: {uut.model_name} S/N: {uut_instance.serial_number}")
print(f" Operator: {operator.name} ({operator.role})")
print(f" Equipment: {[hw.model for hw in hardware_items]}")
print(f" Outcome: {measurement.outcome}")
5. Advanced Analysis Examples
Trend Analysis
# Track performance over time for a UUT model
uut_instances = metadata_store_client.query_uut_instances(
odata_query=f"$filter=UutId eq '{power_supply_uut_id}'"
)
for instance in uut_instances:
# Get test results for this UUT instance
test_results = data_store_client.query_test_results(
odata_query=f"$filter=UutInstanceId eq '{instance.id}'"
)
for test_result in test_results:
measurements = data_store_client.query_measurements(
odata_query=f"$filter=TestResultId eq '{test_result.id}' and Name eq '5V Output Voltage'"
)
# Analyze voltage accuracy trends...
Equipment Performance Analysis
# Analyze calibration impact on measurements
pre_cal_measurements = data_store_client.query_measurements(
odata_query=f"$filter=contains(HardwareItemIds, '{dmm_id}') and start_date_time lt '2024-06-15'"
)
post_cal_measurements = data_store_client.query_measurements(
odata_query=f"$filter=contains(HardwareItemIds, '{dmm_id}') and start_date_time gt '2024-06-15'"
)
# Compare measurement accuracy before/after calibration...
Operator Performance Comparison
# Compare test results between operators
for operator_id in [sarah_id, mike_id]:
operator = metadata_store_client.get_operator(operator_id)
measurements = data_store_client.query_measurements(
odata_query=f"$filter=OperatorId eq '{operator_id}' and outcome eq 'Failed'"
)
failure_rate = len(measurements) / total_measurements_by_operator[operator_id] * 100
print(f"{operator.name}: {failure_rate:.1f}% failure rate")
Best Practices
Metadata Management
Create aliases for frequently referenced entities
Use extension schemas to enforce data consistency
Register metadata entities once and reuse across multiple tests
Keep calibration dates current for traceability
Test Execution
Always create a TestResult before publishing measurements
Group related measurements into logical Steps
Include environmental conditions that might affect results
Use batch operations for parametric sweeps to improve performance
Data Organization
Use consistent naming conventions for measurements and steps
Include meaningful notes and descriptions
Associate measurements with relevant hardware/software for traceability
Set appropriate outcomes (PASSED/FAILED/INDETERMINATE) for analysis
Querying and Analysis
Use specific OData filters to reduce query response size
Combine metadata and measurement queries for comprehensive analysis
Cache frequently accessed metadata entities
Use measurement names and types to group related data
Integration Patterns
Automated Test Systems
class TestAutomation:
def __init__(self):
self.metadata_client = MetadataStoreClient()
self.data_client = DataStoreClient()
def run_automated_test(self, uut_serial: str):
# 1. Look up UUT instance by serial number
instances = self.metadata_client.query_uut_instances(
odata_query=f"$filter=serial_number eq '{uut_serial}'"
)
# 2. Create test result
test_result_id = self.data_client.create_test_result(...)
# 3. Execute test steps
for test_step in self.test_sequence:
step_id = self.data_client.create_step(...)
self.execute_step(step_id, test_step)
# 4. Determine overall result
self.finalize_test_result(test_result_id)
Batch Processing
def process_test_batch(uut_serials: List[str]):
"""Process multiple UUTs efficiently."""
# Create all test results first
test_result_ids = []
for serial in uut_serials:
test_result_id = data_store_client.create_test_result(...)
test_result_ids.append(test_result_id)
# Execute tests in parallel/batch
for test_result_id in test_result_ids:
execute_test_sequence(test_result_id)
# Analyze results
analyze_batch_results(test_result_ids)
This comprehensive workflow ensures full traceability, enables powerful analysis capabilities, and maintains data integrity across your entire test ecosystem.