First foray into object model
-
I am trying to set up a delay after my printer has reached it's bed operating temperature so that the heat has a chance to equalize on the surface of the glass build plate. I thought that this seemed a reasonably simple thing to do as my first attempt at using the Duet object model.
I am using PrusaSlicer (2.6.0 beta 2) and I am trying to insert the following into the start.g custom gcode that should execute at the start of a print file:var startBedTemp = heat.heaters[0].current
echo startBedTempAt this point I haven't even gotten to any conditional code but the idea is to look at the start bed temperature when a print is started and insert a G4 dwell command based on the starting bed temperature. If the bed temp is below 30C then I want 180 seconds delay, if the bed temperature is between 30C and 50C I want a 120 second delay and if the starting bed temperature is over 50C I want a 60 second delay. Eventually the plan was to also look at the requested bed temperature.
Anyway, I am obviously not grasping something very fundamental because when I attempt to slice a very simple model, PrusaSlicer throws an error on the very first line (var startBedTemp)start_gcode
Parsing error at line 31. Expecting a legacy variable expansion format
var startBedTemp = heat.heaters[0].currentI am obviously missing something very fundamental .... but what? Can I not run the object model in my custom start.g from the slicer? Do I have to run it in a RRF macro? ... and if I have to run it in a macro then how do I get the resulting delay time into PrusaSlicer?
-
The problem is that Prusa Slicer has it's own scripting language.
and square brackets [] are seen as a place holder.
You can use them to say put the demanded bed temp in there.
https://help.prusa3d.com/article/macros_1775So you really should do it in RRF
After you set your bed temp in PS call a macro in RRF that has something likevar delay = 0 var inWindow = (heat.heater[0].current > heat.heaters[0].active - 5) && (heat.heater[0].current < heat.heaters[0].active +5) var bedTemp = heat.heater[0].current if var.bedTemp < 30 set var.delay = 180 elif var.bedTemp > 50 set var.delay = 60 else set var.delay = 120 if var.inWindow = true set var.delay = 30 echo "Doing delay for " ^ var.delay G4 S{var.delay}
EDIT
Not sure why you wouldn't just use M116 and then a known soak time though?
The M116 will wait as long as it needs to and then just add a soak time (which I guess could be conditional) -
@OwenD, thanks, I will work through your example.
As it sits, I use M190 to wait for the bed temperature to get to the set value. The problem is that it takes a lot longer for the glass bed to reach the same temperature that the thermistor sees if the print starts with a cold bed vs if the glass bed is already at some higher temperature. From a cold start I need 3 minutes but if the bed is say at 55, a minute would be fine to get to a 70C bed temperature. The goal was to reduce delay on prints if I am printing more than one print in a series. -
@jens55
In that case you would probably call the macro and set a global variable before you set your bed temp.
Then after you set the bed temp do a G4 using the global variable.if !exists(global.soakTime) global soakTime = 0 var bedTemp = heat.heater[0].current if var.bedTemp < 30 set global.soakTime = 180 elif var.bedTemp > 50 set global.soakTime = 60 else set global.soakTime = 120
EDIT
I think you'll still have to trick PS by quoting the curly bracket enclosed stringThen is PS
; set your bed temp as normal G4 S{"{global.soakTime}"}
-
@OwenD, thank you very much. This 'simple' thing turned out to be well above my pay grade and I would never have figured that out but it makes sense and I will give that a try.
-
@jens55
Just keep playing around
The conditional stuff isn't so hard.
It's only when you're trying to do it within prusa slicer that it gets complicated. -
@OwenD, if I may, I would like to ask a follow-up question.
M291 allows for text display but is there a way to display the content of a variable?
I would like to pop up a message saying "Waiting for" global.soakTime "seconds for bed heat to equalize"So this is actually two bits of text with a variable in the middle.
Bonus if I can display this message for global.soakTime seconds .....
Something along the lines of:
M291 P"waiting for" global.soakTime " seconds for bed heat to equalize" S0 Tglobal.soakTime
Preferably all displayed in a single line. -
@jens55
You can use the caret ^ to concatenate the variable and string.
You should wrap it all in curly bracesOnly on my phone but something like
M291 P{"waiting for " ^ global.soakTime ^ " seconds"} S2 T3
-
@OwenD, do you also walk on water? That worked like a charm!
M291 P{"waiting for " ^ global.soakTime ^ " seconds"} S0 T{global.soakTime} ;I would like to find out more about curly brackets and carets but was unable to find any mention. Would you happen to have a link to more information?
-
I found the caret in the meta commands under "Binary infix operators"
I will have to re-read the meta commands documentation to see what other gems show up! -
You need to check out the docs here
https://docs.duet3d.com/User_manual/Reference/Gcode_meta_commandsAnd details of the object model here
https://github.com/Duet3D/RepRapFirmware/wiki/Object-Model-Documentation -
@jens55 - I second OwenD - just keep at it. And post questions to this forum. You'll find lots of helpful folks here.
My contribution is that when you this of adding some meta commands (programming) to the slicer code, it's best to either stick with the Prusa programming language, or even better, just insert an M98 P"Your macro name" to call a macro you've written and stored on the duet board.
Just think of the macro call as pausing the print-file gcode while it runs the macro you wrote. Once inside you macro, you have full access to the object model and every other G, M, and T code that can be run. The sky's thelimit.
-
@OwenD , thanks again for your help! This new configuration works much better than I had hoped for.
In the past, I would manually set the print bed heat in order to let things warm up. Sometimes I would forget that the bed heat was turned on and sometimes I would start doing other things while the bed warms up and come back 15 or 30 minutes later to do the actual print. Of course sometimes I would think 'surly I don't need extra soak time', start a print and promptly loose bed adhesion because of the delay in the heat getting to the top of the glass bed plate.
Now I just start the print and walk away knowing that the printer will look after warming up the bed before it starts to print. Of course I still need to check with the printer to make sure the first layer is ok.
Anyway, the 'convenience factor' is way higher than what I had hoped for. -
Conditional code opens up a vast amount of options.
For example I use a deferred start option a bit. It allows me to send a job to the printer but not start printing immediately.
In my case I call it in start.g because I don't want any active heaters, but you could easily use that to allow for soak time.
The only thing it would give you that you don't already have would be notifications on how long till the print starts.
Because it's in a loop you have to set up a way to cancel it.
If you study these bits of code you should get the idea.
Note.
You need RRF 3.5b3 or later!In start. G I have the following
You'd have to pass the parameters from Prusa slicer or a macro;check if we want to start immediately of after a delay ; Date-Time format must be - 2022-04-01T06:00:00 var hours = 0 var minutes = 0 var startTime = state.time var timeCheck= state.time var delayedStart = false M291 P"Start after delay?" R"Delay?" S4 K{"Yes","No"} if input = 0 set var.delayedStart = true ; start of code to use hours and minutes from now. ;M291 S6 P"Input number of hours till start" L0 H24 F0 ;set var.hours = input ;M291 S6 P"Input number of minute till start" L0 H59 F0 ;set var.minutes = input ;M98 P"0:/macros/print/start_after_delay.g" H{var.hours} S{var.minutes} ; end of code to use hours and minutes from now. ; start of code to use Specific date and time to start. set var.startTime=state.time M291 S4 P"Choose method to set time" R"Select method" K{"Add hours/minutes from now","Specify time",} F0 if input = 1 M291 S7 P{"Enter start date time (Default 1 hour from now). Format = " ^ var.startTime} L19 H19 F{var.startTime + 3600} set var.timeCheck = datetime(input) ; if the format is wrong this will cause an error which cancels the macro & print M118 P0 L1 S{"Start time selected is " ^ input} else var hoursFromNow = 0 var minsFromNow = 0 M291 S5 P"Enter hours from now to start (0-24)" L0 H24 F0 set var.hoursFromNow = input M291 S5 P"Enter minutes to add till start (0-59)" R{"Hours to start = " ^ var.hoursFromNow} L0 H59 F0 set var.minsFromNow = input set var.timeCheck = datetime(var.startTime + (var.hoursFromNow*3600) + (var.minsFromNow*60)) M118 P0 L1 S{"Start time selected is " ^ var.timeCheck} G4 P100 if var.timeCheck <= state.time M118 P0 L1 S{"ERROR: Start time entered is before current time. Print cancelled"} G4 S2 echo >"0:/sys/print_log.txt" "Delayed start was cancelled. Start time less than current time" G4 S1 abort "ERROR: Start time entered is before current time" echo >"0:/sys/print_log.txt" "Delayed start. Print will be started at", var.timeCheck M98 P"0:/macros/print/start_after_delay.g" R{var.timeCheck} ; end of code to use Specific date and time to start.
The deferred start macro is as follows
In your case you'd set the bed temp before calling it; start_after_delay.g ; should be called from slicer start gcode or start.g BEFORE any heating actions ; Accepts four parameters ; X = a testing parameter to allow early exit after X loops ; S = number of minutes to delay ; H = number of hours to delay ; H & S times are added to achieve total delay ; R = start time. Must have the format R"yyyy-mm-ddThh:mm:ss" e.g R"2022-01-04T13:30:00" ; NOTE: the use of curly braces surrounding time works in DWC but fails in SuperSlicer ; R{"2022-01-04T16:47:00"} will fail if used in SuperSlicer ; if R parameter (start time) is passed, H & S are ignored ; at least one time parameter must be passed ; e.g ; M98 P"start_after_delay.g" H4 S20 ; (delay the start of the print for 4 hours 20 minutes) from now ; M98 P"start_after_delay.g" H1 ; (delay the start of the print for 1 hour) from now ; M98 P"start_after_delay.g" S10 ; (delay the start of the print for 10 minutes) from now ; M98 P"start_after_delay.g" R"2022-04-01T06:00:00" ; start the print on 1st April 2022 at 06:00AM var LongDelay = 60 ; Delay between message updates if time left is greater than 1 hour var MediumDelay = 20 ; Delay between message updates if time left is less than 1 hour but greater than 10 minutes var ShortDelay = 10 ; Delay between message updates if time left less than 10 minutes ; at less than one minute updates will happen every 5 seconds ; at less than 10 seconds, updates will happen every second var BeepFrequencyLow = 1000 ; frequency (low pitch) of beep play every var.ShortDelay when one minute left var BeepFrequencyHigh = 3000 ; frequency (high pitch) of beep play every var.ShortDelay when ten seconds left var BeepDuration = 200 ; duration of beep in milliseconds (must be less than one second) - note 1 second will cause constant beep for last 10 seconds ; ************** Don't change below this line ***************** ; Create a global variable to allow the process to be cancelled. if !exists(global.Cancelled) global Cancelled = false ; NOTE: There will be a delay between setting this global to true and the macro/print cancelling ; as it may be called during a G4 wait commmand and can't be activated on until that finishes. ; To use, either send "set global.Cancelled = true" from the command line or a macro, or set up a button with an associated trigger. ; A separate macro may be hard to execute when the loop timer gets down to 5 or 1 seconds. var FileName = "No file selected" if !exists(param.X) if (job.file.fileName = "") || (job.file.fileName=null) abort "No print in progress" else set var.FileName=job.file.fileName else if (job.file.fileName!=null) set var.FileName=job.file.fileName ;sanity check beep if var.BeepDuration > 1000 echo "Invalid beep duration - reset to 1/2 second) set var.BeepDuration = 1000 ; sanity check default delay times if (var.ShortDelay < 5) set var.ShortDelay = 5 if (var.MediumDelay < var.ShortDelay) set var.MediumDelay = var.ShortDelay if (var.LongDelay < var.MediumDelay) set var.LongDelay = var.MediumDelay if !exists(param.R) if !exists(param.H) if !exists(param.S) echo "No parameters passed - exiting macro" M99 var Hours = 0 ; variable for number of whole hours from start time until run time var Minutes = 0 ; variable for number of whole minutes from start time until run time var Delay = 10 ; variable for delay between displaying messages var HoursLeft= 0 ; variable for number of whole hours from current time until run time var MinutesLeft = 0 ; variable for number of whole minutes from current time until run time var SecondsLeft = 0 ; variable for number of whole seconds from current time until run time var StartTime = datetime(state.time) ; variable to hold time when macro first called var RunTime = datetime(state.time) ; variable to hold time when macro will end and print will run var timeLeft = 0 if exists(param.R) set var.StartTime = datetime(param.R) set var.StartTime = var.StartTime - state.time set var.RunTime = state.time + var.StartTime set var.Hours = floor(var.StartTime / 3600) ; calculate number of whole hours till start time set var.Minutes = floor(var.StartTime/60)-(var.Hours*60) ; calculate number of whole minutes till start time else if exists(param.H) set var.Hours = param.H if exists(param.S) set var.Minutes = param.S set var.StartTime = state.time + var.Hours*60*60 + var.Minutes*60 set var.RunTime = var.StartTime var Loops = 0 ; used if a testing parameter X is passed in order to exit after number of loops has expired echo "Print start time is " ^ var.RunTime while state.time < var.RunTime if exists(global.Cancelled) if global.Cancelled = true echo >>"0:/sys/print_log.txt" "Delayed start print cancelled at " ^ state.time M291 P"Operation has been cancelled" S0 T3 G4 S3 abort "Deferred print cancelled." if exists(param.X) set var.Loops = var.Loops + 1 set var.HoursLeft = floor((var.RunTime - state.time )/60/60) set var.MinutesLeft = floor((var.RunTime - state.time)/60)-(var.HoursLeft*60) set var.SecondsLeft = mod((var.RunTime - state.time),3600)-(var.MinutesLeft*60) if var.RunTime - state.time > 3600 set var.Delay = var.LongDelay else set var.Delay = var.MediumDelay if (var.RunTime - state.time) > 600 M291 R{var.FileName} T{var.Delay} S1 P{"Print start deferred for " ^ var.HoursLeft ^ " hrs : " ^ var.MinutesLeft ^ " mins - " ^ var.RunTime} G4 S{var.Delay} elif (var.RunTime - state.time) > 60 set var.Delay = var.ShortDelay M291 R{var.FileName} T{var.Delay} S1 P{"Print start deferred for " ^ var.HoursLeft ^ " hrs : " ^ var.MinutesLeft ^ " mins : " ^ var.SecondsLeft ^ " secs - " ^ var.RunTime} G4 S{var.Delay} elif (var.RunTime - state.time) > 10 set var.Delay = 5 set var.timeLeft = floor(var.RunTime - state.time) M291 R{var.FileName} T{var.Delay} S1 P{"Print starting in " ^ floor(var.RunTime - state.time) ^ " seconds"} M300 S{var.BeepFrequencyLow} P{var.BeepDuration} G4 S{var.Delay} else set var.Delay = 1 M291 R{var.FileName} T{var.Delay} S0 P{"Print starting in " ^ floor(var.RunTime - state.time) ^ " seconds"} M300 S{var.BeepFrequencyHigh} P{var.BeepDuration} G4 S{var.Delay} if exists(param.X) && (var.Loops = param.X) break M118 S"Starting Deferred Print" M300 S{floor(var.BeepFrequencyHigh * 1.2)} P1000 G4 S2
-
@OwenD, while interesting to see what all can be done with conditional code, I do not have the confidence to start a print all by it's own at a particular time. I will always check on print progress on multiple occasions and especially at the beginning of a print. While I let my printer print overnight, I always end up waking up 2 or three times during the night so I can check the print progress on a web cam.
I am currently still on RRF 3.4x but I will study the code to see how I can take advantage of the upgraded message system in 3.5.