Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
31 changes: 16 additions & 15 deletions doc/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ FlowForge is a visual PLC programming platform:
- **Backend β†’ MQTT Broker**: Build notify (wake up build servers)
- **Backend β†’ Monitor Container**: Container lifecycle management (start/stop on demand)
- **Build Server β†’ Backend**: REST (poll for work, report results)
- **Build Server ↔ MQTT Broker**: Receive build notify, send progress updates, deploy commands (ADS over MQTT)
- **Build Server ↔ MQTT Broker**: Receive build notify, send progress/deploy status updates
- **Build Server ↔ GitHub**: Git clone/fetch + push generated PLC solution
- **Monitor Container ↔ MQTT Broker**: ADS over MQTT reads/subscriptions for live PLC data
- **Build Server β†’ MQTT Broker β†’ PLC**: Deploy/activate via ADS over MQTT (requires TwinCAT Engineering on the build server)
- **Build Server β†’ PLC**: Deploy/activate via direct ADS (Beckhoff.TwinCAT.Ads, native TwinCAT router)
- **Monitor Container β†’ PLC**: Live PLC data via direct ADS-over-TCP (Beckhoff.TwinCAT.Ads + TcpRouter)

## User Workflow

Expand Down Expand Up @@ -59,7 +59,7 @@ FlowForge is a visual PLC programming platform:
2. Backend spins up a dedicated monitor container for that session
3. Backend issues a short-lived auth token and returns the container's SignalR endpoint to the frontend
4. Frontend connects directly to the monitor container via SignalR
5. Monitor container reads PLC data via ADS over MQTT and streams it to the frontend in real-time
5. Monitor container reads PLC data via direct ADS (Beckhoff.TwinCAT.Ads + TcpRouter) and streams it to the frontend in real-time
6. User closes the monitoring view β†’ backend stops the container

## Components
Expand Down Expand Up @@ -120,9 +120,9 @@ FlowForge is a visual PLC programming platform:
- Create TwinCAT project structure
- Commit/push generated PLC solution back to the repo
- Interface with Beckhoff Automation Interface for compilation
- Deploy (activate) TwinCAT solution on target PLC via ADS over MQTT (requires TwinCAT Engineering)
- Deploy (activate) TwinCAT solution on target PLC via direct ADS (requires TwinCAT Engineering)

**Note**: Build and Deploy are separate operations with a shared permission hierarchy. The build server handles both: generation/compilation (build) and PLC activation via ADS over MQTT (deploy). Deploy requires TwinCAT Engineering, which is only available on the build server. The backend orchestrates the requests; the build server executes them.
**Note**: Build and Deploy are separate operations with a shared permission hierarchy. The build server handles both: generation/compilation (build) and PLC activation via direct ADS (deploy). Deploy requires TwinCAT Engineering, which is only available on the build server. The backend orchestrates the requests; the build server executes them.

**Technology**: C# (.NET) - Required for Automation Interface compatibility

Expand All @@ -135,7 +135,7 @@ FlowForge is a visual PLC programming platform:
**Architecture**: Single project with organized folders (Hubs, Auth, Services). See [MODULE_ARCHITECTURE.md](MODULE_ARCHITECTURE.md).

**Key Responsibilities**:
- Read PLC variables via ADS over MQTT (cyclic reads or event-based subscriptions)
- Read PLC variables via direct ADS (Beckhoff.TwinCAT.Ads + TcpRouter; cyclic reads or event-based subscriptions)
- Convert ADS data to frontend-consumable format
- Stream live data directly to the frontend via SignalR
- Authenticate frontend connections using short-lived tokens issued by the backend
Expand All @@ -144,11 +144,11 @@ FlowForge is a visual PLC programming platform:

**Why separate from the backend?**
- Monitoring is long-lived and resource-intensive (continuous ADS reads); the backend API is short-lived request-response
- The ADS data is already on the MQTT broker β€” routing it back through MQTT just to proxy via the backend is an unnecessary round-trip
- Direct ADS-over-TCP from the container to the PLC avoids unnecessary MQTT round-trips
- Isolation: a slow or crashing PLC connection does not affect the backend API
- Scalability: containers scale independently per monitoring session

**Technology**: C# (.NET) + SignalR + MQTTnet
**Technology**: C# (.NET) + SignalR + Beckhoff.TwinCAT.Ads + TcpRouter

**Authentication**: Backend generates a short-lived token on monitor start; the container validates this token on SignalR connection β€” no need to implement full SSO in the container.

Expand All @@ -171,7 +171,8 @@ Build scheduling uses PostgreSQL as the queue backend with MQTT for lightweight
**MQTT topics:**
- `flowforge/build/notify/{twincat-version}` β€” backend β†’ build servers (lightweight wake-up signal)
- `flowforge/build/progress/{build-id}` β€” build server β†’ backend (real-time progress)
- `flowforge/deploy/request/{target-id}` β€” build server β†’ PLC (ADS over MQTT activation, requires TwinCAT Engineering)
- `flowforge/deploy/request/{target-id}` β€” backend β†’ build server (deploy request)
- `flowforge/deploy/status/{deploy-id}` β€” build server β†’ backend (deploy progress)

## Authentication & Authorization

Expand Down Expand Up @@ -242,7 +243,7 @@ project-repo/

3. **Deploy Phase** (executed by build server):
- Backend routes deploy request to the appropriate build server
- Build server activates the TwinCAT solution on the target PLC via ADS over MQTT (requires TwinCAT Engineering)
- Build server activates the TwinCAT solution on the target PLC via direct ADS (requires TwinCAT Engineering)
- Separate permissions, blocked by deploy lock on running PLCs
- Status updates sent back through SignalR
- User receives success/error feedback
Expand All @@ -251,7 +252,7 @@ project-repo/
- User requests monitoring for a target PLC
- Backend starts a dedicated monitor container, returns SignalR endpoint + short-lived token
- Frontend connects directly to the monitor container via SignalR
- Monitor container reads PLC data via ADS over MQTT and streams to the frontend
- Monitor container reads PLC data via direct ADS-over-TCP and streams to the frontend
- Backend is not in the data path β€” only manages container lifecycle
- Session ends β†’ backend stops the container

Expand Down Expand Up @@ -361,7 +362,7 @@ All services except the build server run as Docker containers in a single Docker
| **Backend** | ASP.NET Core | API server, build orchestration, monitor container lifecycle, admin API (Keycloak facade) |
| **Keycloak** | quay.io/keycloak/keycloak | Authentication & authorization β€” local users, LDAP federation, external SSO (OIDC/SAML). Admin console not exposed; managed via FlowForge admin UI |
| **PostgreSQL** | postgres | Metadata, build queue, target registry, audit + Keycloak data |
| **MQTT Broker** | mosquitto / emqx | ADS over MQTT communication, build notifications |
| **MQTT Broker** | mosquitto / emqx | FlowForge internal messaging: build notifications, progress, deploy status |
| **docker-socket-proxy** | tecnativa/docker-socket-proxy | Filtered Docker API access for backend (containers only) |
| **Monitor Containers** | On-demand (C#/.NET) | Live PLC data streaming via SignalR, created/destroyed per session |

Expand All @@ -388,7 +389,7 @@ This limits the blast radius if the backend is compromised β€” it can only manag
The build server runs on dedicated Windows Server instances (not in the Docker stack):

- **Requirement**: TwinCAT Engineering (Windows desktop dependency for Beckhoff Automation Interface)
- **Connectivity**: Connects to the stack via REST (poll backend for build jobs) and MQTT (receive notifications, send progress, deploy via ADS over MQTT)
- **Connectivity**: Connects to the stack via REST (poll backend for build jobs) and MQTT (receive notifications, send progress). Deploy uses direct ADS via native TwinCAT router.
- **Scaling**: One instance per supported TwinCAT version
- **Cannot be containerized**: TwinCAT Engineering requires Windows desktop environment with COM interop

Expand Down Expand Up @@ -420,7 +421,7 @@ Monitor containers are registered with Traefik automatically via Docker labels (
- **Service user**: Minimal permissions β€” repo creation only
- **Authorization**: Role-based access control via Keycloak roles/groups
- **Deploy protection**: 4-eyes principle for production targets, deploy lock on running PLCs
- **PLC Access**: ADS over MQTT via central broker (no per-server ADS route configuration)
- **PLC Access**: Direct ADS via Beckhoff.TwinCAT.Ads (TcpRouter for Linux containers, native router for Windows build servers). See [ADS_INTEGRATION.md](doc/ADS_INTEGRATION.md).
- **Docker socket isolation**: Backend accesses Docker API only through docker-socket-proxy (filtered to container create/start/stop/remove)
- **Monitor container auth**: Short-lived tokens issued by backend, validated by container on SignalR connect β€” no full auth stack in containers
- **Code Injection**: Validate all user inputs to prevent malicious code generation
Expand Down
Loading