obico_actions.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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 (
  38. _is_script_plug,
  39. publish_plug_state,
  40. run_linked_scripts_after_automatic_switch,
  41. smart_plug_manager,
  42. )
  43. power_plug_off = None
  44. async with async_session() as db:
  45. plugs = await smart_plug_manager._get_plugs_for_printer(printer_id, db)
  46. for plug in plugs:
  47. # A script can only be run, not turned off; the "printer turns
  48. # off" ones run below, after the plug that feeds the printer.
  49. if not plug.enabled or _is_script_plug(plug):
  50. continue
  51. try:
  52. service = await smart_plug_manager.get_service_for_plug(plug, db)
  53. if await service.turn_off(plug):
  54. await publish_plug_state(
  55. plug.id, plug.name, plug.printer_id, on=False, ha_entity_id=plug.ha_entity_id
  56. )
  57. if plug.controls_printer_power:
  58. power_plug_off = plug
  59. logger.info("Obico action: turned off plug %s for printer %s", plug.name, printer_id)
  60. except Exception as e:
  61. logger.error("Obico action: failed to turn off plug %s: %s", plug.name, e)
  62. if power_plug_off is not None:
  63. # Once, however many plugs feed the printer.
  64. await run_linked_scripts_after_automatic_switch(printer_id, power_plug_off, "OFF")
  65. async def _notify(
  66. printer_id: int, printer_name: str, task_name: str, score: float, action: str, frame: bytes | None = None
  67. ) -> None:
  68. """Fire the AI Failure Detection notification (#1794).
  69. Routed to its own event in 0.2.5b1; previously rode the multiplexed
  70. on_printer_error toggle, which made it indistinguishable from HMS
  71. hardware errors in the UI.
  72. """
  73. from backend.app.services.notification_service import notification_service
  74. async with async_session() as db:
  75. try:
  76. await notification_service.on_ai_failure_detection(
  77. printer_id=printer_id,
  78. printer_name=printer_name,
  79. task_name=task_name,
  80. confidence=score,
  81. action=action,
  82. db=db,
  83. image_data=frame,
  84. )
  85. except Exception as e:
  86. logger.error("Obico notify failed for printer %s: %s", printer_id, e)