Arksine / moonraker

Web API Server for Klipper
https://moonraker.readthedocs.io
GNU General Public License v3.0
1.05k stars 404 forks source link

[FR] Maintenance counter/timer #124

Open grigi opened 3 years ago

grigi commented 3 years ago

Background:

With our local South African Voron WhatsApp group we discussed having a counter that would remind you to "service" (that is oil the rails/rods, and check that everything is still good) your printer. Now that Mainsail keeps track of printing history we think this small feature can work.

We discovered that HiWin lubrication spec recommended a 200km re-oiling interval for MGN9 (which Voron2 uses).

After some extrapolation of what a typical 0.4mm nozzle would have to move we deduced that you could estimate it based off how much filament is printed we estimated that the axis should move about 10× the distance of 1.75mm filament used. That should put a service interval of roughly 20km (or 60kg of filament). This seems to line up with how often the Print-It-Forward printers get serviced.

Proposal:

Model service intervals in a similar way as is done in the automotive industry:

(Whichever comes first)

Since with History we now have reasonable filament length used (it doesn't need to be perfectly accurate), along with a "last serviced timestamp", we could use this simple model to remind the user to re-service.

We could even calculate an estimate % of progress till next service with this data:

wear_filament_ratio = (history.filament_printed - maintenance.last_service_filament) / config.service_filament
wear_time_ratio = (now() - maintenance.last_service_time) / config.service_time
estimated_wear = max(wear_filament_ratio, wear_time_ratio)

Questions

Assuming this model is sane, how would we notify the user about wear level, or if it's overdue? Is this a presentation issue, and we just need to expose it as an API here? This obviously needs access to the history plugin, but should store service counter data in it's own datastore. I'm not certain how this would work in the code. Can we trust the total_printed counter to not shrink when we reach the job limit and start cleaning up old data in the history plugin?

Arksine commented 3 years ago

Interesting. A client could feasibly implement this functionality now without any addition to moonraker. Clients can store persistent data in the database, so they could track the service counter.

Can we trust the total_printed counter to not shrink when we reach the job limit and start cleaning up old data in the history plugin?

Yes. The job_totals are updated as jobs complete, they are not calculated from job entries.

Another alternative would be to track the toolhead position and store total distance traveled for each axis. This would be relatively simple and provide a very accurate representation of distance traveled.

grigi commented 3 years ago

Interesting. A client could feasibly implement this functionality now without any addition to moonraker. Clients can store persistent data in the database, so they could track the service counter.

I do think this data would be useful in Moonraker, as multi-client printers (KlipperScreen & Fluidd/Mainsail) are not that rare, so I would rather it live in here.

Another alternative would be to track the toolhead position and store total distance traveled for each axis. This would be relatively simple and provide a very accurate representation of distance traveled.

Interesting. But is there any benefit in implementing motion tracking per axis?

I suppose so because most printers have anisotropic rail configuration. And I didn't think of printers which have a very different kinematic, which may not cleanly map to anything.

The issue would be in getting the user to configure sane values for the variables? If you only have two easy relatable ones, or a horde of different metrics for different motion systems, it would be really hard for a human to guess good values.

I have a friend with a bunch of CR-10's and he services them after every 10 spools, his Prusas last a bout 30 spools between services. This data one can get from people with experience by just asking after their experiences. If your printer has been sitting on shelf for half a year it probably needs a service just to work well again.

Compare this to having lookup tables that discuss if you have brass or POM lead screw nuts, and how heavy the gantry/bed is, and if you have good or cheap linear bearings or wheels with POM or PC plastic, etc... We can't ask people to just "find" that data, or understand the difference that a z-hop would do to wear.

I think this is why Automotive manufacturers stayed with a very simplistic model, the cognitive overhead is much lower.

So, a simple plugin that stores the previous service intervals (or defaults it to ideal when new), and then just uses the history plugin data, with endpoints to expose the data and to reset the counters would be a good starting point?

OSHW-Rico commented 3 years ago

I think tracking motion distance in moonraker makes kinda sense. Maybe something like total distance per axis and distance since last reset. (Maybe bed cycels, since people like stats?!) The implementation of maintenance intervals could than be "calculated" or choosen based on Moonraker provided data from the frontend.

A value since reset could be stored internally as a snapshot ot the total_xxx data at date Y.

grigi commented 3 years ago

To be honest, I like the detailed motion distance tracking idea. I just fear that users might feel it's too complicated to configure to determine a wear indicator.

But it is obvious with leadscrews that you need to replace the T8 nuts at some point, and it would be nice to be able to track multiple metrics. Even just keeping stats of total movement per axis would be very valuable and won't require the user to actually set anything.

That way we could actually get visibility on usage of each part, and one can't manage without data.

OSHW-Rico commented 3 years ago

To be honest, I like the detailed motion distance tracking idea. I just fear that users might feel it's too complicated to configure to determine a wear indicator.

But it is obvious with leadscrews that you need to replace the T8 nuts at some point, and it would be nice to be able to track multiple metrics. Even just keeping stats of total movement per axis would be very valuable and won't require the user to actually set anything.

That way we could actually get visibility on usage of each part, and one can't manage without data.

The users dont have to in my opinion, at least not per default. Moonraker is not the user interface, but i needs to provide the necessary data. I think in the end, independent of how this is implemented the frontend, those values would be useful.

grigi commented 3 years ago

Moonraker still needs to provide advice as to what is sane values.

But I agree, we should collect all the metrics, and then allow the user to add a service interval to any of those metrics.

@Arksine Could we get usage stats on these metrics? :

Anything else you would want to propose?

Arksine commented 3 years ago

I think tracking lifetime distance per axis makes sense, this could be added to the existing data_store component which is designed to track specific data pushed from Klippy.

It is possible to track heater and fan on time, although I'm not sure this is something that belongs in Moonraker's core, I need to think about it. The switch to components is in preparation for a future 3rd party plugin interface. That may be a better candidate if there is a desire for deep data tracking and analysis.

grigi commented 3 years ago

My question was to try and find out what kind of data we can get to track to build wear indicators against. Fans and heaters tend to last very randomly, so you couldn't really keep track of when it's likely to fail.

OSHW-Rico commented 3 years ago

Your Right about random fails, but it still good to see how long the fan was in service. For someone running more than one printer you can easily spot the difference in operation hours of different brands used etc. Not something for prediction but more analysing after failure.

flexd commented 1 year ago

Did anything come of this? I have been thinking I'd like to have/add something like this to remember to change the Nevermore carbon every 50h print time / 30 days, and other things.

wattsie commented 1 year ago

Hi All, Here is a quick script I knocked up to see the effects of printing various files on the linear rails.

If you would like to see how often and what parts of the linear rails get used the most, this might help.

I created a simple box 75x35x2mm in the slicer, in the center on a 350x350x250 printer with standard voron PIF settings, saved and parsed the file into the code below. This was the output.

Total X traveled: 14607mm
Total Y traveled: 13835mm
Total Z traveled: 2mm
X Buckets: [  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0. 380. 338. 337. 333. 331. 333. 334. 397.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
Y Buckets: [  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0. 714. 543. 543. 744.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.   0.]
Z Buckets: [1271.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.    0.]

I assume, one could, once a file has printed, run the file thru the script and saved the values into a database. Then simply sum up all the buckets across all the print jobs, to give you a count on the number of times the print head has passed that bucket... thus giving you an indication on wear...

I have not looked into the moonraker code yet, so not sure how to integrate...

Hope this helps someone.

#!/usr/bin/env python

import sys
import math
import re
import numpy as np

#
# Allow long lines with printing
#
np.set_printoptions(threshold=sys.maxsize, linewidth=180)

#
# Pass the gcode filename and linear rail backet size (in mm)
# Will only process G0 and G1 commands
#
def process_gcode_file(filename, x_size=350, y_size=350, z_size=250, bucket_size=10):
    bucket_x = np.zeros(int(x_size / bucket_size))
    bucket_y = np.zeros(int(y_size / bucket_size))
    bucket_z = np.zeros(int(z_size / bucket_size))
    with open(filename, 'r') as f:
        current_x, current_y, current_z = None, None, None
        total_distance_x, total_distance_y, total_distance_z = 0, 0, 0
        for line in f:
            parts = line.strip().split()
            if not parts:
                continue
            command = parts[0]
            if command in ('G0', 'G1'):
                x, y, z = current_x, current_y, current_z
                for part in parts[1:]:
                    if part.startswith('X'):
                        try:
                            x = float(part[1:])
                        except ValueError:
                            continue
                    elif part.startswith('Y'):
                        try:
                            y = float(part[1:])
                        except ValueError:
                            continue
                    elif part.startswith('Z'):
                        try:
                            z = float(part[1:])
                        except ValueError:
                            continue
                if current_x is not None:
                    total_distance_x += abs(x - current_x)
                    for i in range(int(min(current_x, x) // bucket_size), int(max(current_x, x) // bucket_size) + 1):
                        bucket_x[i] += 1
                if current_y is not None:
                    total_distance_y += abs(y - current_y)
                    for i in range(int(min(current_y, y) // bucket_size), int(max(current_y, y) // bucket_size) + 1):
                        bucket_y[i] += 1
                if current_z is not None:
                    total_distance_z += abs(z - current_z)
                    for i in range(int(min(current_z, z) // bucket_size), int(max(current_z, z) // bucket_size) + 1):
                        bucket_z[i] += 1
                current_x, current_y, current_z = x, y, z
    return bucket_x, bucket_y, bucket_z, total_distance_x, total_distance_y, total_distance_z

# Printer Dimensions
printer_x = 350
printer_y = 350
printer_z = 250

# Bucket size for counters (the smaller the size, the more numbers stored
# 10mm is probably a good starting point
bucket_size = 10

# Call and process the file
bucket_x, bucket_y, bucket_z, total_distance_x, total_distance_y, total_distance_z = process_gcode_file(sys.argv[1], printer_x, printer_y, printer_z, bucket_size)

# debug outputs
print('Total X traveled:', round(total_distance_x, 'mm')
print('Total Y traveled:', round(total_distance_y, 'mm')
print('Total Z traveled:', round(total_distance_z, 'mm')

print('X Buckets:', bucket_x)
print('Y Buckets:', bucket_y)
print('Z Buckets:', bucket_z)
bigblzrd commented 6 months ago

Did anything come of this? I have been thinking I'd like to have/add something like this to remember to change the Nevermore carbon every 50h print time / 30 days, and other things.

Here 2 years later wondering the same, would love a way to easily keep track of when carbon needs changing. I don't suppose you ever found a solution at all?

Kr0n0s1011 commented 5 months ago

Did anything come of this? I have been thinking I'd like to have/add something like this to remember to change the Nevermore carbon every 50h print time / 30 days, and other things.

Here 2 years later wondering the same, would love a way to easily keep track of when carbon needs changing. I don't suppose you ever found a solution at all?

https://github.com/MapleLeafMakers/KlipperMacros/blob/main/air_filter_timer.cfg i use this to remind me of filter changes ;)

3DCoded commented 1 month ago

I recently developed KlipperMaintenance, which is a maintenance reminder plugin for Klipper. I would appreciate any feedback on it.

atorresdlp commented 1 month ago

I recently developed KlipperMaintenance, which is a maintenance reminder plugin for Klipper. I would appreciate any feedback on it.

I'll be testing it 3DCoded, thanks for the work and I'll be in contact with you very soon to let you know what I think.