Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
5c2bf07
chore: merge release v0.2.0 back to develop
q-soriarty Feb 14, 2026
3c8d1ea
fix(ci): pin cla action version and remove npm cache from commitlint …
q-soriarty Feb 14, 2026
34f02ad
fix(ci): disable subject-case rule for acronym-heavy project (#108)
q-soriarty Feb 14, 2026
7386b6b
fix(ci): store CLA signatures on develop branch
q-soriarty Feb 14, 2026
60d8363
fix(ci): use dedicated cla-signatures branch
q-soriarty Feb 14, 2026
e286436
docs: add architecture design documents for all modules (#98)
q-soriarty Feb 14, 2026
98739f8
feat(config): add shared DTO library (#99)
q-soriarty Feb 14, 2026
0ed96c5
feat(frontend): add feature-based arch with auth and layout (#103)
q-soriarty Feb 14, 2026
076a735
feat(backend): add clean architecture layers (#100)
q-soriarty Feb 14, 2026
c8f7d5a
feat(build-server): add pipeline and TwinCAT facades (#101)
q-soriarty Feb 14, 2026
b03dddd
feat(monitor-server): add typed hub interface, auth, and service laye…
q-soriarty Feb 14, 2026
f3a6f2b
test: add test projects and root solution for all modules (#104)
q-soriarty Feb 14, 2026
e8711af
docs(config): update CLAUDE.md and CHANGELOG.md (#105)
q-soriarty Feb 14, 2026
b784148
fix(ci): add repo owner to CLA allowlist
q-soriarty Feb 14, 2026
bb2caa1
feat(config): add shared ADS types and integration architecture doc (…
q-soriarty Feb 14, 2026
02ac0e7
feat(monitor-server): add direct Beckhoff ADS client (#114)
q-soriarty Feb 14, 2026
57ceff9
feat(build-server): add ADS deploy client (#115)
q-soriarty Feb 14, 2026
9c3875b
refactor(config): remove ADS MQTT topics, add stubs (#116)
q-soriarty Feb 14, 2026
a8cbdb6
chore(config): prepare release v0.3.0
q-soriarty Feb 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
7 changes: 2 additions & 5 deletions .commitlintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,12 @@
"monitor-server",
"docs",
"config",
"ci",
"deps"
]
],
"scope-empty": [0],
"subject-case": [
2,
"always",
"lower-case"
],
"subject-case": [0],
"subject-empty": [2, "never"],
"subject-full-stop": [2, "never", "."],
"header-max-length": [2, "always", 72],
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/cla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: CLA Assistant
uses: contributor-assistant/github-action@v2
uses: contributor-assistant/github-action@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
path-to-signatures: "signatures/cla.json"
path-to-document: "https://gist.github.com/q-soriarty/615e916cd19a34f5f1efa1110592b32a"
branch: "main"
allowlist: "dependabot[bot],github-actions[bot]"
branch: "cla-signatures"
allowlist: "q-soriarty,dependabot[bot],github-actions[bot]"
custom-notsigned-prcomment: |
Thank you for your contribution! Before we can merge this PR, you need to sign our [Contributor License Agreement](https://gist.github.com/q-soriarty/615e916cd19a34f5f1efa1110592b32a).

Expand Down
1 change: 0 additions & 1 deletion .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'

- name: Install commitlint
run: |
Expand Down
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

## [0.3.0] - 2026-02-14

Architecture skeleton and Beckhoff ADS integration.

### Added
- **Shared DTO library** (`src/shared/FlowForge.Shared/`): common DTOs (Flow, Build, Deploy, Project, Target, Auth, Monitor), enums (Permission, ProjectRole, BuildStatus, DeployStatus), MQTT topic constants
- **Backend Clean Architecture** (3-project split):
- `FlowForge.Backend.Application` β€” entities, service/repository interfaces, business logic stubs
- `FlowForge.Backend.Infrastructure` β€” EF Core persistence, repository implementations, external service integrations (Git, MQTT, Docker, Keycloak, AES encryption)
- Refactored `FlowForge.Backend.Api` β€” added controller stubs (Projects, Build, Deploy, Targets, Monitor, Admin), middleware (error handling, request logging), Keycloak JWT authentication setup
- **Build Server architecture** β€” pipeline pattern (IBuildStep), code generation (INodeTranslator strategy), TwinCAT COM facades (IVisualStudioInstance, IAutomationInterface), MessageFilter, template manager, workspace manager
- **Monitor Server architecture** β€” typed SignalR hub interface (IPlcDataHubClient), subscription manager, token validator
- **Frontend architecture** β€” feature-based folder structure (editor, projects, build, deploy, targets, monitoring, admin), auth layer (Keycloak OIDC), layout components, API client with JWT interceptor; added zustand, react-query, keycloak-js, @microsoft/signalr, react-router-dom
- **Test projects** (6): xUnit + NSubstitute + FluentAssertions for Shared, Backend.Api, Backend.Application, Backend.Infrastructure, BuildServer, MonitorServer
- **Root solution** (`src/FlowForge.sln`) including all 12 .NET projects with solution folders
- **Architecture design documents**: `doc/BUILD_SERVER_DESIGN.md`, `doc/MODULE_ARCHITECTURE.md`, `doc/ADS_INTEGRATION.md`
- **Beckhoff ADS integration** β€” direct ADS communication replacing custom MQTT relay:
- Shared ADS types: `PlcAdsState` enum, `PlcStateDto`, `AdsConnectionInfo`, `AdsVariableSubscription` DTOs
- Monitor Server: `IAdsClient` + `AdsClientWrapper` using `Beckhoff.TwinCAT.Ads` + `TcpRouter` for Linux/Docker
- Build Server: `IAdsDeployClient` + `AdsDeployClient` for deploy-time PLC state management
- `DeployService` and `TargetService` stubs for deploy workflow and target management
- `DeployStatus` MQTT topic for deploy progress notifications

### Changed
- Monitor Server: replaced `IMqttAdsClient` with direct `IAdsClient` (Beckhoff ADS over TCP)
- MQTT topics: removed `AdsRead`, `AdsWrite`, `AdsNotification` relay topics β€” MQTT now used for FlowForge internal messaging only
- Architecture docs updated to reflect ADS-direct design

### Removed
- `IMqttAdsClient` and `MqttAdsClient` (MQTT ADS relay no longer needed)

---

## [0.2.0] - 2026-02-14

Open source preparation release β€” AGPL-3.0 dual licensing.
Expand Down
38 changes: 31 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@ FlowForge is a visual no-code PLC programming environment inspired by Unreal Eng

Four main components: **Visual Editor** (web frontend) β†’ **.NET Backend API** β†’ **PLC Build Server(s)** + **Monitor Server(s)** β†’ Beckhoff PLC via ADS over MQTT.

- `src/frontend/` β€” Web-based node editor (React + TypeScript + React Flow); direct SignalR to monitor containers for live PLC data
- `src/backend/` β€” ASP.NET Core API (Controllers, PostgreSQL/EF Core, SignalR, MQTTnet, LibGit2Sharp); manages projects, routes build requests to version-specific build servers, orchestrates monitor containers, authenticates via OIDC (Keycloak), exposes admin API as facade over Keycloak Admin REST API
- `src/build-server/` β€” PLC build server (C#/.NET, required for Beckhoff Automation Interface); handles both build and deploy (TwinCAT Engineering needed for PLC activation); version-specific instances on dedicated Windows Servers
- `src/monitor-server/` β€” On-demand PLC monitoring container (C#/.NET, SignalR, MQTTnet); streams live ADS data directly to frontend via SignalR, backend manages lifecycle only
- `doc/` β€” Architecture docs, decision log, tech decisions
- `src/shared/FlowForge.Shared/` β€” Common DTOs, enums, MQTT topic constants. No external dependencies.
- `src/frontend/` β€” Web-based node editor (React + TypeScript + React Flow + Zustand + React Query); feature-based folder structure with auth (Keycloak), layout, and feature modules (editor, projects, build, deploy, targets, monitoring, admin)
- `src/backend/` β€” Clean Architecture Lite (3 projects):
- `src/backend/src/FlowForge.Backend.Api/` β€” ASP.NET Core API (Controllers, Middleware, Auth, SignalR hubs). References Application + Infrastructure + Shared.
- `src/backend/src/FlowForge.Backend.Application/` β€” Business logic, entities, service/repository interfaces. References Shared only.
- `src/backend/src/FlowForge.Backend.Infrastructure/` β€” EF Core, external integrations (Git, MQTT, Docker, Keycloak, AES encryption). References Application + Shared.
- `src/build-server/` β€” PLC build server (C#/.NET); pipeline architecture with sequential build steps (IBuildStep), code generation (INodeTranslator strategy pattern), TwinCAT COM facades (IVisualStudioInstance, IAutomationInterface). References Shared.
- `src/monitor-server/` β€” On-demand PLC monitoring container (C#/.NET, SignalR, MQTTnet); typed hub interface, subscription manager, MQTT ADS client. References Shared.
- `doc/` β€” Architecture docs (`ARCHITECTURE.md`, `BUILD_SERVER_DESIGN.md`, `MODULE_ARCHITECTURE.md`), decision log, tech decisions
- `samples/` β€” Example visual programs
- `test/` β€” Tests (framework TBD)
- `test/` β€” xUnit + NSubstitute + FluentAssertions test projects: `FlowForge.Shared.Tests`, `FlowForge.Backend.Api.Tests`, `FlowForge.Backend.Application.Tests`, `FlowForge.Backend.Infrastructure.Tests`, `FlowForge.BuildServer.Tests`, `FlowForge.MonitorServer.Tests`
- `src/FlowForge.sln` β€” Root solution including all .NET projects

**Key architectural decisions:**
- **Keycloak as auth layer**: all authentication/authorization via Keycloak (local users, LDAP federation, external SSO β€” all Keycloak config). Backend only validates JWT from Keycloak. User management via FlowForge admin UI (facade over Keycloak Admin REST API).
Expand All @@ -43,6 +48,25 @@ npm install
.\scripts\setup-dev.ps1 # Windows PowerShell
```

## Build & Test Commands

```bash
# Build all .NET projects
dotnet build src/FlowForge.sln

# Run all .NET tests
dotnet test src/FlowForge.sln

# Build/test individual modules
dotnet build src/backend/FlowForge.Backend.sln
dotnet build src/build-server/FlowForge.BuildServer.sln
dotnet build src/monitor-server/FlowForge.MonitorServer.sln

# Frontend
cd src/frontend && npm run build
cd src/frontend && npm run dev
```

## Commit Conventions

Commits are validated by commitlint via husky git hook. **All commits must follow Conventional Commits format:**
Expand All @@ -53,7 +77,7 @@ Commits are validated by commitlint via husky git hook. **All commits must follo

Types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`

Scopes: `frontend`, `backend`, `build-server`, `monitor-server`, `docs`, `config`, `deps`
Scopes: `frontend`, `backend`, `build-server`, `monitor-server`, `docs`, `config`, `ci`, `deps`

Rules: imperative mood, lowercase subject, no trailing period, max 72 chars. Use `!` after type/scope for breaking changes.

Expand Down
212 changes: 212 additions & 0 deletions doc/ADS_INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# ADS Integration Architecture

## Decision

**Use Beckhoff.TwinCAT.Ads for direct ADS communication** instead of a custom ADS-over-MQTT relay protocol.

### Context

The original architecture assumed a custom relay where the monitor server would exchange ADS read/write commands as MQTT messages (`flowforge/ads/read/*`, `flowforge/ads/write/*`, `flowforge/ads/notification/*`). Research revealed this is unnecessary:

- **ADS-over-MQTT is a native TwinCAT router feature** β€” transparent to application code. Once configured on the PLC via `TcConfig.xml`, any `AdsClient` connects normally via `AmsNetId`.
- **`Beckhoff.TwinCAT.Ads.TcpRouter`** provides a software ADS router for non-TwinCAT systems (Linux Docker containers).
- MQTT remains for **FlowForge internal messaging** (build notifications, progress updates) but is no longer used for ADS relay.

### Consequences

| Component | Before | After |
|-----------|--------|-------|
| **Monitor Server** | MQTT relay topics for ADS reads | `Beckhoff.TwinCAT.Ads` + `TcpRouter` for direct ADS-over-TCP |
| **Build Server** | MQTT relay for deploy commands | `Beckhoff.TwinCAT.Ads` natively (Windows/TwinCAT router) |
| **Shared MQTT Topics** | `flowforge/ads/read/*`, `write/*`, `notification/*` | Removed β€” MQTT for build notifications only |

---

## NuGet Packages

| Package | Version | Used By | Purpose |
|---------|---------|---------|---------|
| `Beckhoff.TwinCAT.Ads` | 7.0.* | Monitor Server, Build Server | Core ADS client (`AdsClient`) |
| `Beckhoff.TwinCAT.Ads.TcpRouter` | 7.0.* | Monitor Server only | Software ADS router for Linux/Docker |

Both packages target .NET 8.0, .NET 10.0, and .NET Standard 2.0. They work with .NET 9.0 via the .NET Standard 2.0 target.

---

## Key API Patterns

### Connection

```csharp
// On Windows with TwinCAT installed (build server):
var client = new AdsClient();
client.Connect(AmsNetId.Parse("192.168.1.100.1.1"), 851);

// On Linux/Docker with TcpRouter (monitor server):
// TcpRouter must be started first, then AdsClient connects normally.
```

**Port 851** = PLC Runtime 1 (default). Ports 852, 853 for additional runtimes.

### Variable Access

**Symbol-based read** (dynamic, for discovery):
```csharp
var loader = SymbolLoaderFactory.Create(client, settings);
var value = loader.Symbols["MAIN.nCounter"].ReadValue();
```

**Handle-based read** (faster for repeated access):
```csharp
uint handle = client.CreateVariableHandle("MAIN.nCounter");
int value = (int)client.ReadAny(handle, typeof(int));
client.DeleteVariableHandle(handle);
```

**Sum Commands** (batch β€” critical for performance):
- 4000 individual reads = 4–8 seconds
- 4000 reads via Sum Command = ~10 ms
- Max 500 sub-commands per call

### Notifications (Monitor Server)

```csharp
client.AddDeviceNotificationEx(
"MAIN.nCounter",
AdsTransMode.OnChange,
cycleTime: 100, // ms β€” check interval
maxDelay: 0, // ms β€” max delay before notification
userData: null,
type: typeof(int));
```

- **Max 1024 notifications per connection**
- Notifications fire on background threads
- Always unregister when done (`DeleteDeviceNotification`)

### PLC State Management (Build Server β€” Deploy)

```csharp
// Read state
StateInfo state = client.ReadState();
// state.AdsState == AdsState.Run / Stop / Config / etc.

// Switch to config mode (required before activation)
client.WriteControl(new StateInfo(AdsState.Reconfig, 0));

// Restart to run mode
client.WriteControl(new StateInfo(AdsState.Run, 0));
```

---

## PlcAdsState Enum

Mirrored in `FlowForge.Shared.Models.Ads.PlcAdsState` (no Beckhoff dependency in Shared):

| Value | Name | FlowForge Meaning |
|-------|------|--------------------|
| 5 | **Run** | PLC running β€” deploy needs approval if production target |
| 6 | **Stop** | PLC stopped β€” safe for deploy |
| 11 | **Error** | PLC error β€” needs investigation |
| 15 | **Config** | Config mode β€” safe for deploy |
| 16 | **Reconfig** | Transitioning to config mode |

Deploy lock logic: `IsSafeForDeploy = State is Stop or Config`.

---

## Component Architecture

### Monitor Server (Linux/Docker)

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Monitor Container β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ IAdsClient β”‚ β”‚ ADS-over-TCP
β”‚ β”‚ (AdsClientWrapper) │───────────────────────► PLC
β”‚ β”‚ Uses: AdsClient + β”‚ β”‚ Port 48898
β”‚ β”‚ TcpRouter β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ SubscriptionManager β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ SignalR
β”‚ β”‚ PlcDataHub (SignalR) │◄─────────────────── Frontend
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

- Each container gets a unique local `AmsNetId` (derived from IP or session ID).
- `TcpRouter` establishes the ADS-over-TCP connection to the target PLC.
- `AdsClient` connects through the local `TcpRouter`.

### Build Server (Windows/TwinCAT)

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Build Server (Windows) β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ IAdsDeployClient β”‚ β”‚ Native ADS
β”‚ β”‚ (AdsDeployClient) │───────────────────────► PLC
β”‚ β”‚ Uses: AdsClient β”‚ β”‚ (via TwinCAT router)
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ IAutomationInterface β”‚ β”‚ COM Interop
β”‚ β”‚ (ActivateConfiguration) │───────────────────────► TwinCAT XAE
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

- No `TcpRouter` needed β€” uses the native TwinCAT router on Windows.
- Deploy sequence: connect β†’ read state β†’ switch to config β†’ activate β†’ restart β†’ verify.

---

## Deploy Sequence (Build Server)

1. **Connect** to target PLC via ADS (`IAdsDeployClient.ConnectAsync`)
2. **Read PLC state** β€” deploy lock check (`ReadPlcStateAsync`)
3. If running + production β†’ require 4-eyes approval (handled by backend before queuing)
4. **Switch to config mode** (`SwitchToConfigModeAsync` β†’ `AdsState.Reconfig`)
5. **Activate configuration** via Automation Interface (`IAutomationInterface.ActivateConfiguration`)
6. **Start/restart TwinCAT** via ADS (`StartRestartTwinCatAsync` β†’ `AdsState.Run`)
7. **Verify** PLC is in Run state
8. **Disconnect**

---

## MQTT Topic Changes

### Removed
- `flowforge/ads/read/{amsNetId}` β€” replaced by direct ADS reads
- `flowforge/ads/write/{amsNetId}` β€” replaced by direct ADS writes
- `flowforge/ads/notification/{amsNetId}` β€” replaced by ADS notifications

### Retained
- `flowforge/build/notify/{twincat-version}` β€” backend β†’ build servers (wake-up signal)
- `flowforge/build/progress/{build-id}` β€” build server β†’ backend (progress updates)

### Added
- `flowforge/deploy/status/{deploy-id}` β€” build server β†’ backend (deploy progress)

---

## References

- [Beckhoff.TwinCAT.Ads NuGet](https://www.nuget.org/packages/Beckhoff.TwinCAT.Ads)
- [Beckhoff.TwinCAT.Ads.TcpRouter NuGet](https://www.nuget.org/packages/Beckhoff.TwinCAT.Ads.TcpRouter/)
- [ADS-over-MQTT Manual](https://download.beckhoff.com/download/document/automation/twincat3/ADS-over-MQTT_en.pdf)
- [Beckhoff/ADS-over-MQTT_Samples](https://github.com/Beckhoff/ADS-over-MQTT_Samples)
- [Beckhoff/TF6000_ADS_DOTNET_V5_Samples](https://github.com/Beckhoff/TF6000_ADS_DOTNET_V5_Samples)
- [ADS Notifications](https://infosys.beckhoff.com/content/1033/tc3_adsnetref/7312578699.html)
- [ADS Sum Commands](https://infosys.beckhoff.com/content/1033/tc3_adssamples_net/185258507.html)
- [AdsState Enum](https://infosys.beckhoff.com/content/1033/tc3_adsnetref/7313023115.html)
- [ITcSysManager.ActivateConfiguration](https://infosys.beckhoff.com/content/1033/tc3_automationinterface/242759819.html)
- [Secure ADS](https://download.beckhoff.com/download/document/automation/twincat3/Secure_ADS_EN.pdf)
Loading
Loading