Zum Hauptinhalt springen

Eigene Daten über Pipeline-Endpunkte einbringen

Bisher haben Sie Daten gelesen. In diesem Tutorial senden Sie Daten nach OctoMesh, zum Beispiel die Messwerte Ihres eigenen Zählers, einer Batterie oder eines simulierten Geräts. Lösungen auf OctoMesh stellen solche Eingänge als HTTP-Endpunkte bereit, die als Pipelines auf dem Mesh Adapter umgesetzt sind. Sie lernen, wie ein solcher Endpunkt funktioniert, wie er abgesichert ist und wie Sie ihn aufrufen.

Voraussetzungen

Die Energiegemeinschafts-Endpunkte unten setzen EnergyCommunity.Base 2.11 oder neuer voraus und einen Mesh-Adapter mit den Meter-Reporting-Nodes (RegisterSelfReportedMeteringPoint@1, PrepareMeterReadings@1, CommunityEnergyBalance@1). In älteren Tenants gibt es die Routen nicht.

Test-Tenant verwenden

Anders als die Tutorials 1 bis 4 schreibt dieses Tutorial Daten. Arbeiten Sie in einem Test- oder Labor-Tenant, nicht in einem produktiven.

Das Muster: Ein HTTP-Request startet eine Pipeline​

  1. Eine Pipeline beginnt mit dem Trigger-Node FromHttpRequest@2, der einen Pfad und eine HTTP-Methode auf dem Mesh Adapter des Tenants registriert.
  2. Ein Request an https://<mesh-adapter-host>/<tenant>/<path> führt die Pipeline aus. Der JSON-Body des Requests steht den Nodes unter $.body zur Verfügung.
  3. Die Nodes validieren die Eingabe, lösen die referenzierten Entitäten auf und schreiben, zum Beispiel mit einem Node, der Werte in ein Stream-Data-Archiv speichert.
  4. Was die Pipeline in ihrem Datenkontext hinterlässt, wird als JSON-Response zurückgegeben. Gut gebaute Endpunkte reduzieren ihn auf ein kleines Ergebnisobjekt.

Ein Trigger sieht so aus:

triggers:
- type: FromHttpRequest@2
path: /meterReadings
method: POST
requiredRoles:
- MeterReporter
allowAnonymous: false

Authentifizierung​

FromHttpRequest@2 verlangt ein gültiges Access Token des Tenants und, falls requiredRoles gesetzt ist, eine der aufgeführten Rollen. Requests ohne Token, mit dem Token eines anderen Tenants oder ohne die Rolle werden mit 401 bzw. 403 abgewiesen, bevor die Pipeline läuft.

Ein Gerät oder Service hat keinen Benutzer, der sich im Browser anmelden könnte. Er authentifiziert sich als OAuth-Client mit dem Client-Credentials-Flow:

  1. Eine Administratorin oder ein Administrator des Tenants legt einen Client-Credentials-Client an und weist ihm die Rolle zu, die der Endpunkt verlangt (siehe Clients und API-Scopes). Sie erhalten eine Client-ID und ein Secret. Bewahren Sie das Secret wie ein Passwort auf.
  2. Ihr Programm fordert ein Token an. Der Parameter acr_values=tenant:<tenant> ist Pflicht: Clients werden pro Tenant gespeichert, und ohne ihn sucht der Identity Service an der falschen Stelle und antwortet mit invalid_client.
curl -s https://connect.<your-domain>/connect/token \
-d grant_type=client_credentials \
-d client_id=<client-id> \
-d client_secret="$CLIENT_SECRET" \
-d scope=octo_api \
-d acr_values=tenant:<tenant>
import os, requests

def client_token() -> str:
response = requests.post(
"https://connect.<your-domain>/connect/token",
data={
"grant_type": "client_credentials",
"client_id": os.environ["CLIENT_ID"],
"client_secret": os.environ["CLIENT_SECRET"],
"scope": "octo_api",
"acr_values": f"tenant:{os.environ['OCTO_TENANT']}",
},
timeout=30,
)
response.raise_for_status()
return response.json()["access_token"]

Das Token ist begrenzt gültig (expires_in in der Antwort). Fordern Sie ein neues an, bevor es abläuft, statt bei jedem Aufruf eines anzufordern.

Einen Endpunkt aufrufen​

import os, requests

ADAPTER = "https://<mesh-adapter-host>"
TENANT = os.environ["OCTO_TENANT"]

def call(path: str, payload: dict) -> dict:
response = requests.post(
f"{ADAPTER}/{TENANT}{path}",
json=payload,
headers={"Authorization": f"Bearer {client_token()}"},
timeout=30,
)
response.raise_for_status() # 401/403: token, tenant or role problem
result = response.json()
if result.get("status") not in ("ok", "partial"):
raise RuntimeError(result.get("message"))
return result

Antwortkonvention​

Die Energiegemeinschafts-Endpunkte antworten mit HTTP 200, sobald der Request die Pipeline erreicht hat, und melden das Ergebnis im Body:

{ "status": "ok", "message": "..." }

status ist ok, partial (einige Einträge angenommen, einige abgelehnt, mit Details) oder error. Wie bei GraphQL (siehe Tutorial 2) bedeutet HTTP 200 allein noch keinen Erfolg: Lesen Sie immer status.

Energiegemeinschafts-Endpunkte​

Die EnergyCommunity-Lösung bietet drei Endpunkte für Zählpunkte, deren Daten nicht vom Netzbetreiber kommen (zum Beispiel Ihr eigener Zähler, eine Batterie, die als zwei Zählpunkte für Laden und Entladen modelliert ist, oder ein simuliertes Gerät):

EndpunktZweck
POST /registerMeteringPointEinen Zählpunkt (Verbrauch oder Erzeugung) mit Kunde, Anlage und Teilnahme an der Gemeinschaft registrieren. Idempotent.
POST /meterReadings15-Minuten-Energiewerte (kWh) für einen oder mehrere registrierte Zählpunkte melden. Werte können bis zum täglichen Meldeschluss korrigiert werden.
POST /communityBalanceDie vorläufige Bilanz der Gemeinschaft für die zuletzt abgeschlossenen Slots lesen: Erzeugung, Verbrauch, verteilte Energie, Überschuss, Deckungsgrad. Schreibt nichts.

Gemeldete Werte landen im Rohdatenarchiv als Register 1-1:1.9.0 G.01 (Verbrauch) bzw. 1-1:2.9.0 G.01 (Erzeugung). Die Verteilungsregister G.03, G.01T und P.01T berechnet die tägliche Verteilung der Gemeinschaft; sie werden nie gemeldet (siehe Tutorial 3).

Einen Zählpunkt registrieren​

Rolle MeterReporter. Der Aufruf legt Kunde, eine Anlage, den Zählpunkt mit Datenquelle SelfReported, den Rohdaten-Anker seiner Richtung (1-1:1.9.0 G.01 Verbrauch, 1-1:2.9.0 G.01 Erzeugung) und eine Teilnahmeperiode an oder verwendet sie wieder. Er ist idempotent: Ein erneuter Aufruf mit gleicher Nummer und Richtung antwortet mit created: false und denselben rtIds und aktualisiert displayName, participationFactor und Größen; der Teilnahmebeginn verschiebt sich nie.

  • Nur Nummern aus dem reservierten Selbstmelde-Bereich (AT009998…) werden angenommen. So kann ein Client nie einen Zählpunkt des Netzbetreibers oder der Simulation übernehmen.
  • zipcode (optional) ordnet die Anlage einem Ort aus dem Blueprint Locations.Austria zu; ohne diesen Blueprint antwortet der Aufruf mit CITY_NOT_FOUND.
curl -s -X POST "https://adapter.<your-domain>/<tenant>/registerMeteringPoint" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"meteringPointNumber": "AT0099980000000010100000000000001",
"direction": "consumption",
"displayName": "Batterie Gruppe 1 (Laden)",
"participationFactor": 100,
"zipcode": 5020
}'
{
"status": "ok",
"message": "Zählpunkt registriert.",
"created": true,
"meteringPoint": {
"rtId": "6b0c…",
"anchors": [{ "obisCode": "1-1:1.9.0 G.01", "rtId": "6b0c…" }],
"participation": { "from": "2026-11-13", "factor": 100 }
}
}

Zählerstände melden​

Rolle MeterReporter. Werte werden über Zählpunktnummer und Richtung adressiert, nie über die rtId; einen OBIS-Code geben Sie nicht an. Jeder Wert deckt genau einen 15-Minuten-Slot im UTC-Raster ab und wird mit Datenqualität L1 im Rohregister seiner Richtung gespeichert. Bis zum täglichen Meldeschluss (die Stichzeit der Verteilung, standardmäßig 06:00 Europe/Vienna am Folgetag) ersetzt eine spätere Meldung eine frühere; danach ist der Tag eingefroren.

Jeder Wert wird einzeln geprüft; gültige Werte werden auch dann geschrieben, wenn andere abgelehnt werden (status: partial). Ablehnungscodes: UNKNOWN_METERING_POINT, NOT_SELF_REPORTED, OFF_GRID (nicht im 15-Minuten-Raster), OUT_OF_RANGE, IN_FUTURE, NOT_PARTICIPATING, DAY_FROZEN, DUPLICATE_SLOT. frozenUntil nennt den Zeitpunkt, bis zu dem die Daten bereits endgültig sind.

curl -s -X POST "https://adapter.<your-domain>/<tenant>/meterReadings" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"readings": [{
"meteringPointNumber": "AT0099980000000010100000000000001",
"direction": "consumption",
"values": [
{ "from": "2026-11-13T08:00:00Z", "to": "2026-11-13T08:15:00Z", "kWh": 1.25 },
{ "from": "2026-11-13T08:15:00Z", "to": "2026-11-13T08:30:00Z", "kWh": 1.10 },
{ "from": "2026-11-13T08:30:00Z", "to": "2026-11-13T08:45:00Z", "kWh": 0.95 },
{ "from": "2026-11-13T08:40:00Z", "to": "2026-11-13T08:55:00Z", "kWh": 0.80 }
]
}]
}'
{
"status": "partial",
"message": "3 of 4 values accepted.",
"accepted": 3,
"rejected": [
{ "reading": 0, "value": 3, "code": "OFF_GRID", "message": "…" }
],
"frozenUntil": "2026-11-12T23:00:00Z"
}

Die Gemeinschaftsbilanz lesen​

Rollen MeterReporter oder EnergyCommunityReporting. Nur lesend: liefert die vorläufige Bilanz der letzten abgeschlossenen Slots (slots, Standard 4, höchstens 96). Sie rechnet mit derselben Verteilungslogik wie der tägliche Lauf; ein Slot mit vollständigen Eingaben stimmt mit dem endgültigen Ergebnis überein. Mit meteringPoints enthält die Antwort zusätzlich die Register dieser Zählpunkte.

FeldBedeutung
productionKwh / consumptionKwhGesamterzeugung und -verbrauch der Gemeinschaft im Slot
allocatedKwhIn der Gemeinschaft verteilte Energie (Summe G.03)
surplusKwhNicht in der Gemeinschaft genutzte Erzeugung (Summe P.01T)
shortfallKwhNicht von der Gemeinschaft gedeckter Verbrauch
coverageAnteil des Verbrauchs, den die Gemeinschaft deckt (0–1)
reportedErwartete und erhaltene Zählpunkte, davon simuliert
simulatedIncludedfalse, wenn ein simuliertes Mitglied in den gelieferten Slots noch keinen Wert hat

Fehlende Werte zählen als 0; ist reported.received kleiner als reported.expected, ist der Slot noch unvollständig.

curl -s -X POST "https://adapter.<your-domain>/<tenant>/communityBalance" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{ "slots": 4, "meteringPoints": ["AT0099980000000010100000000000001"] }'
{
"status": "ok",
"message": "…",
"generatedAt": "2026-11-13T09:01:12Z",
"provisional": true,
"simulatedIncluded": true,
"slots": [
{
"from": "2026-11-13T08:00:00Z",
"productionKwh": 42.1,
"allocatedKwh": 31.5,
"surplusKwh": 10.6,
"consumptionKwh": 42.0,
"shortfallKwh": 10.5,
"coverage": 0.749,
"reported": { "expected": 19, "received": 19, "simulated": 16 }
}
]
}

Das Ergebnis prüfen​

Prüfen Sie nach dem Melden mit einer Stream-Data-Abfrage wie in Tutorial 2, ob Ihre Werte angekommen sind: Ermitteln Sie den Anker Ihres Zählpunkts und Registers und lesen Sie die gemeldeten Slots.

Durchgängig macht ein Client, etwa eine Batteriesteuerung, Folgendes:

  1. Seine Zählpunkte einmal registrieren (/registerMeteringPoint, je Richtung einer: Laden = Verbrauch, Entladen = Erzeugung).
  2. Alle 15 Minuten oder gesammelt melden, spätestens einen ganzen Tag mit 96 Werten vor dem Meldeschluss (/meterReadings), und rejected prüfen.
  3. Die vorläufige Bilanz beobachten (/communityBalance), um zu entscheiden, wann geladen oder entladen wird.
  4. Am Folgetag nach dem Verteilungslauf das endgültige Ergebnis lesen: die Register 1-1:2.9.0 G.03 (Verbraucher) bzw. 1-1:2.9.0 G.01T / P.01T (Erzeuger) des Zählpunkts per GraphQL, wie in Tutorial 3.

Gute Praxis für Clients​

  • Ganze Slots im Raster senden. 15-Minuten-Fenster beginnen zu :00, :15, :30 und :45, mit expliziter Zeitzone (Z oder ein Offset).
  • Bündeln. Viele Werte pro Aufruf senden statt eines Aufrufs pro Wert.
  • Sicher wiederholen. Ein erneut gemeldeter Slot ersetzt den Wert bis zum Meldeschluss, Wiederholungen nach einem Timeout sind also unschädlich.
  • Secrets aus dem Code heraushalten. Das Client Secret aus der Umgebung oder einem Secret Store lesen, nie committen, nie ein Token loggen.
  • Die Antwort beobachten. status und message protokollieren; bei error und bei abgelehnten Einträgen in partial-Antworten alarmieren.

Einen eigenen Endpunkt bauen​

Jeder Tenant mit einem Mesh Adapter kann eigene Endpunkte bereitstellen. Schreiben Sie eine Pipeline mit FromHttpRequest@2, schützen Sie sie mit requiredRoles, validieren Sie die Eingabe mit If@1-Nodes, schreiben Sie mit den Save-Nodes Ihres Ziels (Runtime-Entitäten oder Archive) und formen Sie die Antwort mit Project@1. Siehe Data Flows und Nodes und Pipeline-Identität.

Zusammenfassung​

  • Pipeline-Endpunkte sind HTTP-Routen auf dem Mesh Adapter, definiert durch einen FromHttpRequest@2-Trigger.
  • Geräte und Services authentifizieren sich mit Client Credentials; der Token-Request braucht acr_values=tenant:<tenant>, der Client die Rolle des Endpunkts.
  • Antworten sind HTTP 200 mit einem Feld status; lesen Sie es.