inou/health-poller/poller/main.py

69 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""
health-poller: pull vitals from consumer health devices into Inou.
Wraps Home Assistant integrations — never reimplements vendor APIs.
Usage:
python -m poller.main --config config.yaml
"""
import argparse
import asyncio
import logging
from poller.config import load_config
from poller.dedup import Dedup
from poller.sink import Sink
from poller.sources.renpho import RenphoSource
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
log = logging.getLogger("health-poller")
SOURCE_CLASSES = {
"renpho": RenphoSource,
}
def make_source(cfg: dict):
cls = SOURCE_CLASSES.get(cfg["type"])
if not cls:
raise ValueError(f"unknown source type: {cfg['type']}")
if cfg["type"] == "renpho":
return cls(email=cfg["email"], password=cfg["password"], user_id=cfg.get("user_id"))
raise ValueError(f"no constructor for source type: {cfg['type']}")
async def poll_source(src_cfg: dict, dedup: Dedup, sink: Sink):
source = make_source(src_cfg)
dossier_id = src_cfg.get("dossier_id", "")
readings = await source.fetch()
new = dedup.filter_new(readings)
if new:
sink.push(dossier_id, new)
dedup.mark_seen(new)
log.info(f"{src_cfg['type']}: pushed {len(new)} new readings")
else:
log.info(f"{src_cfg['type']}: no new readings")
async def main():
parser = argparse.ArgumentParser(description="Inou health data poller")
parser.add_argument("--config", default="config.yaml", help="config file path")
args = parser.parse_args()
cfg = load_config(args.config)
dedup = Dedup()
sink = Sink(cfg["inou"]["api_url"], cfg["inou"].get("api_key", ""))
for src_cfg in cfg["sources"]:
try:
await poll_source(src_cfg, dedup, sink)
except Exception:
log.exception(f"error polling {src_cfg['type']}")
if __name__ == "__main__":
asyncio.run(main())