Acordado a 13 de abril de 2026. Documento de trabalho para a implementação.
Todos os dados sensíveis ficam apenas no telefone. Parte deles o proprietário pode, se quiser, passar pelo servidor — com um aviso explícito.
| Dados | Por omissão | Opcionalmente pelo servidor |
|---|---|---|
| Coordenadas (lat, lon) | Apenas no telefone | Pode ser partilhado com convidados (com aviso) |
| Impressão Wi-Fi (BSSID[]) | Apenas no telefone | Nunca é transmitida |
| URL e segredo do webhook | Apenas no telefone | Pode ser guardado no servidor (com aviso) |
| Raio, tempo, nível de confirmação | Pelo servidor | — |
Ao adicionar um objeto, o proprietário escolhe o tipo de ligação. A seguir abre-se a janela de definições desse tipo.
| Icon | Nome | Como funciona | Feedback | Example |
|---|---|---|---|---|
| 📞 | Call | Uma chamada a partir do telefone do proprietário | Nenhum (a chamada saiu ou não) | Uma cancela, um intercomunicador |
| 🌐 | URL (webhook) | Um pedido HTTP para o endereço do dispositivo, a partir do telefone ou do servidor Entrixy | Sim — uma resposta JSON com estado | Uma fechadura inteligente com API, Tasmota, Shelly Cloud |
| 📟 | Controlador de internet (WS) | Um comando por WebSocket a um controlador ligado | Sim — uma resposta pelo WebSocket | ESP32 / ESP8266 caseiros, Shelly Plus 1, NodeMCU |
| 📶 | Controlador BLE | Um canal Bluetooth direto entre telefone e controlador, sem internet | Sim — uma resposta por BLE (notify) | Um controlador a pilhas num portão sem Wi-Fi |
O configurador de firmware para os dois últimos tipos: /controller/ (BLE e Internet, no navegador).
Para o utilizador todos os tipos são iguais: um cartão, um botão Ligar, um estado. Só o mecanismo de entrega difere.
A janela de definições do webhook tem um seletor, com as vantagens e desvantagens escritas no ecrã:
| Mode | Onde estão o URL e o segredo | O servidor conhece o URL? |
|---|---|---|
| A partir do telefone | Prefs no telefone do proprietário | No |
| A partir do servidor | A tabela numbers na base de dados | Yes |
Segurança: uma assinatura HMAC. O dispositivo e o Entrixy partilham um 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)"
}
O dispositivo verifica que a assinatura é válida, que a marca temporal tem menos de 30 segundos e que o nonce não se repete. Se estiver tudo bem, atua e responde:
{"status": "ok", "message": "Door opened"}
or
{"status": "error", "message": "Lock jammed"}
O estado é mostrado ao utilizador na aplicação.
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
Em ambos os modos o registo no telefone do proprietário e do convidado anota quem, quando, que objeto e com que estado.
O controlador — são suportados ESP32, ESP32-S3, ESP32-C3 e ESP8266 — não tem IP público. Mantém uma ligação WebSocket de saída ao servidor Entrixy, tal como o telefone do proprietário.
O firmware e o configurador vivem em /esp/socket/. Fontes: 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"}
O servidor encaminha o estado aos clientes — proprietário e convidado — pelas suas ligações WebSocket.
device_key + device_secretwss://entrixy.com/wsO cartão do objeto mostra o estado de ligação do dispositivo, em linha ou offline, tal como os convidados veem o estado do proprietário.
Um controlador em ESP32, S3, C3, C6 ou H2 não precisa nem de Wi-Fi nem de servidor para disparar. Fica em sono profundo, acorda uma vez por segundo durante cerca de 200 ms e emite um anúncio BLE com um contador assinado. Quando se aproxima um telefone com uma chave válida, o Android — ou a aplicação, se estiver em primeiro plano — apanha o anúncio, liga-se por GATT, envia um comando fire com um nonce de uso único, e o controlador fecha o relé num impulso curto. Para motores biestáveis e fechaduras há um modo com abrir e fechar em separado e seguimento da posição, onde aberto/fechado viaja como um byte cifrado dentro do anúncio.
O servidor Entrixy não participa neste canal. Só é preciso ao criar uma chave de convidado: o proprietário assina um GuestToken e passa-o ao convidado pelo canal cifrado ponta a ponta. Depois o convidado trabalha offline.
A especificação completa do protocolo, a disposição dos bits e os vetores de teste estão em entrixy.com/ble. O modelo de ameaças está num documento à parte.
Pode pôr BLE e WebSocket no mesmo motor — na aplicação aparecem como dois objetos distintos. BLE para abrir de perto sem internet, WebSocket para quando está longe.
entrixy-pair)O firmware e o configurador vivem em /esp/ble/. Fonte: /esp32-example/.
No cartão do objeto: cinzento significa não visível no ar, longe demais ou desligado; laranja, visível mas a automação não disparou; verde, disparou ou está a disparar; cinzento com contorno verde, disparou há pouco e pode tocar-se para abrir de novo.
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 acrescenta-se um terceiro papel de WebSocket: device (ao lado de host e guest).
O GPS e o Wi-Fi funcionam de forma independente. Se ambos estiverem configurados, dispara a condição que se cumprir primeiro. A pausa comum entre disparos é de 20 segundos.
Modo 1: a imagem Wi-Fi (impressão)
Modo 2: ligado a uma rede
WifiManager.getScanResults() — resultados em cache sem nova varredura; chamamo-lo a cada atualização de GPS sem custoNetworkCallback, sem qualquer varredura
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
}
O proprietário decide se é preciso confirmação. Como se confirma a identidade fica a cargo do cliente, com o que o dispositivo oferecer.
| Level | O que o utilizador vê | Example |
|---|---|---|
| Automatic | Nada — o objeto abre-se sozinho | Cancelas, portões |
| Confirm | Uma notificação na cortina: Abrir [objeto]? → um toque | Uma garagem, um portinhola, proteção contra disparos falsos |
| Confirmar a identidade | Biometria, o PIN do telefone ou o PIN da aplicação | Uma porta de entrada |
Confirmar não é um passo desperdiçado. A notificação aparece sozinha na cortina no momento certo. Sem procurar a aplicação, sem desbloquear, sem encontrar o objeto. Um toque e a porta está aberta.
BiometricPromptKeyguardManagerO proprietário define Horas permitidas: 07:00–23:00. Fora dessa janela a condição é ignorada em silêncio. Um intervalo noturno também funciona (22:00–06:00).
| Setting | Proprietário (o seu) | Proprietário → convidados | Convidado |
|---|---|---|---|
| Tipo de objeto | Chooses | Transmite | Receives |
| Coordinates | Define-os | Opcional (com aviso) | Define-os ou recebe-os |
| Raio do GPS | Define-o | Transmite | Receives |
| Impressão Wi-Fi | Scans | Never | Varre ele próprio |
| Correspondências mínimas de Wi-Fi | Define-o | Transmite | Receives |
| Janela horária | Define-o | Transmite | Receives |
| Confirmação | Define-o | Transmite | Receives |
| GPS ligado/desligado | yes | — | yes |
| Wi-Fi ligado/desligado | yes | — | yes |
O primeiro passo é escolher o tipo de ligação:
┌────────────────────────────┐ │ Connection type │ │ │ │ 📞 Call │ │ Opening with a phone │ │ call │ │ │ │ 🌐 URL │ │ Sending a command to │ │ a device address │ │ │ │ 📟 Device │ │ Connected through Entrixy │ │ (ESP32, Arduino) │ └────────────────────────────┘
Após a escolha abre-se a janela de definições desse tipo.
┌──────────────────────────────────────────┐ │ Corner barrier 📶 3/5 312m │ │ Mine • ● │ ├──────────────────────────────────────────┤ │ [Edit] [Icon] │ │ ───────────────────────── │ │ [📍 Geo] [📶 Wi-Fi] │ └──────────────────────────────────────────┘
◉ Pela imagem Wi-Fi
○ Pela ligação a uma rede
Por baixo: a janela horária e a confirmação, comuns com o Geo
| Trigger | Indicator |
|---|---|
| GPS | "312 m" |
| Impressão Wi-Fi | 📶 barras (0–4 conforme correspondências) |
| Ligação Wi-Fi | 📶 verde ou cinzento |
| Webhook/Device | ● verde (em linha) ou cinzento (offline) |
Planos internos. Não são mostrados ao utilizador, mas os limites aplicam-se.
| Plano por omissão | Limit |
|---|---|
| Objetos (numbers) | 10 |
| Convidados (user_keys) | 10 |
Ao ultrapassar, uma mensagem suave: O número de objetos é limitado. Contacte o suporte para o aumentar.
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 — preenchidos só se o proprietário tiver ativado Partilhar coordenadas com os convidados.
webhook_url / webhook_secret — preenchidos só no modo servidor. No modo telefone vivem nas Prefs.
O proprietário põe um ícone próprio num objeto e o convidado deve vê-lo. Mas o ícone não é guardado no servidor — o servidor faz de carteiro: entrega e esquece.
Ao ser definido, o ícone é logo reduzido para 64×64 PNG (cerca de 3–5 KB) e guardado localmente nas Prefs ou num ficheiro.
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 |
|---|---|
| O proprietário está offline | O convidado vê o ícone por omissão até o proprietário voltar a ficar em linha. |
| 50 convidados perguntam ao mesmo tempo | 50 × 5 KB = 250 KB pelo WebSocket — insignificante. |
| O proprietário muda o ícone | has_avatar_hash muda e os convidados voltam a pedi-lo |
| O proprietário apaga o ícone | has_avatar: false e os convidados mostram o ícone por omissão |
| Subscribers | Em linha em simultâneo (~15 %) | Ligações WebSocket | Server |
|---|---|---|---|
| 1 000 | ~150 | ~150 | Easy |
| 10 000 | ~1 500 | ~1 500 | Easy |
| 50 000 | ~7 500 | ~7 500 | um trabalhador no limite |
| 100 000 | ~15 000 | ~15 000 | 2–3 trabalhadores |
Workerman num só trabalhador: 5000–10 000 ligações simultâneas, de cerca de 20 KB cada.
é o MySQL, não os WebSockets. Mas 250 000 inserções por dia — 50 mil subscritores × 5 chamadas — não são nada para o MySQL num SSD.
Entrixy — arquitetura final v2, acordada a 13 de abril de 2026