Home / FactoryTalk Optix / Getting Started
Introduction to C# NetLogic in FactoryTalk Optix: A Controls Engineer’s Guide
For controls engineers accustomed to Ladder Logic, Function Block Diagrams, and Structured Text in Studio 5000, the prospect of writing C# code inside an HMI application can initially feel like crossing into unfamiliar IT territory. Legacy HMI platforms have conditioned programmers to rely on proprietary expression editors, sluggish Visual Basic scripts, or periodic background macros that are difficult to debug and maintain.
FactoryTalk Optix NetLogic fundamentally changes how custom logic is executed at the HMI and industrial edge layer. Rather than bolting a scripting engine onto the side of a graphics terminal, FactoryTalk Optix is built natively on .NET and an OPC UA Object-Oriented Information Model. NetLogic gives you direct, compiled C# access to every node, variable, UI widget, and communication driver in your project—both during engineering design and at live runtime.
1. What Is NetLogic? (Design-Time vs. Runtime Execution)
In FactoryTalk Optix, a NetLogic script is a C# class that attaches directly to a node in your project tree. Because Optix treats everything in the project—from a simple integer tag to a complex graphical screen or PLC driver—as an OPC UA node, C# scripts can read, modify, create, or delete objects programmatically.
NetLogic is divided into two distinct execution modes:
| Feature | Design-Time NetLogic | Runtime NetLogic |
| When It Runs | Inside FactoryTalk Optix Studio while you are building the project. | On the target OptixPanel or Edge/IPC Runtime while the application is live. |
| Base Class | BaseDesignTimeNetLogic | BaseNetLogic |
| Primary Use Cases | Automating repetitive engineering tasks: bulk-generating UI screens from a CSV file, auto-building alarm lists, or auditing project nodes. | Executing live application logic: custom calculations, complex file I/O, REST API/Webhook calls, database queries, and dynamic UI manipulation. |
| How It Is Triggered | Right-clicking the script in Optix Studio and selecting Execute (or clicking a custom action button). | Automatically when its parent object loads (Start()), periodically via timers, or on-demand via UI button clicks and PLC tag transitions. |
2. The PLC Programmer’s Rosetta Stone: Mapping Studio 5000 to C# NetLogic
The fastest way to master Runtime NetLogic is to map its core components to the Studio 5000 concepts you already use every day.
The #1 Paradigm Shift: Unlike a PLC’s Continuous Task that scans top-to-bottom every few milliseconds, NetLogic is strictly event-driven. Your C# code only executes when a lifecycle event occurs, a timer fires, or a tag/UI event calls one of your methods.
| Studio 5000 / PLC Concept | FactoryTalk Optix C# NetLogic Equivalent | How It Works in Optix |
First-Scan Bit (S:FS) / Power-Up Handler | public override void Start() | Executes once when the NetLogic object (or the UI container it is attached to) initializes at runtime. |
| Controller Power-Down / Mode Change | public override void Stop() | Executes once when the application stops or when the parent screen/popup closes. Used to clean up timers and tasks. |
| AOI Input/Output Parameters | Child Variables under the NetLogic Node + LogicObject.GetVariable("Name") | Exposes configurable parameters in the Studio Property Panel so you never have to hardcode tag paths or constants inside C#. |
| JSR (Jump to Subroutine) / HMI Button Action | [ExportMethod] attribute | Exposes a public C# method to the Optix Studio UI so buttons, alarms, or tag transitions can call it directly. |
Timer On-Delay (TON) / Periodic Task | DelayedTask / PeriodicTask | Executes C# logic after a preset delay or on a recurring millisecond interval without freezing the HMI user interface. |
One-Shot Rising (ONS) | VariableChange Event / VariableTransitionEventDispatcher | Fires a C# method a single time when a monitored variable transitions (e.g., from 0 to 1). |
3. Anatomy of a Runtime NetLogic Script
When you right-click the NetLogic folder in FactoryTalk Optix Studio and select New -> Runtime NetLogic, Optix generates a C# template file and opens it in your configured code editor (such as Visual Studio Code or Visual Studio).
Every Runtime NetLogic script inherits from BaseNetLogic and provides two built-in object references out of the box:
LogicObject: Points directly to the NetLogic node itself in the project tree. Use this to access child variables configured on the script.Owner: Points to the parent node to which the NetLogic script is attached. For example, if you attach a NetLogic script directly inside a customMotorPopupdialog box,Ownerpoints to that specificMotorPopupinstance.
Basic Structural Template
C#
#region Using directivesusing System;using UAManagedCore;using OpcUa = UAManagedCore.OpcUa;using FTOptix.HMIProject;using FTOptix.NetLogic;using FTOptix.UI;using FTOptix.NativeUI;using FTOptix.CoreBase;using FTOptix.Core;#endregionpublic class MyCustomLogic : BaseNetLogic{ public override void Start() { // Code here runs once when the NetLogic node starts Log.Info("MyCustomLogic", "NetLogic started successfully."); } public override void Stop() { // Code here runs once when the NetLogic node stops Log.Info("MyCustomLogic", "NetLogic stopped."); } [ExportMethod] public void CustomAction() { // Code here can be triggered by an HMI Button or Event Dispatcher Log.Info("MyCustomLogic", "CustomAction was triggered!"); }}
Where Does
Log.Info()Go?Think of the
Logclass (Log.Info(),Log.Warning(),Log.Error()) as your built-in diagnostic watch window. The first string argument is a custom category name (usually your script name), and the second is the message. At runtime, these messages print directly to the Output / Log pane at the bottom of FactoryTalk Optix Studio.
4. Reading and Writing Variables: The “AOI Parameter” Pattern
When beginners write their first NetLogic script, they often try to search the entire project tree by hardcoding paths using Project.Current.GetVariable("Model/MyFolder/TankLevel"). While Project.Current works for global lookups, hardcoding project paths makes your C# code fragile—if someone renames a folder in Studio, your script breaks.
Best Practice: Treat Your NetLogic Node Like an AOI
Instead of hardcoding paths, right-click your Runtime NetLogic node in Optix Studio and add Variables directly underneath it as children.
- In Optix Studio, right-click your NetLogic node (
BatchCalculator) and select New -> Variable -> Int32. Name itBatchCount. - Add a second child variable of type Double named
TotalWeight. - Add a third child variable of type String named
StatusMessage.
Now, in the Optix Studio Properties panel, you can dynamically link those child variables to PLC tags or UI labels—just like wiring tags to the input and output pins of a Studio 5000 Add-On Instruction (AOI). Inside C#, you access them cleanly via LogicObject.GetVariable():
C#
public class BatchCalculator : BaseNetLogic{ private IUAVariable batchCountVar; private IUAVariable totalWeightVar; private IUAVariable statusMessageVar; public override void Start() { // Bind C# references to the child variables configured in Studio batchCountVar = LogicObject.GetVariable("BatchCount"); totalWeightVar = LogicObject.GetVariable("TotalWeight"); statusMessageVar = LogicObject.GetVariable("StatusMessage"); } public override void Stop() { // Clean up resources if needed } [ExportMethod] public void AddBatch(doublecurrentBatchWeight) { // 1. Read current values (cast from UAValue to C# types) int currentCount = (int)batchCountVar.Value; double runningWeight = (double)totalWeightVar.Value; // 2. Perform calculation currentCount++; runningWeight += currentBatchWeight; // 3. Write updated values back to the Optix Information Model batchCountVar.Value = currentCount; totalWeightVar.Value = runningWeight; statusMessageVar.Value = $"Batch #{currentCount} logged at {DateTime.Now:HH:mm:ss}"; Log.Info("BatchCalculator", $"Updated Total: {runningWeight:F2} lbs across {currentCount} batches."); }}
Connecting the [ExportMethod] to the HMI Screen
Because AddBatch(double currentBatchWeight) is decorated with [ExportMethod], Optix Studio automatically exposes both the method and its currentBatchWeight input argument in the graphical editor:
- Select a Button on your HMI screen.
- In the Events properties panel under
OnMouseClick, click the + icon. - Browse to
NetLogic -> BatchCalculator -> AddBatch. - Expand the method arguments in the property panel and bind
currentBatchWeightto a numeric entry box or a live scale PLC tag.
5. Handling Time & Background Execution (PeriodicTask, DelayedTask, LongRunningTask)
In a PLC, if you want to delay an action by 3 seconds or run a calculation every 10 seconds, you drop in a TON timer or create a Periodic Task.
In C#, a common beginner mistake is calling System.Threading.Thread.Sleep(3000). Never use Thread.Sleep() in standard NetLogic methods—it halts the main Optix execution thread and will freeze your HMI screen. Instead, FactoryTalk Optix provides three dedicated task classes in UAManagedCore:
DelayedTask(Equivalent to aTONTimer): Executes a C# method once after a specified millisecond delay.PeriodicTask(Equivalent to a Studio 5000 Periodic Task): Executes a C# method repeatedly on a fixed millisecond interval.LongRunningTask: Spins up a dedicated background worker thread for heavy operations (such as parsing a massive CSV file or running a complex database query) so the UI remains completely responsive.
Example: Running a 5-Second Periodic Task
Whenever you create a PeriodicTask, DelayedTask, or LongRunningTask, you must call .Dispose() on it inside the Stop() method so the background timer doesn’t leak memory when the screen closes or the runtime stops:
C#
public class HeartbeatGenerator : BaseNetLogic{ private PeriodicTask heartbeatTask; private IUAVariable counterVar; public override void Start() { counterVar = LogicObject.GetVariable("HeartbeatCount"); // Create a PeriodicTask that calls IncrementHeartbeat every 5000 ms (5 seconds) heartbeatTask = new PeriodicTask(IncrementHeartbeat, 5000, LogicObject); heartbeatTask.Start(); } public override void Stop() { // Always dispose of tasks in Stop() heartbeatTask?.Dispose(); } private void IncrementHeartbeat() { if (counterVar != null) { int val = (int)counterVar.Value; counterVar.Value = (val + 1) % 1000; } }}
6. Critical Pro-Tip: The “Unpolled PLC Tag” Trap (VariableSynchronizer)
To optimize network bandwidth, FactoryTalk Optix only actively polls PLC tags from a controller (such as a ControlLogix or Micro800) when those tags are bound to an active UI screen, configured as an Alarm, or logged by a DataLogger.
If you write a background C# NetLogic script that reads a PLC tag directly from CommDrivers while that tag is not displayed on the currently visible HMI screen, myPlcTag.Value may return stale data (0 or false) because Optix has paused polling for that tag.
To force Optix to keep a PLC tag continuously synchronized for your background C# script, instantiate a VariableSynchronizer inside your Start() method:
C#
private RemoteVariableSynchronizer tagSynchronizer;private IUAVariable plcPumpSpeedTag;public override void Start(){ // Get reference to the PLC tag (or via a Child Variable / Alias) plcPumpSpeedTag = LogicObject.GetVariable("PumpSpeedTag"); // Force Optix to actively poll this PLC tag in the background tagSynchronizer = new RemoteVariableSynchronizer(); tagSynchronizer.Add(plcPumpSpeedTag);}public override void Stop(){ // Stop background polling when the script stops tagSynchronizer?.Dispose();}
5 Golden Rules for FactoryTalk Optix NetLogic
- Use Built-In Optix Features First, C# Second: Don’t write C# code just to change a button’s color or add two tags together—use inline Key-Value Converters and Expression Evaluators for standard UI animations. Save NetLogic for procedural workflows, file management, custom algorithms, and IT/OT integrations.
- Never Hardcode What You Can Parameterize: Expose inputs and outputs as Child Variables underneath your NetLogic node so any engineer can re-link tags in Optix Studio without opening Visual Studio Code.
- Avoid Naming Collisions: Never name your NetLogic node in Optix Studio the exact same name as an existing .NET class or namespace (e.g., avoid naming a node
HttpClient,File, orTimer), as Optix creates a C# class matching the node name. - Always Clean Up in
Stop(): EveryPeriodicTask,DelayedTask,LongRunningTask,RemoteVariableSynchronizer, orVariableChangeevent subscription created inStart()must be disposed of or unsubscribed inStop(). - Log Early and Often: Use
Log.Info(),Log.Warning(), andLog.Error()insidetry / catchblocks so any runtime issues immediately appear with clear messages in the Optix Studio diagnostic log.
Tutorial List
- Getting Started with FactoryTalk Optix
- FactoryTalk Optix Resources
- Optix Runtime Sizing
- Optix Device Sizing
- How to download FactoryTalk Optix Studio
- Building a simple Optix Application
- FactoryTalk Optix Responsive Display
- Deploy Optix Runtime to Windows PC
- Deploy Optix Runtime to Linux Devices
- FactoryTalk View ME to FactoryTalk Optix
- NetLogic in FactoryTalk Optix
