-
-
Notifications
You must be signed in to change notification settings - Fork 419
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
Added basic integration to the Spoolman filament manager #651
Conversation
Proxies any request to the spoolman server. Records filament usage. Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Been testing this for a couple of prints now and I can confirm that it is working as expected. The updated filament weight after a print in the Spoolman db corresponds pretty much exactly with the estimated weight calculated by my slicer. |
could you provide basic [spoolman] configuration examples for moonraker.conf, please? |
Sure! I have added it to the issue description. |
thanks, so I guessed that correctly ;). I've noticed there is no easy way on spoolman's side to set the "mounted" spool, and as there are no ways from any UIs yet to do so, trying that from a workflow perspective seems to be quite hard. Do you use klipper macros to set the active spool? How is your workflow? |
Yup, that is a known problem. As I mentioned in the description, I'm not settled yet on how this is supposed to work. Either If that is something that is supposed to be set from Spoolman's side, then Spoolman needs a database of printers and everything surrounding that, which makes it's scope increase drastically. Not necessarily a bad thing, but do people want a more complete platform for all the inventory, or would it make it too complicated is the question. Alternatively, it's something that's handled in each printer's database, like Moonraker in this case. Which is how it currently works. This is something I would like some input on from the community. And yes, since it's so new, there is no UI for this. My workflow currently is to set this manually using a POST request to |
I have Spoolman installed and I added the [spoolman] to the moonraker.conf, is there any interface within mainsail? I don't see any additions. I went through the settings and I do not see anything. |
@solo2424 Please read the above comment. There is no UI yet since it's not integrated into any frontend yet. Currently, the only way to set the spool is by sending manual POST requests. |
Thanks, sorry I missed that. So I run a post request with the instructions you gave, I am getting the following error. In advance, I do apologize for my ignorance. If you don't wish to waste your time helping a novice, I get it. POST 192.168.0.93:7126/spoolman/set_spool |
I beleive my issue may have to do with these errors. I looked a the log but I don't see anything that sticks out to me. An error was detected while loading the moonraker component 'spoolman'. Please check the log file and fix the issue. Unparsed config section [spoolman] detected. This may be the result of a component that failed to load. In the future this will result in a startup error. |
@solo2424 the changes presented here are not yet live in Moonraker. You have to fetch my branch until @Arksine has reviewed and merged this. I'm not sure how you have installed Moonraker, but if you have it in the form of a git repository, then you can follow this guide on how to switch Moonraker to use my branch instead: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally#modifying-an-inactive-pull-request-locally |
That did it! Thanks for your patiences and thank you for taking on this project. I think this has a lot of potential and the current state is well worth it. I am shocked that we haven't have more people take on filament management in klipper in the past. I know there was a project started awhile back that never picked up steam. @Arksine , I really hope you decide to implement this. |
moonraker/components/spoolman.py
Outdated
|
||
def _register_endpoints(self): | ||
self.server.register_endpoint( | ||
r"/spoolman/set_spool$", |
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.
Endpoints that use regular expressions won't work correctly with all transports. It is either necessary to limit the endpoint to the http
transport or remove the end of string character.
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.
Ah ok, this regex won't be necessary when I remove the dynamic proxy path so this won't be an issue then.
moonraker/components/spoolman.py
Outdated
) | ||
self.server.register_endpoint( | ||
r"/spoolman/[\W\w]+$", | ||
["GET", "POST", "DELETE", "PUT", "PATCH"], |
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.
Moonraker doesn't support PUT
and PATCH
methods. A better solution for proxying a request might be to have the proxied request method and path as arguments to a single POST request, as most Moonraker applications rely on the websocket transport for API requests.
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.
Good idea, that makes it less janky
moonraker/components/spoolman.py
Outdated
self.server = config.get_server() | ||
self.highest_e_pos = 0.0 | ||
self.extruded = 0.0 | ||
self.sync_rate_seconds = config.getint("sync_rate", 5) |
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 it would be wise to set a minimum value here. Perhaps at 1 second?
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.
Good idea
moonraker/components/spoolman.py
Outdated
self.last_sync_time = datetime.datetime.now() | ||
self.extruded_lock = asyncio.Lock() | ||
|
||
self.spoolman_url = config.get("server", None) |
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.
Since the server
option is required we can leave out the default value and let the ConfigHelper
raise an exception.
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.
Ah of course
moonraker/components/spoolman.py
Outdated
) | ||
|
||
database: MoonrakerDatabase = self.server.lookup_component("database") | ||
database.register_local_namespace(SPOOL_NAMESPACE) |
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.
If spoolman
only stores one key then we likely don't need to register a new namespace. We can use the moonraker
namespace and store it in the spoolman.spool_id
key.
Also we can avoid accessing the DB for each update and keep the spool ID cached in a class attribute (ie: self.spool_id
), initialized in component_init()
. For example, the following would work to initialize and update the spool id:
async def component_init(self):
database: MoonrakerDatabase = self.server.lookup_component("database")
self.spool_id = await database.get_item("moonraker", "spoolman.spool_id", None)
def set_active_spool(self, spool_id: Optional[str]) -> bool:
database: MoonrakerDatabase = self.server.lookup_component("database")
def database.insert_item("moonraker", "spoolman.spool_id", spool_id)
self.spool_id = spool_id
self.server.send_event(
"spool_manager:active_spool_set", {"spool_id": spool_id}
)
logging.info(f"Setting active spool to: {spool_id}")
return True
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.
Very good points
Thanks. I did a quick review and posted some initial feedback above. If the goal is integration with a frontend then the module will likely also need to provide an API to query the current spool (can just add a The All that said, there are two things to consider as well:
Just to add, nothing about the above is intended to discourage merging this. I think this module is a useful addition to Moonraker and "Spoolman" is the best proposed way of doing spool tracking that can synchronize state with multiple printers. There are downsides to the |
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
@Arksine I have not considered the agent approach. It does sound interesting and it would make things less coupled from a Moonraker perspective. But as you said, there is the inherent downside that if Spoolman loses connection in the middle of a print, important extruder events might be missed and the tracking is lost. I think a good approach is to start with this type of integration, and then we can look into later to reverse it and become an agent if we see the need for it. That's something that can be phased in without breaking backwards compatibility. The 3rd party plugins system sounds interesting. How do you envision that would work? How would plugins be connected? |
These are the commands I ran last time. I did the same and you can see the results. I tried running the updated curl and I get an error. But when I run the old command, It works. I have a feeling I didn't get your latest update. solo@SoLoServer:~$ cd moonraker solo@SoLoServer:~/moonraker$ git remote add fork https://github.com/Donkie/moonr solo@SoLoServer:~/moonraker$ git fetch fork spoolman
solo@SoLoServer:~/moonraker$ git checkout spoolman solo@SoLoServer: solo@SoLoServer:~/moonraker$ solo@SoLoServer: |
moonraker/components/spoolman.py
Outdated
body = web_request.get("body", None) | ||
|
||
if method not in {"GET", "POST", "PUT", "PATCH", "DELETE"}: | ||
raise ServerError(f"Invalid HTTP method: {method}") |
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.
Use self.server.error
instead of ServerError
. Likewise it is probably not necessary to import ServerError
for Type Checking since it isn't used in any annotations.
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.
Done!
moonraker/components/spoolman.py
Outdated
"use_length": used_length, | ||
}, | ||
) | ||
response.raise_for_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.
I don't think we should raise the exception here as it will spam the log if Spoolman is down. You could do something like the following:
if response.has_error():
return
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 like fully repressing the error since then you can't debug it. I've made it now so it prints the first error since it was last successful, sounds good?
moonraker/components/spoolman.py
Outdated
async def set_active_spool(self, spool_id: Optional[int]) -> None: | ||
self.database.insert_item(DB_NAMESPACE, ACTIVE_SPOOL_KEY, spool_id) | ||
self.spool_id = spool_id | ||
await self.server.send_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.
The send_event
method returns a future that can be awaited for control flow, however I don't think we need to be concerned with it here as spoolman
doesn't depend on the outcome. It should be enough to fire and forget.
Additionally, I think that perhaps we we need to call track_filament_usage
before setting the new spool if the previous spool_id is not None
, and reset self.extruded
to 0 otherwise. This way we flush any residual filament usage and avoid including extrusion moves that isn't part of the new spool.
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.
Good points
moonraker/components/spoolman.py
Outdated
elif action == "POST": | ||
spool_id = web_request.get_int("spool_id", None) | ||
await self.set_active_spool(spool_id) | ||
return True |
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.
Rather than return True
I would recommend just returning the spool ID for both the GET
and POST
requests.
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.
Good point
moonraker/components/spoolman.py
Outdated
return True | ||
|
||
async def _proxy_spoolman_request(self, web_request: WebRequest): | ||
method = web_request.get_str("method") |
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 may be better to name the argument request_method
rather than method
, so as not to confuse it with JSON-RPC's method
parameter.
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.
Ah, good point
It looks close to merging, I had a few comments above, mostly minor in nature. With regard to |
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
Signed-off-by: Daniel Hultgren <[email protected]>
@Arksine I've addressed your review comments now, and also added configuration, web api and changelog documentation. |
Sick! Thank you :) |
FYI, I just pushed a commit that registers a remote method named Details on how to configure and use this have been added to Moonraker's docs. |
Oh nice, great idea! |
https://github.com/Donkie/Spoolman
The component pretty much does three things:
Regarding 3., I've not yet settled on whether this is something Moonraker should keep track of, or whether Spoolman should be extended to also have a database of printers and spools assigned to them. It would make the interaction with Spoolman more complicated but perhaps the community is looking for a more complete system anyway?
Spoolman itself is also very WIP, but my aim is to start off small and see what needs there are, both from users and from moonraker/frontend devs and develop from that.
Heavily based off of #123 and #597 so many thanks to them for the base code.
Example moonraker.conf: