# Abonnieren von Topics mit dem MQTT-Collector

> Wie ein Mapping ein MQTT-Topic auf Measurement und Field der InfluxDB abbildet, samt JSON-Extraktion, Formeln, Grenzwerten und berechneten Werten.

Der MQTT-Collector kann Nachrichten von verschiedenen (beliebig vielen) Topics abonnieren, verarbeiten und dann in die InfluxDB schreiben. Dazu dienen Zuordnungen („Mappings"). Jedes Mapping legt drei Dinge fest:

- Wo kommt der Wert her, also welches Topic muss abonniert werden?
- Welche Verarbeitung ist notwendig (Vorzeichen-Behandlung, Datentyp-Konvertierung, JSON-Extraktion, Formelbildung)?
- Wohin in der InfluxDB soll der ermittelte Wert geschrieben werden (Measurement und Field)?

Jedes Mapping wird durch mehrere Umgebungsvariablen definiert, die mit dem Präfix `MAPPING_X_` beginnen. `X` ist eine Zahl, und alle Variablen mit derselben Zahl gehören zum selben Mapping. Die Nummerierung beginnt bei `0` und läuft lückenlos weiter.

Ein Mapping muss kein Topic abonnieren. Ab Version 0.8.0 kann es seinen Wert auch aus anderen Mappings berechnen, siehe [Berechnete Werte](#berechnete-werte). Und es muss nicht jede empfangene Nachricht einzeln in die InfluxDB schreiben, siehe [Weniger Schreibzugriffe](#weniger-schreibzugriffe).

## Verfügbare Umgebungsvariablen je Mapping

Pflicht ist `MAPPING_X_TYPE`, dazu `MAPPING_X_TOPIC`, solange das Mapping seinen Wert nicht berechnet. Dazu kommt das Ziel in der InfluxDB, und dafür gibt es zwei Varianten:

- **Der Normalfall**: `MAPPING_X_MEASUREMENT` und `MAPPING_X_FIELD`. Dort landet der Wert, ob positiv oder negativ.
- **Die Vorzeichen-Aufteilung**: `MAPPING_X_MEASUREMENT_POSITIVE`, `MAPPING_X_MEASUREMENT_NEGATIVE`, `MAPPING_X_FIELD_POSITIVE` und `MAPPING_X_FIELD_NEGATIVE`. Der Wert landet je nach Vorzeichen in einem von zwei Feldern, etwa Netzbezug und Einspeisung.

> **Achtung**
>
>
> Die beiden Varianten schließen sich aus. Sobald eine der vier `POSITIVE`/`NEGATIVE`-Variablen gesetzt ist, müssen alle vier gesetzt sein, und `MAPPING_X_MEASUREMENT` bzw. `MAPPING_X_FIELD` dürfen dann **nicht** gesetzt sein. Der Collector prüft das beim Start und bricht mit einer Fehlermeldung ab, wenn beides gemischt ist.
>

> **HELIOS**
>
>
> HELIOS erzeugt die Vorzeichen-Aufteilung nicht, es kennt nur Mappings mit `MEASUREMENT` und `FIELD`. Ersatz sind zwei Mappings auf dasselbe Topic mit einer [Formel](#mapping_x_formula) je Vorzeichen: `IF({value} > 0, {value}, 0)` für den positiven Anteil, `IF({value} < 0, -{value}, 0)` für den negativen. Beim Import einer bestehenden Installation nimmt HELIOS diese Umwandlung selbst vor.
>

### `MAPPING_X_TOPIC`<span class="badge optional"></span>

Pflicht, solange das Mapping seinen Wert nicht aus anderen Mappings berechnet. Das Topic, das der Collector abonniert. Mehrere Mappings dürfen dasselbe Topic verwenden, etwa um aus einem JSON-Payload mehrere Werte zu gewinnen.

Ein Mapping ohne Topic ist ein [berechnetes](#berechnete-werte) und bezieht seinen Wert aus einer Formel. Mindestens ein Mapping mit Topic muss es geben, sonst beendet sich der Collector beim Start mit der Meldung „No mappings defined".

```properties title="Beispiel"
MAPPING_0_TOPIC=senec/0/ENERGY/GUI_INVERTER_POWER
```

> **HELIOS**
>
>
> HELIOS fragt das Topic bei jedem Sensor mit der Quelle _MQTT_ ab, unter _Konfiguration → Sensoren_. Topics ohne zugehörigen Sensor kommen unter _Konfiguration → Datenquellen → MQTT-Topics_ dazu. An beiden Stellen steht statt eines Topics auch die Auswahl _Berechnet_ zur Verfügung.
>

### `MAPPING_X_TYPE`<span class="badge required"></span>

Der Datentyp des Feldes. Erlaubt sind `integer`, `float`, `string` und `boolean`. Jeder andere Wert lässt den Collector beim Start abbrechen.

Bei `boolean` gelten `true`, `ok`, `yes`, `on` und `1` als wahr, unabhängig von Groß- und Kleinschreibung. Jeder andere Wert wird zu `false`.

```properties title="Beispiel"
MAPPING_0_TYPE=float
```

> **HELIOS**
>
>
> HELIOS fragt den Datentyp bei jedem MQTT-Sensor und jedem eigenen Topic ab, vorbelegt mit `float`.
>

### `MAPPING_X_MEASUREMENT`<span class="badge optional"></span>

Pflicht, solange das Mapping die Werte nicht nach Vorzeichen aufteilt und nicht [nur im Speicher bleibt](#mapping_x_skip_write). Der Name des InfluxDB-Measurements, in das der Wert geschrieben werden soll (unabhängig davon, ob er positiv oder negativ ist).

```properties title="Beispiel"
MAPPING_0_MEASUREMENT=PV
```

> **HELIOS**
>
>
> HELIOS fragt das Measurement bei jedem Sensor unter _Konfiguration → Sensoren_ ab.
>

### `MAPPING_X_FIELD`<span class="badge optional"></span>

Pflicht, solange das Mapping die Werte nicht nach Vorzeichen aufteilt und nicht [nur im Speicher bleibt](#mapping_x_skip_write). Der Name des InfluxDB-Feldes, in das der Wert geschrieben werden soll (unabhängig davon, ob er positiv oder negativ ist).

```properties title="Beispiel"
MAPPING_0_FIELD=inverter_power
```

> **HELIOS**
>
>
> HELIOS fragt das Field bei jedem Sensor unter _Konfiguration → Sensoren_ ab.
>

### `MAPPING_X_MEASUREMENT_POSITIVE`<span class="badge optional"></span>

Pflicht, sobald das Mapping die Werte nach Vorzeichen aufteilt. Name des InfluxDB-Measurements, in das der Wert geschrieben werden soll, wenn er **positiv** ist. Andernfalls (also wenn er negativ oder `0` ist) wird `0` geschrieben.

```properties title="Beispiel"
MAPPING_1_MEASUREMENT_POSITIVE=PV
```

> **HELIOS**
>
>
> HELIOS erzeugt die Vorzeichen-Aufteilung nicht. Ersatz sind zwei Mappings mit Vorzeichen-Formel, siehe oben.
>

### `MAPPING_X_MEASUREMENT_NEGATIVE`<span class="badge optional"></span>

Pflicht, sobald das Mapping die Werte nach Vorzeichen aufteilt. Der Name des InfluxDB-Measurements, in das der (absolute) Wert geschrieben werden soll, wenn er **negativ** ist. Andernfalls (also wenn er positiv oder `0` ist) wird `0` geschrieben.

```properties title="Beispiel"
MAPPING_1_MEASUREMENT_NEGATIVE=PV
```

> **HELIOS**
>
>
> HELIOS erzeugt die Vorzeichen-Aufteilung nicht. Ersatz sind zwei Mappings mit Vorzeichen-Formel, siehe oben.
>

### `MAPPING_X_FIELD_POSITIVE`<span class="badge optional"></span>

Pflicht, sobald das Mapping die Werte nach Vorzeichen aufteilt. Name des InfluxDB-Feldes, in das der Wert geschrieben werden soll, wenn er **positiv** ist. Andernfalls (also wenn er negativ oder `0` ist) wird `0` geschrieben.

```properties title="Beispiel"
MAPPING_1_FIELD_POSITIVE=grid_import_power
```

> **HELIOS**
>
>
> HELIOS erzeugt die Vorzeichen-Aufteilung nicht. Ersatz sind zwei Mappings mit Vorzeichen-Formel, siehe oben.
>

### `MAPPING_X_FIELD_NEGATIVE`<span class="badge optional"></span>

Pflicht, sobald das Mapping die Werte nach Vorzeichen aufteilt. Der Name des InfluxDB-Feldes, in das der (absolute) Wert geschrieben werden soll, wenn er **negativ** ist. Andernfalls (also wenn er positiv oder `0` ist) wird `0` geschrieben.

```properties title="Beispiel"
MAPPING_1_FIELD_NEGATIVE=grid_export_power
```

> **HELIOS**
>
>
> HELIOS erzeugt die Vorzeichen-Aufteilung nicht. Ersatz sind zwei Mappings mit Vorzeichen-Formel, siehe oben.
>

### `MAPPING_X_JSON_KEY`<span class="badge optional"></span>

Der Schlüssel, aus dem der Wert extrahiert wird, falls das Topic einen JSON-Payload enthält (mit **nicht** verschachtelten Key/Value-Paaren). Ein Schlüssel ist immer ein String, z.B. `inverter_power`. Ohne die Variable verarbeitet der Collector den Payload als nackten Wert.

```properties title="Beispiel"
MAPPING_2_JSON_KEY=radiation_level
```

> **HELIOS**
>
>
> HELIOS fragt die Wertgewinnung bei jedem MQTT-Sensor und jedem eigenen Topic ab und stellt _JSON-Schlüssel_ zur Wahl. Gesetzt wird immer nur eine der vier Varianten.
>

### `MAPPING_X_JSON_PATH`<span class="badge optional"></span>

Der [JSONPath](https://goessner.net/articles/JsonPath/), mit dem der Wert extrahiert wird, falls das Topic einen komplexen (z.B. verschachtelten) JSON-Payload enthält. Ein JSONPath beginnt immer mit `$.`, z.B. `$.example.foo.bar[2]`.

Sind `JSON_PATH` und `JSON_KEY` gleichzeitig gesetzt, gewinnt `JSON_PATH`. Der Collector meldet das nicht, deshalb gehört immer nur eines von beiden ins Mapping.

```properties title="Beispiel"
MAPPING_3_JSON_PATH=$.ccp[2]
```

> **HELIOS**
>
>
> HELIOS fragt die Wertgewinnung bei jedem MQTT-Sensor und jedem eigenen Topic ab und stellt _JSON-Path_ zur Wahl. Gesetzt wird immer nur eine der vier Varianten.
>

### `MAPPING_X_JSON_FORMULA`<span class="badge optional"></span>

Eine Formel, die aus einem JSON-Payload den zu speichernden Messwert berechnet. Sie darf [einige mathematische Operationen](https://github.com/rubysolo/dentaku?tab=readme-ov-file#built-in-operators-and-functions) enthalten, z.B. `round({radiation_level} * 1.5)`.

Die geschweiften Klammern `{}` dienen dazu, Werte aus dem JSON-Payload zu referenzieren. Es können dabei einfache Schlüssel oder JSONPath verwendet werden. Die Formel wirkt nur, wenn weder `JSON_KEY` noch `JSON_PATH` gesetzt ist. Diese beiden haben Vorrang.

```properties title="Beispiel"
MAPPING_4_JSON_FORMULA=round({reactivity} * {radiation_level}) + 42
```

> **HELIOS**
>
>
> HELIOS fragt die Wertgewinnung bei jedem MQTT-Sensor und jedem eigenen Topic ab und stellt _JSON-Formel_ zur Wahl. Gesetzt wird immer nur eine der vier Varianten.
>

### `MAPPING_X_FORMULA`<span class="badge optional"></span>

Eine Formel, die den zu speichernden Wert berechnet. Verfügbar ab Version 0.5.0. Was sie berechnet, hängt davon ab, ob das Mapping ein Topic abonniert:

- **Mit Topic**: Die Formel rechnet mit der empfangenen Nachricht, also mit einem Topic ohne JSON. Der empfangene Wert wird über den Platzhalter `{value}` referenziert, z.B. `round({value} * 1000)`. Ein anderer Platzhalter ist hier nicht erlaubt. Seit Version 0.8.0 startet der Collector nicht, wenn die Formel den Namen eines anderen Mappings nennt.
- **Ohne Topic**: Die Formel rechnet mit den Werten anderer Mappings, die über ihren [`MAPPING_X_NAME`](#mapping_x_name) referenziert werden, z.B. `{house_power} - {wallbox_power}`. Näheres unter [Berechnete Werte](#berechnete-werte).

Es gelten dieselben Operationen wie bei `JSON_FORMULA`, dazu die Vergleiche `==`, `!=`, `>`, `<`, `>=`, `<=` und die Funktion `IF()`. Eine Formel, die nur aus einem Vergleich besteht, liefert `true` oder `false` und braucht deshalb `MAPPING_X_TYPE=boolean`.

Bei einem Mapping mit Topic wirkt die Formel nur, wenn weder `JSON_KEY` noch `JSON_PATH` noch `JSON_FORMULA` gesetzt ist. Alle drei haben Vorrang.

```properties title="Beispiel"
MAPPING_4_FORMULA=round({value} * 1000)
```

> **HELIOS**
>
>
> HELIOS fragt die Wertgewinnung bei jedem MQTT-Sensor und jedem eigenen Topic ab und stellt _Formel_ zur Wahl. Gesetzt wird immer nur eine der vier Varianten. Bei einem berechneten Wert entfällt diese Auswahl: Dort fragt HELIOS direkt die Formel ab und nennt die Namen, die sich darin verwenden lassen.
>

### `MAPPING_X_MIN`<span class="badge optional"></span>

Unterer Grenzwert für Messwerte, verfügbar ab Version 0.3.0. Wird ein Wert unterhalb dieses Grenzwerts empfangen, wird er ignoriert und **nicht** in die InfluxDB geschrieben. Damit lassen sich Ausreißer ausfiltern, die sonst die Statistik verfälschen. Ohne die Variable gibt es keine untere Grenze.

Der Grenzwert greift erst nach JSON-Extraktion und Formel, also auf dem Wert, der tatsächlich gespeichert würde.

```properties title="Beispiel"
MAPPING_0_MIN=5
```

> **HELIOS**
>
>
> HELIOS fragt den Minimalwert bei jedem MQTT-Sensor und jedem eigenen Topic ab. Bleibt das Feld leer, gibt es keine untere Grenze.
>

### `MAPPING_X_MAX`<span class="badge optional"></span>

Oberer Grenzwert für Messwerte, verfügbar ab Version 0.3.0. Wird ein Wert oberhalb dieses Grenzwerts empfangen, wird er ignoriert und **nicht** in die InfluxDB geschrieben. Ohne die Variable gibt es keine obere Grenze.

Beide Grenzwerte wirken nur bei den Typen `float` und `integer`. Bei `string` und `boolean` bleiben sie ohne Effekt.

```properties title="Beispiel"
MAPPING_0_MAX=15000
```

> **HELIOS**
>
>
> HELIOS fragt den Maximalwert bei jedem MQTT-Sensor und jedem eigenen Topic ab. Bleibt das Feld leer, gibt es keine obere Grenze.
>

### `MAPPING_X_NULL_TO_ZERO`<span class="badge optional"></span>

Wandelt einen Messwert von `NULL` in die Zahl `0` um und schreibt ihn nach InfluxDB. Verfügbar ab Version 0.7.0. Erlaubt sind `true` und `false`, jeder andere Wert lässt den Collector beim Start abbrechen. Standardwert ist `false`.

Bei `false` wird ein Messwert von `NULL` ignoriert, also **nicht** nach InfluxDB geschrieben. Das gilt auch, wenn zwar ein Payload ankommt, der konfigurierte JSON-Schlüssel darin aber fehlt.

```properties title="Beispiel"
MAPPING_3_NULL_TO_ZERO=true
```

> **HELIOS**
>
>
> HELIOS bietet dafür bei jedem MQTT-Sensor und jedem eigenen Topic einen Schalter _NULL als 0 speichern_, standardmäßig aus.
>

## Berechnete Werte

Ein Mapping ohne `MAPPING_X_TOPIC` abonniert nichts, sondern berechnet seinen Wert aus anderen Mappings. Verfügbar ab Version 0.8.0. Es braucht dazu eine [`MAPPING_X_FORMULA`](#mapping_x_formula), die mindestens ein anderes Mapping über dessen [`MAPPING_X_NAME`](#mapping_x_name) referenziert. Sonst verhält es sich wie jedes andere Mapping: Datentyp, Grenzwerte und Ziel in der InfluxDB gelten unverändert.

Gerechnet wird, sobald eine Nachricht einen der referenzierten Werte ändert. Das Ergebnis schreibt der Collector wie einen empfangenen Messwert. Ein typischer Fall ist die Grundlast, also der Hausverbrauch ohne die großen Verbraucher:

```properties
MAPPING_15_MEASUREMENT=Household
MAPPING_15_FIELD=base_load
MAPPING_15_TYPE=integer
MAPPING_15_FORMULA="{house_power} - {wallbox_power} - {heatpump_power}"
```

Referenziert wird immer über den Namen, nie über die Nummer des Mappings. Die Nummer verschiebt sich, sobald ein Mapping davor dazukommt oder wegfällt, der Name bleibt. Aus demselben Grund spielt die Reihenfolge in der Konfiguration keine Rolle. Der Collector bringt die berechneten Mappings selbst in die Reihenfolge, in der sie sich ausrechnen lassen.

Berechnete Werte lassen sich verketten, ein berechnetes Mapping darf also ein anderes referenzieren. Voraussetzung ist auch dort ein `MAPPING_X_NAME`. Führt eine Formel auf sich selbst zurück, startet der Collector nicht, ebenso wenig bei einem Namen, den kein Mapping trägt.

Solange ein referenzierter Wert noch nie eingetroffen ist oder sein [Höchstalter](#mapping_x_max_age) überschritten hat, wird nicht gerechnet und nichts geschrieben. Das Protokoll nennt dann die fehlende Referenz und den Grund:

```log
  Formula for base_load could not be evaluated
  (wallbox_power [senec/0/WALLBOX/APPARENT_CHARGING_POWER/0]: last received 145s ago, exceeds MAX_AGE of 300s), ignoring.
```

Die Warnung erscheint einmal je Situation, nicht bei jeder Nachricht.

Mit [`MAPPING_X_NULL_TO_ZERO`](#mapping_x_null_to_zero) schreibt das Mapping in diesem Fall `0`. Diese `0` ist ein echter Wert: Sie landet in der InfluxDB, und jede Formel rechnet mit ihr weiter. Zusammen mit einem Höchstalter entsteht daraus eine falsche Null statt der beabsichtigten Lücke, deshalb passen die beiden nicht zusammen.

### `MAPPING_X_NAME`<span class="badge optional"></span>

Der Name, unter dem die Formel eines anderen Mappings diesen Wert liest, z.B. `{wallbox_power}`. Verfügbar ab Version 0.8.0. Erlaubt sind Kleinbuchstaben, Ziffern und Unterstriche, wobei das erste Zeichen keine Ziffer sein darf. `value` ist reserviert, denn so heißt der Platzhalter der empfangenen Nachricht. Jeder Name darf nur einmal vorkommen. Ohne die Variable bleibt der Wert für Formeln unsichtbar.

Nötig ist der Name nur, wenn eine Formel den Wert liest. Er ist außerdem Voraussetzung für `MAPPING_X_MAX_AGE` und `MAPPING_X_SKIP_WRITE`. Einen Verstoß gegen diese Regeln meldet der Collector beim Start, und er startet nicht.

```properties title="Beispiel"
MAPPING_1_NAME=wallbox_power
```

> **HELIOS**
>
>
> HELIOS fragt den Namen bei jedem MQTT-Sensor und jedem eigenen Topic ab, im Feld _Name für Formeln_. Sobald eine Formel den Wert liest, lässt sich das Feld nicht mehr leeren. Ein geänderter Name wird in diese Formeln nachgezogen.
>

### `MAPPING_X_MAX_AGE`<span class="badge optional"></span>

Höchstalter des Werts in Sekunden, eine ganze Zahl größer als `0`. Verfügbar ab Version 0.8.0. Ist der zuletzt empfangene Wert älter, gilt er in Formeln als unbekannt. Ohne die Variable altert er nie. Die Variable setzt `MAPPING_X_NAME` voraus, sonst startet der Collector nicht.

Das Höchstalter betrifft nur die Verwendung in Formeln, nicht das Schreiben des Werts selbst. Fällt ein Sensor aus, wird das berechnete Mapping nicht mehr geschrieben. In der Auswertung entsteht eine sichtbare Lücke statt einer Zahl aus veralteten Daten. Über eine Kette vererbt sich das: Ein berechneter Wert ist so alt wie der älteste Wert, aus dem er entstanden ist. Die Mappings dazwischen brauchen deshalb kein eigenes Höchstalter.

```properties title="Beispiel"
MAPPING_1_MAX_AGE=300
```

> **HELIOS**
>
>
> HELIOS fragt das Höchstalter bei jedem MQTT-Sensor und jedem eigenen Topic ab, im Feld _Höchstalter (Sekunden)_. Es erscheint, sobald ein Name vergeben ist. Bleibt es leer, altert der Wert nicht.
>

### `MAPPING_X_SKIP_WRITE`<span class="badge optional"></span>

Hält den Wert nur im Speicher, statt ihn in die InfluxDB zu schreiben. Verfügbar ab Version 0.8.0. Erlaubt sind `true` und `false`, jeder andere Wert lässt den Collector beim Start abbrechen. Standardwert ist `false`.

Bei `true` steht der Wert weiterhin den Formeln und dem Höchstalter zur Verfügung, bekommt aber kein eigenes Measurement und Field. Das passt, wenn nur das Rechenergebnis in der Datenbank landen soll. `MAPPING_X_NAME` ist dann Pflicht, denn ohne Namen könnte keine Formel den Wert lesen. Zusammen mit `MAPPING_X_DEDUP=true` oder `MAPPING_X_AGGREGATE_INTERVAL` bricht der Collector ab, denn die beiden steuern ein Schreiben, das hier nicht stattfindet.

```properties title="Beispiel"
MAPPING_1_SKIP_WRITE=true
```

> **HELIOS**
>
>
> HELIOS bietet dafür bei jedem eigenen Topic einen Schalter _Nur im Speicher halten_, standardmäßig aus. Bei einem Sensor gibt es ihn nicht, denn ein Sensor benennt immer ein Ziel in der InfluxDB.
>

## Weniger Schreibzugriffe

Im Normalfall schreibt der Collector jede empfangene Nachricht in die InfluxDB. Bei einem Topic, das mehrmals pro Sekunde sendet, entstehen dabei mehr Datenpunkte, als eine Auswertung braucht. Zwei Variablen ab Version 0.8.0 verringern die Zahl der Schreibzugriffe: `MAPPING_X_AGGREGATE_INTERVAL` mittelt die Werte eines Zeitfensters, `MAPPING_X_DEDUP` schreibt nur Änderungen.

Beide reagieren ausschließlich auf eintreffende Nachrichten. Bleibt der Sender stumm, schreibt der Collector weder einen Mittelwert noch ein Lebenszeichen, und die Lücke bleibt sichtbar.

Auf die Berechnung wirkt beides nicht. Eine Formel und das Höchstalter sehen weiterhin jeden einzelnen empfangenen Wert, auch wenn er nie in der InfluxDB landet.

Beide Variablen lassen sich kombinieren. Der Collector mittelt zuerst und vergleicht danach, `MAPPING_X_DEDUP` sieht also die Mittelwerte und nicht die einzelnen Nachrichten.

### `MAPPING_X_AGGREGATE_INTERVAL`<span class="badge optional"></span>

Länge des Zeitfensters in Sekunden, über das der Collector die empfangenen Werte mittelt, eine ganze Zahl größer als `0`. Verfügbar ab Version 0.8.0. Geschrieben wird dann nur der Mittelwert. Ohne die Variable landet jeder empfangene Wert einzeln in der InfluxDB. Erlaubt ist sie nur bei `MAPPING_X_TYPE=integer` oder `float`, denn nur Zahlen lassen sich mitteln.

Das Fenster beginnt mit dem ersten Wert und startet nach jedem Schreibvorgang neu. Geschrieben wird also im Abstand des Intervalls, solange Werte eintreffen, und nicht nach der Uhr. Bei einer Aufteilung nach Vorzeichen mittelt der Collector vor der Aufteilung, damit die beiden Felder zusammenpassen.

```properties title="Beispiel"
MAPPING_5_AGGREGATE_INTERVAL=60
```

> **HELIOS**
>
>
> HELIOS fragt das Intervall bei jedem MQTT-Sensor und jedem eigenen Topic ab, im Feld _Mittelungsintervall (Sekunden)_. Bleibt es leer, wird nicht gemittelt.
>

### `MAPPING_X_DEDUP`<span class="badge optional"></span>

Schreibt einen Wert nur, wenn er sich vom zuletzt geschriebenen unterscheidet. Verfügbar ab Version 0.8.0. Erlaubt sind `true` und `false`, jeder andere Wert lässt den Collector beim Start abbrechen. Standardwert ist `false`.

Eine Änderung geht sofort in die InfluxDB. Ein wiederholter Wert wird zurückgehalten und erst nach `MAPPING_X_HEARTBEAT_INTERVAL` Sekunden erneut geschrieben, damit sich „unverändert" von „Sender ausgefallen" unterscheiden lässt. Das gilt auch für eine wiederholte `0`, denn bei den meisten Sensoren ist das ein normaler Zustand. Jedes Feld wird für sich betrachtet, bei einer Aufteilung nach Vorzeichen halten sich die beiden Felder also nicht gegenseitig auf.

Wie viel das spart, hängt vom Sensor ab. Ein Zustand oder ein Gerät im Leerlauf wiederholt sich exakt. Ein Messwert, der um einen Wert herum rauscht, tut das nicht. Dort lässt `MAPPING_X_TYPE=integer` das Rauschen wegfallen, während `float` fast jeden Wert durchlässt. Der Datentyp gehört allerdings festgelegt, bevor das Feld seinen ersten Wert bekommt: InfluxDB behält den Typ eines Feldes bei und lehnt einen anderen später ab.

```properties title="Beispiel"
MAPPING_6_DEDUP=true
```

> **HELIOS**
>
>
> HELIOS bietet dafür bei jedem MQTT-Sensor und jedem eigenen Topic einen Schalter _Nur geänderte Werte speichern_, standardmäßig aus.
>

### `MAPPING_X_HEARTBEAT_INTERVAL`<span class="badge optional"></span>

Abstand in Sekunden, in dem ein unveränderter Wert trotz `MAPPING_X_DEDUP` erneut geschrieben wird, eine ganze Zahl größer als `0`. Verfügbar ab Version 0.8.0. Standardwert ist `60`. Die Variable setzt `MAPPING_X_DEDUP=true` voraus, sonst startet der Collector nicht.

Zusammen mit `MAPPING_X_AGGREGATE_INTERVAL` muss das Lebenszeichen länger sein als das Mittelungsfenster, denn es kann nur gemeinsam mit einem Mittelwert hinausgehen. Ist es kürzer oder gleich lang, wird jeder Mittelwert geschrieben und die Entdopplung bleibt wirkungslos. Der Collector warnt beim Start und läuft weiter. Sinnvoll ist ein Vielfaches des Mittelungsfensters, denn zwischen zwei Mittelwerten liegt das Fenster plus der Abstand zweier Nachrichten.

```properties title="Beispiel"
MAPPING_6_HEARTBEAT_INTERVAL=300
```

> **HELIOS**
>
>
> HELIOS fragt das Intervall bei jedem MQTT-Sensor und jedem eigenen Topic ab, im Feld _Lebenszeichen-Intervall (Sekunden)_. Es erscheint, sobald _Nur geänderte Werte speichern_ eingeschaltet ist. Einen Wert, der nicht länger ist als das Mittelungsintervall, weist HELIOS ab.
>

## Beispiele

### 1. Einfaches Mapping

Topic wird abonniert, der erhaltene Wert wird unverändert als Fließkommazahl in die InfluxDB geschrieben:

```properties
MAPPING_0_TOPIC=senec/0/ENERGY/GUI_INVERTER_POWER
MAPPING_0_MEASUREMENT=PV
MAPPING_0_FIELD=inverter_power
MAPPING_0_TYPE=float
```

### 2. Mapping mit Vorzeichen-Behandlung

Wenn die Werte des Topics positiv oder negativ sein können, erfolgt hier eine Aufteilung. Positive Werte werden in `grid_import_power` geschrieben, negative Werte in `grid_export_power`.

```properties
MAPPING_1_TOPIC=senec/0/ENERGY/GUI_GRID_POW
MAPPING_1_MEASUREMENT_POSITIVE=PV
MAPPING_1_MEASUREMENT_NEGATIVE=PV
MAPPING_1_FIELD_POSITIVE=grid_import_power
MAPPING_1_FIELD_NEGATIVE=grid_export_power
MAPPING_1_TYPE=float
```

- Falls der empfangene Wert positiv ist (z.B. `1000`): `grid_import_power` wird auf `1000` gesetzt, `grid_export_power` auf `0`.
- Falls der empfangene Wert negativ ist (z.B. `-500`): `grid_import_power` wird auf `0` gesetzt, `grid_export_power` auf `500`.
- Falls der empfangene Wert `0` ist: `grid_import_power` und `grid_export_power` werden beide auf `0` gesetzt.

### 3. Mapping mit einfachem JSON-Payload

Verwendung von `JSON_KEY`:

```properties
MAPPING_2_TOPIC=my/little/nuclear/plant
MAPPING_2_JSON_KEY=radiation_level
MAPPING_2_MEASUREMENT=nuclear_power_plant
MAPPING_2_FIELD=radiation_level
MAPPING_2_TYPE=float
```

Aus einem JSON von beispielsweise `{"radiation_level": 90.5, "reactivity": 0.7}` resultiert der Wert `90.5`.

### 4. Mapping mit komplexem JSON-Payload

Verwendung von `JSON_PATH`:

```properties
MAPPING_3_TOPIC=go-e/ATTR
MAPPING_3_JSON_PATH=$.ccp[2]
MAPPING_3_MEASUREMENT=WALLBOX
MAPPING_3_FIELD=power
MAPPING_3_TYPE=float
MAPPING_3_NULL_TO_ZERO=true
```

Dies extrahiert den Wert aus einem Payload wie `{"ccp": [1,2,42,3]}`. Im Beispiel liefert das den Wert an Position 2 (drittes Element) des Arrays `ccp`, also `42`.

Sollte der Wert `null` sein (z.B. bei `{"ccp": [1,2,null,3]}`), wird er als `0` geschrieben.

### 5. Mapping mit Formel

Es gibt zwei Möglichkeiten, eine Formel zu verwenden:

#### a) Bei JSON-Payload

```properties
MAPPING_4_TOPIC=my/little/nuclear/plant
MAPPING_4_JSON_FORMULA="round({reactivity} * {radiation_level}) + 42"
MAPPING_4_MEASUREMENT=nuclear_power_plant
MAPPING_4_FIELD=danger_level
MAPPING_4_TYPE=float
```

Aus einem JSON von z.B. `{"radiation_level": 90.5, "reactivity": 0.7}` entsteht `danger_level` mit `round(0.7 * 90.5) + 42`, also `105`.

#### b) Bei String-Payload

```properties
MAPPING_4_TOPIC=my/little/nuclear/plant/powerInKwH
MAPPING_4_FORMULA="round({value} * 1000)"
MAPPING_4_MEASUREMENT=nuclear_power_plant
MAPPING_4_FIELD=power
MAPPING_4_TYPE=integer
```

Im Gegensatz zum JSON-Fall wird hier der Wert über `{value}` referenziert und die Variable heißt `MAPPING_X_FORMULA`.

Aus einem Payload von z.B. `42.5` entsteht `power` mit `round(42.5 * 1000)`, also `42500`.

### 6. Mapping mit Grenzwerten

```properties
MAPPING_0_TOPIC=senec/0/ENERGY/GUI_INVERTER_POWER
MAPPING_0_MEASUREMENT=PV
MAPPING_0_FIELD=inverter_power
MAPPING_0_TYPE=float
MAPPING_0_MIN=5
MAPPING_0_MAX=15000
```

Werte unter `5` oder über `15000` werden ignoriert und nicht in die InfluxDB geschrieben.

### 7. Berechneter Wert

Zwei Mappings tragen einen Namen, ein drittes rechnet damit. Es hat kein Topic:

```properties
MAPPING_0_TOPIC=senec/0/ENERGY/GUI_HOUSE_POW
MAPPING_0_MEASUREMENT=PV
MAPPING_0_FIELD=house_power
MAPPING_0_TYPE=integer
MAPPING_0_NAME=house_power

MAPPING_1_TOPIC=senec/0/WALLBOX/APPARENT_CHARGING_POWER/0
MAPPING_1_MEASUREMENT=PV
MAPPING_1_FIELD=wallbox_power
MAPPING_1_TYPE=integer
MAPPING_1_NAME=wallbox_power
MAPPING_1_MAX_AGE=300

MAPPING_2_MEASUREMENT=Household
MAPPING_2_FIELD=base_load
MAPPING_2_TYPE=integer
MAPPING_2_FORMULA="{house_power} - {wallbox_power}"
```

Sobald einer der beiden Werte eintrifft, berechnet der Collector `base_load` neu und schreibt ihn. Meldet sich die Wallbox länger als 300 Sekunden nicht, bleibt `base_load` ungeschrieben, und im Dashboard entsteht eine Lücke.

Soll nur die Grundlast in der InfluxDB landen, bekommen die beiden Quell-Mappings ein `MAPPING_X_SKIP_WRITE=true`. Ihre Werte bleiben dann im Speicher, und `MAPPING_X_MEASUREMENT` und `MAPPING_X_FIELD` entfallen dort.

### 8. Gemittelte Werte

Ein Topic sendet mehrmals pro Sekunde. Statt jeder einzelnen Nachricht landet einmal pro Minute deren Mittelwert in der InfluxDB:

```properties
MAPPING_5_TOPIC=my/fast-changing/power
MAPPING_5_MEASUREMENT=METER
MAPPING_5_FIELD=power
MAPPING_5_TYPE=float
MAPPING_5_AGGREGATE_INTERVAL=60
```

### 9. Nur geänderte Werte

Ein Leck-Sensor meldet im Sekundentakt denselben Zustand. In die InfluxDB soll aber nur die Änderung, dazu alle 300 Sekunden ein Lebenszeichen:

```properties
MAPPING_6_TOPIC=my/leak-sensor/state
MAPPING_6_MEASUREMENT=Leak
MAPPING_6_FIELD=detected
MAPPING_6_TYPE=boolean
MAPPING_6_DEDUP=true
MAPPING_6_HEARTBEAT_INTERVAL=300
```

Wechselt der Wert von `false` auf `true`, steht er sofort in der InfluxDB. Bleibt er gleich, überspringt ihn der Collector und schreibt ihn erst nach 300 Sekunden wieder. An dieser Wiederholung ist in der Auswertung zu erkennen, dass der Sensor noch sendet und nicht ausgefallen ist.
