obico_actions.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  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 publish_plug_state, 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. if await service.turn_off(plug):
  46. await publish_plug_state(
  47. plug.id, plug.name, plug.printer_id, on=False, ha_entity_id=plug.ha_entity_id
  48. )
  49. logger.info("Obico action: turned off plug %s for printer %s", plug.name, printer_id)
  50. except Exception as e:
  51. logger.error("Obico action: failed to turn off plug %s: %s", plug.name, e)
  52. async def _notify(
  53. printer_id: int, printer_name: str, task_name: str, score: float, action: str, frame: bytes | None = None
  54. ) -> None:
  55. """Fire the AI Failure Detection notification (#1794).
  56. Routed to its own event in 0.2.5b1; previously rode the multiplexed
  57. on_printer_error toggle, which made it indistinguishable from HMS
  58. hardware errors in the UI.
  59. """
  60. from backend.app.services.notification_service import notification_service
  61. async with async_session() as db:
  62. try:
  63. await notification_service.on_ai_failure_detection(
  64. printer_id=printer_id,
  65. printer_name=printer_name,
  66. task_name=task_name,
  67. confidence=score,
  68. action=action,
  69. db=db,
  70. image_data=frame,
  71. )
  72. except Exception as e:
  73. logger.error("Obico notify failed for printer %s: %s", printer_id, e)