Multi-Sensor-Unterstützung hinzugefügt
- Sensor-Auswahl-Dropdown in feinstaub.html implementiert - sensor_name Feld in README und Webhook-Konfiguration dokumentiert - Automatische Erkennung und Filterung nach Sensoren 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
11
README.md
11
README.md
@@ -57,12 +57,14 @@ Die Anwendung unterstützt folgende JSON-Formate:
|
||||
{
|
||||
"Uhrzeit": "10:00:38",
|
||||
"SDS_P1": 9.15,
|
||||
"SDS_P2": 6.22
|
||||
"SDS_P2": 6.22,
|
||||
"sensor_name": "Sensor-1"
|
||||
},
|
||||
{
|
||||
"Uhrzeit": "10:15:36",
|
||||
"SDS_P1": 9.75,
|
||||
"SDS_P2": 5.97
|
||||
"SDS_P2": 5.97,
|
||||
"sensor_name": "Sensor-1"
|
||||
}
|
||||
]
|
||||
```
|
||||
@@ -72,7 +74,7 @@ Oder als Objekt mit Daten-Array:
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{ "Uhrzeit": "10:00:38", "SDS_P1": 9.15, "SDS_P2": 6.22 }
|
||||
{ "Uhrzeit": "10:00:38", "SDS_P1": 9.15, "SDS_P2": 6.22, "sensor_name": "Sensor-1" }
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -94,6 +96,7 @@ Für die automatische Datenerfassung und -speicherung muss eine NocoDB-Datenbank
|
||||
| `Title` | Text | Optionaler Titel (kann leer bleiben) |
|
||||
| `SDS_P1` | Number | Feinstaub PM10-Wert |
|
||||
| `SDS_P2` | Number | Feinstaub PM2.5-Wert |
|
||||
| `sensor_name` | Text | Bezeichnung des Sensors zur Unterscheidung mehrerer Sensoren |
|
||||
| `BME280_Temperature` | Number | Temperatur in °C |
|
||||
| `BME280_Humidity` | Number | Luftfeuchtigkeit in % |
|
||||
| `BME280_Pressure` | Number | Luftdruck (optional) |
|
||||
@@ -132,7 +135,7 @@ Das Projekt enthält zwei n8n-Workflows für die Automatisierung:
|
||||
|
||||
**Workflow-Schritte**:
|
||||
1. **Webhook**: Empfängt Anfragen auf `/sds-data`
|
||||
2. **NocoDB GetAll**: Liest alle Daten aus der Tabelle (SDS_P1, SDS_P2, Uhrzeit)
|
||||
2. **NocoDB GetAll**: Liest alle Daten aus der Tabelle (SDS_P1, SDS_P2, Uhrzeit, sensor_name)
|
||||
3. **JavaScript Code**: Filtert die letzten 50 Einträge
|
||||
4. **Response**: Gibt JSON-Daten zurück
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"fields": [
|
||||
"SDS_P1",
|
||||
"SDS_P2",
|
||||
"Uhrzeit"
|
||||
"Uhrzeit",
|
||||
"sensor_name"
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -130,7 +131,7 @@
|
||||
"availableInMCP": false,
|
||||
"errorWorkflow": "0bBZzSE6SUzVsif5"
|
||||
},
|
||||
"versionId": "9ece3377-504a-4df7-900d-ef23c1bd5905",
|
||||
"versionId": "220f6cf9-fc5b-4afe-875d-a70cb8cadf36",
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "2f7fd37175cefa82de44e06b8af9ab9c01e7956018045d6efc4f7bf1588a41eb"
|
||||
|
||||
119
feinstaub.html
119
feinstaub.html
@@ -182,6 +182,28 @@
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.sensor-selection {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
background: #f7fafc;
|
||||
border-radius: 5px;
|
||||
border-left: 4px solid #48bb78;
|
||||
display: none;
|
||||
}
|
||||
.sensor-selection h3 {
|
||||
margin-bottom: 10px;
|
||||
color: #2d3748;
|
||||
}
|
||||
.sensor-selection select {
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
border: 2px solid #e2e8f0;
|
||||
border-radius: 5px;
|
||||
font-size: 14px;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -221,7 +243,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="sensor-selection" id="sensorSelection">
|
||||
<h3>Sensor-Auswahl</h3>
|
||||
<select id="sensorSelect">
|
||||
<option value="all">Alle Sensoren</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="status"></div>
|
||||
<div class="stats" id="stats" style="display: none;">
|
||||
<div class="stat-card">
|
||||
@@ -254,9 +283,45 @@
|
||||
let filterSettings = {
|
||||
enabled: true,
|
||||
p1Max: 100,
|
||||
p2Max: 100
|
||||
p2Max: 100,
|
||||
selectedSensor: 'all'
|
||||
};
|
||||
|
||||
function detectAndPopulateSensors(data) {
|
||||
// Prüfen, ob sensor_name Feld vorhanden ist
|
||||
const hasSensorName = data.length > 0 && data.some(item => item.sensor_name);
|
||||
|
||||
if (!hasSensorName) {
|
||||
// Sensor-Auswahl ausblenden, wenn kein sensor_name vorhanden
|
||||
document.getElementById('sensorSelection').style.display = 'none';
|
||||
filterSettings.selectedSensor = 'all';
|
||||
return;
|
||||
}
|
||||
|
||||
// Unique Sensoren extrahieren
|
||||
const sensors = [...new Set(data.map(item => item.sensor_name).filter(Boolean))];
|
||||
|
||||
if (sensors.length > 1) {
|
||||
// Sensor-Auswahl nur anzeigen, wenn mehrere Sensoren vorhanden sind
|
||||
document.getElementById('sensorSelection').style.display = 'block';
|
||||
|
||||
// Dropdown befüllen
|
||||
const select = document.getElementById('sensorSelect');
|
||||
select.innerHTML = '<option value="all">Alle Sensoren</option>';
|
||||
|
||||
sensors.forEach(sensor => {
|
||||
const option = document.createElement('option');
|
||||
option.value = sensor;
|
||||
option.textContent = sensor;
|
||||
select.appendChild(option);
|
||||
});
|
||||
} else {
|
||||
// Nur ein Sensor vorhanden, Auswahl nicht nötig
|
||||
document.getElementById('sensorSelection').style.display = 'none';
|
||||
filterSettings.selectedSensor = sensors[0] || 'all';
|
||||
}
|
||||
}
|
||||
|
||||
function calculateOutlierThreshold(values) {
|
||||
const sorted = [...values].sort((a, b) => a - b);
|
||||
const q1Index = Math.floor(sorted.length * 0.25);
|
||||
@@ -269,20 +334,27 @@
|
||||
}
|
||||
|
||||
function applyFilters(data) {
|
||||
if (!filterSettings.enabled) {
|
||||
document.getElementById('filteredCount').textContent = '0 Datenpunkte entfernt';
|
||||
return data;
|
||||
let filtered = data;
|
||||
|
||||
// Filter nach Sensor
|
||||
if (filterSettings.selectedSensor !== 'all') {
|
||||
filtered = filtered.filter(item => item.sensor_name === filterSettings.selectedSensor);
|
||||
}
|
||||
|
||||
const filtered = data.filter(item => {
|
||||
const p1 = parseFloat(item.SDS_P1) || 0;
|
||||
const p2 = parseFloat(item.SDS_P2) || 0;
|
||||
return p1 <= filterSettings.p1Max && p2 <= filterSettings.p2Max;
|
||||
});
|
||||
// Filter nach Ausreißern (nur wenn aktiviert)
|
||||
if (filterSettings.enabled) {
|
||||
const beforeOutlierFilter = filtered.length;
|
||||
filtered = filtered.filter(item => {
|
||||
const p1 = parseFloat(item.SDS_P1) || 0;
|
||||
const p2 = parseFloat(item.SDS_P2) || 0;
|
||||
return p1 <= filterSettings.p1Max && p2 <= filterSettings.p2Max;
|
||||
});
|
||||
const removed = beforeOutlierFilter - filtered.length;
|
||||
document.getElementById('filteredCount').textContent = `${removed} Datenpunkte entfernt`;
|
||||
} else {
|
||||
document.getElementById('filteredCount').textContent = '0 Datenpunkte entfernt';
|
||||
}
|
||||
|
||||
const removed = data.length - filtered.length;
|
||||
document.getElementById('filteredCount').textContent = `${removed} Datenpunkte entfernt`;
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
@@ -334,13 +406,14 @@
|
||||
|
||||
function processData(jsonData) {
|
||||
let data = Array.isArray(jsonData) ? jsonData : (jsonData.data || jsonData.records || []);
|
||||
|
||||
|
||||
if (data.length === 0) {
|
||||
showStatus('Keine Daten gefunden. Überprüfen Sie das JSON-Format.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
rawData = data;
|
||||
detectAndPopulateSensors(rawData);
|
||||
autoDetectThresholds(rawData);
|
||||
renderChart();
|
||||
}
|
||||
@@ -525,14 +598,20 @@
|
||||
renderChart();
|
||||
});
|
||||
|
||||
// Sensor-Auswahl Event Listener
|
||||
document.getElementById('sensorSelect').addEventListener('change', function(e) {
|
||||
filterSettings.selectedSensor = e.target.value;
|
||||
renderChart();
|
||||
});
|
||||
|
||||
// Beispieldaten für Demo
|
||||
const exampleData = [
|
||||
{ Uhrzeit: '09:45:36', SDS_P1: 17.53, SDS_P2: 96272.72 },
|
||||
{ Uhrzeit: '10:00:38', SDS_P1: 9.15, SDS_P2: 6.22 },
|
||||
{ Uhrzeit: '10:15:36', SDS_P1: 9.75, SDS_P2: 5.97 },
|
||||
{ Uhrzeit: '10:30:36', SDS_P1: 9.93, SDS_P2: 6.35 },
|
||||
{ Uhrzeit: '10:45:36', SDS_P1: 9.55, SDS_P2: 5.88 },
|
||||
{ Uhrzeit: '11:00:36', SDS_P1: 12.15, SDS_P2: 6.93 }
|
||||
{ Uhrzeit: '09:45:36', SDS_P1: 17.53, SDS_P2: 96272.72, sensor_name: 'Sensor-1' },
|
||||
{ Uhrzeit: '10:00:38', SDS_P1: 9.15, SDS_P2: 6.22, sensor_name: 'Sensor-1' },
|
||||
{ Uhrzeit: '10:15:36', SDS_P1: 9.75, SDS_P2: 5.97, sensor_name: 'Sensor-2' },
|
||||
{ Uhrzeit: '10:30:36', SDS_P1: 9.93, SDS_P2: 6.35, sensor_name: 'Sensor-1' },
|
||||
{ Uhrzeit: '10:45:36', SDS_P1: 9.55, SDS_P2: 5.88, sensor_name: 'Sensor-2' },
|
||||
{ Uhrzeit: '11:00:36', SDS_P1: 12.15, SDS_P2: 6.93, sensor_name: 'Sensor-2' }
|
||||
];
|
||||
|
||||
// Demo-Daten initial laden
|
||||
|
||||
Reference in New Issue
Block a user