Согласовано 13 апреля 2026. Рабочий документ к реализации.
Все чувствительные данные хранятся только на телефоне. Хозяин может опционально передать некоторые данные через сервер — с явным предупреждением.
| Данные | По умолчанию | Опционально через сервер |
|---|---|---|
| Координаты (lat, lon) | Только на телефоне | Можно передать гостям (с предупреждением) |
| WiFi fingerprint (BSSID[]) | Только на телефоне | Никогда не передаётся |
| Webhook URL + секрет | Только на телефоне | Можно хранить на сервере (с предупреждением) |
| Радиус, время, уровень подтверждения | Через сервер | — |
При добавлении объекта хозяин выбирает тип подключения. Далее открывается модалка с настройками под этот тип.
| Иконка | Название | Как работает | Обратная связь | Пример |
|---|---|---|---|---|
| 📞 | Звонок | Телефонный звонок с телефона хозяина | Нет (звонок ушёл или нет) | Шлагбаум, домофон |
| 🌐 | URL (webhook) | HTTP-запрос на адрес устройства — с телефона или с сервера Entrixy | Да — JSON-ответ со статусом | Умный замок с API, Tasmota, Shelly Cloud |
| 📟 | Устройство (WS) | Команда через WebSocket на подключённый контроллер | Да — ответ через WS | DIY ESP32 / ESP8266, Sonoff Mini, Shelly Plus 1 |
| 📶 | BLE-контроллер | Прямой Bluetooth-канал между телефоном и контроллером, без интернета | Да — ответ по BLE (notify) | Контроллер на батарейках у ворот без WiFi |
Конфигуратор прошивки для двух последних типов: /esp/ (BLE и WebSocket в браузере).
Для пользователя все типы выглядят одинаково: карточка, кнопка «Вызов», статус. Разница только в механизме доставки команды.
В модалке настройки webhook — radio buttons с пояснением плюсов и минусов прямо на экране:
| Режим | Где URL и секрет | Сервер знает URL? |
|---|---|---|
| С телефона | Prefs на телефоне хозяина | ❌ Нет |
| С сервера | Таблица numbers в БД | ✅ Да |
Безопасность: HMAC-подпись. Устройство и Entrixy знают общий 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)"
}
Устройство проверяет: signature верна, timestamp не старше 30 секунд, nonce не повторяется. Если ок — выполняет и отвечает:
{"status": "ok", "message": "Door opened"}
или
{"status": "error", "message": "Lock jammed"}
Статус отображается пользователю в приложении.
Гость нажал «Вызов»
→ сервер получает запрос
→ шлёт хосту через WS: {type: "do_webhook", ...}
→ телефон хозяина делает HTTP на устройство
→ получает JSON-ответ
→ шлёт на сервер: {type: "webhook_result", status, message}
→ сервер пробрасывает гостю
→ оба видят статус в журнале
В обоих режимах фиксируется в журнале на телефоне хозяина и гостя: кто, когда, какой объект, какой статус ответа.
Контроллер (ESP32, ESP32-S3, ESP32-C3, ESP8266 — поддерживаются все) не имеет белого IP. Держит исходящее WebSocket-соединение к серверу Entrixy — так же как телефон хоста.
Прошивка и конфигуратор — на /esp/socket/. Исходники: ESP32, ESP8266.
// Устройство → сервер: подключение
{"type": "device_hello", "device_key": "xyz789", "device_secret": "..."}
// Сервер → устройство: ОК
{"type": "device_ok"}
// Сервер → устройство: команда
{"type": "device_command", "action": "open", "command_id": "abc123"}
// Устройство → сервер: результат
{"type": "device_response", "command_id": "abc123", "status": "ok", "message": "Opened"}
Сервер пробрасывает статус клиенту (хосту и гостю) через их WS-соединения.
device_key + device_secretwss://entrixy.com/wsВ карточке объекта: статус подключения устройства (онлайн / оффлайн), аналогично статусу хоста для гостей.
Контроллер на ESP32 / S3 / C3 / C6 / H2 не использует WiFi и сервер для срабатывания. Спит в deep-sleep, раз в секунду просыпается на ~200 мс и шлёт BLE-advertise с подписанным счётчиком. Когда телефон с действующим ключом оказывается рядом — система Android (или приложение в активном состоянии) ловит advertise, подключается к контроллеру по GATT, шлёт fire-команду с одноразовым nonce, контроллер замыкает реле на короткий импульс.
Сервер Entrixy в этом канале не участвует. Он только нужен в момент создания гостевого ключа (хозяин подписывает GuestToken, передаёт гостю через E2EE-канал). После этого гость работает оффлайн.
Полная битовая спецификация и test vectors — на /ble.
Можно ставить и BLE и WS на один и тот же привод — это будут два разных объекта в приложении. BLE для близкого срабатывания без интернета, WS для удалённых ситуаций.
entrixy-pair)Прошивка и конфигуратор — на /esp/ble/. Исходник: /esp32-example/.
В карточке объекта: серый = не виден в эфире (далеко или отключён), оранжевый = виден но не сработала автоматика, зелёный = сработал/срабатывает, серый с зелёной обводкой = недавно сработал, можно тапнуть для повторного открытия.
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 );
В server.php добавляется третья роль WS-соединения: device (рядом с host и guest).
GPS и WiFi работают независимо. Если настроены оба — триггерит первый сработавший. Кулдаун 20 секунд общий.
Режим 1: WiFi-картина (fingerprint)
Режим 2: Подключение к сети
WifiManager.getScanResults() — кешированные результаты без нового скана, вызываем на каждом GPS-тике бесплатноNetworkCallback, без сканирования вообще
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
}
Хозяин решает НУЖНО ЛИ подтверждение. Способ подтверждения личности клиент выбирает сам из доступных на устройстве.
| Уровень | Что видит пользователь | Кейс |
|---|---|---|
| Авто | Ничего — объект открывается автоматически | Шлагбаумы, ворота |
| Подтвердить | Пуш в шторку «Открыть [объект]?» → тап | Гараж, калитка, защита от ложных срабатываний |
| Подтвердить личность | Биометрия / PIN телефона / PIN приложения | Входная дверь |
«Подтвердить» — не лишняя кнопка. Уведомление появляется в шторке автоматически в нужный момент. Не нужно искать приложение, разблокировать, находить объект. Одно нажатие — и дверь открыта.
BiometricPromptKeyguardManagerХозяин задаёт: «Разрешённое время: 07:00 — 23:00». Вне окна триггер молча игнорируется. Ночной интервал тоже работает (22:00 — 06:00).
| Параметр | Хозяин (свои) | Хозяин → гостям | Гость |
|---|---|---|---|
| Тип объекта | Выбирает | Передаёт | Получает |
| Координаты | Ставит сам | Опционально (предупреждение) | Ставит сам или получает |
| Радиус GPS | Задаёт | Передаёт | Получает |
| WiFi fingerprint | Сканирует | Никогда | Сканирует сам |
| WiFi мин. совпадений | Задаёт | Передаёт | Получает |
| Временное окно | Задаёт | Передаёт | Получает |
| Подтверждение | Задаёт | Передаёт | Получает |
| Вкл/выкл GPS | ✅ | — | ✅ |
| Вкл/выкл WiFi | ✅ | — | ✅ |
Первый шаг — выбор типа подключения:
┌────────────────────────────┐ │ Тип подключения │ │ │ │ 📞 Звонок │ │ Открытие телефонным │ │ звонком │ │ │ │ 🌐 URL │ │ Отправка команды на │ │ адрес устройства │ │ │ │ 📟 Устройство │ │ Подключение через Entrixy │ │ (ESP32, Arduino) │ └────────────────────────────┘
После выбора — модалка с настройками под выбранный тип.
┌──────────────────────────────────────────┐ │ Шлагбаум УГОЛ 📶 3/5 312м │ │ Свой • ● │ ├──────────────────────────────────────────┤ │ [Редактировать] [Иконка] │ │ ───────────────────────── │ │ [📍 Гео] [📶 WiFi] │ └──────────────────────────────────────────┘
◉ По WiFi-картине
○ По подключению к сети
Внизу: временное окно + подтверждение (общие с Гео)
| Триггер | Индикатор |
|---|---|
| GPS | «312 м» |
| WiFi fingerprint | 📶 деления (0-4 по совпадениям) |
| WiFi подключение | 📶 зелёная / серая |
| Webhook/Device | ● зелёный (онлайн) / серый (оффлайн) |
Внутренние тарифные планы. Не видимы пользователю, но ограничения есть.
| Дефолтный план | Лимит |
|---|---|
| Объекты (numbers) | 10 |
| Гости (user_keys) | 10 |
При превышении — мягкое сообщение: «Количество объектов ограничено. Для увеличения обратитесь в поддержку.»
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;
-- Тарифные планы
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;
-- Тип объекта и настройки
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;
-- Устройства (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 — заполняются только если хозяин включил «Передать координаты гостям».
webhook_url / webhook_secret — заполняются только в режиме «с сервера». В режиме «с телефона» хранятся в Prefs.
Хозяин ставит кастомную иконку объекту. Гость должен её видеть. Но иконка не хранится на сервере — сервер работает как почтальон: передал и забыл.
При установке иконки она сразу сжимается до 64×64 PNG (~3-5 КБ). Хранится локально в Prefs/файле.
1. Гость подключается
→ guest_ok содержит has_avatar: true для объектов с иконкой
2. Клиент гостя проверяет: has_avatar=true, а локально иконки нет?
→ Да → запрашивает у сервера
3. Сервер не имеет иконки
→ шлёт хозяину через WS: {type: "avatar_request", number_id: 42}
4. Телефон хозяина
→ читает локальный файл
→ отправляет: {type: "avatar_data", number_id: 42, data: "base64..."}
5. Сервер пробрасывает гостю
→ {type: "avatar_data", number_id: 42, data: "base64..."}
6. Гость сохраняет локально, больше не запрашивает
7. Сервер ничего не сохранил — данные прошли транзитом
| Ситуация | Поведение |
|---|---|
| Хозяин оффлайн | Гость не видит иконку до появления хозяина онлайн. Дефолтная иконка. |
| 50 гостей запрашивают одновременно | 50 × 5 КБ = 250 КБ через WS. Ничего. |
| Хозяин сменил иконку | has_avatar_hash меняется → гости перезапрашивают |
| Хозяин удалил иконку | has_avatar: false → гости показывают дефолтную |
| Абонентов | Одновременно онлайн (~15%) | WS-соединений | Сервер |
|---|---|---|---|
| 1 000 | ~150 | ~150 | Легко |
| 10 000 | ~1 500 | ~1 500 | Легко |
| 50 000 | ~7 500 | ~7 500 | 1 воркер на пределе |
| 100 000 | ~15 000 | ~15 000 | 2-3 воркера |
Workerman на 1 воркере: 5 000-10 000 одновременных соединений (~20 КБ на соединение).
Не WS, а MySQL. Но 250 000 INSERT/день (50К абонентов × 5 звонков) — MySQL на SSD держит без проблем.
Entrixy — финальная архитектура v2, согласовано 13 апреля 2026