INVERTER-ESP Browser Simulator
Zweck: Live-Demo des E-Paper Displays im Browser fรผr API-Testing ohne Hardware
Zielgruppe: API-Entwickler, Demo fรผr Stakeholder
Status: ๐ Konzept (nicht implementiert)
๐ฏ Anforderungen
Must Have:
- โ Zeigt gleiches Layout wie E-Paper Display (296ร128 Monochrome)
- โ 4 Screens: Dashboard, History, SysInfo, Settings
- โ Navigation mit Buttons (1=Next, 2=Prev, 3=Refresh)
- โ Langsamer Refresh (30 Sekunden wie E-Paper)
- โ Live-Daten von API (Demo-Daten)
- โ Nicht-interaktiv auรer Blรคttern
Nice to Have:
- โธ๏ธ E-Paper "Ghosting" Effect Simulation
- โธ๏ธ Screen-Transition Animation (fade to white, then new screen)
- โธ๏ธ Auto-Rotation durch alle Screens
๐ง Technische Optionen
Option 1: LVGL WASM (Native LVGL im Browser) ๐ EMPFOHLEN
Vorteile: - โ Identischer Code wie ESP32 Firmware - โ Pixel-perfektes Rendering - โ LVGL monochrome Modus funktioniert - โ Einfach zu maintainen (eine Codebase)
Nachteile: - โ ๏ธ Komplexe Build-Pipeline (Emscripten) - โ ๏ธ ~2-3 Tage Setup-Zeit - โ ๏ธ LVGL muss mit Emscripten kompiliert werden
Technologie:
Beispiel: https://docs.lvgl.io/master/get-started/platforms/web.html
Option 2: HTML5 Canvas Nachbau (Einfach & Schnell) โก
Vorteile: - โ Schnell implementiert (1-2 Tage) - โ Keine WASM-Komplexitรคt - โ Einfach in API-Server integrierbar - โ Responsive Design mรถglich
Nachteile: - โ ๏ธ Nicht identisch mit ESP32 Code - โ ๏ธ Manuelles Layout-Nachbauen - โ ๏ธ Zwei Codebases zu maintainen
Technologie:
Option 3: SVG/CSS Nachbau (Einfachste Variante) ๐
Vorteile: - โ Sehr schnell (4-6 Stunden) - โ Kein Canvas-Rendering nรถtig - โ Responsive & skalierbar - โ Einfach zu stylen
Nachteile: - โ ๏ธ Sieht nicht wie E-Paper aus - โ ๏ธ Kein pixel-perfektes Rendering - โ ๏ธ Zwei Codebases
Technologie:
๐ Empfehlung: Option 2 (Canvas Nachbau)
Grund: Beste Balance zwischen Aufwand und Ergebnis fรผr API-Demo
Warum nicht Option 1 (LVGL WASM)?
- Zu komplex fรผr eine Demo
- 2-3 Tage Setup vs 1-2 Tage fertige Demo
- ESP32 Code ist noch nicht fertig (keine UI zu kompilieren)
Warum nicht Option 3 (SVG/CSS)?
- Zu weit von realer Hardware entfernt
- Wirkt nicht wie E-Paper Display
- Fรผr Demo wichtig: "So sieht es auf dem Device aus"
๐ Implementation Plan (Option 2: Canvas)
Phase 1: Basic Canvas Setup (2-3 Stunden)
<!DOCTYPE html>
<html>
<head>
<title>INVERTER-ESP E-Paper Simulator</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #333;
font-family: 'Courier New', monospace;
}
#simulator-container {
background: #e0e0e0;
padding: 40px;
border-radius: 10px;
box-shadow: 0 10px 50px rgba(0,0,0,0.5);
}
#epaper-canvas {
background: white;
border: 2px solid #666;
image-rendering: pixelated;
/* 296ร128 scaled 3x = 888ร384 */
width: 888px;
height: 384px;
}
.controls {
margin-top: 20px;
text-align: center;
}
button {
background: #444;
color: white;
border: none;
padding: 15px 30px;
margin: 0 10px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background: #666;
}
button:active {
background: #222;
}
.status {
margin-top: 10px;
color: #ccc;
font-size: 12px;
}
</style>
</head>
<body>
<div id="simulator-container">
<canvas id="epaper-canvas" width="296" height="128"></canvas>
<div class="controls">
<button onclick="prevScreen()">โ Prev (2)</button>
<button onclick="refreshData()">๐ Refresh (3)</button>
<button onclick="nextScreen()">Next (1) โถ</button>
</div>
<div class="status">
<span id="current-screen">Screen: Dashboard</span> |
<span id="last-update">Updated: --:--</span> |
<span id="next-refresh">Next: 30s</span>
</div>
</div>
<script src="epaper-simulator.js"></script>
</body>
</html>
Phase 2: Canvas Rendering Engine (3-4 Stunden)
// epaper-simulator.js
class EPaperSimulator {
constructor(canvasId, apiBaseUrl) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.apiBaseUrl = apiBaseUrl;
this.width = 296;
this.height = 128;
this.currentScreen = 0;
this.screens = ['dashboard', 'history', 'sysinfo', 'settings'];
this.screenNames = ['Dashboard', 'History', 'System Info', 'Settings'];
this.data = null;
this.lastUpdate = null;
this.refreshInterval = 30000; // 30 seconds
this.init();
}
init() {
// White background (E-Paper style)
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.width, this.height);
// Start data fetching
this.fetchData();
setInterval(() => this.fetchData(), this.refreshInterval);
// Keyboard support
document.addEventListener('keydown', (e) => {
if (e.key === '1') this.nextScreen();
if (e.key === '2') this.prevScreen();
if (e.key === '3') this.refreshData();
});
this.render();
}
async fetchData() {
try {
const response = await fetch(`${this.apiBaseUrl}/api/inverter/status`);
this.data = await response.json();
this.lastUpdate = new Date();
this.updateStatus();
this.render();
} catch (error) {
console.error('API fetch failed:', error);
}
}
render() {
// Clear canvas (E-Paper refresh effect)
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.width, this.height);
// Render current screen
switch (this.screens[this.currentScreen]) {
case 'dashboard':
this.renderDashboard();
break;
case 'history':
this.renderHistory();
break;
case 'sysinfo':
this.renderSysInfo();
break;
case 'settings':
this.renderSettings();
break;
}
}
renderDashboard() {
if (!this.data) return;
// Title
this.drawText('INVERTER-ESP', 148, 15, 16, 'center');
// Power (large)
this.drawText(`${this.data.power} kW`, 148, 50, 32, 'center', 'bold');
// Energy today
this.drawText(`Today: ${this.data.energyToday} kWh`, 148, 80, 14, 'center');
// Status
const statusText = this.data.producing ? 'Producing' : 'Idle';
this.drawText(statusText, 148, 100, 14, 'center');
// Footer
this.drawText('Press 1 for History', 148, 120, 10, 'center');
}
renderHistory() {
// Title
this.drawText('7-Day History', 148, 15, 14, 'center');
if (!this.data || !this.data.history) return;
// Bar chart
const barWidth = 30;
const barSpacing = 10;
const maxHeight = 70;
const baseY = 110;
const maxValue = Math.max(...this.data.history.map(d => d.energy));
this.data.history.forEach((day, i) => {
const x = 20 + i * (barWidth + barSpacing);
const height = (day.energy / maxValue) * maxHeight;
const y = baseY - height;
// Bar
this.ctx.fillStyle = 'black';
this.ctx.fillRect(x, y, barWidth, height);
// Day label
this.drawText(day.label, x + barWidth/2, baseY + 15, 10, 'center');
// Value
this.drawText(`${day.energy}`, x + barWidth/2, y - 5, 8, 'center');
});
}
renderSysInfo() {
this.drawText('System Info', 148, 15, 14, 'center');
if (!this.data) return;
let y = 35;
const lineHeight = 18;
this.drawText(`WiFi: ${this.data.wifi.ssid}`, 10, y, 12); y += lineHeight;
this.drawText(`IP: ${this.data.wifi.ip}`, 10, y, 12); y += lineHeight;
this.drawText(`API: ${this.data.api.status}`, 10, y, 12); y += lineHeight;
this.drawText(`Uptime: ${this.data.system.uptime}`, 10, y, 12); y += lineHeight;
this.drawText(`Memory: ${this.data.system.freeMemory} KB`, 10, y, 12);
}
renderSettings() {
this.drawText('Settings', 148, 15, 14, 'center');
const items = [
'> WiFi Setup',
' API Config',
' Update Interval',
' Display Contrast',
' Firmware Update',
' Factory Reset'
];
let y = 35;
items.forEach(item => {
this.drawText(item, 10, y, 12);
y += 15;
});
}
drawText(text, x, y, size, align = 'left', weight = 'normal') {
this.ctx.fillStyle = 'black';
this.ctx.font = `${weight} ${size}px monospace`;
this.ctx.textAlign = align;
this.ctx.textBaseline = 'top';
this.ctx.fillText(text, x, y);
}
nextScreen() {
this.currentScreen = (this.currentScreen + 1) % this.screens.length;
this.updateStatus();
this.render();
}
prevScreen() {
this.currentScreen = (this.currentScreen - 1 + this.screens.length) % this.screens.length;
this.updateStatus();
this.render();
}
refreshData() {
this.fetchData();
}
updateStatus() {
document.getElementById('current-screen').textContent =
`Screen: ${this.screenNames[this.currentScreen]}`;
if (this.lastUpdate) {
const time = this.lastUpdate.toLocaleTimeString();
document.getElementById('last-update').textContent = `Updated: ${time}`;
}
}
}
// Initialize simulator
const simulator = new EPaperSimulator('epaper-canvas', 'http://localhost:8090');
Phase 3: API Integration (1-2 Stunden)
API Endpoint benรถtigt:
GET /api/inverter/status
Response:
{
"power": 3.5,
"energyToday": 12.5,
"producing": true,
"history": [
{"label": "Mo", "energy": 18.3},
{"label": "Tu", "energy": 22.1},
{"label": "We", "energy": 19.8},
{"label": "Th", "energy": 21.5},
{"label": "Fr", "energy": 20.2},
{"label": "Sa", "energy": 23.7},
{"label": "Su", "energy": 12.5}
],
"wifi": {
"ssid": "HomeWiFi",
"ip": "192.168.1.100",
"rssi": -45
},
"api": {
"status": "Connected",
"lastSync": "2025-10-23T14:30:00Z"
},
"system": {
"uptime": "2d 5h 23m",
"freeMemory": 234
}
}
Phase 4: E-Paper Effekte (Optional, 2-3 Stunden)
// Add ghosting effect (simulate E-Paper partial refresh)
renderWithGhosting() {
// Store old screen
const oldData = this.ctx.getImageData(0, 0, this.width, this.height);
// Flash white (full refresh simulation)
this.ctx.fillStyle = 'white';
this.ctx.fillRect(0, 0, this.width, this.height);
setTimeout(() => {
// Show ghosted old screen (30% opacity)
this.ctx.globalAlpha = 0.3;
this.ctx.putImageData(oldData, 0, 0);
this.ctx.globalAlpha = 1.0;
setTimeout(() => {
// Render new screen
this.render();
}, 100);
}, 100);
}
// Add slow refresh animation
async refreshWithAnimation() {
// Fade to white
for (let alpha = 0; alpha <= 1; alpha += 0.1) {
this.ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`;
this.ctx.fillRect(0, 0, this.width, this.height);
await this.sleep(50);
}
// Fetch new data
await this.fetchData();
// Fade in new screen
this.render();
}
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
๐ Deployment
Im API-Server integrieren
Ordnerstruktur:
api-server/
โโโ src/
โ โโโ routes/
โ โ โโโ inverter.ts API endpoints
โ โโโ public/
โ โโโ simulator/
โ โ โโโ index.html Simulator UI
โ โ โโโ epaper-simulator.js
โ โ โโโ styles.css
โ โโโ index.html Landing page
URL: http://localhost:8090/simulator/
Als standalone HTML
Einfach die 3 Dateien รถffnen:
- index.html
- epaper-simulator.js
- styles.css
Funktioniert auch ohne Server (CORS beachten fรผr API calls).
โ Checkliste fรผr anderen Agent
Must Do:
- API Endpoint
/api/inverter/statuserstellen - Demo-Daten generieren (mock solar data)
- CORS headers setzen (fรผr lokales Testen)
- 30-Sekunden Refresh implementieren (Backend)
Should Do:
- Canvas Simulator HTML/JS erstellen (siehe oben)
- In API-Server integrieren (
/simulator/route) - Keyboard shortcuts testen (1/2/3)
- Responsive Design (optional)
Nice to Have:
- E-Paper Ghosting Effekt
- Auto-Rotation durch Screens
- Screenshot-Funktion
- Download als PNG
๐ Vorbereitete Dateien
Ich kann folgendes vorbereiten:
- โ
simulator/web/index.html- Complete HTML structure - โ
simulator/web/epaper-simulator.js- Full JavaScript implementation - โ
simulator/web/styles.css- Styling (optional, inline mรถglich) - โ
simulator/web/README.md- Integration guide - โธ๏ธ API Endpoint Spec - fรผr anderen Agent (JSON format)
๐ฏ Zeitaufwand
Minimal (ohne Effekte): - HTML/CSS: 1 Stunde - JavaScript: 3 Stunden - API Integration: 1 Stunde - Testing: 1 Stunde Total: ~6 Stunden
Mit E-Paper Effekten: - + Ghosting Effect: 2 Stunden - + Animations: 1 Stunde - + Polish: 1 Stunde Total: ~10 Stunden
๐ค Agent Handover
Fรผr API Agent:
1. Erstelle /api/inverter/status endpoint
2. Returniere JSON mit Demo-Daten (siehe Schema oben)
3. Update alle 30 Sekunden mit neuen mock values
4. Setze CORS headers
Fรผr mich (Simulator Agent): 1. Erstelle HTML/JS Files 2. Test mit mock API 3. Integration guide schreiben 4. Screenshot fรผr README
๐ Nรคchste Schritte
Soll ich vorbereiten?: - [ ] Complete HTML/CSS/JS files erstellen? - [ ] Mock API spec fรผr anderen Agent? - [ ] Integration guide schreiben? - [ ] Quick-Start README?
Oder soll der andere Agent selbst implementieren? โ Dann nur API spec und Requirements dokumentieren
Status: ๐ Konzept fertig, warte auf Entscheidung
Empfehlung: Ich erstelle die Files, anderer Agent integriert in seinen API-Server