###############################
# ERCF module
###############################

[ercf]
# Encoder
# encoder_pin: ^P1.0
encoder_pin: ercf:PA6         # easy brd
# encoder_resolution: 1.365188  # default
encoder_resolution: 1.349528  # from `ercf_calibrate_encoder`
# Speeds and accels
# Long moves are faster than the small ones
# 100mm/s should be "quiet" with the NEMA14 motor or a NEMA17 pancake, but you can go lower if your really want it to be low noise
# Tested without any issue at 200mm/s, but it's noisy. I'm running mine at 170mm/s for long moves and 50mm/s for short moves.
long_moves_speed: 100 # mm/s. Default value is 100mm/s.
long_moves_accel: 400 # mm/s². Default value is 400mm/s²
short_moves_speed: 25 # mm/s. Default value is 25mm/s.
short_moves_accel: 400 # mm/s². Default value is 400mm/s²

[gcode_macro ERCF_VAR]
# =======================================================
# ================== VALUES TO ADJUST ===================
# =======================================================
#
# ==== Toolhead specific values ====
#
# Distance between the end of the reverse bowden and the toolhead sensor. Value is toolhead specific.
# Tested values :
# Galileo Clockwork with ERCF V1.1 sensor (hall effect) : 27.0
# LGX on AfterBurner with ERCF V1.1 sensor (hall effect) : 44.0
# AfterBurner Clockwork with ERCF V1.1 sensor (hall effect) : 36.0
variable_end_of_bowden_to_sensor: 40 # xk
# Length from the sensor to the nozzle melt pool.
# Reduce this value if there are blobs of filament on each load, before the purge on the tower.
# Increase this value if there are big gaps on the purge tower (i.e. if it takes time for the filament to get pushed out after a swap)
# Tested values :
# Galileo Clockwork with ERCF 1.1 sensor (hall effect) & Dragon Normal Flow : 60.5
# LGX on AfterBurner with ERCF 1.1 sensor (hall effect) & Dragon Normal Flow : 55.6
# AfterBurner Clockwork with ERCF 1.1 sensor (hall effect) & Dragon Normal Flow : 54.0
variable_sensor_to_nozzle: 54
#
# ==== Values to tune ====
#
# Tool position for the selector. This has to be tuned manually. Please scale this array to the number of tools you have
# variable_colorselector = [ 2.4, 23.2, 44.0]                   # 3 tools
variable_colorselector = [2.4, 23.2, 44.0, 69.6, 91.2, 112.0] # 6 tools
# Base value for the loading length used by the auto-calibration macro
# Please use a value SMALLER than the real reverse bowden length (like 50mm less)
variable_min_bowden_length: 850
# Servo angle for the Up position (i.e. tool disengaged). Refer to the manual to know how to properly tune this value
# Default values:
# MG90S servo : 30
# SAVOX SH0255MG : 140
variable_servo_up_angle: 30
# Servo angle for the Down position (i.e. tool engaged). Refer to the manual to know how to properly tune this value
# Default values:
# MG90S servo : 140
# SAVOX SH0255MG : 30
variable_servo_down_angle: 130
# Options to use or not
# Beware that the clog detection and endless spool mode are in BETA mode for now
# Use at your own risk (beware of the involved macros and the pause(s) and resume ones)
# Put 0 to disable, 1 to enable
variable_clog_detection: 0
variable_endless_spool_mode: 0
# =======================================================
# ============ END OF VALUES TO ADJUST ==================
# =======================================================
# You shouldn't have to change anything below this point

# Things that you shouldn't have to change
variable_unload_modifier: 9.0                                                               # Modifier to adjust the ERCF park position (where the filament ends when doing an unload)
variable_min_temp_extruder: 210                                                             # Temp used during the auto-calibration macro, to ensure we can move the extruder (but not really extruding)
variable_extruder_eject_temp: 240                                                           # Temp used during filament ejection (in the ERCF_HOME macro, if a filament is detected in the toolhead)
variable_timeout_pause: 72000                                                               # Time out used by the ERCF_PAUSE
variable_disable_heater: 600                                                                # Delay after which the hotend heater is disabled in the ERCF_PAUSE state
gcode:

[save_variables]
filename: /home/pi/klipper_config/ercf/vars.cfg

###############################
# ERCF Calibration macros
###############################
[gcode_macro ERCF_CALIB_SELECTOR]
description: Calibration of the selector position for a defined Tool
gcode:
    ERCF_SERVO_UP

    {% set move_length=(20.0 + (params.TOOL|int + 1)*21.0 + ((params.TOOL|int + 1)/3)*5.0) %}

    M118 Measuring the selector position for tool {params.TOOL}
    ERCF_GET_SELECTOR_POS REF={move_length|int}

    ERCF_MOTORS_OFF

[gcode_macro ERCF_CALIBRATE]
description: Complete calibration of all ERCF Tools
gcode:
    SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=0
    M118 Start the complete auto calibration...
    M118 First home the ERCF
    ERCF_HOME

    {% for chan in range(printer["gcode_macro ERCF_VAR"].colorselector|length) %}
        ERCF_CALIBRATE_SINGLE TOOL={chan|int}
    {% endfor %}

    M118 End of the complete auto calibration!
    M118 Please reload the firmware for the calibration to be active!

[gcode_macro ERCF_CALIBRATE_SINGLE]
description: Calibration of a single ERCF Tool
gcode:
    SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=0
    {% if params.TOOL|int >= 0 and params.TOOL|int < printer["gcode_macro ERCF_VAR"].colorselector|length %}
        {% if printer["gcode_macro ERCF_HOME"].home == -1 %}
            M118 First home the ERCF
            ERCF_HOME
        {% endif %}

        ERCF_SELECT_TOOL TOOL={params.TOOL}
        ERCF_SET_STEPS RATIO=1.0

        {% if params.TOOL|int == 0 %}
            {% if printer['extruder'].temperature < printer["gcode_macro ERCF_VAR"].min_temp_extruder %}
                M109 S{printer["gcode_macro ERCF_VAR"].min_temp_extruder|int}
            {% endif %}

            M118 Calibrating reference tool {params.TOOL}
            ERCF_LOAD LENGTH={printer["gcode_macro ERCF_VAR"].min_bowden_length}
            ERCF_HOME_EXTRUDER TOTAL_LENGTH=400 STEP_LENGTH=0.5

            ERCF_CALIB_SAVE_VAR TOOL={params.TOOL}

            G91
            G92 E0
            MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
            MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} SPEED=25 ACCEL=0 SYNC=0
            G1 E-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} F1500.0

            ERCF_CALIB_UNLOAD

            ERCF_BUZZ_GEAR_MOTOR
            ERCF_IS_FILAMENT_STUCK_IN_ERCF

            ERCF_UNSELECT_TOOL
        {% else %}
            M118 Calibrating tool {params.TOOL}

            ERCF_LOAD LENGTH={printer["gcode_macro ERCF_VAR"].min_bowden_length|float - 100.0}
            ERCF_CALIB_SAVE_VAR tool={params.TOOL}

            ERCF_CALIB_UNLOAD LENGTH={printer["gcode_macro ERCF_VAR"].min_bowden_length|float - 100.0 + 27.0}
        {% endif %}

    {% else %}
        M118 Tool out of range
    {% endif %}

[gcode_macro ERCF_CALIB_SAVE_VAR]
description: Saving ERCF calibration values
gcode:
    {% if params.TOOL|int == 0 %}
        M118 Tool {params.TOOL} calibration value is {printer['ercf'].encoder_pos|float}
        SAVE_VARIABLE VARIABLE=ercf_calib_ref VALUE={printer['ercf'].encoder_pos|float} # this is the reference value
        SAVE_VARIABLE VARIABLE=ercf_calib_{params.TOOL|int} VALUE=1.0
        SET_GCODE_VARIABLE MACRO=ERCF_CALIB_UNLOAD VARIABLE=ref VALUE={printer['ercf'].encoder_pos|float}
        SET_GCODE_VARIABLE MACRO=ERCF_CALIB_UNLOAD VARIABLE=ratio VALUE=1.0
    {% else %}
        {% set ratio = (printer["gcode_macro ERCF_VAR"].min_bowden_length|float - 100.0) / printer['ercf'].encoder_pos|float %}
        M118 Tool {params.TOOL} ratio is {ratio|float}
        SAVE_VARIABLE VARIABLE=ercf_calib_{params.TOOL|int} VALUE={ratio|float}
        SET_GCODE_VARIABLE MACRO=ERCF_CALIB_UNLOAD VARIABLE=ratio VALUE={ratio|float}
    {% endif %}

[gcode_macro ERCF_CALIB_UNLOAD]
description: Filament unload during ERCF calibration
variable_ratio: 0.0
variable_ref: 0.0
gcode:
    {% set unload_length = params.LENGTH|default(0.0)|float %}
    {% if unload_length|float == 0.0 %}
        ERCF_UNLOAD LENGTH={(ref|float)*(ratio|float) - printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float + 27.0}
    {% else %}
        {% if ratio|float < 1.0 %} # No correction move is done in case of "over targeting", hence the correction ratio is not applied during this unload sequence
            ERCF_UNLOAD LENGTH={(unload_length|float)}
        {% else %}
            ERCF_UNLOAD LENGTH={(unload_length|float)*(ratio|float)}
        {% endif %}
    {% endif %}

[respond]
default_type: command

###############################
# ERCF servo
###############################
# Push the top hat down (enable the bondtech gears) while "buzzing" the gear motor to ensure proper BMG gear meshing
[gcode_macro ERCF_SERVO_DOWN]
description: Engage the ERCF gear
gcode:
    MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
    MANUAL_STEPPER STEPPER=gear_stepper MOVE=0.5 SPEED=25 ACCEL=0 SYNC=0
    SET_SERVO SERVO=ercf_servo ANGLE={printer["gcode_macro ERCF_VAR"].servo_down_angle}
    G4 P200
    MANUAL_STEPPER STEPPER=gear_stepper MOVE=0.0 SPEED=25 ACCEL=0 SYNC=0
    G4 P100
    MANUAL_STEPPER STEPPER=gear_stepper MOVE=-0.5 SPEED=25 ACCEL=0 SYNC=0
    G4 P100
    MANUAL_STEPPER STEPPER=gear_stepper MOVE=0.0 SPEED=25 ACCEL=0
    SET_SERVO SERVO=ercf_servo WIDTH=0.0

# Pull the top hat up (disengage the bondtech gears)
[gcode_macro ERCF_SERVO_UP]
description: Disengage the ERCF gear
gcode:
    SET_SERVO SERVO=ercf_servo ANGLE={printer["gcode_macro ERCF_VAR"].servo_up_angle}
    G4 P250
    SET_SERVO SERVO=ercf_servo WIDTH=0.0

###############################
# ERCF motors
###############################
[gcode_macro ERCF_MOTORS_OFF]
description: Turn off both ERCF motors
gcode:
    MANUAL_STEPPER STEPPER=gear_stepper ENABLE=0
    MANUAL_STEPPER STEPPER=selector_stepper ENABLE=0

###############################
# PAUSE MACROS
# ERCF_PAUSE is called when an human intervention is needed
# use ERCF_UNLOCK to start the manual intervention
# and use RESUME when the intervention is over to resume the current print
###############################

# Stop the delayed stop of the heater
[gcode_macro ERCF_UNLOCK]
description: Unlock ERCF operations
gcode:
   M118 Unlock the ERCF
   SET_GCODE_VARIABLE MACRO=ERCF_PAUSE VARIABLE=is_paused VALUE=0
   UPDATE_DELAYED_GCODE ID=disable_heater DURATION=0
   ERCF_UNSELECT_TOOL
   M104 S{printer["gcode_macro ERCF_PAUSE"].extruder_temp}
   RESTORE_GCODE_STATE NAME=ERCF_state
   M118 Refer to the manual before resuming the print

[delayed_gcode disable_heater]
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int != 0 %}
        M118 Disable extruder heater
        M104 S0
    {% endif %}

# Pause the ERCF, park the extruder at the parking position
# Save the current state and start the delayed stop of the heated
# modify the timeout of the printer accordingly to timeout_pause
[gcode_macro ERCF_PAUSE]
description: Pause the current print and lock the ERCF operations
variable_is_paused: 0
variable_extruder_temp: 0
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        SET_GCODE_VARIABLE MACRO=ERCF_PAUSE VARIABLE=extruder_temp VALUE={printer.extruder.target}
        SET_GCODE_VARIABLE MACRO=ERCF_PAUSE VARIABLE=is_paused VALUE=1
        SET_IDLE_TIMEOUT TIMEOUT={printer["gcode_macro ERCF_VAR"].timeout_pause}
        UPDATE_DELAYED_GCODE ID=disable_heater DURATION={printer["gcode_macro ERCF_VAR"].disable_heater}
        M118 An issue with the ERCF has been detected and the ERCF has been PAUSED
        M118 When you intervene to fix the issue, first call the "ERCF_UNLOCK" Gcode
        M118 Refer to the manual before resuming the print
        SAVE_GCODE_STATE NAME=ERCF_state
        SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=0
        PAUSE
    {% else %}
        M118 already paused
    {% endif %}

############################################
# Changing tool macros
# ERCF_CHANGE_TOOL_STANDALONE TOOL=XX to change filament outside of a print
# ERCF_CHANGE_TOOL_SLICER TOOL=XX will be called automatically (from the ACTIVATE_EXTRUDER gcode from SuperSlicer), don't use that for "manual" filament swap
# if the new extruder is different from the current extruder :
#     eject the filament if needed
#     load the new one
###########################################
[gcode_macro T0]
gcode:
    ERCF_CHANGE_TOOL TOOL=0

[gcode_macro T1]
gcode:
    ERCF_CHANGE_TOOL TOOL=1

[gcode_macro T2]
gcode:
    ERCF_CHANGE_TOOL TOOL=2

[gcode_macro T3]
gcode:
    ERCF_CHANGE_TOOL TOOL=3

[gcode_macro T4]
gcode:
    ERCF_CHANGE_TOOL TOOL=4

[gcode_macro T5]
gcode:
    ERCF_CHANGE_TOOL TOOL=5

[gcode_macro T6]
gcode:
    ERCF_CHANGE_TOOL TOOL=6

[gcode_macro T7]
gcode:
    ERCF_CHANGE_TOOL TOOL=7

[gcode_macro T8]
gcode:
    ERCF_CHANGE_TOOL TOOL=8

[gcode_macro ERCF_CHANGE_TOOL]
description: Perform a tool swap
gcode:
    {% if printer.idle_timeout.state == "Printing" %}
        ERCF_CHANGE_TOOL_SLICER TOOL={params.TOOL|int}
    {% else %}
        ERCF_CHANGE_TOOL_STANDALONE TOOL={params.TOOL|int}
    {% endif %}

[gcode_macro ERCF_CHANGE_TOOL_SLICER]
description: Perform a tool swap during a print
gcode:
    SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=0
    {% if printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int != params.TOOL|int %}
        # Add a variable 'SwapCounter' in your PRINT_START macro to display this info
        # {% set newcounter = (printer["gcode_macro PRINT_START"].swapcounter|int + 1) %}
        # SET_GCODE_VARIABLE MACRO=PRINT_START VARIABLE=swapcounter VALUE={newcounter}
        # M118 Swap {newcounter|int}
        M117 Change Tool T{params.TOOL|int}
        ERCF_UNLOAD_TOOL
        ERCF_LOAD_TOOL TOOL={params.TOOL|int}
        ERCF_CHANGE_TOOL_SLICER_END
    {% endif %}

[gcode_macro ERCF_CHANGE_TOOL_SLICER_END]
gcode:
        {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
            {% if printer["gcode_macro ERCF_VAR"].clog_detection|int == 1 %}
                SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=1
            {% endif %}
        {% endif %}

[gcode_macro ERCF_CHANGE_TOOL_STANDALONE]
description: Perform a tool swap out of a print
gcode:
    SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=0
    {% if printer["gcode_macro ERCF_HOME"].home == -1 %}
        M118 ERCF not homed, homing it...
        ERCF_HOME
        M117 Change Tool T{params.TOOL|int}
        ERCF_LOAD_TOOL TOOL={params.TOOL|int}
    {% elif printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int != params.TOOL|int %}
        M117 Change Tool T{params.TOOL|int}
        {% if printer['filament_switch_sensor toolhead_sensor'].filament_detected == True %}
            M118 Unloading current filament
            {% if printer['extruder'].temperature < 178 %}
                M118 Preheat Nozzle
                M109 S{printer["gcode_macro ERCF_VAR"].extruder_eject_temp}
            {% endif %}
            ERCF_EJECT
        {% endif %}

        ERCF_LOAD_TOOL TOOL={params.TOOL|int}
    {% endif %}

############################################
# Unloading/Loading Macros
############################################

# Load filament from ERCF to nozzle
[gcode_macro ERCF_LOAD_TOOL]
description: Load the filament from the ERCF to the toolhead
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        {% if printer["gcode_macro ERCF_HOME"].home == -1 %}
            M118 ERCF not homed, homing it...
            ERCF_HOME
        {% endif %}
        M118 Loading tool {params.TOOL|int} ...
        ERCF_SELECT_TOOL TOOL={params.TOOL|int}
        {% set ercf_params = printer.save_variables.variables %}
        ERCF_SET_STEPS RATIO={ercf_params['ercf_calib_%s' % (params.TOOL|string)]}
        M118 Loading filament from ERCF to extruder ...
        {% set ercf_params = printer.save_variables.variables %}
        ERCF_LOAD LENGTH={ercf_params.ercf_calib_ref|float - printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float}
        ERCF_LOAD_FILAMENT_IN_EXTRUDER
    {% endif %}

# Unload filament from nozzle to ERCF, using built-in tip forming macro
[gcode_macro ERCF_EJECT]
description: Eject the filament out of a print and park it into the ERCF
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        {% if printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int != -1 %}
            M118 Unloading tool {printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int} ...
            {% if printer['filament_switch_sensor toolhead_sensor'].filament_detected == True %}
                ERCF_UNLOAD_FILAMENT_IN_EXTRUDER_WITH_TIP_FORMING
                {% set ercf_params = printer.save_variables.variables %}
                ERCF_SET_STEPS RATIO={ercf_params['ercf_calib_%s' % (printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|string)]}
                ERCF_UNLOAD LENGTH={ercf_params.ercf_calib_ref|float - printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float + printer["gcode_macro ERCF_VAR"].unload_modifier|float}
                ERCF_UNSELECT_TOOL
            {% else %}
                ERCF_EJECT_UNKNOW_STATE
            {% endif %}
        {% else %}
            ERCF_EJECT_UNKNOW_STATE
        {% endif %}
    {% endif %}

# Unload filament from nozzle to ERCF, using SuperSlicer ramming
[gcode_macro ERCF_UNLOAD_TOOL]
description: Eject the filament during a print and park it into the ERCF
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        {% if printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int != -1 %}
            M118 ERCF_UNLOAD_TOOL Unload tool {printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int} ...
            ERCF_HOME_EXTRUDER
            ERCF_SELECT_TOOL TOOL={printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int}
            {% set ercf_params = printer.save_variables.variables %}
            ERCF_SET_STEPS RATIO={ercf_params['ercf_calib_%s' % (printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|string)]}
            G91
            G92 E0
            MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
            MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} SPEED=25 ACCEL=0 SYNC=0
            M118 ERCF_UNLOAD_TOOL G1 E-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} F1500.0
            G1 E-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} F1500.0
            {% set ercf_params = printer.save_variables.variables %}
            ERCF_UNLOAD LENGTH={ercf_params.ercf_calib_ref|float - printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float + printer["gcode_macro ERCF_VAR"].unload_modifier|float}
            ERCF_UNSELECT_TOOL
        {% endif %}
    {% endif %}

############################################
# Select/Unselect a tool
# move the selector (if needed) to the requested tool
############################################

# Select a tool. move the idler and then move the color selector (if needed)
[gcode_macro ERCF_SELECT_TOOL]
description: Move the selector to the Tool and select it
variable_tool_selected: -1
variable_color_selected: -1
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        {% if printer["gcode_macro ERCF_HOME"].home != -1 %}
            M118 ERCF_SELECT_TOOL Select Tool {params.TOOL} ...
            ERCF_SERVO_UP
            MANUAL_STEPPER STEPPER=selector_stepper MOVE={printer["gcode_macro ERCF_VAR"].colorselector[params.TOOL|int]}
            SET_GCODE_VARIABLE MACRO=ERCF_SELECT_TOOL VARIABLE=tool_selected VALUE={params.TOOL}
            SET_GCODE_VARIABLE MACRO=ERCF_SELECT_TOOL VARIABLE=color_selected VALUE={params.TOOL}
            ERCF_SERVO_DOWN
            M118 ERCF_SELECT_TOOL Tool {params.TOOL} Enabled
        {% else %}
            M118 ERCF_SELECT_TOOL Could not select tool, ERCF is not homed
        {% endif %}
    {% endif %}

# Unselect a tool
[gcode_macro ERCF_UNSELECT_TOOL]
description: Unselect current Tool
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        {% if printer["gcode_macro ERCF_HOME"].home != -1 %}
            ERCF_SERVO_UP
            SET_GCODE_VARIABLE MACRO=ERCF_SELECT_TOOL VARIABLE=tool_selected VALUE=-1
            {% if printer['filament_switch_sensor toolhead_sensor'].filament_detected == False %}
                SET_GCODE_VARIABLE MACRO=ERCF_SELECT_TOOL VARIABLE=color_selected VALUE=-1
            {% endif %}
            ERCF_SET_STEPS RATIO=1.0
        {% else %}
            M118 ERCF_UNSELECT_TOOL Could not unselect tool, ERCF is not homed
        {% endif %}
    {% endif %}

############################################
# Loading/Unloading part FROM/TO EXTRUDER TO/FROM NOZZLE
############################################

# Load the filament into the extruder
# Call ERCF_PAUSE if the filament is not detected by the toolhead sensor
[gcode_macro ERCF_LOAD_FILAMENT_IN_EXTRUDER]
description: Load filament from the toolhead entrance to the nozzle
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        {% if printer.extruder.temperature > printer["gcode_macro ERCF_VAR"].min_temp_extruder %}
            M118 ERCF_LOAD_F_I_E Loading Filament...
            G91
            G92 E0
            MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
            MANUAL_STEPPER STEPPER=gear_stepper MOVE={printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float - 7} SPEED=25 ACCEL=0 SYNC=0
            G1 E{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float - 7} F1500.0
            G4 P100
            ERCF_HOME_EXTRUDER TOTAL_LENGTH=30.0 STEP_LENGTH=0.5
            ERCF_UNSELECT_TOOL
            G1 E15 F1800
            G1 E{printer["gcode_macro ERCF_VAR"].sensor_to_nozzle - 20} F1800
            G1 E5 F600
            G92 E0
            G90
            M118 ERCF_LOAD_F_I_E Load Complete
        {% else %}
            M118 ERCF_LOAD_F_I_E Extruder too cold
            ERCF_PAUSE
        {% endif %}
    {% endif %}

# StandAlone cooling moves to extract proper filament tip
[gcode_macro ERCF_FORM_TIP_STANDALONE]
description: Generic tip forming macro
gcode:
    {% set COOLING_TUBE_LENGTH = params.COOLING_TUBE_LENGTH|default(15) %} # Dragon ST: 15, Dragon HF: 10, Mosquito: 20
    {% set COOLING_TUBE_RETRACTION = params.COOLING_TUBE_RETRACTION|default(35) %} # Dragon ST: 35, Dragon HF: 30, Mosquito: 38
    {% set INITIAL_COOLING_SPEED = params.INITIAL_COOLING_SPEED|default(10) %}
    {% set FINAL_COOLING_SPEED = params.FINAL_COOLING_SPEED|default(50) %}
    {% set COOLING_MOVES = params.COOLING_MOVES|default(5) %}
    {% set TOOLCHANGE_TEMP = params.TOOLCHANGE_TEMP|default(0) %}
    {% set USE_SKINNYDIP = params.USE_SKINNYDIP|default(1) %}
    {% set USE_FAST_SKINNYDIP = params.USE_FAST_SKINNYDIP|default(1) %}
    {% set SKINNYDIP_DISTANCE = params.SKINNYDIP_DISTANCE|default(26) %}
    {% set DIP_INSERTION_SPEED = params.DIP_INSERTION_SPEED|default(33) %}
    {% set DIP_EXTRACTION_SPEED = params.DIP_EXTRACTION_SPEED|default(70) %}
    {% set MELT_ZONE_PAUSE = params.MELT_ZONE_PAUSE|default(0) %}
    {% set COOLING_ZONE_PAUSE = params.COOLING_ZONE_PAUSE|default(0) %}
    {% set UNLOADING_SPEED_START = params.UNLOADING_SPEED_START|default(199) %}
    {% set UNLOADING_SPEED = params.UNLOADING_SPEED|default(20) %}
    {% set RAMMING_VOLUME = params.RAMMING_VOLUME|default(0) %} # in mm3
    {% set INITIAL_RETRACT = params.INITIAL_RETRACT|default(0) %} # Use an initial retract or not. Don't use it if you want to ram the filament
    {% set FINAL_EJECT = params.FINAL_EJECT|default(0) %} # Fully eject the filament afterwards, default is no

    G91
    G92 E0

    SET_PRESSURE_ADVANCE ADVANCE=0
    {% set OLD_TEMP = printer.extruder.target %}

    # Ramming with SuperSlicer standard setting
    {% if INITIAL_RETRACT|int == 1 %}
        G1 E-8.5000 F3000
    {% endif %}

    {% set RATIO = (RAMMING_VOLUME|float) /23.0 %}

    G1 E{0.5784 * RATIO|float} F299 #7
    G1 E{0.5834 * RATIO|float} F302 #3
    G1 E{0.5918 * RATIO|float} F306 #6
    G1 E{0.6169 * RATIO|float} F319 #6
    G1 E{0.3393 * RATIO|float} F350 #0
    G1 E{0.3363 * RATIO|float} F350 #0
    G1 E{0.7577 * RATIO|float} F392 #6
    G1 E{0.8382 * RATIO|float} F434 #3
    G1 E{0.7776 * RATIO|float} F469 #9
    G1 E{0.1293 * RATIO|float} F469 #9
    G1 E{0.9673 * RATIO|float} F501 #2
    G1 E{1.0176 * RATIO|float} F527 #2
    G1 E{0.5956 * RATIO|float} F544 #6
    G1 E{0.4555 * RATIO|float} F544 #6
    G1 E{1.0662 * RATIO|float} F552 #4

    # set toolchange temperature just prior to filament being extracted from melt zone and wait for set point
    # (SKINNYDIP--normal mode only)
    {% if TOOLCHANGE_TEMP|float > 0 and USE_FAST_SKINNYDIP|int == 0 %}
       M109 S{TOOLCHANGE_TEMP}
    {% endif %}

    # Retraction
    {% set TOTAL_RETRACTION_DISTANCE = COOLING_TUBE_RETRACTION|float + COOLING_TUBE_LENGTH|float / 2 - 15 %}
    G1 E-15 F{1.0 * UNLOADING_SPEED_START|float * 60}
    G1 E-{0.7 * TOTAL_RETRACTION_DISTANCE} F{1.0 * UNLOADING_SPEED|float * 60}
    G1 E-{0.2 * TOTAL_RETRACTION_DISTANCE} F{0.5 * UNLOADING_SPEED|float * 60}
    G1 E-{0.1 * TOTAL_RETRACTION_DISTANCE} F{0.3 * UNLOADING_SPEED|float * 60}

    {% if TOOLCHANGE_TEMP|float > 0 and USE_FAST_SKINNYDIP|int == 1 %}
       M104 S{TOOLCHANGE_TEMP}
    {% endif %}

    # Generate Cooling Moves
    {% set SPEED_INC = (FINAL_COOLING_SPEED|float - INITIAL_COOLING_SPEED|float) / (2 * COOLING_MOVES|float - 1) %}
    {% for move in range(COOLING_MOVES|int) %}
      G1 E{COOLING_TUBE_LENGTH} F{(INITIAL_COOLING_SPEED|float + SPEED_INC*move*2) * 60}
      G1 E-{COOLING_TUBE_LENGTH} F{(INITIAL_COOLING_SPEED|float + SPEED_INC*(move*2+1)) * 60}
    {% endfor %}

    # wait for extruder to reach toolchange temperature after cooling moves complete (SKINNYDIP--fast mode only)
    {% if TOOLCHANGE_TEMP|float > 0 and USE_FAST_SKINNYDIP|int == 1 %}
        M109 S{TOOLCHANGE_TEMP}
    {% endif %}

    # Generate a skinnydip move
    {% if USE_SKINNYDIP|int == 1 %}
      G1 E{SKINNYDIP_DISTANCE} F{DIP_INSERTION_SPEED|float * 60}
      G4 P{MELT_ZONE_PAUSE}
      G1 E-{SKINNYDIP_DISTANCE} F{DIP_EXTRACTION_SPEED|float * 60}
      G4 P{COOLING_ZONE_PAUSE}
    {% endif %}

    {% if TOOLCHANGE_TEMP|float > 0 %}
      M104 S{OLD_TEMP}
    {% endif %}

    {% if FINAL_EJECT|int == 1 %}
        G92 E0
        G1 E-80 F3000
    {% endif %}

    G92 E0

# Unload from extruder with tip forming sequence
[gcode_macro ERCF_UNLOAD_FILAMENT_IN_EXTRUDER_WITH_TIP_FORMING]
description: Unload filament from the nozzle to the toolhead entrance using generic tip forming macro
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        {% if printer.extruder.temperature > printer["gcode_macro ERCF_VAR"].min_temp_extruder %}
            {% if printer["gcode_macro ERCF_SELECT_TOOL"].tool_selected|int == -1 %}
                M118 ERCR_U_F_IEWTF Forming filament tip and Unloading Filament...
                G91
                ERCF_FORM_TIP_STANDALONE
                G1 E-4.00 F1200.0
                G1 E-15.00 F2000
                ERCF_HOME_EXTRUDER
                ERCF_SERVO_DOWN
                G91
                G92 E0
                MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
                MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} SPEED=25 ACCEL=0 SYNC=0
                G1 E-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} F1500.0
                M118 ERCF_U_F_FIEWTF Filament removed
            {% else %}
                M118 Tool selected, UNSELECT it
                ERCF_PAUSE
            {% endif %}
        {% else %}
            M118 ERCF_U_F_FIEWTF Extruder too cold
            ERCF_PAUSE
        {% endif %}
    {% endif %}

############################################
# Endstop check macros
############################################

# Call ERCF_PAUSE if the filament is not detected by the toolhead sensor
[gcode_macro ERCF_IS_FILAMENT_IN_EXTRUDER]
gcode:
    {% if printer['filament_switch_sensor toolhead_sensor'].filament_detected == True %}
        M118 ERCF_IS_F_IE Filament is in extruder
    {% else %}
        M118 ERCF_IS_F_IE Filament not in extruder
        ERCF_PAUSE
    {% endif %}

# Call ERCF_PAUSE if the filament is stuck in the ERCF
[gcode_macro ERCF_IS_FILAMENT_STUCK_IN_ERCF]
gcode:
    {% if printer.ercf.encoder_pos|float != 0 %}
        M118 ERCF_IS_F_SIE Filament stuck in ERCF
        ERCF_PAUSE
    {% else %}
        M118 ERCF_IS_F_SIE Filament not in ERCF
    {% endif %}

############################################
# Macros called during homing to try to eject the filament if loaded
############################################

# Eject from extruder gear to the ERCF
[gcode_macro ERCF_EJECT_UNKNOW_STATE]
description: Unload filament from an unknown position
gcode:
    M118 EJECT_US Eject Filament if loaded ...
    {% if printer['filament_switch_sensor toolhead_sensor'].filament_detected == True %}
        M118 ERCF_EJECT_US Filament in extruder, trying to eject it ..
        {% set extruder_eject_temp = printer["gcode_macro ERCF_VAR"].extruder_eject_temp %}
        {% if printer['extruder'].temperature < extruder_eject_temp %}
            M118 Preheat Nozzle to {extruder_eject_temp}
            M109 S{extruder_eject_temp}
        {% endif %}
        ERCF_UNLOAD_FILAMENT_IN_EXTRUDER_WITH_TIP_FORMING
        ERCF_UNLOAD LENGTH={printer["gcode_macro ERCF_VAR"].min_bowden_length - 50} UNKNOWN=1
    {% else %}
        ERCF_SERVO_DOWN
    {% endif %}
    ERCF_BUZZ_GEAR_MOTOR
    G4 P200
    ERCF_EJECT_FROM_BOWDEN

# Eject from the bowden to the ERCF
[gcode_macro ERCF_EJECT_FROM_BOWDEN]
description: Unload filament from the reverse bowden
gcode:
    {% if printer.ercf.encoder_pos|float != 0 %}
        ERCF_UNLOAD HOMING=1
        M118 Filament ejected!
    {% else %}
        M118 Filament already ejected!
    {% endif %}
    ERCF_SERVO_UP

############################################
# Homing macros
# ERCF_HOME must be called before using the ERCF
############################################

# Home the ERCF
# eject filament if loaded with ERCF_EJECT_UNKNOW_STATE
# next home the ERCF with ERCF_HOME_ONLY
[gcode_macro ERCF_HOME]
description: Home the ERCF
variable_home: -1
gcode:
    SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=0
    SET_GCODE_VARIABLE MACRO=ERCF_HOME VARIABLE=home VALUE=1
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 1 %}
        ERCF_UNLOCK
    {% endif %}
    M118 Homing ERCF ...
    QUERY_ENDSTOPS
    M118 ERCF_EJECT_UNKNOW_STATE
    ERCF_EJECT_UNKNOW_STATE
    M118 ERCF_HOME_SELECTOR
    ERCF_HOME_SELECTOR
    M118 ERCF_HOME_ONLY
    ERCF_HOME_ONLY

[gcode_macro ERCF_HOME_SELECTOR]
description: Home the ERCF selector
gcode:
    M118 Homing selector
    ERCF_SERVO_UP

    {% set number_of_chan=printer["gcode_macro ERCF_VAR"].colorselector|length %}
    {% set selector_length=(20.0 + number_of_chan*21.0 + (number_of_chan/3)*5.0) %}

    MANUAL_STEPPER STEPPER=selector_stepper SET_POSITION=0
    MANUAL_STEPPER STEPPER=selector_stepper SPEED=100 MOVE=-{selector_length|float} STOP_ON_ENDSTOP=1
    MANUAL_STEPPER STEPPER=selector_stepper SET_POSITION=0
    MANUAL_STEPPER STEPPER=selector_stepper SPEED=100 MOVE=5.0
    MANUAL_STEPPER STEPPER=selector_stepper SPEED=10 MOVE=-10.0 STOP_ON_ENDSTOP=1
    MANUAL_STEPPER STEPPER=selector_stepper SET_POSITION=0
    SET_GCODE_VARIABLE MACRO=ERCF_SELECT_TOOL VARIABLE=tool_selected VALUE=-1
    SET_GCODE_VARIABLE MACRO=ERCF_SELECT_TOOL VARIABLE=color_selected VALUE=-1

# Home the ERCF:
# 1) home the color selector (if needed)
# 2) try to load filament 0 to ERCF and then unload it. Used to verify the ERCF gear
# if all is ok, the ERCF is ready to be used
[gcode_macro ERCF_HOME_ONLY]
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        M118 Test load filament 1
        ERCF_SELECT_TOOL TOOL=0
        ERCF_SET_STEPS RATIO=1.0
        M118 Loading filament to ERCF...
        ERCF_LOAD LENGTH=45
        G4 P50
        ERCF_UNLOAD LENGTH=68
        ERCF_UNSELECT_TOOL
        SET_GCODE_VARIABLE MACRO=ERCF_HOME VARIABLE=home VALUE=1
        M118 Homing ERCF ended ...
    {% else %}
        M118 Homing ERCF failed, ERCF is paused, run "ERCF_UNLOCK" to unlock it ...
    {% endif %}

###############################################
# Test Macros
###############################################
[gcode_macro ERCF_DISPLAY_ENCODER_POS]
description: Display current value of the ERCF encoder
gcode:
    M118 Encoder value is {printer['ercf'].encoder_pos|float}

[gcode_macro ERCF_TEST_MOVE_GEAR]
description: Move the ERCF gear
gcode:
    {% set move_length = params.LENGTH|default(200.0)|float %}
    {% set move_speed = params.SPEED|default(50.0)|float %}
    {% set move_accel = params.ACCEL|default(200.0)|float %}
    MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
    MANUAL_STEPPER STEPPER=gear_stepper MOVE={move_length|float} SPEED={move_speed|float} ACCEL={move_accel|float}

[gcode_macro ERCF_TEST_SERVO]
description: Test the servo angle
gcode:
    SET_SERVO SERVO=ercf_servo ANGLE={params.VALUE|float}
    G4 P250
    SET_SERVO SERVO=ercf_servo WIDTH=0.0

[gcode_macro ERCF_TEST_GRIP]
description: Test the ERCF grip for a Tool
gcode:
    ERCF_SERVO_DOWN
    ERCF_MOTORS_OFF

[gcode_macro ERCF_TEST_LOAD_SEQUENCE]
description: Test sequence
gcode:
    {% set loop_number = params.LOOP|default(10)|int %}
    {% set use_rand = params.RAND|default(0)|int %}
    {% for iteration in range(loop_number|int) %}
        {% for load in range((printer["gcode_macro ERCF_VAR"].colorselector|length)|int) %}
            {% if use_rand|int == 1 %}
                ERCF_SELECT_TOOL TOOL={range(0, printer["gcode_macro ERCF_VAR"].colorselector|length)|random}
            {% else %}
                ERCF_SELECT_TOOL TOOL={load|int}
            {% endif %}

            ERCF_LOAD LENGTH=100
            G4 P50
            ERCF_UNLOAD LENGTH=100
            ERCF_UNSELECT_TOOL
            G4 P200
        {% endfor %}
    {% endfor %}

###############################################
# Endless spool mode and clog detection
###############################################

[gcode_macro ERCF_ENCODER_MOTION_ISSUE]
description: Perform a test when the encoder sense an issue (clog or runout)
gcode:
    M118 Issue on tool {printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int}
    M118 Checking if this is a clog or a runout...

    SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=0

    ERCF_SERVO_DOWN
    ERCF_BUZZ_GEAR_MOTOR
    ERCF_SERVO_UP
    ERCF_CLOG_OR_RUNOUT

[gcode_macro ERCF_CLOG_OR_RUNOUT]
description: Actions taken if a clog or a runout is detected by the ERCF encoder
gcode:
    {% if printer.ercf.encoder_pos|float != 0 %}
        M118 Clog detected, please check the ERCF and the printer
    {% else %}
        M118 Runout detected !
        {% if printer["gcode_macro ERCF_VAR"].endless_spool_mode|int == 1 %}
            {% if printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int >= (printer["gcode_macro ERCF_VAR"].colorselector|length -1) %}
                {% set nexttool = 0 %}
            {% else %}
                {% set nexttool = (printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|int + 1) %}
            {% endif %}

            M118 Loading tool {nexttool|int}
            SAVE_GCODE_STATE NAME=ERCF_Pre_Brush_init
            # Adapt the example below to your own setup
            # The goal is just to clean the nozzle after the change
            # In my case I have a purge bucket with a brush
            # G0 X45 Y300 F18000
            # G0 X45 Y310 Z1 F3000

            # Custom unload sequence
            M118 Forming filament tip and Unloading Filament...
            G91
            ERCF_FORM_TIP_STANDALONE
            ERCF_HOME_EXTRUDER TOTAL_LENGTH={printer["gcode_macro ERCF_VAR"].sensor_to_nozzle} STEP_LENGTH=2.0
            ERCF_SERVO_DOWN

            G91
            G92 E0
            MANUAL_STEPPER STEPPER=gear_stepper SET_POSITION=0
            MANUAL_STEPPER STEPPER=gear_stepper MOVE=-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} SPEED=25 ACCEL=0 SYNC=0
            G1 E-{printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float} F1500.0

            {% set ercf_params = printer.save_variables.variables %}
            ERCF_SET_STEPS RATIO={ercf_params['ercf_calib_%s' % (printer["gcode_macro ERCF_SELECT_TOOL"].color_selected|string)]}
            ERCF_UNLOAD LENGTH={ercf_params.ercf_calib_ref|float - printer["gcode_macro ERCF_VAR"].end_of_bowden_to_sensor|float + printer["gcode_macro ERCF_VAR"].unload_modifier|float}
            ERCF_UNSELECT_TOOL

            ERCF_LOAD_TOOL TOOL={nexttool|int}
            ERCF_CHECK_IF_RESUME
        {% else %}
            M118 EndlessSpool mode not enabled, please do something
        {% endif %}
    {% endif %}

[gcode_macro ERCF_CHECK_IF_RESUME]
description: Safety checks before resuming the print after an encoder event
gcode:
    {% if printer["gcode_macro ERCF_PAUSE"].is_paused|int == 0 %}
        # Adapt the example below to your own setup
        # The goal is just to clean the nozzle after the change
        # In my case I have a purge bucket with a brush
        # BRUSH_PURGE LENGTH=50
        # BRUSH_CLEAN
        RESTORE_GCODE_STATE NAME=ERCF_Pre_Brush_init
        RESUME
        {% if printer["gcode_macro ERCF_VAR"].clog_detection|int == 1 %}
            SET_FILAMENT_SENSOR SENSOR=encoder_sensor ENABLE=1
        {% endif %}
    {% endif %}