From b87505250a853c680425d27a9f90de91aa963f1d Mon Sep 17 00:00:00 2001 From: ash Date: Tue, 31 Mar 2026 08:17:07 +0100 Subject: [PATCH] Add files --- Dockerfile | 18 ++++++ app.py | 139 +++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 9 +++ logo.bin | Bin 0 -> 1080 bytes requirements.txt | 3 + 5 files changed, 169 insertions(+) create mode 100644 Dockerfile create mode 100644 app.py create mode 100644 docker-compose.yml create mode 100644 logo.bin create mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b7029d5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy app +COPY app.py . +COPY logo.bin . + +# Expose port +EXPOSE 5000 + +# Run app +RUN pip install gunicorn +CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"] diff --git a/app.py b/app.py new file mode 100644 index 0000000..ebff9da --- /dev/null +++ b/app.py @@ -0,0 +1,139 @@ +from flask import Flask, jsonify, send_file +import datetime +import os +import time +from garminconnect import Garmin + +app = Flask(__name__) + +# --- CONFIG --- +EMAIL = os.getenv("GARMIN_EMAIL") +PASSWORD = os.getenv("GARMIN_PASSWORD") +START_DATE = datetime.date.fromisoformat(os.getenv("START_DATE", "2026-01-01")) +GOAL_KM = float(os.getenv("GOAL_KM", "80")) +ALLOWED_TYPES = os.getenv("ALLOWED_TYPES", "running,treadmill_running").split(",") + +CACHE_TTL = int(os.getenv("CACHE_TTL", "300")) # seconds (default 5 mins) + +# --- GLOBAL CACHE --- +garmin_client = None +last_login_time = 0 + +cached_data = None +last_fetch_time = 0 + + +def get_client(): + global garmin_client, last_login_time + + # Reuse client if already logged in + if garmin_client: + return garmin_client + + # Otherwise login once + garmin_client = Garmin(EMAIL, PASSWORD) + garmin_client.login() + last_login_time = time.time() + + print("✅ Logged into Garmin") + + return garmin_client + + +def get_garmin_data(): + global cached_data, last_fetch_time + + now = time.time() + + # --- RETURN CACHED DATA --- + if cached_data and (now - last_fetch_time < CACHE_TTL): + print("⚡ Returning cached data") + return cached_data + + try: + client = get_client() + + today = datetime.date.today() + activities = client.get_activities_by_date( + START_DATE.isoformat(), today.isoformat() + ) + + total_meters = 0 + for act in activities: + activity_type = act.get("activityType", {}).get("typeKey", "").lower() + if activity_type in ALLOWED_TYPES: + total_meters += act.get("distance", 0) + + total_km = total_meters / 1000.0 + + result = { + "total_km": round(total_km, 2), + "goal_km": GOAL_KM, + "percent": round((total_km / GOAL_KM) * 100, 1), + "status": "success", + } + + # --- UPDATE CACHE --- + cached_data = result + last_fetch_time = now + + print("🔄 Fetched fresh data") + + return result + + except Exception as e: + # If Garmin session expired → reset client and retry once + print(f"⚠️ Error: {e}, retrying login...") + + try: + reset_client() + + client = get_client() + + today = datetime.date.today() + activities = client.get_activities_by_date( + START_DATE.isoformat(), today.isoformat() + ) + + total_meters = sum( + act.get("distance", 0) + for act in activities + if act.get("activityType", {}).get("typeKey", "").lower() in ALLOWED_TYPES + ) + + total_km = total_meters / 1000.0 + + result = { + "total_km": round(total_km, 2), + "goal_km": GOAL_KM, + "percent": round((total_km / GOAL_KM) * 100, 1), + "status": "success", + } + + cached_data = result + last_fetch_time = now + + return result + + except Exception as e2: + return {"status": "error", "message": str(e2)} + + +def reset_client(): + global garmin_client + garmin_client = None + print("♻️ Garmin client reset") + + +@app.route('/progress') +def progress(): + return jsonify(get_garmin_data()) + + +@app.route('/image') +def get_image(): + return send_file("logo.bin", mimetype='application/octet-stream') + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..534992c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +services: + garmin-api: + build: . + container_name: garmin + ports: + - "5000:5000" + env_file: + - .env + restart: unless-stopped diff --git a/logo.bin b/logo.bin new file mode 100644 index 0000000000000000000000000000000000000000..5e3c6af8ddc0d62d61df12a7358ba00807f7c697 GIT binary patch literal 1080 zcmb8t!DmMu!0ZZ)zh}_9=s(FAjaqL z)CmM~Qt<4-Cy>Qc4k7Jd)78C$CoL%Y>!O(Ht{LDT^j2@yS-KzgnDR>g>?k-Qe1P z0-gr%9Dcom&EWY8U(oi>{3-U)^)CGx?qkyXvOnkA^sWYOyl1WI>l3k4&)ILrp}w<9 zZl*4|DuH?#dPeLVsS9o{jzT@BOEuoJUWK~2h~`x{qK(6)Mth!gxH?fk`i0lgS?fP^ z;he}ORnta~(aoY4)|qf$UTO{(&Dq%KU7PECfEkM9j*|^D1obAl_D<&vjJJ_2&uf_y zGH)Yb$TN(9(J~EFqGdX!gkf5ygkxH!gaa}wU|70Gs3o(OYrPiRu$hWo@I5ZY=iu91 z%31Ku6|Z2_r@X#;YjOTKp0oc=wf=yu@jNnOe)~S;y_|$Ze(*EBHm|)s3uP1+>