No description
  • Kotlin 68.8%
  • Python 30.4%
  • Mako 0.6%
  • Makefile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-23 12:22:59 -03:00
receiver BREAKING CHANGE: commit inicial 2026-08-23 12:22:59 -03:00
wearos-relay BREAKING CHANGE: commit inicial 2026-08-23 12:22:59 -03:00
.gitignore BREAKING CHANGE: commit inicial 2026-08-23 12:22:59 -03:00
LICENSE BREAKING CHANGE: commit inicial 2026-08-23 12:22:59 -03:00
README.md BREAKING CHANGE: commit inicial 2026-08-23 12:22:59 -03:00

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

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:

  1. Abra Configurações > Aplicativos > Health Platform.
  2. Toque repetidamente no título Health Platform (normalmente 10 vezes).
  3. 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

  1. Abra o companion no telefone.
  2. Informe a URL completa do receptor.
  3. Se necessário, informe o valor integral do header Authorization, como Bearer meu-token. Campo vazio significa que o header não é enviado.
  4. Salve a configuração.
  5. 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.db antes 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.