main.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import asyncio
  2. import logging
  3. from robot_control.src.robot.controller import RobotController
  4. from robot_control.src.utils.config import ConfigParser, DefeederMagazineConfig
  5. from robot_control.src.vision.datamatrix import DataMatrixReader
  6. from robot_control.src.api.i2c_handler import I2C, MockI2C
  7. from robot_control.src.vendor.mcp3428 import MCP3428
  8. from robot_control.src.robot.pump_controller import PumpController
  9. from robot_control.src.api.gpio import PiGPIO, MockGPIO
  10. from robot_control.src.utils.logging import setup_logging
  11. from robot_control.src.robot.mag_distributor import MagDistributor
  12. #==============================================================
  13. import threading
  14. import status
  15. from flask_app import start_flask
  16. #==============================================================
  17. class LoaderSystem:
  18. def __init__(self):
  19. #==============================================================
  20. # Your original initialization code...
  21. self.logger = logging.getLogger(__name__)
  22. # Example: set status at init
  23. with status.status_lock:
  24. status.loader_status = "initialized"
  25. #==============================================================
  26. # Load configuration
  27. self.config = ConfigParser().config
  28. setup_logging(self.config)
  29. gpio_config = self.config.gpio
  30. # Initialize GPIO (mock or real based on debug flag)
  31. if gpio_config.debug:
  32. self.gpio = MockGPIO()
  33. else:
  34. self.gpio = PiGPIO(out_pins=[gpio_config.pump_pin, gpio_config.valve_pin, gpio_config.mag_dist_pos_dir_pin, gpio_config.mag_dist_pos_step_pin, gpio_config.mag_dist_pos_en_pin])
  35. self.logger = logging.getLogger(__name__)
  36. # Initialize camera system
  37. self.vision = DataMatrixReader(self.config.vision)
  38. self.logger.info("Initializing LoaderSystem")
  39. # Initialize I2C (mock or real based on debug flag)
  40. i2c_device_class = MCP3428 if not self.config.i2c.debug else MockI2C
  41. self.i2c = I2C(i2c_device_class)
  42. self.i2c.initialize()
  43. self.logger.info(f"I2C initialized with {i2c_device_class.__name__}")
  44. # Initialize pump controller
  45. self.pump_controller = PumpController(self.config, self.gpio)
  46. # Initialize magazine distributor
  47. self.mag_distributor = MagDistributor(self.config, self.gpio)
  48. # Create feeder and defeeder queues used for async handling of cell
  49. # Magazine -> MagDist -> Feeder -> Robot
  50. # Magazine <- MagDist <- Defeeder <- Robot
  51. self.feeder_queue: asyncio.Queue[int] = asyncio.Queue(self.config.feeder.max_num_cells)
  52. self.defeeder_queue: asyncio.Queue[DefeederMagazineConfig] = asyncio.Queue(self.config.defeeder.max_num_cells)
  53. # Pass all hardware interfaces to the controller
  54. self.controller = RobotController(
  55. self.config,
  56. self.gpio,
  57. self.i2c,
  58. self.vision,
  59. self.pump_controller,
  60. self.feeder_queue,
  61. self.defeeder_queue
  62. )
  63. async def run(self):
  64. """
  65. Main entry point for running the loader system.
  66. Starts all main async loops and ensures cleanup on exit.
  67. """
  68. #==============================================================
  69. # 1. Wait until homing is requested
  70. while True:
  71. with status.status_lock:
  72. if status.homing_done:
  73. break
  74. await asyncio.sleep(0.5)
  75. with status.status_lock:
  76. status.loader_status = "homing"
  77. # Perform homing before anything else
  78. await self.mag_distributor.home()
  79. with status.status_lock:
  80. status.loader_status = "homed"
  81. # 2. Wait until start is requested
  82. while True:
  83. with status.status_lock:
  84. if status.start_flag:
  85. break
  86. await asyncio.sleep(0.5)
  87. with status.status_lock:
  88. # Update the shared variable
  89. status.loader_status = "running"
  90. #==============================================================
  91. await self.controller.connect()
  92. try:
  93. await asyncio.gather(
  94. self._loader_loop(), # Main loader orchestration loop
  95. self._poll_i2c_channels(), # Poll I2C sensors
  96. self._queue_monitor_loop(), # Monitor feeder/defeeder queues
  97. )
  98. finally:
  99. self.cleanup()
  100. self.logger.info("Cleaning up resources...")
  101. #==============================================================
  102. with status.status_lock:
  103. status.loader_status = "completed"
  104. #==============================================================
  105. async def _poll_i2c_channels(self):
  106. """
  107. Periodically poll I2C channels for sensor readings.
  108. Handles pressure and end-effector state updates.
  109. """
  110. while True:
  111. try:
  112. readings = await self.i2c.read_channels([3, 4]) # channel 1 is handled on demand in Controller
  113. for channel, value in readings.items():
  114. self.logger.debug(f"Channel {channel} reading: {value}")
  115. if channel == 3: # Pressure reading
  116. self.pump_controller.handle_tank_reading(value)
  117. if channel == 4: # End-effector state
  118. state = self.pump_controller.check_endeffector_state(value)
  119. self.controller.set_suction_state(state)
  120. except Exception as e:
  121. self.logger.error(f"Error polling I2C channels: {str(e)}")
  122. await asyncio.sleep(1) # Poll every second
  123. async def _loader_loop(self):
  124. """
  125. Main loop for the loader system.
  126. Orchestrates cell preparation, slot filling, and measurement processing.
  127. """
  128. while True:
  129. await asyncio.sleep(0.1) # Avoid busy loop
  130. while True:
  131. # Prepare a cell in the feeder (returns True if a cell is ready)
  132. if not await self.controller.prepare_feeder_cell():
  133. break
  134. # Fill the next free slot (returns True if a cell was placed)
  135. if not await self.controller.fill_next_free_slot():
  136. break
  137. # Check for completed measurements and sort cells
  138. await self.controller.process_finished_measurements()
  139. async def _queue_monitor_loop(self):
  140. """
  141. Periodically checks feeder and defeeder queues and calls placeholder functions.
  142. Handles refilling the feeder and processing the defeeder.
  143. """
  144. while True:
  145. # Feeder: If queue below max, trigger refill placeholder
  146. if not self.feeder_queue.full():
  147. self.logger.info(f"Refilling feeder...")
  148. await asyncio.get_event_loop().run_in_executor(
  149. None, self.mag_distributor.mag_to_feeder
  150. )
  151. await self.feeder_queue.put(1) # Add to queue
  152. # Defeeder: If queue not empty, process next cell
  153. if not self.defeeder_queue.empty():
  154. magazine = await self.defeeder_queue.get() # Remove from queue
  155. self.logger.info(f"Processing defeeder to magazine {magazine.name}...")
  156. await asyncio.get_event_loop().run_in_executor(
  157. None, self.mag_distributor.defeeder_to_mag, magazine
  158. )
  159. await asyncio.sleep(2) # Adjust interval as needed
  160. def cleanup(self):
  161. """
  162. Cleanup hardware resources (GPIO, etc.).
  163. """
  164. self.gpio.cleanup() # Ensure PumpController cleans up gpio
  165. if __name__ == "__main__":
  166. #==============================================================
  167. # Start Flask in background
  168. flask_thread = threading.Thread(target=start_flask, daemon=True)
  169. logging.info("Flask app started in background...")
  170. #==============================================================
  171. loader_system = LoaderSystem()
  172. asyncio.run(loader_system.run())