REST API (v1)
REST API-Handbuch v1 für PlantPulse Edge. Dies ist die Standardschnittstelle für Bildschirme und externe Systeme zur Verwaltung von OPC, Tags, Monitoring und Health-Checks für externe Übertragungen.
| Punkt | Wert |
|---|---|
| Base URL | http(s)://<edge-host>/api/v1 |
| Content-Type | application/json; charset=utf-8 |
| Authentifizierung | API-Key erforderlich (edge.rest.api.auth=true) — X-API-Key / Authorization: Bearer oder Passage via Login-Session. ?api_key= wird mit 401 abgelehnt |
| Audit-Log | ApiAccessLogFilter — Jeder /api/* Aufruf wird einzeilig protokolliert (Methode/Pfad/Status/Dauer/IP/Benutzer/Auth-Modus) |
| Hauptcontroller | plantpulse.app.edge.api.v1.* (OpcAPI, TagAPI, AppAPI, SystemAPI, MonitoringAPI) |
| Error Envelope | {data, meta, errors} — Gilt auch für Filter/Advice-Pfade |
| HTTPS | edge.rest.api.https_only=true Standard. Aufrufe via Plain-HTTP-Client werden abgelehnt oder folgen der Tomcat-HTTPS-Redirect-Richtlinie |
Authentifizierung (ApiAuthFilter)
Aktivierung in app.properties über edge.rest.api.auth=true. Der Schlüssel ist edge.rest.api.key und wird im Betrieb über EDGE_REST_API_KEY_FILE oder EDGE_REST_API_KEY injiziert.
Passage erfolgt, wenn mindestens eine der folgenden Bedingungen erfüllt ist:
A. Login-Session (Browser)
Bereits über /login/form angemeldete Benutzer — Wenn ein _USER_LOGIN Attribut (JSONObject) in der Session vorhanden ist, führt ApiAuthFilter direkt chain.doFilter() aus. Alle /api/*, die via Ajax vom Bildschirm (JSP) aufgerufen werden, funktionieren ohne zusätzlichen Schlüssel.
B. X-API-Key Header (Empfohlen)
EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "X-API-Key: %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value
C. Authorization: Bearer <key> Header
EDGE_REST_API_KEY="$(tr -d '\r\n' < /run/secrets/edge-rest-api-key)"
cfg="$(mktemp)"
trap 'rm -f "$cfg"' EXIT
printf 'header = "Authorization: Bearer %s"\n' "$EDGE_REST_API_KEY" > "$cfg"
curl --config "$cfg" https://edge.example.com/api/v1/tag/TAG_LS_XBC_0001/value
D. ?api_key=<key> Query-Parameter — Abgelehnt
Da Schlüssel im Klartext in der URL erscheinen und somit in Access-Logs, Proxy-Caches oder Browser-Historien verbleiben können, werden diese in v1 mit 401 abgelehnt. Bitte nutzen Sie die Header-Methoden (B/C).
Fehlerantwort
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8
{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 401,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "UNAUTHORIZED",
"message": "Invalid or missing X-API-Key."
}
]
}
Der Schlüsselvergleich erfolgt via constantTimeEquals() (Schutz gegen Timing-Attacks).
edge.rest.api.auth=false (Entwicklung / Temporär)
Wenn die Authentifizierung deaktiviert ist, führt ApiAuthFilter sofort chain.doFilter() aus — Aufrufe von extern sind ohne Schlüssel möglich. Im produktiven Netzwerk wird grundsätzlich true empfohlen.
Audit-Log (ApiAccessLogFilter)
Alle /api/* Aufrufe werden als einzeiliges Log aufgezeichnet — einschließlich Authentifizierungsfehlern (401), um einen Audit-Trail zu gewährleisten (Filter-Chain-Reihenfolge: ApiAccessLogFilter → ApiAuthFilter).
Format
[API_ACCESS] method=GET path=/api/v1/tag/TAG_MS_1000/value query= status=200 duration=12ms
ip=100.127.147.98 user=admin auth=session ua=Mozilla/5.0...
| Feld | Bedeutung |
|---|---|
method | HTTP-Methode |
path | Request-URI (ohne Query) |
query | Query-String — Abgelehnte ?api_key=... Teile werden als *** maskiert |
status | HTTP-Response-Code |
duration | Bearbeitungszeit (ms) |
ip | Standard ist RemoteAddr. In Reverse-Proxy-Umgebungen mit edge.rest.api.trust_x_forwarded_for=true die erste IP X-Forwarded-For |
user | user_id bei Session-Benutzer / XXXX*** (len=16) bei API-Key (erste 4 Zeichen + Länge) / - bei anonym |
auth | session (Browser-Login) / apikey (X-API-Key/Bearer) / deprecated_query (Abgelehrter ?api_key= Versuch) / none (Schlüssel fehlt oder anonym) |
ua | User-Agent (bei mehr als 80 Zeichen gekürzt auf …) |
error | Bei Ausnahmen wird error=ClassName hinzugefügt |
Log-Level-Aufteilung
| Status | Level | Absicht |
|---|---|---|
| 5xx oder Throwable | ERROR | Serverfehler — Sofortiger Alarm |
| 401, 403 | WARN | Authentifizierung/Berechtigung abgelehnt — Audit-Priorität |
| Sonstige (2xx, 3xx, allgemeine 4xx) | INFO | Alltagsaufrufe |
Anwendung
- Wer sendet falsche Schlüssel? — Prüfung des 401-Trails via
grep '\[API_ACCESS\].*status=401' - 5xx Regressionsverfolgung — Felder
grep '\[API_ACCESS\].*status=5'+error= - Aufruffrequenz / Aggregation pro Benutzer — Gruppierung nach auth=apikey/session, Analyse nach IP
- Antwortverzögerungen / Slow Queries — Extraktion langer Linien via
duration=
Klartext-Schlüssel werden nirgendwo im Log gespeichert — Identifikation erfolgt nur über Query-Maskierung + User-Feld-Prefix (4 Zeichen). Dies bietet ein Gleichgewicht zwischen ausreichender Identifizierbarkeit für die Analyse von Sicherheitsvorfällen und dem Schutz vor Klartext-Exposition.
Mapping-Reihenfolge in web.xml: Zuerst ApiAccessLogFilter, dann ApiAuthFilter. Da AccessLog den Status im finally-Block aufzeichnet, werden auch fehlgeschlagene 401-Aufrufe im Audit erfasst. Würde die Reihenfolge getauscht, würden 401-Aufrufe im Access-Log fehlen.
Response Envelope
Die Antworten der v1 REST API werden in einen gemeinsamen Envelope gehüllt (RestApiSupport, ApiEnvelopeWriter).
Erfolgreiche Antwort:
{
"data": { },
"meta": {
"timestamp": 1778069486934,
"status": "OK",
"request_id": "f818abd0-eb0f-4497-9b07-7ccc94b2e2d7"
}
}
Fehlerantwort:
{
"data": null,
"meta": {
"timestamp": 1778069486934,
"status": "ERROR",
"http_status": 400,
"request_id": "3b89b8f1-0fc1-4ab4-b4eb-d12dc314d500"
},
"errors": [
{
"code": "VALIDATION_FAILED",
"message": "Validation failed"
}
]
}
Alte RPC-Style-Envelopes (result, _session_id, _user_id) werden in REST v1 nicht mehr verwendet.
1. Gemeinsame Konventionen
Bevor Sie die Endpunkte betrachten, beachten Sie die Regeln, die für alle /api/v1/* gelten. Ohne diese Kenntnisse wird die Implementierung des Clients fehlerhaft sein, selbst wenn die einzelnen Endpunkt-Dokumentationen gelesen wurden.
1.1 Operative Header
| Header | Richtung | Zweck |
|---|---|---|
X-API-Key | Request | Authentifizierung. ?api_key= Queries werden abgelehnt (401) |
Authorization: Bearer <key> | Request | Alternative zum API-Key |
X-Request-Id | Request | Tracking. Wird als meta.request_id in der Antwort zurückgegeben. Falls nicht gesendet, generiert der Server einen Wert |
Idempotency-Key | Request | POST-Duplikationsschutz. TTL 10 Min. Bei erneutem Versuch mit demselben Schlüssel erfolgt die Antwort mit dem gecachten Ergebnis und Idempotency-Replay: true |
If-Match | Request | Optimistisches Locking. 412, wenn es vom ETag der GET-Antwort abweicht |
ETag | Response | Weak Validator (W/"<hex>"). Derzeit wird nur GET /api/v1/opc/{opcId} ausgegeben |
Location | Response | URI der neuen Ressource bei POST-Erstellung (201) |
X-RateLimit-Limit · -Remaining · -Reset | Response | Rate-Limit-Informationen |
Retry-After | Response | Sekunden bis zum nächsten Versuch bei 429 |
Das Rate-Limit wird über edge.rest.api.rate_limit.enabled und edge.rest.api.rate_limit.per_second (Standard 1000) gesteuert.
Idempotency-Key für WiederholungsversucheVor-Ort-Verbindungen können unterbrochen werden. Ein POST, auf das keine Antwort folgte, ist nicht „fehlgeschlagen“, sondern „unbekannt“ — ein einfaches erneutes Senden könnte zu doppelten OPC-Einträgen führen. Senden Sie dieselbe Key-ID, und die Box gibt das erste Ergebnis erneut zurück.
1.2 Pagination
Gemeinsame Queries für Listen-Endpunkte.
| Query | Standardwert | Beschreibung |
|---|---|---|
page | 0 | Seitennummer, beginnend bei 0 |
size | 0 | Bei 0 wird die gesamte Liste zurückgegeben. Bei 1..1000 wird die Liste entsprechend gekürzt und meta.pagination gefüllt |
Wenn size 1000 überschreitet oder negativ ist, oder wenn page negativ ist, erfolgt eine 400 INVALID_RANGE.
1.3 ID-Regeln
| Ziel | Regex |
|---|---|
site_id | ^SITE_[A-Z0-9_]{1,60}$ |
opc_id | ^OPC_[A-Z0-9_]{1,60}$ |
tag_id | ^TAG_[A-Z0-9_]{1,60}$ |
data_type kann nur sechs Werte annehmen: Boolean · Integer · Long · Float · Double · String (entspricht Cassandra ENUM). opc_type ist eine Whitelist von Protokollkonstanten des Treibers (LS · OPCUA · MODBUS · MODBUS_RTU · HTTP · MELSEC · S7 · EIP …), die Referenzliste befindet sich in GET /v3/api-docs. Werte außerhalb der Whitelist werden als field_errors[].reason = "not_in_whitelist" abgelehnt.
2. Endpunkt-Liste
Alle 52 Endpunkte. Die OpenAPI-Referenz befindet sich in GET /v3/api-docs der Box, die visuelle Ansicht in /public/swagger-ui/index.html.
2.1 OPC — 8
| Methode | URL | Beschreibung | Antwort |
|---|---|---|---|
| GET | /api/v1/opc | OPC-Liste + Laufzeitstatus + 60-Minuten-Trend | 200 · 400 |
| POST | /api/v1/opc | OPC-Registrierung. Tags können via tag_list gleichzeitig hinzugefügt werden | 201 · 400 · 409 |
| GET | /api/v1/opc/{opcId} | Details + ETag. Tag-Array via ?include=tags integriert | 200 · 404 |
| PUT | /api/v1/opc/{opcId} | Änderung (Unterstützung für If-Match) | 200 · 404 · 412 |
| PATCH | /api/v1/opc/{opcId} | Teilweise Änderung — nur gesendete Felder werden geändert | 200 · 400 · 404 · 412 |
| DELETE | /api/v1/opc/{opcId} | Löschen von OPC + allen untergeordneten Tags | 200 · 404 · 412 |
| POST | /api/v1/opc/{opcId}/start | Erfassung starten | 200 · 404 |
| POST | /api/v1/opc/{opcId}/stop | Erfassung stoppen | 200 · 404 |
POST Erforderliche 7 Felder — site_id · opc_id · opc_name · opc_type · opc_agent_ip · opc_agent_port (1~65535) · timecycle (mind. 100ms). Verstöße werden gesammelt zurückgegeben (field_errors[]).
Tags werden über tag_list gleichzeitig registriert. Da normalerweise Dutzende Tags pro Verbindung registriert werden, führt eine Trennung der Registrierung zu einem Zwustand, in dem die OPC existiert, aber keine Tags vorhanden sind. Senden Sie alles in einem Aufruf — alternativ können Tags später einzeln über POST /api/v1/opc/{opcId}/tag hinzugefügt werden.
2.2 Tag — 10
| Methode | URL | Beschreibung | Antwort |
|---|---|---|---|
| GET | /api/v1/tag | Gesamte Tag-Liste | 200 · 400 |
| GET | /api/v1/opc/{opcId}/tag | Tag-Liste eines einzelnen OPC | 200 · 400 |
| POST | /api/v1/opc/{opcId}/tag | Registrierung eines einzelnen Tags | 201 · 400 · 409 |
| GET | /api/v1/tag/{tagId} | Tag-Details | 200 · 404 |
| PUT | /api/v1/tag/{tagId} | Tag-Änderung | 200 · 404 |
| DELETE | /api/v1/tag/{tagId} | Tag-Löschung | 200 · 404 |
| GET | /api/v1/tag/{tagId}/value | Letzter Wert. Bei ?fresh=true wird der Wert neu vom Gerät gelesen | 200 · 404 · 503 |
| POST | /api/v1/tag/{tagId}/value | Wert schreiben + anschließendes erneutes Lesen (actual_read) | 200 · 400 · 404 · 503 |
| POST | /api/v1/tag-values:read | Cache-Werte mehrerer Tags gleichzeitig (max. 100) | 200 · 400 |
| GET | /api/v1/tag/{tagId}/points | Zeitreihe abfragen + Downsampling | 200 · 400 · 500 |
POST Erforderliche 6 Felder — site_id · opc_id (automatisch aus Pfad) · tag_id · tag_name · data_type · plc_address.
Query für points: from·to (Epoch ms, from < to) · minutes (Standard 10, 11440) · 5000 — bei Überschreitung wird intervallbasiert ausgedünnt).limit (Standard 600, 1
Code-Mapping bei Schreibfehlern — Tag fehlt 404 TAG_NOT_FOUND · Verbindung unterbrochen 503 DISCONNECTED · Erfassung gestoppt 503 OPC_STOPPED · Schreibgeschützter Tag 400 RW_VIOLATION · Typkonflikt 400 TYPE_MISMATCH · Sonstige 500 INTERNAL_ERROR.
2.3 App (Docker Container) — 9
| Methode | URL | Beschreibung | Antwort |
|---|---|---|---|
| GET | /api/v1/app | Container-Liste | 200 · 400 |
| POST | /api/v1/app | Run nach Image-Pull | 201 · 400 · 409 · 502 |
| GET | /api/v1/app/{name} | Ergebnis von docker inspect | 200 · 404 |
| DELETE | /api/v1/app/{name} | Löschen (inkl. Image bei ?image=true) | 200 · 404 |
| POST | /api/v1/app/{name}/start | Starten | 200 · 404 |
| POST | /api/v1/app/{name}/stop | Stoppen | 200 · 404 |
| POST | /api/v1/app/{name}/restart | Neustarten | 200 · 404 |
| GET | /api/v1/app/{name}/logs?lines=N | Letzte N Zeilen (1~5000, Standard 200) | 200 · 404 |
| GET | /api/v1/app/{name}/stats | CPU · Memory · Network · Disk Snapshot | 200 · 404 |
name und image müssen Regex-Prüfungen und die Sperre von Shell-Metazeichen passieren (; & | \ $ ( ) < > \ " ' Leerzeichen). memory_limit_mb는 16 이상,cpu_limit은 0.1 이상,network는bridge·host·none` .
Bereitstellungsfehler geben die Stufe an — Pull-Fehler 502 DOCKER_PULL_FAILED, Run-Fehler 502 DOCKER_RUN_FAILED, existierender Name 409 CONTAINER_EXISTS.
2.4 System — 12
| Methode | URL | Beschreibung | Authentifizierung |
|---|---|---|---|
| GET | /api/v1/system/health | Liveness + Prüfung von 8 Komponenten | Nicht erforderlich |
| GET | /api/v1/system/ready | Readiness (Monitor + V5 Client) | Nicht erforderlich |
| GET | /api/v1/system/version | Version · Build-Datum · Image-Tag · Container-Status | Nicht erforderlich |
| GET | /api/v1/system/time | Aktuelle Zeit der Box (Epoch ms) | Erforderlich |
| POST | /api/v1/system/time/sync | Zeit an Plattform angleichen ({"platform_time":…}) | Erforderlich |
| GET | /api/v1/system/logs | Aktuelle Logs als JSON | Erforderlich |
| GET | /api/v1/system/logs/download | Log-Verzeichnis via tar.gz | Erforderlich |
| GET | /api/v1/system/events | SSE-Stream (text/event-stream) | Erforderlich |
| GET | /api/v1/system/upgrade/check | Vergleich mit neuester Version des Download-Servers | Erforderlich |
| POST | /api/v1/system/upgrade | Upgrade-Trigger | Erforderlich |
| GET | /api/v1/system/upgrade/status | Upgrade-Fortschritt (Polling) | Erforderlich |
| POST | /api/v1/system/firmware | Firmware-Update-Trigger | Erforderlich |
| POST | /api/v1/system/reboot | Host-Reboot nach 5 Sekunden | Erzwungen |
| POST | /api/v1/system/restart | Edge-Agent-Neustart nach 3 Sekunden | Erzwungen |
reboot · restart kann «nicht deaktiviert» werdenSelbst wenn die Authentifizierung über edge.rest.api.auth=false deaktiviert wurde, fordern diese beiden Endpunkte weiterhin eine Session-Anmeldung oder einen API-Key. Die Offenheit dieser Funktionen würde an sich zu einem Denial-of-Service führen.
2.5 Monitoring · Edge — 4
| Methode | URL | Beschreibung |
|---|---|---|
| GET | /api/v1/monitoring | Uptime · Anzahl OPCs · registrierte Tags · Scan-Geschwindigkeit |
| GET | /api/v1/edge | edge_id · site_id · Version · Hostname · Modus |
| GET | /api/v1/edge/status | Edge-Status-Zusammenfassung |
| GET | /api/v1/transfer | Status pro ausgehender Übertragung (MQTT · SparkplugB · Plattform-API) |
monitoring gibt unmittelbar nach dem Booten, solange kein Monitor vorhanden ist, einen 503 MONITOR_NOT_READY aus — dies ist kein Fehler, sondern ein Status «noch nicht bereit».
2.6 Redundanz (HA) — 4
| Methode | URL | Beschreibung |
|---|---|---|
| GET | /api/v1/ha/status | Schreibrechte dieser Edge · Modus (manual/auto) · Startzeitpunkt · Status der Gegenpartner-Box |
| PUT | /api/v1/ha/write-authority | Schreibrechte dieser Edge erhöhen oder senken ({"enabled":true}) |
| PUT | /api/v1/ha/failover/override | Manueller Eingriff in automatisches Failover — nur im auto Modus (sonst 409 NOT_AUTO_MODE) |
| POST | /api/v1/ha/definitions | Tag-Definitionen an Partner replizieren |
Schreibrechte — Grundwissen vor dem Lesen
Was wird blockiert? Eine Edge ohne Berechtigung lehnt reale PLC-Schreibvorgänge ab (WRITE_NOT_AUTHORIZED). Der Konfigurationsschlüssel ist edge.plc.write_authority; falls nicht vorhanden, gilt true — eine frisch installierte Box startet mit Berechtigung.
HTTP Erfassung durchläuft dieses Gate nicht — jedoch nicht «immer»Verbindungen, bei denen opc_type auf HTTP gesetzt ist, stellen keine Schreibvorgänge an Geräte dar, sondern eine eingehende Erfassung. Daher wird die HTTP-Erfassung auch an einer Edge mit gesenkten Rechten fortgesetzt, da keine Gefahr eines Doppel-Schreibzugriffs besteht.
⚠ Unmittelbar nach dem Booten kann auch dieses HTTP-Schreiben kurzzeitig abgelehnt werden. Das Gate liest opc_type aus dem Cache. Wenn der Cache noch nicht gefüllt ist oder die Abfrage fehlschlägt, wird es als «reale PLC» eingestuft und blockiert (Sicherheitsmodus). Dieser Abfragefehler wird nicht protokolliert.
Sollte das Verhalten «HTTP sollte funktionieren, wurde aber abgelehnt» auftreten, prüfen Sie zuerst, ob der Startvorgang gerade erfolgt ist. Sobald der Cache gefüllt ist, löst sich das Problem von selbst — es ist keine Konfigurationsänderung erforderlich.
Dieser Aufruf ändert nur die eigene Box. Er fragt den Partner nicht ab, sperrt ihn nicht und übernimmt keine Rechte. Es gibt keinen Koordinationscode.
Daher ist ein Zustand, in dem beide Boxen gleichzeitig Berechtigungen besitzen, tatsächlich möglich. Halten Sie die Reihenfolge ein:
- A senken —
PUT /api/v1/ha/write-authority {"enabled":false} - Bestätigen, dass A auf
falsesteht —GET /api/v1/ha/status - B erhöhen
Bei falscher Reihenfolge entsteht ein Zeitfenster, in dem beide true sind und gleichzeitig auf dieselbe PLC schreiben.
GET /api/v1/ha/status zeigt via peer.write_authority zwar den Wert des Partners an, dies ist jedoch eine Beobachtung, kein Zwang — der Aufruf wird nicht blockiert, selbst wenn der Partner die Berechtigung hat.
persisted:false ist kein «Fehler», sondern bedeutet «verschwindet nach Neustart»Das Toggle erfolgt in zwei Schritten. ① Live-Übernahme — das Gate sieht den neuen Wert sofort (kein Neustart erforderlich). ② app.properties Persistenz — selbst wenn dies fehlschlägt, bleibt ① aktiv und wird als persisted:false gemeldet.
Das bedeutet: Es funktioniert jetzt, kehrt aber beim nächsten Neustart zum alten Wert zurück. Dies ist die häufigste Fehlerquelle vor Ort, wenn man glaubt, die Berechtigung gesenkt zu haben, diese aber nach dem Neustart wieder aktiv ist. Lösen Sie die Ursache vor dem Neustart — meist handelt es sich um die Schreibberechtigung von /etc/kopens/conf. Die Diagnose findet sich nicht in der Antwort, sondern im Server-Log:
[HA] write-authority persist to app.properties failed — applied live only
since ist der «Zeitpunkt der letzten erfolgreichen Steuerungsübergabe». Dieser Wert wird nur aktualisiert, wenn sich der Wert tatsächlich ändert; beim Senden desselben Wertes erfolgt keine Aktualisierung.
Audits werden immer erstellt — bei Änderung WARN, bei Unverändertheit INFO mit Anhäng «[no-op]», beide enthalten IP und Konto des Aufrufers.
[HA][AUDIT] write-authority false -> true by 10.0.0.5 (admin)
⚠ Übergabehistorien und Plattform-Diagnose-Pushes erfolgen nur bei Änderung. Wenn diese fehlschlagen, bleibt das Toggle dennoch intakt (nur Log-Eintrag). «Fehlt in der Historie» bedeutet nicht zwingend «nicht geändert» — Prüfen Sie den Status via status.
Zwei Zustände, die die Edge selbst meldet. Die überwachende Seite protokolliert abweichende Zustände. Die Konfiguration wird nicht automatisch korrigiert — dies muss der Operator tun.
[HA] DUAL WRITE AUTHORITY detected — both edges have edge.plc.write_authority=true.
[HA] NO WRITE AUTHORITY — both edges have edge.plc.write_authority=false.
Der hintere Zustand bedeutet, dass überall PLC-Schreibvorgänge blockiert werden — tritt auf, wenn «beide gesenkt, aber vergessen, eine wieder zu erhöhen».
Manuelles Failover-Intervent — PUT /api/v1/ha/failover/override
Das automatische Failover stoppt die Entscheidung für diese Edge und fixiert die Rolle. Die automatische Entscheidung setzt erst nach Aufhebung wieder ein.
role | Bedeutung |
|---|---|
"WRITER" | Heartbeat senden und Berechtigung halten |
"STANDBY" | Schreiben stoppen und Berechtigung freigeben |
null | Intervention aufheben — Rückkehr zur automatischen Entscheidung |
{ "data": { "override": "WRITER", "previous": null }, "meta": { "status": "OK" } }
- Nur im
autoModus verfügbar. Wenn kein automatisches Failover läuft, erfolgt eine 409NOT_AUTO_MODE— dawrite-authorityin beiden Modi funktioniert, nutzen Sie diesen bei manuellem Betrieb. WRITER·STANDBYsind case-insensitive.- Wenn der Wert nicht einem der drei entspricht, erfolgt eine 400
VALIDATION_FAILEDmitfield_errors:[{field:"role", reason:"invalid_value"}].
Zeitsynchronisation — POST /api/v1/system/time/sync
{ "platform_time": 1789780169674 }
Epoch-Millisekunden (positiv), akzeptiert Zahlen oder Zahlen-Strings. Ohne platform_time erfolgt die Synchronisation über die in der Box konfigurierte NTP-Quelle — das Senden eines Wertes und das Weglassen sind unterschiedliche Operationen.
Die Antwort erfolgt im Format {"applied":true,"method":"system","before":…,"after":…}. Ein Vergleich von before/after zeigt, wie stark die Zeit tatsächlich abgewichen war.
Da es sich um einen systemverändernden Aufruf handelt, wird zuerst die Authentifizierung geprüft. Ohne Schlüssel oder Session erfolgt 401; ist die Authentifizierung korrekt, aber der Body fehlerhaft, folgt 400 VALIDATION_FAILED. Verwechseln Sie dies nicht mit APIs, bei denen die Reihenfolge umgekehrt ist — ein 401 bei fehlerhaftem Body liegt nicht am Body.
2.7 Backup · Recovery — 2
| Methode | URL | Beschreibung |
|---|---|---|
| POST | /api/v1/connect/backup | Alle OPC + alle Tags in einem JSON |
| POST | /api/v1/connect/restore | Dieses JSON anwenden (Upsert) |
Symmetrisch — die Antwort von Backup kann direkt als Body für Restore gesendet werden. Prüfen Sie unbedingt skipped[] in der Restore-Antwort: Selbst wenn einzelne Zeilen fehlschlagen, schlägt nicht der gesamte Prozess fehl, sondern Zeilen werden übersprungen und der Zähler reduziert.
2.8 Formel — 1
| Methode | URL | Beschreibung |
|---|---|---|
| POST | /api/v1/tags/formula/preview | Ergebnis einer Formel vor der Speicherung prüfen — Formel |
2.9 Nicht-authentifizierte Schnittstellen
Nur dieses Netz durchläuft ApiAuthFilter. Es gibt keine OPC- oder Tag-Informationen aus.
GET /api/v1/system/health · GET /api/v1/system/ready · GET /api/v1/system/version · GET /api/health (Alias ohne Version)
Dieses Netz gibt 200 zurück, selbst wenn X-API-Key fehlt oder falsch ist. Daher eignet es sich nicht, um zu prüfen, ob ein Schlüssel korrekt geändert wurde — nutzen Sie hierfür Pfade, die Schlüssel erfordern, wie GET /api/v1/opc.
/health prüft acht Felder. Wenn alle acht UP sind, erfolgt status=UP + 200, ansonsten status=DEGRADED + 503.
{
"data": {
"status": "UP",
"uptime_ms": 1720008,
"components": {
"cassandra": "UP", "redis": "UP", "mqtt": "UP", "node_red": "UP",
"opc_ua": "UP", "edge_core": "UP", "tse": "UP", "grafana": "UP"
}
},
"meta": { "timestamp": 1778925572946, "request_id": "..." }
}
🔴 Auch bei 503 wird components unverändert übertragen. Dies ist die einzige Diagnosemöglichkeit — wer den Body aufgrund des Statuscodes verwirft, kann nicht feststellen, welches der acht Felder ausgefallen ist.
Die folgenden drei Felder werden separat erläutert, um Missverständnisse zu vermeiden.
| Feld | Tatsächliche Prüfung |
|---|---|
edge_core | Es handelt sich um readiness — ob Collector/Monitor gestartet sind. Während eines Boot-Race ist dies DOWN, weshalb Health 503 ist. Es ist unabhängig von der Plattform-Verbindung und daher auch für STANDALONE-Boxen relevant |
opc_ua | Der Port ist nicht fix. Er folgt opc.ua.server.tcp.port von app.properties (Dev-Box 4840, Standard 12000). Da früher 12000 hardcodiert war, wurden Dev-Boxen fälschlicherweise als dauerhaft DOWN gemeldet |
grafana | Ab Image 2026-07 ist dies HTTPS. Bei Abfrage via Plain-TCP hinterlässt Grafana alle 10 Sekunden TLS handshake error … EOF im Log — daher nutzt die Box zuerst eine Probe, die den Handshake vollständig durchführt, und hält die TCP-Prüfung als Fallback für alte Stacks bereit |
cassandra(9042) · redis(6379) · mqtt(1883) · node_red(1880) · tse(7800) sind TCP-Prüfungen auf fixen Ports.
Beispiel für /version:
{
"data": {
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "20260523",
"image_tag": "2026-20260523",
"container_mode": true
},
"meta": { "timestamp": 1778925572946, "request_id": "..." }
}
OTA upgrade.sh prüft /api/health über 300 Sekunden, um einen automatischen Rollback zu entscheiden; Docker HEALTHCHECK nutzt ebenfalls diesen Pfad.
2.10 Nicht-API-Endpunkte — /ui/opcua/* · /config/*
Es gibt Pfade, die nur vom Frontend genutzt werden. Neue Clients sollen diese nicht verwenden.
/ui/opcua/info·/tree·/history— Nur für die integrierte OPC UA Server-Oberfläche. Die offizielle v1 API des Produkts hat diese als obsolet erklärt. Der Code antwortet noch, ist aber kein verbindlicher Vertrag./config/load·/save·/restart·/reboot·/upgrade·/firmware·/backup·/temp-clean·/platform/test— Pfade der Administrationsseite. Nutzen Sie stattdessen/api/v1/system/*, das die gleiche Funktion erfüllt und dem v1-Standard für Envelopes und Fehlercodes folgt./api/http(APIController) — Obsolet. Nutzen SiePOST /api/v1/tag/{tagId}/value.
3. Request / Response Beispiele
Alle folgenden Beispiele verwenden den aktuellen Envelope. Beispielhafte Formen wie {"result":"OK", ...} aus alten Dokumenten wurden entfernt, da sie nicht mehr mit dem Produkt übereinstimmen — Clients, die so implementiert wurden, funktionieren nicht mehr.
3.1 OPC-Registrierung
curl -sk -X POST "https://<엣지>/api/v1/opc" \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"site_id": "SITE_00001",
"opc_id": "OPC_LS_XBM_0001",
"opc_name": "1라인 LS XBM",
"opc_type": "LS",
"opc_agent_ip": "192.168.0.10",
"opc_agent_port": 2004,
"timecycle": 1000,
"tag_list": [
{ "site_id": "SITE_00001", "opc_id": "OPC_LS_XBM_0001",
"tag_id": "TAG_LS_XBM_0001", "tag_name": "1호기 토출압력",
"data_type": "Float", "plc_address": "%MW100" }
]
}'
tag_list kann weggelassen werden — dann wird nur die OPC erstellt.
201 + Location: /api/v1/opc/OPC_LS_XBM_0001:
{
"data": { "info": { "opc_id": "OPC_LS_XBM_0001", "opc_name": "1라인 LS XBM" } },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
opc_agent_port und timecycle können als Zahlen gesendet werdenDa die entsprechenden Spalten im Speicher Strings sind, konvertiert der Server diese. Es ist normal, Werte als Zahlen zu senden und als Strings zu empfangen.
3.2 Löschung
{
"data": { "deleted": true, "opc_id": "OPC_X" },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
DELETE antwortet ebenfalls mit 200 + Body. Auch wenn in Swagger-Kommentaren 204 steht, ist das tatsächliche Verhalten 200 — in v1 gibt jede Methode einen Envelope zurück.
3.3 Fehlerantworten
POST /api/v1/opc/OPC_NONE/start — 404:
{
"meta": { "timestamp": 1778925572946, "status": "ERROR", "request_id": "..." },
"errors": [ { "code": "OPC_NOT_FOUND", "message": "OPC OPC_NONE 가 존재하지 않습니다." } ]
}
data fehlt komplett (das Feld null wird bei der Serialisierung weggelassen). Unterscheiden Sie Erfolg und Fehler nicht an der Präsenz von data, sondern am HTTP-Status oder an meta.status.
3.4 Mehrere Tag-Werte gleichzeitig
curl -sk -X POST "https://<엣지>/api/v1/tag-values:read" \
-H "X-API-Key: <key>" -H "Content-Type: application/json" \
-d '{ "tag_ids": ["TAG_LS_XBM_0001", "TAG_LS_XBM_0002"] }'
{
"data": [
{ "tag_id": "TAG_LS_XBM_0001", "value": "1234", "timestamp": "2026-05-15T11:32:45.000Z", "quality": "SUCCESS" },
{ "tag_id": "TAG_LS_XBM_0002", "value": null, "timestamp": null, "quality": "PENDING" }
],
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
🔴 Beachten Sie quality. PENDING bedeutet nicht «Wert ist 0», sondern «noch nie gelesen». Wenn dies als 0 interpretiert wird, wird eine nie befragte Anlage als «Normal» gemeldet.
Leere Arrays oder mehr als 100 Einträge führen zu 400(MISSING_FIELD / TOO_MANY).
3.5 Neustart
{
"data": { "message": "reboot scheduled in 5 seconds" },
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
Status 202 — bedeutet nicht «erledigt», sondern «wird erledigt». Die Box ist zum Zeitpunkt der Antwort noch aktiv.
3.6 Noch nicht bereit
GET /api/v1/system/ready — 503:
{
"meta": { "timestamp": 1778925572946, "status": "ERROR", "request_id": "..." },
"errors": [ { "code": "NOT_READY", "message": "엣지의 일부 구성요소가 준비되지 않았습니다." } ]
}
3.7 Symmetrie von Backup und Restore
{
"data": {
"opcs": [ { "opc_id": "OPC_LS_XBM_0001", "opc_name": "..." } ],
"tags": [ { "tag_id": "TAG_LS_XBM_0001", "opc_id": "OPC_LS_XBM_0001" } ],
"count": { "opc": 1, "tag": 1 },
"exported_at": 1778925572946
},
"meta": { "timestamp": 1778925572946, "status": "OK", "request_id": "..." }
}
Senden Sie data einfach als Body für POST /api/v1/connect/restore. Prüfen Sie unbedingt skipped[] in der Restore-Antwort — selbst wenn einzelne Zeilen fehlschlagen, bleibt der Status 200, und nur die übersprungenen Einträge werden dort gelistet. Wer nur auf count achtet, übersieht eine unvollständige Wiederherstellung.
4. Fehlercodes
🔴 v1 kommuniziert Fehler über den HTTP-Status. Die alte RPC-Methode (Verpackung von {result:"ERROR"} in HTTP 200) wurde eingestellt. Clients, die den Statuscode ignorieren und im Body nach result suchen, interpretieren jeden Fehler als Erfolg.
Die Entscheidung erfolgt über entweder den HTTP-Statuscode oder meta.status ("OK" / "ERROR"). Es ist nicht nötig, nach errors[] zu suchen; eine einzige Key-Prüfung genügt.
| Code | HTTP | Bedeutung |
|---|---|---|
MISSING_BODY | 400 | Request-Body ist leer |
MISSING_FIELD | 400 | Erforderliche Felder fehlen |
INVALID_JSON | 400 | JSON-Parsing fehlgeschlagen |
INVALID_RANGE | 400 | Bereichsverletzung page·size·from~to |
INVALID_ARGUMENT | 400 | Allgemeiner Argumentenfehler (Image/Volume Whitelist etc.) |
INVALID_NAME | 400 | Injection-Zeichen im Containernamen |
VALIDATION_FAILED | 400 | Mehrere Felder gleichzeitig fehlerhaft — siehe field_errors[] |
TOO_MANY | 400 | Batch-Limit (100 Einträge) überschritten |
RW_VIOLATION · TYPE_MISMATCH | 400 | Tag-Schreibfehler — schreibgeschützter Tag / Typkonflikt |
UNAUTHORIZED | 401 | Authentifizierung fehlt oder ungültig |
HTTPS_REQUIRED | 403 | Aufruf via Plain-HTTP erfolgt — kein Schlüsselproblem |
OPC_NOT_FOUND · TAG_NOT_FOUND · APP_NOT_FOUND | 404 | Ziel nicht gefunden |
CONTAINER_EXISTS | 409 | Container mit diesem Namen existiert bereits |
PRECONDITION_FAILED | 412 | ETag von If-Match weicht ab — Änderung durch Dritte |
START_FAILED · STOP_FAILED | 422 | OPC-Erfassungssteuerung fehlgeschlagen |
DOCKER_PULL_FAILED · DOCKER_RUN_FAILED | 502 | Docker-Befehl fehlgeschlagen |
MONITOR_NOT_READY · NOT_READY · DISCONNECTED · OPC_STOPPED | 503 | Noch nicht bereit / Unterbrochen — Request ist nicht fehlerhaft |
MISCONFIGURED | 500 | edge.rest.api.key ist leer |
INTERNAL_ERROR · *_FAILED | 500 | Sonstige interne Fehler |
DISCONNECTED · OPC_STOPPED · MONITOR_NOT_READY liegen nicht an einem fehlerhaften Request, sondern an einer aktuellen Unmöglichkeit der Ausführung. Derselbe Request sollte später erneut gesendet werden. Eine Behandlung wie 400 («Korrigieren und erneut senden») führt zu unnötigen Änderungsversuchen, obwohl kein Fehler im Request vorliegt.
5. Funktionsnotizen
- Einzelabfrage Tag:
R03vonAPP_TAG.xml(WHERE TAG_ID = :tag_id ALLOW FILTERING). - Nach OPC-Registrierung · Tag-Registrierung wird
ConnectService.restartComponents()zurückgerufen, um den Cache neu zu lesen und den OPC-UA Server neu zu starten — dies führt zu einer kurzzeitigen Unterbrechung der Erfassung unmittelbar nach der Registrierung. - Wert schreiben (
tagWriteByTagId): HTTP viaHTTPDriver.bind(), andere viadriver.write(ProtocolAddress). Bei Fehlern wird ein hilfreicher Hinweis (ok=false, z.B. EIP-Firmware-Kompatibilität) beigefügt. Das Ergebnis eines sofortigen Read-After-Write wird inactual_readzurückgegeben (außer bei HTTP). - Transfer Health (
/api/v1/transfer): SammeltgetStatus()aller inTransferRegistryregistrierten Outbound-Komponenten. Die Sparkplug B Card (/ui/main) ruft dies alle 5 Sekunden auf. - OPC UA Server (
/ui/opcua/*) nutzt internes Polling (2s) — Verwendung nur vonOPCAndTagsCache.getInstance()+LastValueMap.getInstance()zur Lastminimierung. - Legacy Deprecated:
/api/http(APIController) — Verwendung von/api/v1/tag/{tagId}/valueempfohlen.
6. Code-Positionen
- Controller:
src/plantpulse/app/edge/api/v1/—OpcAPI·TagAPI·AppAPI·SystemAPI·SystemTimeAPI·MonitoringAPI·EdgeStatusAPI·HaAPI·ConnectIoAPI·FormulaAPI - MCP-Server:
src/plantpulse/app/edge/module/mcp/— MCP-Server - Envelope · Gemeinsame Antworten:
api/v1/RestApiSupport.java·api/v1/dto/Api*.java - OPC UA Server:
src/plantpulse/app/edge/module/opcua/OPCUAViewerController.java - Upgrade-Proxy:
src/plantpulse/app/edge/module/system/config/ConfigController.java - Business-Logik:
src/plantpulse/app/edge/module/connect/ConnectService.java - Response-Envelope-Helper:
plantpulse.app.edge.core.web.ControllerSupport(ok()/error())