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.
This commit is contained in:
parent
dd1642b6e9
commit
f704498d11
7 changed files with 295 additions and 3 deletions
51
tools/hr_log.py
Normal file
51
tools/hr_log.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
#!/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())
|
||||
Loading…
Add table
Add a link
Reference in a new issue