-
Notifications
You must be signed in to change notification settings - Fork 0
/
maxflow.py
executable file
·231 lines (181 loc) · 6.37 KB
/
maxflow.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/usr/bin/env python3
import argparse
import contextlib
import datetime
import time
import numpy as np
import requests
XY_TRAVEL_SPEED = 6000
Z_TRAVEL_SPEED = 600
RETRACT_SPEED = 1800
class GCodeError(Exception):
def __init__(self, command, status_code, response_text):
self.command = command
self.status_code = status_code
self.response_text = response_text
def __str__(self):
return (
f"G-code command {self.command} failed with status {self.status_code} and "
f"response text {self.response_text}"
)
def _run_gcode(gcode):
host = "http://localhost:7125/printer/gcode/script"
response = requests.post(host, json={"script": gcode})
if response.status_code != 200:
raise GCodeError(gcode, response.status_code, response.text)
def flow_test(volumetric_rate, temp, length):
"""
Run a volumetric flow test on the printer.
@param temp - The temperature to extrude at
@param length - The length of filament to extrude
@param volumetric_rate - The volumetric rate to extrude at in mm^3/s
@return The path to the accelerometer data file
"""
linear_rate = volumetric_rate * 60 / (3.14159 * 1.75**2 / 4)
timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
accel_name = f"{timestamp}-{volumetric_rate}mm3s"
accel_filename = f"/tmp/adxl345-{accel_name}.csv"
cmd = f"""
; G28
; Ensure the extruder is at the correct temperature
M109 S{temp}
; movement to absolute
G90
; move near build plate
; TODO: make movement xy depend on rate? to avoid conflicts
G1 Z5 F{Z_TRAVEL_SPEED}
; Start accelerometer measurement
ACCELEROMETER_MEASURE NAME={accel_name}
; Wait for 1 second to collect background noise
G4 P1000
; extruder movement to relative
M83
; Extrude
G1 E{length} F{linear_rate}
; Stop accelerometer measurement
ACCELEROMETER_MEASURE NAME={accel_name}
; retract 1mm
G1 E-1 F{RETRACT_SPEED}
; move back up 10mm
G91
G1 Z10 F{Z_TRAVEL_SPEED}
G90
"""
_run_gcode(cmd)
return accel_filename
def contains_extruder_click(file_path):
"""
Determine if the accelerometer data contains an extruder click.
@param file_path - The path to the accelerometer data file
@return True if the accelerometer data contains an extruder click, False otherwise.
"""
data = np.genfromtxt(file_path, delimiter=",", skip_header=1)
avg_accel = np.mean(data[:, 1:4], axis=1)
# the first second is just background noise, so we can use that to determine
# a threshold for what is a click and what is not.
sample_hz = int(1 / (data[1, 0] - data[0, 0]))
first_second_data = avg_accel[:sample_hz]
mean = np.mean(first_second_data)
std_dev = np.std(first_second_data)
# use a crazy high z score to avoid false positives
upper_bound = mean + 10 * std_dev
return np.any(avg_accel[sample_hz:] > upper_bound)
@contextlib.contextmanager
def extruder_at_temp(temp):
"""
Context manager to ensure the extruder is at the correct temperature.
@param temp - The temperature to set.
"""
try:
_run_gcode(f"M109 S{temp}")
yield
finally:
_run_gcode("M109 S0")
def run_test(min_flow, temp, length, max_flow):
"""
Run a flow test on the printer.
@param min_flow - The minimum flow rate to extrude at in mm^3/s.
@param temp - The temperature to extrude at.
@param length - The length of filament to extrude.
@param max_flow - The maximum flow rate to extrude at in mm^3/s.
"""
max_pos_xy = [200, 200]
original_pos_xy = [20, 20]
pos_xy = original_pos_xy.copy()
_run_gcode(
f"""
; home
G28
; move to initial position
G90
G1 X{pos_xy[0]} Y{pos_xy[1]} F{XY_TRAVEL_SPEED}
"""
)
with extruder_at_temp(temp):
low = min_flow
high = max_flow
click_flow = None
while low <= high:
mid = (low + high) // 2
print(f"checking {mid} mm^3/s")
file_path = flow_test(volumetric_rate=mid, temp=temp, length=length)
# wait for csv file to be fully written
time.sleep(5)
if contains_extruder_click(file_path):
print(f"Extruder click detected at {mid} mm^3/s")
click_flow = mid
high = mid - 1
else:
low = mid + 1
# move over 10mm, possibly up 10mm
pos_xy[0] += 10
if pos_xy[0] > max_pos_xy[0]:
pos_xy[1] += 10
if pos_xy[1] > max_pos_xy[1]:
raise RuntimeError("Y max exceeded, something is very wrong.")
pos_xy[0] = original_pos_xy[0]
_run_gcode(f"G1 X{pos_xy[0]} Y{pos_xy[1]} F{XY_TRAVEL_SPEED}")
if click_flow is not None:
print(f"Max flow rate with no extruder click: {click_flow - 1} mm^3/s")
else:
print(f"No extruder click detected. Stopped at {max_flow} mm^3/s.")
class CLIArgs:
def __init__(self, min_flow, temp, length, max_flow):
self.min_flow = min_flow
self.temp = temp
self.length = length
self.max_flow = max_flow
@classmethod
def from_argv(cls):
parser = argparse.ArgumentParser(description="Run a flow test on the printer.")
parser.add_argument(
"--min-flow",
type=int,
default=5,
help="The minimum flow rate to extrude at in mm^3/s.",
)
parser.add_argument(
"--temp", type=int, required=True, help="The temperature to extrude at."
)
parser.add_argument(
"--length",
type=int,
default=50,
help="The length of filament to extrude in mm.",
)
parser.add_argument(
"--max-flow",
type=int,
default=30,
help="The maximum flow rate to extrude at in mm^3/s.",
)
args = parser.parse_args()
return cls(args.min_flow, args.temp, args.length, args.max_flow)
if __name__ == "__main__":
args = CLIArgs.from_argv()
run_test(
min_flow=args.min_flow,
temp=args.temp,
length=args.length,
max_flow=args.max_flow,
)