293 lines
9.7 KiB
Python
293 lines
9.7 KiB
Python
#!/usr/bin/python
|
|
|
|
###### Cyclone Host v1.0 ######
|
|
#
|
|
# DESCRIPTION:
|
|
# Controller for the Cyclone PCB Factory:
|
|
# "a 3D printable CNC machine for PCB manufacture" (http://www.thingiverse.com/thing:49484)
|
|
# This software has been tested with a Sanguinololu board running a modified Marlin firmware
|
|
# that supports the G30 probing G-code.
|
|
#
|
|
# AUTHOR:
|
|
# Carlosgs (http://carlosgs.es)
|
|
# LICENSE:
|
|
# Attribution - Share Alike - Creative Commons (http://creativecommons.org/licenses/by-sa/3.0/)
|
|
#
|
|
# DISCLAIMER:
|
|
# This software is provided "as is", and you use the software at your own risk. Under no
|
|
# circumstances shall Carlosgs be liable for direct, indirect, special, incidental, or
|
|
# consequential damages resulting from the use, misuse, or inability to use this software,
|
|
# even if Carlosgs has been advised of the possibility of such damages.
|
|
#
|
|
# CREDIT:
|
|
# This script was created using as a base:
|
|
# "Upload GCode to SpereBot" by Tgfuellner http://www.thingiverse.com/thing:9941 (CC-BY-SA)
|
|
# Please refer to http://carlosgs.es for more information on this probing method
|
|
#
|
|
# REQUISITE:
|
|
# http://pyserial.sourceforge.net
|
|
# Installation on Ubuntu: sudo aptitude install python-serial
|
|
#
|
|
######################################
|
|
|
|
# Begin modules
|
|
import sys
|
|
import serial
|
|
import time
|
|
from datetime import datetime
|
|
|
|
import random
|
|
|
|
from CycloneHost.helper import *
|
|
# End modules
|
|
|
|
# Begin configuration. It is overwritten when running connect()
|
|
BAUDRATE = 115200
|
|
DEVICE = "/dev/ttyUSB0"
|
|
Emulate = 0
|
|
# End configuration
|
|
|
|
lastDrillPos = [0,0,0]
|
|
|
|
seconds_wait = 0.5 # Delay used when re-trying to send/receive from the serial port [seconds]
|
|
serial_timeout = 5 # Timeout for the serial port [seconds]
|
|
|
|
OK_response = "ok" # First two characters of an OK response (case insensitive)
|
|
|
|
CNC_Machine = []
|
|
|
|
def connect(baudrate, device, emulate = 0):
|
|
global CNC_Machine, Emulate, BAUDRATE, DEVICE
|
|
BAUDRATE = baudrate
|
|
DEVICE = device
|
|
print("Connecting to Cyclone...")
|
|
if emulate == 0:
|
|
CNC_Machine = serial.Serial(DEVICE, BAUDRATE, timeout = serial_timeout)
|
|
Emulate = 0
|
|
else:
|
|
Emulate = 1
|
|
print("EMULATING MACHINE!")
|
|
print("Serial port opened, checking connection...")
|
|
time.sleep(2)
|
|
checkConnection()
|
|
print("Connected!")
|
|
|
|
def flushRecvBuffer(): # We could also use flushInput(), but showing the data that is being discarded is useful for debugging
|
|
if Emulate:
|
|
return
|
|
while CNC_Machine.inWaiting() > 0:
|
|
response = CNC_Machine.readline()
|
|
# if response != '': print("IGNO: " + response)
|
|
time.sleep(seconds_wait) # Wait some milliseconds between attempts
|
|
|
|
def sendLine(line):
|
|
if Emulate == 0:
|
|
flushRecvBuffer()
|
|
CNC_Machine.write(line)
|
|
# print("SENT: " + line)
|
|
|
|
def recvLine():
|
|
if Emulate:
|
|
response = "ok\n" # Asume OK
|
|
else:
|
|
response = CNC_Machine.readline()
|
|
# if response != '': print("RECV: " + response)
|
|
# else: print("RECV: Receive timed out!")
|
|
return response
|
|
|
|
def recvOK():
|
|
response = recvLine()
|
|
if response[:2].lower() == OK_response.lower():
|
|
return 1
|
|
return 0
|
|
|
|
def waitForOK(command="",timeoutResend=30): # This is a blocking function
|
|
# print("Waiting for confirmation")
|
|
i = 0
|
|
cmnd = command[:3].lower()
|
|
timeoutResend = float(timeoutResend)
|
|
#timeoutResend = 5.0 # Resend command every 5 seconds, error recovery
|
|
#if cmnd == "g28": timeoutResend = 60.0 # Homing moves will take more than 5 seconds
|
|
i_timeout = int(timeoutResend/float(serial_timeout+seconds_wait))
|
|
# print("i_timeout = " + str(i_timeout))
|
|
while recvOK() != 1:
|
|
print(" Checking again... timeout: " + str(i_timeout) )
|
|
time.sleep(seconds_wait) # Wait some milliseconds between attempts
|
|
if cmnd != "g30" and i >= i_timeout: # WARNING: Commands that take >5s may have problems here!
|
|
print(" WATCHOUT! RESENDING: " + str(command) )
|
|
sendLine(command)
|
|
i = 0
|
|
else:
|
|
i = i + 1
|
|
|
|
def sendCommand(command,timeoutResend=15): # Send command and wait for OK
|
|
sendLine(command)
|
|
waitForOK(command,timeoutResend)
|
|
|
|
def checkConnection():
|
|
# print("Checking the connection...")
|
|
sendLine("G21\n") # We check the connection setting millimiters as the unit and waiting for the OK response
|
|
time.sleep(0.5)
|
|
while recvOK() != 1:
|
|
sendLine("G21\n")
|
|
time.sleep(seconds_wait) # Wait some milliseconds between attempts
|
|
|
|
def homeZXY():
|
|
global lastDrillPos
|
|
print("Homing all axis...")
|
|
timeoutResend=30
|
|
sendCommand("G28 Z0\n",timeoutResend) # move Z to min endstop
|
|
sendCommand("G28 X0\n",timeoutResend) # move X to min endstop
|
|
sendCommand("G28 Y0\n",timeoutResend) # move Y to min endstop
|
|
if Emulate:
|
|
time.sleep(2)
|
|
lastDrillPos = [0,0,0]
|
|
print("Done homing")
|
|
|
|
def moveXYZ(X, Y, Z, F):
|
|
global lastDrillPos
|
|
# print("Moving to:")
|
|
if F <= 0:
|
|
print("ERROR: F <= 0")
|
|
sendCommand("G1 X"+floats(X)+" Y"+floats(Y)+" Z"+floats(Z)+" F"+floats(F)+"\n")
|
|
if Emulate:
|
|
dist = ((X-lastDrillPos[0])**2+(Y-lastDrillPos[1])**2+(Z-lastDrillPos[2])**2)**0.5 # [mm]
|
|
speed = float(F)/60.0 # [mm/s]
|
|
time.sleep(float(dist)/speed)
|
|
lastDrillPos = [X,Y,Z]
|
|
|
|
def moveXY(X, Y, F):
|
|
global lastDrillPos
|
|
# print("Moving to:")
|
|
if F <= 0:
|
|
print("ERROR: F <= 0")
|
|
sendCommand("G1 X"+floats(X)+" Y"+floats(Y)+" F"+floats(F)+"\n")
|
|
if Emulate:
|
|
dist = ((X-lastDrillPos[0])**2+(Y-lastDrillPos[1])**2)**0.5 # [mm]
|
|
speed = float(F)/60.0 # [mm/s]
|
|
time.sleep(float(dist)/speed)
|
|
lastDrillPos = [X,Y,lastDrillPos[2]]
|
|
|
|
def moveZ(Z, F):
|
|
global lastDrillPos
|
|
# print("Moving Z absolute:")
|
|
if F <= 0:
|
|
print("ERROR: F <= 0")
|
|
sendCommand("G1 Z"+floats(Z)+" F"+floats(F)+"\n")
|
|
if Emulate:
|
|
dist = abs(Z-lastDrillPos[2]) # [mm]
|
|
speed = float(F)/60.0 # [mm/s]
|
|
time.sleep(float(dist)/speed)
|
|
lastDrillPos = [lastDrillPos[0],lastDrillPos[1],Z]
|
|
|
|
def moveZrel(Z, F):
|
|
global lastDrillPos
|
|
# print("Moving Z relative:")
|
|
if F <= 0:
|
|
print("ERROR: F <= 0")
|
|
sendCommand("G91\n") # Set relative positioning
|
|
sendCommand("G1 Z"+floats(Z)+" F"+floats(F)+"\n")
|
|
if Emulate:
|
|
dist = abs(Z) # [mm]
|
|
speed = float(F)/60.0 # [mm/s]
|
|
time.sleep(float(dist)/speed)
|
|
lastDrillPos = [lastDrillPos[0],lastDrillPos[1],lastDrillPos[2]+Z] # Relative movement
|
|
sendCommand("G90\n") # Set absolute positioning
|
|
|
|
def moveZrelSafe(Z, F):
|
|
if F <= 0:
|
|
print("ERROR: F <= 0")
|
|
print("Moving Z " + str(Z) + "mm safely...")
|
|
sendCommand("M121\n") # Enable endstops (for protection! usually it should **NOT** hit neither the endstop nor the PCB)
|
|
moveZrel(Z, F)
|
|
dist = abs(Z) # [mm]
|
|
speed = float(F)/60.0 # [mm/s]
|
|
wait = float(dist)/speed # [s]
|
|
time.sleep(wait) # Wait for the movement to finish, this way the M121 command is effective
|
|
print(" Done moving Z!")
|
|
sendCommand("M120\n") # Disable endstops (we only use them for homing)
|
|
|
|
def probeZ():
|
|
print("Probing Z")
|
|
sendLine("G30\n") # Launch probe command
|
|
response = recvLine() # Read the response, it is a variable run time so we may need to make multiple attempts
|
|
while response == '':
|
|
#print(".")
|
|
time.sleep(seconds_wait) # Wait some milliseconds between attempts
|
|
response = recvLine()
|
|
if Emulate:
|
|
response = "ok Z"+str(random.gauss(0, 0.25))+"\n" # Generate random measure
|
|
response_vals = response.split() # Split the response (i.e. "ok Z1.23")
|
|
if response_vals[0][:2].lower() == OK_response.lower():
|
|
Zres = response_vals[1][2:] # Ignore the "Z:" and read the coordinate value
|
|
print("Result is Z = " + str(Zres))
|
|
return float(Zres)
|
|
return 400 # Error case, don't worry: it has never happened :)
|
|
|
|
def close():
|
|
# IMPORTANT: Before closing the serial port we must make a blocking move in order to wait for all the buffered commands to end
|
|
sendCommand("G28 Z0\n") # move Z to min endstop
|
|
if Emulate == 0:
|
|
CNC_Machine.close() # Close the serial port connection
|
|
|
|
def probeGrid(grid_origin, grid_len, grid_N, Zlift, F_fastMove, F_slowMove):
|
|
grid_origin_X = float(grid_origin[0]) # Initial point of the grid [mm]
|
|
grid_origin_Y = float(grid_origin[1])
|
|
|
|
grid_len_X = float(grid_len[0]) # Distance to probe [mm]
|
|
grid_len_Y = float(grid_len[1])
|
|
|
|
grid_N_X = int(grid_N[0]) # Number of points
|
|
grid_N_Y = int(grid_N[1])
|
|
|
|
Z_probing_lift = float(Zlift) # lift between Z probings [mm]
|
|
|
|
grid_inc_X = grid_len_X/float(grid_N_X-1) # [mm]
|
|
grid_inc_Y = grid_len_Y/float(grid_N_Y-1)
|
|
|
|
x_points = [ float(x_i)*grid_inc_X + grid_origin_X for x_i in range(grid_N_X) ] # Calculate X coordinates
|
|
y_points = [ float(y_i)*grid_inc_Y + grid_origin_Y for y_i in range(grid_N_Y) ] # Calculate X coordinates
|
|
|
|
probe_result = [ [ 0 for j in range(grid_N_X) ] for i in range(grid_N_Y) ]
|
|
|
|
# Show our grid (initialised as zeros)
|
|
for row in probe_result:
|
|
print(str(row))
|
|
|
|
print("Probing begins!")
|
|
print("WARNING: Keep an eye on the machine, unplug if something goes wrong!")
|
|
beginTime = datetime.now() # Store current time in a variable, will be used to measure duration of the probing
|
|
|
|
# Move to grid's origin
|
|
moveXY(grid_origin_X, grid_origin_Y, F_fastMove)
|
|
|
|
for x_i in range(grid_N_X): # For each point on the grid...
|
|
x_val = float(x_i)*grid_inc_X + grid_origin_X # Calculate X coordinate
|
|
optimal_range = range(grid_N_Y)
|
|
if isOdd(x_i): # This optimises a bit the probing path
|
|
optimal_range = reversed(optimal_range)
|
|
for y_i in optimal_range:
|
|
y_val = float(y_i)*grid_inc_Y + grid_origin_Y # Calculate Y coordinate
|
|
moveXY(x_val, y_val, F_fastMove) # Move to position
|
|
probe_result[y_i][x_i] = probeZ() # Do the Z probing
|
|
moveZrel(Z_probing_lift, F_slowMove) # Lift the probe
|
|
|
|
# Once we have all the points, we set the origin as (0,0) and offset the rest of values
|
|
Z_offset = probe_result[0][0]
|
|
print("The origin Z height is " + str(Z_offset))
|
|
probe_result = [[elem - Z_offset for elem in row] for row in probe_result]
|
|
|
|
# Return to the grid's origin
|
|
moveZrel(10, F_slowMove) # Lift Z
|
|
moveXY(grid_origin_X, grid_origin_Y, F_fastMove) # Move to grid's origin
|
|
|
|
duration = datetime.now() - beginTime
|
|
print("Probing duration:" + str(duration))
|
|
duration_s = duration.total_seconds()
|
|
|
|
return (x_points, y_points, probe_result, Z_offset, duration_s)
|
|
|
|
|
|
|
|
|