Units
Track serial numbers, part numbers, and revision history for every unit under test. View full test history and traceability per unit in TofuPilot.

Overview
A Unit is a physical object being tested. Each Unit has a unique serial number and is defined by part number and revision for complete traceability.
In TofuPilot, we use "Unit" as the standard term. However, you may also see "Unit under test" (UUT) or "Device under test" (DUT) in OpenHTF and other testing frameworks. All refer to the same concept.
Create Units
You can create units automatically when creating a Run, or directly with the API client.
Parameters
- OpenHTF: Define parameters in the
Testconstructor (part_number,revision,batch_number) and pass the serial number totest.execute(). - Python: Use
client.units.create()to defineserial_number,part_number,revision_number, andbatch_number.
| Prop | Type | Default |
|---|---|---|
unit_under_test? | dict | – |
serial_number? | str | – |
part_number? | str | – |
revision? | str (optional) | A |
batch_number? | str (optional) | – |
from openhtf import Test
from tofupilot.openhtf import TofuPilot
def main():
test = Test(
procedure_id="FVT1",
part_number="PCB01", # required
revision="A", # optional
batch_number="12-24", # optional
)
with TofuPilot(test):
test.execute(lambda: "SN-0001") # Unit serial number (required)
if __name__ == "__main__":
main()from tofupilot.v2 import TofuPilot
client = TofuPilot()
# Create a unit directly
unit = client.units.create(
serial_number="SN-0001",
part_number="PCB01",
revision_number="A",
)
print(f"Unit created: {unit.serial_number}")using TofuPilot;
using TofuPilot.Models.Requests;
var client = new TofuPilot();
var unit = await client.Units.CreateAsync(new UnitCreateRequest
{
SerialNumber = "SN-0001",
PartNumber = "PCB01",
RevisionNumber = "A",
});
Console.WriteLine($"Unit created: {unit.SerialNumber}");#include <tofupilot/tofupilot.hpp>
auto client = tofupilot::TofuPilot("your-api-key");
auto unit = client.units().create()
.serial_number("SN-0001")
.part_number("PCB01")
.revision_number("A")
.send();sdk = tofupilot.TofuPilot('your-api-key');
unit = sdk.Units.create(struct( ...
'serial_number', 'SN-0001', ...
'part_number', 'PCB01', ...
'revision_number', 'A'));With OpenHTF, you can decide whether to assign the serial number at the beginning of the test or later during execution. For more details, see the Operator UI section in the OpenHTF documentation.
Batch Number
Include the batch_number field in your script. TofuPilot will display it with the run and let you filter analytics by batch.

Revision
Specify the revision field. If omitted, revision A is assumed. Revisions are shown in the Inventory and Unit pages, and can be used to segment your analytics.

Sub-units
Sub-units are smaller Units that get assembled into a larger Unit. Each sub-unit has its own serial number and test history.
- OpenHTF: Define
sub_unitsparameter in theTestconstructor as a list of dictionaries withserial_numberof each sub-unit. - Python: Use
client.units.add_child()to attach previously tested sub-units byserial_number.
| Prop | Type | Default |
|---|---|---|
sub_units? | array (optional) | – |
serial_number? | str (optional) | – |
from openhtf import PhaseResult, Test
from tofupilot.openhtf import TofuPilot
# Please ensure both units PCB1A001 and LEN1A001 exist before running this script
def main():
test = Test(
procedure_id="FVT2", # Create the procedure first in the Dashboard
part_number="CAM1",
sub_units=[{"serial_number": "PCB1A001"},
{"serial_number": "LEN1A001"}],
)
with TofuPilot(test):
test.execute(lambda: "CAM1A001")
if __name__ == "__main__":
main()from tofupilot.v2 import TofuPilot
client = TofuPilot()
# Add sub-units to a parent unit
client.units.add_child(serial_number="CAM1A001", child_serial_number="PCB1A001")
client.units.add_child(serial_number="CAM1A001", child_serial_number="LEN1A001")
# Remove a sub-unit
client.units.remove_child(serial_number="CAM1A001", child_serial_number="LEN1A001")using TofuPilot;
using TofuPilot.Models.Requests;
var client = new TofuPilot();
// Add sub-units to a parent unit
await client.Units.AddChildAsync("CAM1A001", new UnitAddChildRequestBody { ChildSerialNumber = "PCB1A001" });
await client.Units.AddChildAsync("CAM1A001", new UnitAddChildRequestBody { ChildSerialNumber = "LEN1A001" });
// Remove a sub-unit
await client.Units.RemoveChildAsync("CAM1A001", "LEN1A001");#include <tofupilot/tofupilot.hpp>
auto client = tofupilot::TofuPilot("your-api-key");
// Add sub-units
client.units().add_child()
.serial_number("CAM1A001")
.child_serial_number("PCB1A001")
.send();
// Remove a sub-unit
client.units().remove_child()
.serial_number("CAM1A001")
.child_serial_number("LEN1A001")
.send();sdk = tofupilot.TofuPilot('your-api-key');
% Add sub-units
sdk.Units.addChild('CAM1A001', struct('child_serial_number', 'PCB1A001'));
sdk.Units.addChild('CAM1A001', struct('child_serial_number', 'LEN1A001'));
% Remove a sub-unit
sdk.Units.removeChild('CAM1A001', 'childSerialNumber', 'LEN1A001');The sub-units must already exist as Units in TofuPilot when creating the Run. Ensure each sub-unit has been tested and registered before referencing it in the assembly.
Attachments
You can attach files like photos, calibration data, or diagnostic logs directly to a unit.
from tofupilot.v2 import TofuPilot
client = TofuPilot()
# Upload a file to a unit
client.units.attachments.upload(serial_number="SN-0001", file="data/pcb-inspection.jpg")
# Upload multiple files
for f in ["data/pcb-inspection.jpg", "data/calibration.csv"]:
client.units.attachments.upload(serial_number="SN-0001", file=f)using TofuPilot;
var client = new TofuPilot();
// Upload a file to a unit
await client.Units.Attachments().UploadAsync("SN-0001", "data/pcb-inspection.jpg");
// Upload multiple files
foreach (var f in new[] { "data/pcb-inspection.jpg", "data/calibration.csv" })
await client.Units.Attachments().UploadAsync("SN-0001", f);#include <tofupilot/upload.hpp>
auto client = tofupilot::TofuPilot("your-api-key");
// Upload a file to a unit
auto id = client.units().attachments().upload("SN-0001", "data/pcb-inspection.jpg");sdk = tofupilot.TofuPilot('your-api-key');
% Upload a file to a unit
sdk.Units.Attachments.upload('SN-0001', 'data/pcb-inspection.jpg');Supported formats include images (JPEG, PNG), documents (PDF, CSV, TXT), and any binary file.
Browse & Filter Units
You can browse and filter units from the Dashboard or with the API client.
from tofupilot.v2 import TofuPilot
client = TofuPilot()
# List all units for a part number
units = client.units.list(part_numbers=["PCB01"])
for u in units.data:
print(f"{u.serial_number}")
# Filter by batch
units = client.units.list(batch_numbers=["2024-001"])
# Get a specific unit
unit = client.units.get(serial_number="SN-0001")
print(f"Serial: {unit.serial_number}")
print(f"Part: {unit.part.number} rev {unit.part.revision.number}")using TofuPilot;
var client = new TofuPilot();
// List all units for a part number
var units = await client.Units.ListAsync(partNumbers: new List<string> { "PCB01" });
foreach (var u in units.Data)
Console.WriteLine(u.SerialNumber);
// Filter by batch
var batchUnits = await client.Units.ListAsync(batchNumbers: new List<string> { "2024-001" });
// Get a specific unit
var unit = await client.Units.GetAsync("SN-0001");
Console.WriteLine($"Serial: {unit.SerialNumber}");#include <tofupilot/tofupilot.hpp>
auto client = tofupilot::TofuPilot("your-api-key");
auto units = client.units().list()
.part_numbers({"PCB01"})
.send();
auto unit = client.units().get()
.serial_number("SN-0001")
.send();sdk = tofupilot.TofuPilot('your-api-key');
units = sdk.Units.list('partNumbers', {{'PCB01'}});
unit = sdk.Units.get('SN-0001');Download Attachments
You can download attachments from a unit.
from tofupilot.v2 import TofuPilot
client = TofuPilot()
unit = client.units.get(serial_number="SN-0001")
for a in unit.attachments:
client.units.attachments.download(a)
print(f"Downloaded {a.name} ({a.size} bytes)")using TofuPilot;
var client = new TofuPilot();
var unit = await client.Units.GetAsync("SN-0001");
foreach (var a in unit.Attachments)
{
await client.Units.Attachments().DownloadAsync(a.DownloadUrl, a.Name);
Console.WriteLine($"Downloaded {a.Name} ({a.Size} bytes)");
}#include <tofupilot/tofupilot.hpp>
auto client = tofupilot::TofuPilot("your-api-key");
auto unit = client.units().get()
.serial_number("SN-0001")
.send();
for (const auto& a : *unit.attachments)
client.units().attachments().download(a.download_url, a.name);sdk = tofupilot.TofuPilot('your-api-key');
unit = sdk.Units.get('SN-0001');
for i = 1:numel(unit.attachments)
a = unit.attachments{i};
sdk.Units.Attachments.download(a.download_url, a.name);
endDelete Attachments
You can delete attachments from a unit by their IDs.
from tofupilot.v2 import TofuPilot
client = TofuPilot()
client.units.attachments.delete(serial_number="SN-0001", ids=["attachment-id"])using TofuPilot;
var client = new TofuPilot();
await client.Units.Attachments().DeleteAsync("SN-0001", new List<string> { "attachment-id" });#include <tofupilot/upload.hpp>
auto client = tofupilot::TofuPilot("your-api-key");
client.units().attachments().delete_("SN-0001", {"attachment-id"});sdk = tofupilot.TofuPilot('your-api-key');
sdk.Units.Attachments.delete('SN-0001', {'attachment-id'});Update Units
You can update units from the Dashboard or with the API client.

from tofupilot.v2 import TofuPilot
client = TofuPilot()
# Update serial number, part, revision, or batch
client.units.update(
serial_number="SN-0001",
new_serial_number="SN-0002",
part_number="PCB02",
revision_number="B",
)using TofuPilot;
using TofuPilot.Models.Requests;
var client = new TofuPilot();
await client.Units.UpdateAsync("SN-0001", new UnitUpdateRequestBody
{
NewSerialNumber = "SN-0002",
PartNumber = "PCB02",
RevisionNumber = "B",
});#include <tofupilot/tofupilot.hpp>
auto client = tofupilot::TofuPilot("your-api-key");
client.units().update()
.serial_number("SN-0001")
.new_serial_number("SN-0002")
.part_number("PCB02")
.revision_number("B")
.send();sdk = tofupilot.TofuPilot('your-api-key');
sdk.Units.update('SN-0001', struct( ...
'new_serial_number', 'SN-0002', ...
'part_number', 'PCB02', ...
'revision_number', 'B'));Delete Units
You can delete units from the Dashboard or with the API client.
from tofupilot.v2 import TofuPilot
client = TofuPilot()
client.units.delete(serial_numbers=["SN-0001"])using TofuPilot;
var client = new TofuPilot();
await client.Units.DeleteAsync(new List<string> { "SN-0001" });#include <tofupilot/tofupilot.hpp>
auto client = tofupilot::TofuPilot("your-api-key");
client.units().delete_()
.serial_numbers({"SN-0001"})
.send();sdk = tofupilot.TofuPilot('your-api-key');
sdk.Units.delete('serialNumbers', {{'SN-0001'}});Unit Activity
Track unit activity on the unit’s page in the Dashboard. Shows all tests performed on the unit, grouped by Procedure name, and changes made when creating Runs.

How is this guide?
Stations
Deploy test stations with controlled access and monitoring. Assign procedures, manage station uptime, and track test execution across your fleet.
Parts & Revisions
Track components, part numbers, and revision history. Manage your hardware inventory and link parts to individual test units in TofuPilot.