Acordado el 13 de abril de 2026. Documento de trabajo para la implementación.
Todos los datos sensibles se guardan solo en el teléfono. Parte de ellos el propietario puede pasarlos por el servidor si quiere, con un aviso explícito.
| Datos | Por defecto | Opcionalmente por el servidor |
|---|---|---|
| Coordenadas (lat, lon) | Solo en el teléfono | Se puede compartir con invitados (con aviso) |
| Huella Wi-Fi (BSSID[]) | Solo en el teléfono | No se transmite nunca |
| URL y secreto del webhook | Solo en el teléfono | Se puede guardar en el servidor (con aviso) |
| Radio, tiempo, nivel de confirmación | Por el servidor | — |
Al añadir un objeto, el propietario elige el tipo de conexión. Después se abre la ventana de ajustes de ese tipo.
| Icon | Nombre | Cómo funciona | Feedback | Example |
|---|---|---|---|---|
| 📞 | Call | Una llamada desde el teléfono del propietario | Ninguno (la llamada salió o no) | Una barrera, un portero automático |
| 🌐 | URL (webhook) | Una petición HTTP a la dirección del dispositivo, desde el teléfono o desde el servidor de Entrixy | Sí: una respuesta JSON con estado | Una cerradura inteligente con API, Tasmota, Shelly Cloud |
| 📟 | Controlador de internet (WS) | Una orden por WebSocket a un controlador conectado | Sí: una respuesta por el WebSocket | ESP32 / ESP8266 caseros, Shelly Plus 1, NodeMCU |
| 📶 | Controlador BLE | Un canal Bluetooth directo entre teléfono y controlador, sin internet | Sí: una respuesta por BLE (notify) | Un controlador a pilas en un portón sin Wi-Fi |
El configurador de firmware para los dos últimos tipos: /controller/ (BLE e Internet, en el navegador).
Para el usuario todos los tipos se ven igual: una tarjeta, un botón de Llamar, un estado. Solo cambia el mecanismo de entrega.
La ventana de ajustes del webhook ofrece un conmutador, con los pros y los contras escritos en pantalla:
| Mode | Dónde están la URL y el secreto | ¿Conoce el servidor la URL? |
|---|---|---|
| Desde el teléfono | Prefs en el teléfono del propietario | No |
| Desde el servidor | La tabla numbers en la base de datos | Yes |
Seguridad: una firma HMAC. El dispositivo y Entrixy comparten un 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)"
}
El dispositivo comprueba que la firma es válida, que la marca de tiempo tiene menos de 30 segundos y que el nonce no se repite. Si todo va bien, actúa y responde:
{"status": "ok", "message": "Door opened"}
or
{"status": "error", "message": "Lock jammed"}
El estado se muestra al usuario en la aplicación.
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
En ambos modos el registro en el teléfono del propietario y del invitado anota quién, cuándo, qué objeto y con qué estado.
El controlador —se admiten ESP32, ESP32-S3, ESP32-C3 y ESP8266— no tiene IP pública. Mantiene una conexión WebSocket saliente al servidor de Entrixy, igual que el teléfono del propietario.
La firmware y el configurador viven en /esp/socket/. Fuentes: 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"}
El servidor transmite el estado a los clientes —propietario e invitado— por sus conexiones WebSocket.
device_key + device_secretwss://entrixy.com/wsEn la tarjeta del objeto se ve el estado de conexión del dispositivo, en línea o desconectado, igual que los invitados ven el estado del propietario.
Un controlador sobre ESP32, S3, C3, C6 o H2 no necesita ni Wi-Fi ni servidor para dispararse. Duerme profundamente, despierta una vez por segundo unos 200 ms y emite un anuncio BLE con un contador firmado. Cuando se acerca un teléfono con una clave válida, Android —o la aplicación, si está en primer plano— capta el anuncio, se conecta por GATT, envía una orden fire con un nonce de un solo uso y el controlador cierra el relé con un pulso breve. Para motores biestables y cerraduras hay un modo con abrir y cerrar por separado y seguimiento de la posición, donde abierto/cerrado viaja como un byte cifrado dentro del anuncio.
El servidor de Entrixy no participa en este canal. Solo hace falta al crear una clave de invitado: el propietario firma un GuestToken y se lo pasa al invitado por el canal cifrado de extremo a extremo. Después el invitado trabaja sin conexión.
La especificación completa del protocolo, la disposición de bits y los vectores de prueba están en entrixy.com/ble. El modelo de amenazas está en un documento aparte.
Puedes poner BLE y WebSocket en el mismo motor: en la aplicación aparecen como dos objetos distintos. BLE para abrir de cerca sin internet; WebSocket para cuando estás lejos.
entrixy-pair)La firmware y el configurador viven en /esp/ble/. Fuente: /esp32-example/.
En la tarjeta del objeto: gris significa que no se ve en el aire, por lejanía o por estar apagado; naranja, que se ve pero la automatización no ha disparado; verde, que disparó o está disparando; gris con borde verde, que disparó hace poco y se puede tocar para volver a abrir.
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 se añade un tercer rol de WebSocket: device (junto a host y guest).
El GPS y el Wi-Fi funcionan por separado. Con ambos configurados, dispara la condición que se cumpla primero. La pausa común entre disparos es de 20 segundos.
Modo 1: la imagen Wi-Fi (huella)
Modo 2: conectado a una red
WifiManager.getScanResults() — resultados en caché sin nuevo escaneo; lo llamamos en cada actualización de GPS sin costeNetworkCallback, sin escanear en absoluto
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
}
El propietario decide si hace falta confirmación. Cómo se confirma la identidad lo decide el cliente, con lo que ofrezca el dispositivo.
| Level | Qué ve el usuario | Example |
|---|---|---|
| Automatic | Nada: el objeto se abre solo | Barreras, portones |
| Confirm | Una notificación en la cortinilla: ¿Abrir [objeto]? → un toque | Un garaje, un portillo, protección contra disparos falsos |
| Confirmar la identidad | Biometría, el PIN del teléfono o el PIN de la aplicación | Una puerta de entrada |
Confirmar no es un paso desperdiciado. La notificación aparece sola en la cortinilla en el momento justo. Sin buscar la aplicación, sin desbloquear, sin encontrar el objeto. Un toque y la puerta está abierta.
BiometricPromptKeyguardManagerEl propietario fija Horas permitidas: 07:00–23:00. Fuera de esa ventana la condición se ignora en silencio. Un intervalo nocturno también funciona (22:00–06:00).
| Setting | Propietario (lo suyo) | Propietario → invitados | Invitado |
|---|---|---|---|
| Tipo de objeto | Chooses | Lo transmite | Receives |
| Coordinates | Los fija | Opcional (con aviso) | Los fija o los recibe |
| Radio del GPS | Lo fija | Lo transmite | Receives |
| Huella Wi-Fi | Scans | Never | Escanea él mismo |
| Coincidencias mínimas de Wi-Fi | Lo fija | Lo transmite | Receives |
| Ventana horaria | Lo fija | Lo transmite | Receives |
| Confirmación | Lo fija | Lo transmite | Receives |
| GPS activado/desactivado | yes | — | yes |
| Wi-Fi activado/desactivado | yes | — | yes |
El primer paso es elegir el tipo de conexión:
┌────────────────────────────┐ │ Connection type │ │ │ │ 📞 Call │ │ Opening with a phone │ │ call │ │ │ │ 🌐 URL │ │ Sending a command to │ │ a device address │ │ │ │ 📟 Device │ │ Connected through Entrixy │ │ (ESP32, Arduino) │ └────────────────────────────┘
Tras la elección se abre la ventana de ajustes de ese tipo.
┌──────────────────────────────────────────┐ │ Corner barrier 📶 3/5 312m │ │ Mine • ● │ ├──────────────────────────────────────────┤ │ [Edit] [Icon] │ │ ───────────────────────── │ │ [📍 Geo] [📶 Wi-Fi] │ └──────────────────────────────────────────┘
◉ Por la imagen Wi-Fi
○ Por la conexión a una red
Debajo: la ventana horaria y la confirmación, compartidas con Geo
| Trigger | Indicator |
|---|---|
| GPS | "312 m" |
| Huella Wi-Fi | 📶 barras (0–4 según coincidencias) |
| Conexión Wi-Fi | 📶 verde o gris |
| Webhook/Device | ● verde (en línea) o gris (desconectado) |
Planes internos. No se muestran al usuario, pero los límites se aplican.
| Plan por defecto | Limit |
|---|---|
| Objetos (numbers) | 10 |
| Invitados (user_keys) | 10 |
Al superarlo, un mensaje suave: El número de objetos es limitado. Contacta con soporte para ampliarlo.
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 — se rellenan solo si el propietario ha activado Compartir coordenadas con los invitados.
webhook_url / webhook_secret — se rellenan solo en modo servidor. En modo teléfono viven en Prefs.
El propietario pone un icono propio a un objeto y el invitado debe verlo. Pero el icono no se guarda en el servidor — el servidor hace de cartero: entrega y olvida.
Al ponerlo, el icono se reduce enseguida a 64×64 PNG (unos 3–5 KB) y se guarda localmente en Prefs o en un archivo.
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 |
|---|---|
| El propietario está desconectado | El invitado ve el icono por defecto hasta que el propietario se conecte. |
| 50 invitados preguntan a la vez | 50 × 5 KB = 250 KB por el WebSocket: insignificante. |
| El propietario cambia el icono | has_avatar_hash cambia y los invitados vuelven a pedirlo |
| El propietario borra el icono | has_avatar: false y los invitados muestran el icono por defecto |
| Subscribers | En línea a la vez (~15 %) | Conexiones WebSocket | Server |
|---|---|---|---|
| 1 000 | ~150 | ~150 | Easy |
| 10 000 | ~1 500 | ~1 500 | Easy |
| 50 000 | ~7 500 | ~7 500 | un trabajador al límite |
| 100 000 | ~15 000 | ~15 000 | 2–3 trabajadores |
Workerman en un solo trabajador: 5000–10 000 conexiones simultáneas, de unos 20 KB cada una.
es MySQL, no los WebSockets. Pero 250 000 inserciones al día —50 mil suscriptores × 5 llamadas— no son nada para MySQL sobre SSD.
Entrixy — arquitectura final v2, acordada el 13 de abril de 2026