Skip to content

Commit

Permalink
Add initial MMU support
Browse files Browse the repository at this point in the history
  • Loading branch information
TojikCZ committed Nov 28, 2023
1 parent 72424e7 commit 528d2c9
Show file tree
Hide file tree
Showing 13 changed files with 490 additions and 29 deletions.
13 changes: 8 additions & 5 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# ChangeLog

0.7.2 (2023-10-11)
* Add an automatic PrusaLink image builder script
* Add multi-instance documentation
* Telemetry improvement
* Attempt to turn the RPi wifi power management off in the images
0.7.3dev
* Stop showing the tune menu after connecting to the printer
* Use the 2023-10-10 Raspberry Pi OS base image for the image builder
* Decrease the severity of web server access log messages
Expand All @@ -16,6 +12,13 @@
* Support the set ready and cancel ready LCD menu toggle
* Add gcodes that flag the state of a usb print for the printer statistics to get saved
* Handle the new re-print LCD menu item
* Add the initial support for the MMU

0.7.2 (2023-10-11)
* Add an automatic PrusaLink image builder script
* Add multi-instance documentation
* Telemetry improvement
* Attempt to turn the RPi wifi power management off in the images

0.7.1rc1 (2023-08-10)
* Fixed HTTP response status on HEAD request for non-existing files
Expand Down
110 changes: 110 additions & 0 deletions prusa/link/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
30302: PrinterType.I3MK3S,
}

MMU3_TYPE_CODE = 30302

PRINTER_CONF_TYPES = bidict({
"MK2.5": PrinterType.I3MK25,
"MK2.5S": PrinterType.I3MK25S,
Expand Down Expand Up @@ -245,3 +247,111 @@ class LimitsMK3S(LimitsFDM):
}

MMU_SLOTS = 5

MMU_PROGRESS_MAP = {
"OK": 0,

"Engaging idler": 1,
"Disengaging idler": 2,
"Unloading to FINDA": 3,
"Unloading to pulley": 4,
"Feeding to FINDA": 5,
"Feeding to extruder": 6,
"Feeding to nozzle": 7,
"Avoiding grind": 8,
"ERR Disengaging idler": 10,
"ERR Engaging idler": 11,
"ERR Wait for User": 12,
"ERR Internal": 13,
"ERR Help filament": 14,
"ERR TMC failed": 15,
"Selecting fil. slot": 18,
"Preparing blade": 19,
"Pushing filament": 20,
"Performing cut": 21,
"Returning selector": 22,
"Ejecting filament": 24,
"Parking selector": 23,
"Retract from FINDA": 25,
"Homing": 26,
"Moving selector": 27,
"Feeding to FSensor": 28,
}

MMU_ERROR_MAP = {
0x8001: 101, # FINDA didn't switch on -> FINDA didn't trigger
0x8002: 102, # FINDA didn't switch off -> FINDA filament stuck
0x8003: 103, # Filament sensor didn't switch on -> FSENSOR didn't trigger
0x8004: 104, # Filament sensor didn't switch off -> FSENSOR filament stuck
0x800b: 105, # MOVE_PULLEY_FAILED -> MECHANICAL pulley cannot move
0x8009: 106, # FSensor triggered too early -> MECHANICAL FSENSOR too early
0x800a: 107, # FINDA flickers -> MECHANICAL inspect FINDA
0x802a: 108, # LOAD_TO_EXTRUDER_FAILED -> Loading to extruder failed.
# Inspect the filament tip shape. Refine the sensor
# calibration, if needed

0x8007 | 0x0080: 115, # Selector homing failed
0x800b | 0x0080: 116, # Selector move failed
0x8007 | 0x0100: 125, # Idler homing failed
0x800b | 0x0100: 126, # Idler move failed

0xA000: 201, # TMC_OVER_TEMPERATURE_WARN -> Temperature warning TMC pulley
# too hot
0xC000: 202, # TMC_OVER_TEMPERATURE_ERROR -> Temperature TMC pulley
# overheat error

# Temperature errors for the selector driver
0xA000 | 0x0080: 211, # Temperature warning TMC selector too hot
0xC000 | 0x0080: 212, # Temperature TMC selector overheat error

# Temperature errors for the idler driver
0xA000 | 0x0100: 221, # Temperature warning TMC idler too hot
0xC000 | 0x0100: 222, # Temperature TMC idler overheat error

# Electrical errors
0x8200: 301, # TMC_IOIN_MISMATCH -> Electrical TMC pulley driver error
0x8400: 302, # TMC_RESET -> Electrical TMC pulley driver reset
0x8800: 303, # TMC_UNDERVOLTAGE_ON_CHARGE_PUMP -> Electrical TMC
# pulley undervoltage error
0x9000: 304, # TMC_SHORT_TO_GROUND -> Electrical TMC pulley driver shorted
0xC200: 305, # ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED -> Electrical
# TMC pulley selftest failed

# Electrical errors for the selector driver
0x8200 | 0x0080: 311, # Electrical TMC selector driver error
0x8400 | 0x0080: 312, # Electrical TMC selector driver reset
0x8800 | 0x0080: 313, # Electrical TMC selector undervoltage error
0x9000 | 0x0080: 314, # Electrical TMC selector driver shorted
0xC200 | 0x0080: 315, # Electrical TMC selector selftest failed

# Electrical errors for the idler driver
0x8200 | 0x0100: 321, # Electrical TMC idler driver error
0x8400 | 0x0100: 322, # Electrical TMC idler driver reset
0x8800 | 0x0100: 323, # Electrical TMC idler undervoltage error
0x9000 | 0x0100: 324, # Electrical TMC idler driver shorted
0xC200 | 0x0100: 325, # Electrical TMC idler selftest failed

0x0800d: 306, # MMU MCU detected a 5V undervoltage. There might be an
# issue with the electronics. Check the wiring and
# connectors

# Connectivity errors
0x802e: 401, # MMU not responding -> CONNECT MMU not responding
0x802d: 402, # MMU not responding correctly. Check the wiring and
# connectors

# System errors
0x8005: 501, # Filament already loaded -> SYSTEM filament already loaded
0x8006: 502, # Invalid tool -> SYSTEM invalid tool
0x802b: 503, # QUEUE_FULL -> MMU Firmware internal error, please reset
# the MMU
0x802c: 504, # VERSION_MISMATCH -> The MMU firmware version is
# incompatible with the printer's FW. Update to compatible
# version
0x802f: 505, # PROTOCOL_ERROR -> Internal runtime error. Try resetting
# the MMU or updating the firmware
0x8008: 506, # FINDA_VS_EEPROM_DISCREPANCY -> Unload manually
0x800c: 507, # Filament was ejected -> SYSTEM filament ejected
0x8029: 508, # FILAMENT_CHANGE -> SYSTEM filament change

}
133 changes: 133 additions & 0 deletions prusa/link/printer_adapter/mmu_observer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"""Contains the mmu output observing code, that compiles the readouts into
telemetry values"""
from re import Match

from blinker import Signal

from prusa.connect.printer.const import Event, Source

from ..const import MMU_ERROR_MAP, MMU_PROGRESS_MAP
from ..sdk_augmentation.printer import MyPrinter
from ..serial.serial_parser import ThreadedSerialParser
from .model import Model
from .structures.model_classes import Slot, Telemetry
from .structures.module_data_classes import MMUObserverData
from .structures.regular_expressions import (
MMU_PROGRESS_REGEX,
MMU_Q0_REGEX,
MMU_Q0_RESPONSE_REGEX,
MMU_SLOT_REGEX,
)
from .telemetry_passer import TelemetryPasser


class MMUObserver:
"""The class that observes the MMU output and sends passes the info
from it as telemetry"""

def __init__(self,
serial_parser: ThreadedSerialParser,
model: Model,
printer: MyPrinter,
telemetry_passer: TelemetryPasser):
self.serial_parser = serial_parser
self.model = model
self.model.mmu_observer = MMUObserverData(current_error_code=None)
self.data = self.model.mmu_observer
self.printer = printer
self.telemetry_passer = telemetry_passer

self.capture_q0 = False

self.serial_parser.add_decoupled_handler(
MMU_PROGRESS_REGEX, self._handle_mmu_progress)
self.serial_parser.add_decoupled_handler(
MMU_SLOT_REGEX, self._handle_active_slot)
self.serial_parser.add_decoupled_handler(
MMU_Q0_RESPONSE_REGEX, self._handle_q0_response)
self.serial_parser.add_decoupled_handler(
MMU_Q0_REGEX, self._prime_q0)

self.error_changed_signal = Signal()

self.telemetry_passer.set_telemetry(
Telemetry(
slot=Slot(
active=0,
),
),
)

def _prime_q0(self, _, match: Match) -> None:
"""Starts listening for the Q0 response"""
assert match is not None
self.capture_q0 = True

def _handle_mmu_progress(self, _, match: Match):
message = match.group("message")
code = MMU_PROGRESS_MAP.get(message)
self.telemetry_passer.set_telemetry(
Telemetry(
slot=Slot(
state=code,
),
),
)

def _handle_active_slot(self, _, match: Match):
raw_active_slot = int(match.group("slot"), 16)
if raw_active_slot == 99:
active_slot = 0
else:
active_slot = raw_active_slot + 1
self.telemetry_passer.set_telemetry(
Telemetry(
slot=Slot(
active=active_slot,
),
),
)

def _handle_mmu_error(self, error_code):
"""Report an mmu error"""
prusa_error_code = "04" + str(MMU_ERROR_MAP.get(error_code))
if self.data.current_error_code == prusa_error_code:
return
self.data.current_error_code = prusa_error_code
self.printer.event_cb(
Event.SLOT_EVENT,
source=Source.SLOT,
code=prusa_error_code,
)
self.error_changed_signal.send()

def _handle_mmu_no_error(self):
"""Clear the mmu error"""
self.data.current_error_code = None
self.error_changed_signal.send()

def _handle_q0_response(self, _, match: Match):
"""Parse the mmu Q0 status response"""
if not self.capture_q0:
return
self.capture_q0 = False

command_code = match.group("command")
progress_code = match.group("progress")

# Is there a command in progress? If yes, send it
if progress_code[0] in "PE":
self.telemetry_passer.set_telemetry(
Telemetry(
slot=Slot(
command=command_code,
),
),
)

# Figure out if there's an error being reported
if progress_code.startswith("E"):
error_code = int(progress_code[1:], 16)
self._handle_mmu_error(error_code)
else:
self._handle_mmu_no_error()
2 changes: 2 additions & 0 deletions prusa/link/printer_adapter/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
FilePrinterData,
IPUpdaterData,
JobData,
MMUObserverData,
PrintStatsData,
SDCardData,
SerialAdapterData,
Expand Down Expand Up @@ -34,6 +35,7 @@ class Model(metaclass=MCSingleton):
sd_card: SDCardData
folder_storage: StorageData
filesystem_storage: StorageData
mmu_observer: MMUObserverData

def __init__(self) -> None:
self.latest_telemetry: Telemetry = Telemetry()
Loading

0 comments on commit 528d2c9

Please sign in to comment.