Das Problem
Jeder Hallenboden den ich gesehen habe hat dasselbe Muster. Jemand braucht SPS-Daten in einem Web-Dashboard. Oder einer Datenpipeline. Oder einer App. Also schreibt man einen kleinen Service der sich per ADS mit der SPS verbindet, ein paar Variablen liest und sie über HTTP bereitstellt.
Der Service funktioniert. Eine Weile.
Dann braucht jemand anderes andere Variablen. Neuer Service. Jetzt gibt es zwei. Keiner hat Authentifizierung. Beide halten eigene ADS-Verbindungen. Niemand weiß mehr auf welchem Port sie laufen. Sechs Monate später gibt es fünf davon, in drei Sprachen geschrieben, und der der die Lackieranlage überwacht läuft auf einem Laptop unter einem Schreibtisch.
Das ist kein hypothetisches Szenario. Ich hab das mehr als einmal erlebt.
Was ADS eigentlich ist
Beckhoffs ADS (Automation Device Specification) ist das Protokoll das TwinCAT zur Kommunikation mit SPSen nutzt. Ein Binärprotokoll über TCP. Man adressiert Ziele über AMS Net IDs — eine Sechs-Byte-Adresse die aussieht wie eine IP, aber keine ist. Um mit einer SPS zu reden braucht man einen lokalen ADS-Router der weiß wie er die AMS Net ID des Ziels erreicht.
Das Protokoll selbst ist in Ordnung. Schnell, zuverlässig, macht was es soll. Das Problem ist das Tooling drum herum. Das offizielle .NET SDK (Beckhoff.TwinCAT.Ads) funktioniert gut, setzt aber voraus dass man auf einer Windows-Maschine mit TwinCAT-Installation arbeitet. Wenn man von einem Linux-Server aus mit der SPS reden will, aus einem Container, oder von einer Maschine ohne TwinCAT — muss man nachdenken.
Das TwinCAT.Ads.TcpRouter NuGet-Paket löst einen Teil davon. Es bettet einen ADS-Router in den Prozess ein. Keine TwinCAT-Installation nötig. Läuft unter Linux, macOS, Windows. Aber den Service der es nutzt muss man immer noch selbst schreiben.
Die Entscheidung
Ich hab beschlossen es einmal richtig zu schreiben und aufzuhören es ständig neu zu bauen.
Die Anforderungen waren überschaubar:
- HTTP API zum Lesen und Schreiben von SPS-Variablen. JSON rein, JSON raus.
- Authentifizierung. Echte Authentifizierung, nicht “ist im internen Netz, wird schon passen.”
- Multi-SPS-Support. Eine API-Instanz, mehrere SPS-Ziele mit Aliasen.
- Echtzeit-Subscriptions. Kein Polling. ADS kann nativ Notifications — nutzen wir das.
- Rate Limiting. Weil das Erste was passiert wenn man Leuten eine API gibt ist dass jemand eine Endlosschleife schreibt.
- Symbol-Schutz. Nicht jede Variable sollte von außen schreibbar sein.
Das war’s. Keine Workflow-Engine, kein Historian, kein Dashboard. Nur die API.
Architektur
Adsify ist eine ASP.NET Core Anwendung in Vertical Slices organisiert. Jedes Feature — Variablen, Symbole, Notifications, EtherCAT-Diagnose — hat seinen eigenen Controller, Service und Models. Querschnittsthemen wie Authentifizierung, Rate Limiting und ADS-Verbindungsmanagement sind geteilte Infrastruktur.
Die ADS-Schicht verwaltet einen Pool von Verbindungen. Jedes SPS-Ziel bekommt seine eigene Verbindung mit automatischem Reconnect. Der eingebettete ADS-Router übernimmt das AMS-Routing ohne TwinCAT-Installation. Man konfiguriert SPS-Ziele in der appsettings.json mit Alias, AMS Net ID und Port:
{
"PlcTargets": {
"Line1": {
"AmsNetId": "5.80.201.1.1.1",
"Port": 851,
"DisplayName": "Assembly Line 1"
}
}
}
Dann liefert GET /api/plcs/Line1/variables/MAIN.nSpeed den aktuellen Wert von MAIN.nSpeed auf dieser SPS. Das ist die ganze Idee.
Authentifizierung und Zugriffskontrolle
Authentifizierung ist OIDC/OAuth2 mit JWT Bearer Tokens. Jeder Identity Provider funktioniert — Keycloak, Entra ID, Auth0. Im Development-Modus wird die Authentifizierung umgangen damit man mit curl testen kann ohne erst einen IdP aufzusetzen.
Autorisierung hat drei Ebenen:
Rollen definieren welche Operationen man ausführen darf. Ein viewer kann lesen. Ein operator kann lesen und schreiben. Ein admin kann den SPS-Lifecycle steuern.
Per-SPS-Zugriff kontrolliert welche SPSen ein Benutzer erreichen kann. Nicht jeder sollte jede SPS sehen.
Symbol Guards kontrollieren welche Variablen geschrieben werden dürfen. Man konfiguriert eine Allowlist oder Denylist mit Glob-Patterns:
{
"SymbolAccess": {
"Mode": "denylist",
"Denied": ["System.**", "TwinCAT.**"]
}
}
Darüber hinaus erlauben Value Constraints Min/Max-Bereiche und Enum-Validierung pro Variable. Wenn jemand versucht eine Motordrehzahl auf 50.000 RPM zu setzen, wird der Request abgelehnt bevor er die SPS erreicht.
Echtzeit-Benachrichtigungen
ADS hat native Unterstützung für Variablenänderungs-Notifications. Die SPS sendet Updates wenn sich ein Wert ändert, mit konfigurierbarer Zykluszeit. Adsify bietet das über zwei Transporte an:
Server-Sent Events für einfache HTTP-Clients. Verbindung öffnen, JSON-Event-Stream empfangen. Funktioniert mit curl, funktioniert mit EventSource im Browser.
WebSocket über SignalR für Anwendungen die bidirektionale Kommunikation brauchen. Variablen auf einer laufenden Verbindung subscriben und unsubscriben. Das nutzt man für ein HMI.
Der interessante Teil sind Shared Subscriptions. Wenn zehn Clients MAIN.nTemperature subscriben, erstellt Adsify eine ADS-Notification und verteilt sie. Referenzgezählt. Wenn sich der letzte Client trennt wird die Notification freigegeben. Das ist wichtig weil ADS-Notifications auf der SPS-Seite eine begrenzte Ressource sind.
EtherCAT-Diagnose
Das kam spät dazu, hat sich aber als eines der nützlichsten Features herausgestellt. Adsify kann EtherCAT-Diagnosedaten von der SPS lesen — Master-Status, Slave-Health, Fehlerzähler, Frame-Statistiken, Topologie.
Dieselben Daten die man im TE2000 EtherCAT Diagnostics Tool sehen würde, aber über HTTP verfügbar. Man kann Monitoring-Dashboards bauen, Alerts auf CRC-Fehlerschwellwerte setzen, oder die Daten in den bestehenden Observability-Stack einspeisen.
Feature-geflaggt. Standardmäßig deaktiviert. Einschalten wenn man es braucht.
Der MCP-Server
Das ist der Teil den ich ursprünglich nicht geplant hatte, aber nicht weglassen konnte.
Das Model Context Protocol ist ein offener Standard der KI-Assistenten die Interaktion mit externen Tools ermöglicht. Adsify stellt seine API als MCP-Server unter /mcp bereit. Ein verbundener KI-Assistent kann SPSen auflisten, Variablen lesen, Symbole browsen und EtherCAT-Health prüfen — alles über natürliche Sprache.
“Wie schnell dreht der Motor auf Linie 1?” wird zu einem read_variable Tool Call. “Zeig mir die EtherCAT-Topologie” wird zu einem get_ethercat_health Call. Die KI übersetzt.
Das ist kein Spielzeug. In einem Inbetriebnahme-Szenario kann ein Ingenieur Fragen über den Systemzustand stellen ohne die genauen Symbolpfade zu kennen. Der Assistent browst den Symbolbaum, findet die relevanten Variablen und liest sie.
Sicherheit
Sobald man SPS-Variablen über HTTP exponiert, ist Sicherheit nicht mehr optional. Eine falsch konfigurierte Brücke die jedem erlaubt jede Variable zu schreiben ist kein theoretisches Risiko — es ist der Standardzustand der meisten ad-hoc Lösungen die ich in der Praxis gesehen habe.
Adsify behandelt Sicherheit als Kernfeature, nicht als Nachgedanken. Was bereits vorhanden ist:
- JWT-Authentifizierung mit OIDC/OAuth2. Kein anonymer Zugriff in Produktion.
- Rollenbasierte Zugriffskontrolle — Viewer, Operator, Admin — mit Scope-basierten Berechtigungen (
ads:read,ads:write,ads:lifecycle). - Per-SPS-Autorisierung. Benutzer sehen nur die SPSen für die sie berechtigt sind.
- Symbol Guards mit Allowlist/Denylist und Glob-Patterns. Schreibzugriffe auf
System.**undTwinCAT.**standardmäßig verweigern. - Value Constraints — Min/Max-Bereiche, Enum-Validierung, Read-only Flags. Die API lehnt ungültige Schreibzugriffe ab bevor sie die SPS erreichen.
- Rate Limiting pro Benutzer, pro Endpoint. Variable Writes sind auf 20/Sek. begrenzt. Batch-Operationen auf 5/Sek.
- Security Headers — HSTS, CSP, X-Frame-Options von Haus aus.
- Feature Flags — deaktivieren was nicht gebraucht wird, Angriffsfläche reduzieren.
Und es kommt noch mehr. Die aktuelle Roadmap umfasst Default-Deny SPS-Zugriff bei fehlenden Claims, Input-Validierung über alle DTOs, eine dedizierte Security-Test-Suite für Traversal-, Injection- und Auth-Bypass-Szenarien, Request-Body-Größenlimits, Audit-Logging für alle Schreiboperationen und mTLS-Support für Machine-to-Machine-Authentifizierung.
Das Ziel ist simpel: Wenn man Adsify zwischen Netzwerk und SPSen setzt, sollten die SPSen schwerer zu missbrauchen sein als vorher. Nicht leichter.
Was es nicht tut
Adsify ist kein SCADA-System. Kein Historian. Es speichert keine Zeitreihendaten. Es hat keine UI. Es verwaltet keine SPS-Programme und deployt keinen Code.
Es ist eine Brücke. HTTP auf der einen Seite, ADS auf der anderen. Alles andere ist der Job von jemand anderem.
Feature Flags
Jedes größere Feature kann unabhängig geschaltet werden. Wenn man nur Variablen lesen und schreiben will, deaktiviert man Notifications, EtherCAT, MCP und Dateizugriff. Weniger aktivierte Features heißt weniger exponierte Endpoints, heißt kleinere Angriffsfläche.
{
"Features": {
"Variables": { "Enabled": true },
"Symbols": { "Enabled": true },
"Notifications": { "Enabled": false },
"EtherCatDiagnostics": { "Enabled": false },
"Mcp": { "Enabled": false }
}
}
Verfügbarkeit
Adsify ist aktuell Closed Source. Der Plan ist es unter Apache 2.0 zu veröffentlichen sobald es soweit ist — das heißt das Security Hardening ist abgeschlossen, die API-Oberfläche ist stabil und die Dokumentation deckt ab was sie abdecken muss. Ich will nichts halbfertiges rausgeben und es Open Source nennen.
Wenn du mit TwinCAT arbeitest und das hier ein Problem löst das du hast, meld dich. Ich gebe gerne Early Access an Leute die es nutzen und Feedback geben wollen. So baut man etwas das tatsächlich für die Leute funktioniert die es brauchen.


