portdic 1.1.97

dotnet add package portdic --version 1.1.97
                    
NuGet\Install-Package portdic -Version 1.1.97
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="portdic" Version="1.1.97" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="portdic" Version="1.1.97" />
                    
Directory.Packages.props
<PackageReference Include="portdic" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add portdic --version 1.1.97
                    
#r "nuget: portdic, 1.1.97"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package portdic@1.1.97
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=portdic&version=1.1.97
                    
Install as a Cake Addin
#tool nuget:?package=portdic&version=1.1.97
                    
Install as a Cake Tool

Quick Start

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


1. Converting Documents to Pages

1.1 What is a Page?

A Page is the fundamental data definition unit in the Port system. A single .page file is a collection of related Entries. Once pushed to the Port server, those entries are created in the in-memory data store.

Page = a list of Entries belonging to one functional unit (e.g. IO, Sensor, Motor)

Pages can be generated automatically from external documents (.docx, .xlsx, .csv), or defined directly in C# using the [Page] attribute.


1.2 Page Structure and Role

.page File Syntax
[EntryKey]  [DataType]  [pkg:PackageName]  [property:{...}]
Field Required Description
EntryKey Unique identifier for the entry
DataType Data type (f8, Enum.OnOff, char, etc.)
pkg optional Package API to bind
property optional Per-entry user property (JSON)

Example (io.page):

Bulb1OnOff  Enum.OnOff pkg:IODevice.DI property:{"IO.No":"D0.01","Model":"IODevice"}
Bulb2OnOff  Enum.OnOff pkg:IODevice.DI property:{"IO.No":"D0.02","Model":"IODevice"}
Bulb1Temp   f8         pkg:IODevice.AI property:{"IO.No":"A0.01","Model":"IODevice"}
Bulb2Temp   f8         pkg:IODevice.AI property:{"IO.No":"A0.02","Model":"IODevice"}
Entry Structure

An Entry is the smallest data unit in the Port system. Each entry has:

  • Key — fully-qualified identifier (category.entryName, e.g. Bulb1.OnOff)
  • DataType — type of the value (numeric, enum, char, etc.)
  • Value — current value held in memory (real-time state)
  • Property — supplementary config such as hardware address or unit (JSON)

SECS-compatible data types:

Type Description
f4 / f8 Floating point (4 / 8 bytes)
i1 ~ i8 Signed integer
u1 ~ u8 Unsigned integer
A(n) ASCII n characters
string Variable-length UTF-8 string (max 255 bytes)
Enum.XXX Enumeration referencing a pre-defined enum
bool Boolean
Local Entry Definitions (category-scoped)

Logical setpoints with no corresponding hardware document entry (e.g. a target temperature) are defined in a separate .page file inside the category folder:

bulb1/.page:

TargetTemp  f8

1.3 Converting a Document to a Page

The framework translates specification tables into structured data models.

Source Specification

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

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

Define a 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!;
}
Attribute Role
[ColumnHeader] Maps the property to an Excel/Word column by name
[EntryKey] Designates this column as the entry name
[EntryProperty] Includes this column in the property:{...} JSON
Generated .cs File

Port.Document<T> writes a constants class alongside the .page file.

// 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 2: Sample .cs File


1.4 Inline Entry Definitions with Page

For entries that don't come from an external document (e.g. EFEM I/O signals), define them directly in a C# class decorated with [Page].

Auto-generated class (from Port.Pull)

Port.Pull writes a partial class file (e.g. entry.cs) containing every pulled entry as a const string. This file is regenerated on every pull — do not edit it manually.

entry.cs also includes a Defined class that contains all enum definitions stored in the database, generated automatically alongside the entry constants.

// Auto-generated by Port.Pull — do not edit manually.
namespace Portdic
{
    public partial class EFEM
    {
        public const string LP1_Main_Air_i = "EFEM.LP1_Main_Air_i";
        public const string LP2_Cont_o     = "EFEM.LP2_Cont_o";
        // ... all pulled entries ...
    }

    public partial class Defined
    {
        public enum OffOn : int
        {
            Off = 0,
            On  = 1,
        }

        public enum UnkOffOn : int
        {
            Unknown = 0,
            Off     = 1,
            On      = 2,
        }
        // ... all enums from the database ...
    }
}
User-defined extension (CustomEFEM)

Add entries and enum types not yet in the pulled class by creating a separate [Page]-decorated partial class.

Rules:

  • The field value is the enum key — EnumName on [PageEntry] must match it exactly.
  • [PageEnum] fields are pushed before [PageEntry] fields so enum references always resolve.
  • Enum definitions land in app/.enum so port pull keeps them in the right file.
using Portdic;
using Portdic.SECS;

namespace sample.Controller
{
    [Page("EFEM")]
    public partial class CustomEFEM
    {
        // ── Enum declarations ─────────────────────────────────────────
        [PageEnum("Unknown", "Off", "On")]
        public const string UnkOffOn = "UnkOffOn";

        [PageEnum("Unknown", "TurnOff", "TrunOn")]
        public const string UnkTurnOffOn = "UnkTurnOffOn";

        // ── Entry declarations ────────────────────────────────────────
        [PageEntry(PortDataType.Char)]
        public const string LP1_Cont1_o = "EFEM.LP1_Cont1_o";

        [PageEntry(PortDataType.Enum, EnumName = UnkTurnOffOn)]
        public const string LP1_OffOn_o = "EFEM.LP1_OffOn_o";

        // ── Package & Property binding ────────────────────────────────
        // Package: "Name.PropertyName" → pushed as pkg:Name.PropertyName
        // Property: raw JSON string   → pushed as property:{...}
        [PageEntry(PortDataType.Enum, EnumName = "OffOn",
            Package  = "Bulb1.OffOn",
            Property = "{\"MIN\":0,\"MAX\":1}")]
        public const string LP1_BulbOnOff_o = "EFEM.LP1_BulbOnOff_o";
    }
}

Push the class instance before starting the port server:

Port.Push("sample", new CustomEFEM());

1.5 How to Push and Pull Pages

Push — Register Entries with the Server

Three overloads are available depending on the source of the entry data:

Overload Use case
Push(reponame, obj) Push from a [Page]-decorated class instance
Push(reponame, page) Push from a Page returned by Document<T>.NewPage()
Push(repo) Push an entire directory via the port REST API (RepositoryInfo)
// From a [Page]-decorated class (enums + entries)
Port.Push("sample", new CustomEFEM());

// From a document-derived Page (entries only)
Port.Push("sample", ioDoc.NewPage("Device"));
Pull — Reconstruct Files from the DB

Port.Pull calls port pull {reponame} via the CLI and writes reconstructed .page, .enum, and related files into a port/ subfolder inside the specified root directory.

// Syntax
Port.Pull(string reponame, string root);

// Example: writes files to D:\sample\Repo\pull\port\
Port.Pull("sample", @"D:\sample\Repo\pull\");

port.exe is resolved from the parent of %PortPath% first; if not found, it falls back to the system PATH.

Full Initialization Pattern (#if DEBUG)

The recommended pattern in a #if DEBUG block synchronizes the DB with your latest class definitions before loading the repository at runtime:

#if DEBUG
// 1. Ensure project root exists
Port.Repository.New(@"D:\sample\Repo\pull\", "sample");

// 2. Convert external document to entries
var ioDoc = Port.Document<IOModel>(@"C:\Users\admin\Documents\IO.docx");
ioDoc.Where(v => v.Key.Contains("OnOff")).ToList()
     .ForEach(v => v.DataType = "Enum.OnOff");
ioDoc.Where(v => v.Key.Contains("Temp")).ToList()
     .ForEach(v => v.DataType = "f8");

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

// 3. Push inline-defined entries (enums + EFEM signals)
Port.Push("sample", new CustomEFEM());

// 4. Push document-derived entries
Port.Push("sample", ioDoc.NewPage("Device"));

// 5. Reconstruct .page/.enum files from DB
Port.Pull("sample", @"D:\sample\Repo\pull\");
#endif

2. Mapping Pages to Models

2.1 What is a Model?

A Model is the data-binding layer that connects Port Entries to C# properties.

  • Declare the class with the [Model] attribute.
  • Each property is linked to a specific Entry via [ModelBinding].
  • Controllers and Flows read and write Entry values through the Model.

2.2 Relationship Between Model and Page

.page file (Entry definitions)
    ↓  Push
Port in-memory DB (Entry values)
    ↕  ModelBinding
Model (C# property ↔ Entry mapping)
    ↕
Controller / Flow (business logic)

The Page is the Single Source of Truth; the Model is a type-safe view of that data in code.


2.3 How to Map a Model to a Page

Use the [ModelBinding(instanceKey, entryKey)] attribute.

  • First argument — controller instance key (e.g. "Bulb1", "LP1")
  • Second argument — Entry constant from the auto-generated .cs file
[Model]
public class BulbModel
{
    // Both "Bulb1" and "Bulb2" instances share the same property structure
    [ModelBinding("Bulb1", Io.Bulb1OnOff)]
    [ModelBinding("Bulb2", Io.Bulb2OnOff)]
    public Entry OnOff { get; set; }

    [ModelBinding("Bulb1", Io.Bulb1Temp)]
    [ModelBinding("Bulb2", Io.Bulb2Temp)]
    public Entry Temp { get; set; }

    [ModelBinding("Bulb1", Io.Bulb1TargetTemp)]
    [ModelBinding("Bulb2", Io.Bulb2TargetTemp)]
    public Entry TargetTemp { get; set; }
}

[ModelBinding] attributes can freely mix entries from the auto-generated class and a user-defined [Page] class:

[Model]
public class LoadportModel
{
    // Entry added via CustomEFEM
    [ModelBinding("LP1", CustomEFEM.LP1_Cont1_o)]
    // Entry from the auto-generated EFEM class
    [ModelBinding("LP2", EFEM.LP2_Cont_o)]
    public Entry LP_Cont_o { get; set; }

    [ModelBinding("LP1", CustomEFEM.LP1_OffOn_o)]
    public Entry LP_OffOn_o { get; set; }
}

3. Using Models in Controllers and Flows

3.1 What is a Controller?

A Controller is a logic container that holds one or more Flows.

  • Declare it with the [Controller] attribute.
  • Register it with Port.Add<TController, TModel>(instanceKey).
  • The same Controller can be reused across multiple instances (e.g. LP1, LP2).
Port.Add<BulbController, BulbModel>("Bulb1");
Port.Add<BulbController, BulbModel>("Bulb2");
Port.Run();

3.2 What is a Flow?

A Flow is a sequential workflow defined inside a Controller.

  • Declare an inner class with [Flow("FlowName")].
  • Define each step as a method decorated with [FlowStep(order)].
  • Trigger execution externally via Port.Set("Bulb1", FlowAction.Executing).

3.3 Handling a Model Inside a Flow

Receive the Model directly as a method parameter to access Entry values:

[Controller]
public class BulbController
{
    [Flow("BulbOn")]
    public class BulbOn
    {
        [Handler]
        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
            }
        }
    }
}

Start / cancel a Flow:

Port.Set("Bulb1", FlowAction.Executing);   // Start BulbOn Flow
Port.Set("Bulb1", FlowAction.Canceled);    // Cancel

3.4 Application Entry Point (Port.App<T>)

Port.App<T>() is the mandatory first call when using the [Port] attribute-based initialization style. Decorate your application class with [Port] to declare the repository name and pull path, then call Port.App<T>() before any other Port API.

Port.App<T>() reads the [Port] attribute on T, creates the repository via Port.Repository.New(pullPath, reponame), and starts the PortDic instance. All subsequent Port.Push, Port.Pull, and Port.Repository.Load calls depend on this initialization being completed.

Overloads
Signature Description
Port.App<T>() Initializes the Port. Subscribe to Port.OnReady separately to run code when ready.
Port.App<T>(Action onReady) Initializes the Port and registers onReady to fire automatically when the port reaches the Synchronized state. No manual Port.OnReady += needed.
Compact Form — Port.App<T>(Action onReady)

Pass the UI initialization callback directly to Port.App<T>(). The callback is invoked automatically on Synchronized, so there is no need to subscribe to Port.OnReady separately.

[Port("sample")]
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        try
        {
            Port.App<MainWindow>(() => Dispatcher.Invoke(() =>
            {
                // Runs automatically when Port reaches Synchronized state
                vm.StartPolling();
                RefreshDisplay();
            }));

            Port.Add<LoadportController, LoadportModel>("LP1");
            Port.Add<LoadportController, LoadportModel>("LP2");

            Port.Run();
        }
        catch (Exception ex)
        {
            MessageBox.Show($"{ex.Message}");
        }
    }
}
Traditional Form — Port.App<T>() + manual Port.OnReady

Use this form when you need to store the handler reference (e.g. to unsubscribe on close).

[Port("sample")]
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        try
        {
            Port.App<MainWindow>();

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

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

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

            Port.Push("sample", new CustomEFEM());
            Port.Push("sample", ioDoc.NewPage("Device"));
            Port.Pull("sample", @"D:\PORT\SampleArduinoLib\sample\Repo\pull\");
#endif

            Port.Add<LoadportController, LoadportModel>("LP1");
            Port.Add<LoadportController, LoadportModel>("LP2");

            Port.OnReady += Port_OnReady;
            Port.Run();
        }
        catch (Exception ex)
        {
            MessageBox.Show(
                $"{ex.Message}\n\nInner: {ex.InnerException?.Message}\n\nStack: {ex.StackTrace}");
        }
    }

    private void Port_OnReady(object? sender, EventArgs e)
    {
        Dispatcher.Invoke(() =>
        {
            vm.StartPolling();
            RefreshDisplay();
        });
    }
}

4. Handlers

4.1 Handler Types

Interface Purpose
IFlowHandler Basic Flow progression control (Next())
IFlowWithModelHandler<T> Flow event subscriptions that carry the Model
ISchedulerHandler<T> Transfer-completion scheduling

4.2 Handler Roles and Usage

IFlowHandler — Basic Progression Control
[Handler]
public IFlowHandler handler { get; set; } = null!;

handler.Next();   // Advance to the next FlowStep
handler.Done();   // Synchronously force the Flow to Idle
IFlowWithModelHandler<T> — Event-Driven Model Access

IFlowCACD<T> is the recommended interface for equipment transfer flows. Declare the handler as IFlowWithModelHandler<T> to gain access to flow lifecycle events that carry the model. Subscribe in [Preset] — subscriptions survive across all executions of the same flow key.

[Controller]
internal class WTRController
{
    [Flow("Pick")]
    public class Pick : IFlowCACD<WTRCommModel>
    {
        [Handler]
        public IFlowWithModelHandler<WTRCommModel> handler { set; get; } = null!;

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

        // Preset() runs once at registration time.
        // Event subscriptions made here are preserved for every subsequent execution.
        [Preset]
        public void Preset()
        {
            handler.SetLogger(@"D:\log");
            handler.OnFlowFinished += (s, e) =>
                scheduler.TransferCompleted(e.Model.SelectedArm);
        }

        public void CheckStatus(WTRCommModel m) { Task.Delay(300).Wait(); handler.Next(); }
        public void Action(WTRCommModel m)      { handler.Next(); }
        public void CheckAction(WTRCommModel m) { handler.Next(); }
        public void Done(WTRCommModel m)        { handler.Done(); }
    }
}
handler.Next() vs handler.Done()
Method Behaviour
handler.Next() Advances to the next step via the normal state machine
handler.Done() Synchronously forces the flow to Idle before returning, then fires OnFlowFinished

Use handler.Done() in the last step when a sibling flow must start inside the OnFlowFinished callback. Using handler.Next() there risks an AlreadyExecutingFlowException because the flow has not yet transitioned to Idle when the callback fires.


4.3 Handler Property Table

IFlowCACD<T> — Standard 4-Step Flow

The recommended interface for equipment transfer flows. Step order is fixed:

Step Method Description
0 CheckStatus Verify preconditions before acting
1 Action Execute the physical operation
2 CheckAction Confirm the operation completed correctly
3 Done Finalize — call handler.Done() to close the flow
IFlowWithModelHandler<T> Lifecycle Events
Event When Fired Args
OnFlowFinished Flow reached the Done step and completed FlowFinishedWithModelArgs<T> — Model, timing, step records
OnFlowOccurred A step transition occurred PortFlowOccurredWithModelArgs<T> — Model, step status
OnFlowIssue Flow stopped due to an alarm PortFlowIssueWithModelArgs<T> — Model, alarm code

All event args expose a Model property that is the singleton model instance bound to the flow key, so reading e.Model.Target.Value always reflects the current shared-memory value.


5. Calling Packages

5.1 What is a Package?

A Package is a reusable device-driver module that bridges a physical device (Bulb, Heater, IO card, etc.) with Port Entries.

  • Link a package in a .page file using the pkg:PackageName.PropertyName syntax.
  • Declare the C# class with the [Package] attribute.
  • When an Entry value changes, the matching property setter on the package is called automatically.

5.2 How to Call Package APIs

Linking a Package in a .page File
Bulb1OnOff  Enum.OnOff  pkg:Bulb.OffOn  property:{"IO.No":"D0.01"}
Bulb1Temp   f8          pkg:Bulb.Temp   property:{"IO.No":"A0.01"}
Linking a Package from a [Page] Class

Use the Package parameter of the [PageEntry] attribute:

[Page("EFEM")]
public partial class CustomEFEM
{
    // Package: "Name.PropertyName" → pushed as pkg:Name.PropertyName
    // Property: raw JSON string   → pushed as property:{...}
    [PageEntry(PortDataType.Enum, EnumName = "OffOn",
        Package  = "Bulb1.OffOn",
        Property = "{\"MIN\":0,\"MAX\":1}")]
    public const string LP1_BulbOnOff_o = "EFEM.LP1_BulbOnOff_o";
}
Implementing a Package Class
[Package]
public class Bulb
{
    [Logger]
    public ILogger Logger { get; set; }

    [Property]
    public IProperty Property { get; set; }

    [Valid("Device not connected")]
    public bool Valid() => serialPort.IsOpen;

    // Setter is called automatically when the Entry value changes
    [API(EntryDataType.Enum)]
    public string OffOn
    {
        set { Logger.Write($"[INFO] Bulb OffOn → {value}"); }
        get => _offOn;
    }

    private string _offOn = "Off";
    private SerialPort serialPort = new SerialPort();
}

Push the [Page] class before Port.Run():

Port.Push("sample", new CustomEFEM());

6. Port.Set

Port.Set writes a value to the specified Entry in the in-memory DB immediately. If a Package is bound to that Entry, its setter is invoked, triggering the physical operation.

Basic Usage

// Write an Entry value
Port.Set("Bulb1.OnOff", "On");
Port.Set("Bulb1.TargetTemp", "85.0");

// Trigger / cancel a Flow
Port.Set("Bulb1", FlowAction.Executing);
Port.Set("Bulb1", FlowAction.Canceled);

Set via Model Property

// Inside a Flow — set directly through the model parameter
model.OnOff.Set("On");
model.TargetTemp.Set("80.0");

Set via IFlowModel (@ Binding)

[FlowModel]
public IFlowModel model { get; set; }

model.Set("@OnOff", "On");   // @ prefix references a ModelBinding key

7. Port.Get

Port.Get reads the current value of the specified Entry from the in-memory DB. It always returns the latest value and supports high-performance in-memory access.

Basic Usage

// Read an Entry value
string onOff = Port.Get("Bulb1.OnOff");   // → "On" or "Off"

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

Get via Model Property

// Access the current value through the Entry.Value property
double temp   = (double)model.Temp.Value;
string status = (string)model.OnOff.Value;

Get via IFlowModel (@ Binding)

var value = model.Get("@Temp");   // Reads the Entry bound to the ModelBinding key

8. Controlling Set/Get with Rule Scripts

A Rule Script defines write guards and periodic automation using .rule files stored in the app/ directory (e.g. app/.rule). Two rule types are available: set and get.


8.1 set Rule — Write Guard

A set rule is evaluated synchronously whenever Port.Set is called on the matching key. It acts as a gate: if the action condition is not satisfied, the write is blocked and an error is returned.

Syntax
set("write condition", "allow condition")
Argument Role
First — write condition Specifies which write attempt triggers this rule.<br>Bare key (Bulb1.OnOff) matches any write;<br>key+op+value (Bulb1.OnOff == Off) matches only that value.
Second — allow condition Boolean expression evaluated against the current memory state.<br>If true → write is allowed. If false → write is blocked.
Example
// Allow turning Bulb1 off only when temperature is safe (>= 80)
set("Bulb1.OnOff == Off", "Bulb1.Temp >= 80")

// Block any write to Bulb2.OnOff while Bulb1 is still On
set("Bulb2.OnOff", "Bulb1.OnOff == Off")

set rules evaluate the allow condition against the state before the write. A write is blocked if any matching rule's allow condition is false.


8.2 get Rule — Periodic Automation

A get rule runs in the background every second. When the condition becomes true, the listed assignments execute once (one-shot). They will not re-trigger until the condition first goes false and then becomes true again.

Syntax
get("condition", "key1=value1; key2=value2; ...")
Argument Role
First — condition Boolean expression evaluated against current memory state
Second — assignments Semicolon-separated key=value pairs to write when condition is true
Example
// When Bulb1 temperature reaches 80 or above, turn it off automatically
get("Bulb1.Temp >= 80", "Bulb1.OnOff=Off")

// When both temperatures are in range, reset both bulbs
get("(Bulb1.Temp >= 0) && (Bulb2.Temp >= 0)", "Bulb1.OnOff=Off; Bulb2.OnOff=Off")

The condition must transition false → true to re-execute. Once fired, assignments will not repeat until the condition resets.


8.3 Operators

Comparison operators (both rule types):

Operator Description
== Equal (string or numeric)
> Greater than
< Less than
>= Greater than or equal
<= Less than or equal

Logical operators (combine conditions with parentheses):

Operator Description
&& Logical AND
\|\| Logical OR

Always wrap each sub-condition in parentheses when combining:

(Bulb1.Temp >= 80) && (Bulb2.Temp >= 80)
(Bulb1.OnOff == Off) || (Bulb2.OnOff == Off)

8.4 Complete .rule File Example

// ── SET rules (write guards) ──────────────────────────────────────────
// Block turning Bulb1 off unless temperature has reached the target
set("Bulb1.OnOff == Off", "Bulb1.Temp >= 80")

// Block turning Bulb2 off unless both bulbs are warm enough
set("Bulb2.OnOff == Off", "(Bulb1.Temp >= 80) && (Bulb2.Temp >= 80)")

// ── GET rules (periodic automation) ──────────────────────────────────
// Auto-turn off when temperature exceeds limit
get("Bulb1.Temp >= 100", "Bulb1.OnOff=Off")

// Reset both bulbs once temperatures are both safe
get("(Bulb1.Temp >= 0) && (Bulb2.Temp >= 0)", "Bulb1.OnOff=Off; Bulb2.OnOff=Off")

8.5 Condition Monitoring Inside a Flow

For logic that depends on flow state, express conditions directly inside Flow steps instead of a rule file:

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

8.6 Data Flow

Port.Set("Bulb1.OnOff", "Off") called
    ↓
set rule evaluated (write guard)
    ├─ allow condition true  → write proceeds → Package setter called → Hardware responds
    └─ allow condition false → write BLOCKED, error returned

Every 1 second (background)
    ↓
get rule condition evaluated
    ├─ condition true (first time) → assignments executed → Port.Set called internally
    └─ condition false or already fired → no action

Rule Scripts operate independently of Flows. Conditions can be modified at any time by editing the .rule file — no code recompilation required.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on portdic:

Package Downloads
Port.SDK

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.97 91 5/25/2026
1.1.96 113 4/26/2026
1.1.95 109 4/16/2026
1.1.94 118 4/14/2026
1.1.93 99 4/12/2026
1.1.92 112 4/9/2026
1.1.91 113 4/8/2026
1.1.90 117 4/6/2026
1.1.89 103 4/5/2026
1.1.88 111 4/1/2026
1.1.87 107 3/31/2026
1.1.86 113 3/29/2026
1.1.85 107 3/24/2026
1.1.84 106 3/23/2026
1.1.83 104 3/18/2026
1.1.82 104 3/17/2026
1.1.81 106 3/15/2026
1.1.80 115 3/10/2026
1.1.79 103 3/9/2026
1.1.78 106 3/2/2026
Loading failed