diff --git a/backend/app/api/routes/cloud.py b/backend/app/api/routes/cloud.py index 36ebf38d3..f1dc73cc5 100644 --- a/backend/app/api/routes/cloud.py +++ b/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} diff --git a/scripts/debug_preset.py b/scripts/debug_preset.py new file mode 100644 index 000000000..c52d359f3 --- /dev/null +++ b/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())