kprofiles.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. """API routes for K-profile (pressure advance) management."""
  2. import asyncio
  3. import logging
  4. from fastapi import APIRouter, Depends, HTTPException
  5. from sqlalchemy.ext.asyncio import AsyncSession
  6. from sqlalchemy import select
  7. from backend.app.core.database import get_db
  8. from backend.app.models.printer import Printer
  9. from backend.app.schemas.kprofile import (
  10. KProfile,
  11. KProfileCreate,
  12. KProfileDelete,
  13. KProfilesResponse,
  14. )
  15. from backend.app.services.printer_manager import printer_manager
  16. logger = logging.getLogger(__name__)
  17. router = APIRouter(prefix="/printers/{printer_id}/kprofiles", tags=["kprofiles"])
  18. @router.get("/", response_model=KProfilesResponse)
  19. async def get_kprofiles(
  20. printer_id: int,
  21. nozzle_diameter: str = "0.4",
  22. db: AsyncSession = Depends(get_db),
  23. ):
  24. """Get K-profiles from a printer.
  25. Args:
  26. printer_id: ID of the printer
  27. nozzle_diameter: Filter by nozzle diameter (default: "0.4")
  28. """
  29. # Check printer exists
  30. result = await db.execute(select(Printer).where(Printer.id == printer_id))
  31. printer = result.scalar_one_or_none()
  32. if not printer:
  33. raise HTTPException(404, "Printer not found")
  34. # Get MQTT client for printer
  35. client = printer_manager.get_client(printer_id)
  36. if not client or not client.state.connected:
  37. raise HTTPException(400, "Printer not connected")
  38. # Request K-profiles from printer
  39. profiles = await client.get_kprofiles(nozzle_diameter=nozzle_diameter)
  40. # Convert from MQTT dataclass to Pydantic schema
  41. return KProfilesResponse(
  42. profiles=[
  43. KProfile(
  44. slot_id=p.slot_id,
  45. extruder_id=p.extruder_id,
  46. nozzle_id=p.nozzle_id,
  47. nozzle_diameter=p.nozzle_diameter,
  48. filament_id=p.filament_id,
  49. name=p.name,
  50. k_value=p.k_value,
  51. n_coef=p.n_coef,
  52. ams_id=p.ams_id,
  53. tray_id=p.tray_id,
  54. setting_id=p.setting_id,
  55. )
  56. for p in profiles
  57. ],
  58. nozzle_diameter=nozzle_diameter,
  59. )
  60. @router.post("/", response_model=dict)
  61. async def set_kprofile(
  62. printer_id: int,
  63. profile: KProfileCreate,
  64. db: AsyncSession = Depends(get_db),
  65. ):
  66. """Create or update a K-profile on the printer.
  67. For H2D edits (slot_id > 0), this performs an in-place edit using cali_idx.
  68. For other printers or new profiles, this adds a new profile.
  69. Args:
  70. printer_id: ID of the printer
  71. profile: K-profile data to set
  72. """
  73. is_edit = profile.slot_id > 0
  74. operation = "edit" if is_edit else "add"
  75. logger.info(
  76. f"[API] set_kprofile ({operation}): printer={printer_id}, slot_id={profile.slot_id}, "
  77. f"extruder_id={profile.extruder_id}, nozzle_id={profile.nozzle_id}, "
  78. f"name={profile.name}, filament_id={profile.filament_id}, k_value={profile.k_value}"
  79. )
  80. # Check printer exists
  81. result = await db.execute(select(Printer).where(Printer.id == printer_id))
  82. printer = result.scalar_one_or_none()
  83. if not printer:
  84. raise HTTPException(404, "Printer not found")
  85. # Get MQTT client for printer
  86. client = printer_manager.get_client(printer_id)
  87. if not client or not client.state.connected:
  88. raise HTTPException(400, "Printer not connected")
  89. # Detect H2D by serial number prefix
  90. is_h2d = printer.serial_number.startswith("094")
  91. if is_edit and is_h2d:
  92. # H2D in-place edit: use cali_idx with slot_id=0 and empty setting_id
  93. logger.info(f"[API] H2D in-place edit: cali_idx={profile.slot_id}")
  94. success = client.set_kprofile(
  95. filament_id=profile.filament_id,
  96. name=profile.name,
  97. k_value=profile.k_value,
  98. nozzle_diameter=profile.nozzle_diameter,
  99. nozzle_id=profile.nozzle_id,
  100. extruder_id=profile.extruder_id,
  101. setting_id=None,
  102. slot_id=0,
  103. cali_idx=profile.slot_id, # Pass the original slot for in-place edit
  104. )
  105. elif is_edit:
  106. # Non-H2D edit: use delete + add approach
  107. logger.info(f"[API] Edit: deleting existing profile slot_id={profile.slot_id}")
  108. delete_success = client.delete_kprofile(
  109. cali_idx=profile.slot_id,
  110. filament_id=profile.filament_id,
  111. nozzle_id=profile.nozzle_id,
  112. nozzle_diameter=profile.nozzle_diameter,
  113. extruder_id=profile.extruder_id,
  114. setting_id=profile.setting_id,
  115. )
  116. if not delete_success:
  117. raise HTTPException(500, "Failed to delete existing K-profile for edit")
  118. # Wait for printer to process the delete before adding
  119. await asyncio.sleep(0.5)
  120. logger.info("[API] Edit: delete complete, now adding updated profile")
  121. success = client.set_kprofile(
  122. filament_id=profile.filament_id,
  123. name=profile.name,
  124. k_value=profile.k_value,
  125. nozzle_diameter=profile.nozzle_diameter,
  126. nozzle_id=profile.nozzle_id,
  127. extruder_id=profile.extruder_id,
  128. setting_id=None, # Generate new setting_id for add
  129. slot_id=0, # Always 0 for add (new profile)
  130. )
  131. else:
  132. # New profile: add with slot_id=0
  133. success = client.set_kprofile(
  134. filament_id=profile.filament_id,
  135. name=profile.name,
  136. k_value=profile.k_value,
  137. nozzle_diameter=profile.nozzle_diameter,
  138. nozzle_id=profile.nozzle_id,
  139. extruder_id=profile.extruder_id,
  140. setting_id=None, # Generate new setting_id for add
  141. slot_id=0, # Always 0 for add (new profile)
  142. )
  143. if not success:
  144. raise HTTPException(500, "Failed to send K-profile command")
  145. message = "K-profile updated successfully" if is_edit else "K-profile added successfully"
  146. return {"success": True, "message": message}
  147. @router.delete("/", response_model=dict)
  148. async def delete_kprofile(
  149. printer_id: int,
  150. profile: KProfileDelete,
  151. db: AsyncSession = Depends(get_db),
  152. ):
  153. """Delete a K-profile from the printer.
  154. Args:
  155. printer_id: ID of the printer
  156. profile: K-profile identification data for deletion
  157. """
  158. # Check printer exists
  159. result = await db.execute(select(Printer).where(Printer.id == printer_id))
  160. printer = result.scalar_one_or_none()
  161. if not printer:
  162. raise HTTPException(404, "Printer not found")
  163. # Get MQTT client for printer
  164. client = printer_manager.get_client(printer_id)
  165. if not client or not client.state.connected:
  166. raise HTTPException(400, "Printer not connected")
  167. # Send the delete command to printer
  168. success = client.delete_kprofile(
  169. cali_idx=profile.slot_id,
  170. filament_id=profile.filament_id,
  171. nozzle_id=profile.nozzle_id,
  172. nozzle_diameter=profile.nozzle_diameter,
  173. extruder_id=profile.extruder_id,
  174. setting_id=profile.setting_id,
  175. )
  176. if not success:
  177. raise HTTPException(500, "Failed to send K-profile delete command")
  178. return {"success": True, "message": "K-profile deleted successfully"}