First Commit - sieht schon gut aus
This commit is contained in:
62
.gitignore
vendored
Normal file
62
.gitignore
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Node.js
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
.env.test
|
||||||
|
.env.local
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
49
README.md
Normal file
49
README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Stromverbrauch Monitor
|
||||||
|
|
||||||
|
Node.js-Anwendung zur Überwachung des Stromverbrauchs aus MongoDB-Daten.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Oder für die Entwicklung mit Auto-Reload:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Anwendung läuft dann auf: http://localhost:3000
|
||||||
|
|
||||||
|
## Konfiguration
|
||||||
|
|
||||||
|
- **MongoDB-Server**: nuccy:27017
|
||||||
|
- **Datenbank**: smarthome
|
||||||
|
- **Collection**: maschinen (weitere können hinzugefügt werden)
|
||||||
|
|
||||||
|
## Verwendung
|
||||||
|
|
||||||
|
1. Öffnen Sie http://localhost:3000 im Browser
|
||||||
|
2. Wählen Sie die Collection aus
|
||||||
|
3. Geben Sie den Zeitraum ein (Von/Bis)
|
||||||
|
4. Klicken Sie auf "Verbrauch anzeigen"
|
||||||
|
|
||||||
|
Die Tabelle zeigt für jeden Verbraucher:
|
||||||
|
- Anfangswert und -zeit
|
||||||
|
- Endwert und -zeit
|
||||||
|
- Verbrauch in Wh und kWh
|
||||||
|
- Gesamtverbrauch am Ende
|
||||||
|
|
||||||
|
## Erweiterung für weitere Collections
|
||||||
|
|
||||||
|
Um weitere Collections zu unterstützen, müssen Sie den Code anpassen, falls die Feldnamen unterschiedlich sind:
|
||||||
|
- Verbrauchername-Feld (aktuell: `maschine`)
|
||||||
|
- Arbeit-Feld (aktuell: `arbeit`)
|
||||||
|
- Timestamp-Feld (aktuell: `datetime`)
|
||||||
1750
package-lock.json
generated
Normal file
1750
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
package.json
Normal file
25
package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "stromverbrauch",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Stromverbrauch Monitor mit MongoDB",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"dev": "nodemon server.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"stromverbrauch",
|
||||||
|
"mongodb",
|
||||||
|
"express"
|
||||||
|
],
|
||||||
|
"author": "Reinhard X. Fürst",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"mongodb": "^6.3.0",
|
||||||
|
"pug": "^3.0.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.0.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
194
public/css/styles.css
Normal file
194
public/css/styles.css
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background: linear-gradient(180deg, #c8e6c9 0%, #81c784 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-container {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label input[type="checkbox"] {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus, select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #43a047;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: linear-gradient(135deg, #43a047 0%, #1b5e20 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 30px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(67, 160, 71, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-container {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-box {
|
||||||
|
background: #e3f2fd;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-left: 4px solid #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-box {
|
||||||
|
background: #ffebee;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-left: 4px solid #f44336;
|
||||||
|
color: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-top: 20px;
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background: linear-gradient(135deg, #43a047 0%, #1b5e20 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 15px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:hover {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
text-align: right;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
background: #f1f8e9;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-left: 4px solid #8bc34a;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #558b2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-value {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #33691e;
|
||||||
|
}
|
||||||
142
public/js/script.js
Normal file
142
public/js/script.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
// Datum auf heute und vor 7 Tagen setzen
|
||||||
|
function initializeDates() {
|
||||||
|
const today = new Date();
|
||||||
|
const weekAgo = new Date();
|
||||||
|
weekAgo.setDate(today.getDate() - 7);
|
||||||
|
|
||||||
|
document.getElementById('endDate').valueAsDate = today;
|
||||||
|
document.getElementById('startDate').valueAsDate = weekAgo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datum formatieren
|
||||||
|
function formatDate(dateString) {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleString('de-DE', {
|
||||||
|
day: '2-digit',
|
||||||
|
month: '2-digit',
|
||||||
|
year: 'numeric',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zahl formatieren
|
||||||
|
function formatNumber(num) {
|
||||||
|
return new Intl.NumberFormat('de-DE', {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2
|
||||||
|
}).format(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formular absenden
|
||||||
|
document.getElementById('verbrauchForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Ausgewählte Collections sammeln
|
||||||
|
const checkboxes = document.querySelectorAll('.checkbox-group input[type="checkbox"]:checked');
|
||||||
|
const selectedCollections = Array.from(checkboxes).map(cb => cb.value);
|
||||||
|
|
||||||
|
if (selectedCollections.length === 0) {
|
||||||
|
alert('Bitte wählen Sie mindestens eine Collection aus.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startDate = document.getElementById('startDate').value;
|
||||||
|
const endDate = document.getElementById('endDate').value;
|
||||||
|
|
||||||
|
const resultsDiv = document.getElementById('results');
|
||||||
|
resultsDiv.innerHTML = '<div class="loading">Lade Daten...</div>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const collectionsParam = selectedCollections.join(',');
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/verbrauch?collections=${collectionsParam}&startDate=${startDate}&endDate=${endDate}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Fehler beim Abrufen der Daten');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
displayResults(data);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
resultsDiv.innerHTML = `
|
||||||
|
<div class="error-box">
|
||||||
|
<strong>Fehler:</strong> ${error.message}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ergebnisse anzeigen
|
||||||
|
function displayResults(data) {
|
||||||
|
const resultsDiv = document.getElementById('results');
|
||||||
|
|
||||||
|
if (data.verbraucher.length === 0) {
|
||||||
|
resultsDiv.innerHTML = `
|
||||||
|
<div class="info-box">
|
||||||
|
Keine Daten für den gewählten Zeitraum gefunden.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gesamtverbrauch berechnen
|
||||||
|
const gesamtVerbrauch = data.verbraucher.reduce((sum, v) => sum + v.verbrauch, 0);
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<div class="info-box">
|
||||||
|
<strong>Zeitraum:</strong> ${formatDate(data.zeitraum.von)} - ${formatDate(data.zeitraum.bis)}<br>
|
||||||
|
<strong>Collections:</strong> ${data.collections.join(', ')}<br>
|
||||||
|
<strong>Anzahl Verbraucher:</strong> ${data.verbraucher.length}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Collection</th>
|
||||||
|
<th>Verbraucher</th>
|
||||||
|
<th class="number">Anfangswert (Wh)</th>
|
||||||
|
<th>Anfangszeit</th>
|
||||||
|
<th class="number">Endwert (Wh)</th>
|
||||||
|
<th>Endzeit</th>
|
||||||
|
<th class="number">Verbrauch (Wh)</th>
|
||||||
|
<th class="number">Verbrauch (kWh)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
`;
|
||||||
|
|
||||||
|
data.verbraucher.forEach(v => {
|
||||||
|
html += `
|
||||||
|
<tr>
|
||||||
|
<td><strong>${v.collection}</strong></td>
|
||||||
|
<td><strong>${v.verbraucher}</strong></td>
|
||||||
|
<td class="number">${formatNumber(v.anfangsWert)}</td>
|
||||||
|
<td>${formatDate(v.anfangsZeit)}</td>
|
||||||
|
<td class="number">${formatNumber(v.endWert)}</td>
|
||||||
|
<td>${formatDate(v.endZeit)}</td>
|
||||||
|
<td class="number">${formatNumber(v.verbrauch)}</td>
|
||||||
|
<td class="number">${formatNumber(v.verbrauch / 1000)}</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="summary">
|
||||||
|
<span class="summary-label">Gesamtverbrauch:</span>
|
||||||
|
<span class="summary-value">${formatNumber(gesamtVerbrauch / 1000)} kWh</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
resultsDiv.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beim Laden initialisieren
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
initializeDates();
|
||||||
|
});
|
||||||
214
server.js
Normal file
214
server.js
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { MongoClient, ObjectId } = require('mongodb');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const PORT = 3000;
|
||||||
|
|
||||||
|
// MongoDB-Verbindung
|
||||||
|
const MONGO_URL = 'mongodb://nuccy:27017';
|
||||||
|
const DB_NAME = 'smarthome';
|
||||||
|
|
||||||
|
// Konfiguration für verschiedene Collections
|
||||||
|
const COLLECTION_CONFIG = {
|
||||||
|
'maschinen': {
|
||||||
|
verbraucherFeld: 'maschine',
|
||||||
|
arbeitsFeld: 'arbeit',
|
||||||
|
zeitFeld: 'datetime',
|
||||||
|
faktor: 1, // Wh
|
||||||
|
einheit: 'Wh',
|
||||||
|
// Verbraucher-spezifische Faktoren
|
||||||
|
verbraucherFaktoren: {
|
||||||
|
'spuelmaschine': 1000 // kWh -> Wh
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'auto': {
|
||||||
|
verbraucherFeld: null, // kein Verbraucher-Feld
|
||||||
|
verbraucherName: 'Auto', // fester Name
|
||||||
|
arbeitsFeld: 'gesamt',
|
||||||
|
zeitFeld: '_id', // Zeitstempel aus ObjectId
|
||||||
|
faktor: 100, // 0.1 kWh = 100 Wh
|
||||||
|
einheit: 'Wh'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let db;
|
||||||
|
|
||||||
|
// Verbindung zur MongoDB herstellen
|
||||||
|
MongoClient.connect(MONGO_URL)
|
||||||
|
.then(client => {
|
||||||
|
console.log('Erfolgreich mit MongoDB verbunden');
|
||||||
|
db = client.db(DB_NAME);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('MongoDB Verbindungsfehler:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
// View-Engine konfigurieren
|
||||||
|
app.set('view engine', 'pug');
|
||||||
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
|
|
||||||
|
// Middleware
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
// Hauptseite rendern
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.render('index');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hilfsfunktion: Zeitstempel aus Dokument extrahieren
|
||||||
|
function getTimestamp(doc, zeitFeld) {
|
||||||
|
if (zeitFeld === '_id') {
|
||||||
|
// Zeitstempel aus ObjectId extrahieren
|
||||||
|
return doc._id.getTimestamp();
|
||||||
|
}
|
||||||
|
return doc[zeitFeld];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion: Query für Zeitbereich erstellen
|
||||||
|
function createTimeQuery(start, end, zeitFeld) {
|
||||||
|
if (zeitFeld === '_id') {
|
||||||
|
// Bei ObjectId: IDs im Zeitbereich generieren
|
||||||
|
const startId = ObjectId.createFromTime(Math.floor(start.getTime() / 1000));
|
||||||
|
const endId = ObjectId.createFromTime(Math.floor(end.getTime() / 1000));
|
||||||
|
return { _id: { $gte: startId, $lte: endId } };
|
||||||
|
}
|
||||||
|
return { [zeitFeld]: { $gte: start, $lte: end } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion: Daten für eine Collection abrufen
|
||||||
|
async function fetchCollectionData(collectionName, start, end) {
|
||||||
|
const config = COLLECTION_CONFIG[collectionName];
|
||||||
|
if (!config) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const coll = db.collection(collectionName);
|
||||||
|
const timeQuery = createTimeQuery(start, end, config.zeitFeld);
|
||||||
|
|
||||||
|
let verbraucherList;
|
||||||
|
|
||||||
|
if (config.verbraucherFeld) {
|
||||||
|
// Alle Verbraucher im Zeitraum finden
|
||||||
|
verbraucherList = await coll.distinct(config.verbraucherFeld, timeQuery);
|
||||||
|
} else {
|
||||||
|
// Nur ein Verbraucher mit festem Namen
|
||||||
|
verbraucherList = [config.verbraucherName];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Für jeden Verbraucher den ersten und letzten Wert im Zeitraum holen
|
||||||
|
const verbrauchsData = await Promise.all(
|
||||||
|
verbraucherList.map(async (verbraucher) => {
|
||||||
|
let query = { ...timeQuery };
|
||||||
|
|
||||||
|
// Verbraucher-Filter hinzufügen, falls vorhanden
|
||||||
|
if (config.verbraucherFeld) {
|
||||||
|
query[config.verbraucherFeld] = verbraucher;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sortierfeld festlegen
|
||||||
|
const sortFeld = config.zeitFeld;
|
||||||
|
|
||||||
|
// Erster Wert im Zeitraum
|
||||||
|
const ersterWert = await coll
|
||||||
|
.find(query)
|
||||||
|
.sort({ [sortFeld]: 1 })
|
||||||
|
.limit(1)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
// Letzter Wert im Zeitraum
|
||||||
|
const letzterWert = await coll
|
||||||
|
.find(query)
|
||||||
|
.sort({ [sortFeld]: -1 })
|
||||||
|
.limit(1)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
// Verbraucher-spezifischen Faktor prüfen
|
||||||
|
let faktor = config.faktor;
|
||||||
|
if (config.verbraucherFaktoren && config.verbraucherFaktoren[verbraucher]) {
|
||||||
|
faktor = config.verbraucherFaktoren[verbraucher];
|
||||||
|
}
|
||||||
|
|
||||||
|
const anfangsWert = (ersterWert[0][config.arbeitsFeld] || 0) * faktor;
|
||||||
|
const endWert = (letzterWert[0][config.arbeitsFeld] || 0) * faktor;
|
||||||
|
const verbrauch = endWert - anfangsWert;
|
||||||
|
|
||||||
|
return {
|
||||||
|
verbraucher: verbraucher,
|
||||||
|
collection: collectionName,
|
||||||
|
anfangsWert: anfangsWert,
|
||||||
|
anfangsZeit: getTimestamp(ersterWert[0], config.zeitFeld),
|
||||||
|
endWert: endWert,
|
||||||
|
endZeit: getTimestamp(letzterWert[0], config.zeitFeld),
|
||||||
|
verbrauch: verbrauch,
|
||||||
|
einheit: 'Wh' // Normalisiert auf Wh
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Null-Werte filtern (Verbraucher ohne Daten)
|
||||||
|
return verbrauchsData.filter(item => item !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API-Endpoint zum Abrufen der Verbrauchsdaten
|
||||||
|
app.get('/api/verbrauch', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { collections, startDate, endDate } = req.query;
|
||||||
|
|
||||||
|
if (!collections || !startDate || !endDate) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: 'Parameter fehlen: collections, startDate, endDate erforderlich'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = new Date(startDate);
|
||||||
|
const end = new Date(endDate);
|
||||||
|
|
||||||
|
// Ende auf 23:59:59 setzen
|
||||||
|
end.setHours(23, 59, 59, 999);
|
||||||
|
|
||||||
|
// Collections-Parameter kann kommaseparierte Liste sein
|
||||||
|
const collectionList = typeof collections === 'string'
|
||||||
|
? collections.split(',').map(c => c.trim())
|
||||||
|
: [collections];
|
||||||
|
|
||||||
|
// Daten für alle Collections abrufen
|
||||||
|
const allResults = await Promise.all(
|
||||||
|
collectionList.map(collectionName =>
|
||||||
|
fetchCollectionData(collectionName, start, end)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Alle Ergebnisse zusammenführen
|
||||||
|
const alleVerbraucher = allResults.flat();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
zeitraum: { von: start, bis: end },
|
||||||
|
collections: collectionList,
|
||||||
|
verbraucher: alleVerbraucher
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Abrufen der Daten:', error);
|
||||||
|
res.status(500).json({ error: 'Serverfehler: ' + error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// API-Endpoint zum Abrufen verfügbarer Collections
|
||||||
|
app.get('/api/collections', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const collections = await db.listCollections().toArray();
|
||||||
|
const collectionNames = collections.map(c => c.name);
|
||||||
|
res.json({ collections: collectionNames });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Abrufen der Collections:', error);
|
||||||
|
res.status(500).json({ error: 'Serverfehler: ' + error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Server starten
|
||||||
|
app.listen(PORT, () => {
|
||||||
|
console.log(`Server läuft auf http://localhost:${PORT}`);
|
||||||
|
});
|
||||||
34
views/index.pug
Normal file
34
views/index.pug
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
doctype html
|
||||||
|
html(lang='de')
|
||||||
|
head
|
||||||
|
meta(charset='UTF-8')
|
||||||
|
meta(name='viewport', content='width=device-width, initial-scale=1.0')
|
||||||
|
title Stromverbrauch Monitor
|
||||||
|
link(rel='stylesheet', href='/css/styles.css')
|
||||||
|
body
|
||||||
|
.container
|
||||||
|
h1 ⚡ Stromverbrauch Monitor
|
||||||
|
|
||||||
|
.form-container
|
||||||
|
form#verbrauchForm
|
||||||
|
.form-group
|
||||||
|
.form-field
|
||||||
|
label Collections auswählen:
|
||||||
|
.checkbox-group
|
||||||
|
label.checkbox-label
|
||||||
|
input#coll_maschinen(type='checkbox', value='maschinen', checked)
|
||||||
|
span Maschinen
|
||||||
|
label.checkbox-label
|
||||||
|
input#coll_auto(type='checkbox', value='auto', checked)
|
||||||
|
span Auto
|
||||||
|
.form-field
|
||||||
|
label(for='startDate') Von:
|
||||||
|
input#startDate(type='date', required)
|
||||||
|
.form-field
|
||||||
|
label(for='endDate') Bis:
|
||||||
|
input#endDate(type='date', required)
|
||||||
|
button(type='submit') Verbrauch anzeigen
|
||||||
|
|
||||||
|
#results.results-container
|
||||||
|
|
||||||
|
script(src='/js/script.js')
|
||||||
Reference in New Issue
Block a user