obico_actions.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. """Action dispatch for Obico failure detection.
  2. Separated from the detection loop so actions can be unit-tested and swapped.
  3. """
  4. import logging
  5. from sqlalchemy import select
  6. from backend.app.core.database import async_session
  7. from backend.app.models.printer import Printer
  8. logger = logging.getLogger(__name__)
  9. async def execute_action(
  10. printer_id: int, action: str, task_name: str, score: float, frame: bytes | None = None
  11. ) -> None:
  12. """Run the configured action for a detected print failure.
  13. action: 'notify' | 'pause' | 'pause_and_off'
  14. frame: the camera snapshot the ML model flagged, if any — attached to
  15. the notification so the recipient can see what triggered it.
  16. """
  17. printer_name = await _get_printer_name(printer_id)
  18. if action in ("pause", "pause_and_off"):
  19. _pause_print(printer_id)
  20. if action == "pause_and_off":
  21. await _turn_off_linked_plugs(printer_id)
  22. await _notify(printer_id, printer_name, task_name, score, action, frame)
  23. async def _get_printer_name(printer_id: int) -> str:
  24. async with async_session() as db:
  25. result = await db.execute(select(Printer).where(Printer.id == printer_id))
  26. printer = result.scalar_one_or_none()
  27. return printer.name if printer else f"Printer {printer_id}"
  28. def _pause_print(printer_id: int) -> None:
  29. from backend.app.services.printer_manager import printer_manager
  30. client = printer_manager.get_client(printer_id)
  31. if not client:
  32. logger.warning("Obico pause: no MQTT client for printer %s", printer_id)
  33. return
  34. if not client.pause_print():
  35. logger.warning("Obico pause: pause_print() returned False for printer %s", printer_id)
  36. async def _turn_off_linked_plugs(printer_id: int) -> None:
  37. from backend.app.services.smart_plug_manager import smart_plug_manager
  38. async with async_session() as db:
  39. plugs = await smart_plug_manager._get_plugs_for_printer(printer_id, db)
  40. for plug in plugs:
  41. if not plug.enabled:
  42. continue
  43. try:
  44. service = await smart_plug_manager.get_service_for_plug(plug, db)
  45. await service.turn_off(plug)
  46. logger.info("Obico action: turned off plug %s for printer %s", plug.name, printer_id)
  47. except Exception as e:
  48. logger.error("Obico action: failed to turn off plug %s: %s", plug.name, e)
  49. async def _notify(
  50. printer_id: int, printer_name: str, task_name: str, score: float, action: str, frame: bytes | None = None
  51. ) -> None:
  52. """Fire the AI Failure Detection notification (#1794).
  53. Routed to its own event in 0.2.5b1; previously rode the multiplexed
  54. on_printer_error toggle, which made it indistinguishable from HMS
  55. hardware errors in the UI.
  56. """
  57. from backend.app.services.notification_service import notification_service
  58. async with async_session() as db:
  59. try:
  60. await notification_service.on_ai_failure_detection(
  61. printer_id=printer_id,
  62. printer_name=printer_name,
  63. task_name=task_name,
  64. confidence=score,
  65. action=action,
  66. db=db,
  67. image_data=frame,
  68. )
  69. except Exception as e:
  70. logger.error("Obico notify failed for printer %s: %s", printer_id, e)