Abgestimmt am 13. April 2026. Ein Arbeitsdokument für die Umsetzung.
Alle sensiblen Daten bleiben nur auf dem Telefon. Einen Teil davon kann der Besitzer auf Wunsch über den Server leiten — mit ausdrücklicher Warnung.
| Daten | Standardmäßig | Optional über den Server |
|---|---|---|
| Koordinaten (lat, lon) | Nur auf dem Telefon | Kann mit Gästen geteilt werden (mit Warnung) |
| WLAN-Fingerabdruck (BSSID[]) | Nur auf dem Telefon | Wird nie übertragen |
| Webhook-Adresse und -Geheimnis | Nur auf dem Telefon | Kann auf dem Server gespeichert werden (mit Warnung) |
| Radius, Zeit, Bestätigungsstufe | Über den Server | — |
Beim Hinzufügen eines Objekts wählt der Besitzer den Verbindungstyp. Danach öffnet sich das Einstellungsfenster dieses Typs.
| Icon | Name | So funktioniert es | Feedback | Example |
|---|---|---|---|---|
| 📞 | Call | Ein Telefonanruf vom Telefon des Besitzers | Keine (der Anruf ging hinaus oder nicht) | Eine Schranke, eine Türsprechanlage |
| 🌐 | URL (Webhook) | Eine HTTP-Anfrage an die Adresse des Geräts, vom Telefon oder vom Entrixy-Server | Ja — eine JSON-Antwort mit Status | Ein smartes Schloss mit API, Tasmota, Shelly Cloud |
| 📟 | Internet-Controller (WS) | Ein Befehl über WebSocket an einen verbundenen Controller | Ja — eine Antwort über den WebSocket | Selbstbau ESP32 / ESP8266, Shelly Plus 1, NodeMCU |
| 📶 | BLE-Controller | Ein direkter Bluetooth-Kanal zwischen Telefon und Controller, ohne Internet | Ja — eine Antwort über BLE (notify) | Ein batteriebetriebener Controller am Tor ohne WLAN |
Der Firmware-Konfigurator für die letzten beiden Typen: /controller/ (BLE und Internet, im Browser).
Für den Nutzer sehen alle Typen gleich aus: eine Karte, eine Anruf-Schaltfläche, ein Status. Nur der Zustellmechanismus unterscheidet sich.
Im Einstellungsfenster des Webhooks gibt es einen Umschalter, Vor- und Nachteile stehen direkt auf dem Bildschirm:
| Mode | Wo Adresse und Geheimnis liegen | Kennt der Server die Adresse? |
|---|---|---|
| Vom Telefon | Prefs auf dem Telefon des Besitzers | No |
| Vom Server | Die Tabelle numbers in der Datenbank | Yes |
Sicherheit: eine HMAC-Signatur. Gerät und Entrixy teilen sich ein webhook_secret.
POST https://device-url.com/open
Content-Type: application/json
{
"action": "open",
"object_id": 42,
"timestamp": 1713020000,
"nonce": "a1b2c3",
"signature": "hmac-sha256(secret, timestamp + nonce + action)"
}
Das Gerät prüft, dass die Signatur gültig ist, der Zeitstempel jünger als 30 Sekunden und die Nonce keine Wiederholung. Passt alles, handelt es und antwortet:
{"status": "ok", "message": "Door opened"}
or
{"status": "error", "message": "Lock jammed"}
Der Status wird dem Nutzer in der App angezeigt.
The guest presses Call
→ the server receives the request
→ sends it to the owner over the WebSocket: {type: "do_webhook", ...}
→ the owner's phone makes the HTTP call to the device
→ receives the JSON response
→ sends it to the server: {type: "webhook_result", status, message}
→ the server passes it to the guest
→ both see the status in the log
In beiden Modi hält das Protokoll auf dem Telefon von Besitzer und Gast fest, wer, wann, welches Objekt und mit welchem Status.
Der Controller — unterstützt werden ESP32, ESP32-S3, ESP32-C3 und ESP8266 — hat keine öffentliche IP. Er hält eine ausgehende WebSocket-Verbindung zum Entrixy-Server, genau wie das Telefon des Besitzers.
Firmware und Konfigurator liegen unter /esp/socket/. Quellen: ESP32, ESP8266.
// Device → server: connect
{"type": "device_hello", "device_key": "xyz789", "device_secret": "..."}
// Server → device: OK
{"type": "device_ok"}
// Server → device: command
{"type": "device_command", "action": "open", "command_id": "abc123"}
// Device → server: result
{"type": "device_response", "command_id": "abc123", "status": "ok", "message": "Opened"}
Der Server leitet den Status über deren WebSocket-Verbindungen an die Clients weiter — Besitzer und Gast.
device_key + device_secretwss://entrixy.com/wsAuf der Objektkarte ist der Verbindungsstatus des Geräts zu sehen — online oder offline —, genau wie Gäste den Status des Besitzers sehen.
Ein Controller auf ESP32, S3, C3, C6 oder H2 braucht zum Auslösen weder WLAN noch Server. Er liegt im Tiefschlaf, wacht einmal pro Sekunde für etwa 200 ms auf und sendet ein BLE-Advertisement mit signiertem Zähler. Kommt ein Telefon mit gültigem Schlüssel in die Nähe, fängt Android — oder die App, wenn sie im Vordergrund ist — das Advertisement ab, verbindet sich per GATT, schickt einen Fire-Befehl mit einmaliger Nonce, und der Controller schließt das Relais für einen kurzen Impuls. Für bistabile Antriebe und Schlösser gibt es einen Modus mit getrenntem Öffnen und Schließen sowie Positionsverfolgung, bei dem offen/geschlossen als verschlüsseltes Byte im Advertisement mitreist.
Der Entrixy-Server ist an diesem Kanal nicht beteiligt. Er wird nur beim Erstellen eines Gastschlüssels gebraucht: Der Besitzer signiert ein GuestToken und übergibt es dem Gast über den Ende-zu-Ende verschlüsselten Kanal. Danach arbeitet der Gast offline.
Die vollständige Protokollspezifikation, das Bit-Layout und die Testvektoren stehen unter entrixy.com/ble. Das Bedrohungsmodell steht in einem eigenen Dokument.
An denselben Antrieb lassen sich BLE und WebSocket zugleich setzen — in der App erscheinen sie als zwei getrennte Objekte. BLE zum Öffnen aus der Nähe ohne Internet, WebSocket für die Zeiten, in denen Sie weit weg sind.
entrixy-pair)Firmware und Konfigurator liegen unter /esp/ble/. Quelle: /esp32-example/.
Auf der Objektkarte: Grau heißt im Funk nicht sichtbar, zu weit weg oder ausgeschaltet; Orange heißt sichtbar, aber die Automatik hat nicht ausgelöst; Grün heißt ausgelöst oder löst gerade aus; Grau mit grünem Rand heißt: kürzlich ausgelöst, ein Tippen öffnet erneut.
CREATE TABLE devices ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, host_id INT UNSIGNED NOT NULL, device_key VARCHAR(64) NOT NULL UNIQUE, secret_hash VARCHAR(64) NOT NULL, label VARCHAR(128) DEFAULT '', last_seen DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
In server.php kommt eine dritte WebSocket-Rolle hinzu: device (neben host und guest).
GPS und WLAN arbeiten unabhängig. Sind beide eingerichtet, löst die Bedingung aus, die zuerst erfüllt ist. Die gemeinsame Pause zwischen den Auslösungen beträgt 20 Sekunden.
Modus 1: das WLAN-Bild (Fingerabdruck)
Modus 2: mit einem Netz verbunden
WifiManager.getScanResults() — zwischengespeicherte Ergebnisse ohne neuen Scan; wir rufen es bei jeder GPS-Aktualisierung kostenlos aufNetworkCallback, ganz ohne Scannen
wifi_trigger_<actionKey> = {
"mode": "fingerprint" | "connect",
"bssids": ["AA:BB:CC:DD:EE:FF", ...],
"min_match": 5,
"connect_bssid": "AA:BB:CC:DD:EE:FF",
"connect_ssid": "TP-Link_5G",
"enabled": true
}
Der Besitzer entscheidet, ob eine Bestätigung nötig ist. Wie die Identität bestätigt wird, entscheidet der Client — aus dem, was das Gerät bietet.
| Level | Was der Nutzer sieht | Example |
|---|---|---|
| Automatic | Nichts — das Objekt öffnet sich von selbst | Schranken, Tore |
| Confirm | Eine Benachrichtigung in der Leiste: „[Objekt] öffnen?“ → ein Tippen | Eine Garage, eine Pforte, Schutz vor Fehlauslösungen |
| Identität bestätigen | Biometrie, die PIN des Telefons oder die PIN der App | Eine Haustür |
„Bestätigen“ ist kein verschwendeter Schritt. Die Benachrichtigung erscheint im richtigen Moment von selbst in der Leiste. Kein Suchen der App, kein Entsperren, kein Finden des Objekts. Ein Tippen, und die Tür ist offen.
BiometricPromptKeyguardManagerDer Besitzer setzt „Erlaubte Stunden: 07:00–23:00“. Außerhalb des Fensters wird die Bedingung still ignoriert. Ein Nachtintervall funktioniert ebenso (22:00–06:00).
| Setting | Besitzer (eigenes) | Besitzer → Gäste | Gast |
|---|---|---|---|
| Objekttyp | Chooses | Gibt weiter | Receives |
| Coordinates | Legt sie fest | Optional (mit Warnung) | Legt sie fest oder erhält sie |
| GPS-Radius | Legt ihn fest | Gibt weiter | Receives |
| WLAN-Fingerabdruck | Scans | Never | Scannt selbst |
| Mindestübereinstimmungen beim WLAN | Legt ihn fest | Gibt weiter | Receives |
| Zeitfenster | Legt ihn fest | Gibt weiter | Receives |
| Bestätigung | Legt ihn fest | Gibt weiter | Receives |
| GPS ein/aus | yes | — | yes |
| WLAN ein/aus | yes | — | yes |
Der erste Schritt ist die Wahl des Verbindungstyps:
┌────────────────────────────┐ │ Connection type │ │ │ │ 📞 Call │ │ Opening with a phone │ │ call │ │ │ │ 🌐 URL │ │ Sending a command to │ │ a device address │ │ │ │ 📟 Device │ │ Connected through Entrixy │ │ (ESP32, Arduino) │ └────────────────────────────┘
Nach der Wahl öffnet sich das Einstellungsfenster dieses Typs.
┌──────────────────────────────────────────┐ │ Corner barrier 📶 3/5 312m │ │ Mine • ● │ ├──────────────────────────────────────────┤ │ [Edit] [Icon] │ │ ───────────────────────── │ │ [📍 Geo] [📶 Wi-Fi] │ └──────────────────────────────────────────┘
◉ Nach dem WLAN-Bild
○ Nach der Verbindung mit einem Netz
Darunter: Zeitfenster und Bestätigung, gemeinsam mit Geo
| Trigger | Indicator |
|---|---|
| GPS | "312 m" |
| WLAN-Fingerabdruck | 📶 Balken (0–4 nach Übereinstimmungen) |
| WLAN-Verbindung | 📶 grün oder grau |
| Webhook/Device | ● grün (online) oder grau (offline) |
Interne Tarife. Sie werden dem Nutzer nicht gezeigt, die Grenzen gelten dennoch.
| Standardtarif | Limit |
|---|---|
| Objekte (numbers) | 10 |
| Gäste (user_keys) | 10 |
Bei Überschreitung eine sanfte Meldung: „Die Zahl der Objekte ist begrenzt. Wenden Sie sich an den Support, um sie zu erhöhen.“
CREATE TABLE plans (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL,
max_numbers INT NOT NULL DEFAULT 10,
max_keys INT NOT NULL DEFAULT 10
);
INSERT INTO plans (name) VALUES ('default');
ALTER TABLE hosts ADD COLUMN plan_id INT UNSIGNED DEFAULT 1;
-- Plans
CREATE TABLE plans (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(64) NOT NULL,
max_numbers INT NOT NULL DEFAULT 10,
max_keys INT NOT NULL DEFAULT 10
);
INSERT INTO plans (name) VALUES ('default');
ALTER TABLE hosts ADD COLUMN plan_id INT UNSIGNED DEFAULT 1;
-- Object type and settings
ALTER TABLE numbers
ADD COLUMN type ENUM('call','webhook','device') DEFAULT 'call',
ADD COLUMN radius INT DEFAULT 80,
ADD COLUMN time_from TIME NULL,
ADD COLUMN time_to TIME NULL,
ADD COLUMN security_level ENUM('auto','confirm','identity') DEFAULT 'auto',
ADD COLUMN geo_available TINYINT(1) DEFAULT 1,
ADD COLUMN wifi_available TINYINT(1) DEFAULT 1,
ADD COLUMN wifi_min_match INT DEFAULT 5,
ADD COLUMN share_lat DOUBLE NULL,
ADD COLUMN share_lon DOUBLE NULL,
ADD COLUMN webhook_url VARCHAR(512) NULL,
ADD COLUMN webhook_secret VARCHAR(64) NULL,
ADD COLUMN webhook_mode ENUM('phone','server') DEFAULT 'phone',
ADD COLUMN device_id INT UNSIGNED NULL;
-- Devices (ESP32)
CREATE TABLE devices (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
host_id INT UNSIGNED NOT NULL,
device_key VARCHAR(64) NOT NULL UNIQUE,
secret_hash VARCHAR(64) NOT NULL,
label VARCHAR(128) DEFAULT '',
last_seen DATETIME NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
share_lat / share_lon — nur befüllt, wenn der Besitzer „Koordinaten mit Gästen teilen“ aktiviert hat.
webhook_url / webhook_secret — nur im Servermodus befüllt. Im Telefonmodus liegen sie in den Prefs.
Der Besitzer setzt einem Objekt ein eigenes Symbol, und der Gast soll es sehen. Doch das Symbol wird nicht auf dem Server gespeichert — der Server ist Postbote: Er stellt zu und vergisst.
Beim Setzen wird das Symbol sofort verkleinert auf 64×64 PNG (etwa 3–5 KB) und lokal in den Prefs oder als Datei gehalten.
1. The guest connects
→ guest_ok carries has_avatar: true for objects that have an icon
2. The guest client checks: has_avatar=true but no icon locally?
→ Yes → it asks the server
3. The server has no icon
→ it asks the owner over the WebSocket: {type: "avatar_request", number_id: 42}
4. The owner's phone
→ reads the local file
→ sends: {type: "avatar_data", number_id: 42, data: "base64..."}
5. The server passes it to the guest
→ {type: "avatar_data", number_id: 42, data: "base64..."}
6. The guest stores it locally and never asks again
7. The server stored nothing — the data merely passed through
| Situation | Behaviour |
|---|---|
| Der Besitzer ist offline | Der Gast sieht das Standardsymbol, bis der Besitzer online geht. |
| 50 Gäste fragen gleichzeitig | 50 × 5 KB = 250 KB über den WebSocket — vernachlässigbar. |
| Der Besitzer wechselt das Symbol | has_avatar_hash ändert sich und die Gäste fordern es erneut an |
| Der Besitzer löscht das Symbol | has_avatar: false, und die Gäste zeigen das Standardsymbol |
| Subscribers | Gleichzeitig online (~15 %) | WebSocket-Verbindungen | Server |
|---|---|---|---|
| 1 000 | ~150 | ~150 | Easy |
| 10 000 | ~1 500 | ~1 500 | Easy |
| 50 000 | ~7 500 | ~7 500 | ein Worker am Limit |
| 100 000 | ~15 000 | ~15 000 | 2–3 Worker |
Workerman auf einem Worker: 5.000–10.000 gleichzeitige Verbindungen zu je rund 20 KB.
ist MySQL, nicht die WebSockets. Doch 250.000 Inserts pro Tag — 50.000 Abonnenten × 5 Aufrufe — sind für MySQL auf einer SSD nichts.
Entrixy — endgültige Architektur v2, abgestimmt am 13. April 2026