Skip to content

Instantly share code, notes, and snippets.

@rogerlz
Last active May 19, 2024 13:50
Show Gist options
  • Save rogerlz/03ae2b7530cb7587cf652ae4de88a4ae to your computer and use it in GitHub Desktop.
Save rogerlz/03ae2b7530cb7587cf652ae4de88a4ae to your computer and use it in GitHub Desktop.
heated_fan.py
# Support fans that are enabled when a heater is on
#
# This file may be distributed under the terms of the GNU GPLv3 license.
from . import fan
PIN_MIN_TIME = 0.100
class HeatedFan:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.gcode = config.get_printer().lookup_object("gcode")
try:
self.printer.lookup_object("fan")
except:
self.printer.add_object("fan", self)
else:
raise self.printer.config_error(
"Can't setup fan and heated_fan together"
)
# setup heater
pheaters = self.printer.load_object(config, "heaters")
self.heater = pheaters.setup_heater(config, "F")
self.heater_temp = config.getfloat("heater_temp", 50.0)
self.stats = self.heater.stats
# setup fan
self.fan = fan.Fan(config, default_shutdown_speed=1.0)
self.min_speed = config.getfloat(
"min_speed", 1.0, minval=0.0, maxval=1.0
)
self.idle_timeout = config.getfloat("idle_timeout", 60.0, minval=0.0)
self.set_speed = 0.0
self.timeout_timer = None
self.gcode.register_command(
"SET_HEATED_FAN_TARGET", self.cmd_SET_HEATED_FAN_TARGET
)
self.gcode.register_command("M106", self.cmd_M106)
self.gcode.register_command("M107", self.cmd_M107)
self.printer.register_event_handler("klippy:ready", self.handle_ready)
def cmd_SET_HEATED_FAN_TARGET(self, gcmd):
self.heater_temp = gcmd.get_float("TARGET", 0)
def cmd_M106(self, gcmd):
self.set_speed = gcmd.get_float("S", 255.0, minval=0.0) / 255.0
if not self.set_speed:
return self.cmd_M107(gcmd)
if self.set_speed < self.min_speed:
self.set_speed = self.min_speed
self.fan.set_speed_from_command(self.set_speed)
_, target_temp = self.heater.get_temp(self.reactor.monotonic())
# if the heater is off or has a different target temp than configured, set it
if not target_temp or target_temp != self.heater_temp:
self.heater.set_temp(self.heater_temp)
def cmd_M107(self, gcmd):
self.set_speed = 0.0
self.heater.set_temp(0)
self.start_timeout_timer()
def start_timeout_timer(self):
self.fan.set_speed_from_command(self.min_speed)
self.timeout_timer = self.reactor.register_timer(
self.handle_timeout_timer,
self.reactor.monotonic() + self.idle_timeout,
)
def handle_ready(self):
self.reactor.register_timer(
self.callback, self.reactor.monotonic() + PIN_MIN_TIME
)
def handle_timeout_timer(self, eventtime):
self.fan.set_speed_from_command(self.set_speed)
return self.reactor.NEVER
def callback(self, eventtime):
_, target_temp = self.heater.get_temp(eventtime)
# if the heater has a target and the fan is off, turn it on to min fan speed
if target_temp and not self.set_speed:
self.fan.set_speed_from_command(self.min_speed)
return eventtime + 1.0
def get_status(self, eventtime):
status = self.fan.get_status(eventtime).copy()
status.update(self.heater.get_status(eventtime))
return status
def load_config(config):
return HeatedFan(config)
@rogerlz
Copy link
Author

rogerlz commented May 2, 2024

Your HEAT_SOAK has a line _SET_FAN_SPEED, which runs M106 to turn on the fan

@levins-law
Copy link

levins-law commented May 4, 2024

I'm glad to report some success!

The rename_existing: M104.1 macro successfully responded to the Slicer's GCODE.

On the S3D Cooling tab I set the slicer to 0% fan at layer 1, 50% at layer 2, and 100% from layer 3 onward.
On the Temperature tab I then configured a new temperature controller Part Fan Heater and assigned it to toolhead T1.

What didn't work:

For some reason when the print was sent to the printer the heated fan turned on immediately at the configuration defaults (currently 150C and 45% fan speed- which is the min speed allowed above zero)
I turned that off manually, and let the print do its homing and warming up before it prints the purge line and starts printing layer 1 and that worked as expected.
Layer 1 printed fine, layer 2 started and the fan kicked on at 200C air temp, then layer 3 started at 100% fan.

The problem is that I had a fan speed set for layer 1,2, & 3 but I did not set a temperature for layer 3. I believe what happens is thus:

  • Layer 1, fan speed was zero and slicer temperature requested was zero.
  • Layer 2, fan speed was 50% and slicer temperature was 200C.
  • Layer 3, fan speed was 100% but I did not set a repeated request for 200C, so the Klipper default of 150C was used.
  • I manually set_heater_temp to 200C and the print was fine for the remaining layers.

At this point is not really a Klipper problem, as the engineer has to set fan temp and speed properly in the slicer. However, It is a failure mechanism that could be averted if the slicer understood the relationship between the cooling fan speed and the temperature control.
A simple promot letting the user know their layer number setpoints don't match might be enough. Something to think about when heated part cooling becomes more commonplace, and users demand these features.

Here is what the GCODE for the print looked like:

G90
M82
M106 S0 P0
M141 S135
M140 S217
M104 S400 T0
M104 S0 T1
START_PRINT
; process Ultem GF Thrust Block
; layer 1, Z = 0.3250
T0
G92 E0.00000
G1 E-0.40000 F1800
; feature skirt
; tool H0.2250 W0.480
G1 Z0.3250 F1002
G1 X134.537 Y134.685 F7200
G92 E0.00000
G1 E0.40000 F1800
G92 E0.00000
G1 X135.685 Y133.537 E0.07288 F750
G1 X137.168 Y132.770 E0.14788...

skipping a bunch of stuff...

...G1 X139.215 Y171.478 E68.29043
G92 E0.00000
G1 E-0.40000 F1800
; layer 2, Z = 0.5750
M106 S128 P0
M104 S200 T1
; feature inner perimeter
; tool H0.2500 W0.480
G1 Z0.5750 F1002
G1 X139.063 Y172.430 F7200
G92 E0.00000
G1 E0.40000 F1800
G92 E0.00000
G1 X138.777 Y172.385 E0.01443 F2400

skipping a bunch of stuff...

G1 X173.019 Y171.922 E78.84363
G1 X172.922 Y172.019 E78.85051
G92 E0.00000
G1 E-0.40000 F1800
; layer 3, Z = 0.8250
M106 S255 P0
; feature inner perimeter
G1 Z0.8250 F1002
G1 X172.937 Y172.680 F7200
G92 E0.00000
G1 E0.40000 F1800
G92 E0.00000
G1 X139.063 Y172.680 E1.69000 F2400
G1 X138.700 Y172.623 E1.70833

Click for Temperature settings pic

image

Click for Cooling settings pic

image

@levins-law
Copy link

I was trying to print some Extem TPI for the first time and maxing out my bed heat at 250C and hotend at 440C. I decided to try heated part cooling air with the first layer but I think it was somehow preventing the print from starting.

I figured if the filament requires 260 or 270 bed temps, and my bed can only get to 250, then hot part cooling air may help the first layer in this case.

In Simplify 3D I set the fan speed at 100% for Layer 1, and the temperature for the fan to 260C, and checked the "wait for temp to stabilize before printing" box. I haven't been doing that because it's not a CRITICAL thing to wait for and I don't want the print to pause and ooze while that comes up to temp at the start of layer 2.

I sent the GCODE and the printer seemed to get all the temps to their targets but the print just wouldn't go forward. I killed it, and went back to no 1st layer cooling so my torture with Extem could continue.

The reason this could be unrelated to heated_fan is because there were a lot of new variables. I definitely found the limit of my Chube hotend's 80W heater cartridge. I didn't know people had best results with a 120W cart so I'm waiting for that upgrade. I tried tuning the PIDs at 400C but the filament was cooling the hotend too fast and I got several ADC errors.
Then my thermo ADC board kept "erroring" but I'm pretty sure it's the jumper wires from the board to the Pi GPIO pins being janky. I'll fix that too.

The bed heater is 120vac and I think it has enough power, but it probably needs a special PID tune for this extreme filament too. I got a couple ADC errors from it because it's close to its max.

These are possibly isolated failure mechanism unrelated to heated_fan, but since I had the weird hang (no error, just inaction) I thought it prudent to examine.

@rogerlz if you would like these notes in the PR let me know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment