New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
filament_sensor: advanced filament detection #1301
filament_sensor: advanced filament detection #1301
Conversation
d167279
to
30dff32
Compare
Thanks. I have a couple of high-level comments.
-Kevin |
I'll get on these items and have it updated as soon as I can. |
Okay, I have added some commits to address some of the current issues:
I still need to look into moving the additional debounce period to buttons.py, I think I have an idea of how to do it without breaking the API for all other callers. I also think it might be useful to add a get_filament_sensors() function to the module, because it will be possible to have multiple prefixes when other sensor implementations are added. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. In general it looks good to me. I am a bit confused by the callbacks and the use of the reactor (see below), but nothing that would prevent merging.
config/example-extras.cfg
Outdated
#[filament_switch_sensor my_sensor] | ||
#pause_on_runout: True | ||
# When set to True, a PAUSE will execute immediately on runout. Note | ||
# That when runout_on_pause is set to True the runout event will be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo - should be "pause_on_runout".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I'll fix the typos, formatting issues, and clarify the gcode descriptions as recommended.
config/example-extras.cfg
Outdated
# The gcode to execute after a filament runout event. If pause_on_runout is | ||
# set to True this gcode will execute after the pause is complete. Otherwise | ||
# it will execute as soon as resources are available. If the runout_gcode | ||
# is omitted and runout_on_pause is set to False, the runout event is disabled. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not clear what a "runout event" is. Could this just be worded as "The default is to not run any g-code commands".
docs/G-Codes.md
Outdated
## Filament Sensor | ||
|
||
The following command is available when a "filament sensor" config section is enabled. | ||
- `QUERY_FILAMENT_SENSOR SENSOR=<sensor_name>`: Querys the current status of the filament sensor. The data displayed on the terminal will depend on the sensor type defined in the confguration. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo - "queries". Also, in keeping with the formatting of this file, lines should be wrapped at 80 columns.
for status in ["idle", "ready", "printing"]: | ||
self.printer.register_event_handler( | ||
"idle_timeout:%s" % (status), | ||
(lambda e, s=self, st=status: s._update_print_status(st))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I think the code would be more readable if explicit handle_idle(), handle_ready(), and handle_printing() methods were used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I will change this.
self.gcode.register_mux_command( | ||
"QUERY_FILAMENT_SENSOR", "SENSOR", self.name, | ||
self.cmd_QUERY_FILAMENT_SENSOR, | ||
desc=self.cmd_QUERY_FILAMENT_SENSOR_help) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I think the code would be more readable if the command code and its registration was done in SwitchSensor() (and any other descendent classes).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, I will make that change. I do think it would still be wise to keep the following in the base class:
def cmd_QUERY_FILAMENT_SENSOR(self, params):
raise NotImplementedError(
"Sensor must implement cmd_QUERY_FILAMENT_SENSOR")
This makes sure subclasses implement the correct gcode.
except: | ||
raise | ||
def set_callbacks(self, runout_cb=None, detected_cb=None, monitor=True): | ||
# Allow other modules to receive sensor status notifications |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what this method does - it isn't invoked anywhere. If the goal is to allow other modules to be aware of this event, then I'd use self.printer.send_event("filament_switch_sensor:my_event", some, parameters)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this method is specifically for external modules to be able to set their own callbacks. I have some concerns with using Klippers internal event system for this, as it would execute a callback for every module registered. I'll provide an example:
A module (such as MMU) is changing filament, and it wants to be notified when filament triggers the sensor closest to the extruder. To do this it would have to change the behavior of the sensor, as it would need to enable insert detection while the printer is in a "printing" state. Also, it wouldn't be desirable for the built-in callback to execute the autoload_gcode while its being used for this.
There are ways around this. Let me think on this and I'll see if I can come up with a solution. I do think that I would need a more generic name for the event prefix, I wouldnt want to have to register event handlers for each underlying implementation (ie: filament_switch_sensor:runout_event
, pat9125:runout_event
, etc)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I'd say add that callback support when the software for the first callee is added. It's hard to predict what future code will need.
klippy/extras/gcode_macro.py
Outdated
@@ -37,6 +37,8 @@ def cmd(self, params): | |||
self.gcode.run_script_from_command(script) | |||
finally: | |||
self.in_script = False | |||
def contains(self, gc): | |||
return gc.upper() in self.script.upper() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this change should no longer be necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I will remove it.
klippy/extras/pause_resume.py
Outdated
if from_event: | ||
self.v_sd.do_pause_from_event() | ||
else: | ||
self.v_sd.cmd_M25({}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this just be self.v_sd.do_pause()
instead of making the distinction between M25 and do_pause_from_event()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, not a problem.
reactor.monotonic() + 2.) | ||
def _update_print_status(self, status): | ||
logging.info( | ||
"filament_sensor: print status changed to: %s" % (status)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This adds logging for every printer state change. I'm not sure that is desirable. If it is desirable, it should be added to idle_timeout.py and be done separate from this series.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are correct, I used that to make sure the internal state was updating correctly, I will remove it.
return | ||
self.event_button_state = state | ||
self.reactor.update_timer( | ||
self.switch_event_timer, eventtime + SETTLE_TIME) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this is attempting to do, but it doesn't seem right. Timing in the host code is always a bit iffy because there could be variable amounts of delays between the events and their reception in the host. Better to get the actual event time (a clock/print_time from the micro-controller) or to use the sent/receive_time from the actual timestamp of the received micro-controller messages.
At a high-level I don't think the filament sensor should need to use the reactor at all. The reactor is more intended to schedule system events - at first glance the filament sensor is just reacting to internal events.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This timer makes sure that a change in switch state is held for ~100ms before executing a callback. I see where the issue is here, I shouldn't use eventtime to update the timer.
I moved this functionality to buttons.py. There I use reactor.monotonic() to set the wake time. This probably isn't absolutely necessary functionality, I thought it would be wise given that the switch will likely move around and have filament running past it. Depending on the way the switch is mounted this could result in the switch triggering prematurely. If you do not like it I have no problem removing it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, I'd say add support for extended debouncing when we get confirmation that it is required. There's a good chance the troublesome hardware will need something different than anticipated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm battling with the cheap filament runout sensor that came with my Two Trees Sapphire Pro. It keeps giving false alerts and pausing my prints. I have a feeling a longer debounce could be helpful in my case
This allows for pausing from inside a reactor callback. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
…nt runout/insert sensor This implementation includes a BaseSensor class that all underlying sensor implementations should subclass. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
Signed-off-by: Eric Callahan <arksine.code@gmail.com>
f2203ca
to
f3f436d
Compare
I have force pushed with the above recommendations, including removal of the debounce timer and the set_callbacks() function. Since I moved the gcode registration to the subclass, I also moved registration for the klippy:ready event there, that should keep subclasses from registering it twice. Finally, I renamed autoload_gcode to insert_gcode, as that seems more consistent with the rest of the module. |
Thanks. It looks good to me. If there are no further comments I'll look to commit in the next couple of days. FYI, my earlier comment on the calls to -Kevin |
Thanks! -Kevin |
This PR contains an advanced implementation for filament detection. Features include:
There are a couple of things in here that may require explanation. First, when pausing Octoprint, I have to send the action command from inside the runout event handler before the current move is acknowledged. Otherwise Octoprint will queue up the next move after acknowledgement, which will be executed after the PAUSE gcode. To make PAUSE optional on runout I do a nested search on the runout_gcode for the PAUSE command. This is why I added the contains() function to gcode_macro.
Second, changes to gcode.py are necessary in order for the SD card to pause immediately, as mentioned in proposal outlined in #1236. Alternatively I could directly call cmd_M25 inside the runout event handler as PR #1098 does. I don't think this would have any negative consequences as it just sets must_pause_work to True, but it probably isn't good practice.
Signed-off-by: Eric Callahan arksine.code@gmail.com