Duet3D Logo Duet3D
    • Tags
    • Documentation
    • Order
    • Register
    • Login
    1. Home
    2. CthulhuLabs
    • Profile
    • Following 0
    • Followers 1
    • Topics 30
    • Posts 125
    • Best 28
    • Controversial 0
    • Groups 0

    CthulhuLabs

    @CthulhuLabs

    56
    Reputation
    22
    Profile views
    125
    Posts
    1
    Followers
    0
    Following
    Joined Last Online

    CthulhuLabs Unfollow Follow

    Best posts made by CthulhuLabs

    • Surfacing Macro

      I wrote a Macro for surfacing stock and figured I should share:

      ; Constants
      var Debug = false;
      var ProbeThickness = 15.5;
      var CoordSystem	= 20;
      var StepOver = 10;
      var DepthOfCut = 0.2;
      var FeedRate = 8000;
      var ZClearanceHeight = 25;
      
      ; Variables
      var StartX = 0;			; The X starting position
      var StartY = 0;			; The Y starting position
      var EndX = 0;			; The X ending position
      var EndY = 0;			; The Y ending position
      var YDir = 0;			; The direction to go in Y
      var YDist = 0;			; The distance to go in Y
      var YPos = 0;			; The position in Y to move to
      var YDistLeft = 0;		; How much we have left to move in Y
      var ZDepth = 0;			; How far down we should be in Z
      var XSorE = "End"		; Whether X should move towards the start or the end position
      
      ; If the printer hasn't been homed, home it
      if !move.axes[0].homed || !move.axes[1].homed || !move.axes[2].homed
      	G28
      
      ; Adjust probe for low speed probing
      M558 K0 P8 C"!io3.in" H20 F120 T300					; Z probe number - Z probe switch type - probe recovery 1s probe speed and travel speed
      G31 K0 P500 X0 Y0 Z0								; Set trigger value - set offset and trigger height - Z probe number
      
      M291 P"Insert your surfacing bit into your spindle and position it above the probe plate" R"Probing" S3 X1 Y1 Z1
      
      ; Set bit above center of probe
      G91																			; relative positioning
      
      ; Probe Z component
      G38.2 Z{move.axes[2].userPosition - (var.ProbeThickness + 2.25)}			; seek until the probe circuit is closed Z-axis 25 mm
      G0 Z{var.ZClearanceHeight}													; rapid move Z axis 5 mm
      
      G10 P1 L{var.CoordSystem} Z{var.ProbeThickness + var.ZClearanceHeight}		; store relative probe offset for coordinates system 1
      
      G90																			; Absolute positioning
      
      M291 P"Move to the starting X Y position for surfacing" R"Start Position" S3 X1 Y1 Z0
      set var.StartX = move.axes[0].userPosition;
      set var.StartY = move.axes[1].userPosition;
      
      if var.Debug
      	M118 P0 S{"StartX = " ^ var.StartX} L2
      	M118 P0 S{"StartY = " ^ var.StartY} L2
      
      M291 P"Move to the ending X Y position for surfacing" R"End Position" S3 X1 Y1 Z0
      set var.EndX = move.axes[0].userPosition;
      set var.EndY = move.axes[1].userPosition;
      
      if var.Debug
      	M118 P0 S{"EndX = " ^ var.EndX} L2
      	M118 P0 S{"EndY = " ^ var.EndY} L2
      
      M291 P"Are you ready to begin surfacing?" S3 X0 Y0 Z0
      
      ; Figure out which direction in Y we are going
      if var.EndY < var.StartY
      	set var.YDir = -1;
      elif var.EndY > var.StartY
      	set var.YDir = 1;
      else
      	set var.YDir = 0;
      
      if var.Debug
      	M118 P0 S{"YDir = " ^ var.YDir} L2
      
      
      set var.YDist = {abs(var.StartY - var.EndY)};
      if var.Debug
      	M118 P0 S{"YDist = " ^ var.YDist} L2
      
      ; Begin surfacing loop
      while true
      	if var.Debug
      		M118 P0 S"Begin Layer Loop" L2;
      
      	set var.ZDepth = {var.ZDepth - var.DepthOfCut};
      	set var.YDistLeft = {var.YDist};
      
      	; go to the start X and Y position then down to Z Depth
      	if var.Debug
      		M118 P0 S{"Move to start X" ^ var.StartX ^ ",Y" ^ var.StartY} L2
      	G0 X{var.StartX} Y{var.StartY}
      	if var.Debug
      		M118 P0 S{"Move to start Z" ^ var.ZDepth} L2
      	G1 Z{var.ZDepth}
      
      	; we should be at the Start X position so move to the End X position
      	if var.Debug
      		M118 P0 S{"Move to X" ^ var.EndX} L2
      	G1 X{var.EndX} F{var.FeedRate}
      	set var.XSorE = "Start"
      
      	while var.YDistLeft > var.StepOver
      		if var.Debug
      			M118 P0 S"Begin Y Loop" L2
      
      		; move in Y by StepOver
      		set var.YPos = {move.axes[1].userPosition + (var.StepOver * var.YDir)}
      		if var.Debug
      			M118 P0 S{"Move to Y" ^ var.YPos} L2
      		G1 Y{var.YPos} F{var.FeedRate}
      		set var.YDistLeft = {var.YDistLeft - var.StepOver}
      
      		if var.XSorE == "Start"
      			; move X to the start position
      			if var.Debug
      				M118 P0 S{"Move to X" ^ var.StartX} L2
      			G1 X{var.StartX} F{var.FeedRate}
      			set var.XSorE = "End"
      		else
      			; move X to the end position
      			if var.Debug
      				M118 P0 S{"Move to X" ^ var.EndX} L2
      			G1 X{var.EndX} F{var.FeedRate}
      			set var.XSorE = "Start"
      
      	; move in Y the rest of the distance left
      	set var.YPos = {move.axes[1].userPosition + (var.YDistLeft * var.YDir)}
      	if var.Debug
      		M118 P0 S{"Move to Y" ^ var.YPos} L2
      	G1 Y{var.YPos} F{var.FeedRate}
      
      	; one last move in X
      	if var.XSorE == "Start"
      		; move X to the start position
      		if var.Debug
      			M118 P0 S{"Move to X" ^ var.StartX} L2
      		G1 X{var.StartX} F{var.FeedRate}
      		set var.XSorE = "End"
      	else
      		; move X to the end position
      		if var.Debug
      			M118 P0 S{"Move to X" ^ var.EndX} L2
      		G1 X{var.EndX} F{var.FeedRate}
      		set var.XSorE = "Start"
      
      	G0 Z{var.ZClearanceHeight}					; get out of the way
      	M400 										; wait for the movement buffer to catch up
      	M291 P"Surface another layer?" S3 X0 Y0 Z0	; then prompt the user if they want to do another layer
      
      

      It is working really well so far, but I cannot promise it is perfect. It is designed to be used with a Z Probe like this:

      https://openbuildspartstore.com/xyz-touch-probe-plus/

      Once the top surface of the piece is determined it will then have you jog to the X / Y start position and press Ok. Next it will have you jog to the X / Y end position and press Ok. Then it will ask you if you are ready. Press Ok and it will begin surfacing your part. Once it has made a pass it will prompt you if you want to surface another layer. If you say Ok it will move back to the X / Y start position and go down in Z by the DepthOfCut variable and continue surfacing. It will keep going till you cancel at the end of a pass.

      There are several constants you can set at the top:

      Debug - Determines if it should spit out the M118 debug output. (Will probably switch this over to echos to a file once 3.4 is finally released)
      ProbeThickness - How thick your XYZ probe is.
      CoordSystem - The coordinate system to save the top of your piece in as Z0
      StepOver - The step over in MM for each pass of your cutter
      DepthOfCut - The depth of cut that will be taken off with each pass
      FeedRate - The feed rate used when cutting
      ZClearanceHeight - How high to retract in Z when moving to the Start Point

      Would love your feedback.

      EDIT: So I forgot to mention that the Start and End points define two opposite corners of the rectangle to be surfaced.

      posted in CNC
      CthulhuLabsundefined
      CthulhuLabs
    • Split Firmware Version In ObjectModel

      Please add a way to get the Major, Minor, and Point info on a boards firmware version in the object model. I am in the process of updating my Surfacing Macro ( https://forum.duet3d.com/topic/26762/surfacing-macro ) to take advantage of 3.5.0's new M291 options for user input. I want to be able to check the firmware version to make sure the Macro will work properly and if not provide feedback to the user. The issue is that:

      echo {boards[0].firmwareVersion}   ; currently returns "3.4.5"
      

      returns the version number as a string like "3.4.5" (I have not update to the beta yet). Without basic string functions there is no way to split that apart and check if the version is equal to or greater than "3.5.0". If however there was:

      echo {boards[0].firmwareVersion}   ; returns "3.4.5"
      echo {boards[0].firmwareVersion.major}   ; returns "3"
      echo {boards[0].firmwareVersion.minor}   ; returns "4"
      echo {boards[0].firmwareVersion.point}   ; returns "5"
      

      I could then do something like:

      if boards[0].firmwareVersion.major == 3 && boards[0].firmwareVersion.minor >= 5
              ; Do all the things
              ...
      else
              M291 P"Sorry but your firmware version is not compatible with this Macro" R"Error" S2
      
      posted in Firmware wishlist
      CthulhuLabsundefined
      CthulhuLabs
    • DRO half function with a 1 line macro

      So I recently got an edge finder for my CNC and wanted to replicate the half function found on many DROs I have worked with in the past to easily find the center of a part. After scratching my head for a bit and playing around I realized it could be done with one Meta Gcode line in a Macro file. Here is the X version of it:

      ; Half X
      G10 L2 P1 X{move.axes[0].workplaceOffsets[0] + (move.axes[0].userPosition / 2) }
      

      To use this I touch off on one side of the part and set that as zero for that axis. Next I go to the other side of the part and touch off. Then I run the above macro. This sets the new work offset half way between the old zero and the current position.

      For completeness here is the Y version:

      ; Half Y
      G10 L2 P1 Y{move.axes[1].workplaceOffsets[0] + (move.axes[1].userPosition / 2) }
      

      I figured I would share as this has proved to be super useful.

      posted in CNC
      CthulhuLabsundefined
      CthulhuLabs
    • RE: CNC Tool Offsets Confusion

      Here is my finished Macro. Hopefully will be helpful for others. I am not entirely sure why certain things behave a certain way though. This Macro does allow me to reliably change tools and still position the base of the tool a the work piece zero position.

      ; If the printer hasn't been homed, home it
      if !move.axes[0].homed || !move.axes[1].homed || !move.axes[2].homed
      	G28
      
      ; Clear the tool Z offset
      G10 P0 L1 Z0
      
      G53 G0 Z0											; Go to 0 Z in Machine Co-ordanates
      
      var ZBeginPos = move.axes[2].userPosition			; Save the Z starting position (might not be 0)
      
      G53 G0 X{global.BtStrXLoc} Y{global.BtStrYLoc}		; Move to BitSetter location
      G91													; relative positioning
      
      ; Adjust probe for high speed probing
      M558 K0 P8 C"!io3.in" H20 F600 T300					; Z probe number - Z probe switch type - probe recovery 1s probe speed and travel speed
      G31 K0 P500 X0 Y0 Z0								; Set trigger value - set offset and trigger height - Z probe number
      
      ; Seak down rapidly till we contact the probe
      G38.2 Z{move.axes[2].userPosition + global.BtStrZTrig}
      
      ; Move up 2mm
      G0 Z2
      
      ; Adjust probe for low speed probing
      M558 K0 P8 C"!io3.in" H20 F120 T300					; Z probe number - Z probe switch type - probe recovery 1s probe speed and travel speed
      G31 K0 P500 X0 Y0 Z0								; Set trigger value - set offset and trigger height - Z probe number
      
      ; Seak down slowly till we contact the probe
      G38.2 Z{move.axes[2].userPosition - 3}
      
      var ZEndPos = move.axes[2].userPosition				; Save the Z end position
      
      ; set the tool offset
      G10 P0 L1 Z{global.BtStrZTrig - (var.ZEndPos - var.ZBeginPos)}
      
      G0 Z5												; rapid move Z axis up 5mm
      
      posted in CNC
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Opinions on "FYSETC" ... politely, please.

      @breed said in Opinions on "FYSETC" ... politely, please.:

      I wish sometimes that duet made a board with stepsticks even if it had limited driver support, as I'd rather support duet than a Chinese company.

      I am curious why you need this functionality. When I first got into 3D printing I was using a RAMPS 1.4 board and then went to a RADDS board. I burned out 8x DRV8825 over the course of a year and a half of use. I thought that having built in drivers was a bad idea because everyone seemed to blow them. After a ton of research though I learned that most of the time the blow because of excessive heat or mistakes that can be avoided with improved circuit design. I eventually moved to a Duet 2 Wifi board. Since then I have not burned a single stepper driver even doing the same dumb mistakes as I did in the past. I did have one stepper driver die on my Duet 2 Wifi, but that was on a brand new board and Filastruder immediately RMAed the board once it was confirmed to be a bad board. The Duet boards properly sink the heat away from the stepper driver chips and have all of the necessary protection circuits on them to prevent dumb mistakes. Sure there are still ways to burn one out, but it takes serious effort.

      posted in General Discussion
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Pre crimped cables

      @markuskruse I completely agree with you. Crimping Cables is the biggest pain in the ass. The reason though is because you literally need three hands. It wasn't till I learned this that I was able to do it.

      Get a set of these:

      https://www.amazon.com/ProsKit-900-015-Helping-Hands-Soldering/dp/B002PIA6Z4

      or you can just do what I do and use an alligator clip.

      Here are the steps I use with pictures.

      You will need a set of helping hands or an alligator clip, wire strippers, needle nose pliers, and a crimping tool.

      Position the alligator clip or helping hand vertically:
      0_1560913137433_Crimping-02.jpg

      Put the connector in the alligator clip with the flaps facing up like this:
      0_1560913200590_Crimping-03.jpg

      Strip 2mm from the end of the wire:
      0_1560913215689_Crimping-04.jpg

      Put the wire in the connector so the sheathing is in the first two flaps and the bare wire is in the second two:
      0_1560913229356_Crimping-05.jpg

      Take a pair of needle nose pliers and squeeze the first two flaps so that they are gripping the sheathing, but are not fully closed. You want them to hold the connector to the wire while you put it in your crimping tool:
      0_1560913234832_Crimping-06.jpg

      It should look like this:
      0_1560913243026_Crimping-07.jpg

      Put the connector in your crimping tool so that the flaps are facing into grove and are 90 degrees to the jaws like this:
      0_1560913246336_Crimping-08.jpg

      Squeeze your crimping tool as tight as it will go:
      0_1560913256128_Crimping-09.jpg

      If done correctly it should look like this:
      0_1560913263932_Crimping-10.jpg

      I had to use this method a few dozen times before I got it down. Also I would say I am successful only about 90% of the time with this method. Do not get discouraged.

      posted in Duet Hardware and wiring
      CthulhuLabsundefined
      CthulhuLabs
    • Add Numeric Keypad To M291 Gcode

      It would be very handy for Macros if there was a way to prompt the operator to enter a number and store it in a variable. For instance I am writing a Macro to flatten a piece of stock on my CNC. It would be handy to be able to specify some parameters like the stepover, depth and RPM to use when launching the Macro. Right now I have to hard code them into the Macro and change them if I change tools / materials. The M291 Gcode seems like the obvious choice for this addition to me.

      posted in Firmware wishlist
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Surfacing Macro

      So I am working on a new version of this Macro that will prompt for user input with the M291 command. It will make the Macro far more interactive for things like DepthOfCut, RPM, FeedRate, etc. One of the prompts I want to add is to ask if it should work in Pass mode or Depth mode.

      In pass mode it will prompt you for the number of passes to take. It will then do that many passes before prompting you whether to surface another layer. Default will obviously be 1.

      In Depth mode it will prompt you for how much material to remove. So if your DepthOfCut is say 0.3mm and you enter a depth of 1mm the Macro will do three passes at 0.3mm and then a fourth pass at 0.1mm for a total of 1mm.

      posted in CNC
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Http Post from Macro

      @jay_s_uk Doh! I'm sorry I missed the last word of your post.

      posted in Firmware wishlist
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Surfacing Macro

      @jay_s_uk you just adjust the step over. So if you were using a 1/4in end mill you'd set the step over to like 2mm. I'm using a 1" surfacing bit so I set it to 10mm. Seems to leave a nice finish.

      posted in CNC
      CthulhuLabsundefined
      CthulhuLabs

    Latest posts made by CthulhuLabs

    • RE: Getting actual spindle speed from "M3 R1" Mcode

      @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.

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Getting actual spindle speed from "M3 R1" Mcode

      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()
      
      
      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Getting actual spindle speed from "M3 R1" Mcode

      That did it. @chrishamm @dc42 thank you for your help.

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Getting actual spindle speed from "M3 R1" Mcode

      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.

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Getting actual spindle speed from "M3 R1" Mcode

      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.

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Getting actual spindle speed from "M3 R1" Mcode

      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.

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Getting actual spindle speed from "M3 R1" Mcode

      @dc42 ahhh I saw it in the object model:

      https://github.com/Duet3D/RepRapFirmware/wiki/Object-Model-Documentation#staterestorepointsspindlespeeds

      that is a good alternative though. So I should do something like:

      set global.PauseRPM = spindles[0].active

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Getting actual spindle speed from "M3 R1" Mcode

      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.

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: 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.

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs
    • RE: Getting actual spindle speed from "M3 R1" Mcode

      @chrishamm

      1. I am flushing. I left out a good chunk of my code. Here is the full code:
      #!/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
      
      
      # 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)
      
      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") :
                                  rpm = getRRFVar( '{state.restorePoints[0].spindleSpeeds[0]}', cde.channel )
                                  setRPS( float(int(rpm) / 60) )
                                  intercept_connection.resolve_code(MessageType.Success, "ODrive RPM set to " + str(rpm) )
                              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("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:
                              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()
      
      1. I get this:
      echo state.restorePoints[0].spindleSpeeds[0]
      Error: Failed to evaluate "state.restorePoints[0].spindleSpeeds[0]": unknown value 'spindleSpeeds^'
      

      which I guess is the real issue here. I also tried:

      echo state.restorePoints[0].spindleSpeeds[1]
      Error: Failed to evaluate "state.restorePoints[0].spindleSpeeds[1]": unknown value 'spindleSpeeds^'
      
      echo state.restorePoints[1].spindleSpeeds[0]
      Error: Failed to evaluate "state.restorePoints[1].spindleSpeeds[0]": unknown value 'spindleSpeeds^'
      
      echo state.restorePoints[1].spindleSpeeds[1]
      Error: Failed to evaluate "state.restorePoints[1].spindleSpeeds[1]": unknown value 'spindleSpeeds^'
      

      I even tried with the the tool number:

      echo state.restorePoints[0].spindleSpeeds[251]
      Error: Failed to evaluate "state.restorePoints[0].spindleSpeeds[251]": unknown value 'spindleSpeeds^'
      
      1. I know that if I put:
      M3 S{state.restorePoints[0].spindleSpeeds[0]}
      

      in my resume.g file my ODrive control script would just get "{state.restorePoints[0].spindleSpeeds[0]}" as the string_value for cde.parameter("S") so I figured I would try doing this in my resume.g:

      var ResumeRPM = 0;
      set var.ResumeRPM = {state.restorePoints[0].spindleSpeeds[0]}
      
      ; turn the spindle back on
      M3 S{var.ResumeRPM}
      

      This way {state.restorePoints[0].spindleSpeeds[0]} would get resolved by RRF and not my code. Unfortunately {state.restorePoints[0].spindleSpeeds[0]} does not actually evaluate.

      So the question now is where is the my spindle speed getting save to? Could it have something to do with the fact that I am also intercepting the M5 Mcode so the save state for the spindle is not actually getting created?

      posted in DSF Development
      CthulhuLabsundefined
      CthulhuLabs