[阶段5.1] 开机自启 + 接入真实 garminconnect,修复同步的三处 API 误用
fix(garmin): 同步代码调用的 garminconnect API 全是错的 装上库核对签名后发现(garminconnect 0.2.8),原代码从未被真正 执行过,三处调用都不成立: 1. get_activities(date) —— 该方法签名是 (start, limit),收的是 分页下标和条数,不是日期。传日期等于在问"第 2026-08-23 条运动", 而且少传一个参数必然 TypeError。 改为 get_activities_by_date(start, end),整个窗口一次取回, 顺带把原来"每天一次调用"减为一次。 2. 睡眠数据不在 get_user_summary 里,是独立的 get_sleep_data(cdate); 原代码从 summary 里读 sleep 字段,结果会把每一晚都记成无睡眠数据。 HRV 同理,走 get_hrv_data(cdate)。 3. 字段名不符:实际是 totalSteps / totalKilocalories / averageStressLevel,原代码用的是 steps / calories.total / stress.average。 其他改进: - 运动记录改用 Garmin 自己的 activityId 作主键,重复同步同一窗口 不再产生重复行(原来每次同步都会把同一条运动再插一遍) - 某天无数据时返回全 None,不再写入空行让读端点再过滤掉 - 原来 bare except 吞掉每天的异常,全部失败也报 success; 现在全窗口失败会如实返回 error 并记录原因 - 同步天数、是否走 Garmin 中国区改为环境变量可配 tests/test_garmin_sync.py (23 通过): - 用 StubClient 模拟真实 0.2.8 的接口形状,无需库、凭证或网络 - 回归用例覆盖上述三处误用:活动必须按日期区间一次取回、 睡眠与 HRV 必须走各自端点 - 重复同步不产生重复的天和重复的运动记录 - 单天失败跳过、全窗口失败报错、登录失败如实上报 部署: - NAS 安装 garminconnect(pydantic-core 有 cp38 x86_64 轮子, 无需 gcc) - 新增 deploy/S99garmin.sh 开机自启脚本,与 NAS 上既有的 S99frpc.sh 同一套惯例;以 root 启动但降权到 ericwyuan 运行, 因为服务不需要特权而 .env 里有数据库和 API 凭证 - PID 文件放应用目录而非 /var/run(非特权用户写不了) NAS 真机全量测试: 240 passed Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
46
deploy/S99garmin.sh
Normal file
46
deploy/S99garmin.sh
Normal file
@@ -0,0 +1,46 @@
|
||||
#!/bin/sh
|
||||
# Garmin Health Lab — DSM boot script.
|
||||
# Mirrors the convention already used by S99frpc.sh on this NAS.
|
||||
APP=/var/services/homes/ericwyuan/apps/garmin-health-lab
|
||||
RUNAS=ericwyuan
|
||||
# Kept inside the app dir, not /var/run: the service runs as an unprivileged
|
||||
# user, which cannot write there.
|
||||
PIDFILE="$APP/app.pid"
|
||||
|
||||
start() {
|
||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
||||
echo "already running (pid $(cat "$PIDFILE"))"
|
||||
return 0
|
||||
fi
|
||||
# Runs as the owning user: the service needs nothing privileged and its
|
||||
# .env holds database and API credentials.
|
||||
# setsid + closed stdio so it survives the invoking shell and never holds
|
||||
# an SSH session open.
|
||||
if [ "$(id -un)" = "$RUNAS" ]; then
|
||||
sh "$APP/start.sh" >/dev/null 2>&1 </dev/null
|
||||
else
|
||||
su - "$RUNAS" -c "sh $APP/start.sh" >/dev/null 2>&1 </dev/null
|
||||
fi
|
||||
sleep 2
|
||||
echo "started (pid $(cat "$PIDFILE" 2>/dev/null))"
|
||||
}
|
||||
|
||||
stop() {
|
||||
[ -f "$PIDFILE" ] && kill "$(cat "$PIDFILE")" 2>/dev/null
|
||||
rm -f "$PIDFILE"
|
||||
pkill -f "gunicorn.*8123" 2>/dev/null
|
||||
echo "stopped"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start) start ;;
|
||||
stop) stop ;;
|
||||
restart) stop; sleep 2; start ;;
|
||||
status)
|
||||
if [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; then
|
||||
echo "running (pid $(cat "$PIDFILE"))"
|
||||
else
|
||||
echo "not running"
|
||||
fi ;;
|
||||
*) echo "Usage: $0 {start|stop|restart|status}" ;;
|
||||
esac
|
||||
Reference in New Issue
Block a user