baldav/tools/hr_log.py
4aiman f704498d11 Workout mode: HR-only recording (зарядка) without GPS
New 'Зарядка' activity type records a session with no GPS - no location
service, no watchPosition, no track points - just the heart-rate poll and a
timer, for indoor exercise. kcal comes from HR (Keytel gross minus BMR, net
like the rest of the app) and counts in the daily balance.

- gps.js: GPS.hrKcalNet; startRecording/stopRecording 'workout' branches;
  the HR poll appends [relSec,bpm] samples.
- db.js: gps_sessions.hr_samples column + setSessionHrSamples.
- main.js: 'Зарядка (пульс)' option; renderWorkoutInline (map-less live
  screen: big HR, timer, kcal, connect-sensor button); renderWorkoutDetail
  (HR chart by zones, load, avg/max).
- tools/hr_log.py: background HR logger (standard 0x180D, no pairing).

Sensor read from the desktop confirmed on a Coospo H808S (F7:72:74:A4:FB:07,
~1 Hz) without pairing.
2026-10-17 00:54:21 +03:00

51 lines
1.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Фоновый логгер ЧСС с BLE HR-датчика (стандарт 0x180D / 0x2A37, напр. Coospo H808S).
Пишет строки 'epoch,bpm' в указанный файл, пока процесс не убьют. Авто-переподключение
при обрыве. Спаривание не требуется.
Использование: python3 tools/hr_log.py <BLE-addr> <logfile>
"""
import asyncio, sys, time
from bleak import BleakClient, BleakScanner
ADDR = sys.argv[1]
LOG = sys.argv[2]
HR_CHR = "00002a37-0000-1000-8000-00805f9b34fb"
def make_cb(logf):
def cb(_, data: bytearray):
flags = data[0]
bpm = int.from_bytes(data[1:3], "little") if flags & 1 else data[1]
logf.write(f"{time.time():.1f},{bpm}\n")
logf.flush()
return cb
async def main():
logf = open(LOG, "a")
logf.write(f"# {time.time():.1f} start addr={ADDR}\n")
logf.flush()
while True:
try:
dev = await BleakScanner.find_device_by_address(ADDR, timeout=15.0)
if dev is None:
logf.write(f"# {time.time():.1f} not_found, retry\n")
logf.flush()
await asyncio.sleep(3)
continue
async with BleakClient(dev) as cli:
logf.write(f"# {time.time():.1f} connected\n")
logf.flush()
await cli.start_notify(HR_CHR, make_cb(logf))
while cli.is_connected:
await asyncio.sleep(1)
logf.write(f"# {time.time():.1f} disconnected, reconnect\n")
logf.flush()
except Exception as e:
logf.write(f"# {time.time():.1f} err {e}\n")
logf.flush()
await asyncio.sleep(3)
asyncio.run(main())