Running a Background Macro
-
Good morning @dc42,
I am trying to run a macro in the background using daemon.g. I want to use it to monitor an end-of-material sensor by checking the state of a capacitive sensor. If it does not detect material, it initiates a material load. For this load, I use a fan output to activate my pellet feed system, which I want to turn off after a certain time and not execute again for another period. To do this, I have also blocked daemon.g so that it does not run if one has already been executed and not finished.
The issue is that if I use this in the same plane as a GCode, I cannot use the M400 command as this would pause the print for the specified time. On the other hand, without using it, when my single-line print codes exceed my G4 codes, they take longer to respond. Could you review my code and see if I am acting appropriately or if there is something I might have overlooked?
;daemon.g if state.status = "processing" M596 P1 if global.DaemonActive = true set global.DaemonActive = false if global.ContPelletsT0 <= 10 if {sensors.gpIn[1].value = 1} var TiempoCarga = 6 var Bucle = true echo >>"0:/sys/Alimentador Registro" {global.ContPelletsT0} while var.Bucle = true M106 P3 S1 G4 S1 M400 if sensors.gpIn[1].value = 0 && var.Bucle = true M106 P3 S0 G4 S1 M400 if sensors.gpIn[1].value = 0 set global.ContPelletsT0 = 1 M300 S5000 P500 set var.Bucle = false if var.TiempoCarga <= 0 && var.Bucle = true set global.ContPelletsT0 = global.ContPelletsT0+1 M300 S5000 P3000 set var.Bucle = false set var.TiempoCarga = var.TiempoCarga-1 M106 P3 S0 G4 S15 M400 else set global.ContPelletsT0 = 1 M596 P0 M25 M400 M291 R"Sensor Right" P"No Pellets detected" S2 T0 set global.DaemonActive = true
Product Short Name Version Duet 3 MB6HC MB6HC 3.5.1 Duet 3 Expansion EXP3HC EXP3HC 3.5.1 Duet WiFi Server n/a 2.1.0 Duet Web Control DWC 3.5.1
Best regards,
-
Good morning @dc42,
I think I'm doing something wrong, or there is something I haven't understood well. I am trying to use Multiple Motion Systems to run a macro that turns my pellet feeder on and off, to prevent lines from taking too long during printing and to ensure it reacts in the expected time, but I can't get it to work properly and I don't know what I'm doing wrong.
Today, I tried configuring a T5 tool, for example:
M563 P5 F3 X4 Y5 S"Feeder"; assigning it a "part cooling fan" which will be my feeder and assigning it fictitious axes, so that when it is selected in M596 P1, there are no waiting conflicts.
Despite this, the result was not what I expected, as now it behaves strangely. It seems to execute everything I specify in the macro, but once the long movements of the main gcode finish, the secondary ones execute all at once, causing my feeder to turn on and off almost instantly.
This is the modified code I used this time. I thought I might need to treat it as a tool to make it work:
;daemon.g M596 P1 T5 if state.status = "processing" if global.DaemonActive = true set global.DaemonActive = false if global.ContPelletsT0 <= 10 if {sensors.gpIn[1].value = 1} var TiempoCarga = 6 var Bucle = true echo >>"0:/sys/Alimentador Registro" {global.ContPelletsT0} while var.Bucle = true M106 S1 G4 S1 M400 if sensors.gpIn[1].value = 0 && var.Bucle = true M107 M300 S5000 P500 G4 S1 M400 if sensors.gpIn[1].value = 0 set global.ContPelletsT0 = 1 M300 S5000 P500 set var.Bucle = false if var.TiempoCarga <= 0 && var.Bucle = true set global.ContPelletsT0 = global.ContPelletsT0+1 M300 S5000 P3000 set var.Bucle = false set var.TiempoCarga = var.TiempoCarga-1 M107 G4 S20 M400 else set global.ContPelletsT0 = 1 else set global.ContPelletsT0 = 1 M596 P0 M25 M400 M291 R"Sensor Right" P"No Pellets T0 detected" S2 T0 set global.DaemonActive = true
Best regards,
-
I haven't delved into your code, but might I suggest an alternate method?
I presume you have some sort of hopper that has an inductive sensor at the bottom?
If so then I suggest you also fit an inductive sensor near the top.
Then simply set up triggers on the two sensors. (they can both point to the same trigger file)
The lower one simply turns on a motor (a fan or heater output would work)
The upper sensor trigger turns the motor off again when the hopper is full.so your config.g might look like this.
I don't know what board you have but this should work on a mini5EDIT
Added code to pause the print if the hopper hasn't filled in X secondsM950 F3 C"!out9" ; Fan 3 uses inverted !out9 pin M106 P3 C"Pellet_Feeder" S0 H-1 M950 J1 C"IO2.in" ; Input 1 uses IO2.in pin M950 J2 C"IO3.in" ; Input 2 uses IO2.in pin M581 T5 P1 S0 R1 ; ; invoke trigger 5 when an active to inactive edge is detected on input 1 and a file is being printed from SD card M581 T5 P2 S1 R1 ; invoke trigger 5 when an inactive-to-active edge is detected on input 2 and a file is being printed from SD card
Then in trigger5.g something like
var maxFillTime = 60 ; maximum time (seconds) to fill hopper. Print paused if not filled in this time if !exist global.pauseNoPelletsTime global pauseNoPelletsTime = state.upTime + var.maxFillTime if sensors.gpIn[1].value = 0 ; if the lower sensor M106 P3 S1 set global.pauseNoPelletsTime = state.upTime + var.maxFillTime if sensors.gpIn[2].value = 1 M106 P3 S0
If you want the beep then I'd put this in daemon.g
The while true loop will keep it running so it must have a delay in it to hand back to the main process.while true if fans[3].actualValue > 0 M300 S5000 P500 G4 P500 ; at this point you could check either sensor depending how you want it to work if (global.pauseNoPelletsTime < state.upTime) && (sensors.gpIn[2].value = 0) && (state.status = "processing") M106 P3 S0 ; turn off feeder M25 ; pause print as hopper hasn't refilled G4 S3 ; delay between loops
-
@Aitor I am out of the office today but I have a few comments on your posts:
-
I don't understand the reason for the M400 commands. Please explain what you mean by "On the other hand, without using it, when my single-line print codes exceed my G4 codes, they take longer to respond."
-
You should not need to use a tool in the daemon.g code.
-
What's the purpose of setting global.DaemonActive false and then true in the daemon.g file? RRF will never re-enter the daemon.g file, so you don't need this to prevent the daemon.g code being executed twice in parallel.
-
-
Good morning @dc42,
Thanks for taking a look. First of all, to also respond to @OWEND, initially I had configured this code as a trigger and used the file called trigger2.g, as I normally do with filament equipment. However, I encountered certain problems that a constant check would solve. Therefore, I decided to use daemon.g, although it is likely that if I solve the daemon.g problem, I can apply it in trigger#. (response to point 2)
The case is that I am using a Pulsar head, which integrates a capacitive sensor to detect if there are pellets in the head. I have connected it to the Duet and configured it as a trigger as follows:
M950 J1 C"1.io2.in" ; Create GPIO/servo pin M581 P1 T2 R-1 ; Configure external trigger#.g
I am using a custom feeder that absorbs pellets through the Venturi effect, so my fan output simply activates or deactivates an electropneumatic relay.
The main problem is that I must activate this output for 6 seconds. Less time does not perform a load, and more time excessively empties my compressor, losing the necessary pressure for a second load. Therefore, I also keep the valve closed for at least 20 seconds. (I am trying to buy a larger capacity compressor, but I doubt it will be ready in time for the fair). Additionally, I count failed load attempts, and if this number is 10 or more, I pause the print, assuming that either my feed load has failed or it has run out of material. (response to point 1)
The reason for blocking daemon.g was that I realized it sometimes executed before a previous execution had finished, causing strange behaviors. I decided to block it for safety, although I am not sure this was the cause of the strange behavior. To be sure, I blocked it. If there is an active daemon.g and it has not finished executing in 10 seconds, is it possible for it to execute again? (response to point 3)
Delving into point 1, whether I use daemon.g or trigger#.g in the same stack as the g-code, daemon or trigger mix with the print code. That is, they execute at the same time as printing. When I print pieces where the extrusion lines are short and execute quickly, such as printing a circle composed of hundreds of small straight lines, the macros execute quickly and even behave as expected. The problem arises when the extrusion lines are long, such as in a solid fill. Also, keep in mind that my print area is 1000mm x 1000mm, so these lines can take quite a while to go from one point to another. For example, I notice that when my solenoid valve is activated, and then a long print line follows, such as a diagonal from side to side, from point 0,0 to 1000,1000, which can take approximately 15-30 seconds, my solenoid valve remains active all this time, emptying my compressor and preventing a successful second load. Therefore, I thought of using M596 P1, either for daemon.g or trigger#.g, so that my macro executes at the times set in the macro, regardless of what lines of code the main stack is printing and the time it takes to execute them. The problem is that M596 P1 does not work as I expect, or I am not using it correctly.
Sorry for the long message, and if something is unclear, please feel free to ask.
Best regards,
-
@Aitor said in Running a Background Macro:
The problem arises when the extrusion lines are long, such as in a solid fill.
If it works on short line segments, but not on long ones, you could try using segmentation, to segment long lines into smaller moves. See M996 S and T parameters, here: https://docs.duet3d.com/en/User_manual/Reference/Gcodes#m669-set-kinematics-type-and-kinematics-parameters
e.g.:
M669 Kinematics is Cartesian, no segmentation, matrix: 1.00 0 0 0 1.00 0 0 0 1.00 M669 S1 M669 Kinematics is Cartesian, 1 segments/sec, min. segment length 0.20mm, matrix: 1.00 0 0 0 1.00 0 0 0 1.00
Ian
-
Good morning @OwenD,
I will carefully review your suggestion, as there are certain points that I am not fully understanding and need to analyze in depth. I am still getting used to programming and there are certain object models in your code that I do not know by heart.
Anyway, at first glance, I think my main problem with the compressor issue will still exist.
Thank you for your contribution. I would be happy to hear any further suggestions you have.
Best regards,
-
Good morning @droftarts,
I will try this immediately. Just to clarify, if I have code running, for example, a print line from point 0,0 to point 1000,1000, does this mean that it will be divided into parts so that other commands can execute before this line finishes?
Best regards,
-
@Aitor said in Running a Background Macro:
Good morning @OwenD,
I will carefully review your suggestion, as there are certain points that I am not fully understanding and need to analyze in depth. I am still getting used to programming and there are certain object models in your code that I do not know by heart.
Anyway, at first glance, I think my main problem with the compressor issue will still exist.
Thank you for your contribution. I would be happy to hear any further suggestions you have.
Best regards,
OK
If I understand your setup correctly then I believe this code will work.
By using the while true in daemon.g, it will only open once (and continue endlessly), not run every 10 seconds,
So you MUST have a delay inside the while loopif !exists global.runUntilTime global runUntilTime = state.upTime + 6 if !exists global.noRunBeforeTime global noRunBeforeTime = state.upTime if !exists global.failedLoadCount global failedLoadCount = 0 while true if (state.status = "processing") && (sensors.gpIn[2].value=0) && (global.noRunBeforeTime < state.upTime) M106 P3 S1 set global.runUntilTime = state.upTime + 6 while state.upTime < global.runUntilTime ; count off the fill time M300 S5000 P500 G4 S1 M106 P3 S0 set global.noRunBeforeTime = state.upTime + 20 ; set the new time to allow compressor refill if sensors.gpIn[2].value=0 ; is hopper still empty? set global.failedLoadCount = global.failedLoadCount + 1 else global.failedLoadCount = 0 if global.failedLoadCount > 10 M25 G4 S1
-
@Aitor said in Running a Background Macro:
Just to clarify, if I have code running, for example, a print line from point 0,0 to point 1000,1000, does this mean that it will be divided into parts so that other commands can execute before this line finishes?
Yes, it should do. Use the M669 S and T parameters to get the granularity that you need. My example chose M669 S1, which means minimum 1 segment per second, so it can have more than 1 segment per second (ie multiple short moves) and will stop/pause within a second (or do other tasks like checking triggers).
M669 T parameter is similar, it just means that any move longer than the minimum segment size is broken up into equal length moves that are, at a minimum, the minimum segment size (yes, slightly confusing that one!).
e.g. with M669 T0.1 set:
- a 0.199mm move will be 1 segment of 0.199mm, as it cannot be divided into equal length moves of less than 0.1mm.
- a 0.201mm move would be split into two moves of 0.1005mm
- a 0.299mm move would be split into two moves of 0.1495mm
So any move that is more than double the size of M669 T#, will be split into equal length segments that are between # and 1.5x # long.
Ian
-
Good morning everyone,
Thank you very much @droftarts, your contribution was key to solving the main problem of the excessively long lines. Configuring M669 T0.1 S1 worked perfectly, starting and stopping my feeder during long runs. However, it only worked when using M596 P1; I don't completely understand why, but I really appreciate your help.
@OwenD Thank you too for your code. Although it doesn't behave as it should, probably due to the "while true", it will help me a lot to optimize my code. After seeing yours, I realize that sometimes I'm a bit clumsy.
Best regards,
-
Good morning everyone again,
I have a question: Can M669 T0.1 S1 potentially slow down other processes or make the Duet work excessively? I will adjust this value to something less extreme, as I understand that M669 T15 S1 might be sufficient.
I also increased the value of the second stack with the command M595 Q1 P60. My question here is similar: What could be the implications of increasing the stack so much? I will continue testing, but I understand that setting the same number of lines as my macro, or even fewer, should be enough, right? Or would this slow down the process?
Best regards,
-
@Aitor I'm not sure if segmentation is applied before or after the command is added to the queue, but I think after, so a command added to the queue is still just a single command (I'll check with @dc42). I don't think segmentation would slow down other processes particularly. Some kinematics (Polar, SCARA) usually have segmentation configured, with S100 T0.2, but as far as I'm aware there's no appreciable slow down.
Having a long command queue can cause problems. Yes, you don't want a short queue, and then not be able to supply enough commands to keep the axes moving continuously, but then you also have the problem of sending commands that go into the queue, and how long it takes for them to be enacted. For a summary of this, see https://docs.duet3d.com/User_manual/Reference/Gcodes#command-queueing
You don't need to have the whole of your macro in the queue, just enough of it that it doesn't stutter. You also don't have any moves that need to be 'planned' ie G0/1/2/3 moves, you're just turning on the hopper motor with M106 commands, and there's a lot of G4 pauses.Running the macro supplied by @OwenD should work, though looking at it it doesn't look like it's running in the second motion system? With all the pauses, and only using M106, perhaps it doesn't need to. I have a feeling that running the M300 command to play a beep sound pauses the whole queue while sound is played, so that might be causing pauses if it's not run in the second motion system.
Ian
-
Good morning, @droftarts,
I understand the topic of segmentation and also the queue. I believe that leaving the stack of 3 as the default might be sufficient; however, I will conduct tests regarding this.
I have modified @OwenD's macro a bit to run it in the background. Additionally, I have added some features to save compressed air and to keep a record of my attempts, in order to analyze the number of attempts I usually need, and thus be able to better adjust the value of 10 repetitions. The result so far is this:
M596 P1 if (state.status = "processing") && (sensors.gpIn[1].value = 1) && (global.DaemonActive < state.upTime) M106 P3 S1 set global.TimerPelletsT0 = state.upTime + 6 echo >>"0:/sys/Alimentador Registro" {global.ContPelletsT0} ;Log of number of unsuccessful attempts while state.upTime < global.TimerPelletsT0 ; count off the fill time if sensors.gpIn[1].value = 0 ;Check for pellets M106 P3 S0 G4 P500 M400 ;Perform a short wait before checking if sensors.gpIn[1].value = 0 ;Check that it was not a false detection set global.TimerPelletsT0 = state.upTime - 6 M300 S5000 P500 else ;A false detection was detected, continue. M106 P3 S1 G4 S1 M400 M106 P3 S0 set global.DaemonActive = state.upTime + 20 ; set the new time to allow compressor refill if sensors.gpIn[1].value = 1 ; is hopper still empty? set global.ContPelletsT0 = global.ContPelletsT0 + 1 else set global.ContPelletsT0 = 1 if global.ContPelletsT0 > 10 M596 P0 M25 M291 R"Material Sensor" P"No Pellets detected" S2 T0
I can also tell you that making beeps does not cause any pauses, they only generate an interruption if you place a G4 and M400 right after, but in my case, running in the background, the M400s do not cause pauses. What I have noticed is that when I run this same macro without M400 in the foreground, the delays execute before reaching those lines, meaning they don't wait to reach that line and then everything executes at once. However, it's true that segmenting the line shouldn't cause this, so I will review it again. I am sure that if I run this macro with M400s in the foreground, I will have pauses during printing, hence the reason for running them in the background.
Regards,
-
@Aitor said in Running a Background Macro:
The reason for blocking daemon.g was that I realized it sometimes executed before a previous execution had finished, causing strange behaviors.
RRF will never re-enter daemon.g, unless you use M98 to execute daemon.g explicitly from somewhere else.
Likewise when a trigger is executing, another trigger (whether the same one of a different one) won't be executed until the original one has completed or aborted; except that trigger 0 can always be executed.
@Aitor said in Running a Background Macro:
Delving into point 1, whether I use daemon.g or trigger#.g in the same stack as the g-code, daemon or trigger mix with the print code. That is, they execute at the same time as printing.
What do you mean by "in the same stack"?
@Aitor said in Running a Background Macro:
For example, I notice that when my solenoid valve is activated, and then a long print line follows, such as a diagonal from side to side, from point 0,0 to 1000,1000, which can take approximately 15-30 seconds, my solenoid valve remains active all this time, emptying my compressor and preventing a successful second load.
That is not expected, if the solenoid valve is activated only by daemon.g or by the trigger. Both daemon.g and a trigger file (if triggered) should execute concurrently (several times if needed) with long moves in the job file; provided that you do not either command motion in the same motion system or use M400 in the trigger file or daemon.g.
If your daemon.g or trigger files does not command any motion, you should not need to use M596 P1.
-
Good morning @dc42,
Thank you for your response. Regarding blocking daemon.g, it is clear to me that it is not necessary. It was likely a feeling I had due to the behavior I observed, and I decided to block it to be sure, but I have now removed it from my code.
Regarding "stack," it is probably a mistranslation; I mean the movement queue (M596 P0 or P1).
As for your last comment, I just tried it and still have to use M569 P1 because if I don't use it, it does not execute in the desired times. Even using M669 T0.1 S1, the execution response of daemon.g is slow. Here are the details of how I conducted the tests.
Gcode being printed:
; G-Code generated by INDART3D G21 ; Metric values T0 P0 ; Select Tool G28 ; Home all G0 Z900 M400 ; Wait for current moves to finish ; Macro G1 X0 Y0 F3000 G1 X1000 Y1000 G1 X0 Y1000 G1 X1000 Y0 G1 X0 Y0 F3000 G1 X1000 Y1000 G1 X0 Y1000 G1 X1000 Y0 G1 X0 Y0 F3000 G1 X1000 Y1000 G1 X0 Y1000 G1 X1000 Y0 G1 X1000 Y0 G1 X0 Y0 F3000 ; The gcode keeps repeating these commands hundreds more times
daemon.g modified with your instructions:
; M596 P1 if (state.status == "processing") && (sensors.gpIn[1].value == 1) && (global.DaemonActive < state.upTime) M106 P3 S1 set global.TimerPelletsT0 = state.upTime + 6 echo >>"0:/sys/Alimentador Registro" {global.ContPelletsT0} ; Log of number of unsuccessful attempts while state.upTime < global.TimerPelletsT0 ; count off the fill time if sensors.gpIn[1].value == 0 ; Check for pellets M106 P3 S0 G4 P500 ; M400 ; Perform a short wait before checking if sensors.gpIn[1].value == 0 ; Check that it was not a false detection set global.TimerPelletsT0 = state.upTime - 6 M300 S5000 P500 else ; A false detection was detected, continue. M106 P3 S1 G4 S1 ; M400 M106 P3 S0 set global.DaemonActive = state.upTime + 20 ; set the new time to allow compressor refill if sensors.gpIn[1].value == 1 ; is hopper still empty? set global.ContPelletsT0 = global.ContPelletsT0 + 1 else set global.ContPelletsT0 = 1 if global.ContPelletsT0 > 10 set global.ContPelletsT0 = 1 ; M596 P0 M25 M291 R"Material Sensor" P"No Pellets detected" S2 T0
The result I get is the same. Right now, I am testing with an empty system, so sensors.gpIn[1].value is always equal to 1. The issue is that without using the secondary motion queue, it does not respect the 6 seconds, it always takes longer than desired, and it always turns off or on at the end of the complete Gcode lines, despite being fragmented with M669 T0.1 S1.
Best regards,