serial_manager.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import serial
  2. import serial.tools.list_ports
  3. import threading
  4. import time
  5. import logging
  6. # Configure logging
  7. logger = logging.getLogger(__name__)
  8. # Global state
  9. ser = None
  10. ser_port = None
  11. serial_lock = threading.RLock()
  12. IGNORE_PORTS = ['/dev/cu.debug-console', '/dev/cu.Bluetooth-Incoming-Port']
  13. # Device information
  14. arduino_table_name = None
  15. arduino_driver_type = 'Unknown'
  16. firmware_version = 'Unknown'
  17. def list_serial_ports():
  18. """Return a list of available serial ports."""
  19. ports = serial.tools.list_ports.comports()
  20. available_ports = [port.device for port in ports if port.device not in IGNORE_PORTS]
  21. logger.debug(f"Available serial ports: {available_ports}")
  22. return available_ports
  23. def startup_gcodes():
  24. ser.write(f"Report/Status=2".encode())
  25. ser.flush()
  26. while True:
  27. if ser.in_waiting > 0:
  28. response = ser.readline().decode().strip()
  29. logger.debug(f"Response: {response}")
  30. def connect_to_serial(port=None, baudrate=115200):
  31. """Automatically connect to the first available serial port or a specified port."""
  32. global ser, ser_port, arduino_table_name, arduino_driver_type, firmware_version
  33. try:
  34. if port is None:
  35. ports = list_serial_ports()
  36. if not ports:
  37. logger.warning("No serial port connected")
  38. return False
  39. port = ports[0] # Auto-select the first available port
  40. with serial_lock:
  41. if ser and ser.is_open:
  42. ser.close()
  43. ser = serial.Serial(port, baudrate, timeout=2)
  44. ser_port = port
  45. # startup_gcodes()
  46. home()
  47. logger.info(f"Connected to serial port: {port}")
  48. time.sleep(2) # Allow time for the connection to establish
  49. # Read initial startup messages from Arduino
  50. while ser.in_waiting > 0:
  51. line = ser.readline().decode().strip()
  52. logger.debug(f"Arduino: {line}")
  53. # Store the device details based on the expected messages
  54. if "Table:" in line:
  55. arduino_table_name = line.replace("Table: ", "").strip()
  56. elif "Drivers:" in line:
  57. arduino_driver_type = line.replace("Drivers: ", "").strip()
  58. elif "Version:" in line:
  59. firmware_version = line.replace("Version: ", "").strip()
  60. logger.info(f"Detected Table: {arduino_table_name or 'Unknown'}")
  61. logger.info(f"Detected Drivers: {arduino_driver_type or 'Unknown'}")
  62. return True
  63. except serial.SerialException as e:
  64. logger.error(f"Failed to connect to serial port {port}: {e}")
  65. ser_port = None
  66. logger.error("Max retries reached. Could not connect to a serial port.")
  67. return False
  68. def disconnect_serial():
  69. """Disconnect the current serial connection."""
  70. global ser, ser_port
  71. if ser and ser.is_open:
  72. logger.info("Disconnecting serial connection")
  73. ser.close()
  74. ser = None
  75. ser_port = None
  76. def restart_serial(port, baudrate=115200):
  77. """Restart the serial connection."""
  78. logger.info(f"Restarting serial connection on port {port}")
  79. disconnect_serial()
  80. return connect_to_serial(port, baudrate)
  81. def is_connected():
  82. """Check if serial connection is established and open."""
  83. return ser is not None and ser.is_open
  84. def get_port():
  85. """Get the current serial port."""
  86. return ser_port
  87. def send_grbl_coordinates(x, y, speed=600, timeout=2, retry_interval=1):
  88. """
  89. Send a G-code command to FluidNC and wait up to 2s for an 'ok' response.
  90. If no 'ok' is received, retry every 1 second until successful.
  91. """
  92. logger.debug(f"Sending G-code: X{x}, Y{y} at F{speed}")
  93. while True: # Keep retrying indefinitely until 'ok' is received
  94. with serial_lock:
  95. gcode = f"$J=G91 G21 X{x} Y{y} F{speed}"
  96. ser.write(f"{gcode}\n".encode())
  97. ser.flush()
  98. logger.debug(f"Sent command: {gcode}")
  99. start_time = time.time()
  100. while time.time() - start_time < timeout:
  101. if ser.in_waiting > 0:
  102. response = ser.readline().decode().strip()
  103. logger.debug(f"Response: {response}")
  104. if response.lower() == "ok":
  105. logger.debug("Command execution confirmed.")
  106. return # Exit function when 'ok' is received
  107. logger.warning(f"No 'ok' received for X{x}, Y{y}. Retrying in {retry_interval}s...")
  108. time.sleep(retry_interval) # Wait before retrying
  109. def home():
  110. logger.info("Homing")
  111. send_grbl_coordinates(0, -110, 1000)
  112. current_theta = current_rho = 0
  113. def check_idle():
  114. """Continuously check if the machine is in the 'Idle' state."""
  115. logger.info("Checking idle")
  116. while True:
  117. with serial_lock:
  118. ser.write('?'.encode()) # Send status query
  119. ser.flush() # Ensure it's sent immediately
  120. if ser.in_waiting > 0:
  121. response = ser.readline().decode().strip()
  122. logger.info(f"Response: {response}")
  123. if "Idle" in response:
  124. logger.info("Tabble is idle")
  125. return True # Exit function once 'Idle' is received
  126. time.sleep(1) # Wait before retrying
  127. def check_buffer():
  128. """Check the available planner and serial buffer in FluidNC."""
  129. logger.debug("Checking buffer availability")
  130. with serial_lock:
  131. ser.write('?'.encode()) # Send status query
  132. ser.flush() # Ensure it's sent immediately
  133. if ser.in_waiting > 0:
  134. response = ser.readline().decode().strip()
  135. logger.debug(f"Response: {response}")
  136. # Extract buffer values from the response (Format: <Idle|MPos:...|Bf:xx,yy|FS:...>)
  137. buffer_info = None
  138. if "|Bf:" in response:
  139. try:
  140. buffer_section = response.split("|Bf:")[1].split("|")[0]
  141. planner_buffer, serial_buffer = map(int, buffer_section.split(","))
  142. buffer_info = {"planner_buffer": planner_buffer, "serial_buffer": serial_buffer}
  143. except ValueError:
  144. logger.warning("Failed to parse buffer info from response")
  145. logger.debug(f"Buffer Left: {buffer_info}")
  146. return buffer_info
  147. return None # Return None if no buffer data is available