Metadaten im Plattform-Adapter
Liest ein Workflow im hetida designer ein Signal oder eine Signalgruppe über den Plattform-Adapter, erhält er neben den Datenpunkten auch deren Metadaten: Name, Einheit, Datentyp, die dynamischen Eigenschaften einschließlich der geerbten und die Kommentare im abgefragten Zeitraum. Diese Seite beschreibt, wie diese Metadaten aufgebaut sind, wo ein Feld der Plattform-Oberfläche darin steht – und welche Felder nicht übertragen werden.
Die Struktur folgt der Metadaten-Konvention des hetida designers (attached_metadata.md). Die Plattform verwendet ausschließlich diese Konvention; das frühere Format unter single_metric_metadata.structured_metadata wird nicht mehr geliefert.
Übertragung
Der Adapter liefert die Metadaten im HTTP-Header Data-Attributes der Leseantwort, als Base64-kodiertes JSON. Der hetida designer dekodiert den Header und legt den Inhalt in attrs der gelesenen Daten ab:
| Datentyp im Workflow | Adapter-Endpunkt | Inhalt des Headers | Im Komponentencode |
|---|---|---|---|
SERIES (Signal) | GET /api/adapter/timeseries?id=… | ein Objekt je Signal-ID, Schlüssel ist die Signal-ID | series.attrs |
MULTITSFRAME (Signalgruppe) | GET /api/adapter/multitsframe?id=… | ein Objekt für die ganze Gruppe | frame.attrs |
In beiden Fällen hat ein Metadaten-Objekt dieselben zwei Teile:
attrs
├── dataset_metadata Angaben zum Datensatz: Schlüssel, Zeitraum, ggf. Signalgruppe
└── metrics[] je Signal ein Eintrag
├── id, external_id, name, display_name, description
├── default_aggregation_method
├── inherited dynamische Eigenschaften (geerbt und eigene)
├── comments[] Kommentare im Zeitraum, mit replies[]
└── value_dimensions[] genau ein Eintrag: column = "value"
Felder ohne Wert stehen mit null im JSON. Eine Komponente sollte deshalb nicht nur auf das Vorhandensein eines Schlüssels prüfen, sondern auch auf None.
Signal (SERIES)
Der Header einer SERIES-Abfrage enthält ein Objekt je angefragtem Signal; der hetida designer gibt jeder Series den Eintrag ihrer timeseriesId.
{
"dataset_metadata": {
"metric_key": "id",
"single_metric": "0190c3a2-…",
"queried_metrics": ["0190c3a2-…"],
"signal_group": null,
"ref_interval_start_timestamp": "2026-10-01T00:00:00Z",
"ref_interval_end_timestamp": "2026-10-02T00:00:00Z",
"ref_interval_type": "closed"
},
"metrics": [
{
"id": "0190c3a2-…",
"external_id": "wasserwerk.pumpe1.druck",
"name": "Druck Pumpe 1",
"display_name": null,
"description": "Ausgangsdruck hinter Pumpe 1",
"default_aggregation_method": "AVG",
"inherited": { "grenzwert_max": 6.5, "standort": "Nord" },
"comments": [
{
"id": "…",
"start_date": "2026-10-01T08:00:00Z",
"end_date": "2026-10-01T09:30:00Z",
"created_at": "2026-10-01T10:12:00Z",
"edited_at": null,
"content": "Wartung, Werte nicht repräsentativ",
"username": "mmuster",
"tags": ["wartung"],
"replies": []
}
],
"value_dimensions": [
{
"column": "value",
"name": "Druck Pumpe 1",
"unit": "bar",
"measurement": "Druck",
"value_data_type": "float"
}
]
}
]
}
Die Felder von dataset_metadata bei einer Leseabfrage:
| Feld | Inhalt |
|---|---|
metric_key | immer id: Ein Eintrag in metrics wird über sein Feld id (die Signal-ID) gefunden. |
single_metric | Signal-ID des einen Signals der Series. |
queried_metrics | angefragte Signal-IDs, auch ohne Daten im Zeitraum. |
signal_group | bei SERIES immer null. |
ref_interval_start_timestamp, ref_interval_end_timestamp | der abgefragte Zeitraum (from, to der Abfrage). |
ref_interval_type | derzeit immer closed. Achtung: Gelesen wird tatsächlich [from, to), ein Punkt genau bei to ist nicht enthalten. Der Header wird auf right_open umgestellt (HET-2021). |
Die übrigen Felder von dataset_metadata (invalidate_dataset, invalidation_interval_* usw.) setzt der Adapter beim Lesen nicht; sie sind für das Schreiben gedacht.
Signalgruppe (MULTITSFRAME)
Eine Signalgruppe wird als MULTITSFRAME mit den Spalten timestamp, metric und value geliefert. Die Spalte metric enthält die Signal-ID, nicht den Namen: Namen sind nicht eindeutig, und mehrere Signale können dieselbe Eingangszeitreihe nutzen. Über die Signal-ID findet eine Komponente den passenden Eintrag in metrics.
Gegenüber der Series unterscheidet sich der Header so:
| Feld | Inhalt |
|---|---|
dataset_metadata.single_metric | null, der Frame enthält mehrere Signale. |
dataset_metadata.queried_metrics | Signal-IDs aller gelieferten Mitglieder. |
dataset_metadata.signal_group | { "id", "name", "description" } der Signalgruppe. |
metrics[] | ein Eintrag je Mitglied, aufgebaut wie bei der Series (Eigenschaften mit Vererbung, Kommentare). |
metrics[].display_name | die Bezeichnung des Signals in dieser Gruppe. |
metrics[].default_aggregation_method | die Aggregationsmethode, die in der Gruppe für das Mitglied eingestellt ist – nicht die des Signals. |
Zu beachten:
- Die Werte sind Rohdaten. Weder Aggregationsintervall noch Aggregationsmethode der Gruppe werden angewendet;
default_aggregation_methodsagt nur, wie die Plattform das Signal in Diagrammen und Exporten zusammenfasst. - Ein Mitglied ohne Eingangszeitreihe oder ein gelöschtes Signal wird übersprungen, ohne dass der Abruf scheitert. Es fehlt dann in den Daten und in
metrics. - Nur eigenständige Signalgruppen aus dem Explorer stehen als Quelle zur Verfügung, Gruppen innerhalb eines Formulars nicht.
- Eine Signalgruppe ist keine Senke: Ein Schreibversuch wird mit
501 Not Implementedabgelehnt.
Felder eines Eintrags in metrics
| Pfad | Inhalt |
|---|---|
id | Signal-ID (UUID). |
external_id | Externe ID der Eingangszeitreihe, null bei einem Signal ohne Eingangszeitreihe. |
name | Name des Signals. |
display_name | Bezeichnung des Mitglieds in der Signalgruppe; bei SERIES null. |
description | Beschreibung des Signals. |
default_aggregation_method | AVG, MEDIAN, SUM, MIN oder MAX; null bei Keine Aggregation. |
inherited | Objekt { Schlüssel: Wert } der wirksamen dynamischen Eigenschaften, siehe unten. |
comments[] | Kommentare, siehe unten. |
value_dimensions[0].column | immer value – die Wertspalte der Daten. |
value_dimensions[0].name | Name des Signals (wie name). |
value_dimensions[0].unit | Einheit. |
value_dimensions[0].measurement | Messgröße. |
value_dimensions[0].value_data_type | float, integer oder boolean aus dem Wertetyp. |
Einheit, Messgröße und Datentyp stehen nur in value_dimensions, nicht direkt am Eintrag. Die Felder display_name, short_display_name und description einer Wertdimension sieht die Konvention vor, die Plattform belegt sie nicht.
Dynamische Eigenschaften (inherited)
inherited enthält die dynamischen Eigenschaften, die für das Signal wirksam sind – trotz des Namens nicht nur die geerbten:
- Schlüssel ist die Bezeichnung (
label) der Eigenschaft, Wert ihr Wert. - Eigenschaften der Assets oberhalb des Signals werden geerbt; bei gleichem Schlüssel gewinnt das nächstgelegene Asset.
- Eine eigene Eigenschaft des Signals überschreibt eine geerbte. Eine eigene Eigenschaft ohne Wert überschreibt nicht, dann bleibt der geerbte Wert wirksam.
- Ein Schlüssel ohne wirksamen Wert fehlt im Objekt.
- Woher ein Wert stammt (Signal oder welches Asset), wird nicht übertragen.
Dieselbe Auflösung liefert der Adapter auch über seine Metadaten-Endpunkte für Assets und Signale, eine Komponente erhält also auf beiden Wegen dieselben Werte.
Kommentare (comments)
Übertragen werden die Kommentare des Signals, deren Zeitraum den abgefragten Zeitraum überschneidet – der zuletzt erstellte zuerst, jeweils mit seinen Antworten.
| Pfad | Inhalt |
|---|---|
comments[].id | ID des Kommentars. |
comments[].start_date, end_date | Zeitraum, auf den sich der Kommentar bezieht. |
comments[].created_at, edited_at | Erstellt, zuletzt bearbeitet (null, wenn nie bearbeitet). |
comments[].content | Text. |
comments[].username | Benutzername des Verfassers. |
comments[].tags | Liste der Tags. |
comments[].replies[] | Antworten mit id, content, username, created_at, edited_at, älteste zuerst. |
Wird mit einem Betrachtungszeitpunkt (queryDate) gelesen, enthält der Header nur Kommentare und Antworten, die zu diesem Zeitpunkt schon existierten. Eine Historie der Kommentare gibt es nicht: Ein bearbeiteter Kommentar kommt mit seinem aktuellen Text, ein gelöschter gar nicht.
Zuordnung zur Oberfläche
Signal
| Oberfläche (Signal-Maske) | Feld in den Metadaten |
|---|---|
| Allgemeines › Name | metrics[].name und metrics[].value_dimensions[0].name |
| Allgemeines › Beschreibung | metrics[].description |
| Eigenschaften (dynamische Eigenschaften) | metrics[].inherited, zusammen mit den geerbten |
| Metadata › Wertetyp | metrics[].value_dimensions[0].value_data_type |
| Metadata › Messgröße | metrics[].value_dimensions[0].measurement |
| Metadata › Einheit | metrics[].value_dimensions[0].unit |
| Aggregation › Standard-Aggregationsmethode | metrics[].default_aggregation_method (nur bei SERIES) |
| Eingangszeitreihen › Externe ID | metrics[].external_id |
| ID des Signals (nicht in der Maske) | metrics[].id, single_metric, queried_metrics |
| Kommentare am Signal | metrics[].comments |
Nicht übertragen werden:
| Oberfläche | Hinweis |
|---|---|
| Anzeigeeinstellungen (Zeitbereich, Aktualisierungsintervall) | nur für die Darstellung in der Plattform. |
| Ausgänge (Berechnungen und ihre Namen) | Die Ausgänge eines Signals sind eigene Werte der Plattform und gehören nicht zur Zeitreihe. |
| OPC-UA-Abruf und Verdichtung | Konfiguration der Datenerfassung; welcher Weg die Daten lieferte, steht nicht in den Metadaten. |
| Position im Explorer (übergeordnete Assets, Pfad) | Nur die Eigenschaften der Assets kommen über inherited an, nicht die Assets selbst. |
| Herkunft einer Eigenschaft | inherited enthält nur den wirksamen Wert. |
| Berechtigungen, Änderungshistorie | – |
Signalgruppe
| Oberfläche (Signalgruppen-Maske) | Feld in den Metadaten |
|---|---|
| Allgemeines › Name | dataset_metadata.signal_group.name |
| Allgemeines › Beschreibung | dataset_metadata.signal_group.description |
| ID der Signalgruppe | dataset_metadata.signal_group.id |
| Signale auswählen (Mitglieder) | ein Eintrag je Mitglied in metrics, IDs in queried_metrics |
| Bezeichnung eines Signals in der Gruppe | metrics[].display_name |
| Aggregationsmethode eines Signals in der Gruppe | metrics[].default_aggregation_method |
| Felder der einzelnen Signale | wie bei der Series, siehe oben |
Nicht übertragen wird das Aggregationsintervall der Gruppe; der Frame enthält die Rohdaten. Eine Komponente sollte Mitglieder über die Signal-ID ansprechen, nicht über ihre Position in metrics – ein übersprungenes Mitglied verschiebt die Positionen.
Schreiben
Schreibt ein Workflow eine Series in ein Signal (POST /api/adapter/timeseries?timeseriesId=…), schickt der hetida designer die attrs der Ausgabe wieder als Data-Attributes-Header mit. Die Plattform wertet daraus nur Felder in dataset_metadata aus, die festlegen, ob vorhandene Werte vor dem Schreiben ungültig gemacht werden:
| Feld | Wirkung |
|---|---|
invalidate_dataset | true: Vorhandene Werte im Intervall werden ungültig gemacht, bevor die neuen geschrieben werden. Ohne das Feld werden die Werte nur hinzugefügt. |
only_invalidate | true: Es wird nur ungültig gemacht, keine neuen Werte geschrieben. Wirkt nur zusammen mit invalidate_dataset; allein gesetzt werden die Werte geschrieben. |
invalidation_interval_start, invalidation_interval_end | Intervall des Ungültigmachens. |
ref_interval_start_timestamp, ref_interval_end_timestamp | Ersatz, wenn kein invalidation_interval_* gesetzt ist. Fehlen beide, gilt der Bereich vom ersten bis zum letzten geschriebenen Zeitpunkt. |
ref_dataset_discrete | true: Statt eines Intervalls werden nur die Zeitpunkte der neuen Werte ungültig gemacht. Wird ignoriert, sobald ein invalidation_interval_* gesetzt ist. |
Das Intervall wird als links geschlossen, rechts offen behandelt; ref_interval_type und invalidation_interval_type werden nicht ausgewertet. Ebenfalls nicht ausgewertet werden delete_invalidated, invalidation_timestamp und new_data_invalidation_date. Alle übrigen Inhalte des Headers, also auch metrics, ändern am Signal nichts – Name, Einheit und Eigenschaften lassen sich über den Adapter nicht schreiben.
Ein Header, der weder gültiges Base64 noch gültiges JSON ist, wird mit 400 Bad Request abgelehnt.
Zeigt die Plattform die Ausgabe eines Workflows direkt an, etwa in einem Dashboard, liest sie Name und Einheit der Series aus metrics[] (Eintrag zu single_metric, sonst der einzige Eintrag) und dort aus der Wertdimension value.