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.
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.
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
- Eine Pipeline beginnt mit dem Trigger-Node
FromHttpRequest@2, der einen Pfad und eine HTTP-Methode auf dem Mesh Adapter des Tenants registriert. - Ein Request an
https://<mesh-adapter-host>/<tenant>/<path>führt die Pipeline aus. Der JSON-Body des Requests steht den Nodes unter$.bodyzur Verfügung. - 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.
- 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:
- 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.
- 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 mitinvalid_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):
| Endpunkt | Zweck |
|---|---|
POST /registerMeteringPoint | Einen Zählpunkt (Verbrauch oder Erzeugung) mit Kunde, Anlage und Teilnahme an der Gemeinschaft registrieren. Idempotent. |
POST /meterReadings | 15-Minuten-Energiewerte (kWh) für einen oder mehrere registrierte Zählpunkte melden. Werte können bis zum täglichen Meldeschluss korrigiert werden. |
POST /communityBalance | Die 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 mitCITY_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.
| Feld | Bedeutung |
|---|---|
productionKwh / consumptionKwh | Gesamterzeugung und -verbrauch der Gemeinschaft im Slot |
allocatedKwh | In der Gemeinschaft verteilte Energie (Summe G.03) |
surplusKwh | Nicht in der Gemeinschaft genutzte Erzeugung (Summe P.01T) |
shortfallKwh | Nicht von der Gemeinschaft gedeckter Verbrauch |
coverage | Anteil des Verbrauchs, den die Gemeinschaft deckt (0–1) |
reported | Erwartete und erhaltene Zählpunkte, davon simuliert |
simulatedIncluded | false, 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:
- Seine Zählpunkte einmal registrieren (
/registerMeteringPoint, je Richtung einer: Laden = Verbrauch, Entladen = Erzeugung). - Alle 15 Minuten oder gesammelt melden, spätestens einen ganzen Tag mit 96
Werten vor dem Meldeschluss (
/meterReadings), undrejectedprüfen. - Die vorläufige Bilanz beobachten (
/communityBalance), um zu entscheiden, wann geladen oder entladen wird. - 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,:30und:45, mit expliziter Zeitzone (Zoder 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.
statusundmessageprotokollieren; beierrorund bei abgelehnten Einträgen inpartial-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.