Skip to content

ControlSpace

Engineering & Sciencev0.1.0 MIT

A PLC engineering workspace with a virtual controller, in your browser

ControlSpace is an independent automation-engineering workspace that implements a TIA Portal-style workflow in C#, .NET 10 and Uno Platform with shared Skia rendering. Edit PLC tag tables, ladder logic, an SCL subset, hardware topology and HMI screens, then compile and run them on a deterministic virtual PLC. The engineering model, compiler, simulator, persistence, rendering, controls and workbench ship as eight reusable libraries.

8reusable libraries
5PLC data types: BOOL, INT, DINT, REAL, TIME
10,000tag project verified in the virtualized grid
2,048trace snapshots of bounded history
ControlSpacewieslawsoltes.github.io/ControlSpace/
ControlSpace running in a browser
Highlights

What makes ControlSpace special

Familiar if you know Siemens TIA Portal-style PLC engineering suites — but original, local‑first and written end‑to‑end in C#.

TIA Portal-style project view

Compact menus and toolbars, a project tree, central editors, task cards, a Properties/Info/Diagnostics inspector and an open-editor bar.

Ladder logic editor

Series contacts and parallel paths, NO/NC and edge contacts, comparisons, set/reset coils, TON/TOF/TP timers, CTU counters and MOVE.

Typed SCL subset

A bounded parser for assignments, IF/THEN/ELSE, arithmetic, comparisons and Boolean logic. Unsupported syntax is rejected, never evaluated.

Deterministic virtual PLC

Run, stop, reset and single-scan with input images, virtual forcing, scan-order execution, rollback on faults and a bounded trace.

Editable tag tables

A virtualized grid with inline editing, rectangular clipboard paste, validation, undo/redo, filtering, sorting and reference-safe renaming.

HMI screens

Labels, lamps, buttons, numeric displays, tanks and gauges bound to tags, with per-screen navigation and virtual-input buttons.

Shared Skia rendering

Editors draw into the host canvas via SKCanvasElement, cull offscreen ladder networks and return hit regions for interaction.

Simulation only, by design

No controller transport or hardware communication. Every execution runs in-process against a virtual controller.

0reusable libraries
0lines of C# & XAML
0features catalogued
0.NET projects
0CI workflows
0docs

From the repository

Feature tour

35 capabilities, 7 areas

PLC tags

  • BOOL, INT, DINT, REAL and TIME tags with %I, %Q and %M addressing
  • Overlap and value validation, comments and retain metadata
  • Add and Duplicate allocate unique names and unoccupied marker addresses
  • Delete rejects tags still referenced by LAD, SCL or HMI objects
  • Renaming rewrites symbols across LAD, SCL and HMI sources

Tag-table grid

  • Viewport-recycled grid that only realizes visible rows
  • Inline editing with F2, Enter, Tab and Escape
  • Rectangular tab-separated copy/paste committed as one transaction
  • Column sort, resize, reorder and visibility; filter by name, address, type or comment
  • Optional read-only monitor values from the virtual controller

Ladder and SCL

  • LAD networks of series contacts and parallel paths feeding one output
  • NO/NC and edge contacts, comparisons, assignment/set/reset coils
  • TON/TOF/TP timers, CTU counters and constant MOVE
  • SCL typed assignments, IF/THEN/ELSE/END_IF, arithmetic and Boolean expressions
  • Bounded parser: 1,000,000 characters, 100,000 tokens, nesting depth 64

Virtual PLC

  • Run, stop, reset and single-scan execution
  • Input image and virtual forcing
  • Faults roll back the scan, clear outputs and require explicit reset
  • Cold and warm (retain-aware) reset
  • Bounded trace history with a speed plot

Hardware and HMI

  • Generic controller, panel and remote-I/O views
  • Editable addresses, modules and topology metadata
  • HMI labels, lamps, buttons, numeric displays, tanks and gauges
  • Per-screen navigation, tag bindings, dragging and virtual-input buttons

Workbench

  • Portal view grouping Start, Devices & networks, PLC programming, Visualization and diagnostics
  • Project search that keeps matching ancestors visible
  • Unique editor tabs that can be closed, cycled and reordered
  • Pointer and keyboard pane resizing, collapsible panes and Reset window layout
  • Compact windows switch task cards and project tree to overlay drawers
  • Clicking a compile diagnostic navigates to its source block and position

Projects and persistence

  • Versioned ControlSpace JSON with duplicate-key rejection and import limits
  • CSV tag-table interchange
  • Draft recovery that preserves SCL drafts without compiling them
  • Validated editing transactions with undo/redo
  • Layout preferences stored separately from project files
Integrated playground

Run ControlSpace right here

Open the compiled Uno WebAssembly build and program, compile and simulate PLC logic entirely in your browser. Switch viewports, go fullscreen and tick off the guided tour as you explore.

https://wieslawsoltes.github.io/ControlSpace/
ControlSpace screenshot

Try ControlSpace live

Open the compiled Uno WebAssembly build and program, compile and simulate PLC logic entirely in your browser.

Real C# · Uno WebAssembly · runs locally in your browser
Booting .NET WebAssembly runtime…
Reusable packages

8 libraries you can embed

ControlSpace is assembled from independently reusable libraries, each published on NuGet.org. Hover the graph to trace dependencies; click a node to jump to its package, and open a card for install and usage.

Core 262 lines Languages 284 lines Simulation 340 lines Storage 95 lines Engineering 650 lines Rendering.Skia 387 lines Controls.Uno 1,292 lines Workbench.Uno 1,014 lines App 122 lines
Arrows point from a project to what it references. Bars show relative size. ModelEngineI/OEditorRenderingControlsWorkbench
Install from NuGet

8 packages on NuGet.org

ControlSpace ships as eight MIT-licensed packages on NuGet.org, versioned together and published by release tags with symbol packages (.snupkg) and SourceLink. The six engine packages target net10.0 and have no UI dependency (only ControlSpace.Rendering.Skia needs SkiaSharp); the two Uno packages target net10.0-desktop and net10.0-browserwasm and can be consumed by any Uno Platform host. ControlSpace.App only supplies entry points and platform storage/file pickers and is not published; neither is the JavaScript prototype.

8packages
0.1.0stable · Sep 29, 2026
6plain .NET — no UI framework
Newjust published
bashStart with the foundation package
dotnet add package ControlSpace.Core
xmlEvery package — keep only what you need
<PackageReference Include="ControlSpace.Core" Version="0.1.0" />
<PackageReference Include="ControlSpace.Languages" Version="0.1.0" />
<PackageReference Include="ControlSpace.Simulation" Version="0.1.0" />
<PackageReference Include="ControlSpace.Storage" Version="0.1.0" />
<PackageReference Include="ControlSpace.Engineering" Version="0.1.0" />
<PackageReference Include="ControlSpace.Rendering.Skia" Version="0.1.0" />
<PackageReference Include="ControlSpace.Controls.Uno" Version="0.1.0" />
<PackageReference Include="ControlSpace.Workbench.Uno" Version="0.1.0" />
xmlCentral package management
<!-- Directory.Packages.props -->
<ItemGroup>
  <PackageVersion Include="ControlSpace.Core" Version="0.1.0" />
  <PackageVersion Include="ControlSpace.Languages" Version="0.1.0" />
  <PackageVersion Include="ControlSpace.Simulation" Version="0.1.0" />
  <PackageVersion Include="ControlSpace.Storage" Version="0.1.0" />
  <PackageVersion Include="ControlSpace.Engineering" Version="0.1.0" />
  <PackageVersion Include="ControlSpace.Rendering.Skia" Version="0.1.0" />
  <PackageVersion Include="ControlSpace.Controls.Uno" Version="0.1.0" />
  <PackageVersion Include="ControlSpace.Workbench.Uno" Version="0.1.0" />
</ItemGroup>

Published with NuGet Trusted Publishing from GitHub Actions, with symbols (.snupkg) and SourceLink. Release v0.1.0 ↗

Dependencies follow the real project references: Core ← Languages ← Simulation, Core ← Storage, Languages + Simulation + Storage ← Engineering, Simulation ← Rendering.Skia, Engineering + Rendering.Skia ← Controls.Uno ← Workbench.Uno.

ControlSpace.Core

ModelNo UI depsnet10.0v0.1.0

The typed automation-engineering model: immutable records for tags, instructions, LAD networks, program blocks, devices, links and HMI screens, plus a validator that reports addressing overlaps, invalid values and broken references. Use it on its own to generate, inspect or lint ControlSpace projects. No dependencies and no UI.

bashInstall
dotnet add package ControlSpace.Core
Depends onno other ControlSpace packageUsed byLanguagesSimulationStorageEngineeringRendering.Skia

Key types

  • ControlProject — root record (Tags, Blocks, Devices, Links, Screens, Revision).
  • PlcTag, ProgramBlock, LadderNetwork, Instruction — tag table and LAD/SCL program model.
  • ProjectValidator.Validate — returns Diagnostic records with severity, code and location.
  • ProjectSnapshot.Clone — detached copy of all mutable lists; leaves share immutable records.
  • PlcValues — type range checks, %I/%Q helpers and TIA-style value formatting.
  • DemoProject.Create — the conveyor sample project.

Basic usage

csharpUsage
using ControlSpace.Core;

var project = DemoProject.Create();
project.Tags.Add(new PlcTag("Alarm_Lamp", PlcType.Bool, "%Q0.2", Comment: "Alarm indicator"));

foreach (var d in ProjectValidator.Validate(project))
    Console.WriteLine($"{d.Severity} {d.Code}: {d.Message} ({d.Location})");

var copy = ProjectSnapshot.Clone(project);   // detached lists, shared immutable records
copy.Tags.RemoveAt(0);                       // does not affect `project`
Console.WriteLine(PlcValues.Format(PlcType.Time, 2000));  // T#2000ms

ControlSpace.Languages

EngineNo UI depsnet10.0v0.1.0

Compiles a ControlProject into a typed, interpreter-friendly program: LAD networks become contact paths and outputs, SCL blocks go through a bounded parser (IF/THEN/ELSE, arithmetic, comparisons, Boolean logic; no eval or code generation). Use it standalone to check programs or build other runtimes on the IR. Depends on ControlSpace.Core; no UI.

bashInstall
dotnet add package ControlSpace.Languages
Depends onCoreUsed bySimulationEngineering

Key types

  • ProjectCompiler.Compile — validates and compiles a project into a CompilationResult.
  • CompilationResult — Program, Diagnostics and Success.
  • CompiledProgram / CompiledBlock — symbol slots, compiled networks and SCL statements.
  • SclParser — SCL subset parser; SclParser.RenameSymbol performs token-aware renames.
  • Expression, Statement — the typed IR records (BinaryExpression, IfStatement, …).

Basic usage

csharpUsage
using ControlSpace.Core;
using ControlSpace.Languages;

var result = ProjectCompiler.Compile(DemoProject.Create());
foreach (var d in result.Diagnostics)
    Console.WriteLine($"{d.Severity} {d.Code} at {d.Location}:{d.Line}:{d.Column} {d.Message}");

if (result.Success)
{
    CompiledProgram program = result.Program!;
    Console.WriteLine($"{program.Blocks.Count} blocks, {program.Symbols.Count} tag slots");
}

// Token-aware SCL rename: comments and longer names are left alone.
string scl = SclParser.RenameSymbol("\"Motor_Run\" := TRUE; // Motor_Run_Old", "Motor_Run", "Conveyor_Run");

ControlSpace.Simulation

EngineNo UI depsnet10.0v0.1.0

A deterministic virtual PLC that executes a compiled program in scan order with an input image, virtual forcing, TON/TOF/TP timers, CTU counters, rollback on faults and a bounded trace. Use it for headless tests of control logic. Depends on ControlSpace.Core and ControlSpace.Languages; no UI. Simulation only — never connect it to real machinery.

bashInstall
dotnet add package ControlSpace.Simulation

Key types

  • VirtualPlc — Run/Stop/Reset, Step(TimeSpan), SetInput, Force/Release, Read.
  • ScanSnapshot — cycle, virtual time, state, value image and power flow of one scan.
  • TraceBuffer — bounded ring of ScanSnapshot samples (VirtualPlc.Trace).
  • ControllerState — Stopped, Running or Faulted (see VirtualPlc.Fault).

Basic usage

csharpUsage
using ControlSpace.Core;
using ControlSpace.Languages;
using ControlSpace.Simulation;

var compilation = ProjectCompiler.Compile(DemoProject.Create());
if (!compilation.Success)
    throw new InvalidOperationException(string.Join("\n", compilation.Diagnostics));

var plc = new VirtualPlc(compilation.Program!);
plc.SetInput("Start_PB", 1);
plc.Run();
plc.Step(TimeSpan.FromMilliseconds(100));
Console.WriteLine(plc.Read("Motor_Run"));   // 1 in the sample simulation

plc.Force("Stop_PB", 1);                     // virtual forcing, never real I/O
plc.Step(TimeSpan.FromMilliseconds(100));
Console.WriteLine($"{plc.State}, cycle {plc.Cycle}, {plc.Trace.Count} trace samples");
plc.ReleaseAll();
plc.Stop();

ControlSpace.Storage

I/ONo UI depsnet10.0v0.1.0

Persistence for ControlSpace projects: versioned camelCase JSON with an 8 MiB import limit, duplicate-key rejection and validation on load, CSV tag import/export and an atomic write-then-replace save. Depends on ControlSpace.Core; no UI.

bashInstall
dotnet add package ControlSpace.Storage
Depends onCoreUsed byEngineering

Key types

  • ProjectStorage.Serialize / Deserialize — JSON round trip with limits and validation.
  • ProjectStorage.SaveAtomicAsync — writes a temporary file, then replaces the target.
  • ProjectStorage.ExportTagsCsv / ImportTagsCsv — tag-table interchange.
  • ProjectStorage.MaximumBytes — the import size limit.

Basic usage

csharpUsage
using ControlSpace.Core;
using ControlSpace.Storage;

var project = DemoProject.Create();

string json = ProjectStorage.Serialize(project);          // versioned camelCase JSON
ControlProject loaded = ProjectStorage.Deserialize(json);  // limits, duplicate keys, validation
await ProjectStorage.SaveAtomicAsync("Conveyor_Line.json", loaded);

string csv = ProjectStorage.ExportTagsCsv(project.Tags);
IReadOnlyList<PlcTag> tags = ProjectStorage.ImportTagsCsv(csv);

ControlSpace.Engineering

EditorNo UI depsnet10.0v0.1.0

UI-independent editing on top of the model: every change is a validated transaction with undo/redo that invalidates compilation, tag renames update LAD operands, SCL source and HMI bindings, and dedicated editors cover program blocks, networks, contacts and tag tables. It also holds the workbench layout and open-editor state. Depends on Core, Languages, Simulation and Storage; no UI.

bashInstall
dotnet add package ControlSpace.Engineering

Key types

  • Workspace — current Project, Edit, Undo/Redo, IsDirty, RenameTag, CrossReferences, Compile and Controller.
  • ProgramEditor — add/duplicate/move blocks, networks, branches and contacts.
  • TagTableEditor — cell edits, add/delete, filtering/sorting and value parsing.
  • TableClipboard — tab-separated rectangular copy/paste.
  • WorkbenchLayout / EditorSession — persisted pane layout and open editor tabs.

Basic usage

csharpUsage
using ControlSpace.Core;
using ControlSpace.Engineering;

var workspace = new Workspace(DemoProject.Create());
workspace.Changed += (_, _) => Console.WriteLine($"rev {workspace.Project.Revision}, dirty {workspace.IsDirty}");

// Validated transactions with undo/redo; references follow the rename.
workspace.RenameTag("Motor_Run", "Conveyor_Run");
new TagTableEditor(workspace).Add(PlcType.Int);
var blocks = new ProgramEditor(workspace);
string id = blocks.AddBlock(workspace.Project, "Diagnostics", BlockLanguage.SCL);
workspace.Undo();

foreach (var reference in workspace.CrossReferences("Conveyor_Run"))
    Console.WriteLine($"{reference.Block}/{reference.Network} {(reference.Write ? "write" : "read")}");

var result = workspace.Compile();   // creates workspace.Controller on success

ControlSpace.Rendering.Skia

RenderingSkiaSharpnet10.0v0.1.0

Shared vector drawing for ladder networks, device topology, HMI screens and trace charts onto any SKCanvas, returning hit regions for interaction. Offscreen networks and contacts are culled and text runs are cached. Use it to render or export views without Uno. Depends on Core, Simulation and SkiaSharp 3.119; no UI framework.

bashInstall
dotnet add package ControlSpace.Rendering.Skia

Key types

  • EngineeringRenderer — Ladder, Devices, Hmi and Trace drawing; SetFonts for custom typefaces.
  • RenderResult / HitRegion — hit-test regions and content height of a drawn view.
  • LadderLayout — network positions, collapsed networks and visible ranges.

Basic usage

csharpUsage
using ControlSpace.Core;
using ControlSpace.Rendering.Skia;
using SkiaSharp;

var project = DemoProject.Create();
var main = project.Blocks.First(b => b.Name == "Main");

using var renderer = new EngineeringRenderer();
using var surface = SKSurface.Create(new SKImageInfo(1200, 800));
RenderResult result = renderer.Ladder(surface.Canvas, 1200, 800, main, project.Tags, snapshot: null);

foreach (HitRegion hit in result.Hits)
    Console.WriteLine($"{hit.Kind} {hit.Id} at {hit.Bounds}");

using var png = surface.Snapshot().Encode(SKEncodedImageFormat.Png, 100);
File.WriteAllBytes("main-ladder.png", png.ToArray());

ControlSpace.Controls.Uno

ControlsUno + Skianet10.0-browserwasmnet10.0-desktopv0.1.0

Reusable Uno Platform controls built on the engines: an SKCanvasElement-based engineering canvas for LAD, hardware, HMI and trace views, host-managed scrollbars, a virtualized editable tag table, project tree, block browser, editor bar, panes, splitters and theme. Use them to build your own engineering UI. Depends on Engineering, Rendering.Skia and Uno.WinUI.Graphics2DSK; requires Uno Platform (Skia renderer).

bashInstall
dotnet add package ControlSpace.Controls.Uno

Key types

  • EngineeringCanvas — Project, Controller, Mode (EditorMode), BlockId, Zoom, Selected/EditRequested events.
  • EngineeringViewport — wraps a canvas with scrollbars.
  • EngineeringTable — tag grid; Bind(Workspace) for undoable inline edits.
  • ProjectTree / ProgramBlockBrowser — project navigation (SetProject).
  • EditorBar, WorkbenchPane, WorkbenchSplitter, EngineeringTheme — shell chrome.

Basic usage

csharpUsage
using ControlSpace.Controls.Uno;
using ControlSpace.Core;
using ControlSpace.Engineering;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;

var workspace = new Workspace(DemoProject.Create());
var canvas = new EngineeringCanvas { Project = workspace.Project, Mode = EditorMode.Ladder, BlockId = "main" };
var tags = new EngineeringTable();
tags.Bind(workspace);                       // inline edits become undoable transactions
workspace.Changed += (_, _) => { canvas.Project = workspace.Project; canvas.Invalidate(); };

var layout = new Grid();
layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(2, GridUnitType.Star) });
layout.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
layout.Children.Add(new EngineeringViewport(canvas));
Grid.SetRow(tags, 1);
layout.Children.Add(tags);

var window = new Window { Title = "Ladder", Content = layout };
window.Activate();

ControlSpace.Workbench.Uno

WorkbenchUnonet10.0-browserwasmnet10.0-desktopv0.1.0

The complete TIA Portal-style workbench as one UserControl: menus, toolbars, project tree, editors, task cards, inspector, Portal view and simulation commands. Hosts provide file access through IProjectFiles and may also implement IWorkbenchPreferences on the same object to persist the window layout. Depends on ControlSpace.Controls.Uno; requires Uno Platform.

bashInstall
dotnet add package ControlSpace.Workbench.Uno
Depends onControls.Uno

Key types

  • WorkbenchView — the workbench; InitializeAsync, Navigate("block:main"), Workspace, Dispose.
  • IProjectFiles — open/save pickers and draft recovery storage.
  • IWorkbenchPreferences — optional layout storage, kept separate from project files.

Basic usage

csharpUsage
using ControlSpace.Workbench.Uno;
using Microsoft.UI.Xaml;

sealed class MemoryProjectFiles : IProjectFiles
{
    private string? _recovery;
    public Task<string?> OpenAsync() => Task.FromResult<string?>(null);   // show a file picker here
    public Task<bool> SaveAsync(string contents, string name, string extension = ".json") => Task.FromResult(true);
    public Task<string?> ReadRecoveryAsync() => Task.FromResult(_recovery);
    public Task WriteRecoveryAsync(string contents) { _recovery = contents; return Task.CompletedTask; }
}

// In Application.OnLaunched:
var window = new Window { Title = "ControlSpace" };
var workbench = new WorkbenchView(new MemoryProjectFiles());
window.Content = workbench;
window.Activate();
await workbench.InitializeAsync();
window.Closed += (_, _) => workbench.Dispose();
Architecture

From engine to workbench

Layers build bottom‑up: each one depends only on those beneath it.

L1Model

Typed project records with validation and detached snapshots.

ControlSpace.Core
L2Compiler and runtime

SCL parser and LAD compiler feed a deterministic, fault-rollback virtual scan engine.

ControlSpace.LanguagesControlSpace.Simulation
L3Persistence and editing

JSON/CSV storage plus validated transactions, symbol renaming, layout and editor-session state.

ControlSpace.StorageControlSpace.Engineering
L4Rendering and controls

Shared Skia drawing with hit regions, wrapped in public Uno canvas, table, tree and pane controls.

ControlSpace.Rendering.SkiaControlSpace.Controls.Uno
L5Workbench

Composable engineering shell with host-supplied project-file and preference adapters.

ControlSpace.Workbench.Uno
Use it in code

Embed, script, extend

csharpCompile and simulate without the UI
using ControlSpace.Core;
using ControlSpace.Languages;
using ControlSpace.Simulation;

var compilation = ProjectCompiler.Compile(DemoProject.Create());
if (!compilation.Success)
    throw new InvalidOperationException(string.Join("\n", compilation.Diagnostics));

var plc = new VirtualPlc(compilation.Program!);
plc.SetInput("Start_PB", 1);
plc.Run();
plc.Step(TimeSpan.FromMilliseconds(100));
Console.WriteLine(plc.Read("Motor_Run")); // 1 in the sample simulation
plc.Stop();

File formats

ControlSpace JSON v1 (.json)read/write
Tag table CSV (.csv)export
Tab-separated tag clipboardread/write

Keyboard essentials

Next / previous workbench paneF6 / Shift F6
Next / previous editorCtrl F6 / Ctrl Shift F6
Next / previous editor (alternate)Ctrl Alt Right / Ctrl Alt Left
Close active editorCtrl W
Project tree / inspector / task cardsCtrl 1 / Ctrl 2 / Ctrl 3
Focus project searchCtrl Shift F
Open / export projectCtrl O / Ctrl S
Project undo / redoCtrl Z / Ctrl Y
Compile / start / stop simulationF7 / F5 / Esc
Edit tag-table cellF2
Apply and move down / rightEnter / Tab
Resize focused pane divider / resetArrow keys / Home
Build & CI

Continuously verified

Each push runs through GitHub Actions. The public browser build is deployed only from successful pipelines.

Toolchain

.NET SDK10.0.100
Uno SDK6.7.30
Target frameworksnet10.0-browserwasm · net10.0-desktop · net10.0

Third‑party NuGet packages

SkiaSharp3.119.2
Uno.WinUI.Graphics2DSK6.7.135
bashClone & run the browser build locally
git clone https://github.com/wieslawsoltes/ControlSpace.git
cd ControlSpace
dotnet workload install wasm-tools
dotnet run --project src/ControlSpace.App -f net10.0-browserwasm
Honest boundaries

What it doesn’t claim

The project documents its limits as carefully as its features.

  • Simulation only: never use it to operate or commission real machinery
  • 0.1.0 preview, not a feature-complete or pixel-exact replacement for Siemens TIA Portal
  • No FBD/STL/GRAPH, full SCL, arrays/UDTs or native Siemens project files
  • No S7/PROFINET/OPC UA communication, WinCC compatibility, safety, drives or motion
  • Floating/split editor windows and drag-and-drop tab docking are not implemented