# Getränkeautomat Monitor Getränkeautomat Monitor ist eine kleine PHP/HTML/JS-Anwendung zur Überwachung von Füllständen in Getränkeautomaten. Ein ESP32 oder ein anderer Sensor-Client sendet Messwerte an eine einfache REST-nahe API. Die Anwendung berechnet daraus den geschätzten Bestand pro Fach, visualisiert den aktuellen Status im Browser und löst bei kritischen Schwellwerten Alarme aus. Die Anwendung ist bewusst simpel gehalten: - Backend mit plain PHP - Frontend mit HTML, CSS und Vanilla JavaScript - Persistenz nur in JSON-Dateien - keine Datenbank - kein Framework ## Dokumentation - Deployment auf Shared Hosting: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) - Konfiguration: [docs/CONFIG.md](docs/CONFIG.md) - API: [docs/API.md](docs/API.md) ## Funktionsumfang - mehrere Automaten in einer Instanz - mehrere Fächer pro Automat - REST-nahe Eingangs-API für Einzelmessungen - Dashboard mit visueller Füllstandsanzeige - Alarmierung per Webhook und per Email - Adminpanel zur Bearbeitung der JSON-Config - statischer Admin-Login mit bcrypt-Hash ## Architektur im Überblick Der Datenfluss ist einfach: 1. Ein Sensor-Client sendet einen Messwert in Millimetern an `POST /api/v1/readings.php`. 2. Die Anwendung sucht das passende Fach über `machine_id` und `sensor_id`. 3. Aus `full_distance_mm`, `empty_distance_mm` und `distance_per_unit` werden Füllstand und Flaschenanzahl berechnet. 4. Der letzte bekannte Zustand wird in `data/state.json` gespeichert. 5. Wenn sich der Status von `ok` nach `critical` oder von `critical` nach `ok` ändert, wird ein Alarmereignis erzeugt. 6. Dashboard und Adminpanel lesen den aktuellen Zustand über `GET /api/v1/status.php`. ## Projektstruktur Die Anwendung ist jetzt so aufgebaut, dass der gesamte Projektordner direkt als Apache-Unterordner deployt werden kann. - `index.php`, `admin/`, `api/`, `api-docs/`, `app.js`, `styles.css`, `openapi.yaml` - öffentliche Runtime-Dateien - `src/` - interne PHP-Kernlogik - wird auf Apache-Hosting per `.htaccess` gesperrt - `data/` - JSON-Dateien für Konfiguration, aktuellen Zustand und Alarmhistorie - enthält zusätzlich `php_errors.log` für PHP-Fehler - liegt im selben Ordner wie die App, ist aber per `.htaccess` gesperrt - `docs/` - Markdown-Dokumentation für Entwickler - wird auf Apache-Hosting ebenfalls per `.htaccess` gesperrt Wichtige Dateien: - `index.php`: Dashboard - `admin/index.php`: Login und Adminpanel - `api/v1/readings.php`: API für eingehende Sensorwerte - `api/v1/status.php`: Status-API für Dashboard und Adminpanel - `api-docs/index.html`: Swagger UI - `openapi.yaml`: OpenAPI-Spec - `src/MonitorService.php`: zentrale Orchestrierung für Lesen, Berechnen und Status - `src/InventoryService.php`: Berechnung von Füllgrad und Bestand - `src/AlertService.php`: Webhook- und Email-Alarmierung - `data/config.json`: Hauptkonfiguration - `data/state.json`: letzter bekannter Zustand je Fach - `data/alert_log.json`: Alarm- und Entwarnungsereignisse - `data/php_errors.log`: PHP-Fehlerlog der Anwendung ## Voraussetzungen Für den Betrieb wird auf dem Zielsystem benötigt: - PHP 8.1 oder neuer empfohlen - Schreibrechte auf `data/` - Apache mit aktivem `.htaccess`-Support für die Produktionsbereitstellung - funktionierende Mail-Konfiguration, falls `mail()` für Email-Alarme genutzt werden soll - Webserver oder PHP Built-in Server PHP schreibt Laufzeitfehler in `data/php_errors.log`. ## Schnellstart Sobald PHP auf dem Zielsystem installiert ist: ```bash php -S localhost:8000 ``` Danach ist die Anwendung erreichbar unter: - Dashboard: `http://localhost:8000/` - Adminpanel: `http://localhost:8000/admin/` - Status-API: `http://localhost:8000/api/v1/status.php` - Swagger UI: `http://localhost:8000/api-docs/` - OpenAPI-Spec: `http://localhost:8000/openapi.yaml` Hinweis: Der PHP Built-in Server wertet `.htaccess` nicht aus. Die Verzeichnisse `src/`, `data/` und `docs/` werden auf Shared Hosting erst durch Apache geschützt. ## Default-Zugangsdaten Die Beispiel-Konfiguration bringt absichtlich einfache Startwerte mit: - Admin-Benutzer: `admin` - Admin-Passwort: `admin123` - API-Bearer-Token: `demo-esp32-token` Diese Werte sollten direkt nach dem ersten Login angepasst werden. ## Dashboard Das Dashboard ist die öffentliche Visualisierung der Anlage. Es zeigt: - Anzahl Automaten, Fächer und aktuell kritische Fächer - Filter nach Automat - pro Fach: - Label - Produktname - visuellen Füllstand - geschätzte Anzahl Flaschen - Alarmgrenze - letzten Messwert in Millimetern - letzten Messzeitpunkt - letzte Alarm- und Entwarnungsereignisse Die Daten werden regelmäßig per Polling über `status.php` aktualisiert. Das Intervall wird in `config.json` über `app.dashboard_refresh_seconds` gesteuert. ## Adminpanel Das Adminpanel ist über `/admin/` erreichbar und erlaubt: - Ändern des App-Namens und des Refresh-Intervalls - Ändern des API-Bearer-Tokens - Ändern von Admin-Benutzername und Passwort - Verwalten von Webhooks - Verwalten von Email-Empfängern - Verwalten von Automaten und ihren Fächern Beim Speichern des Admin-Passworts wird immer ein bcrypt-Hash erzeugt. Das Passwort selbst wird nicht im Klartext gespeichert. ## Alarmverhalten Alarme werden nicht bei jeder eingehenden kritischen Messung ausgelöst, sondern nur bei Zustandswechseln: - `ok -> critical`: Alarm - `critical -> ok`: Entwarnung - `critical -> critical`: kein weiterer Alarm - `ok -> ok`: kein Alarm Dadurch werden doppelte Daueralarme vermieden. Ein Fach gilt als kritisch, wenn gilt: ```text units_estimated < alert_below_units ``` Ein Fach mit `alert_below_units = 2` löst also erst dann Alarm aus, wenn nur noch `1` oder `0` Einheiten geschätzt werden. ## Wie die Bestandsberechnung funktioniert Jedes Fach hat drei wichtige Kalibrierwerte: - `full_distance_mm` - `empty_distance_mm` - `distance_per_unit` Die App behandelt `full_distance_mm` als 100 Prozent und `empty_distance_mm` als 0 Prozent. Dabei ist es egal, ob kleinere oder größere Zahlen "voll" bedeuten, weil die Berechnung die Sensororientierung automatisch berücksichtigt. Die grobe Logik ist: ```text fill_ratio = (distance_mm - empty_distance_mm) / (full_distance_mm - empty_distance_mm) fill_ratio wird auf 0..1 begrenzt max_units = abs(full_distance_mm - empty_distance_mm) / distance_per_unit units_estimated = round(fill_ratio * max_units) ``` Damit repräsentiert `distance_per_unit` die Änderung des Messwerts pro Flasche oder Einheit. ## API-Referenz Die API ist jetzt auf drei Ebenen dokumentiert: - Interaktive Swagger UI unter `http://localhost:8000/api-docs/` - Maschinenlesbare OpenAPI-Spec unter `http://localhost:8000/openapi.yaml` - Erläuternde Referenz in [docs/API.md](docs/API.md) ### `POST /api/v1/readings.php` Nimmt genau einen Messwert entgegen. Header: - `Authorization: Bearer ` - `Content-Type: application/json` Body: ```json { "machine_id": "automat-lobby", "sensor_id": "fach-a1", "distance_mm": 184, "measured_at": "2026-04-15T19:20:00Z" } ``` `measured_at` ist optional. Wenn der Wert fehlt, setzt der Server die aktuelle Zeit. Beispiel: ```bash curl -X POST http://localhost:8000/api/v1/readings.php \ -H 'Authorization: Bearer demo-esp32-token' \ -H 'Content-Type: application/json' \ -d '{ "machine_id": "automat-lobby", "sensor_id": "fach-a1", "distance_mm": 184, "measured_at": "2026-04-15T19:20:00Z" }' ``` Erfolg: ```json { "ok": true, "machine_id": "automat-lobby", "sensor_id": "fach-a1", "slot_label": "A1", "units_estimated": 4, "fill_percent": 63, "state": "ok" } ``` ### `GET /api/v1/status.php` Liefert den aktuellen aggregierten Zustand aller Automaten und Fächer inklusive letzter Alarmereignisse. ## Konfiguration Die Konfiguration liegt in `data/config.json`. Eine detaillierte Beschreibung aller Felder steht in [docs/CONFIG.md](docs/CONFIG.md). Die obersten Bereiche sind: - `app`: Name, Zeitzone, UI-Refresh, Email-Absender - `api`: Bearer-Token für Sensor-Clients - `admin`: Benutzername und Passwort-Hash - `alerts`: wiederverwendbare Webhooks und Email-Empfänger - `machines`: Automaten mit ihren Fächern ## Zustandsdateien ### `data/state.json` Diese Datei enthält den letzten bekannten Zustand jedes Fachs. Sie wird von der API bei jeder gültigen Messung aktualisiert. Typische Inhalte: - letzter Messwert - letzter Messzeitpunkt - geschätzter Bestand - maximaler Bestand - aktueller Status - verknüpfte Alarmkanal-IDs ### `data/alert_log.json` Diese Datei enthält die zuletzt ausgelösten Alarmereignisse und Entwarnungen inklusive Lieferstatus für Webhooks und Emails. ## JSON-Persistenz und Dateisperren Die Anwendung nutzt für Schreibzugriffe Dateisperren über `flock()`. Das reduziert das Risiko beschädigter JSON-Dateien bei parallelen Requests, zum Beispiel wenn mehrere Sensoren fast gleichzeitig Messwerte senden. ## Betriebshinweise - `data/` sollte nicht direkt öffentlich über den Webserver auslieferbar sein. - Das Bearer-Token sollte nur an bekannte Sensor-Clients verteilt werden. - Die Beispiel-Zugangsdaten sollten in echten Umgebungen sofort ersetzt werden. - Webhooks sollten mit HTTPS betrieben werden. - Email funktioniert nur, wenn `mail()` auf dem Zielsystem korrekt eingerichtet ist. ## Typische Erweiterungen für spätere Versionen - Batch-Endpoint für mehrere Sensorwerte in einem Request - Langzeithistorie mit Zeitreihen oder Charts - Benutzerverwaltung statt statischem Admin-Login - Retry-Mechanismus für fehlgeschlagene Webhooks - Healthcheck-Endpunkte - CSV- oder PDF-Export ## Weitere Dokumentation - [docs/API.md](docs/API.md) - [docs/CONFIG.md](docs/CONFIG.md)