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 0000000..5e3c6af Binary files /dev/null and b/logo.bin differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b834994 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +flask +garminconnect +requests