Based on Pflichtenheft v2.2, documents the full system design including daemon, API, frontend, and infrastructure with agreed deviations (Axum 0.8, SQLx 0.8, Vite 6, Tailwind 4). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
147 lines
3.8 KiB
Markdown
147 lines
3.8 KiB
Markdown
# ChirpStack Decoder für Füllstandsmessung
|
||
|
||
## Payload-Format (8 Bytes)
|
||
|
||
Das Gerät sendet 8 Bytes via LoRaWAN:
|
||
|
||
| Bytes | Beschreibung | Einheit | Berechnung |
|
||
|-------|--------------|---------|------------|
|
||
| 0-1 | Distanz | mm | Big Endian uint16 |
|
||
| 2-3 | Füllhöhe | mm | Big Endian uint16 |
|
||
| 4-5 | Füllstand | % × 10 | Big Endian uint16 (855 = 85.5%) |
|
||
| 6-7 | Batterie | mV | Big Endian uint16 (3700 = 3.7V) |
|
||
|
||
## Decoder-Code für ChirpStack v4
|
||
|
||
```javascript
|
||
function decodeUplink(input) {
|
||
var bytes = input.bytes;
|
||
var fPort = input.fPort;
|
||
|
||
// Validierung
|
||
if (bytes.length !== 8) {
|
||
return {
|
||
errors: ["Invalid payload length - expected 8 bytes, got " + bytes.length]
|
||
};
|
||
}
|
||
|
||
if (fPort !== 1) {
|
||
return {
|
||
errors: ["Invalid fPort - expected 1, got " + fPort]
|
||
};
|
||
}
|
||
|
||
// Distanz (Byte 0-1, Big Endian)
|
||
var distanceMm = (bytes[0] << 8) | bytes[1];
|
||
var distanceCm = distanceMm / 10.0;
|
||
var distanceM = distanceMm / 1000.0;
|
||
|
||
// Füllhöhe (Byte 2-3, Big Endian)
|
||
var fillHeightMm = (bytes[2] << 8) | bytes[3];
|
||
var fillHeightCm = fillHeightMm / 10.0;
|
||
var fillHeightM = fillHeightMm / 1000.0;
|
||
|
||
// Füllstand in % (Byte 4-5, Big Endian, × 10)
|
||
var fillPercentRaw = (bytes[4] << 8) | bytes[5];
|
||
var fillPercent = fillPercentRaw / 10.0;
|
||
|
||
// Batteriespannung (Byte 6-7, Big Endian, in mV)
|
||
var batteryMv = (bytes[6] << 8) | bytes[7];
|
||
var batteryV = batteryMv / 1000.0;
|
||
|
||
// Batterie-Prozent berechnen (LiPo: 3.0V = 0%, 4.2V = 100%)
|
||
var batteryPercent = ((batteryV - 3.0) / (4.2 - 3.0)) * 100.0;
|
||
if (batteryPercent < 0) batteryPercent = 0;
|
||
if (batteryPercent > 100) batteryPercent = 100;
|
||
|
||
return {
|
||
data: {
|
||
// Distanz (Sensor bis Flüssigkeit)
|
||
distance_mm: distanceMm,
|
||
distance_cm: distanceCm,
|
||
distance_m: distanceM,
|
||
|
||
// Füllhöhe (Flüssigkeitshöhe im Tank)
|
||
fill_height_mm: fillHeightMm,
|
||
fill_height_cm: fillHeightCm,
|
||
fill_height_m: fillHeightM,
|
||
|
||
// Füllstand in Prozent
|
||
fill_percent: fillPercent,
|
||
|
||
// Batterie
|
||
battery_mv: batteryMv,
|
||
battery_v: batteryV,
|
||
battery_percent: Math.round(batteryPercent)
|
||
}
|
||
};
|
||
}
|
||
```
|
||
|
||
## Beispiel-Payloads
|
||
|
||
Angenommen: Tank-Höhe = 180 cm
|
||
|
||
| Payload (Hex) | Distanz | Füllhöhe | Füllstand |
|
||
|---------------|---------|----------|-----------|
|
||
| `0348 0AA0 0226` | 84.0 cm | 170.0 cm | 94.4% |
|
||
| `05DC 0708 0197` | 150.0 cm | 180.0 cm | 40.9% |
|
||
| `0708 0000 0000` | 180.0 cm | 0.0 cm | 0% |
|
||
| `0014 06F8 03E8` | 2.0 cm | 178.0 cm | 100.0% |
|
||
|
||
### Berechnung Beispiel 1: `0348 0AA0 0226`
|
||
|
||
- **Distanz**: `0x0348` = 840 mm = 84.0 cm
|
||
- **Füllhöhe**: `0x0AA0` = 2720 mm = 272.0 cm (Fehler! sollte max 180cm sein)
|
||
- **Füllstand**: `0x0226` = 550 = 55.0%
|
||
|
||
## Installation
|
||
|
||
1. Öffne ChirpStack → **Device Profile** → **Codec**
|
||
2. Wähle **JavaScript** als Codec
|
||
3. Kopiere den Decoder-Code ins **Uplink decoder** Feld
|
||
4. Klicke **Submit**
|
||
|
||
## Anzeige in ChirpStack
|
||
|
||
Nach erfolgreicher Dekodierung siehst du in ChirpStack:
|
||
|
||
```json
|
||
{
|
||
"distance_mm": 840,
|
||
"distance_cm": 84.0,
|
||
"distance_m": 0.084,
|
||
"fill_height_mm": 960,
|
||
"fill_height_cm": 96.0,
|
||
"fill_height_m": 0.096,
|
||
"fill_percent": 53.3,
|
||
"battery_mv": 3850,
|
||
"battery_v": 3.85,
|
||
"battery_percent": 71
|
||
}
|
||
```
|
||
|
||
## Grafana Dashboard
|
||
|
||
Empfohlene Visualisierungen:
|
||
|
||
1. **Gauge**: `fill_percent` (0-100%)
|
||
2. **Graph**: `fill_height_cm` über Zeit
|
||
3. **Gauge**: `battery_percent` (0-100%)
|
||
4. **Graph**: `battery_v` über Zeit (Batterie-Entladung)
|
||
5. **Graph**: `distance_cm` über Zeit (Fehlerdiagnose)
|
||
|
||
## Troubleshooting
|
||
|
||
### Negative Füllhöhe
|
||
|
||
**Problem**: Füllhöhe zeigt negative Werte
|
||
|
||
**Lösung**: Tank-Höhe (`TANK_HEIGHT`) im Code anpassen
|
||
|
||
### Füllstand über 100%
|
||
|
||
**Problem**: Füllstand zeigt über 100%
|
||
|
||
**Lösung**: Sensor ist zu nah am Boden installiert oder Tank-Höhe falsch konfiguriert
|