Zum Hauptinhalt springen

REST API (v1)

Handbuch zur v1 REST API von PlantPulse Edge. Standardschnittstelle, über die Oberflächen und Fremdsysteme OPC, Tags, Monitoring und Healthchecks der externen Übertragung ansprechen.

PunktWert
Base URLhttp(s)://<edge-host>/api/v1
Content-Typeapplication/json; charset=utf-8
AuthentifizierungAPI Key erzwungen (edge.rest.api.auth=true) — X-API-Key / Authorization: Bearer, oder Durchlass per Login-Session. ?api_key= wird mit 401 abgewiesen
Audit-LogApiAccessLogFilter — protokolliert jeden /api/*-Aufruf in einer Zeile (Methode/Pfad/Status/Dauer/IP/Benutzer/auth-Modus)
Haupt-Controllerplantpulse.app.edge.api.v1.* (OpcAPI, TagAPI, AppAPI, SystemAPI, MonitoringAPI)
Fehler-Envelope{data, meta, errors} — auch auf Filter-/Advice-Pfaden identisch
HTTPSedge.rest.api.https_only=true standardmäßig. Aufrufe von Clients über reines HTTP werden abgewiesen oder folgen der HTTPS-Redirect-Policy von Tomcat

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.

Der Zugriff wird gewährt, wenn mindestens eine der folgenden 3 Bedingungen erfüllt ist:

A. Durchlass per Login-Session (Browser)

Benutzer, die bereits über /login/form angemeldet sind — enthält die Session das Attribut _USER_LOGIN (JSONObject), führt der ApiAuthFilter direkt chain.doFilter() aus. Damit funktionieren alle per Ajax aus der Oberfläche (JSP) aufgerufenen /api/* ohne zusätzlichen Schlüssel.

B. Header X-API-Key (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. Header Authorization: Bearer <key>

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. Query-Parameter ?api_key=<key> — abgewiesen

Da der Schlüssel im Klartext in der URL steht und so in Access-Logs, Proxy-Caches oder im Browser-Verlauf verbleiben kann, wird er in v1 mit 401 abgewiesen. Verwenden Sie die Header-Varianten (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 per constantTimeEquals() (Schutz vor Timing-Angriffen).

edge.rest.api.auth=false (Entwicklung / temporär)

Ist die Authentifizierung deaktiviert, führt der ApiAuthFilter sofort chain.doFilter() aus — externe Aufrufe sind ohne Schlüssel möglich. Im Produktivnetz stets true empfohlen.


Audit-Log (ApiAccessLogFilter)

Protokolliert jeden /api/*-Aufruf in einer Logzeile — auch fehlgeschlagene Authentifizierungen (401) werden erfasst und hinterlassen einen Audit-Trail (Reihenfolge der Filterkette: 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...
FeldBedeutung
methodHTTP-Methode
pathRequest-URI (ohne Query)
queryQuery-String — auch der abgewiesene ?api_key=...-Teil wird mit *** maskiert
statusHTTP-Antwortcode
durationVerarbeitungszeit (ms)
ipStandardmäßig RemoteAddr. Nur bei Reverse-Proxy-Betrieb mit edge.rest.api.trust_x_forwarded_for=true die erste IP aus X-Forwarded-For
userBei Session-Benutzer user_id / bei apikey XXXX*** (len=16) (erste 4 Zeichen + Länge) / bei anonym -
authsession (Browser-Login) / apikey (X-API-Key/Bearer) / deprecated_query (abgewiesener ?api_key=-Versuch) / none (Schlüssel fehlt oder anonym)
uaUser-Agent (bei mehr als 80 Zeichen mit abgeschnitten)
errorBei Exception wird error=ClassName ergänzt

Verzweigung der Log-Level

StatusLevelZweck
5xx oder throwableERRORServerfehler — sofortiger Alarm
401, 403WARNAuthentifizierungs-/Berechtigungsablehnung — Audit hat Vorrang
Sonstige (2xx, 3xx, allgemeine 4xx)INFONormale Aufrufe

Anwendung

  • Wer sendet falsche Schlüssel — 401-Trail per grep '\[API_ACCESS\].*status=401' prüfen
  • 5xx-Regressionen nachverfolgengrep '\[API_ACCESS\].*status=5' + Feld error=
  • Aufrufhäufigkeit / Auswertung pro Benutzer — Gruppierung nach auth=apikey/session, Analyse pro ip
  • Langsame Antworten / Slow Queries — Zeilen mit hohem duration= extrahieren

Der Schlüssel erscheint nirgends im Klartext im Log — Query-Maskierung plus die ersten 4 Zeichen im user-Feld genügen zur Identifikation. Ein ausgewogenes Verhältnis aus ausreichender Identifizierbarkeit für die Vorfallanalyse und Schutz vor Klartextoffenlegung.

Die Reihenfolge der Filterkette ist entscheidend

Mapping-Reihenfolge in web.xml: zuerst ApiAccessLogFilter, danach ApiAuthFilter. Da der AccessLog den Status im finally-Block protokolliert, werden auch mit 401 abgelehnte Aufrufe vollständig auditiert. Bei umgekehrter Reihenfolge fehlen 401-Aufrufe im Access-Log.


Antwort-Envelope

Antworten der v1 REST API werden in einem gemeinsamen Envelope gekapselt (RestApiSupport, ApiEnvelopeWriter).

Normale 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"
}
]
}

Der alte Envelope im RPC-Stil (result, _session_id, _user_id) wird in REST v1 nicht mehr verwendet.


1. Übersicht der Endpunkte

1.1 OPC

SlugMethodURLBeschreibung
flow_edge_opc_listGET/api/v1/opcOPC-Liste (inkl. tag_count / connection_status / scan_status)
flow_edge_opc_createPOST/api/v1/opcOPC + Tags gebündelt anlegen
flow_edge_opc_updatePUT/api/v1/opc/{opcId}OPC + Tags gebündelt aktualisieren (opcId aus dem Pfad überschreibt opc_id im Body)
flow_edge_opc_deleteDELETE/api/v1/opc/{opcId}OPC und alle untergeordneten Tags gemeinsam löschen
flow_edge_opc_startPOST/api/v1/opc/{opcId}/startErfassung starten
flow_edge_opc_stopPOST/api/v1/opc/{opcId}/stopErfassung stoppen

1.2 Tag

SlugMethodURLBeschreibung
flow_edge_tag_listGET/api/v1/opc/{opcId}/tagTag-Liste eines bestimmten OPC (inkl. letztem Wert/Status)
flow_edge_tag_createPOST/api/v1/opc/{opcId}/tagEinzelnen Tag anlegen
flow_edge_tag_updatePUT/api/v1/tag/{tagId}Einzelnen Tag teilweise aktualisieren (Merge mit bestehender Row, dann Upsert)
flow_edge_tag_deleteDELETE/api/v1/tag/{tagId}Einzelnen Tag löschen
flow_edge_tag_readGET/api/v1/tag/{tagId}/value[?fresh=true]Cache-Wert (default), bei fresh=true direkter Read von der PLC
flow_edge_tag_writePOST/api/v1/tag/{tagId}/valueWert schreiben — alle Protokolle unterstützt (HTTP / OPC-UA / Modbus / MELSEC / S7 / LS / EIP)

1.3 Monitoring / Edge / Transfer

SlugMethodURLBeschreibung
flow_edge_monitoringGET/api/v1/monitoringAlle Felder von MonitorBean wie CPU/Speicher/Netzwerk/Festplatte/Threads
flow_edge_infoGET/api/v1/edgeMetadaten zu Edge-Identifikation/Version/Standort/OS/Betriebszeit
flow_edge_transferGET/api/v1/transferHealth-Status je outbound transfer (API/MQTT/Sparkplug)

1.3a System (keine Authentifizierung — für LB / k8s-Probe / OTA)

Von /api/v1/system/* passieren die folgenden 3 die Whitelist ApiAuthFilter (es werden keinerlei OPC-/Tag-Informationen offengelegt).

MethodURLBeschreibungHTTP
GET/api/v1/system/health8 Komponenten (cassandra / redis / mqtt / node_red / opc_ua / edge_core / tse / grafana) TCP-Probe mit 200 ms + Gesamtstatus200 (all UP) / 503 (DEGRADED)
GET/api/v1/system/readyBereitschaftsstatus von monitor + V5 api_client200 / 503
GET/api/v1/system/versionMetadata.VERSION + BUILD_DATE + image_tag aus /etc/kopens/version.env + container_mode aus /.dockerenv200

Beispiel /health:

{
"data": {
"status": "UP",
"uptime_ms": 1720008,
"components": {
"cassandra": "UP", "redis": "UP", "mqtt": "UP",
"node_red": "UP", "opc_ua": "UP"
}
}
}

Beispiel /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 entscheidet anhand der /api/health-Probe (300 Sekunden) über ein Auto-Rollback. Auch der Docker HEALTHCHECK verwendet /health.

1.4 OPC-UA Viewer (OPCUAViewerAPI)

Endpoint / Knotenbaum / Zeitreihe des integrierten OPC-UA-Servers — wird vom Bildschirm /ui/opcua verwendet.

MethodURLBeschreibung
GET/ui/opcua/info[?reveal=true]Endpoint-URL (TCP/TLS) / Application-Name / Authentifizierungsdaten des integrierten OPC-UA-Servers (reveal=true + bei authentifizierter Session Passwort im Klartext)
GET/ui/opcua/treeBaum Site → OPC → Tag (inkl. NodeId). Das System-OPC EDGE_* ist immer connection_status=CONNECTED
GET/ui/opcua/history?tagId=...&minutes=N&limit=MZeitreihenabfrage aus Cassandra tm_tag_point. Liefert nach Eingabeprüfung ein aufsteigend sortiertes points-Array

1.5 Upgrade / System (ConfigAPI)

/config/* ist von der v1 REST API getrennt (für die Administrationsoberfläche), wird hier aber der Vollständigkeit halber mit dokumentiert.

MethodURLBeschreibung
GET/config/upgrade/checkHolt server-to-server die VERSION.JSON von product.kopens.io und liefert {result, latest_version, latest_build_date} zurück. Bei Fehlschlag result=ERROR
POST/config/upgradeFührt bin/upgrade.sh aus (langlaufender Vorgang)
POST/config/restartFührt bin/restart.sh aus (Tomcat-Neustart)
POST/config/rebootFührt bin/reboot.sh aus (OS-Neustart)
POST/config/temp-cleanFührt bin/clean.sh aus (Bereinigung von Logs/temporären Dateien)
POST/config/backupFührt bin/backup.sh aus
POST/config/firmwareFührt bin/firmware.sh aus (dnf update -y)
GET/config/loadLiefert app.properties als Text
POST/config/saveSpeichert app.properties

2. Beispiele für Request / Response

2.1 GET /api/v1/opc — OPC-Liste

curl -s http://<edge-host>/api/v1/opc | jq
{
"result": "OK",
"data": {
"data": [
{
"opc_id": "OPC_UA_Kepware",
"opc_type": "OPCUA",
"opc_name": "OPC_UA_Kepware",
"opc_agent_ip": "192.168.0.40",
"opc_agent_port": "49320",
"auto_collect": "true",
"timecycle": "1000",
"options": {"username": "kopens", "password": "***", "discovery": "false"},
"tag_count": 11,
"point_count": 5819,
"connection_status": "CONNECTED",
"scan_status": "START"
}
]
}
}

⚠ Form data.data[] (doppeltes Wrapping) — siehe Abschnitt Envelope oben.

2.2 POST /api/v1/opc — OPC + Tags gebündelt anlegen

curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_NEW",
"opc_type": "OPCUA",
"opc_name": "신규 연결",
"opc_agent_ip": "10.0.0.10",
"opc_agent_port": "49320",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"tag_list": [
{"tag_id": "TAG_001", "tag_name": "Sine",
"plc_address": "ns=2;s=Sine1", "data_type": "Float"}
]
}'

2.3 POST /api/v1/opc/{opcId}/tag — Einzelnen Tag anlegen

{
"tag_id": "TAG_NEW",
"tag_name": "신규 태그",
"plc_address": "ns=2;s=NewTag",
"data_type": "Float",
"description": "..."
}

Antwort:

{ "result": "OK", "data": { "result": "SUCCESS", "tag_id": "TAG_NEW" } }

site_id wird automatisch aus dem OPC übernommen.

2.4 PUT /api/v1/tag/{tagId} — Tag teilweise aktualisieren

Im Request-Body werden nur die zu ändernden Felder gesendet. Der Server merged mit der bestehenden Row und führt anschließend ein Upsert aus.

{ "description": "변경된 설명" }

2.5 GET /api/v1/tag/{tagId}/value — Letzten Wert abfragen

Standard (Cache):

curl -s http://<edge-host>/api/v1/tag/TAG_UA_0004/value | jq
{
"result": "OK",
"data": {
"tag_id": "TAG_UA_0004",
"value": "28.9688",
"value_time": "2026-05-06 20:59:39.000",
"value_read_status": "SUCCESS",
"value_read_error_message": ""
}
}

fresh=true (direkter Read von der PLC):

curl -s 'http://<edge-host>/api/v1/tag/TAG_UA_0004/value?fresh=true' | jq

Bei fehlgeschlagenem PLC-Read äußeres result=ERROR + message:

{ "result": "ERROR", "message": "fresh read failed: ..." }

⚠ Auch die Abfrage eines nicht existierenden Tags antwortet mit Envelope result=OK und liefert eine Platzhalter-Payload zurück, in der value mit "-" gefüllt ist (aktuelles Verhalten).

2.6 POST /api/v1/tag/{tagId}/value — Wert schreiben

curl -X POST http://<edge-host>/api/v1/tag/TAG_HTTP_001/value \
-H "Content-Type: application/json" \
-d '{ "value": "42" }'

Erfolg:

{
"result": "OK",
"data": {
"result": "SUCCESS",
"opc_id": "OPC_LS_TEST",
"plc_address": "D1000",
"value": "42",
"actual_read": "42"
}
}

Fehlschlag (Treiber liefert false / OPC DISCONNECTED / Scheduler nicht aktiv) — äußeres Envelope result=ERROR:

{ "result": "ERROR", "message": "쓰기 실패 (opc_type=EIP) — EIP 펌웨어/패치에 따라 ..." }

Unterstützte Protokolle: HTTP / OPC-UA / Modbus / MELSEC / S7 / LS XGT / EIP — alle möglich. Bei EIP sind je nach PLC-Firmware/Patch Einschränkungen möglich.

2.7 POST /api/v1/opc/{opcId}/start — Erfassung starten

curl -X POST http://<edge-host>/api/v1/opc/OPC_NEW/start

Ein automatischer Start der integrierten OPCUA-/MODBUS-Simulatoren wird nicht unterstützt. Verwenden Sie für Test-/Demodaten externe plantpulse-simulator oder Test-Tags.

2.8 GET /api/v1/edge — Edge-Identifikation / Betriebsinformationen

Felder, die EdgeAPI.edgeInfo() direkt befüllt:

{
"result": "OK",
"data": {
"id": "EDGE_00303",
"site_id": "SITE_00001",
"site_name": "S1_LOTTE_CS_DJ_SITE",
"hostname": "EDGE-303",
"product_name": "PlantPulse Edge",
"version": "2026",
"build_date": "2026-05-08",
"os_name": "Linux",
"os_version": "6.14.5-100.fc40.x86_64",
"os_arch": "amd64",
"started": true,
"started_date": 1778066700000,
"uptime_ms": 124500,
"always_on": false,
"ttl": 30,
"sended_count": 8063
}
}

2.9 GET /api/v1/monitoring — Systemmetriken

Serialisiert alle Felder von MonitorBean unverändert. Aktualisierung im Sekundentakt.

curl -s http://<edge-host>/api/v1/monitoring | jq '.data | keys'

Wichtige Felder: cpu_used_percent, memory_used_percent, disk_used_percent, temperature, ping, api, opc_count, tag_count, mps, mps_history, queue_size, plc_value_read_success_count, plc_value_read_error_count, plc_value_write_success_count, plc_value_write_error_count, plc_con_connected_count, plc_con_disconnected_count, plc_scan_start_count, plc_scan_not_collect_count, plc_scan_stop_count, sended_point_count, sended_point_bytes, system_total_db_size, system_error_count, docker_on, docker_container_up_count, docker_container_total_count.

2.10 GET /api/v1/transfer — Health der externen Übertragung

curl -s http://<edge-host>/api/v1/transfer | jq
{
"result": "OK",
"data": {
"transfers": [
{ "type": "API", "enabled": true, "connected": true, "sent_count": 8063 },
{ "type": "MQTT", "enabled": true, "connected": true, "sent_count": 12345 },
{ "type": "Sparkplug", "enabled": true, "connected": true,
"group_id": "Plant1", "edge_node_id": "EDGE_00303",
"bdSeq": 7, "seq": 211 }
]
}
}

Gibt das getStatus()-Ergebnis jedes transfer unverändert als Array zurück. Tritt während des Aufrufs eine Exception auf, erfolgt ein Fallback auf {type, status_error}.

2.11 GET /ui/opcua/info — Informationen zum integrierten OPC-UA-Server

curl -s http://<edge-host>/ui/opcua/info | jq
{
"result": "OK",
"data": {
"application_name": "PlantPulse Edge OPC-UA Server",
"product_uri": "urn:plantpulse:opcua:server",
"domain": "127.0.0.1",
"tcp_endpoint": "opc.tcp://127.0.0.1:12000",
"tls_endpoint": "opc.tcp://127.0.0.1:12443",
"namespace_index": 2,
"auth_username": "edge",
"auth_password_set": true,
"auth_anonymous": false
}
}

Bei ?reveal=true und authentifizierter Session wird auth_password zusätzlich im Klartext ausgegeben.

2.12 GET /ui/opcua/tree — Knotenbaum

Dreistufiger Baum Site → OPC → Tag. Jeder Tag enthält NodeId, aktuellen Wert, Datentyp und description. Für EDGE-System-OPCs wird connection_status=CONNECTED erzwungen.

NodeId-Konvention: ns=2;s=<SITE>.<OPC>.<TAG>.

2.13 GET /ui/opcua/history — Zeitreihe

ParameterStandardGrenzwert
tagIderforderlichmax. 200 Zeichen
minutes101 – 1440 (24 h)
limit6001 – 5000
curl -s 'http://<edge-host>/ui/opcua/history?tagId=TAG_S7_1000&minutes=10&limit=100' | jq
{
"result": "OK",
"data": {
"tag_id": "TAG_S7_1000",
"minutes": 10,
"count": 100,
"points": [
{ "ts": 1778176880010, "value": "211", "type": "integer", "quality": 192 }
]
}
}

Fehlt tagId, ist leer oder länger als 200 Zeichen, folgt äußeres result=ERROR (message). Auch wenn der Cassandra-Timestamp als Wrapper-Objekt eintrifft, wird er per extractTimestampMs in Epoch-ms umgewandelt.


3. Muster von Fehlerantworten

FallAntwortform
Äußeres Envelope ERROR (Schreibfehler / fehlgeschlagener Fresh-Read / fehlgeschlagene Eingabeprüfung bei opcua/history / fehlgeschlagener Download bei upgrade/check){result:"ERROR", message:"..."} (HTTP 200)
Internes fachliches ERROR (bei CRUD: tag/opc not found / DB-Integrität / Scheduler nicht aktiv){result:"OK", data:{result:"ERROR", msg:"..."}} (HTTP 200)
Fehlerhaftes Spring-Mapping (fehlender Pflichtparameter, falsche Methode usw.)HTTP 500 + Stacktrace — empfohlen: im Controller @RequestParam(required=false) behandeln und in ein Envelope umwandeln
Authentifizierung — edge.rest.api.auth ist der Installationsstandard true, und ApiAuthFilter verlangt X-API-Key / Bearer (siehe Abschnitt Authentifizierung oben). Nur die Health-Endpunkte sind ausgenommen

4. Hinweise zum Verhalten

  • Abfrage eines einzelnen Tags: R03 von APP_TAG.xml (WHERE TAG_ID = :tag_id ALLOW FILTERING).
  • Nach gebündeltem Anlegen von OPC + Tags / Anlegen eines einzelnen Tags wird ConnectService.restartComponents() aufgerufen → Reload von OPCAndTagsCache und Neustart des OPC-UA-Servers.
  • Wert schreiben (tagWriteByTagId): bei HTTP HTTPDriver.bind(), sonst driver.write(ProtocolAddress). Bei Fehlschlag (ok=false) inklusive hilfreicher Hinweise (z. B. EIP-Firmware-Kompatibilität). Das unmittelbar nach dem Schreiben gelesene Ergebnis wird zusätzlich als actual_read zurückgegeben (außer bei HTTP).
  • Transfer-Health (/api/v1/transfer): sammelt getStatus() aller in TransferRegistry registrierten outbound-Komponenten. Die Sparkplug-B-Karte (/ui/main) ruft den Endpunkt alle 5 Sekunden auf.
  • Der OPC-UA Viewer (/ui/opcua/*) pollt im internen Netz (2 Sekunden) — nutzt nur OPCAndTagsCache.getInstance() + LastValueMap.getInstance() und erzeugt daher geringe Last.
  • Legacy, deprecated: /api/http (APIController) — Verwendung von /api/v1/tag/{tagId}/value empfohlen.

5. Codepfade

  • Controller: src/plantpulse/app/edge/api/v1/OpcAPI.java · api/v1/TagAPI.java
  • OPC-UA Viewer: src/plantpulse/app/edge/module/opcua/OPCUAViewerController.java
  • Upgrade-Proxy: src/plantpulse/app/edge/module/system/config/ConfigController.java
  • Geschäftslogik: src/plantpulse/app/edge/module/connect/ConnectService.java
  • Helper für Antwort-Envelope: plantpulse.app.edge.core.web.ControllerSupport (ok() / error())