Entrixy v2 — final architecture

Agreed 13 April 2026. A working document for implementation.

Contents
  1. Privacy: the server knows nothing
  2. Four object types: Call, URL, Device (WS), BLE controller
  3. Webhook (URL) in detail
  4. Device (ESP32 / ESP8266, WebSocket) in detail
  5. BLE controller (ESP32) in detail
  6. Triggers: GPS and Wi-Fi
  7. Three levels of confirmation
  8. Owner versus guest — splitting the settings
  9. The app interface
  10. Plan limits
  11. Object icons for guests
  12. Load forecast
  13. Database structure
  14. Implementation plan

1. Privacy: the server knows nothing (by default)

The promise we make: "We know neither our users' phone numbers nor their coordinates"

All sensitive data is kept on the phone only. The owner may optionally pass some of it through the server — with an explicit warning.

DataBy defaultOptionally through the server
Coordinates (lat, lon)On the phone onlyCan be shared with guests (with a warning)
WiFi fingerprint (BSSID[])On the phone onlyNever transmitted
Webhook URL and secretOn the phone onlyCan be stored on the server (with a warning)
Radius, time, confirmation levelThrough the server
A Wi-Fi fingerprint is as good as coordinates. From a set of BSSIDs, services locate you to within 20–50 m. The Wi-Fi picture never passes through the server under any circumstances.

2. Four object types

When adding an object the owner picks a connection type. A settings window for that type opens next.

IconNameHow it worksFeedbackExample
📞CallA phone call from the owner's phoneNone (the call either went out or did not)A barrier, an intercom
🌐URL (webhook)An HTTP request to the device's address, from the phone or from the Entrixy serverYes — a JSON response with a statusA smart lock with an API, Tasmota, Shelly Cloud
📟Internet controller (WS)A command over WebSocket to a connected controllerYes — a reply over the WebSocketDIY ESP32 / ESP8266, Shelly Plus 1, NodeMCU
📶BLE controllerA direct Bluetooth channel between phone and controller, with no internetYes — a reply over BLE (notify)A battery-powered controller at a gate with no Wi-Fi

The firmware configurator for the last two types: /controller/ (BLE and Internet, in the browser).

To the user every type looks the same: a card, a Call button, a status. Only the delivery mechanism differs.

3. Webhook (URL) in detail

Two sending modes

The webhook settings window offers a switch, with the pros and cons spelled out on screen:

◉ From the owner's phone (default)
The request is sent from your phone. The device's URL and secret stay with you — the Entrixy server never learns them.
Plus: maximum privacy
Minus: your phone has to be online
○ From the Entrixy server
Our server sends the request. It works even when your phone is offline.
Plus: it works without the owner's phone
Minus: the device's address is stored on the server

Where the data lives

ModeWhere the URL and secret areDoes the server know the URL?
From the phonePrefs on the owner's phoneNo
From the serverThe table numbers in the databaseYes

Request protocol

Security: an HMAC signature. The device and Entrixy share a 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)"
}

The device checks that the signature is valid, the timestamp is under 30 seconds old and the nonce is not a repeat. If all is well it acts and replies:

{"status": "ok", "message": "Door opened"}
or
{"status": "error", "message": "Lock jammed"}

The status is shown to the user in the app.

Data flow (phone mode)

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

Logging

In both modes the log on the owner's and the guest's phone records who, when, which object and what status came back.

4. Device (ESP32 / ESP8266, WebSocket) in detail

How it works

The controller — ESP32, ESP32-S3, ESP32-C3 or ESP8266, all supported — has no public IP. It keeps an outbound WebSocket connection to the Entrixy server, exactly as the owner's phone does.

Firmware and configurator live at /esp/socket/. Sources: ESP32, ESP8266.

The device's WebSocket protocol

// 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"}

The server relays the status to the clients — owner and guest — over their WebSocket connections.

Registering a device

  1. In the app the owner chooses Add object → type Device
  2. This generates device_key + device_secret
  3. A QR code with the firmware details is shown
  4. The user flashes the ESP32 or ESP8266 (see the configurator) and the device connects to wss://entrixy.com/ws

Indication

The object card shows the device's connection status, online or offline, just as guests see the owner's status.

4a. BLE controller (ESP32) in detail

How it works

A controller on the ESP32, S3, C3, C6 or H2 needs neither Wi-Fi nor a server to fire. It sits in deep sleep, wakes once a second for about 200 ms and broadcasts a BLE advertisement with a signed counter. When a phone holding a valid key comes near, Android — or the app, if it is in the foreground — catches the advertisement, connects over GATT, sends a fire command with a one-time nonce, and the controller closes the relay for a brief pulse. For bistable drives and locks there is a mode with separate open and close and position tracking, where open/closed travels as an encrypted byte in the advertisement.

The Entrixy server plays no part in this channel. It is needed only when a guest key is created: the owner signs a GuestToken and passes it to the guest over the end-to-end encrypted channel. After that the guest works offline.

The BLE protocol in brief

The full protocol specification, bit layout and test vectors are at entrixy.com/ble. The threat model is in a separate document.

When BLE beats WebSocket

When WebSocket beats BLE

You can fit both BLE and WebSocket to the same drive — they appear as two separate objects in the app. BLE for opening up close without internet, WebSocket for the times you are far away.

Registering a device

  1. In the app the owner chooses Add object → type BLE controller
  2. The app scans the air for devices in pairing mode (named entrixy-pair)
  3. Selecting a device runs an ECDH exchange over GATT; the shared secret is saved in the phone's Prefs and in the controller's NVS
  4. No QR code and no button presses — a new device enters pairing mode by itself once the program is loaded

Firmware and configurator live at /esp/ble/. Source: /esp32-example/.

Indication

On the object card: grey means not visible on the air, either too far or switched off; orange means visible but the automation has not fired; green means it fired or is firing; grey with a green outline means it fired recently and can be tapped to open again.

Database

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 a third WebSocket role is added: device (alongside host and guest).

5. Triggers: GPS and Wi-Fi

GPS and Wi-Fi work independently. With both configured, whichever condition is met first does the firing. The shared cooldown between firings is 20 seconds.

GPS — as it is today

Wi-Fi — two modes

Mode 1: the Wi-Fi picture (fingerprint)

Mode 2: connected to a network

Power consumption

Storage (Prefs, never leaves the phone)

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
}

6. Three levels of confirmation

The owner decides whether confirmation is required. How identity is confirmed is up to the client, from whatever the device offers.

LevelWhat the user seesExample
AutomaticNothing — the object opens by itselfBarriers, gates
ConfirmA notification in the shade: "Open [object]?" → one tapA garage, a wicket gate, protection against false triggers
Confirm identityBiometrics, the phone PIN or the app PINA front door

"Confirm" is not a wasted step. The notification appears in the shade by itself at the right moment. No hunting for the app, no unlocking, no finding the object. One tap and the door is open.

Priority of identity confirmation methods

  1. Biometrics, if a sensor exists → BiometricPrompt
  2. The phone's PIN or password, when there is no biometrics → KeyguardManager
  3. The app's own PIN, on a device with no lock → our own dialog

The time window — an extra factor

The owner sets "Allowed hours: 07:00–23:00". Outside that window the condition is silently ignored. An overnight interval works too (22:00–06:00).

Three independent factors of protection

  1. WHERE — GPS, a Wi-Fi fingerprint or a connection
  2. WHO — automatic, confirmation or biometrics
  3. WHEN — the time window
A front door: a Wi-Fi fingerprint plus identity confirmation plus 07:00–23:00 — sturdier than most "smart locks".

7. Owner versus guest — splitting the settings

SettingOwner (their own)Owner → guestsGuest
Object typeChoosesPasses onReceives
CoordinatesSets themOptional (with a warning)Sets them or receives them
GPS radiusSets itPasses onReceives
WiFi fingerprintScansNeverScans for themselves
Wi-Fi minimum matchesSets itPasses onReceives
Time windowSets itPasses onReceives
ConfirmationSets itPasses onReceives
GPS on/offyesyes
Wi-Fi on/offyesyes

8. The app interface

Adding an object (SettingsScreen)

The first step is choosing a connection type:

┌────────────────────────────┐
│  Connection type           │
│                            │
│  📞  Call                  │
│  Opening with a phone      │
│  call                      │
│                            │
│  🌐  URL                   │
│  Sending a command to      │
│  a device address          │
│                            │
│  📟  Device                │
│  Connected through Entrixy │
│  (ESP32, Arduino)          │
└────────────────────────────┘

After the choice, a settings window for that type opens.

The expanded object card (ActionCard)

┌──────────────────────────────────────────┐
│  Corner barrier             📶 3/5  312m │
│  Mine • ●                                │
├──────────────────────────────────────────┤
│  [Edit]  [Icon]                          │
│  ─────────────────────────               │
│  [📍 Geo]   [📶 Wi-Fi]                   │
└──────────────────────────────────────────┘

The 📍 Geo window

The 📶 Wi-Fi window

◉ By the Wi-Fi picture

○ By connection to a network

Below: the time window and confirmation, shared with Geo

The webhook window (🌐 URL)

◉ From the phone (default)
The URL is kept on your phone alone. The Entrixy server never learns it.
Plus: maximum privacy
Minus: your phone has to be online
○ From the Entrixy server
The server sends the request. It works without your phone.
Plus: it always works
Minus: the device's address is stored on the server

Indication on the collapsed card

TriggerIndicator
GPS"312 m"
WiFi fingerprint📶 bars (0–4 by matches)
Wi-Fi connection📶 green or grey
Webhook/Device● green (online) or grey (offline)

9. Plan limits

Internal plans. They are not shown to the user, but the limits apply.

Default planLimit
Objects (numbers)10
Guests (user_keys)10

On exceeding it, a gentle message: "The number of objects is limited. Contact support to raise it."

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;

10. Database — the full set of changes

-- 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 — filled in only when the owner has enabled "Share coordinates with guests".

webhook_url / webhook_secret — filled in only in server mode. In phone mode they live in Prefs.

11. Object icons for guests

The owner sets a custom icon on an object and the guest should see it. But the icon is not stored on the server — the server acts as a postman: it delivers and forgets.

Preparation on the owner's side

When the icon is set it is immediately shrunk to 64×64 PNG (about 3–5 KB) and kept locally in Prefs or a file.

How it travels

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

Edge cases

SituationBehaviour
The owner is offlineThe guest sees the default icon until the owner comes online.
50 guests ask at once50 × 5 KB = 250 KB over the WebSocket — negligible.
The owner changes the iconhas_avatar_hash changes and guests request it again
The owner deletes the iconhas_avatar: false and guests show the default icon
Privacy is preserved. A barrier's icon is not sensitive data, yet even it does not settle on the server: it passes through the WebSocket in transit and is never written to disk.

12. Load forecast

One active owner generates

One active guest

Scaling

SubscribersOnline at once (~15%)WebSocket connectionsServer
1 000~150~150Easy
10 000~1 500~1 500Easy
50 000~7 500~7 500one worker at its limit
100 000~15 000~15 0002–3 workers

Workerman on a single worker: 5,000–10,000 concurrent connections, at roughly 20 KB each.

Webhook and Device

The bottleneck

is MySQL, not the WebSockets. But 250,000 inserts a day — 50k subscribers × 5 calls — is nothing for MySQL on an SSD.

Conclusion: до 50 000 абонентов текущий сервер справится без изменений. После — увеличиваем worker->count and add indexes.

13. Implementation plan

  1. Database — plans, the new columns in numbers and hosts, the devices table
  2. Server-side limits — checks in number_add and key_create
  3. Object type — choosing a type when adding, with different dialogs
  4. API — host_sync and guest_ok with the new profile fields
  5. Webhook — the HMAC protocol, two sending modes, the response status
  6. Device WS — device_hello, device_command and device_response in server.php
  7. The Wi-Fi trigger — fingerprint and connection
  8. Time window — time_from/time_to
  9. Confirmation — three levels: automatic, confirm, confirm identity
  10. Icons — transit delivery over the WebSocket, compressed to 64×64
  11. UI — the Geo and Wi-Fi buttons, the dialogs, the indicators, the type picker
  12. Sharing coordinates — an option with a warning
  13. Build and release
PWA: The Wi-Fi trigger is unavailable — browsers have no Wi-Fi API. Webhook and Device work through the PWA: button → server → device. The time window and confirmation exist only in the native app.
Backward compatibility: there are hardly any old clients. We do what is convenient and force an update through version_check when necessary.

Entrixy — final architecture v2, agreed 13 April 2026