#include "mmu2_error_converter.h" #include "mmu2/error_codes.h" #include "mmu2/errors_list.h" #include "language.h" #include namespace MMU2 { static ButtonOperations buttonSelectedOperation = ButtonOperations::NoOperation; // we don't have a constexpr find_if in C++17/STL yet template constexpr InputIt find_if_cx(InputIt first, InputIt last, UnaryPredicate p) { for (; first != last; ++first) { if (p(*first)) { return first; } } return last; } // Making a constexpr FindError should instruct the compiler to optimize the PrusaErrorCodeIndex // in such a way that no searching will ever be done at runtime. // A call to FindError then compiles to a single instruction even on the AVR. static constexpr uint8_t FindErrorIndex(uint16_t pec) { constexpr uint16_t errorCodesSize = sizeof(errorCodes) / sizeof(errorCodes[0]); constexpr const auto *errorCodesEnd = errorCodes + errorCodesSize; const auto *i = find_if_cx(errorCodes, errorCodesEnd, [pec](uint16_t ed){ return ed == pec; }); return (i != errorCodesEnd) ? (i-errorCodes) : (errorCodesSize - 1); } // check that the searching algoritm works static_assert( FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_TRIGGER) == 0); static_assert( FindErrorIndex(ERR_MECHANICAL_FINDA_FILAMENT_STUCK) == 1); static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER) == 2); static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK) == 3); constexpr ErrorCode operator&(ErrorCode a, ErrorCode b){ return (ErrorCode)((uint16_t)a & (uint16_t)b); } constexpr bool ContainsBit(ErrorCode ec, ErrorCode mask){ return (uint16_t)ec & (uint16_t)mask; } uint8_t PrusaErrorCodeIndex(ErrorCode ec) { switch (ec) { case ErrorCode::FINDA_DIDNT_SWITCH_ON: return FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_TRIGGER); case ErrorCode::FINDA_DIDNT_SWITCH_OFF: return FindErrorIndex(ERR_MECHANICAL_FINDA_FILAMENT_STUCK); case ErrorCode::FSENSOR_DIDNT_SWITCH_ON: return FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER); case ErrorCode::FSENSOR_DIDNT_SWITCH_OFF: return FindErrorIndex(ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK); case ErrorCode::FSENSOR_TOO_EARLY: return FindErrorIndex(ERR_MECHANICAL_FSENSOR_TOO_EARLY); case ErrorCode::FINDA_FLICKERS: return FindErrorIndex(ERR_MECHANICAL_INSPECT_FINDA); case ErrorCode::LOAD_TO_EXTRUDER_FAILED: return FindErrorIndex(ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED); case ErrorCode::FILAMENT_EJECTED: return FindErrorIndex(ERR_SYSTEM_FILAMENT_EJECTED); case ErrorCode::FILAMENT_CHANGE: return FindErrorIndex(ERR_SYSTEM_FILAMENT_CHANGE); case ErrorCode::STALLED_PULLEY: case ErrorCode::MOVE_PULLEY_FAILED: return FindErrorIndex(ERR_MECHANICAL_PULLEY_CANNOT_MOVE); case ErrorCode::HOMING_SELECTOR_FAILED: return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_HOME); case ErrorCode::MOVE_SELECTOR_FAILED: return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_MOVE); case ErrorCode::HOMING_IDLER_FAILED: return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_HOME); case ErrorCode::MOVE_IDLER_FAILED: return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_MOVE); case ErrorCode::MMU_NOT_RESPONDING: return FindErrorIndex(ERR_CONNECT_MMU_NOT_RESPONDING); case ErrorCode::PROTOCOL_ERROR: return FindErrorIndex(ERR_CONNECT_COMMUNICATION_ERROR); case ErrorCode::FILAMENT_ALREADY_LOADED: return FindErrorIndex(ERR_SYSTEM_FILAMENT_ALREADY_LOADED); case ErrorCode::INVALID_TOOL: return FindErrorIndex(ERR_SYSTEM_INVALID_TOOL); case ErrorCode::QUEUE_FULL: return FindErrorIndex(ERR_SYSTEM_QUEUE_FULL); case ErrorCode::VERSION_MISMATCH: return FindErrorIndex(ERR_SYSTEM_FW_UPDATE_NEEDED); case ErrorCode::INTERNAL: return FindErrorIndex(ERR_SYSTEM_FW_RUNTIME_ERROR); case ErrorCode::FINDA_VS_EEPROM_DISREPANCY: return FindErrorIndex(ERR_SYSTEM_UNLOAD_MANUALLY); case ErrorCode::MCU_UNDERVOLTAGE_VCC: return FindErrorIndex(ERR_ELECTRICAL_MMU_MCU_ERROR); default: break; } // Electrical issues which can be detected somehow. // Need to be placed before TMC-related errors in order to process couples of error bits between single ones // and to keep the code size down. if (ContainsBit(ec, ErrorCode::TMC_PULLEY_BIT)) { if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) return FindErrorIndex(ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED); } else if (ContainsBit(ec, ErrorCode::TMC_SELECTOR_BIT)) { if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) return FindErrorIndex(ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED); } else if (ContainsBit(ec, ErrorCode::TMC_IDLER_BIT)) { if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) return FindErrorIndex(ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED); } // TMC-related errors - multiple of these can occur at once // - in such a case we report the first which gets found/converted into Prusa-Error-Codes (usually the fact, that one TMC has an issue is serious enough) // By carefully ordering the checks here we can prioritize the errors being reported to the user. if (ContainsBit(ec, ErrorCode::TMC_PULLEY_BIT)) { if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH)) return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR); if (ContainsBit(ec, ErrorCode::TMC_RESET)) return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET); if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)) return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR); if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND)) return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED); if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN)) return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT); if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR)) return FindErrorIndex(ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR); } else if (ContainsBit(ec, ErrorCode::TMC_SELECTOR_BIT)) { if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH)) return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR); if (ContainsBit(ec, ErrorCode::TMC_RESET)) return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET); if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)) return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR); if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND)) return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED); if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN)) return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT); if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR)) return FindErrorIndex(ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR); } else if (ContainsBit(ec, ErrorCode::TMC_IDLER_BIT)) { if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH)) return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR); if (ContainsBit(ec, ErrorCode::TMC_RESET)) return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET); if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)) return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR); if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND)) return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED); if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN)) return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT); if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR)) return FindErrorIndex(ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR); } // if nothing got caught, return a generic runtime error return FindErrorIndex(ERR_OTHER_UNKNOWN_ERROR); } uint16_t PrusaErrorCode(uint8_t i){ return pgm_read_word(errorCodes + i); } const char * PrusaErrorTitle(uint8_t i){ return (const char *)pgm_read_ptr(errorTitles + i); } const char * PrusaErrorDesc(uint8_t i){ return (const char *)pgm_read_ptr(errorDescs + i); } uint8_t PrusaErrorButtons(uint8_t i){ return pgm_read_byte(errorButtons + i); } const char * PrusaErrorButtonTitle(uint8_t bi){ // -1 represents the hidden NoOperation button which is not drawn in any way return (const char *)pgm_read_ptr(btnOperation + bi - 1); } const char * PrusaErrorButtonMore(){ return MSG_BTN_MORE; } Buttons ButtonPressed(ErrorCode ec) { if (buttonSelectedOperation == ButtonOperations::NoOperation) { return Buttons::NoButton; // no button } const auto result = ButtonAvailable(ec); buttonSelectedOperation = ButtonOperations::NoOperation; // Reset operation return result; } Buttons ButtonAvailable(ErrorCode ec) { uint8_t ei = PrusaErrorCodeIndex(ec); // The list of responses which occur in mmu error dialogs // Return button index or perform some action on the MK3 by itself (like Reset MMU) // Based on Prusa-Error-Codes errors_list.h // So far hardcoded, but shall be generated in the future switch ( PrusaErrorCode(ei) ) { case ERR_MECHANICAL_FINDA_DIDNT_TRIGGER: case ERR_MECHANICAL_FINDA_FILAMENT_STUCK: case ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER: case ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK: case ERR_MECHANICAL_FSENSOR_TOO_EARLY: case ERR_MECHANICAL_INSPECT_FINDA: case ERR_MECHANICAL_SELECTOR_CANNOT_MOVE: case ERR_MECHANICAL_IDLER_CANNOT_MOVE: case ERR_MECHANICAL_PULLEY_CANNOT_MOVE: case ERR_SYSTEM_UNLOAD_MANUALLY: switch (buttonSelectedOperation) { // may be allow move selector right and left in the future case ButtonOperations::Retry: // "Repeat action" return Buttons::Middle; default: break; } break; case ERR_MECHANICAL_SELECTOR_CANNOT_HOME: case ERR_MECHANICAL_IDLER_CANNOT_HOME: switch (buttonSelectedOperation) { // may be allow move selector right and left in the future case ButtonOperations::Tune: // Tune Stallguard threshold return Buttons::TuneMMU; case ButtonOperations::Retry: // "Repeat action" return Buttons::Middle; default: break; } break; case ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED: case ERR_SYSTEM_FILAMENT_EJECTED: switch (buttonSelectedOperation) { case ButtonOperations::Continue: // User solved the serious mechanical problem by hand - there is no other way around return Buttons::Middle; default: break; } break; case ERR_SYSTEM_FILAMENT_CHANGE: switch (buttonSelectedOperation) { case ButtonOperations::Load: return Buttons::Load; case ButtonOperations::Eject: return Buttons::Eject; default: break; } break; case ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT: case ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT: case ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT: switch (buttonSelectedOperation) { case ButtonOperations::Continue: // "Continue" return Buttons::Left; case ButtonOperations::ResetMMU: // "Reset MMU" return Buttons::ResetMMU; default: break; } break; case ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR: case ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR: case ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR: case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR: case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR: case ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR: case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET: case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET: case ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET: case ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR: case ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR: case ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR: case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED: case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED: case ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED: case ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED: case ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED: case ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED: case ERR_SYSTEM_QUEUE_FULL: case ERR_SYSTEM_FW_RUNTIME_ERROR: case ERR_ELECTRICAL_MMU_MCU_ERROR: switch (buttonSelectedOperation) { case ButtonOperations::ResetMMU: // "Reset MMU" return Buttons::ResetMMU; default: break; } break; case ERR_CONNECT_MMU_NOT_RESPONDING: case ERR_CONNECT_COMMUNICATION_ERROR: case ERR_SYSTEM_FW_UPDATE_NEEDED: switch (buttonSelectedOperation) { case ButtonOperations::DisableMMU: // "Disable" return Buttons::DisableMMU; case ButtonOperations::ResetMMU: // "ResetMMU" return Buttons::ResetMMU; default: break; } break; case ERR_SYSTEM_FILAMENT_ALREADY_LOADED: switch (buttonSelectedOperation) { case ButtonOperations::Unload: // "Unload" return Buttons::Left; case ButtonOperations::Continue: // "Proceed/Continue" return Buttons::Right; default: break; } break; case ERR_SYSTEM_INVALID_TOOL: switch (buttonSelectedOperation) { case ButtonOperations::StopPrint: // "Stop print" return Buttons::StopPrint; case ButtonOperations::ResetMMU: // "Reset MMU" return Buttons::ResetMMU; default: break; } break; default: break; } return Buttons::NoButton; } void SetButtonResponse(ButtonOperations rsp){ buttonSelectedOperation = rsp; } } // namespace MMU2