Getting actual spindle speed from "M3 R1" Mcode
-
@chrishamm I switched:
intercept_connection.resolve_code(MessageType.Success, "ODrive RPM set to 0")
for the M5 command to:
intercept_connection.ignore_code()
and now the state.restorePoints[0].spindleSpeeds[0] is getting set. However when I try to resume my M3 code is getting executed but my spindle does not change speed. Then the entire tool path is skilled up until my next tool change. Let me get a full run's debug output for my script and ill post it.
-
Sorry I was mistaken. My code is not getting executed. It is still throwing this error:
Error: {state.restorePoints[0].spindleSpeeds[0]}unknown value 'spindleSpeeds^' of resume.g
to the console and my code is NOT getting executed.
-
@CthulhuLabs currently the state saved in a restore point does not include the spindle speed(s). So I suggest that in pause.g you save the spindle speed in a global variable as @chrishamm suggested.
-
@dc42 ahhh I saw it in the object model:
that is a good alternative though. So I should do something like:
set global.PauseRPM = spindles[0].active
-
Well "set global.PauseRPM = spindles[0].active" is evaluating to 0 so something is not right there. Also when I resume it is moving to the position it paused at which is good but it is not completing the tool path. Instead it is prompting me to change tools and continues on with the next tool path.
-
I commented out all the code in pause.g and resume.g related to the spindle and it is still skipping the rest of the tool path on resume.
-
I fixed the toolpath issue. My CAD software post processor was generating tool paths like this:
G1Z-1.016F304.8 X-163.747Y-122.162F1524.0 X-162.316Y-123.371 X-160.765Y-124.590 X-159.269Y-125.682 X-157.679Y-126.760 X-156.063Y-127.774 X-154.436Y-128.716 X-152.260Y-129.862 X-152.125Y-129.929 X-149.886Y-130.974 X-147.582Y-131.923 X-145.238Y-132.763 X-145.096Y-132.810 X-142.726Y-133.532 X-140.273Y-134.157
I switched it to generate tool paths like this:
G1Z-1.016F304.8 G1X-163.747Y-122.162F1524.0 G1X-162.316Y-123.371 G1X-160.765Y-124.590 G1X-159.269Y-125.682 G1X-157.679Y-126.760 G1X-156.063Y-127.774 G1X-154.436Y-128.716 G1X-152.260Y-129.862 G1X-152.125Y-129.929 G1X-149.886Y-130.974 G1X-147.582Y-131.923 G1X-145.238Y-132.763 G1X-145.096Y-132.810 G1X-142.726Y-133.532 G1X-140.273Y-134.157
I guess resume doesn't know how to pick up in the middle of a G1/0 command.
As for the M5 and M3 I think I am going to make my dsf-python script save the spindle speed to an internal LastRPM variable on M5 and restore that when it gets an M3 R1. Ill let you know if that works.
-
That did it. @chrishamm @dc42 thank you for your help.
-
Incase someone finds this thread and is looking to do something similar here is my dsf-python script for controlling my ODrive Spindle:
#!/usr/bin/env python3 """ Script for controlling an ODrive powered BDLC spindle """ import subprocess import traceback from dsf.connections import InterceptConnection, InterceptionMode, CommandConnection from dsf.commands.code import CodeType from dsf.commands.generic import evaluate_expression from dsf.commands.code_channel import CodeChannel from dsf.object_model import MessageType import serial from time import sleep port = '/dev/ttyACM0' # serial port baud = 115200 # baudrate timeout = 1 # read timeout LastRPS = 0 # What the spindle speed was in RPS before the last M5 was called # function to return the serial port or none if it fails def getSerial(portname,baud,to): try: return serial.Serial(port=portname,baudrate=baud,timeout=to) except: return None # function to set the ODrive rps (Rotations Per Second#) def setRPS(rps): attempt = True while attempt: ser = getSerial(port,baud,timeout) if (ser is not None): # get the current state of the ODrive ser.write(b'r axis0.current_state\n') axisState = ser.readline().decode().strip() # get the currently set velocity in RPS ser.write(b'r axis0.controller.input_vel\n') reqVel = ser.readline().decode().strip() # check if ODrive is ideal and the desired rps is not 0 if (( axisState == "1" ) and ( rps != 0 )): # if so set the ODrive state to 8 (CLOSED LOOP CONTROL) ser.write(b'w axis0.requested_state 8\n') ser.flush() # check if ODrive is not ideal and the desired rps is 0 if (( axisState != "1" ) and ( rps == 0)): # if so set the ODrive state to 1 (IDLE) ser.write(b'w axis0.requested_state 1\n') ser.flush() # check if the current set velocity is not the desired rps if ( float(reqVel) != rps ): # if so set the velocity to rps command = str.encode('w axis0.controller.input_vel %.6f\n' %rps ) ser.write(command) ser.flush() ser.close() attempt = False else: sleep(0.1) # function to get the ODrive rps (Rotations Per Second#) def getRPS(): attempt = True rps = 0 while attempt: ser = getSerial(port,baud,timeout) if (ser is not None): # get the currently set velocity in RPS ser.write(b'r axis0.controller.input_vel\n') rps = float(ser.readline().decode().strip()) ser.close() attempt = False else: sleep(0.1) return rps def getRRFVar( dsf_variable, channel): command_connection = CommandConnection(debug=True) command_connection.connect() try: res = command_connection.perform_command(evaluate_expression( channel, dsf_variable )) result = res.result print(f"Evaluated expression: {result}") finally: command_connection.close() return result def start_intercept(): filters = ["M3","M4","M5"] intercept_connection = InterceptConnection(InterceptionMode.PRE, filters=filters, debug=True) while True: intercept_connection.connect() try: while True: # Wait for a code to arrive cde = intercept_connection.receive_code() # Check for the type of the code if cde.type == CodeType.MCode and cde.majorNumber in [3,4,5]: # --------------- BEGIN FLUSH --------------------- # Flushing is only necessary if the action below needs to be in sync with the machine # at this point in the GCode stream. Otherwise it can an should be skipped # Flush the code's channel to be sure we are being in sync with the machine success = intercept_connection.flush(cde.channel) # Flushing failed so we need to cancel our code if not success: print("Flush failed") intercept_connection.cancel_code() continue # -------------- END FLUSH ------------------------ # M3 if cde.majorNumber == 3 : if cde.parameter("R") : if LastRPS < 0 : # this is M3 LastRPS should be positve setRPS( LastRPS * -1 ) else : setRPS( LastRPS ) intercept_connection.resolve_code(MessageType.Success, "ODrive resuming RPM " + str( LastRPS * 60 ) ) else : if cde.parameter("S") is None : # Let DCS know there was an ERROR intercept_connection.resolve_code(MessageType.Error, "M3 must include an S parameter") else : rpm = cde.parameter("S").string_value if cde.parameter("S").is_expression : rpm = getRRFVar( rpm, cde.channel ) setRPS( float(int(rpm) / 60) ) # Resolve it so that DCS knows we took care of it intercept_connection.resolve_code(MessageType.Success, "ODrive RPM set to " + str(rpm) ) # M4 if cde.majorNumber == 4: if cde.parameter("R") : if LastRPS > 0 : # this is M4 LastRPS should be negative setRPS( LastRPS * -1 ) else : setRPS( LastRPS ) intercept_connection.resolve_code(MessageType.Success, "ODrive resuming RPM " + str( LastRPS * 60 ) ) else : if cde.parameter("S") is None : # Let DCS know there was an ERROR intercept_connection.resolve_code(MessageType.Error, "M4 must include an S parameter") else : rpm = cde.parameter("S").string_value if cde.parameter("S").is_expression : rpm = getRRFVar( rpm, cde.channel ) setRPS( -( float(int(rpm) / 60) ) ) # Resolve it so that DCS knows we took care of it intercept_connection.resolve_code(MessageType.Success, "Ordive RPM set to -" + str(rpm) ) # M5 if cde.majorNumber == 5: LastRPS = getRPS() setRPS( 0 ) # Resolve it so that DCS knows we took care of it intercept_connection.resolve_code(MessageType.Success, "ODrive RPM set to 0") else: # We did not handle it so we ignore it and it will be continued to be processed intercept_connection.ignore_code() except Exception as e: print("Closing connection: ", e) traceback.print_exc() intercept_connection.close() sleep(5) if __name__ == "__main__": start_intercept()
-
@CthulhuLabs said in Getting actual spindle speed from "M3 R1" Mcode:
I guess resume doesn't know how to pick up in the middle of a G1/0 command.
Yes, that will be the reason. When you pause the print, RRF may cancel some moves that are in the movement queue. When you resume, RRF rewinds the input file back to the offset in that file of the first movement command that was cancelled. However, RRF doesn't know the context of that move, for example whether it is following on from a G0, G1, G2, G3 or some other command.
I have raised https://github.com/Duet3D/RepRapFirmware/issues/871.
-
@dc42 Would you like me to send you the original GCode file I was using before I altered the post processor? Each tool path is just one giant G1 Gcode.