Answers from the documentation; checking which model is on duty
Hi - I answer from the OpenSmartRoute documentation: routing, the API, plans and quotas, self-hosting. Ask away, or open a support ticket if you need a person.
Grounded in the docs - follow a source before acting on it.
Prompt file imported from ramonsss/MQTT_Projeto_Vena (.github/prompts/phase4-ble.prompt.md). Copyright stays with the author.
Fase 4 — BLE: Comunicação Local e Provisioning
Objetivo: Adicionar BLE GATT ao firmware ESP32 (NimBLE) e ao app Flutter (flutter_reactive_ble) para monitoramento local sem internet, provisioning Wi-Fi e pareamento. A tela de detalhe passa a exibir dados de BLE e MQTT simultaneamente, unificados no SQLite, com badge indicando a fonte.
Pré-requisito: Fase 3 concluída (app Flutter com login Google, listagem de devices, MQTT live, histórico REST, testes T1–T10 passando, Google Sign-In funcional com google-services.json).
Decisões técnicas (resolver ANTES de codificar)
#
Decisão
Resolução
1
Lib BLE Flutter
flutter_reactive_ble (preferido sobre flutter_blue_plus — melhor suporte a bonding, restore, e produção).
2
Stack BLE firmware
NimBLE via h2zero/NimBLE-Arduino (menor footprint que Bluedroid, ~50% menos RAM).
3
Advertising name
Vena-<últimos 4 hex do MAC> (ex.: Vena-D2E3). Identificação visual rápida.
4
GATT UUIDs
Namespace custom 128-bit: base 0000XXXX-VENA-4F6C-8E12-A0B765C1D2E3. Service UUID 0x0001, characteristics 0x0002–0x0006.
5
Segurança BLE
Just Works pairing (sem PIN) para MVP — simplifica UX. pairing_code lido via characteristic requer bonding (encrypted read).
6
Provisioning
Tela de wizard no app: scan QR → connect BLE → write SSID+PSK+device_jwt → ESP reinicia com Wi-Fi → claim no backend.
7
Live telemetry BLE
Notify a cada 2s (independente do MQTT 5s) — menor latência local. Payload compacto (binário ou JSON curto).
8
Coexistência Wi-Fi+BLE
ESP32 suporta ambos simultaneamente. BLE fica sempre ativo (advertising/connected). Wi-Fi pode estar off durante provisioning inicial.
9
Merge BLE+MQTT no SQLite
Ambas fontes gravam no mesmo latest_states e telemetry_cache via Drift. Chave (deviceId, ts) — mais recente vence. Campo source indica origem.
JSON a cada 2s: {"ts":1737830400000,"at":22.5,"ah":65.2,"dt":18.3,"dh":60.1,"sp":18.0,"po":120}
wifi_status
0x0004
Read, Notify
JSON: {"connected":true,"ssid":"FazendaWifi","ip":"192.168.1.42","rssi":-65} ou {"connected":false}
wifi_provisioning
0x0005
Write
Input JSON: {"ssid":"...","psk":"...","jwt":"<device_jwt>"}. ESP salva em NVS, reinicia Wi-Fi.
pairing_code
0x0006
Read (encrypted)
Retorna "K7X9-M2P4" — requer bonding ativo.
Backend REST (novos ou alterados)
Endpoint
Método
Uso
POST /devices/provision
POST
Body: {"device_id":"vena-...", "pairing_code":"K7X9-M2P4"}. Retorna {"device_jwt":"<jwt>"} para o ESP. Chamado pelo app durante wizard.
POST /devices/{id}/claim
POST
Já existe (Fase 2). App chama após provisioning bem-sucedido.
Checklist de implementação
A. Firmware — NimBLE GATT
#
Arquivo/Pasta
O que faz
A1
firmware/platformio.ini
Adicionar h2zero/NimBLE-Arduino@^2.1.0 em lib_deps. Adicionar build flag -D CONFIG_BT_NIMBLE_ROLE_BROADCASTER -D CONFIG_BT_NIMBLE_ROLE_PERIPHERAL.
A2
firmware/include/config.h
Adicionar bloco BLE: BLE_DEVICE_PREFIX "Vena-", UUIDs do service e characteristics, BLE_NOTIFY_INTERVAL_MS 2000, BLE_MTU 247.
A3
firmware/lib/BleManager/BleManager.h
Declaração da classe: init(), startAdvertising(), stopAdvertising(), isConnected(), notifyTelemetry(payload), setWifiProvisioningCallback(cb).
A4
firmware/lib/BleManager/BleManager.cpp
Implementação NimBLE: cria server, service, characteristics. Callbacks para connect/disconnect, write em wifi_provisioning, read em device_info/pairing_code. Notify loop em live_telemetry.
A5
firmware/include/ble_uuids.h
Constantes UUID 128-bit para service e cada characteristic. Separado para clareza.
A6
firmware/src/main.cpp
Integrar BleManager::init() no setup(). No loop(), chamar bleManager.notifyTelemetry() a cada 2s (timer separado do MQTT 5s). Callback de provisioning → salvar NVS + restart Wi-Fi.
provisionDevice(deviceId, pairingCode, ssid, psk): chama POST /devices/provision → recebe device_jwt → escreve via BLE → poll wifi_status até connected (timeout 30s).
bleTelemetryProvider(deviceId): conecta BLE ao entrar na tela → subscribe telemetry notify → emite stream de BleTelemetry. Desconecta ao sair (com delay 5s para evitar reconexão desnecessária ao navegar rápido).
Agora combina: latestStateProvider(deviceId) lê do Drift (que recebe de BLE + MQTT). Adicionar sourceProvider(deviceId) computed que indica fonte do dado mais recente.
Expandir para 3 estados: ble, mqtt, offline. Cada um com ícone, cor e micro-animação pulse quando ativo.
F5
mobile/lib/core/ble/ble_lifecycle.dart
Mixin/listener AppLifecycleState: em resumed → reconecta BLE se tela de detalhe ativa; em paused → desconecta BLE após 5s.
G. Backend — Provisioning endpoint
#
Arquivo
O que faz
G1
backend/app/devices/routes.py (alteração)
Novo endpoint POST /devices/provision: recebe {device_id, pairing_code}, valida pairing_code contra hash do device → gera device JWT (scope=device, longa duração) → retorna {device_jwt}.
G2
backend/app/auth/jwt.py (alteração)
Nova função create_device_jwt(device_id) → str: JWT com sub=device:{id}, scope=device, exp=1 ano.
G3
backend/app/devices/schemas.py (alteração)
Novo schema ProvisionRequest(device_id: str, pairing_code: str) e ProvisionResponse(device_jwt: str).
PairingProvider state machine — simula fluxo QR → BLE scan → connect → provision → claim → done. Verifica cada transição de estado.
T7
bleTelemetryProvider — emite dados BLE enquanto conectado; para ao desconectar; reconecta com backoff após erro.
T8
Widget test: DeviceDetailScreen com BLE ativo — badge mostra "BLE", valor atualiza ao receber notify do stream mockado.
T9
Widget test: BleScanStep — mostra lista de devices descobertos, tap seleciona, timeout mostra retry.
T10
Widget test: WifiProvisionStep — form validate (SSID obrigatório, PSK min 8 chars), submit → progress → success/error.
T11
Integration: merge BLE+MQTT — simula BLE com ts=100 e MQTT com ts=102 → latest_states mostra ts=102 source='mqtt'. Depois BLE ts=104 → atualiza para source='ble'.
T12
Firmware unit test (PlatformIO): BleManager inicializa service com UUIDs corretos; notifyTelemetry serializa payload; provisioning callback dispara com credenciais parseadas.
Critérios de aceite (DoD)
#
Critério
Como verificar
1
ESP32 faz advertising BLE com nome Vena-XXXX
Scanner genérico (nRF Connect) vê o device
2
App descobre Venas próximas via BLE scan
Tela de scan mostra device com RSSI
3
Conexão BLE + bonding funciona
App conecta, bond persiste entre reinícios
4
device_info retorna JSON válido
nRF Connect ou app lê characteristic com sucesso
5
Telemetry notify funciona a cada 2s
Tela de detalhe atualiza com dados BLE; badge mostra "BLE"
6
Provisioning Wi-Fi via BLE funciona
ESP sem Wi-Fi → app envia credenciais → ESP conecta → status muda para online
7
ESP mantém BLE + Wi-Fi simultaneamente
Após provisioning, BLE continua disponível enquanto MQTT publica
8
Merge BLE + MQTT no SQLite
Ambas fontes gravam; UI mostra dado mais recente; badge alterna corretamente
9
Wizard de pairing completo funciona
QR → BLE scan → connect → provision → claim → device na lista
10
Permissões tratadas graciosamente
Sem permissão → dialog explicativo → deep link para settings
OTA via BLE — complexidade alta, pouco valor no MVP. Firmware update manual por USB.
Múltiplas conexões BLE simultâneas — uma por vez é suficiente para UX de produtor.
BLE Mesh — desnecessário; cada Vena opera independentemente.
Notificações push baseadas em BLE proximity — fase posterior (FCM + geofencing).
Criptografia customizada no payload BLE — bonding/encryption nativo do BLE 4.2+ é suficiente.
BLE em background contínuo no app — bateria proibitiva. MQTT cobre dados remotos; BLE é para interação ativa.
Installed into a catalogue, chosen by a router
Install phase4-ble and it becomes one more candidate the router can pick - when it fits.
A listing is a routing target with a manifest: what it does, which domains it covers, what it costs and who publishes it. Once installed it sits beside your own models and tools, is scored like any other candidate for each request, and shows up in the trace when it wins. Ratings come from workspaces that installed it, one per account.