Skip to content

Quick Start

A step-by-step guide to building a .NET equipment application with Port.


1. Overview

The Port Framework aims to enable flexible service architecture by seamlessly integrating physical documentation with system design.

In traditional development environments, we often encounter communication bottlenecks caused by:

  • Inconsistent variable naming between hardware specifications and software code.
  • The manual overhead of re-allocating variables whenever documentation changes.
  • Ambiguous service designs that lead to development delays.

To resolve these issues, Port Framework automates the bridge between your specifications (.docx, .xlsx, *.csv) and implementation (.cs), ensuring a "Single Source of Truth."

2. Document Conversion (DOCX to Code)

The framework translates your specification tables into structured data models.

2.1 Source Specification

Assume a table exists in C:\Users\admin\Documents\IO.docx as follows:

IO.No Description Model
D0.01 Bulb1.OnOff IODevice
D0.02 Bulb2.OnOff IODevice
A0.01 Bulb1.Temp IODevice
A0.02 Bulb2.Temp IODevice

Figure 1: Sample Specification Table

2.2 Define Document Model

Map the table columns to a C# class using attributes.

public class IOModel
{
    [ColumnHeader("IO.No"), EntryProperty]
    public string IONo { get; set; } = null!;

    [ColumnHeader("Description"), EntryKey]
    public string Description { get; set; } = null!;

    [ColumnHeader("Model"), EntryProperty]
    public string Model { get; set; } = null!; 
}

2.3 Execution: Conversion & Generation

The Convert() function processes the document and generates the necessary configuration and constant files.

public void Convert()
{
    try
    {
        if (!Directory.Exists("C:\\Users\\admin\\Documents\\Sample"))
        {
            Port.Repository.New("C:\\Users\\admin\\Documents\\Sample", "sample");
        }

        var doc = Port.Document<IOModel>(@"C:\Users\admin\Documents\IO.docx");

        doc.Where(v => v.Key.Contains("OnOff")).ToList().ForEach(v => v.DataType = "Enum.OnOff");
        doc.Where(v => v.Key.Contains("Temp")).ToList().ForEach(v => v.DataType = "f8");

        if (doc.Count > 0)
        {
            doc.New(@"C:\Users\admin\Documents\sample\port\device\io.page");
            doc.New(@"C:\Users\admin\Documents\sample\.net\io.cs");
        } 

        Port.Push("C:\\Users\\admin\\Documents\\Sample\\port", "sample", "v0.0.1");
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

3. Project Configuration

3.1 Initialize Project

Port.Repository.New("C:\\Users\\admin\\Documents\\Sample", "sample");

3.2 Page Files (.page)

The .page file is a collection of Entry definitions. Packages are referenced using the pkg: prefix.

[io.page]

Bulb1OnOff                      Enum.OnOff  property:{"IO.No":"D0.01","Model":"IODevice"}
Bulb2OnOff                      Enum.OnOff  property:{"IO.No":"D0.02","Model":"IODevice"}
Bulb1Temp                       f8          property:{"IO.No":"A0.01","Model":"IODevice"}
Bulb2Temp                       f8          property:{"IO.No":"A0.02","Model":"IODevice"}
Figure 2: Sample .page File

3.3 Custom User Entries

You can define additional entries that are not part of the hardware document (e.g., logic setpoints) by creating localized .page files.

[bulb1/.page]

TargetTemp  f8

3.4 Generated Code Files (.cs)

The .cs file contains constant definitions for the entries, which can be used in your application code. [io.cs]

// Auto-generated by Port. Do not edit manually.

namespace sample
{
    public static class Io
    {
        public const string Bulb1OnOff = "Bulb1OnOff";
        public const string Bulb2OnOff = "Bulb2OnOff";
        public const string Bulb1Temp = "Bulb1Temp";
        public const string Bulb2Temp = "Bulb2Temp";
    }
}
Figure 3: Sample .cs File


4. MCF Pattern (Model, Controller, Flow)

The MCF pattern decouples data (Model) from logic (Flow) via a centralized Controller.

4.1 Binding the Model

Models use [Binding] attributes to link software properties to the Entry keys defined in your documents.

[Model]
public class BulbModel
{
    // Bind multiple instances (Bulb1, Bulb2) to the same property structure
    [Binding("Bulb1", io.Bulb1_OnOff)]
    [Binding("Bulb2", io.Bulb2_OnOff)]
    public Entry OnOff { get; set; }

    [Binding("Bulb1", io.Bulb1_Temp)]
    [Binding("Bulb2", io.Bulb2_Temp)]
    public Entry Temp { get; set; }

    [Binding("Bulb1", io.Bulb1_TargetTemp)]
    [Binding("Bulb2", io.Bulb2_TargetTemp)]
    public Entry TargetTemp { get; set; }
}

4.2 Defining Logic with Flows

A Controller contains Flows, which are sequences of FlowSteps.

[Controller]
public class BulbController
{ 
    [Flow("BulbOn")]
    public class BulbOn
    {
        [FlowHandler]
        public IFlowHandler handler { get; set; } = null!;

        [FlowStep(0)] // Validation Step
        public void CheckInitialState(BulbModel model)
        {
            if (model.Temp.Value <= 100)
            {
                handler?.Next();
            }
        }

        [FlowStep(1)] // Action Step
        public void TurnOn(BulbModel model)
        {
            model.OnOff.Set("On");
            handler?.Next();
        }

        [FlowStep(2)] // Monitoring Step
        public void MonitorTemperature(BulbModel model)
        {
            if (model.Temp.Value >= model.TargetTemp.Value)
            {
                model.OnOff.Set("Off");
                handler?.Next(); // Marks Flow as Completed
            }
        }
    } 
}

5. Runtime & Repository Management

5.1 Loading the Repository

Synchronize the repository and initialize the runtime components.

if (Port.Repository.Load("sample"))
{
    try
    {
        // Add controllers with unique identifiers
        Port.Add<BulbController, BulbModel>("Bulb1");
        Port.Add<BulbController, BulbModel>("Bulb2");

        Port.Run();
        Port.OnReady += (s, e) => { /* Initialization Logic */ };
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Initialization Failed: {ex.Message}");
    }
} 

5.2 Fast In-Memory Access

Access and control entries in real-time with high performance.

// Direct Entry Access
if (Port.Get("Bulb1.OnOff") == "On")
{
    Port.Set("Bulb1.OnOff", "Off");
}

// Flow Control
public void StartBulb1() => Port.Set("Bulb1", FlowAction.Executing);
public void StopBulb1()  => Port.Set("Bulb1", FlowAction.Canceled);