- Kotlin 68.8%
- Python 30.4%
- Mako 0.6%
- Makefile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| receiver | ||
| wearos-relay | ||
| .gitignore | ||
| LICENSE | ||
| README.md | ||
Heart Relay: Galaxy Watch para PNG em streaming local
Coleta frequência cardíaca e sinais brutos de um Galaxy Watch, encaminha-os pelo telefone e mantém o histórico em um receptor local. A frequência cardíaca mais recente também é renderizada em PNG para uso em OBS, Twitch, YouTube ou outra composição de vídeo, sem round-trip por um serviço na internet.
Repositório: https://git.adlerneves.com/adler/stream_heart_beat_from_GALAXYWATCH_into_STREAMPNG
Galaxy Watch / Samsung Health Sensor SDK
│ Wear OS Data Layer
▼
companion no telefone
SQLite + WorkManager
│ HTTP JSON, até 100 lotes por POST
▼
receptor Flask + SQLite
│ GET /heart_rate
▼
chart_hr.py → PNG
Componentes
wearos-relay/wear: app Wear OS que coleta sensores contínuos e forma lotes.wearos-relay/mobile: companion Android que persiste a fila e entrega os lotes via HTTP.wearos-relay/common: contrato JSON compartilhado pelos apps.receiver: receptor Flask, banco SQLite/Alembic e renderizador PNG.
Dados coletados
Quando suportados pelo relógio, firmware e Samsung Health Sensor SDK, o app tenta coletar:
| Sensor | Campo sensor |
Ordem de values |
|---|---|---|
| Acelerômetro | accelerometer |
[x, y, z] |
| Atividade eletrodérmica | eda |
[skin_conductance] |
| Frequência cardíaca | heart_rate |
[bpm] |
| Intervalo entre batimentos | ibi |
lista de intervalos |
| Fotopletismografia | ppg |
[green, red, ir] |
| Temperatura da pele | skin_temperature |
[object, ambient] |
Cada amostra preserva o timestamp original e os códigos de status fornecidos pelo SDK. Um evento de frequência cardíaca fecha o lote imediatamente. Se nenhum HR chegar, um timer fecha o lote após 750 ms; toda emissão cancela e rearma esse timer.
Por que SpO₂ aparece como indisponível?
O SDK disponibiliza PPG verde, vermelho e infravermelho contínuos, mas não uma medição contínua pronta e validada de SpO₂. O projeto armazena os sinais PPG brutos para análise futura, porém não inventa uma porcentagem de oxigenação. Este projeto não é um dispositivo médico.
Pré-requisitos
Relógio e telefone
- Galaxy Watch compatível com o Samsung Health Sensor SDK;
- telefone Android pareado ao relógio;
- Java 17 e Android SDK para compilar os APKs;
- mesma assinatura e application ID nos dois APKs, como na configuração atual.
Ative o modo de desenvolvimento do Health Platform no relógio:
- Abra Configurações > Aplicativos > Health Platform.
- Toque repetidamente no título Health Platform (normalmente 10 vezes).
- Confirme que
[Dev mode]e a versão aparecem novamente no fim da tela. Não há necessariamente um pedido adicional de licença.
Para instalar por ADB, ative também as opções do desenvolvedor tocando repetidamente em Configurações > Sobre o relógio > Informações do software > Versão do software, depois habilite a depuração ADB/Wi-Fi.
Baixe o Samsung Health Sensor SDK e copie:
samsung-health-sensor-api-1.4.1.aar
para:
wearos-relay/wear/libs/samsung-health-sensor-api-1.4.1.aar
Receptor
- Python 3.14 ou superior;
uv;sqlite3é opcional, mas útil para consultas manuais.
Compilar e instalar os apps
cd wearos-relay
env -u GRADLE_HOME ./gradlew test lint assembleDebug
Os APKs serão gerados em:
wearos-relay/wear/build/outputs/apk/debug/wear-debug.apk
wearos-relay/mobile/build/outputs/apk/debug/mobile-debug.apk
Instale explicitamente em cada dispositivo. Use adb devices -l para identificar os serials, especialmente quando relógio e telefone estiverem conectados em portas diferentes:
adb -s SERIAL_DO_RELOGIO install -r wear/build/outputs/apk/debug/wear-debug.apk
adb -s SERIAL_DO_TELEFONE install -r mobile/build/outputs/apk/debug/mobile-debug.apk
Executar o receptor
Em outro terminal:
cd receiver
uv sync
make migrate
make serve
O servidor escuta em [::]:8798. Use uma URL alcançável pelo telefone, por exemplo http://192.168.1.20:8798/; localhost no companion aponta para o próprio telefone.
Configurar e iniciar o relay
- Abra o companion no telefone.
- Informe a URL completa do receptor.
- Se necessário, informe o valor integral do header
Authorization, comoBearer meu-token. Campo vazio significa que o header não é enviado. - Salve a configuração.
- Abra o app no relógio, conceda as permissões e toque em Iniciar.
O companion mostra o tamanho da fila persistente. Reiniciar fila recria a infraestrutura de entrega sem apagar os lotes salvos.
Autenticação
Crie receiver/authtoken.txt com o valor exato esperado no header:
Bearer meu-token
O receptor aplica str.strip() ao arquivo. Se ele não existir ou ficar vazio após essa normalização, POST / e GET /heart_rate ficam sem autenticação. Caso contrário, um valor ausente ou diferente retorna HTTP 401.
HTTP sem TLS é aceito para uma LAN confiável e gera aviso no companion. Fora dela, use HTTPS ou um proxy TLS; o token trafega em texto claro sobre HTTP.
Contrato HTTP
O companion agrupa de 1 a 100 lotes em cada POST /:
{
"schema_version": 1,
"batches": [
{
"schema_version": 2,
"batch_id": "6e07d53e-55e6-4faa-9146-56b96e89004d",
"session_id": "ca45f030-08a4-48bc-82ea-f94ef94912ac",
"device_id": "wearos:0ec18a2f-7e0d-44b7-8b38-4e58a44074a7",
"sequence": 42,
"emitted_at": "2026-08-21T20:15:30.000Z",
"trigger": "heart_rate",
"samples": [
{
"timestamp_ms": 1787444130500,
"sensor": "heart_rate",
"values": [73.0],
"status": [0]
}
]
}
]
}
Headers usados:
Content-Type: application/json
Accept: application/json
X-Batch-Count: 1..100
Authorization: valor configurado (somente quando não vazio)
O receptor também aceita um único objeto de lote schema v2 por compatibilidade. Em Flask, o conteúdo está em request.get_json(); request.form e request.args aparecem como ImmutableMultiDict([]) porque o corpo é JSON.
batch_id torna reenvios idempotentes. O servidor ainda protege a combinação session_id + sequence; lotes duplicados retornam sucesso sem duplicar amostras.
Persistência e entrega
- O relógio envia pelo Data Layer, mas não mantém uma fila durável durante desconexões do telefone.
- O companion valida e grava cada lote em
measurement_delivery_queue.dbantes de agendar o WorkManager. - A fila é lida em ordem FIFO e entregue em envelopes de até 100 lotes.
- Falhas de rede e HTTP 408, 429 ou 5xx são tentadas novamente; outros 4xx são finais.
- Reinícios do telefone, pausas térmicas e restrições de bateria não apagam a fila SQLite.
- O receptor insere o envelope inteiro em uma transação no
sensor-readings.sqlite3.
No receptor, out.json é apenas uma cópia de compatibilidade do último lote aceito. O histórico autoritativo está nas tabelas sensor_batches e sensor_samples.
Consultas úteis:
cd receiver
sqlite3 -cmd '.timeout 5000' -header -column sensor-readings.sqlite3 \
'SELECT sensor, COUNT(*) AS samples FROM sensor_samples GROUP BY sensor ORDER BY sensor;'
sqlite3 -cmd '.timeout 5000' -header -column sensor-readings.sqlite3 \
'SELECT timestamp_ms, sensor, "values", status FROM sensor_samples ORDER BY timestamp_ms DESC LIMIT 20;'
Consultar a frequência cardíaca
curl -H 'Authorization: Bearer meu-token' \
http://127.0.0.1:8798/heart_rate
Exemplo de resposta:
{"heart_rate_bpm": 72.0, "timestamp_ms": 1787452958871}
Sem uma medição armazenada, a rota retorna HTTP 404.
Gerar o PNG
Com o receptor em execução:
cd receiver
make chart_hr
chart_hr.py consulta GET /heart_rate, reutiliza authtoken.txt e atualiza vuMeter_rendered.png. Os scripts chart_hr_scale_relax.py e chart_hr_scale_intensive.py ajustam os perfis de escala. Para personalizar a aparência, edite ou substitua chart_hr.py; outra opção é criar uma página HTML que consulte a mesma rota.
Desenvolvimento e testes
cd wearos-relay
env -u GRADLE_HOME ./gradlew test lint assembleDebug
cd ../receiver
make test
Antes de alterar o modelo do banco, crie e aplique uma revisão Alembic em receiver/migrations/versions/.
Solução de problemas
Só chega heart_rate
Confirme o [Dev mode], o AAR correto, as permissões e o suporte de hardware/firmware. Procure o tag SamsungSensors no logcat: trackers não suportados são registrados e os demais continuam ativos. O app não pode fabricar canais que o dispositivo não expõe.
O companion mostra “envio agendado”, mas não entrega
Verifique URL, token e conectividade; observe o contador da fila e use Reiniciar fila para recriar o job sem perder dados. Android pode adiar WorkManager por bateria ou temperatura, mas a fila continua no SQLite.
O relógio não encontra o telefone
Confirme que ambos estão pareados, conectados e com APKs de mesmo application ID e assinatura. Reinstalar apenas um APK com outra chave impede a associação pelo Data Layer.
A tela apagou e as atualizações pararam
Conceda as permissões de sensores em segundo plano e mantenha o serviço foreground iniciado. O app usa o sensor cardíaco wake-up do Android como fallback de HR e um wake lock parcial para concluir as janelas de lote; outros canais ainda dependem do SDK e do estado do dispositivo.
Projetos anteriores
- BLE para HTTP: Amazfit Bip, GTR 2 e dispositivos com serviço BLE acessível.
- HTTP para PNG: transforma sinais HTTP em uma imagem para composição.
- Este projeto: coleta pelo stack proprietário da Samsung, usa o telefone como ponte, persiste em SQLite e renderiza o PNG.
Uso de IA
GPT-5.6-Sol foi usado no projeto. O desenho e a revisão foram conduzidos por alguém com 12 anos de experiência em Python e 8 anos em desenvolvimento Android.
Licença
MIT No Attribution. A fonte receiver/SourceCodePro-Medium.ttf tem licença própria em receiver/SourceCodePro-Medium.LICENSE.txt.