diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 2444c53b19ee..63c84f56db5b 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -476,6 +476,20 @@ enabled. `SET_FAN_SPEED FAN=config_name SPEED=` This command sets the speed of a fan. "speed" must be between 0.0 and 1.0. +`SET_FAN_SPEED PIN=config_name TEMPLATE= +[=]`: If `TEMPLATE` is specified then it assigns a +[display_template](Config_Reference.md#display_template) to the given +fan. For example, if one defined a `[display_template +my_fan_template]` config section then one could assign +`TEMPLATE=my_fan_template` here. The display_template should produce a +string containing a floating point number with the desired value. The +template will be continuously evaluated and the fan will be +automatically set to the resulting speed. One may set display_template +parameters to use during template evaluation (parameters will be +parsed as Python literals). If TEMPLATE is an empty string then this +command will clear any previous template assigned to the pin (one can +then use `SET_FAN_SPEED` commands to manage the values directly). + ### [filament_switch_sensor] The following command is available when a @@ -857,6 +871,20 @@ output `VALUE`. VALUE should be 0 or 1 for "digital" output pins. For PWM pins, set to a value between 0.0 and 1.0, or between 0.0 and `scale` if a scale is configured in the output_pin config section. +`SET_PIN PIN=config_name TEMPLATE= [=]`: +If `TEMPLATE` is specified then it assigns a +[display_template](Config_Reference.md#display_template) to the given +pin. For example, if one defined a `[display_template +my_pin_template]` config section then one could assign +`TEMPLATE=my_pin_template` here. The display_template should produce a +string containing a floating point number with the desired value. The +template will be continuously evaluated and the pin will be +automatically set to the resulting value. One may set display_template +parameters to use during template evaluation (parameters will be +parsed as Python literals). If TEMPLATE is an empty string then this +command will clear any previous template assigned to the pin (one can +then use `SET_PIN` commands to manage the values directly). + ### [palette2] The following commands are available when the diff --git a/klippy/extras/controller_fan.py b/klippy/extras/controller_fan.py index df141e7eb7d1..b1286b59712a 100644 --- a/klippy/extras/controller_fan.py +++ b/klippy/extras/controller_fan.py @@ -62,9 +62,7 @@ def callback(self, eventtime): self.last_on += 1 if speed != self.last_speed: self.last_speed = speed - curtime = self.printer.get_reactor().monotonic() - print_time = self.fan.get_mcu().estimated_print_time(curtime) - self.fan.set_speed(print_time + PIN_MIN_TIME, speed) + self.fan.set_speed(speed) return eventtime + 1. def load_config_prefix(config): diff --git a/klippy/extras/dotstar.py b/klippy/extras/dotstar.py index 4186534fead6..0262c13d0ade 100644 --- a/klippy/extras/dotstar.py +++ b/klippy/extras/dotstar.py @@ -1,9 +1,9 @@ # Support for "dotstar" leds # -# Copyright (C) 2019-2022 Kevin O'Connor +# Copyright (C) 2019-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -from . import bus +from . import bus, led BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 @@ -22,9 +22,8 @@ def __init__(self, config): self.spi = bus.MCU_SPI(mcu, None, None, 0, 500000, sw_spi_pins) # Initialize color data self.chain_count = config.getint('chain_count', 1, minval=1) - pled = printer.load_object(config, "led") - self.led_helper = pled.setup_helper(config, self.update_leds, - self.chain_count) + self.led_helper = led.LEDHelper(config, self.update_leds, + self.chain_count) self.prev_data = None # Register commands printer.register_event_handler("klippy:connect", self.handle_connect) diff --git a/klippy/extras/fan.py b/klippy/extras/fan.py index 14769473a371..37b531badfdf 100644 --- a/klippy/extras/fan.py +++ b/klippy/extras/fan.py @@ -66,8 +66,8 @@ def _apply_speed(self, print_time, value): return "delay", self.kick_start_time self.last_fan_value = self.last_req_value = value self.mcu_fan.set_pwm(print_time, value) - def set_speed(self, print_time, value): - self.gcrq.send_async_request(print_time, value) + def set_speed(self, value, print_time=None): + self.gcrq.send_async_request(value, print_time) def set_speed_from_command(self, value): self.gcrq.queue_gcode_request(value) def _handle_request_restart(self, print_time): diff --git a/klippy/extras/fan_generic.py b/klippy/extras/fan_generic.py index def9a77e146b..7584974ffddd 100644 --- a/klippy/extras/fan_generic.py +++ b/klippy/extras/fan_generic.py @@ -1,9 +1,9 @@ # Support fans that are controlled by gcode # -# Copyright (C) 2016-2020 Kevin O'Connor +# Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -from . import fan +from . import fan, output_pin class PrinterFanGeneric: cmd_SET_FAN_SPEED_help = "Sets the speed of a fan" @@ -12,6 +12,9 @@ def __init__(self, config): self.fan = fan.Fan(config, default_shutdown_speed=0.) self.fan_name = config.get_name().split()[-1] + # Template handling + self.template_eval = output_pin.lookup_template_eval(config) + gcode = self.printer.lookup_object("gcode") gcode.register_mux_command("SET_FAN_SPEED", "FAN", self.fan_name, @@ -20,8 +23,21 @@ def __init__(self, config): def get_status(self, eventtime): return self.fan.get_status(eventtime) + def _template_update(self, text): + try: + value = float(text) + except ValueError as e: + logging.exception("fan_generic template render error") + self.fan.set_speed(value) def cmd_SET_FAN_SPEED(self, gcmd): - speed = gcmd.get_float('SPEED', 0.) + speed = gcmd.get_float('SPEED', None, 0.) + template = gcmd.get('TEMPLATE', None) + if (speed is None) == (template is None): + raise gcmd.error("SET_FAN_SPEED must specify SPEED or TEMPLATE") + # Check for template setting + if template is not None: + self.template_eval.set_template(gcmd, self._template_update) + return self.fan.set_speed_from_command(speed) def load_config_prefix(config): diff --git a/klippy/extras/heater_fan.py b/klippy/extras/heater_fan.py index ab4c8a81e7f6..3630366e915f 100644 --- a/klippy/extras/heater_fan.py +++ b/klippy/extras/heater_fan.py @@ -33,9 +33,7 @@ def callback(self, eventtime): speed = self.fan_speed if speed != self.last_speed: self.last_speed = speed - curtime = self.printer.get_reactor().monotonic() - print_time = self.fan.get_mcu().estimated_print_time(curtime) - self.fan.set_speed(print_time + PIN_MIN_TIME, speed) + self.fan.set_speed(speed) return eventtime + 1. def load_config_prefix(config): diff --git a/klippy/extras/led.py b/klippy/extras/led.py index a2758ab8e1cf..22b51e8e6117 100644 --- a/klippy/extras/led.py +++ b/klippy/extras/led.py @@ -1,13 +1,10 @@ # Support for PWM driven LEDs # -# Copyright (C) 2019-2022 Kevin O'Connor +# Copyright (C) 2019-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import logging, ast -from .display import display - -# Time between each led template update -RENDER_TIME = 0.500 +import logging +from . import output_pin # Helper code for common LED initialization and control class LEDHelper: @@ -22,14 +19,22 @@ def __init__(self, config, update_func, led_count=1): blue = config.getfloat('initial_BLUE', 0., minval=0., maxval=1.) white = config.getfloat('initial_WHITE', 0., minval=0., maxval=1.) self.led_state = [(red, green, blue, white)] * led_count + # Support setting an led template + self.template_eval = output_pin.lookup_template_eval(config) + self.tcallbacks = [(lambda text, s=self, index=i: + s._template_update(index, text)) + for i in range(led_count)] # Register commands name = config.get_name().split()[-1] gcode = self.printer.lookup_object('gcode') gcode.register_mux_command("SET_LED", "LED", name, self.cmd_SET_LED, desc=self.cmd_SET_LED_help) - def get_led_count(self): - return self.led_count - def set_color(self, index, color): + gcode.register_mux_command("SET_LED_TEMPLATE", "LED", name, + self.cmd_SET_LED_TEMPLATE, + desc=self.cmd_SET_LED_TEMPLATE_help) + def get_status(self, eventtime=None): + return {'color_data': self.led_state} + def _set_color(self, index, color): if index is None: new_led_state = [color] * self.led_count if self.led_state == new_led_state: @@ -41,7 +46,17 @@ def set_color(self, index, color): new_led_state[index - 1] = color self.led_state = new_led_state self.need_transmit = True - def check_transmit(self, print_time): + def _template_update(self, index, text): + try: + parts = [max(0., min(1., float(f))) + for f in text.split(',', 4)] + except ValueError as e: + logging.exception("led template render error") + parts = [] + if len(parts) < 4: + parts += [0.] * (4 - len(parts)) + self._set_color(index, tuple(parts)) + def _check_transmit(self, print_time=None): if not self.need_transmit: return self.need_transmit = False @@ -62,9 +77,9 @@ def cmd_SET_LED(self, gcmd): color = (red, green, blue, white) # Update and transmit data def lookahead_bgfunc(print_time): - self.set_color(index, color) + self._set_color(index, color) if transmit: - self.check_transmit(print_time) + self._check_transmit(print_time) if sync: #Sync LED Update with print time and send toolhead = self.printer.lookup_object('toolhead') @@ -72,112 +87,15 @@ def lookahead_bgfunc(print_time): else: #Send update now (so as not to wake toolhead and reset idle_timeout) lookahead_bgfunc(None) - def get_status(self, eventtime=None): - return {'color_data': self.led_state} - -# Main LED tracking code -class PrinterLED: - def __init__(self, config): - self.printer = config.get_printer() - self.led_helpers = {} - self.active_templates = {} - self.render_timer = None - # Load templates - dtemplates = display.lookup_display_templates(config) - self.templates = dtemplates.get_display_templates() - gcode_macro = self.printer.lookup_object("gcode_macro") - self.create_template_context = gcode_macro.create_template_context - # Register handlers - gcode = self.printer.lookup_object('gcode') - gcode.register_command("SET_LED_TEMPLATE", self.cmd_SET_LED_TEMPLATE, - desc=self.cmd_SET_LED_TEMPLATE_help) - def setup_helper(self, config, update_func, led_count=1): - led_helper = LEDHelper(config, update_func, led_count) - name = config.get_name().split()[-1] - self.led_helpers[name] = led_helper - return led_helper - def _activate_timer(self): - if self.render_timer is not None or not self.active_templates: - return - reactor = self.printer.get_reactor() - self.render_timer = reactor.register_timer(self._render, reactor.NOW) - def _activate_template(self, led_helper, index, template, lparams): - key = (led_helper, index) - if template is not None: - uid = (template,) + tuple(sorted(lparams.items())) - self.active_templates[key] = (uid, template, lparams) - return - if key in self.active_templates: - del self.active_templates[key] - def _render(self, eventtime): - if not self.active_templates: - # Nothing to do - unregister timer - reactor = self.printer.get_reactor() - reactor.unregister_timer(self.render_timer) - self.render_timer = None - return reactor.NEVER - # Setup gcode_macro template context - context = self.create_template_context(eventtime) - def render(name, **kwargs): - return self.templates[name].render(context, **kwargs) - context['render'] = render - # Render all templates - need_transmit = {} - rendered = {} - template_info = self.active_templates.items() - for (led_helper, index), (uid, template, lparams) in template_info: - color = rendered.get(uid) - if color is None: - try: - text = template.render(context, **lparams) - parts = [max(0., min(1., float(f))) - for f in text.split(',', 4)] - except Exception as e: - logging.exception("led template render error") - parts = [] - if len(parts) < 4: - parts += [0.] * (4 - len(parts)) - rendered[uid] = color = tuple(parts) - need_transmit[led_helper] = 1 - led_helper.set_color(index, color) - context.clear() # Remove circular references for better gc - # Transmit pending changes - for led_helper in need_transmit.keys(): - led_helper.check_transmit(None) - return eventtime + RENDER_TIME cmd_SET_LED_TEMPLATE_help = "Assign a display_template to an LED" def cmd_SET_LED_TEMPLATE(self, gcmd): - led_name = gcmd.get("LED") - led_helper = self.led_helpers.get(led_name) - if led_helper is None: - raise gcmd.error("Unknown LED '%s'" % (led_name,)) - led_count = led_helper.get_led_count() - index = gcmd.get_int("INDEX", None, minval=1, maxval=led_count) - template = None - lparams = {} - tpl_name = gcmd.get("TEMPLATE") - if tpl_name: - template = self.templates.get(tpl_name) - if template is None: - raise gcmd.error("Unknown display_template '%s'" % (tpl_name,)) - tparams = template.get_params() - for p, v in gcmd.get_command_parameters().items(): - if not p.startswith("PARAM_"): - continue - p = p.lower() - if p not in tparams: - raise gcmd.error("Invalid display_template parameter: %s" - % (p,)) - try: - lparams[p] = ast.literal_eval(v) - except ValueError as e: - raise gcmd.error("Unable to parse '%s' as a literal" % (v,)) + index = gcmd.get_int("INDEX", None, minval=1, maxval=self.led_count) + set_template = self.template_eval.set_template if index is not None: - self._activate_template(led_helper, index, template, lparams) + set_template(gcmd, self.tcallbacks[index-1], self._check_transmit) else: - for i in range(led_count): - self._activate_template(led_helper, i+1, template, lparams) - self._activate_timer() + for i in range(self.led_count): + set_template(gcmd, self.tcallbacks[i], self._check_transmit) PIN_MIN_TIME = 0.100 MAX_SCHEDULE_TIME = 5.0 @@ -205,8 +123,7 @@ def __init__(self, config): % (config.get_name(),)) self.last_print_time = 0. # Initialize color data - pled = printer.load_object(config, "led") - self.led_helper = pled.setup_helper(config, self.update_leds, 1) + self.led_helper = LEDHelper(config, self.update_leds, 1) self.prev_color = color = self.led_helper.get_status()['color_data'][0] for idx, mcu_pin in self.pins: mcu_pin.setup_start_value(color[idx], 0.) @@ -225,8 +142,5 @@ def update_leds(self, led_state, print_time): def get_status(self, eventtime=None): return self.led_helper.get_status(eventtime) -def load_config(config): - return PrinterLED(config) - def load_config_prefix(config): return PrinterPWMLED(config) diff --git a/klippy/extras/neopixel.py b/klippy/extras/neopixel.py index b6daeb4d29e3..e72b8a91a188 100644 --- a/klippy/extras/neopixel.py +++ b/klippy/extras/neopixel.py @@ -4,6 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging +from . import led BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 @@ -40,9 +41,7 @@ def __init__(self, config): if len(self.color_map) > MAX_MCU_SIZE: raise config.error("neopixel chain too long") # Initialize color data - pled = printer.load_object(config, "led") - self.led_helper = pled.setup_helper(config, self.update_leds, - chain_count) + self.led_helper = led.LEDHelper(config, self.update_leds, chain_count) self.color_data = bytearray(len(self.color_map)) self.update_color_data(self.led_helper.get_status()['color_data']) self.old_color_data = bytearray([d ^ 1 for d in self.color_data]) diff --git a/klippy/extras/output_pin.py b/klippy/extras/output_pin.py index 5e03b423100c..24d27a62de4d 100644 --- a/klippy/extras/output_pin.py +++ b/klippy/extras/output_pin.py @@ -3,9 +3,15 @@ # Copyright (C) 2017-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. +import logging, ast +from .display import display + + +###################################################################### +# G-Code request queuing helper +###################################################################### PIN_MIN_TIME = 0.100 -MAX_SCHEDULE_TIME = 5.0 # Helper code to queue g-code requests class GCodeRequestQueue: @@ -52,7 +58,10 @@ def _queue_request(self, print_time, value): def queue_gcode_request(self, value): self.toolhead.register_lookahead_callback( (lambda pt: self._queue_request(pt, value))) - def send_async_request(self, print_time, value): + def send_async_request(self, value, print_time=None): + if print_time is None: + systime = self.printer.get_reactor().monotonic() + print_time = self.mcu.estimated_print_time(systime + PIN_MIN_TIME) while 1: next_time = max(print_time, self.next_min_flush_time) # Invoke callback for the request @@ -67,6 +76,109 @@ def send_async_request(self, print_time, value): if action != "delay": break + +###################################################################### +# Template evaluation helper +###################################################################### + +# Time between each template update +RENDER_TIME = 0.500 + +# Main template evaluation code +class PrinterTemplateEvaluator: + def __init__(self, config): + self.printer = config.get_printer() + self.active_templates = {} + self.render_timer = None + # Load templates + dtemplates = display.lookup_display_templates(config) + self.templates = dtemplates.get_display_templates() + gcode_macro = self.printer.load_object(config, "gcode_macro") + self.create_template_context = gcode_macro.create_template_context + def _activate_timer(self): + if self.render_timer is not None or not self.active_templates: + return + reactor = self.printer.get_reactor() + self.render_timer = reactor.register_timer(self._render, reactor.NOW) + def _activate_template(self, callback, template, lparams, flush_callback): + if template is not None: + uid = (template,) + tuple(sorted(lparams.items())) + self.active_templates[callback] = ( + uid, template, lparams, flush_callback) + return + if callback in self.active_templates: + del self.active_templates[callback] + def _render(self, eventtime): + if not self.active_templates: + # Nothing to do - unregister timer + reactor = self.printer.get_reactor() + reactor.unregister_timer(self.render_timer) + self.render_timer = None + return reactor.NEVER + # Setup gcode_macro template context + context = self.create_template_context(eventtime) + def render(name, **kwargs): + return self.templates[name].render(context, **kwargs) + context['render'] = render + # Render all templates + flush_callbacks = {} + rendered = {} + template_info = self.active_templates.items() + for callback, (uid, template, lparams, flush_callback) in template_info: + text = rendered.get(uid) + if text is None: + try: + text = template.render(context, **lparams) + except Exception as e: + logging.exception("display template render error") + text = "" + rendered[uid] = text + if flush_callback is not None: + flush_callbacks[flush_callback] = 1 + callback(text) + context.clear() # Remove circular references for better gc + # Invoke optional flush callbacks + for flush_callback in flush_callbacks.keys(): + flush_callback() + return eventtime + RENDER_TIME + def set_template(self, gcmd, callback, flush_callback=None): + template = None + lparams = {} + tpl_name = gcmd.get("TEMPLATE") + if tpl_name: + template = self.templates.get(tpl_name) + if template is None: + raise gcmd.error("Unknown display_template '%s'" % (tpl_name,)) + tparams = template.get_params() + for p, v in gcmd.get_command_parameters().items(): + if not p.startswith("PARAM_"): + continue + p = p.lower() + if p not in tparams: + raise gcmd.error("Invalid display_template parameter: %s" + % (p,)) + try: + lparams[p] = ast.literal_eval(v) + except ValueError as e: + raise gcmd.error("Unable to parse '%s' as a literal" % (v,)) + self._activate_template(callback, template, lparams, flush_callback) + self._activate_timer() + +def lookup_template_eval(config): + printer = config.get_printer() + te = printer.lookup_object("template_evaluator", None) + if te is None: + te = PrinterTemplateEvaluator(config) + printer.add_object("template_evaluator", te) + return te + + +###################################################################### +# Main output pin handling +###################################################################### + +MAX_SCHEDULE_TIME = 5.0 + class PrinterOutputPin: def __init__(self, config): self.printer = config.get_printer() @@ -93,6 +205,8 @@ def __init__(self, config): # Create gcode request queue self.gcrq = GCodeRequestQueue(config, self.mcu_pin.get_mcu(), self._set_pin) + # Template handling + self.template_eval = lookup_template_eval(config) # Register commands pin_name = config.get_name().split()[1] gcode = self.printer.lookup_object('gcode') @@ -109,10 +223,23 @@ def _set_pin(self, print_time, value): self.mcu_pin.set_pwm(print_time, value) else: self.mcu_pin.set_digital(print_time, value) + def _template_update(self, text): + try: + value = float(text) + except ValueError as e: + logging.exception("output_pin template render error") + self.gcrq.send_async_request(value) cmd_SET_PIN_help = "Set the value of an output pin" def cmd_SET_PIN(self, gcmd): + value = gcmd.get_float('VALUE', None, minval=0., maxval=self.scale) + template = gcmd.get('TEMPLATE', None) + if (value is None) == (template is None): + raise gcmd.error("SET_PIN command must specify VALUE or TEMPLATE") + # Check for template setting + if template is not None: + self.template_eval.set_template(gcmd, self._template_update) + return # Read requested value - value = gcmd.get_float('VALUE', minval=0., maxval=self.scale) value /= self.scale if not self.is_pwm and value not in [0., 1.]: raise gcmd.error("Invalid pin value") diff --git a/klippy/extras/pca9533.py b/klippy/extras/pca9533.py index f84f9a65ad37..a94e1334e056 100644 --- a/klippy/extras/pca9533.py +++ b/klippy/extras/pca9533.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -from . import bus +from . import bus, led BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 @@ -16,8 +16,7 @@ class PCA9533: def __init__(self, config): self.printer = config.get_printer() self.i2c = bus.MCU_I2C_from_config(config, default_addr=98) - pled = self.printer.load_object(config, "led") - self.led_helper = pled.setup_helper(config, self.update_leds, 1) + self.led_helper = led.LEDHelper(config, self.update_leds, 1) self.i2c.i2c_write([PCA9533_PWM0, 85]) self.i2c.i2c_write([PCA9533_PWM1, 170]) self.update_leds(self.led_helper.get_status()['color_data'], None) diff --git a/klippy/extras/pca9632.py b/klippy/extras/pca9632.py index 00876dc18aca..8a3551cf242a 100644 --- a/klippy/extras/pca9632.py +++ b/klippy/extras/pca9632.py @@ -3,7 +3,7 @@ # Copyright (C) 2022 Ricardo Alcantara # # This file may be distributed under the terms of the GNU GPLv3 license. -from . import bus, mcp4018 +from . import bus, led, mcp4018 BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 @@ -34,8 +34,7 @@ def __init__(self, config): raise config.error("Invalid color_order '%s'" % (color_order,)) self.color_map = ["RGBW".index(c) for c in color_order] self.prev_regs = {} - pled = printer.load_object(config, "led") - self.led_helper = pled.setup_helper(config, self.update_leds, 1) + self.led_helper = led.LEDHelper(config, self.update_leds, 1) printer.register_event_handler("klippy:connect", self.handle_connect) def reg_write(self, reg, val, minclock=0): if self.prev_regs.get(reg) == val: diff --git a/klippy/extras/temperature_fan.py b/klippy/extras/temperature_fan.py index aee94f281594..a9aa4d0bab52 100644 --- a/klippy/extras/temperature_fan.py +++ b/klippy/extras/temperature_fan.py @@ -46,7 +46,7 @@ def __init__(self, config): self.cmd_SET_TEMPERATURE_FAN_TARGET, desc=self.cmd_SET_TEMPERATURE_FAN_TARGET_help) - def set_speed(self, read_time, value): + def set_tf_speed(self, read_time, value): if value <= 0.: value = 0. elif value < self.min_speed: @@ -60,7 +60,7 @@ def set_speed(self, read_time, value): speed_time = read_time + self.speed_delay self.next_speed_time = speed_time + 0.75 * MAX_FAN_TIME self.last_speed_value = value - self.fan.set_speed(speed_time, value) + self.fan.set_speed(value, speed_time) def temperature_callback(self, read_time, temp): self.last_temp = temp self.control.temperature_callback(read_time, temp) @@ -128,10 +128,10 @@ def temperature_callback(self, read_time, temp): and temp <= target_temp-self.max_delta): self.heating = True if self.heating: - self.temperature_fan.set_speed(read_time, 0.) + self.temperature_fan.set_tf_speed(read_time, 0.) else: - self.temperature_fan.set_speed(read_time, - self.temperature_fan.get_max_speed()) + self.temperature_fan.set_tf_speed( + read_time, self.temperature_fan.get_max_speed()) ###################################################################### # Proportional Integral Derivative (PID) control algo @@ -171,7 +171,7 @@ def temperature_callback(self, read_time, temp): # Calculate output co = self.Kp*temp_err + self.Ki*temp_integ - self.Kd*temp_deriv bounded_co = max(0., min(self.temperature_fan.get_max_speed(), co)) - self.temperature_fan.set_speed( + self.temperature_fan.set_tf_speed( read_time, max(self.temperature_fan.get_min_speed(), self.temperature_fan.get_max_speed() - bounded_co)) # Store state for next measurement