Skip to main content

Attribute

Port uses C# attributes to declaratively define package registration, API generation, dependency injection, and workflow control. This page is a categorized reference for all Port attributes.


Quick Reference

A summary table of all Port attributes.

CategoryAttributeTargetDescription
Portdic[Portdic]classRegisters the main window as the Port project entry point
Package[Package]classRegisters a Port-managed package
[Handler]propertyInjects IPackageHandler (log + property)
[Preset]methodInitialization after injection
[API]property, fieldGenerates a REST API endpoint
[Valid]methodValidation gate
[Comment]propertyAPI documentation
Equipment[Equipment]classTransfer-scheduler hub — owns TMC, PMC, LMC, and score methods
[TMC]propertyInjects ITransferModule<T> into [Equipment] or [Flow]
[LMC]propertyInjects ILoadModule (load port)
[PMC]propertyInjects IProcessModule (process chamber)
[TransferScore]methodDefine pick/place priority score for the transfer scheduler
Controller[Controller]classFlow controller
[Flow]classWorkflow definition
[FlowStep]methodWorkflow step
[Handler]propertyInjects IFlowHandler (basic step control)
[Handler]propertyInjects IFlowWithModelHandler<T> (lifecycle events with model)
[Handler]propertyInjects ISchedulerHandler<T> (transfer scheduling)
[Model]classDefine flow model class
[ModelBinding]propertyBind model property to a Port entry
[FlowWatcherCompare]methodStep condition watcher
[FlowWatcherAction]methodStep completion action
[Timeout]methodStep timeout
[FlowDelay]methodWait N ms before invoking the step body
[RecordTime]methodRecord elapsed ms into a Port entry on every poll tick
[TransferBlockWhenExecutingFlow]classBlock TM transfers to this location while the flow is Executing
FileSender[FileSender]classRegisters a QUIC file transfer handler container
[FileSenderHandler]propertyInjects IFileSenderHandler
Document[Document]classSource document path
[Save]methodOutput file paths
[ColumnHeader]propertyColumn header mapping
[EntryKey]propertyKey column
[EntryProperty]propertyProperty column
[EntryDataType]propertyData type

Portdic

[Portdic("repoName")] is the entry-point attribute. It must be applied to the application's main class (e.g., MainWindow) and registers the project under the given repository name. All Port services, packages, controllers, and flows are resolved within this project scope.

The repoName string must match the name used in .page file paths and Port.Push() / Port.Add() calls throughout the project.

Constructor:

Portdic(string reponame, string pull_path = "")
ParameterTypeRequiredDescription
reponamestringYesRepository name — must match all Port.Add() / Port.Push() calls in the project
pull_pathstringNoLocal directory path for pulling remote data; defaults to "" (disabled)
[Portdic("EQ-01-B05")]
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Port.App<MainWindow>();

Port.Add<SessionHelper>("Session");
Port.Add<LoadportController>("LP1");
Port.Add<LoadportController>("LP2");
Port.Add<WTRController>("WTR");
}
}

Package Attributes

Attributes used for defining package classes and generating APIs.

AttributeTargetInjected TypeArgumentsDescription
[Package]classRegisters class as a Port-managed package
[Handler]propertyIPackageHandlerInjects a handler, providing unified access to logging and entry properties
[Preset]methodCalled once after injection; use this for initialization and event wiring
[API]property, fieldEntryDataType, PropertyFormat, keysGenerates a REST API endpoint
[Valid]methodmessageValidation gate; returns error message when invalid
[Comment]propertystringAPI property documentation

Package

Registers a class as a managed package in the Port Dictionary system.

Constructor: No parameters.

Package()
[Package]
public class Bulb
{
[Handler]
public IPackageHandler Handler { get; set; }

[API(EntryDataType.Enum)]
public string OffOn { get; set; } = "Off";
}

Handler

Injects IPackageHandler, which provides unified access to logging and entry property values. Use SetLogger in [Preset] to enable file logging, then call Write anywhere inside the package.

Constructor: No parameters — applies to both [Handler] and [Preset].

Handler()
Preset()
[Package]
public class Heater
{
[Handler]
public IPackageHandler Handler { get; set; }

[Preset]
public void Preset()
{
// Enable file logging — writes to C:\Logs\Heater\
Handler.SetLogger(@"C:\Logs");
}

[API]
public double Temp
{
get
{
// Read entry property (set in .page via property:{...})
if (Handler.EntryProperty.TryToGetValue("Unit", out string unit))
{
Handler.Write($"[INFO] Unit={unit}");
return unit == "F" ? 212.0 : 100.0;
}
return double.NaN;
}
}
}

IPackageHandler members:

MemberDescription
SetLogger(rootPath)Enable file logging; creates rootPath/packageName/ sub-directory
SetLogger(rootPath, conf)Same with rotation and retention options via PortLogConfiguration
Write(message)Write a plain text log entry
Write(code, header, dict)Write a structured log entry with LogTypeCode, header, and key-value data
EntryPropertyCurrent IProperty for the active Get/Set request; use TryToGetValue to read values

IProperty.TryToGetValue usage:

// .page entry definition
// RoomTemp f8 property:{"Unit":"C","Max":"300"}

// Inside a package API property getter:
if (Handler.EntryProperty.TryToGetValue("Unit", out string unit))
{
// unit == "C"
}
if (Handler.EntryProperty.TryToGetValue("Max", out string max))
{
double limit = double.Parse(max); // limit == 300.0
}

API

Automatically registers a property as a REST API endpoint. Specify the data type with EntryDataType.

Constructors:

API()
API(EntryDataType dataType)
API(string key)
API(EntryDataType dataType, PropertyFormat format)
API(EntryDataType dataType, PropertyFormat format, params string[] required)
API(EntryDataType dataType, PropertyFormat format, params int[] required)
ParameterTypeDescription
dataTypeEntryDataTypeSECS-compatible data type of the exposed property (default: Text)
keystringCustom entry key name; use when the property name differs from the entry key
formatPropertyFormatSerialization format for multi-value properties (Json, Array, etc.)
requiredstring[] or int[]Required property keys (strings) or required index list (ints) for structured types
// Basic usage
[API]
public string Status { get; set; }

// Enum type
[API(EntryDataType.Enum)]
public string OffOn { get; set; }

// Numeric with JSON property keys
[API(EntryDataType.Num, PropertyFormat.Json, "Unit")]
public double Temp1 { get; set; }

// String type
[API(EntryDataType.Char)]
public string Power { get; set; }

// List type
[API(EntryDataType.List, PropertyFormat.Array, 0, 1, 2)]
public List<string> Readings { get; set; }

EntryDataType values:

TypeDescription
EntryDataType.TextText
EntryDataType.NumNumeric (double)
EntryDataType.CharASCII string
EntryDataType.EnumEnumeration
EntryDataType.ListList

Linking an API property to a Package in .page:

Use pkg:PackageName.PropertyName in the .page file to connect an entry to a [API] property in a [Package] class.

# device/io.page
RoomTemp f8 pkg:Heater.Temp
BulbStatus enum pkg:Bulb.OffOn
Power char pkg:Bulb.Power
// Package class — property names must match the pkg: declaration
[Package]
public class Heater
{
[API]
public double Temp { get; set; } // → pkg:Heater.Temp
}

[Package]
public class Bulb
{
[API(EntryDataType.Enum)]
public string OffOn { get; set; } // → pkg:Bulb.OffOn

[API(EntryDataType.Char)]
public string Power { get; set; } // → pkg:Bulb.Power
}

Valid

Defines a validation method for the package. When it returns false, the error message passed as argument is displayed.

Constructor:

Valid(string invalidComment)
ParameterTypeDescription
invalidCommentstringThe error message returned to the caller when validation fails
[Valid("Device not connected")]
public bool Valid()
{
return serialPort.IsOpen;
}

Comment

Adds a description to an API property.

Constructors:

Comment(string comment)
Comment(Dictionary<string, string> comment)
ParameterTypeDescription
commentstringSingle description string for the property
commentDictionary<string, string>Multi-language or multi-key description map
[API, Comment("Current temperature (Celsius)")]
public double Temperature { get; set; }

Equipment Attributes

Attributes used for the [Equipment] transfer-scheduler hub class. These attributes are parsed by the dedicated [Equipment] path in the framework and apply to flat (non-nested) scheduler classes registered via Port.Add<T>().

AttributeTargetInjected TypeArgumentsDescription
[Equipment]classtmcNameMarks the class as the scheduler hub for the named TM
[TMC]propertyITransferModule<T>capacityInjects the scheduler singleton
[LMC]propertyILoadModulecapacityMarks location as load port; injects ILoadModule instance
[PMC]propertyIProcessModulecapacityMarks location as process module; injects IProcessModule instance
[TransferScore]methodlocation, directionReturns int score for scheduler pick/place decisions
[Preset]methodInvoked after all DI is complete; wire scheduler events here

Controller Attributes

Attributes used for defining and controlling workflows (process flows) inside [Controller] classes. Registered via Port.Add<TController, TModel>(key).

AttributeTargetInjected TypeArgumentsDescription
[Controller]classDefines a controller containing flows
[Flow]classkeyDefines a workflow class (inner class of Controller)
[FlowStep]methodindex, relatedEntryDefines a workflow step
[Handler]propertyIFlowHandlerInjects basic flow step control into a Flow class
[Handler]propertyIFlowWithModelHandler<T>Injects model-bound handler with lifecycle events
[Handler]propertyISchedulerHandler<T>Injects transfer scheduler handler
[Model]classDefines a flow model class; properties use [ModelBinding]
[ModelBinding]propertyEntrycontrollerName, entryKeyBinds a model property to a Port entry
[TMC]propertyITransferModule<T>Injects transfer module controller into a [Flow] class
[LMC]propertyILoadModulecapacityInjects load port module into a [Flow] class
[PMC]propertyIProcessModulecapacityInjects process chamber module into a [Flow] class
[TransferScore]methodlocation, directionReturns int score for scheduler pick/place decisions
[FlowWatcherCompare]methodentry, op, valueDefines step execution condition
[FlowWatcherAction]methodentry, valueDefines action on step completion
[Timeout]methodms, controller, alarmidSets step timeout and alarm
[FlowDelay]methoddelayMsWait N ms after step entry before invoking the step body
[RecordTime]methodcategory, fullKeyRecord elapsed ms into a Port entry on every poll tick
[TransferBlockWhenExecutingFlow]classtmNameBlock TM transfers to this location while the flow is Executing

Controller

[Controller] defines a controller that contains multiple flows. All [Flow] classes must be declared as inner classes inside a [Controller] class.

Constructors:

Controller()
Flow(string key)
AttributeParameterTypeDescription
[Controller]No parameters; marks the class as a Port flow controller
[Flow]keystringFlow name — used to identify and trigger the flow at runtime
// Model class — defined outside the controller
[Model]
public class LoadportModel
{
[ModelBinding(Controller.LP1, EFEM.LP1_Command)]
[ModelBinding(Controller.LP2, EFEM.LP2_Command)]
public Entry LP_Command { get; set; }

[ModelBinding(Controller.LP1, EFEM.LP1_Main_Air_i)]
[ModelBinding(Controller.LP2, EFEM.LP2_Main_Air_i)]
public Entry LP_Main_Air_i { get; set; }
}

// Controller — [Flow] classes must be inner classes
[Controller]
public class LoadportController
{
[Flow("Load")]
public class FoupLoadFlow
{
[Handler]
public IFlowHandler Handler { get; set; } = null!;

[FlowStep(0)]
public void StatusCheck(LoadportModel m)
{
Handler.ClearAlarm(-1);
Debug.WriteLine(m.LP_Main_Air_i.Name + " : " + m.LP_Main_Air_i.Value.String());
Handler.Next();
}

[FlowStep(1)]
public void SendLoadCommand(LoadportModel m)
{
Handler.Next();
}

[FlowStep(2)]
public void Done(LoadportModel m)
{
Handler.Done();
}
}
}

Handler

Injects a handler interface into a [Flow] class. The injected type is determined by the property's declared type.

Property typePurpose
IFlowHandlerBasic step control (Next(), Done(), logging)
IFlowWithModelHandler<T>Lifecycle events that carry the bound model
ISchedulerHandler<T>Transfer scheduling for robot arm coordination

FlowStep methods always receive the model as a method parameter — they do not inject it as a property.

Constructor: No parameters — applies to [Handler] and [Preset].

Handler()
Preset()
// [Handler] — basic step control
[Flow("Load")]
public class FoupLoadFlow
{
[Handler]
public IFlowHandler Handler { get; set; } = null!;

[Preset]
public void Preset()
{
Handler.SetLogger(@"D:\logs");
}

[FlowStep(0)]
public void StatusCheck(LoadportModel m)
{
Handler.ClearAlarm(-1);
Handler.Next();
}

[FlowStep(1)]
public void Done(LoadportModel m)
{
Handler.Done();
}
}

// [Handler] — lifecycle events with model
//
// OnFlowFinished fires after handler.Done() completes.
// e.Model carries the bound model so the caller can act on the result
// (e.g. notify a scheduler that this transfer location is free).
[Flow("Place")]
public class Place
{
[Handler]
public IFlowWithModelHandler<WTRCommModel> handler { get; set; } = null!;

[Handler]
public ISchedulerHandler<DualArmActionArgs> scheduler { get; set; } = null!;

[Preset]
public void Preset()
{
handler.SetLogger(@"D:\logs");
handler.OnFlowOccurred += Handler_OnFlowOccurred;
handler.OnFlowFinished += Handler_OnFlowFinished;
}

// Called when the flow is triggered (before Step 0 runs).
// e.Model is the bound model populated at flow start.
private void Handler_OnFlowOccurred(object sender, PortFlowOccurredWithModelArgs<WTRCommModel> e)
{
// e.Model.Target, e.Model.Source, etc. are already populated
}

// Called after handler.Done() returns the flow to Idle.
// Use e.Model to read the final state and notify downstream systems.
private void Handler_OnFlowFinished(object sender, FlowFinishedWithModelArgs<WTRCommModel> e)
{
// e.Model.Target contains the destination that was set when the flow was triggered
scheduler.TransferCompleted(e.Model.Target);
}

[FlowStep(0)]
public void CheckAction(WTRCommModel m)
{
handler.WriteLog("CheckAction", WriteRule.NotAllowDuplicate | WriteRule.WithDebug);
handler.Next();
}

[FlowStep(1)]
public void Done(WTRCommModel m)
{
handler.Done(); // triggers Handler_OnFlowFinished after returning to Idle
}
}

IFlowHandler members:

MemberDescription
Next()Move to the next FlowStep
Done()Complete the flow and return to Idle synchronously
Move(stepName)Jump to a named step
Alert(message)Send an alert notification
OccurredAlarm(alid)Raise an alarm by alarm ID
ClearAlarm(alid = -9999)Clear alarm; -9999 clears all alarms
SetLogger(rootPath)Enable file logging under rootPath/flowName/
WriteLog(message)Write a log entry
WriteLog(message, rule)Write a log entry with WriteRule flags

WriteRule flags:

FlagDescription
NoneWrite to file only (default)
NotAllowDuplicateSkip consecutive duplicate messages
WithDebugAlso write to Debug.WriteLine
WithConsoleAlso write to Console.WriteLine
WithTraceAlso write to Trace.WriteLine

IFlowWithModelHandler<T> additional members:

MemberDescription
T ModelThe model instance bound to this flow
OnFlowOccurredFired when the flow starts
OnFlowFinishedFired when the flow completes; e.Model carries the bound model

FlowStep

Registers a method as a workflow step. Steps are ordered by index number. The bound model is received as a method parameter — omit it if no model is needed.

Constructors:

FlowStep(int index = 0, params string[] relatedEntry)
FlowStep(string prev)
FlowStep(int index, ushort ceid, params string[] relatedEntry)
OverloadParameterTypeDescription
DefaultindexintStep execution order; lower numbers run first (default: 0)
DefaultrelatedEntrystring[]Entry keys that trigger step re-evaluation on value change
Named-prevprevstringName of the preceding step; use when step order is defined by name instead of index
CEIDindexintStep execution order
CEIDceidushortCollection Event ID to fire when this step completes
CEIDrelatedEntrystring[]Entry keys that trigger step re-evaluation
// With model parameter
[FlowStep(0)]
public void StatusCheck(LoadportModel m)
{
Debug.WriteLine(m.LP_Main_Air_i.Value.String());
Handler.Next();
}

[FlowStep(1)]
public void SendCommand(LoadportModel m)
{
Handler.Next();
}

[FlowStep(2)]
public void Done(LoadportModel m)
{
Handler.Done(); // Complete flow and return to Idle
}

FlowWatcherCompare

Defines pre/post conditions for a step. All conditions must be met before proceeding to the next step.

Constructors:

FlowWatcherCompare(string a, string op, object b, bool OR = false)
FlowWatcherCompare(string controller_name, string model_key_name, string op, object value, bool OR = false)
OverloadParameterTypeDescription
DirectastringLeft-hand entry key; prefix @ to reference a model binding
DirectopstringComparison operator: >=, <=, >, <, ==, !=
DirectbobjectRight-hand value or entry key
DirectORbooltrue = combine with previous watcher using OR logic (default: false = AND)
Controllercontroller_namestringLimit this watcher to the named controller instance (e.g. "LP1")
Controllermodel_key_namestringEntry key from the controller's model
ControlleropstringComparison operator
ControllervalueobjectRight-hand comparison value
ControllerORboolOR logic flag (default: false)
[FlowStep]
[FlowWatcherCompare("@Temp1", ">=", 50)] // Model binding (@)
[FlowWatcherCompare("room2.HeaterTemp4", ">=", 100)] // Direct reference
[FlowWatcherCompare("room2.HeaterTemp5", ">=", Room2.HeaterTemp4)] // Entry-to-entry comparison
public void Step1()
{
Handler.Next();
}

Supported operators: >=, <=, >, <, ==, !=

FlowWatcherAction

Automatically sets a value when a step completes.

Constructors:

FlowWatcherAction(string target, object v)
FlowWatcherAction(string controller_name, string target, object v)
OverloadParameterTypeDescription
DirecttargetstringEntry key to write to when the step finishes
DirectvobjectValue to write
Controllercontroller_namestringLimit this action to the named controller instance
ControllertargetstringEntry key to write to
ControllervobjectValue to write
[FlowStep]
[FlowWatcherAction("room2.BulbOnOff", "Off")] // Sets BulbOnOff = "Off" on completion
[FlowWatcherAction(Room2.BulbOnOff, "On")] // Using token constants
public void Step1()
{
Handler.Next();
}

Model

[Model] marks a class as a flow model. Properties are of type Entry and decorated with [ModelBinding] to bind to Port entries. The platform instantiates the model and passes it as a method parameter to each [FlowStep].

Multiple [ModelBinding] attributes on one property let the same model class serve different controllers — the binding that matches the running controller's name is applied.

Constructors:

// [Model]
Model()
Model(string controllerName)

// [ModelBinding]
ModelBinding(string controller_name, string key)
ModelBinding(string key)
AttributeParameterTypeDescription
[Model]No parameters; marks the class as a Port flow model
[Model]controllerNamestringRestrict this model to a specific controller name
[ModelBinding]controller_namestringController instance name this binding applies to (e.g. "LP1")
[ModelBinding]keystringPort entry key to bind the property to
[ModelBinding] (1-arg)keystringBind to this entry for all controllers (no controller filter)
// Define the model class — outside the controller
[Model]
public class LoadportModel
{
// Bound to LP1_Command when run under "LP1", LP2_Command under "LP2"
[ModelBinding(Controller.LP1, EFEM.LP1_Command)]
[ModelBinding(Controller.LP2, EFEM.LP2_Command)]
public Entry LP_Command { get; set; }

[ModelBinding(Controller.LP1, EFEM.LP1_Configure_Value_i)]
[ModelBinding(Controller.LP2, EFEM.LP2_Configure_Value_i)]
public Entry LP_Configure_Value_i { get; set; }

[ModelBinding(Controller.LP1, EFEM.LP1_Main_Air_i)]
[ModelBinding(Controller.LP2, EFEM.LP2_Main_Air_i)]
public Entry LP_Main_Air_i { get; set; }
}

// Receive model as a parameter in each FlowStep
[FlowStep(0)]
public void StatusCheck(LoadportModel m)
{
Debug.WriteLine(m.LP_Configure_Value_i.Name + " : " + m.LP_Configure_Value_i.Value.String());
Handler.ClearAlarm(-1);
Handler.Next();
}

[FlowStep(1)]
public void SendLoadCommand(LoadportModel m)
{
Debug.WriteLine(m.LP_Main_Air_i.Name + " : " + m.LP_Main_Air_i.Value.String());
Handler.Next();
}

Entry members:

MemberDescription
NameEntry key string (e.g. "EFEM.LP1_Command")
Value.String()Current value as string
Value.Double()Current value as double
Value.Int()Current value as int

Timeout

Sets a timeout for a FlowStep. When the step exceeds the duration, the specified alarm is raised on the given controller.

Constructor:

Timeout(int ms, string controller, int alarmid)
ParameterTypeDescription
msintTimeout duration in milliseconds
controllerstringController instance name this timeout applies to (e.g. "LP1")
alarmidintAlarm ID to raise when the timeout expires
[FlowStep]
[Timeout(0, 0)] // min, max (0 = unlimited)
public void Step1()
{
Handler.Next();
}

TMC / LMC / PMC

[TMC], [LMC], and [PMC] inject module interfaces into a [Flow] class. They declare which equipment modules the flow can interact with and provide the scheduler with the equipment topology needed to plan transfers.

AttributeInjected TypeArgumentDescription
[TMC]ITransferModule<T>Transfer robot arm; T is the arm action type (e.g. DualArmAction)
[LMC(n)]ILoadModulecapacity (int)Load port; capacity = number of wafer slots (e.g. 25 for a FOUP)
[PMC(n)]IProcessModulecapacity (int)Process chamber; capacity = number of substrate slots

Constructors:

TMC()
LMC(int capacity)
PMC(int capacity)
[Flow("Queued")]
public class Queued
{
[TMC] public ITransferModule<DualArmAction> TM1 { set; get; } = null!;

[LMC(25)] public ILoadModule LP1 { get; set; } = null!;
[LMC(25)] public ILoadModule LP2 { get; set; } = null!;

[PMC(1)] public IProcessModule Stage1 { set; get; } = null!;
[PMC(1)] public IProcessModule Stage2 { set; get; } = null!;
[PMC(1)] public IProcessModule Aligner { set; get; } = null!;

[Preset]
public void Preset()
{
TM1.SetRule(TransferRule.PreferSwap);
TM1.SetChildLocation("Upper", "Lower");
TM1.OnRequestTransferAction += OnTransferRequested;
TM1.OnTransferActionCompleted += OnTransferCompleted;
TM1.OnLotCompleted += OnLotCompleted;

Stage1.SetRecipeRootPath(@"D:\Stage1");
Stage1.OnRequestProcess += Stage1_OnRequestProcess;

LP1.OnRequestAction += LP1_OnRequestAction;
}
}

ITransferModule<T> members:

MemberDescription
SetRule(TransferRule)Set the scheduling strategy (e.g. TransferRule.PreferSwap)
SetChildLocation(params string[])Declare virtual arm names within the module (e.g. "Upper", "Lower")
Register(lotId, SubstrateJob[])Register a lot with its substrate route list
Execute(lotId)Start executing the registered lot
ClearLot()Clear the current lot from the scheduler
IsCompleted(lotId)Returns true when all substrates in the lot have completed
NextRequest()Advance the scheduler to the next pending transfer action
OnRequestTransferActionFired when the scheduler needs a physical robot move
OnTransferActionCompletedFired when a robot action completes
OnLotCompletedFired when all substrates in the lot are done
OnSameLocationProcessFired when a substrate needs in-place processing

DualArmAction members (callback argument for OnRequestTransferAction / OnTransferActionCompleted):

MemberTypeDescription
ActionTypeArmActionTypeUpperGet, UpperPut, LowerGet, or LowerPut
Target.NamestringDestination location name
Target.IndexintSlot index within the location
SubstrateKeystringSubstrate identifier

ILoadModule members:

MemberDescription
OnRequestActionFired when the scheduler requests a load or unload action; argument is LoadPortActionArgs

IProcessModule members:

MemberDescription
SetRecipeRootPath(path)Set the root directory for recipe files
OnRequestProcessFired when the scheduler delivers a substrate for processing; argument is ProcessArgs

Equipment

[Equipment("tmcName")] registers a flat class as the transfer-scheduler hub for the named Transfer Module. Unlike [Controller] — which contains nested [Flow] inner classes — an [Equipment] class has no sub-flows. Instead, it owns the scheduler, declares all location scores and module properties at the top level, and triggers flows in separate [Controller] classes by setting FlowAction.Executing.

Constructor:

Equipment(string TMC)
ParameterTypeDescription
TMCstringName of the Transfer Module Controller this hub is bound to (e.g. "TM1")

Registration — single-generic, no model:

Port.Add<Equipment>(); // key is taken from [Equipment("TM1")] automatically
// or
Port.Add<Equipment>("TM1");

What the framework injects automatically:

StepAction
[TMC] propertyInjects ITransferModule<T> (the scheduler singleton)
[PMC] propertyMarks location as ProcessModule + injects IProcessModule instance
[LMC] propertyMarks location as LoadModule + injects ILoadModule instance + registers slot count
[TransferScore] methodsRegisters scoring lambdas with the scheduler
[Preset] methodInvoked after all DI is complete — wire event handlers here
JobEntity lifecycleBridges JobEntity.ProcessingOnRequestQueued / OnRequestLotProcessing automatically

Comparison with [Controller]:

Aspect[Controller][Equipment]
StructureHas nested [Flow] inner classesFlat — no inner classes
Flow executionRuns steps internallyTriggers flows in other controllers
Scheduler ownershipMay share the singletonAlways owns the singleton
JobEntity bridgeManualAutomatic
RegistrationPort.Add<T, M>(key)Port.Add<T>()
[Equipment("TM1")]
public class Equipment
{
// ── Module declarations ───────────────────────────────────────────────
[TMC(2)] public ITransferModule<DualArmAction> TM1 { get; set; } = null!;

[LMC(25)] public ILoadModule LP1 { get; set; } = null!;
[LMC(25)] public ILoadModule LP2 { get; set; } = null!;

[PMC(1)] public IProcessModule Stage1 { get; set; } = null!;
[PMC(1)] public IProcessModule Stage2 { get; set; } = null!;
[PMC(1)] public IProcessModule Aligner { get; set; } = null!;

// ── Arm scores ────────────────────────────────────────────────────────
[TransferScore("Upper", Direction.In)] public int InUpper() => 1;
[TransferScore("Upper", Direction.Out)] public int OutUpper() => 1;

// ── Location scores ───────────────────────────────────────────────────
[TransferScore("LP1", Direction.In)]
public int InLP1() => (Port.Get(portdic.LP1.LP_Status)?.String() ?? "") == "Loaded" ? 1 : 0;

[TransferScore("Stage1", Direction.In)]
public int InStage1() => GateOpen("Stage1") ? StageReady("Stage1") : -1;

[TransferScore("Stage1", Direction.Out)]
public int OutStage1() => GateOpen("Stage1") ? 1 : -1;

[TransferScore("Aligner", Direction.In)]
public int InAligner() => Port.GetEntity<SubstrateEntity>("Aligner").GetExists() ? 1 : 0;

// ── Scheduler wiring ─────────────────────────────────────────────────
[Preset]
public void Preset()
{
TM1.OnScanTarget += TM1_OnAskTarget;
TM1.OnRequestTransfer += OnRequestTransfer;
TM1.OnRequestQueued += TM1_OnRequestQueued;
TM1.OnTransferCompleted += OnTransferCompleted;
TM1.OnLotCompleted += TM1_OnLotCompleted;
TM1.SetRule(TransferRule.PreferSwap);
TM1.SetChildLocation("Upper", "Lower");
LP1.OnRequestAction += LoadPort1_OnRequestAction;
}

// ── Transfer handler — delegates to robot flow ────────────────────────
private void OnRequestTransfer(DualArmAction args)
{
if (isSkip(args)) { TM1.NextRequest(); return; }
Port.Set(Cat.Robot, Flows.Robot_Pick, FlowAction.Executing);
}

// Blocks transfers to a location while its process flow is Executing.
private static bool isSkip(DualArmAction args)
=> Port.IsTransferBlocked("TM1", args.Target.Name);

// ── Transfer-completed handler — triggers process flows ───────────────
private static void OnTransferCompleted(DualArmAction args)
{
if (args.Target.Name.StartsWith("Stage"))
Port.Set(args.Target.Name, Flows.Stage_Process, FlowAction.Executing);
}

private static bool GateOpen(string loc) =>
(Port.Get($"{loc}.Gate_Open_o")?.String() ?? "") == "On";

private int StageReady(string loc) =>
Port.GetEntity<SubstrateEntity>(loc).GetExists() ? 1 : 0;
}

TransferScore

[TransferScore] decorates methods that return an int priority score. The scheduler calls all score methods on each tick and uses the scores to select the best pick/place pair. Methods can be declared directly in an [Equipment] class or inside a [Flow] class within a [Controller], and must return int.

Constructor:

TransferScore(string location, Direction direction)
ParameterTypeDescription
locationstringLocation name — must match the name of a [TMC], [LMC], or [PMC] property, or a virtual child location registered via SetChildLocation
directionDirectionDirection.In = score for picking FROM this location; Direction.Out = score for placing TO it

Score convention:

Return valueMeaning
> 0Available — higher values are preferred
0Not ready — skip this location this tick
< 0 (e.g. -1)Hard block — do not schedule this location under any condition
// Arm scores — arms are always available
[TransferScore("Upper", Direction.In)] public int InUpper() => 1;
[TransferScore("Upper", Direction.Out)] public int OutUpper() => 1;
[TransferScore("Lower", Direction.In)] public int InLower() => 1;
[TransferScore("Lower", Direction.Out)] public int OutLower() => 1;

// Load port — available only when a FOUP is docked
[TransferScore("LP1", Direction.In)]
public int InLP1() =>
(Port.Get(portdic.LP1.LP_Status)?.String() ?? "") == "Loaded" ? 1 : 0;

// Stage — hard-blocked while gate is closed; pick-ready only when processing is done
[TransferScore("Stage1", Direction.In)]
public int InStage1() => GateOpen("Stage1") ? StageReady("Stage1") : -1;

[TransferScore("Stage1", Direction.Out)]
public int OutStage1() => GateOpen("Stage1") ? 1 : -1;

Direction values:

ValueDescription
Direction.InPick — get substrate FROM this location
Direction.OutPlace — put substrate TO this location

FlowDelay

Delays the execution of a [FlowStep] method body by a fixed number of milliseconds after the step is first entered. On every poll tick the framework checks whether the delay has elapsed; if not, the step body is skipped and the flow remains at the current step without advancing.

Constructor:

FlowDelay(int delayMs)
ParameterTypeDescription
delayMsintMinimum wait in milliseconds from the moment the step is entered

The delay fires once per step entry. If Handler.Next() is not called inside the body, the step is re-polled on each tick — the delay will not restart between polls.

[Flow(Flows.Stage_Process)]
public class StageProcessFlow
{
[Handler]
public IFlowHandler Handler { get; set; } = null!;

// Step 0: signal gate to close, then advance immediately.
[FlowStep(0)]
public void CheckStatus(StageModel m)
{
m.Gate_Open_o.Set("Off");
Handler.Next();
}

// Step 1: framework waits 3 s after this step is entered before
// calling StartProcess — gives the gate time to physically close.
[FlowStep(1), FlowDelay(3000)]
public void StartProcess(StageModel m)
{
m.WaferPresent_i.Set("On");
m.Start_o.Set("On");
Handler.Next();
}

[FlowStep(2)]
public void WaitDone(StageModel m)
{
if (m.Done_i.Get() != "On") return;
Handler.Done();
}
}

RecordTime

Records elapsed milliseconds into a Port entry on every poll tick while the decorated [FlowStep] is the active step. Attach one attribute per controller instance (category). The timer resets automatically when the entry value is 0.0.

Constructor:

RecordTime(string category, string fullKey)
ParameterTypeDescription
categorystringController instance key (e.g. "Stage1") — only the attribute matching the running instance fires
fullKeystringFully-qualified Port entry key to write elapsed ms to (e.g. "Stage1.ProcessTimer")

[RecordTime] can appear multiple times on the same method (one per instance). The framework matches category against the current controller key and fires only the matching attribute.

// Reset the timer entry to 0.0 in the previous step to start a fresh measurement.
[FlowStep(1)]
public void StartProcess(StageModel m)
{
m.ProcessTimer.Set(0.0); // sentinel — RecordTime auto-starts on next poll
Handler.Next();
}

// One [RecordTime] per stage instance; only the matching one fires each tick.
[FlowStep(2)]
[RecordTime("Stage1", portdic.Stage1.ProcessTimer)]
[RecordTime("Stage2", portdic.Stage2.ProcessTimer)]
[RecordTime("Stage3", portdic.Stage3.ProcessTimer)]
[RecordTime("Stage4", portdic.Stage4.ProcessTimer)]
[RecordTime("Stage5", portdic.Stage5.ProcessTimer)]
public void WaitProcessDone(StageModel m)
{
if (m.ProcessTimer.Get() < _processDurationMs) return;
Handler.Next();
}

Timer lifecycle:

EventAction
Entry value is 0.0 on first pollTimer resets and starts; entry is set to 1 (ms sentinel)
Subsequent pollsEntry is updated to now − startTime (ms)
Flow advances past this stepNo more updates until the step is re-entered

TransferBlockWhenExecutingFlow

Declares that TM transfers targeting this controller's location should be skipped while the flow is in the Executing state. The framework automatically releases the block when the flow transitions back to Idle (via Handler.Done()).

Constructor:

TransferBlockWhenExecutingFlow(string tmName)
ParameterTypeDescription
tmNamestringName of the Transfer Module to block (e.g. "TM1")

Apply this attribute to the [Flow] class — not to individual step methods. Multiple attributes are allowed for flows that must block more than one TM.

[TransferBlockWhenExecutingFlow] replaces manual SetBusy(true/false) calls. The scheduler's isSkip callback queries Port.IsTransferBlocked(tmName, location) on every transfer request; if true, the request is skipped and the next pending transfer is evaluated instead.

[Controller]
public class StageController
{
// TransferBlockWhenExecutingFlow prevents TM1 from putting a new wafer
// to this Stage while the current process is running.
[Flow(Flows.Stage_Process), TransferBlockWhenExecutingFlow("TM1")]
public class StageProcessFlow
{
[Handler]
public IFlowHandler Handler { get; set; } = null!;

[FlowStep(0)]
public void CheckStatus(StageModel m) { Handler.Next(); }

[FlowStep(1)]
public void WaitDone(StageModel m)
{
if (notDone) return;
Handler.Done(); // block is released automatically here
}
}
}

Combine with [TransferCompletedAfterCall] when the flow is started by a robot Put event:

// AlignerAlignFlow starts when TM1 completes a Put to "Aligner".
// While executing, TM1 is blocked from scheduling any further transfer to "Aligner".
[Flow(Flows.Aligner_Align),
TransferCompletedAfterCall("TM1", "Aligner"),
TransferBlockWhenExecutingFlow("TM1")]
public class AlignerAlignFlow
{
[Handler]
public IFlowHandler Handler { get; set; } = null!;

[FlowStep(0)]
public void StartAlignment(AlignerModel m) { Handler.Next(); }

[FlowStep(1)]
public void WaitAlignDone(AlignerModel m)
{
if (notDone) return;
Handler.Done(); // releases block and signals scheduler to schedule Get
}
}

Equipment isSkip wiring:

// In the OnRequestTransfer handler, skip the transfer if the target is blocked.
private static bool isSkip(DualArmAction args)
=> Port.IsTransferBlocked("TM1", args.Target.Name);

Document Attributes

Attributes used for extracting data from Excel/Word documents and generating .page files and C# constant classes.

AttributeTargetInjected TypeArgumentsDescription
[Document]classkeySpecifies source Excel/Word document path
[Save]methodfilenameSpecifies output file paths (.page, .cs)
[ColumnHeader]propertyheaderMaps to Excel column header
[EntryKey]propertyDesignates primary key column
[EntryProperty]propertyDesignates additional property column
[EntryDataType]propertySpecifies SECS data type

Document + Save

Converts an Excel/Word document into a .page file and a C# constants class. This is the approach used in the equipment project to generate device/io.page from IO_Map Ver2.2.docx.

Constructors:

Document(string key)
Save(params string[] filename)
ColumnHeader(string header)
AttributeParameterTypeDescription
[Document]keystringDocument category key or source file path; used to identify the document source
[Save]filenamestring[]One or more output file paths — typically a .page file and a .cs constants file
[ColumnHeader]headerstringExcel column header name to map to this property; omit to match by property name

Step 1: Define a model class that maps Excel columns to .page entry fields.

public class IOModel
{
[ColumnHeader, EntryProperty]
public string Pin_No { set; get; } = string.Empty;

[ColumnHeader, EntryProperty]
public string Port_NO { set; get; } = string.Empty;

[ColumnHeader, EntryKey] // This column becomes the entry Name
public string Description { set; get; } = string.Empty;

[ColumnHeader, EntryProperty]
public string Model { set; get; } = string.Empty;

[ColumnHeader, EntryProperty]
public string Bit_On { set; get; } = string.Empty;

[EntryDataType] // Default data type for all entries
public string DataType { set; get; } = "enum.OffOn";
}
AttributeRole
[ColumnHeader]Maps property to an Excel column by name
[EntryKey]Designates this column as the entry name (first field in .page)
[EntryProperty]Includes this column in the property:{...} JSON
[EntryDataType]Sets the data type field (second field in .page)

Step 2: Define the document converter with [Document] and [Save].

[Document(@"D:\PORT\SampleArduinoLib\equipment\port\IO_Map Ver2.2.docx")]
public class IODocument
{
[Save(@"D:\PORT\SampleArduinoLib\equipment\port\device\io.page",
@"D:\PORT\SampleArduinoLib\equipment\port\io1.cs")]
public Document<IOModel> Convert(Document<IOModel> doc)
{
doc.ForEach(v => v.DataType = "enum.OffOn"); // Set all entries to enum type
doc.ForEach(v => v.Package = "DigitalIO.DI"); // Link all to DigitalIO.DI package
return doc;
}
}

Step 3: Register in your MainWindow to trigger conversion.

Port.Add<IODocument>(@"D:\PORT\SampleArduinoLib\equipment\port\IO_Map Ver2.2.docx");

Generated io.page:

Main_CDA_Pressure_Switch_Check enum.OffOn pkg:DigitalIO.DI property:{"Pin_No":"1","Port_NO":"X01.00","Model":"ISE40A-C6-R-F","Bit_On":"Sensing"}
Door_Lock_On_Check enum.OffOn pkg:DigitalIO.DI property:{"Pin_No":"6","Port_NO":"X01.05","Model":"G7SA-2A2B","Bit_On":"Lock"}
FFU1_Normal_Status enum.OffOn pkg:DigitalIO.DI property:{"Pin_No":"15","Port_NO":"X01.14","Model":"MS-FC300","Bit_On":"Normal"}

Generated io1.cs:

// Auto-generated by Port. Do not edit manually.
namespace equipment
{
public static class Io1
{
public const string Main_CDA_Pressure_Switch_Check = "Main_CDA_Pressure_Switch_Check";
public const string Door_Lock_On_Check = "Door_Lock_On_Check";
public const string FFU1_Normal_Status = "FFU1_Normal_Status";
// ...
}
}

For more detailed usage, see the .NET Integration Guide.


FileSender Attributes

Attributes used for declaring QUIC-based file transfer handler classes.

AttributeTargetInjected TypeArgumentsDescription
[FileSender]classRegisters class as a QUIC file transfer handler container
[FileSenderHandler]propertyIFileSenderHandlerInjects the handler for configuration and file operations

FileSender

[FileSender] marks a class as a QUIC file transfer handler. Register it with Port.Add<T>(key) — the platform injects IFileSenderHandler into [FileSenderHandler] properties and calls [Preset] methods before opening the connection.

Constructors:

FileSender()
FileSenderHandler()
// Server — receives files
[FileSender]
public class FileReceiveServer
{
[FileSenderHandler]
public IFileSenderHandler handler { get; set; } = null!;

[Preset]
private void Preset()
{
handler.SetMode(FileSenderMode.Server);
handler.SetHost("0.0.0.0");
handler.SetPort(5000);
handler.SetSaveDirectory(@"C:\Received");
handler.OnFileReceived += (n, fn, fp, fs) =>
Console.WriteLine($"[{n}] {fn} saved to {fp}");
}
}

// Client — sends files with Certificate Pinning
[FileSender]
public class FileSendClient
{
[FileSenderHandler]
public IFileSenderHandler handler { get; set; } = null!;

[Preset]
private void Preset()
{
handler.SetMode(FileSenderMode.Client);
handler.SetHost("192.168.1.100");
handler.SetPort(5000);

// Pin the server cert obtained out-of-band
byte[] cert = File.ReadAllBytes(@"server.cer");
handler.SetPinnedCert(cert);

handler.OnEvent += (n, e, d) => Console.WriteLine($"[{n}] {e}: {d}");
}
}

Port.Add<FileReceiveServer>("file_server");
Port.Add<FileSendClient>("file_client");
Port.Run();

IFileSenderHandler members:

MemberDescription
SetMode(FileSenderMode)FileSenderMode.Server (receive) or FileSenderMode.Client (send)
SetHost(host)Server: bind address (e.g. "0.0.0.0"). Client: target server address
SetPort(port)QUIC port number
SetSaveDirectory(path)Directory to save received files (server mode only)
Open()Start server or prepare client endpoint; returns ERROR_CODE
Close()Stop server or disconnect client; returns ERROR_CODE
SendFile(filePath)Send a single file (loads into memory); returns 0 on success
SendFileMmap(filePath)Send a large file using chunked streaming (recommended for > 100 MB)
SendFilesParallel(filePaths)Send multiple files via parallel QUIC streams
GetServerCert()Returns server's DER certificate after Open() (server mode)
SetPinnedCert(certDer)Pin expected server certificate before Open() (client mode)
SetLogger(rootPath)Enable hourly-rotated log files
WriteLog(message)Write a custom entry to the log
OnProgressFired periodically with transfer progress (name, fileName, transferred, total, percent)
OnFileReceivedFired when a file is fully received — server mode only (name, fileName, filePath, fileSize)
OnEventFired on connection and transfer state changes (name, eventType, description)

For the full protocol guide, event types, and error codes, see FileSender.