Skip to content

Commit 21e158f

Browse files
authored
Release v1.0.32
DEV to main merge for release v1.0.32
2 parents 07ff136 + ee119db commit 21e158f

File tree

15 files changed

+1198
-202
lines changed

15 files changed

+1198
-202
lines changed

CHANGELOG.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,63 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2025-11-01]
9+
### Changed
10+
- Removed code for Belay.
11+
- Cleanup terminology around compressing/expanding of the buffer to make it easier to understand for users.
12+
13+
## [2025-10-29]
14+
### Fixes
15+
- Fixed klipper crashing when commanding distance of zero for LANE_MOVE macro.
16+
17+
## [2025-10-25]
18+
### Fixes
19+
- Resolved a bug where runout logic could potentially be triggered during a toolchange.
20+
- Resolved a bug where AFC_STATUS would crash klipper when using buffer as toolhead sensor and last lane was loaded into toolhead.
21+
22+
## [2025-10-18]
23+
### Fixes
24+
- On startup, or when assigning a spool to a lane, AFC will now check the weight of the spool to check if it is either zero, null,
25+
or a negative value. If any of these conditions are met, AFC will not assign the spool. This check can be disabled by
26+
setting `disable_weight_check: True` in the `[AFC]` section of the `AFC.cfg` file.
27+
28+
## [2025-10-16]
29+
### Fixes
30+
- Fixed issue with debounce logic on latest version of Kalico.
31+
32+
## [2025-10-12]
33+
### Fixes
34+
- Capitalized AFC_CALIBRATION help text
35+
- Removing returning TD-1 color as color in api endpoint, TD-1 color is still returned in td1_color variable per lane
36+
- Current toolchange will return zero if current toolchange is below zero(starts at -1 when first starting a print)
37+
- Added additional logic when parsing TD-1 scan_time to work with updated format in moonraker
38+
39+
## [2025-10-10]
40+
### Added
41+
- Created a new folder for community-contributed mods and configurations at ``/community_mods/``
42+
- Added Blurolls AFC-X mcu board with a path of ``/community_mods/mcu/AFC-X.cfg``. [Customer image of board](https://ae-pic-a1.aliexpress-media.com/kf/A030fad34724c426ba8564ca98bb570dfQ.jpg_.webp) Colors do **NOT** match the product description on online retailers.
43+
44+
## [2025-09-30]
45+
### Added
46+
- Allow `tool_stn_unload` to be `0` for toolheads with cutter above extruder.
47+
48+
## [2025-09-26]
49+
### Added
50+
- Support to move filament to TD-1 device that is inline with PTFE tube to gather TD and color
51+
52+
## [2025-09-27]
53+
### Fixes
54+
- Logging the same information multiple times to AFC.log file
55+
56+
## [2025-09-07]
57+
### Added
58+
- Support to push lane information to moonrakers `machine/lane_data` endpoint so that third-parties can pull this information easily(eg. orcaslicer)
59+
60+
### Fixes
61+
- The `AFC_LANE_RESET` macro will properly check for input instead of crashing Klipper.
62+
### Added
63+
- Added ability to auto level when `auto_level_macro` is defined with a valid leveling macro.
64+
865
## [2025-09-05]
966
### Added
1067
- Check to verify that pin_tool_start/end is not set to `Unknown`, throws error if pins are set to `Unknown`.

community_mods/mcu/AFC-X.cfg

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[board_pins Turtle_1]
2+
mcu: Turtle_1
3+
aliases:
4+
M1_STEP=PD4 , M1_DIR=PD3 , M1_EN=PD5 , M1_UART=PC12 , # M1_DIAG=PD2 ,
5+
M2_STEP=PD0 , M2_DIR=PC11 , M2_EN=PD1 , M2_UART=PC10 , # M2_DIAG=PC10 ,
6+
M3_STEP=PC8 , M3_DIR=PA10 , M3_EN=PC9 , M3_UART=PA9 , # M3_DIAG=PE4 ,
7+
M4_STEP=PD13 , M4_DIR=PD12 , M4_EN=PD14 , M4_UART=PC6 , # M4_DIAG=PC8 ,
8+
9+
# suggested switch pin configurations
10+
HUB=PE7 ,
11+
TRG1=PA1 , TRG2=PA2 , TRG3=PA3 , TRG4=PA4 ,
12+
EXT1=PA5 , EXT2=PA6 , EXT3=PA7 , EXT4=PC4 ,
13+
#TN_ADV=PE12 , TN_TRL=PE13 ,
14+
# alternate names for endstop ports
15+
SW1=PA1 , SW2=PA2 , SW3=PA3 , SW4=PA4 , SW5=PA5 , SW6=PA6 ,
16+
SW7=PA7 , SW8=PC4 , SW9=PC5 , SW10=PB0 , SW11=PB1 , SW12=PE7 ,
17+
18+
MOT1_RWD=PE15 , MOT1_FWD=PB10 , MOT1_EN=PD11 ,
19+
MOT2_RWD=PE13 , MOT2_FWD=PE14 , MOT2_EN=PD10 ,
20+
MOT3_RWD=PE11 , MOT3_FWD=PE12 , MOT3_EN=PD9 ,
21+
MOT4_RWD=PE8 , MOT4_FWD=PE10 , MOT4_EN=PD8 ,
22+
23+
RGB1=PB15 , RGB2=PB14 , RGB3=PB13 , RGB4=PB12 ,

extras/AFC.py

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
try: from extras.AFC_stats import AFCStats
2828
except: raise error(ERROR_STR.format(import_lib="AFC_stats", trace=traceback.format_exc()))
2929

30-
AFC_VERSION="1.0.31"
30+
AFC_VERSION="1.0.32"
3131

3232
# Class for holding different states so its clear what all valid states are
3333
class State:
@@ -65,15 +65,19 @@ def __init__(self, config):
6565
# Registering webhooks endpoint for <ip_address>/printer/afc/status
6666
self.webhooks.register_endpoint("afc/status", self._webhooks_status)
6767

68-
self.current = None
69-
self.current_loading= None
70-
self.next_lane_load = None
71-
self.error_state = False
72-
self.current_state = State.INIT
73-
self.position_saved = False
74-
self.spoolman = None
75-
self.prep_done = False # Variable used to hold of save_vars function from saving too early and overriding save before prep can be ran
76-
self.in_print_timer = None
68+
self.current = None
69+
self.current_loading = None
70+
self.next_lane_load = None
71+
self.error_state = False
72+
self.current_state = State.INIT
73+
self.position_saved = False
74+
self.spoolman = None
75+
self.moonraker = None
76+
self.td1_defined = False
77+
self._td1_present = False
78+
self.lane_data_enabled = False
79+
self.prep_done = False # Variable used to hold of save_vars function from saving too early and overriding save before prep can be ran
80+
self.in_print_timer = None
7781

7882
# Objects for everything configured for AFC
7983
self.units = {}
@@ -123,6 +127,7 @@ def __init__(self, config):
123127
self.common_density_values = list(self.common_density_values)
124128
self.test_extrude_amt = config.get('test_extrude_amt', 10)
125129

130+
self.disable_weight_check = config.getboolean("disable_weight_check", False) # Set to True to disable weight check when loading filament into lane/toolhead
126131
#LED SETTINGS
127132
self.ind_lights = None
128133
# led_name is not used, either use or needs to be removed, removing this would break everyone's config as well
@@ -161,7 +166,8 @@ def __init__(self, config):
161166

162167
# MOVE SETTINGS
163168
self.quiet_mode = False # Flag indicating if quiet move is enabled or not
164-
self.auto_home = config.getboolean("auto_home", False) # Flag indicating if homing needs to be done if printer is not already homed
169+
self.auto_home = config.getboolean("auto_home", False) # Flag indicating if homing needs to be done if printer is not already homed
170+
self.auto_level_macro = config.get("auto_level_macro", None) # Set name for macro to run for auto bed leveling before tool change if auto_home is True and printer is not already homed
165171
self.show_quiet_mode = config.getboolean("show_quiet_mode", True) # Flag indicating if quiet move is enabled or not
166172
self.quiet_moves_speed = config.getfloat("quiet_moves_speed", 50) # Max speed in mm/s to move filament during quietmode
167173
self.long_moves_speed = config.getfloat("long_moves_speed", 100) # Speed in mm/s to move filament when doing long moves
@@ -175,6 +181,7 @@ def __init__(self, config):
175181

176182
self.tool_max_unload_attempts= config.getint('tool_max_unload_attempts', 4) # Max number of attempts to unload filament from toolhead when using buffer as ramming sensor
177183
self.tool_max_load_checks = config.getint('tool_max_load_checks', 4) # Max number of attempts to check to make sure filament is loaded into toolhead extruder when using buffer as ramming sensor
184+
self.max_move_tries = config.getint("max_move_tries", 20)
178185

179186
self.rev_long_moves_speed_factor = config.getfloat("rev_long_moves_speed_factor", 1.) # scalar speed factor when reversing filamentalist
180187

@@ -200,7 +207,9 @@ def __init__(self, config):
200207
self.enable_tool_runout = config.getboolean("enable_tool_runout", True)
201208
self.debounce_delay = config.getfloat("debounce_delay", 0.)
202209

210+
self.td1_when_loaded = config.getboolean("capture_td1_when_loaded", False)
203211
self.debug = config.getboolean('debug', False) # Setting to True turns on more debugging to show on console
212+
self.log_frame_data = config.getboolean('log_frame_data', True)
204213
self.testing = config.getboolean('testing', False) # Set to true for testing only so that failure states can be tested without stats being reset
205214
# Get debug and cast to boolean
206215
self.logger.set_debug( self.debug )
@@ -292,12 +301,18 @@ def handle_moonraker_connect(self):
292301
self.moonraker = AFC_moonraker( self.moonraker_host, self.moonraker_port, self.logger )
293302
if not self.moonraker.wait_for_moonraker( toolhead=self.toolhead, timeout=self.moonraker_connect_to ):
294303
return False
304+
305+
# Remove current lane_data from database before pushing data back up so that
306+
# stale lane data is not in database
307+
self.moonraker.delete_lane_data()
295308
self.spoolman = self.moonraker.get_spoolman_server()
309+
self.td1_defined, self._td1_present, self.lane_data_enabled = self.moonraker.check_for_td1()
296310
self.afc_stats = AFCStats(self.moonraker, self.logger, self.tool_cut_threshold)
311+
312+
self.printer.send_event("afc:moonraker_connect")
297313
except Exception as e:
298-
self.logger.debug("Moonraker/Spoolman/afc_stats error: {}\n{}".format(e, traceback.format_exc()))
314+
self.logger.debug("Moonraker/Spoolman/afc_stats/td1 error\nError: {}\n{}".format(e, traceback.format_exc()))
299315
self.spoolman = None # set to none if not found
300-
return True
301316

302317
def handle_connect(self):
303318
"""
@@ -339,6 +354,16 @@ def print_version(self, console_only=False):
339354

340355
self.logger.info(string, console_only)
341356

357+
@property
358+
def td1_present(self):
359+
present = self._td1_present
360+
if self.printer.state_message == 'Printer is ready' and self.moonraker is not None:
361+
if not self.function.is_printing(check_movement=True):
362+
present = self.moonraker.check_for_td1()[1]
363+
self._td1_present = present
364+
365+
return present
366+
342367
def _reset_file_callback(self):
343368
"""
344369
Set timer to check back to see if printer is printing. This is needed as file and print status is set after
@@ -658,6 +683,11 @@ def cmd_LANE_MOVE(self, gcmd):
658683
return
659684
lane = gcmd.get('LANE', None)
660685
distance = gcmd.get_float('DISTANCE', 0)
686+
687+
if distance == 0:
688+
self.error.AFC_error("Distance to move cannot be zero", pause=False)
689+
return
690+
661691
if lane not in self.lanes:
662692
self.logger.info('{} Unknown'.format(lane))
663693
return
@@ -668,7 +698,6 @@ def cmd_LANE_MOVE(self, gcmd):
668698
if abs(distance) >= 200: speed_mode = SpeedMode.LONG
669699

670700
cur_lane.set_load_current() # Making current is set correctly when doing lane moves
671-
cur_lane.do_enable(True)
672701
cur_lane.move_advanced(distance, speed_mode, assist_active = AssistActive.YES)
673702
cur_lane.do_enable(False)
674703
self.current_state = State.IDLE
@@ -884,7 +913,6 @@ def cmd_HUB_LOAD(self, gcmd):
884913
if not cur_lane.prep_state: return
885914
cur_lane.status = AFCLaneState.HUB_LOADING
886915
if not cur_lane.load_state:
887-
cur_lane.do_enable(True)
888916
while not cur_lane.load_state:
889917
cur_lane.move_advanced( cur_hub.move_dis, SpeedMode.SHORT)
890918
if not cur_lane.loaded_to_hub:
@@ -940,7 +968,6 @@ def LANE_UNLOAD(self, cur_lane):
940968
# once user removes filament lanes status will go to None
941969
cur_lane.status = AFCLaneState.EJECTING
942970
self.save_vars()
943-
cur_lane.do_enable(True)
944971
if cur_lane.loaded_to_hub:
945972
cur_lane.move_advanced(cur_lane.dist_hub * -1, SpeedMode.HUB, assist_active = AssistActive.DYNAMIC)
946973
cur_lane.loaded_to_hub = False
@@ -1045,9 +1072,6 @@ def TOOL_LOAD(self, cur_lane, purge_length=None):
10451072
if self._check_extruder_temp(cur_lane):
10461073
self.afcDeltaTime.log_with_time("Done heating toolhead")
10471074

1048-
# Enable the lane for filament movement.
1049-
cur_lane.do_enable(True)
1050-
10511075
# Move filament to the hub if it's not already loaded there.
10521076
if not cur_lane.loaded_to_hub or cur_lane.hub == 'direct':
10531077
cur_lane.move_advanced(cur_lane.dist_hub, SpeedMode.HUB, assist_active = AssistActive.DYNAMIC)
@@ -1295,9 +1319,6 @@ def TOOL_UNLOAD(self, cur_lane):
12951319
# Synchronize the extruder stepper with the lane.
12961320
cur_lane.sync_to_extruder()
12971321

1298-
# Enable the lane for unloading operations.
1299-
cur_lane.do_enable(True)
1300-
13011322
# Perform filament cutting and parking if specified.
13021323
if self.tool_cut:
13031324
self.afc_stats.increase_cut_total()
@@ -1354,11 +1375,26 @@ def TOOL_UNLOAD(self, cur_lane):
13541375
self.error.handle_lane_failure(cur_lane, msg)
13551376
return False
13561377
cur_lane.sync_to_extruder(False)
1357-
with cur_lane.assist_move(cur_extruder.tool_unload_speed, True, cur_lane.assisted_unload):
1358-
self.move_e_pos( cur_extruder.tool_stn_unload * -1, cur_extruder.tool_unload_speed, "Buffer Move")
1378+
# we only need to do this if we need to move off the extruder gears
1379+
if cur_extruder.tool_stn_unload > 0:
1380+
with cur_lane.assist_move(cur_extruder.tool_unload_speed, True, cur_lane.assisted_unload):
1381+
self.move_e_pos( cur_extruder.tool_stn_unload * -1, cur_extruder.tool_unload_speed, "Buffer Move")
13591382

13601383
self.function.log_toolhead_pos("Buffer move after ")
13611384
else:
1385+
1386+
if cur_extruder.tool_stn_unload == 0:
1387+
cur_lane.unsync_to_extruder()
1388+
while cur_lane.get_toolhead_pre_sensor_state():
1389+
# attempt to move filament back from sensor without moving extruder
1390+
cur_lane.move_advanced(cur_lane.short_move_dis * -1, SpeedMode.SHORT)
1391+
num_tries += 1
1392+
if num_tries > self.tool_max_unload_attempts:
1393+
# note that this will break out of the loop and immediately fall into the error
1394+
# condition of the next loop for messaging to the user
1395+
break
1396+
self.reactor.pause(self.reactor.monotonic() + 0.1)
1397+
13621398
while cur_lane.get_toolhead_pre_sensor_state() or cur_extruder.tool_end_state:
13631399
num_tries += 1
13641400
if num_tries > self.tool_max_unload_attempts:
@@ -1613,9 +1649,11 @@ def get_status(self, eventtime=None):
16131649
str['current_lane'] = self.current_loading
16141650
str['next_lane'] = self.next_lane_load
16151651
str['current_state'] = self.current_state
1616-
str["current_toolchange"] = self.current_toolchange
1652+
str["current_toolchange"] = self.current_toolchange if self.current_toolchange >= 0 else 0
16171653
str["number_of_toolchanges"] = self.number_of_toolchanges
16181654
str['spoolman'] = self.spoolman
1655+
str["td1_present"] = self.td1_present
1656+
str["lane_data_enabled"] = self.lane_data_enabled
16191657
str['error_state'] = self.error_state
16201658
str["bypass_state"] = bool(self._get_bypass_state())
16211659
str["quiet_mode"] = bool(self._get_quiet_mode())
@@ -1657,6 +1695,8 @@ def _webhooks_status(self, web_request):
16571695
str["system"]['num_lanes'] = numoflanes
16581696
str["system"]['num_extruders'] = len(self.tools)
16591697
str["system"]['spoolman'] = self.spoolman
1698+
str["system"]["td1_present"] = self.td1_present
1699+
str["system"]["lane_data_enabled"] = self.lane_data_enabled
16601700
str["system"]["current_toolchange"] = self.current_toolchange
16611701
str["system"]["number_of_toolchanges"] = self.number_of_toolchanges
16621702
str["system"]["extruders"] = {}
@@ -1693,6 +1733,7 @@ def cmd_AFC_STATUS(self, gcmd):
16931733
"""
16941734
status_msg = ''
16951735

1736+
# TODO: Update this function for correct usage with multiple hubs/toolheads
16961737
for unit in self.units.values():
16971738
# Find the maximum length of lane names to determine the column width
16981739
max_lane_length = max(len(lane) for lane in unit.lanes.keys())
@@ -1703,11 +1744,14 @@ def cmd_AFC_STATUS(self, gcmd):
17031744
header_format = '{:<{}} | Prep | Load |\n'
17041745
status_msg += header_format.format("LANE", max_lane_length)
17051746

1747+
# Initialize extruder_msg to its default value before processing lanes
1748+
extruder_msg = ' Tool: <span class=error--text>x</span>'
17061749
for cur_lane in unit.lanes.values():
17071750
lane_msg = ''
17081751
if self.current is not None:
17091752
if self.current == cur_lane.name:
1710-
if not cur_lane.get_toolhead_pre_sensor_state() or not cur_lane.hub_obj.state:
1753+
if (not cur_lane.get_toolhead_pre_sensor_state() or
1754+
not cur_lane.hub_obj.state):
17111755
lane_msg += '<span class=warning--text>{:<{}} </span>'.format(cur_lane.name, max_lane_length)
17121756
else:
17131757
lane_msg += '<span class=success--text>{:<{}} </span>'.format(cur_lane.name, max_lane_length)
@@ -1726,19 +1770,20 @@ def cmd_AFC_STATUS(self, gcmd):
17261770
lane_msg += ' <span class=error--text>xx</span> |\n'
17271771
status_msg += lane_msg
17281772

1773+
if cur_lane.extruder_obj.tool_start != "buffer":
1774+
if cur_lane.extruder_obj.tool_start_state:
1775+
extruder_msg = ' Tool: <span class=success--text><-></span>'
1776+
else:
1777+
if (cur_lane.tool_loaded and
1778+
cur_lane.extruder_obj.lane_loaded in unit.lanes):
1779+
if cur_lane.get_toolhead_pre_sensor_state():
1780+
extruder_msg = ' Tool: <span class=success--text><-></span>'
1781+
17291782
if cur_lane.hub_obj.state:
17301783
status_msg += 'HUB: <span class=success--text><-></span>'
17311784
else:
17321785
status_msg += 'HUB: <span class=error--text>x</span>'
17331786

1734-
extruder_msg = ' Tool: <span class=error--text>x</span>'
1735-
if cur_lane.extruder_obj.tool_start != "buffer":
1736-
if cur_lane.extruder_obj.tool_start_state:
1737-
extruder_msg = ' Tool: <span class=success--text><-></span>'
1738-
else:
1739-
if cur_lane.tool_loaded and cur_lane.extruder_obj.lane_loaded in self.units[unit]:
1740-
if cur_lane.get_toolhead_pre_sensor_state():
1741-
extruder_msg = ' Tool: <span class=success--text><-></span>'
17421787

17431788
status_msg += extruder_msg
17441789
if cur_lane.extruder_obj.tool_start == 'buffer':
@@ -1851,4 +1896,4 @@ def cmd_AFC_CLEAR_MESSAGE(self, gcmd):
18511896
def cmd__AFC_TEST_MESSAGES(self, gcmd):
18521897
self.logger.error("Test Message 1")
18531898
self.logger.error("Test Message 2")
1854-
self.logger.error("Test Message 3")
1899+
self.logger.error("Test Message 3")

0 commit comments

Comments
 (0)