use push/pop state to save current state on suspend/resume
[clinton/Smoothieware.git] / src / modules / tools / extruder / Extruder.cpp
index 6f9de1e..212f5b7 100644 (file)
@@ -24,6 +24,8 @@
 #include "Gcode.h"
 #include "libs/StreamOutput.h"
 #include "PublicDataRequest.h"
+#include "StreamOutputPool.h"
+#include "ExtruderPublicAccess.h"
 
 #include <mri.h>
 
@@ -36,9 +38,9 @@
 #define extruder_dir_pin_checksum            CHECKSUM("extruder_dir_pin")
 #define extruder_en_pin_checksum             CHECKSUM("extruder_en_pin")
 #define extruder_max_speed_checksum          CHECKSUM("extruder_max_speed")
+#define extruder_default_feed_rate_checksum  CHECKSUM("extruder_default_feed_rate")
 
 // NEW config names
-#define extruder_checksum                    CHECKSUM("extruder")
 
 #define default_feed_rate_checksum           CHECKSUM("default_feed_rate")
 #define steps_per_mm_checksum                CHECKSUM("steps_per_mm")
@@ -59,9 +61,6 @@
 #define retract_zlift_length_checksum        CHECKSUM("retract_zlift_length")
 #define retract_zlift_feedrate_checksum      CHECKSUM("retract_zlift_feedrate")
 
-#define save_state_checksum                  CHECKSUM("save_state")
-#define restore_state_checksum               CHECKSUM("restore_state")
-
 #define X_AXIS      0
 #define Y_AXIS      1
 #define Z_AXIS      2
@@ -81,6 +80,7 @@
 Extruder::Extruder( uint16_t config_identifier, bool single )
 {
     this->absolute_mode = true;
+    this->milestone_absolute_mode = true;
     this->enabled = false;
     this->paused = false;
     this->single_config = single;
@@ -88,7 +88,9 @@ Extruder::Extruder( uint16_t config_identifier, bool single )
     this->retracted = false;
     this->volumetric_multiplier = 1.0F;
     this->extruder_multiplier = 1.0F;
-    this->stepper_motor= nullptr;
+    this->stepper_motor = nullptr;
+    this->milestone_last_position = 0;
+    this->max_volumetric_rate = 0;
 
     memset(this->offset, 0, sizeof(this->offset));
 }
@@ -131,7 +133,9 @@ void Extruder::on_module_loaded()
     this->register_for_event(ON_SET_PUBLIC_DATA);
 
     // Update speed every *acceleration_ticks_per_second*
-    THEKERNEL->step_ticker->register_acceleration_tick_handler([this](){acceleration_tick(); });
+    THEKERNEL->step_ticker->register_acceleration_tick_handler([this]() {
+        acceleration_tick();
+    });
 }
 
 // Get config
@@ -143,7 +147,7 @@ void Extruder::on_config_reload(void *argument)
         this->steps_per_millimeter        = THEKERNEL->config->value(extruder_steps_per_mm_checksum      )->by_default(1)->as_number();
         this->filament_diameter           = THEKERNEL->config->value(extruder_filament_diameter_checksum )->by_default(0)->as_number();
         this->acceleration                = THEKERNEL->config->value(extruder_acceleration_checksum      )->by_default(1000)->as_number();
-        this->feed_rate                   = THEKERNEL->config->value(default_feed_rate_checksum          )->by_default(1000)->as_number();
+        this->feed_rate                   = THEKERNEL->config->value(extruder_default_feed_rate_checksum )->by_default(1000)->as_number();
 
         this->step_pin.from_string(         THEKERNEL->config->value(extruder_step_pin_checksum          )->by_default("nc" )->as_string())->as_output();
         this->dir_pin.from_string(          THEKERNEL->config->value(extruder_dir_pin_checksum           )->by_default("nc" )->as_string())->as_output();
@@ -161,7 +165,7 @@ void Extruder::on_config_reload(void *argument)
         this->steps_per_millimeter = THEKERNEL->config->value(extruder_checksum, this->identifier, steps_per_mm_checksum      )->by_default(1)->as_number();
         this->filament_diameter    = THEKERNEL->config->value(extruder_checksum, this->identifier, filament_diameter_checksum )->by_default(0)->as_number();
         this->acceleration         = THEKERNEL->config->value(extruder_checksum, this->identifier, acceleration_checksum      )->by_default(1000)->as_number();
-        this->feed_rate            = THEKERNEL->config->value(                                     default_feed_rate_checksum )->by_default(1000)->as_number();
+        this->feed_rate            = THEKERNEL->config->value(extruder_checksum, this->identifier, default_feed_rate_checksum )->by_default(1000)->as_number();
 
         this->step_pin.from_string( THEKERNEL->config->value(extruder_checksum, this->identifier, step_pin_checksum          )->by_default("nc" )->as_string())->as_output();
         this->dir_pin.from_string(  THEKERNEL->config->value(extruder_checksum, this->identifier, dir_pin_checksum           )->by_default("nc" )->as_string())->as_output();
@@ -179,7 +183,7 @@ void Extruder::on_config_reload(void *argument)
     this->retract_recover_length   = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_recover_length_checksum)->by_default(0)->as_number();
     this->retract_recover_feedrate = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_recover_feedrate_checksum)->by_default(8)->as_number();
     this->retract_zlift_length     = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_zlift_length_checksum)->by_default(0)->as_number();
-    this->retract_zlift_feedrate   = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_zlift_feedrate_checksum)->by_default(100*60)->as_number(); // mm/min
+    this->retract_zlift_feedrate   = THEKERNEL->config->value(extruder_checksum, this->identifier, retract_zlift_feedrate_checksum)->by_default(100 * 60)->as_number(); // mm/min
 
     if(filament_diameter > 0.01F) {
         this->volumetric_multiplier = 1.0F / (powf(this->filament_diameter / 2, 2) * PI);
@@ -190,13 +194,14 @@ void Extruder::on_config_reload(void *argument)
     this->stepper_motor->attach(this, &Extruder::stepper_motor_finished_move );
     if( this->single_config ) {
         this->stepper_motor->set_max_rate(THEKERNEL->config->value(extruder_max_speed_checksum)->by_default(1000)->as_number());
-    }else{
+    } else {
         this->stepper_motor->set_max_rate(THEKERNEL->config->value(extruder_checksum, this->identifier, max_speed_checksum)->by_default(1000)->as_number());
     }
 }
 
-void Extruder::on_get_public_data(void* argument){
-    PublicDataRequest* pdr = static_cast<PublicDataRequest*>(argument);
+void Extruder::on_get_public_data(void *argument)
+{
+    PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
 
     if(!pdr->starts_with(extruder_checksum)) return;
 
@@ -207,20 +212,83 @@ void Extruder::on_get_public_data(void* argument){
     }
 }
 
+// check against maximum speeds and return the rate modifier
+float Extruder::check_max_speeds(float target, float isecs)
+{
+    float rm = 1.0F; // default no rate modification
+    float delta;
+    // get change in E (may be mm or mm³)
+    if(milestone_absolute_mode) {
+        delta = fabsf(target - milestone_last_position); // delta move
+        milestone_last_position = target;
+
+    } else {
+        delta = target;
+        milestone_last_position += target;
+    }
+
+    if(this->max_volumetric_rate > 0 && this->filament_diameter > 0.01F) {
+        // volumetric enabled and check for volumetric rate
+        float v = delta * isecs; // the flow rate in mm³/sec
+
+        // return the rate change needed to stay within the max rate
+        if(v > max_volumetric_rate) {
+            rm = max_volumetric_rate / v;
+            isecs *= rm; // this slows the rate down for the next test
+        }
+        //THEKERNEL->streams->printf("requested flow rate: %f mm³/sec, corrected flow rate: %f  mm³/sec\n", v, v * rm);
+    }
+
+    // check for max speed as well
+    float max_speed = this->stepper_motor->get_max_rate();
+    if(max_speed > 0) {
+        if(this->filament_diameter > 0.01F) {
+            // volumetric so need to convert delta which is mm³ to mm
+            delta *= volumetric_multiplier;
+        }
+
+        float sm = 1.0F;
+        float v = delta * isecs; // the speed in mm/sec
+        if(v > max_speed) {
+            sm *= (max_speed / v);
+        }
+        //THEKERNEL->streams->printf("requested speed: %f mm/sec, corrected speed: %f  mm/sec\n", v, v * sm);
+        rm *= sm;
+    }
+    return rm;
+}
+
 void Extruder::on_set_public_data(void *argument)
 {
     PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
 
     if(!pdr->starts_with(extruder_checksum)) return;
 
+    // handle extrude rates request from robot
+    if(pdr->second_element_is(target_checksum)) {
+        // disabled extruders do not reply NOTE only one enabled extruder supported
+        if(!this->enabled) return;
+
+        float *d = static_cast<float *>(pdr->get_data_ptr());
+        float target = d[0]; // the E passed in on Gcode is in mm³ (maybe absolute or relative)
+        float isecs = d[1]; // inverted secs
+
+        // check against maximum speeds and return rate modifier
+        d[1] = check_max_speeds(target, isecs);
+
+        pdr->set_taken();
+        return;
+    }
+
     // save or restore state
     if(pdr->second_element_is(save_state_checksum)) {
-        this->saved_current_position= this->current_position;
-        this->saved_absolute_mode= this->absolute_mode;
+        this->saved_current_position = this->current_position;
+        this->saved_absolute_mode = this->absolute_mode;
         pdr->set_taken();
-    }else if(pdr->second_element_is(restore_state_checksum)) {
-        this->current_position= this->saved_current_position;
-        this->absolute_mode= this->saved_absolute_mode;
+    } else if(pdr->second_element_is(restore_state_checksum)) {
+        // NOTE this only gets called when the queue is empty so the milestones will be the same
+        this->milestone_last_position= this->current_position = this->saved_current_position;
+        this->milestone_absolute_mode= this->absolute_mode = this->saved_absolute_mode;
         pdr->set_taken();
     }
 }
@@ -249,7 +317,6 @@ void Extruder::on_gcode_received(void *argument)
             char buf[16];
             int n = snprintf(buf, sizeof(buf), " E:%1.3f ", this->current_position);
             gcode->txt_after_ok.append(buf, n);
-            gcode->mark_as_taken();
 
         } else if (gcode->m == 92 && ( (this->enabled && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier) ) ) {
             float spm = this->steps_per_millimeter;
@@ -260,7 +327,6 @@ void Extruder::on_gcode_received(void *argument)
 
             gcode->stream->printf("E:%g ", spm);
             gcode->add_nl = true;
-            gcode->mark_as_taken();
 
         } else if (gcode->m == 200 && ( (this->enabled && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
             if (gcode->has_letter('D')) {
@@ -268,85 +334,105 @@ void Extruder::on_gcode_received(void *argument)
                 this->filament_diameter = gcode->get_value('D');
                 if(filament_diameter > 0.01F) {
                     this->volumetric_multiplier = 1.0F / (powf(this->filament_diameter / 2, 2) * PI);
-                }else{
+                } else {
                     this->volumetric_multiplier = 1.0F;
                 }
-            }else {
+            } else {
                 if(filament_diameter > 0.01F) {
                     gcode->stream->printf("Filament Diameter: %f\n", this->filament_diameter);
-                }else{
+                } else {
                     gcode->stream->printf("Volumetric extrusion is disabled\n");
                 }
             }
-            gcode->mark_as_taken();
+
+        } else if (gcode->m == 203 && ( (this->enabled && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
+            // M203 Exxx Vyyy Set maximum feedrates xxx mm/sec and/or yyy mm³/sec
+            if(gcode->get_num_args() == 0) {
+                gcode->stream->printf("E:%g V:%g", this->stepper_motor->get_max_rate(), this->max_volumetric_rate);
+                gcode->add_nl = true;
+
+            } else {
+                if(gcode->has_letter('E')) {
+                    this->stepper_motor->set_max_rate(gcode->get_value('E'));
+                }
+                if(gcode->has_letter('V')) {
+                    this->max_volumetric_rate = gcode->get_value('V');
+                }
+            }
 
         } else if (gcode->m == 204 && gcode->has_letter('E') &&
                    ( (this->enabled && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
             // extruder acceleration M204 Ennn mm/sec^2 (Pnnn sets the specific extruder for M500)
-            this->acceleration= gcode->get_value('E');
-            gcode->mark_as_taken();
+            this->acceleration = gcode->get_value('E');
 
         } else if (gcode->m == 207 && ( (this->enabled && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
             // M207 - set retract length S[positive mm] F[feedrate mm/min] Z[additional zlift/hop] Q[zlift feedrate mm/min]
             if(gcode->has_letter('S')) retract_length = gcode->get_value('S');
-            if(gcode->has_letter('F')) retract_feedrate = gcode->get_value('F')/60.0F; // specified in mm/min converted to mm/sec
+            if(gcode->has_letter('F')) retract_feedrate = gcode->get_value('F') / 60.0F; // specified in mm/min converted to mm/sec
             if(gcode->has_letter('Z')) retract_zlift_length = gcode->get_value('Z');
             if(gcode->has_letter('Q')) retract_zlift_feedrate = gcode->get_value('Q');
-            gcode->mark_as_taken();
 
         } else if (gcode->m == 208 && ( (this->enabled && !gcode->has_letter('P')) || (gcode->has_letter('P') && gcode->get_value('P') == this->identifier)) ) {
             // M208 - set retract recover length S[positive mm surplus to the M207 S*] F[feedrate mm/min]
             if(gcode->has_letter('S')) retract_recover_length = gcode->get_value('S');
-            if(gcode->has_letter('F')) retract_recover_feedrate = gcode->get_value('F')/60.0F; // specified in mm/min converted to mm/sec
-            gcode->mark_as_taken();
+            if(gcode->has_letter('F')) retract_recover_feedrate = gcode->get_value('F') / 60.0F; // specified in mm/min converted to mm/sec
 
         } else if (gcode->m == 221 && this->enabled) { // M221 S100 change flow rate by percentage
-            if(gcode->has_letter('S')) this->extruder_multiplier= gcode->get_value('S')/100.0F;
-            gcode->mark_as_taken();
+            if(gcode->has_letter('S')) {
+                this->extruder_multiplier = gcode->get_value('S') / 100.0F;
+            } else {
+                gcode->stream->printf("Flow rate at %6.2f %%\n", this->extruder_multiplier * 100.0F);
+            }
 
         } else if (gcode->m == 500 || gcode->m == 503) { // M500 saves some volatile settings to config override file, M503 just prints the settings
             if( this->single_config ) {
                 gcode->stream->printf(";E Steps per mm:\nM92 E%1.4f\n", this->steps_per_millimeter);
                 gcode->stream->printf(";E Filament diameter:\nM200 D%1.4f\n", this->filament_diameter);
-                gcode->stream->printf(";E retract length, feedrate, zlift length, feedrate:\nM207 S%1.4f F%1.4f Z%1.4f Q%1.4f\n", this->retract_length, this->retract_feedrate*60.0F, this->retract_zlift_length, this->retract_zlift_feedrate);
-                gcode->stream->printf(";E retract recover length, feedrate:\nM208 S%1.4f F%1.4f\n", this->retract_recover_length, this->retract_recover_feedrate*60.0F);
-                gcode->stream->printf(";E acceleration mm/sec^2:\nM204 E%1.4f\n", this->acceleration);
+                gcode->stream->printf(";E retract length, feedrate, zlift length, feedrate:\nM207 S%1.4f F%1.4f Z%1.4f Q%1.4f\n", this->retract_length, this->retract_feedrate * 60.0F, this->retract_zlift_length, this->retract_zlift_feedrate);
+                gcode->stream->printf(";E retract recover length, feedrate:\nM208 S%1.4f F%1.4f\n", this->retract_recover_length, this->retract_recover_feedrate * 60.0F);
+                gcode->stream->printf(";E acceleration mm/sec²:\nM204 E%1.4f\n", this->acceleration);
+                gcode->stream->printf(";E max feed rate mm/sec:\nM203 E%1.4f\n", this->stepper_motor->get_max_rate());
+                if(this->max_volumetric_rate > 0) {
+                    gcode->stream->printf(";E max volumetric rate mm³/sec:\nM203 V%1.4f\n", this->max_volumetric_rate);
+                }
 
             } else {
                 gcode->stream->printf(";E Steps per mm:\nM92 E%1.4f P%d\n", this->steps_per_millimeter, this->identifier);
                 gcode->stream->printf(";E Filament diameter:\nM200 D%1.4f P%d\n", this->filament_diameter, this->identifier);
-                gcode->stream->printf(";E retract length, feedrate:\nM207 S%1.4f F%1.4f Z%1.4f Q%1.4f P%d\n", this->retract_length, this->retract_feedrate*60.0F, this->retract_zlift_length, this->retract_zlift_feedrate, this->identifier);
-                gcode->stream->printf(";E retract recover length, feedrate:\nM208 S%1.4f F%1.4f P%d\n", this->retract_recover_length, this->retract_recover_feedrate*60.0F, this->identifier);
-                gcode->stream->printf(";E acceleration mm/sec^2:\nM204 E%1.4f P%d\n", this->acceleration, this->identifier);
+                gcode->stream->printf(";E retract length, feedrate:\nM207 S%1.4f F%1.4f Z%1.4f Q%1.4f P%d\n", this->retract_length, this->retract_feedrate * 60.0F, this->retract_zlift_length, this->retract_zlift_feedrate, this->identifier);
+                gcode->stream->printf(";E retract recover length, feedrate:\nM208 S%1.4f F%1.4f P%d\n", this->retract_recover_length, this->retract_recover_feedrate * 60.0F, this->identifier);
+                gcode->stream->printf(";E acceleration mm/sec²:\nM204 E%1.4f P%d\n", this->acceleration, this->identifier);
+                gcode->stream->printf(";E max feed rate mm/sec:\nM203 E%1.4f P%d\n", this->stepper_motor->get_max_rate(), this->identifier);
+                if(this->max_volumetric_rate > 0) {
+                    gcode->stream->printf(";E max volumetric rate mm³/sec:\nM203 V%1.4f P%d\n", this->max_volumetric_rate, this->identifier);
+                }
             }
-            gcode->mark_as_taken();
+
         } else if( gcode->m == 17 || gcode->m == 18 || gcode->m == 82 || gcode->m == 83 || gcode->m == 84 ) {
             // Mcodes to pass along to on_gcode_execute
             THEKERNEL->conveyor->append_gcode(gcode);
-            gcode->mark_as_taken();
+
         }
 
-    }else if(gcode->has_g) {
+    } else if(gcode->has_g) {
         // G codes, NOTE some are ignored if not enabled
         if( (gcode->g == 92 && gcode->has_letter('E')) || (gcode->g == 90 || gcode->g == 91) ) {
             // Gcodes to pass along to on_gcode_execute
             THEKERNEL->conveyor->append_gcode(gcode);
-            gcode->mark_as_taken();
 
-        }else if( this->enabled && gcode->g < 4 && gcode->has_letter('E') && !gcode->has_letter('X') && !gcode->has_letter('Y') && !gcode->has_letter('Z') ) {
-            // This is a solo move, we add an empty block to the queue to prevent subsequent gcodes being executed at the same time
+        } else if( this->enabled && gcode->g < 4 && gcode->has_letter('E') && fabsf(gcode->millimeters_of_travel) < 0.00001F ) { // With floating numbers, we can have 0 != 0, NOTE needs to be same as in Robot.cpp#745
+            // NOTE was ... gcode->has_letter('E') && !gcode->has_letter('X') && !gcode->has_letter('Y') && !gcode->has_letter('Z') ) {
+            // This is a SOLO move, we add an empty block to the queue to prevent subsequent gcodes being executed at the same time
             THEKERNEL->conveyor->append_gcode(gcode);
             THEKERNEL->conveyor->queue_head_block();
-            gcode->mark_as_taken();
 
-        }else if( this->enabled && (gcode->g == 10 || gcode->g == 11) ) { // firmware retract command
-            gcode->mark_as_taken();
+        } else if( this->enabled && (gcode->g == 10 || gcode->g == 11) ) { // firmware retract command
             // check we are in the correct state of retract or unretract
             if(gcode->g == 10 && !retracted) {
-                this->retracted= true;
-                this->cancel_zlift_restore= false;
-            } else if(gcode->g == 11 && retracted){
-                this->retracted= false;
+                this->retracted = true;
+                this->cancel_zlift_restore = false;
+            } else if(gcode->g == 11 && retracted) {
+                this->retracted = false;
             } else
                 return; // ignore duplicates
 
@@ -356,13 +442,13 @@ void Extruder::on_gcode_received(void *argument)
                 // reverse zlift happens before unretract
                 // NOTE we do not do this if cancel_zlift_restore is set to true, which happens if there is an absolute Z move inbetween G10 and G11
                 char buf[32];
-                int n= snprintf(buf, sizeof(buf), "G0 Z%1.4f F%1.4f", -retract_zlift_length, retract_zlift_feedrate);
+                int n = snprintf(buf, sizeof(buf), "G0 Z%1.4f F%1.4f", -retract_zlift_length, retract_zlift_feedrate);
                 string cmd(buf, n);
                 Gcode gc(cmd, &(StreamOutput::NullStream));
-                bool oldmode= THEKERNEL->robot->absolute_mode;
-                THEKERNEL->robot->absolute_mode= false; // needs to be relative mode
+                bool oldmode = THEKERNEL->robot->absolute_mode;
+                THEKERNEL->robot->absolute_mode = false; // needs to be relative mode
                 THEKERNEL->robot->on_gcode_received(&gc); // send to robot directly
-                THEKERNEL->robot->absolute_mode= oldmode; // restore mode
+                THEKERNEL->robot->absolute_mode = oldmode; // restore mode
             }
 
             // This is a solo move, we add an empty block to the queue to prevent subsequent gcodes being executed at the same time
@@ -371,18 +457,42 @@ void Extruder::on_gcode_received(void *argument)
 
             if(retract_zlift_length > 0 && gcode->g == 10) {
                 char buf[32];
-                int n= snprintf(buf, sizeof(buf), "G0 Z%1.4f F%1.4f", retract_zlift_length, retract_zlift_feedrate);
+                int n = snprintf(buf, sizeof(buf), "G0 Z%1.4f F%1.4f", retract_zlift_length, retract_zlift_feedrate);
                 string cmd(buf, n);
                 Gcode gc(cmd, &(StreamOutput::NullStream));
-                bool oldmode= THEKERNEL->robot->absolute_mode;
-                THEKERNEL->robot->absolute_mode= false; // needs to be relative mode
+                bool oldmode = THEKERNEL->robot->absolute_mode;
+                THEKERNEL->robot->absolute_mode = false; // needs to be relative mode
                 THEKERNEL->robot->on_gcode_received(&gc); // send to robot directly
-                THEKERNEL->robot->absolute_mode= oldmode; // restore mode
+                THEKERNEL->robot->absolute_mode = oldmode; // restore mode
             }
 
-        }else if( this->enabled && this->retracted && (gcode->g == 0 || gcode->g == 1) && gcode->has_letter('Z')) {
+        } else if( this->enabled && this->retracted && (gcode->g == 0 || gcode->g == 1) && gcode->has_letter('Z')) {
             // NOTE we cancel the zlift restore for the following G11 as we have moved to an absolute Z which we need to stay at
-            this->cancel_zlift_restore= true;
+            this->cancel_zlift_restore = true;
+        }
+    }
+
+    // handle some codes now for the volumetric rate limiting
+    // G90 G91 G92 M82 M83
+    if(gcode->has_m) {
+        switch(gcode->m) {
+            case 82: this->milestone_absolute_mode = true; break;
+            case 83: this->milestone_absolute_mode = false; break;
+        }
+
+    } else if(gcode->has_g) {
+        switch(gcode->g) {
+            case 90: this->milestone_absolute_mode = true; break;
+            case 91: this->milestone_absolute_mode = false; break;
+            case 92:
+                if(this->enabled) {
+                    if(gcode->has_letter('E')) {
+                        this->milestone_last_position = gcode->get_value('E');
+                    } else if(gcode->get_num_args() == 0) {
+                        this->milestone_last_position = 0;
+                    }
+                }
+                break;
         }
     }
 }
@@ -398,21 +508,21 @@ void Extruder::on_gcode_execute(void *argument)
     // Absolute/relative mode, globably modal affect all extruders whether enabled or not
     if( gcode->has_m ) {
         switch(gcode->m) {
-        case 17:
-            this->en_pin.set(0);
-            break;
-        case 18:
-            this->en_pin.set(1);
-            break;
-        case 82:
-            this->absolute_mode = true;
-            break;
-        case 83:
-            this->absolute_mode = false;
-            break;
-        case 84:
-            this->en_pin.set(1);
-            break;
+            case 17:
+                this->en_pin.set(0);
+                break;
+            case 18:
+                this->en_pin.set(1);
+                break;
+            case 82:
+                this->absolute_mode = true;
+                break;
+            case 83:
+                this->absolute_mode = false;
+                break;
+            case 84:
+                this->en_pin.set(1);
+                break;
         }
         return;
 
@@ -437,7 +547,7 @@ void Extruder::on_gcode_execute(void *argument)
 
         } else if (gcode->g == 10) {
             // FW retract command
-            feed_rate= retract_feedrate; // mm/sec
+            feed_rate = retract_feedrate; // mm/sec
             this->mode = SOLO;
             this->travel_distance = -retract_length;
             this->target_position += this->travel_distance;
@@ -445,7 +555,7 @@ void Extruder::on_gcode_execute(void *argument)
 
         } else if (gcode->g == 11) {
             // un retract command
-            feed_rate= retract_recover_feedrate; // mm/sec
+            feed_rate = retract_recover_feedrate; // mm/sec
             this->mode = SOLO;
             this->travel_distance = (retract_length + retract_recover_length);
             this->target_position += this->travel_distance;
@@ -454,7 +564,7 @@ void Extruder::on_gcode_execute(void *argument)
         } else if (gcode->g == 0 || gcode->g == 1) {
             // Extrusion length from 'G' Gcode
             if( gcode->has_letter('E' )) {
-                // Get relative extrusion distance depending on mode ( in absolute mode we must substract target_position )
+                // Get relative extrusion distance depending on mode ( in absolute mode we must subtract target_position )
                 float extrusion_distance = gcode->get_value('E');
                 float relative_extrusion_distance = extrusion_distance;
                 if (this->absolute_mode) {
@@ -465,22 +575,22 @@ void Extruder::on_gcode_execute(void *argument)
                 }
 
                 // If the robot is moving, we follow it's movement, otherwise, we move alone
-                if( fabs(gcode->millimeters_of_travel) < 0.00001F ) { // With floating numbers, we can have 0 != 0, NOTE needs to be same as in Robot.cpp#701
+                if( fabsf(gcode->millimeters_of_travel) < 0.00001F ) { // With floating numbers, we can have 0 != 0, NOTE needs to be same as in Robot.cpp#745
                     this->mode = SOLO;
                     this->travel_distance = relative_extrusion_distance;
                 } else {
                     // We move proportionally to the robot's movement
                     this->mode = FOLLOW;
                     this->travel_ratio = (relative_extrusion_distance * this->volumetric_multiplier * this->extruder_multiplier) / gcode->millimeters_of_travel; // adjust for volumetric extrusion and extruder multiplier
-                    // TODO: check resulting flowrate, limit robot speed if it exceeds max_speed
                 }
 
                 this->en_pin.set(0);
             }
 
+            // NOTE this is only used in SOLO mode, but any F on a G0/G1 will set the speed for future retracts that are not firmware retracts
             if (gcode->has_letter('F')) {
                 feed_rate = gcode->get_value('F') / THEKERNEL->robot->get_seconds_per_minute();
-                if (feed_rate > stepper_motor->get_max_rate())
+                if (stepper_motor->get_max_rate() > 0 && feed_rate > stepper_motor->get_max_rate())
                     feed_rate = stepper_motor->get_max_rate();
             }
         }
@@ -526,9 +636,10 @@ void Extruder::on_block_begin(void *argument)
         if(this->mode == FOLLOW) {
             on_speed_change(this); // set initial speed
             this->stepper_motor->set_moved_last_block(true);
-        }else{
+        } else {
             // SOLO
-            this->stepper_motor->set_speed(rate_increase());  // start at first acceleration step
+            uint32_t target_rate = floorf(this->feed_rate * this->steps_per_millimeter);
+            this->stepper_motor->set_speed(min( target_rate, rate_increase() ));  // start at first acceleration step
             this->stepper_motor->set_moved_last_block(false);
         }
 
@@ -547,7 +658,8 @@ void Extruder::on_block_end(void *argument)
     this->current_block = NULL;
 }
 
-uint32_t Extruder::rate_increase() const {
+uint32_t Extruder::rate_increase() const
+{
     return floorf((this->acceleration / THEKERNEL->acceleration_ticks_per_second) * this->steps_per_millimeter);
 }