Procházet zdrojové kódy

Fix Bambu Cloud preset lookup by transforming filament_id to setting_id

Printers report filament_id (e.g., GFA00) but the Bambu Cloud API expects
setting_id format which has an "S" inserted after "GF" (e.g., GFSA00).

- Add _filament_id_to_setting_id() helper function
- Transform IDs before API calls: GFx## -> GFSx##
- User presets (P-prefix) and already-correct IDs unchanged
- Improved warning message to show both original and transformed IDs
maziggy před 3 měsíci
rodič
revize
65a6e7791b
2 změnil soubory, kde provedl 118 přidání a 2 odebrání
  1. 36 2
      backend/app/api/routes/cloud.py
  2. 82 0
      scripts/debug_preset.py

+ 36 - 2
backend/app/api/routes/cloud.py

@@ -267,6 +267,34 @@ _filament_cache_time: float = 0
 FILAMENT_CACHE_TTL = 300  # 5 minutes
 
 
+def _filament_id_to_setting_id(filament_id: str) -> str:
+    """
+    Convert filament_id to setting_id format for Bambu Cloud API.
+
+    Printers report filament_id (e.g., GFA00, GFG02) but the API expects
+    setting_id format which has an "S" inserted after "GF" (e.g., GFSA00, GFSG02).
+
+    User presets (starting with "P") and already-correct IDs are returned unchanged.
+    """
+    if not filament_id:
+        return filament_id
+
+    # User presets start with "P" - leave unchanged
+    if filament_id.startswith("P"):
+        return filament_id
+
+    # Official Bambu presets: GFx## -> GFSx##
+    # Check if it matches the filament_id pattern (GF followed by letter and digits)
+    if filament_id.startswith("GF") and len(filament_id) >= 4:
+        # Check if it's already a setting_id (has S after GF)
+        if filament_id[2] == "S":
+            return filament_id
+        # Insert "S" after "GF": GFA00 -> GFSA00
+        return f"GFS{filament_id[2:]}"
+
+    return filament_id
+
+
 @router.post("/filament-info")
 async def get_filament_info(setting_ids: list[str] = Body(...), db: AsyncSession = Depends(get_db)):
     """
@@ -308,7 +336,10 @@ async def get_filament_info(setting_ids: list[str] = Body(...), db: AsyncSession
             continue
 
         try:
-            data = await cloud.get_setting_detail(setting_id)
+            # Transform filament_id to setting_id format (GFA00 -> GFSA00)
+            api_setting_id = _filament_id_to_setting_id(setting_id)
+
+            data = await cloud.get_setting_detail(api_setting_id)
             setting = data.get("setting", {})
 
             # Extract name (e.g., "Bambu PLA Basic Jade White")
@@ -323,11 +354,14 @@ async def get_filament_info(setting_ids: list[str] = Body(...), db: AsyncSession
                     k_value = None
 
             info = {"name": name, "k": k_value}
+            # Cache using original ID so frontend gets expected response
             _filament_cache[setting_id] = info
             result[setting_id] = info
 
         except Exception as e:
-            logger.warning(f"Failed to get cloud preset {setting_id}: {e}")
+            logger.warning(
+                f"Failed to get cloud preset {setting_id} (API ID: {_filament_id_to_setting_id(setting_id)}): {e}"
+            )
             # Cache the failure to avoid repeated requests
             _filament_cache[setting_id] = {"name": "", "k": None}
             result[setting_id] = {"name": "", "k": None}

+ 82 - 0
scripts/debug_preset.py

@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+"""Debug script to investigate Bambu Cloud preset API responses."""
+
+import asyncio
+import json
+import sys
+from pathlib import Path
+
+# Add backend to path
+sys.path.insert(0, str(Path(__file__).parent.parent))
+
+import httpx
+from sqlalchemy import create_engine, text
+
+from backend.app.core.config import settings
+
+# Test preset IDs (from the warning logs)
+TEST_IDS = ["GFG02", "GFL05", "GFA00", "GFA02", "GFA06"]
+
+
+def get_token_from_db() -> str | None:
+    """Get the stored token from the database."""
+    db_path = settings.base_dir / "bambuddy.db"
+    engine = create_engine(f"sqlite:///{db_path}")
+
+    with engine.connect() as conn:
+        result = conn.execute(text("SELECT value FROM settings WHERE key = 'bambu_cloud_token'"))
+        row = result.fetchone()
+
+        if row and row[0]:
+            return row[0]
+    return None
+
+
+async def test_preset(setting_id: str, token: str, base_url: str = "https://api.bambulab.com"):
+    """Test fetching a single preset and show full response."""
+    url = f"{base_url}/v1/iot-service/api/slicer/setting/{setting_id}"
+    headers = {
+        "Authorization": f"Bearer {token}",
+        "Content-Type": "application/json",
+    }
+
+    print(f"\n{'=' * 60}")
+    print(f"Testing preset: {setting_id}")
+    print(f"URL: {url}")
+    print(f"{'=' * 60}")
+
+    async with httpx.AsyncClient() as client:
+        response = await client.get(url, headers=headers)
+
+        print(f"Status: {response.status_code}")
+        print("\nResponse body:")
+        try:
+            data = response.json()
+            print(json.dumps(data, indent=2))
+        except Exception:
+            print(response.text)
+
+    return response.status_code
+
+
+async def main():
+    # Get token from DB
+    token = get_token_from_db()
+
+    if not token:
+        print("Could not find token in database.")
+        print("Make sure you're logged into Bambu Cloud in Bambuddy.")
+        sys.exit(1)
+
+    print(f"Found token in database (length: {len(token)})")
+
+    # Allow testing specific preset IDs from command line
+    test_ids = sys.argv[1:] if len(sys.argv) > 1 else TEST_IDS
+
+    # Test each preset
+    for preset_id in test_ids:
+        await test_preset(preset_id, token)
+
+
+if __name__ == "__main__":
+    asyncio.run(main())