/** * Marlin 3D Printer Firmware * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin] * * Based on Sprinter and grbl. * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ /************** * ui_api.cpp * **************/ /**************************************************************************** * Written By Marcio Teixeira 2018 - Aleph Objects, Inc. * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * To view a copy of the GNU General Public License, go to the following * * location: . * ****************************************************************************/ #include "../../inc/MarlinConfigPre.h" #if ENABLED(EXTENSIBLE_UI) #include "../ultralcd.h" #include "../../gcode/queue.h" #include "../../module/motion.h" #include "../../module/planner.h" #include "../../module/probe.h" #include "../../module/temperature.h" #include "../../module/printcounter.h" #include "../../libs/duration_t.h" #include "../../HAL/shared/Delay.h" #if ENABLED(PRINTCOUNTER) #include "../../core/utility.h" #include "../../libs/numtostr.h" #endif #if EXTRUDERS > 1 #include "../../module/tool_change.h" #endif #if ENABLED(EMERGENCY_PARSER) #include "../../feature/e_parser.h" #endif #if ENABLED(SDSUPPORT) #include "../../sd/cardreader.h" #define IFSD(A,B) (A) #else #define IFSD(A,B) (B) #endif #if HAS_TRINAMIC_CONFIG #include "../../feature/tmc_util.h" #include "../../module/stepper/indirection.h" #endif #include "ui_api.h" #if ENABLED(BACKLASH_GCODE) #include "../../feature/backlash.h" #endif #if HAS_LEVELING #include "../../feature/bedlevel/bedlevel.h" #endif #if HAS_FILAMENT_SENSOR #include "../../feature/runout.h" #endif #if ENABLED(CASE_LIGHT_ENABLE) #include "../../feature/caselight.h" #endif #if ENABLED(BABYSTEPPING) #include "../../feature/babystep.h" #endif #if ENABLED(HOST_PROMPT_SUPPORT) #include "../../feature/host_actions.h" #endif namespace ExtUI { static struct { uint8_t printer_killed : 1; TERN_(JOYSTICK, uint8_t jogging : 1); TERN_(SDSUPPORT, uint8_t was_sd_printing : 1); } flags; #ifdef __SAM3X8E__ /** * Implement a special millis() to allow time measurement * within an ISR (such as when the printer is killed). * * To keep proper time, must be called at least every 1s. */ uint32_t safe_millis() { // Not killed? Just call millis() if (!flags.printer_killed) return millis(); static uint32_t currTimeHI = 0; /* Current time */ // Machine was killed, reinit SysTick so we are able to compute time without ISRs if (currTimeHI == 0) { // Get the last time the Arduino time computed (from CMSIS) and convert it to SysTick currTimeHI = (uint32_t)((GetTickCount() * (uint64_t)(F_CPU / 8000)) >> 24); // Reinit the SysTick timer to maximize its period SysTick->LOAD = SysTick_LOAD_RELOAD_Msk; // get the full range for the systick timer SysTick->VAL = 0; // Load the SysTick Counter Value SysTick->CTRL = // MCLK/8 as source // No interrupts SysTick_CTRL_ENABLE_Msk; // Enable SysTick Timer } // Check if there was a timer overflow from the last read if (SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) { // There was. This means (SysTick_LOAD_RELOAD_Msk * 1000 * 8)/F_CPU ms has elapsed currTimeHI++; } // Calculate current time in milliseconds uint32_t currTimeLO = SysTick_LOAD_RELOAD_Msk - SysTick->VAL; // (in MCLK/8) uint64_t currTime = ((uint64_t)currTimeLO) | (((uint64_t)currTimeHI) << 24); // The ms count is return (uint32_t)(currTime / (F_CPU / 8000)); } #endif // __SAM3X8E__ void delay_us(unsigned long us) { DELAY_US(us); } void delay_ms(unsigned long ms) { if (flags.printer_killed) DELAY_US(ms * 1000); else safe_delay(ms); } void yield() { if (!flags.printer_killed) thermalManager.manage_heater(); } void enableHeater(const extruder_t extruder) { #if HOTENDS && HEATER_IDLE_HANDLER thermalManager.reset_hotend_idle_timer(extruder - E0); #else UNUSED(extruder); #endif } void enableHeater(const heater_t heater) { #if HEATER_IDLE_HANDLER switch (heater) { #if HAS_HEATED_BED case BED: thermalManager.reset_bed_idle_timer(); return; #endif #if HAS_HEATED_CHAMBER case CHAMBER: return; // Chamber has no idle timer #endif default: TERN_(HAS_HOTEND, thermalManager.reset_hotend_idle_timer(heater - H0)); break; } #else UNUSED(heater); #endif } #if ENABLED(JOYSTICK) /** * Jogs in the direction given by the vector (dx, dy, dz). * The values range from -1 to 1 mapping to the maximum * feedrate for an axis. * * The axis will continue to jog until this function is * called with all zeros. */ void jog(const xyz_float_t &dir) { // The "destination" variable is used as a scratchpad in // Marlin by GCODE routines, but should remain untouched // during manual jogging, allowing us to reuse the space // for our direction vector. destination = dir; flags.jogging = !NEAR_ZERO(dir.x) || !NEAR_ZERO(dir.y) || !NEAR_ZERO(dir.z); } // Called by the polling routine in "joystick.cpp" void _joystick_update(xyz_float_t &norm_jog) { if (flags.jogging) { #define OUT_OF_RANGE(VALUE) (VALUE < -1.0f || VALUE > 1.0f) if (OUT_OF_RANGE(destination.x) || OUT_OF_RANGE(destination.y) || OUT_OF_RANGE(destination.z)) { // If destination on any axis is out of range, it // probably means the UI forgot to stop jogging and // ran GCODE that wrote a position to destination. // To prevent a disaster, stop jogging. flags.jogging = false; return; } norm_jog = destination; } } #endif bool isHeaterIdle(const extruder_t extruder) { return false #if HOTENDS && HEATER_IDLE_HANDLER || thermalManager.hotend_idle[extruder - E0].timed_out #else ; UNUSED(extruder) #endif ; } bool isHeaterIdle(const heater_t heater) { #if HEATER_IDLE_HANDLER switch (heater) { TERN_(HAS_HEATED_BED, case BED: return thermalManager.bed_idle.timed_out); #if HAS_HEATED_CHAMBER case CHAMBER: return false; // Chamber has no idle timer #endif default: #if HAS_HOTEND return thermalManager.hotend_idle[heater - H0].timed_out; #else return false; #endif } #else UNUSED(heater); return false; #endif } #ifdef TOUCH_UI_LCD_TEMP_SCALING #define GET_TEMP_ADJUSTMENT(A) float(A)/TOUCH_UI_LCD_TEMP_SCALING #else #define GET_TEMP_ADJUSTMENT(A) A #endif float getActualTemp_celsius(const heater_t heater) { switch (heater) { TERN_(HAS_HEATED_BED, case BED: return GET_TEMP_ADJUSTMENT(thermalManager.degBed())); TERN_(HAS_HEATED_CHAMBER, case CHAMBER: return GET_TEMP_ADJUSTMENT(thermalManager.degChamber())); default: return GET_TEMP_ADJUSTMENT(thermalManager.degHotend(heater - H0)); } } float getActualTemp_celsius(const extruder_t extruder) { return GET_TEMP_ADJUSTMENT(thermalManager.degHotend(extruder - E0)); } float getTargetTemp_celsius(const heater_t heater) { switch (heater) { TERN_(HAS_HEATED_BED, case BED: return GET_TEMP_ADJUSTMENT(thermalManager.degTargetBed())); TERN_(HAS_HEATED_CHAMBER, case CHAMBER: return GET_TEMP_ADJUSTMENT(thermalManager.degTargetChamber())); default: return GET_TEMP_ADJUSTMENT(thermalManager.degTargetHotend(heater - H0)); } } float getTargetTemp_celsius(const extruder_t extruder) { return GET_TEMP_ADJUSTMENT(thermalManager.degTargetHotend(extruder - E0)); } float getTargetFan_percent(const fan_t fan) { #if HAS_FAN return thermalManager.fanPercent(thermalManager.fan_speed[fan - FAN0]); #else UNUSED(fan); return 0; #endif } float getActualFan_percent(const fan_t fan) { #if HAS_FAN return thermalManager.fanPercent(thermalManager.scaledFanSpeed(fan - FAN0)); #else UNUSED(fan); return 0; #endif } float getAxisPosition_mm(const axis_t axis) { return #if ENABLED(JOYSTICK) flags.jogging ? destination[axis] : #endif current_position[axis]; } float getAxisPosition_mm(const extruder_t extruder) { const extruder_t old_tool = getActiveTool(); setActiveTool(extruder, true); const float epos = ( #if ENABLED(JOYSTICK) flags.jogging ? destination.e : #endif current_position.e ); setActiveTool(old_tool, true); return epos; } void setAxisPosition_mm(const float position, const axis_t axis) { // Start with no limits to movement float min = current_position[axis] - 1000, max = current_position[axis] + 1000; // Limit to software endstops, if enabled #if HAS_SOFTWARE_ENDSTOPS if (soft_endstops_enabled) switch (axis) { case X_AXIS: TERN_(MIN_SOFTWARE_ENDSTOP_X, min = soft_endstop.min.x); TERN_(MAX_SOFTWARE_ENDSTOP_X, max = soft_endstop.max.x); break; case Y_AXIS: TERN_(MIN_SOFTWARE_ENDSTOP_Y, min = soft_endstop.min.y); TERN_(MAX_SOFTWARE_ENDSTOP_Y, max = soft_endstop.max.y); break; case Z_AXIS: TERN_(MIN_SOFTWARE_ENDSTOP_Z, min = soft_endstop.min.z); TERN_(MAX_SOFTWARE_ENDSTOP_Z, max = soft_endstop.max.z); default: break; } #endif // HAS_SOFTWARE_ENDSTOPS // Delta limits XY based on the current offset from center // This assumes the center is 0,0 #if ENABLED(DELTA) if (axis != Z_AXIS) { max = SQRT(sq((float)(DELTA_PRINTABLE_RADIUS)) - sq(current_position[Y_AXIS - axis])); // (Y_AXIS - axis) == the other axis min = -max; } #endif current_position[axis] = constrain(position, min, max); line_to_current_position(manual_feedrate_mm_s[axis]); } void setAxisPosition_mm(const float position, const extruder_t extruder) { setActiveTool(extruder, true); current_position.e = position; line_to_current_position(manual_feedrate_mm_s.e); } void setActiveTool(const extruder_t extruder, bool no_move) { #if EXTRUDERS > 1 const uint8_t e = extruder - E0; if (e != active_extruder) tool_change(e, no_move); active_extruder = e; #else UNUSED(extruder); UNUSED(no_move); #endif } extruder_t getActiveTool() { switch (active_extruder) { case 5: return E5; case 4: return E4; case 3: return E3; case 2: return E2; case 1: return E1; default: return E0; } } bool isMoving() { return planner.has_blocks_queued(); } bool canMove(const axis_t axis) { switch (axis) { #if IS_KINEMATIC || ENABLED(NO_MOTION_BEFORE_HOMING) case X: return TEST(axis_homed, X_AXIS); case Y: return TEST(axis_homed, Y_AXIS); case Z: return TEST(axis_homed, Z_AXIS); #else case X: case Y: case Z: return true; #endif default: return false; } } bool canMove(const extruder_t extruder) { return !thermalManager.tooColdToExtrude(extruder - E0); } #if HAS_SOFTWARE_ENDSTOPS bool getSoftEndstopState() { return soft_endstops_enabled; } void setSoftEndstopState(const bool value) { soft_endstops_enabled = value; } #endif #if HAS_TRINAMIC_CONFIG float getAxisCurrent_mA(const axis_t axis) { switch (axis) { #if AXIS_IS_TMC(X) case X: return stepperX.getMilliamps(); #endif #if AXIS_IS_TMC(Y) case Y: return stepperY.getMilliamps(); #endif #if AXIS_IS_TMC(Z) case Z: return stepperZ.getMilliamps(); #endif default: return NAN; }; } float getAxisCurrent_mA(const extruder_t extruder) { switch (extruder) { #if AXIS_IS_TMC(E0) case E0: return stepperE0.getMilliamps(); #endif #if AXIS_IS_TMC(E1) case E1: return stepperE1.getMilliamps(); #endif #if AXIS_IS_TMC(E2) case E2: return stepperE2.getMilliamps(); #endif #if AXIS_IS_TMC(E3) case E3: return stepperE3.getMilliamps(); #endif #if AXIS_IS_TMC(E4) case E4: return stepperE4.getMilliamps(); #endif #if AXIS_IS_TMC(E5) case E5: return stepperE5.getMilliamps(); #endif #if AXIS_IS_TMC(E6) case E6: return stepperE6.getMilliamps(); #endif #if AXIS_IS_TMC(E7) case E7: return stepperE7.getMilliamps(); #endif default: return NAN; }; } void setAxisCurrent_mA(const float mA, const axis_t axis) { switch (axis) { #if AXIS_IS_TMC(X) case X: stepperX.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(Y) case Y: stepperY.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(Z) case Z: stepperZ.rms_current(constrain(mA, 500, 1500)); break; #endif default: break; }; } void setAxisCurrent_mA(const float mA, const extruder_t extruder) { switch (extruder) { #if AXIS_IS_TMC(E0) case E0: stepperE0.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(E1) case E1: stepperE1.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(E2) case E2: stepperE2.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(E3) case E3: stepperE3.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(E4) case E4: stepperE4.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(E5) case E5: stepperE5.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(E6) case E6: stepperE6.rms_current(constrain(mA, 500, 1500)); break; #endif #if AXIS_IS_TMC(E7) case E7: stepperE7.rms_current(constrain(mA, 500, 1500)); break; #endif default: break; }; } int getTMCBumpSensitivity(const axis_t axis) { switch (axis) { TERN_(X_SENSORLESS, case X: return stepperX.homing_threshold()); TERN_(Y_SENSORLESS, case Y: return stepperY.homing_threshold()); TERN_(Z_SENSORLESS, case Z: return stepperZ.homing_threshold()); default: return 0; } } void setTMCBumpSensitivity(const float value, const axis_t axis) { switch (axis) { #if X_SENSORLESS || Y_SENSORLESS || Z_SENSORLESS #if X_SENSORLESS case X: stepperX.homing_threshold(value); break; #endif #if Y_SENSORLESS case Y: stepperY.homing_threshold(value); break; #endif #if Z_SENSORLESS case Z: stepperZ.homing_threshold(value); break; #endif #else UNUSED(value); #endif default: break; } } #endif float getAxisSteps_per_mm(const axis_t axis) { return planner.settings.axis_steps_per_mm[axis]; } float getAxisSteps_per_mm(const extruder_t extruder) { UNUSED_E(extruder); return planner.settings.axis_steps_per_mm[E_AXIS_N(extruder - E0)]; } void setAxisSteps_per_mm(const float value, const axis_t axis) { planner.settings.axis_steps_per_mm[axis] = value; } void setAxisSteps_per_mm(const float value, const extruder_t extruder) { UNUSED_E(extruder); planner.settings.axis_steps_per_mm[E_AXIS_N(axis - E0)] = value; } feedRate_t getAxisMaxFeedrate_mm_s(const axis_t axis) { return planner.settings.max_feedrate_mm_s[axis]; } feedRate_t getAxisMaxFeedrate_mm_s(const extruder_t extruder) { UNUSED_E(extruder); return planner.settings.max_feedrate_mm_s[E_AXIS_N(axis - E0)]; } void setAxisMaxFeedrate_mm_s(const feedRate_t value, const axis_t axis) { planner.set_max_feedrate(axis, value); } void setAxisMaxFeedrate_mm_s(const feedRate_t value, const extruder_t extruder) { UNUSED_E(extruder); planner.set_max_feedrate(E_AXIS_N(extruder - E0), value); } float getAxisMaxAcceleration_mm_s2(const axis_t axis) { return planner.settings.max_acceleration_mm_per_s2[axis]; } float getAxisMaxAcceleration_mm_s2(const extruder_t extruder) { UNUSED_E(extruder); return planner.settings.max_acceleration_mm_per_s2[E_AXIS_N(extruder - E0)]; } void setAxisMaxAcceleration_mm_s2(const float value, const axis_t axis) { planner.set_max_acceleration(axis, value); } void setAxisMaxAcceleration_mm_s2(const float value, const extruder_t extruder) { UNUSED_E(extruder); planner.set_max_acceleration(E_AXIS_N(extruder - E0), value); } #if HAS_FILAMENT_SENSOR bool getFilamentRunoutEnabled() { return runout.enabled; } void setFilamentRunoutEnabled(const bool value) { runout.enabled = value; } #ifdef FILAMENT_RUNOUT_DISTANCE_MM float getFilamentRunoutDistance_mm() { return runout.runout_distance(); } void setFilamentRunoutDistance_mm(const float value) { runout.set_runout_distance(constrain(value, 0, 999)); } #endif #endif #if HAS_CASE_LIGHT bool getCaseLightState() { return case_light_on; } void setCaseLightState(const bool value) { case_light_on = value; update_case_light(); } #if DISABLED(CASE_LIGHT_NO_BRIGHTNESS) float getCaseLightBrightness_percent() { return ui8_to_percent(case_light_brightness); } void setCaseLightBrightness_percent(const float value) { case_light_brightness = map(constrain(value, 0, 100), 0, 100, 0, 255); update_case_light(); } #endif #endif #if ENABLED(LIN_ADVANCE) float getLinearAdvance_mm_mm_s(const extruder_t extruder) { return (extruder < EXTRUDERS) ? planner.extruder_advance_K[extruder - E0] : 0; } void setLinearAdvance_mm_mm_s(const float value, const extruder_t extruder) { if (extruder < EXTRUDERS) planner.extruder_advance_K[extruder - E0] = constrain(value, 0, 999); } #endif #if HAS_JUNCTION_DEVIATION float getJunctionDeviation_mm() { return planner.junction_deviation_mm; } void setJunctionDeviation_mm(const float value) { planner.junction_deviation_mm = constrain(value, 0.001, 0.3); TERN_(LIN_ADVANCE, planner.recalculate_max_e_jerk()); } #else float getAxisMaxJerk_mm_s(const axis_t axis) { return planner.max_jerk[axis]; } float getAxisMaxJerk_mm_s(const extruder_t) { return planner.max_jerk.e; } void setAxisMaxJerk_mm_s(const float value, const axis_t axis) { planner.set_max_jerk((AxisEnum)axis, value); } void setAxisMaxJerk_mm_s(const float value, const extruder_t) { planner.set_max_jerk(E_AXIS, value); } #endif feedRate_t getFeedrate_mm_s() { return feedrate_mm_s; } int16_t getFlowPercentage(const extruder_t extr) { return planner.flow_percentage[extr]; } feedRate_t getMinFeedrate_mm_s() { return planner.settings.min_feedrate_mm_s; } feedRate_t getMinTravelFeedrate_mm_s() { return planner.settings.min_travel_feedrate_mm_s; } float getPrintingAcceleration_mm_s2() { return planner.settings.acceleration; } float getRetractAcceleration_mm_s2() { return planner.settings.retract_acceleration; } float getTravelAcceleration_mm_s2() { return planner.settings.travel_acceleration; } void setFeedrate_mm_s(const feedRate_t fr) { feedrate_mm_s = fr; } void setFlow_percent(const int16_t flow, const extruder_t extr) { planner.set_flow(extr, flow); } void setMinFeedrate_mm_s(const feedRate_t fr) { planner.settings.min_feedrate_mm_s = fr; } void setMinTravelFeedrate_mm_s(const feedRate_t fr) { planner.settings.min_travel_feedrate_mm_s = fr; } void setPrintingAcceleration_mm_s2(const float acc) { planner.settings.acceleration = acc; } void setRetractAcceleration_mm_s2(const float acc) { planner.settings.retract_acceleration = acc; } void setTravelAcceleration_mm_s2(const float acc) { planner.settings.travel_acceleration = acc; } #if ENABLED(BABYSTEPPING) bool babystepAxis_steps(const int16_t steps, const axis_t axis) { switch (axis) { #if ENABLED(BABYSTEP_XY) case X: babystep.add_steps(X_AXIS, steps); break; case Y: babystep.add_steps(Y_AXIS, steps); break; #endif case Z: babystep.add_steps(Z_AXIS, steps); break; default: return false; }; return true; } /** * This function adjusts an axis during a print. * * When linked_nozzles is false, each nozzle in a multi-nozzle * printer can be babystepped independently of the others. This * lets the user to fine tune the Z-offset and Nozzle Offsets * while observing the first layer of a print, regardless of * what nozzle is printing. */ void smartAdjustAxis_steps(const int16_t steps, const axis_t axis, bool linked_nozzles) { const float mm = steps * planner.steps_to_mm[axis]; if (!babystepAxis_steps(steps, axis)) return; #if ENABLED(BABYSTEP_ZPROBE_OFFSET) // Make it so babystepping in Z adjusts the Z probe offset. if (axis == Z #if EXTRUDERS > 1 && (linked_nozzles || active_extruder == 0) #endif ) probe.offset.z += mm; #else UNUSED(mm); #endif #if EXTRUDERS > 1 && HAS_HOTEND_OFFSET /** * When linked_nozzles is false, as an axis is babystepped * adjust the hotend offsets so that the other nozzles are * unaffected by the babystepping of the active nozzle. */ if (!linked_nozzles) { HOTEND_LOOP() if (e != active_extruder) hotend_offset[e][axis] += mm; normalizeNozzleOffset(X); normalizeNozzleOffset(Y); normalizeNozzleOffset(Z); } #else UNUSED(linked_nozzles); UNUSED(mm); #endif } /** * Converts a mm displacement to a number of whole number of * steps that is at least mm long. */ int16_t mmToWholeSteps(const float mm, const axis_t axis) { const float steps = mm / planner.steps_to_mm[axis]; return steps > 0 ? CEIL(steps) : FLOOR(steps); } #endif float getZOffset_mm() { #if HAS_BED_PROBE return probe.offset.z; #elif ENABLED(BABYSTEP_DISPLAY_TOTAL) return (planner.steps_to_mm[Z_AXIS] * babystep.axis_total[BS_AXIS_IND(Z_AXIS)]); #else return 0.0; #endif } void setZOffset_mm(const float value) { #if HAS_BED_PROBE if (WITHIN(value, Z_PROBE_OFFSET_RANGE_MIN, Z_PROBE_OFFSET_RANGE_MAX)) probe.offset.z = value; #elif ENABLED(BABYSTEP_DISPLAY_TOTAL) babystep.add_mm(Z_AXIS, (value - getZOffset_mm())); #else UNUSED(value); #endif } #if HAS_HOTEND_OFFSET float getNozzleOffset_mm(const axis_t axis, const extruder_t extruder) { if (extruder - E0 >= HOTENDS) return 0; return hotend_offset[extruder - E0][axis]; } void setNozzleOffset_mm(const float value, const axis_t axis, const extruder_t extruder) { if (extruder - E0 >= HOTENDS) return; hotend_offset[extruder - E0][axis] = value; } /** * The UI should call this if needs to guarantee the first * nozzle offset is zero (such as when it doesn't allow the * user to edit the offset the first nozzle). */ void normalizeNozzleOffset(const axis_t axis) { const float offs = hotend_offset[0][axis]; HOTEND_LOOP() hotend_offset[e][axis] -= offs; } #endif // HAS_HOTEND_OFFSET #if HAS_BED_PROBE float getProbeOffset_mm(const axis_t axis) { return probe.offset.pos[axis]; } void setProbeOffset_mm(const float val, const axis_t axis) { probe.offset.pos[axis] = val; } #endif #if ENABLED(BACKLASH_GCODE) float getAxisBacklash_mm(const axis_t axis) { return backlash.distance_mm[axis]; } void setAxisBacklash_mm(const float value, const axis_t axis) { backlash.distance_mm[axis] = constrain(value,0,5); } float getBacklashCorrection_percent() { return ui8_to_percent(backlash.correction); } void setBacklashCorrection_percent(const float value) { backlash.correction = map(constrain(value, 0, 100), 0, 100, 0, 255); } #ifdef BACKLASH_SMOOTHING_MM float getBacklashSmoothing_mm() { return backlash.smoothing_mm; } void setBacklashSmoothing_mm(const float value) { backlash.smoothing_mm = constrain(value, 0, 999); } #endif #endif uint8_t getProgress_percent() { return ui.get_progress_percent(); } uint32_t getProgress_seconds_elapsed() { const duration_t elapsed = print_job_timer.duration(); return elapsed.value; } #if HAS_LEVELING bool getLevelingActive() { return planner.leveling_active; } void setLevelingActive(const bool state) { set_bed_leveling_enabled(state); } bool getMeshValid() { return leveling_is_valid(); } #if HAS_MESH bed_mesh_t& getMeshArray() { return Z_VALUES_ARR; } float getMeshPoint(const xy_uint8_t &pos) { return Z_VALUES(pos.x, pos.y); } void setMeshPoint(const xy_uint8_t &pos, const float zoff) { if (WITHIN(pos.x, 0, GRID_MAX_POINTS_X) && WITHIN(pos.y, 0, GRID_MAX_POINTS_Y)) { Z_VALUES(pos.x, pos.y) = zoff; TERN_(ABL_BILINEAR_SUBDIVISION, bed_level_virt_interpolate()); } } #endif #endif #if ENABLED(HOST_PROMPT_SUPPORT) void setHostResponse(const uint8_t response) { host_response_handler(response); } #endif #if ENABLED(PRINTCOUNTER) char* getTotalPrints_str(char buffer[21]) { strcpy(buffer,i16tostr3left(print_job_timer.getStats().totalPrints)); return buffer; } char* getFinishedPrints_str(char buffer[21]) { strcpy(buffer,i16tostr3left(print_job_timer.getStats().finishedPrints)); return buffer; } char* getTotalPrintTime_str(char buffer[21]) { return duration_t(print_job_timer.getStats().printTime).toString(buffer); } char* getLongestPrint_str(char buffer[21]) { return duration_t(print_job_timer.getStats().longestPrint).toString(buffer); } char* getFilamentUsed_str(char buffer[21]) { printStatistics stats = print_job_timer.getStats(); sprintf_P(buffer, PSTR("%ld.%im"), long(stats.filamentUsed / 1000), int16_t(stats.filamentUsed / 100) % 10); return buffer; } #endif float getFeedrate_percent() { return feedrate_percentage; } #if ENABLED(PIDTEMP) float getPIDValues_Kp(const extruder_t tool) { return PID_PARAM(Kp, tool); } float getPIDValues_Ki(const extruder_t tool) { return unscalePID_i(PID_PARAM(Ki, tool)); } float getPIDValues_Kd(const extruder_t tool) { return unscalePID_d(PID_PARAM(Kd, tool)); } void setPIDValues(const float p, const float i, const float d, extruder_t tool) { thermalManager.temp_hotend[tool].pid.Kp = p; thermalManager.temp_hotend[tool].pid.Ki = scalePID_i(i); thermalManager.temp_hotend[tool].pid.Kd = scalePID_d(d); thermalManager.updatePID(); } void startPIDTune(const float temp, extruder_t tool){ thermalManager.PID_autotune(temp, (heater_ind_t)tool, 8, true); } #endif #if ENABLED(PIDTEMPBED) float getBedPIDValues_Kp() { return thermalManager.temp_bed.pid.Kp; } float getBedPIDValues_Ki() { return unscalePID_i(thermalManager.temp_bed.pid.Ki); } float getBedPIDValues_Kd() { return unscalePID_d(thermalManager.temp_bed.pid.Kd); } void setBedPIDValues(const float p, const float i, const float d) { thermalManager.temp_bed.pid.Kp = p; thermalManager.temp_bed.pid.Ki = scalePID_i(i); thermalManager.temp_bed.pid.Kd = scalePID_d(d); thermalManager.updatePID(); } void startBedPIDTune(const float temp) { thermalManager.PID_autotune(temp, H_BED, 4, true); } #endif void injectCommands_P(PGM_P const gcode) { queue.inject_P(gcode); } bool commandsInQueue() { return (planner.movesplanned() || queue.has_commands_queued()); } bool isAxisPositionKnown(const axis_t axis) { return TEST(axis_known_position, axis); } bool isAxisPositionKnown(const extruder_t) { return TEST(axis_known_position, E_AXIS); } bool isPositionKnown() { return all_axes_known(); } bool isMachineHomed() { return all_axes_homed(); } PGM_P getFirmwareName_str() { static const char firmware_name[] PROGMEM = "Marlin " SHORT_BUILD_VERSION; return firmware_name; } void setTargetTemp_celsius(float value, const heater_t heater) { #ifdef TOUCH_UI_LCD_TEMP_SCALING value *= TOUCH_UI_LCD_TEMP_SCALING; #endif enableHeater(heater); #if HAS_HEATED_CHAMBER if (heater == CHAMBER) thermalManager.setTargetChamber(LROUND(constrain(value, 0, CHAMBER_MAXTEMP - 10))); else #endif #if HAS_HEATED_BED if (heater == BED) thermalManager.setTargetBed(LROUND(constrain(value, 0, BED_MAX_TARGET))); else #endif { #if HAS_HOTEND const int16_t e = heater - H0; thermalManager.setTargetHotend(LROUND(constrain(value, 0, thermalManager.heater_maxtemp[e] - HOTEND_OVERSHOOT)), e); #endif } } void setTargetTemp_celsius(float value, const extruder_t extruder) { #ifdef TOUCH_UI_LCD_TEMP_SCALING value *= TOUCH_UI_LCD_TEMP_SCALING; #endif #if HAS_HOTEND const int16_t e = extruder - E0; enableHeater(extruder); thermalManager.setTargetHotend(LROUND(constrain(value, 0, thermalManager.heater_maxtemp[e] - HOTEND_OVERSHOOT)), e); #endif } void setTargetFan_percent(const float value, const fan_t fan) { #if HAS_FAN if (fan < FAN_COUNT) thermalManager.set_fan_speed(fan - FAN0, map(constrain(value, 0, 100), 0, 100, 0, 255)); #else UNUSED(value); UNUSED(fan); #endif } void setFeedrate_percent(const float value) { feedrate_percentage = constrain(value, 10, 500); } void setUserConfirmed() { TERN_(HAS_RESUME_CONTINUE, wait_for_user = false); } void printFile(const char *filename) { UNUSED(filename); IFSD(card.openAndPrintFile(filename), NOOP); } bool isPrintingFromMediaPaused() { return IFSD(isPrintingFromMedia() && !IS_SD_PRINTING(), false); } bool isPrintingFromMedia() { #if ENABLED(SDSUPPORT) // Account for when IS_SD_PRINTING() reports the end of the // print when there is still SD card data in the planner. flags.was_sd_printing = card.isFileOpen() || (flags.was_sd_printing && commandsInQueue()); return flags.was_sd_printing; #else return false; #endif } bool isPrinting() { return (commandsInQueue() || isPrintingFromMedia() || IFSD(IS_SD_PRINTING(), false)); } bool isMediaInserted() { return IFSD(IS_SD_INSERTED() && card.isMounted(), false); } void pausePrint() { ui.pause_print(); } void resumePrint() { ui.resume_print(); } void stopPrint() { ui.abort_print(); } void onUserConfirmRequired_P(PGM_P const pstr) { char msg[strlen_P(pstr) + 1]; strcpy_P(msg, pstr); onUserConfirmRequired(msg); } void onStatusChanged_P(PGM_P const pstr) { char msg[strlen_P(pstr) + 1]; strcpy_P(msg, pstr); onStatusChanged(msg); } FileList::FileList() { refresh(); } void FileList::refresh() { num_files = 0xFFFF; } bool FileList::seek(const uint16_t pos, const bool skip_range_check) { #if ENABLED(SDSUPPORT) if (!skip_range_check && (pos + 1) > count()) return false; card.getfilename_sorted(SD_ORDER(pos, count())); return card.filename[0] != '\0'; #else UNUSED(pos); UNUSED(skip_range_check); return false; #endif } const char* FileList::filename() { return IFSD(card.longFilename[0] ? card.longFilename : card.filename, ""); } const char* FileList::shortFilename() { return IFSD(card.filename, ""); } const char* FileList::longFilename() { return IFSD(card.longFilename, ""); } bool FileList::isDir() { return IFSD(card.flag.filenameIsDir, false); } uint16_t FileList::count() { return IFSD((num_files = (num_files == 0xFFFF ? card.get_num_Files() : num_files)), 0); } bool FileList::isAtRootDir() { return (true #if ENABLED(SDSUPPORT) && card.flag.workDirIsRoot #endif ); } void FileList::upDir() { #if ENABLED(SDSUPPORT) card.cdup(); num_files = 0xFFFF; #endif } void FileList::changeDir(const char * const dirname) { #if ENABLED(SDSUPPORT) card.cd(dirname); num_files = 0xFFFF; #else UNUSED(dirname); #endif } } // namespace ExtUI // At the moment, we piggy-back off the ultralcd calls, but this could be cleaned up in the future void MarlinUI::init() { #if ENABLED(SDSUPPORT) && PIN_EXISTS(SD_DETECT) SET_INPUT_PULLUP(SD_DETECT_PIN); #endif ExtUI::onStartup(); } void MarlinUI::update() { ExtUI::onIdle(); } void MarlinUI::kill_screen(PGM_P const error, PGM_P const component) { using namespace ExtUI; if (!flags.printer_killed) { flags.printer_killed = true; onPrinterKilled(error, component); } } #endif // EXTENSIBLE_UI