mpo_table_saw.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. from data_models.mqtt_message import MqttMessage
  2. from pypdevs.DEVS import AtomicDEVS
  3. from pypdevs.infinity import INFINITY
  4. from dataclasses import dataclass
  5. from loguru import logger
  6. from data_models.workpiece import Workpiece
  7. from utils.get_timestamp import get_timestamp
  8. from utils.timed_phase_enum import TimedPhaseEnum
  9. class MPOTableSawPhase(TimedPhaseEnum):
  10. """ Steps in order, along with their timings """
  11. IDLE = ('IDLE', INFINITY)
  12. ROTATE_TO_FRONT = ('ROTATE_TO_FRONT', 2.697) # rotate table to face the gripper
  13. AWAIT_WP = ('AWAIT_WP', INFINITY) # await workpiece delivery by gripper
  14. ROTATE_TO_BACK = ('ROTATE_TO_BACK', 1.791) # rotate table to face saw (at back)
  15. SAW_RIGHT = ('SAW_RIGHT', 3.5)
  16. SAW_LEFT = ('SAW_LEFT', 3.5)
  17. ROTATE_TO_SIDE = ('ROTATE_TO_SIDE', 0.904) # turn table towards conveyor (to the right side)
  18. EJECT_WP = ('EJECT_WP', 0.630) # eject/push workpiece on conveyor
  19. @dataclass
  20. class MPOTableSawState:
  21. delta_t: float = INFINITY
  22. phase: MPOTableSawPhase = MPOTableSawPhase.IDLE
  23. workpiece: Workpiece | None = None
  24. visual_update_pending: bool = False
  25. def get_mpo_produced_msg() -> MqttMessage:
  26. """ Get the 'MPO_PRODUCED' mqtt message """
  27. message = MqttMessage()
  28. message.topic = "fl/mpo/ack"
  29. message.payload = {
  30. "code": 2,
  31. "ts": get_timestamp(),
  32. }
  33. return message
  34. class MPOTableSaw(AtomicDEVS):
  35. """ MPO rotating table and table saw """
  36. def __init__(self, name: str):
  37. super(MPOTableSaw, self).__init__(name)
  38. self.gripper_in = self.addInPort("gripper_in") # vacuum gripper which brings us workpiece
  39. self.gripper_out = self.addOutPort("gripper_out") # to acknowledge table turning to the gripper
  40. self.conveyor_out = self.addOutPort("conveyor_out") # conveyor belt output after we're done
  41. self.mqtt_out = self.addOutPort("mqtt_out")
  42. self.state = MPOTableSawState()
  43. def change_phase(self, new_phase: MPOTableSawPhase):
  44. """ Wrapper for changing the phase and time associated with it, helps with logging """
  45. self.state.phase = new_phase
  46. self.state.delta_t = new_phase.timing
  47. logger.trace(f"{type(self).__name__} '{self.name}' phase changed to {new_phase}")
  48. self.state.visual_update_pending = True
  49. def get_visual_update_data(self) -> MqttMessage:
  50. """ Get visual update data for the animation, contains the action taken and the duration of that action left """
  51. message = MqttMessage()
  52. message.topic = "visualization/mpo/table_saw"
  53. duration = self.state.delta_t
  54. if duration == INFINITY: duration = None
  55. message.payload = {
  56. "action": self.state.phase.value,
  57. "duration": duration, # should be equal to the timing of the phase
  58. "workpiece": self.state.workpiece.to_dict() if self.state.workpiece else None,
  59. }
  60. return message
  61. def get_status_message(self, use_next_phase=True) -> MqttMessage:
  62. phase = self.state.phase.next() if use_next_phase else self.state.phase
  63. active = 0 if (phase == MPOTableSawPhase.IDLE) else 1
  64. code = 2 if active == 1 else 1
  65. message = MqttMessage()
  66. message.topic = "f/i/state/mpo"
  67. message.payload = {
  68. "active": active,
  69. "code": code,
  70. "description": '',
  71. "station": 'mpo',
  72. "ts": get_timestamp(),
  73. }
  74. return message
  75. def extTransition(self, inputs):
  76. self.state.delta_t -= self.elapsed
  77. if not (self.state.phase == MPOTableSawPhase.IDLE or self.state.phase == MPOTableSawPhase.AWAIT_WP):
  78. logger.error(f"{type(self).__name__} '{self.name}' received input while not expecting: {inputs}")
  79. return self.state
  80. if self.gripper_in in inputs:
  81. # 2 cases: instruction to turn, or workpiece
  82. if self.state.phase == MPOTableSawPhase.IDLE:
  83. self.change_phase(MPOTableSawPhase.ROTATE_TO_FRONT)
  84. elif self.state.phase == MPOTableSawPhase.AWAIT_WP:
  85. wp = inputs[self.gripper_in][0]
  86. self.state.workpiece = wp
  87. self.change_phase(MPOTableSawPhase.ROTATE_TO_BACK)
  88. else:
  89. logger.error(f"{type(self).__name__} '{self.name}' received gripper input while not expecting: {inputs}")
  90. return self.state # important, return state
  91. def timeAdvance(self):
  92. if self.state.visual_update_pending:
  93. return 0.0
  94. return self.state.delta_t
  95. def outputFnc(self):
  96. if self.state.visual_update_pending:
  97. return {self.mqtt_out: [self.get_visual_update_data()]}
  98. if self.state.phase == MPOTableSawPhase.ROTATE_TO_FRONT:
  99. return {self.gripper_out: {}, self.mqtt_out: [self.get_status_message()]} # acknowledge rotation
  100. elif self.state.phase == MPOTableSawPhase.EJECT_WP:
  101. # output wp, and publish 'mpo produced' message to mqtt
  102. return {self.conveyor_out: [self.state.workpiece], self.mqtt_out: [get_mpo_produced_msg(), self.get_status_message()]}
  103. return {self.mqtt_out: [self.get_status_message()]}
  104. def intTransition(self):
  105. if self.state.visual_update_pending:
  106. self.state.visual_update_pending = False
  107. return self.state
  108. if self.state.phase == MPOTableSawPhase.EJECT_WP:
  109. self.state.workpiece = None # remove the workpiece we just output to the conveyor
  110. # Transition to next state
  111. self.change_phase(self.state.phase.next())
  112. return self.state