Merge pull request #390 from wolfmanjm/upstreamedge
[clinton/Smoothieware.git] / src / modules / tools / zprobe / ZProbe.cpp
CommitLineData
88443c6b
JM
1/*
2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie 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.
4 Smoothie 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.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6*/
7
8#include "ZProbe.h"
9
10#include "Kernel.h"
11#include "BaseSolution.h"
12#include "Config.h"
13#include "Robot.h"
14#include "StepperMotor.h"
15#include "StreamOutputPool.h"
16#include "Gcode.h"
17#include "Conveyor.h"
18#include "Stepper.h"
19#include "checksumm.h"
20#include "ConfigValue.h"
21#include "SlowTicker.h"
22#include "Planner.h"
037c350d 23#include "SerialMessage.h"
88443c6b 24
681a62d7
JM
25#include <tuple>
26#include <algorithm>
27
88443c6b
JM
28#define zprobe_checksum CHECKSUM("zprobe")
29#define enable_checksum CHECKSUM("enable")
30#define probe_pin_checksum CHECKSUM("probe_pin")
31#define debounce_count_checksum CHECKSUM("debounce_count")
681a62d7
JM
32#define slow_feedrate_checksum CHECKSUM("slow_feedrate")
33#define fast_feedrate_checksum CHECKSUM("fast_feedrate")
34#define probe_radius_checksum CHECKSUM("probe_radius")
681a62d7 35#define probe_height_checksum CHECKSUM("probe_height")
88443c6b 36
681a62d7 37// from endstop section
b7cd847e 38#define delta_homing_checksum CHECKSUM("delta_homing")
88443c6b
JM
39
40#define X_AXIS 0
41#define Y_AXIS 1
42#define Z_AXIS 2
43
56ce2b5a
JM
44#define STEPS_PER_MM(a) (this->steppers[a]->steps_per_mm)
45#define Z_STEPS_PER_MM STEPS_PER_MM(Z_AXIS)
46
88443c6b
JM
47void ZProbe::on_module_loaded()
48{
49 // if the module is disabled -> do nothing
56ce2b5a 50 if(!THEKERNEL->config->value( zprobe_checksum, enable_checksum )->by_default(false)->as_bool()) {
88443c6b
JM
51 // as this module is not needed free up the resource
52 delete this;
53 return;
54 }
681a62d7 55 this->running = false;
88443c6b
JM
56
57 // load settings
58 this->on_config_reload(this);
59 // register event-handlers
60 register_for_event(ON_CONFIG_RELOAD);
61 register_for_event(ON_GCODE_RECEIVED);
62 register_for_event(ON_IDLE);
63
64 THEKERNEL->slow_ticker->attach( THEKERNEL->stepper->acceleration_ticks_per_second , this, &ZProbe::acceleration_tick );
65}
66
67void ZProbe::on_config_reload(void *argument)
68{
681a62d7
JM
69 this->pin.from_string( THEKERNEL->config->value(zprobe_checksum, probe_pin_checksum)->by_default("nc" )->as_string())->as_input();
70 this->debounce_count = THEKERNEL->config->value(zprobe_checksum, debounce_count_checksum)->by_default(0 )->as_number();
71
037c350d
JM
72 // see what type of arm solution we need to use
73 this->is_delta = THEKERNEL->config->value(delta_homing_checksum)->by_default(false)->as_bool();
74 if(this->is_delta) {
75 // default is probably wrong
76 this->probe_radius = THEKERNEL->config->value(zprobe_checksum, probe_radius_checksum)->by_default(100.0F)->as_number();
77 }
681a62d7 78
681a62d7 79 this->probe_height = THEKERNEL->config->value(zprobe_checksum, probe_height_checksum)->by_default(5.0F)->as_number();
88443c6b
JM
80
81 this->steppers[0] = THEKERNEL->robot->alpha_stepper_motor;
82 this->steppers[1] = THEKERNEL->robot->beta_stepper_motor;
83 this->steppers[2] = THEKERNEL->robot->gamma_stepper_motor;
84
681a62d7
JM
85 this->slow_feedrate = THEKERNEL->config->value(zprobe_checksum, slow_feedrate_checksum)->by_default(5)->as_number(); // feedrate in mm/sec
86 this->fast_feedrate = THEKERNEL->config->value(zprobe_checksum, fast_feedrate_checksum)->by_default(100)->as_number(); // feedrate in mm/sec
88443c6b
JM
87}
88
681a62d7 89bool ZProbe::wait_for_probe(int steps[3])
88443c6b
JM
90{
91 unsigned int debounce = 0;
92 while(true) {
93 THEKERNEL->call_event(ON_IDLE);
94 // if no stepper is moving, moves are finished and there was no touch
95 if( !this->steppers[X_AXIS]->moving && !this->steppers[Y_AXIS]->moving && !this->steppers[Z_AXIS]->moving ) {
96 return false;
97 }
98
99 // if the touchprobe is active...
100 if( this->pin.get() ) {
101 //...increase debounce counter...
102 if( debounce < debounce_count) {
103 // ...but only if the counter hasn't reached the max. value
104 debounce++;
105 } else {
106 // ...otherwise stop the steppers, return its remaining steps
107 for( int i = X_AXIS; i <= Z_AXIS; i++ ) {
108 steps[i] = 0;
109 if ( this->steppers[i]->moving ) {
110 steps[i] = this->steppers[i]->stepped;
111 this->steppers[i]->move(0, 0);
112 }
113 }
114 return true;
115 }
116 } else {
117 // The probe was not hit yet, reset debounce counter
118 debounce = 0;
119 }
120 }
121}
122
123void ZProbe::on_idle(void *argument)
124{
125}
126
127// single probe and report amount moved
681a62d7 128bool ZProbe::run_probe(int& steps, bool fast)
88443c6b
JM
129{
130 // Enable the motors
131 THEKERNEL->stepper->turn_enable_pins_on();
56ce2b5a 132 this->current_feedrate = (fast ? this->fast_feedrate : this->slow_feedrate) * Z_STEPS_PER_MM; // steps/sec
88443c6b
JM
133
134 // move Z down
681a62d7 135 this->running = true;
88443c6b 136 this->steppers[Z_AXIS]->set_speed(0); // will be increased by acceleration tick
56ce2b5a 137 this->steppers[Z_AXIS]->move(true, 1000 * Z_STEPS_PER_MM); // always probes down, no more than 1000mm TODO should be 2*maxz
b7cd847e
JM
138 if(this->is_delta) {
139 // for delta need to move all three actuators
140 this->steppers[X_AXIS]->set_speed(0);
56ce2b5a 141 this->steppers[X_AXIS]->move(true, 1000 * STEPS_PER_MM(X_AXIS));
b7cd847e 142 this->steppers[Y_AXIS]->set_speed(0);
56ce2b5a 143 this->steppers[Y_AXIS]->move(true, 1000 * STEPS_PER_MM(Y_AXIS));
b7cd847e
JM
144 }
145
681a62d7
JM
146 int s[3];
147 bool r = wait_for_probe(s);
56ce2b5a 148 steps= s[Z_AXIS]; // only need z
681a62d7 149 this->running = false;
88443c6b
JM
150 return r;
151}
152
681a62d7
JM
153bool ZProbe::return_probe(int steps)
154{
155 // move probe back to where it was
56ce2b5a 156 this->current_feedrate = this->fast_feedrate * Z_STEPS_PER_MM; // feedrate in steps/sec
681a62d7
JM
157 bool dir= steps < 0;
158 steps= abs(steps);
159
160 this->running = true;
161 this->steppers[Z_AXIS]->set_speed(0); // will be increased by acceleration tick
162 this->steppers[Z_AXIS]->move(dir, steps);
163 if(this->is_delta) {
164 this->steppers[X_AXIS]->set_speed(0);
165 this->steppers[X_AXIS]->move(dir, steps);
166 this->steppers[Y_AXIS]->set_speed(0);
167 this->steppers[Y_AXIS]->move(dir, steps);
168 }
169 while(this->steppers[X_AXIS]->moving || this->steppers[Y_AXIS]->moving || this->steppers[Z_AXIS]->moving) {
170 // wait for it to complete
171 THEKERNEL->call_event(ON_IDLE);
172 }
173
174 this->running = false;
175
176 return true;
177}
178
179// calculate the X and Y positions for the three towers given the radius from the center
180static std::tuple<float, float, float, float, float, float> getCoordinates(float radius)
181{
182 float px = 0.866F * radius; // ~sin(60)
183 float py = 0.5F * radius; // cos(60)
184 float t1x = -px, t1y = -py; // X Tower
185 float t2x = px, t2y = -py; // Y Tower
186 float t3x = 0.0F, t3y = radius; // Z Tower
187 return std::make_tuple(t1x, t1y, t2x, t2y, t3x, t3y);
188}
189
190bool ZProbe::probe_delta_tower(int& steps, float x, float y)
191{
192 int s;
193 // move to tower
194 coordinated_move(x, y, NAN, this->fast_feedrate);
195 if(!run_probe(s)) return false;
196
197 // return to original Z
198 return_probe(s);
199 steps= s;
200
201 return true;
202}
203
fc7b9a7b
JM
204/* Run a calibration routine for a delta
205 1. Home
206 2. probe for z bed
681a62d7
JM
207 3. probe initial tower positions
208 4. set initial trims such that trims will be minimal negative values
209 5. home, probe three towers again
210 6. calculate trim offset and apply to all trims
211 7. repeat 5, 6 4 times to converge on a solution
212 8. home, Probe center
fc7b9a7b
JM
213 9. calculate delta radius and apply it
214 10. check level
215*/
216
037c350d 217bool ZProbe::calibrate_delta_endstops(Gcode *gcode)
fc7b9a7b 218{
037c350d
JM
219 // get probe points
220 float t1x, t1y, t2x, t2y, t3x, t3y;
221 std::tie(t1x, t1y, t2x, t2y, t3x, t3y) = getCoordinates(this->probe_radius);
222
4553f13b
JM
223 gcode->stream->printf("Calibrating Endstops\n");
224
56ce2b5a
JM
225 // TODO get current trim, and continue from that if requested
226
681a62d7
JM
227 // zero trim values
228 set_trim(0, 0, 0, &(StreamOutput::NullStream));
229
230 // home
231 home();
232
233 // find bed, run at fast rate
234 int s;
235 if(!run_probe(s, true)) return false;
236
4553f13b
JM
237 float bedht= s/Z_STEPS_PER_MM - this->probe_height; // distance to move from home to 5mm above bed
238 gcode->stream->printf("Bed ht is %f mm\n", bedht);
681a62d7
JM
239
240 // move to start position
241 home();
4553f13b 242 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // do a relative move from home to the point above the bed
681a62d7
JM
243
244 // get initial probes
245 // probe the base of the X tower
246 if(!probe_delta_tower(s, t1x, t1y)) return false;
56ce2b5a 247 float t1z= s / Z_STEPS_PER_MM;
681a62d7
JM
248 gcode->stream->printf("T1-1 Z:%1.4f C:%d\n", t1z, s);
249
250 // probe the base of the Y tower
251 if(!probe_delta_tower(s, t2x, t2y)) return false;
56ce2b5a 252 float t2z= s / Z_STEPS_PER_MM;
681a62d7
JM
253 gcode->stream->printf("T2-1 Z:%1.4f C:%d\n", t2z, s);
254
255 // probe the base of the Z tower
256 if(!probe_delta_tower(s, t3x, t3y)) return false;
56ce2b5a 257 float t3z= s / Z_STEPS_PER_MM;
681a62d7
JM
258 gcode->stream->printf("T3-1 Z:%1.4f C:%d\n", t3z, s);
259
260 float trimscale= 1.2522F; // empirically determined
261
262 // set initial trims to worst case so we always have a negative trim
263 float min= std::min({t1z, t2z, t3z});
264 float trimx= (min-t1z)*trimscale, trimy= (min-t2z)*trimscale, trimz= (min-t3z)*trimscale;
265
266 // set initial trim
267 set_trim(trimx, trimy, trimz, gcode->stream);
268
56ce2b5a
JM
269 float target= 0.03F;
270 if(gcode->has_letter('I')) target= gcode->get_value('I'); // override default target
271
272 for (int i = 1; i <= 10; ++i) {
681a62d7
JM
273 // home and move probe to start position just above the bed
274 home();
4553f13b 275 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // do a relative move from home to the point above the bed
681a62d7
JM
276
277 // probe the base of the X tower
278 if(!probe_delta_tower(s, t1x, t1y)) return false;
56ce2b5a 279 t1z= s / Z_STEPS_PER_MM;
681a62d7
JM
280 gcode->stream->printf("T1-2-%d Z:%1.4f C:%d\n", i, t1z, s);
281
282 // probe the base of the Y tower
283 if(!probe_delta_tower(s, t2x, t2y)) return false;
56ce2b5a 284 t2z= s / Z_STEPS_PER_MM;
681a62d7
JM
285 gcode->stream->printf("T2-2-%d Z:%1.4f C:%d\n", i, t2z, s);
286
287 // probe the base of the Z tower
288 if(!probe_delta_tower(s, t3x, t3y)) return false;
56ce2b5a 289 t3z= s / Z_STEPS_PER_MM;
681a62d7
JM
290 gcode->stream->printf("T3-2-%d Z:%1.4f C:%d\n", i, t3z, s);
291
292 auto mm= std::minmax({t1z, t2z, t3z});
56ce2b5a 293 if((mm.second-mm.first) <= target) break; // probably as good as it gets
681a62d7
JM
294
295 // set new trim values based on min difference
296 min= mm.first;
297 trimx += (min-t1z)*trimscale;
298 trimy += (min-t2z)*trimscale;
299 trimz += (min-t3z)*trimscale;
300
301 // set trim
4553f13b 302 gcode->stream->printf("Set Trim: ");
681a62d7
JM
303 set_trim(trimx, trimy, trimz, gcode->stream);
304
305 // flush the output
306 THEKERNEL->call_event(ON_IDLE);
307 }
308
309 // move probe to start position just above the bed
310 home();
4553f13b 311 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // do a relative move from home to the point above the bed
681a62d7
JM
312
313 // probe the base of the three towers again to see if we are level
314 int dx= 0, dy= 0, dz= 0;
315 if(!probe_delta_tower(dx, t1x, t1y)) return false;
56ce2b5a 316 gcode->stream->printf("T1-final Z:%1.4f C:%d\n", dx / Z_STEPS_PER_MM, dx);
681a62d7 317 if(!probe_delta_tower(dy, t2x, t2y)) return false;
56ce2b5a 318 gcode->stream->printf("T2-final Z:%1.4f C:%d\n", dy / Z_STEPS_PER_MM, dy);
681a62d7 319 if(!probe_delta_tower(dz, t3x, t3y)) return false;
56ce2b5a 320 gcode->stream->printf("T3-final Z:%1.4f C:%d\n", dz / Z_STEPS_PER_MM, dz);
681a62d7
JM
321
322 // compare the three and report
323 auto mm= std::minmax({dx, dy, dz});
56ce2b5a 324 gcode->stream->printf("max endstop delta= %1.4f\n", (mm.second-mm.first)/Z_STEPS_PER_MM);
681a62d7 325
037c350d
JM
326 return true;
327}
328
329bool ZProbe::calibrate_delta_radius(Gcode *gcode)
330{
331 gcode->stream->printf("Calibrating delta radius\n");
332
333 // get probe points
334 float t1x, t1y, t2x, t2y, t3x, t3y;
335 std::tie(t1x, t1y, t2x, t2y, t3x, t3y) = getCoordinates(this->probe_radius);
336
337 home();
338 // find bed, then move to a point 5mm above it
339 int s;
340 if(!run_probe(s, true)) return false;
56ce2b5a 341 float bedht= s/Z_STEPS_PER_MM - this->probe_height; // distance to move from home to 5mm above bed
037c350d
JM
342 gcode->stream->printf("Bed ht is %f mm\n", bedht);
343
344 home();
345 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // do a relative move from home to the point above the bed
346
347 // probe the base of the three towers to get reference point at this Z height
348 int dx= 0, dy= 0, dz= 0, dc= 0;
349 if(!probe_delta_tower(dx, t1x, t1y)) return false;
56ce2b5a 350 gcode->stream->printf("T1 Z:%1.3f C:%d\n", dx / Z_STEPS_PER_MM, dx);
037c350d 351 if(!probe_delta_tower(dy, t2x, t2y)) return false;
56ce2b5a 352 gcode->stream->printf("T2 Z:%1.3f C:%d\n", dy / Z_STEPS_PER_MM, dy);
037c350d 353 if(!probe_delta_tower(dz, t3x, t3y)) return false;
56ce2b5a 354 gcode->stream->printf("T3 Z:%1.3f C:%d\n", dz / Z_STEPS_PER_MM, dz);
681a62d7 355 if(!probe_delta_tower(dc, 0, 0)) return false;
56ce2b5a
JM
356 gcode->stream->printf("CT Z:%1.3f C:%d\n", dc / Z_STEPS_PER_MM, dc);
357
358 float target= 0.03F;
359 if(gcode->has_letter('I')) target= gcode->get_value('I'); // override default target
360
361 // See if we are already in range and skip calibration if not needed
362 float cmm= dc / Z_STEPS_PER_MM;
363 float m= dx / Z_STEPS_PER_MM;
364 float d= cmm-m;
365 if(abs(d) <= target) {
366 gcode->stream->printf("Delta Radius already in range: %1.3f\n", d);
367 return true;
368 }
681a62d7 369
037c350d
JM
370 // get current delta radius
371 float delta_radius= 0.0F;
372 BaseSolution::arm_options_t options;
373 if(THEKERNEL->robot->arm_solution->get_optional(options)) {
374 delta_radius= options['R'];
375 }
376 if(delta_radius == 0.0F) {
377 gcode->stream->printf("This appears to not be a delta arm solution\n");
378 return false;
379 }
380 options.clear();
fc7b9a7b 381
037c350d
JM
382 // probe t1, but use coordinated moves, probing center won't change
383 float drinc= 2.5F; // approx
384 for (int i = 1; i <= 10; ++i) {
385 // set the new delta radius
386 options['R']= delta_radius;
387 THEKERNEL->robot->arm_solution->set_optional(options);
388 gcode->stream->printf("Setting delta radius to: %1.4f\n", delta_radius);
389
390 home();
391 coordinated_move(NAN, NAN, -bedht, this->fast_feedrate, true); // needs to be a relative coordinated move
392 if(!probe_delta_tower(dx, t1x, t1y)) return false;
393
394 // now look at the difference and reduce it by adjusting delta radius
56ce2b5a
JM
395 m= dx / Z_STEPS_PER_MM;
396 d= cmm-m;
037c350d 397 gcode->stream->printf("T1-%d Z:%1.4f C:%d delta: %1.3f\n", i, m, dx, d);
56ce2b5a 398 if(abs(d) <= target) break; // resolution of success
037c350d
JM
399 // increase delta radius to adjust for low center
400 // decrease delta radius to adjust for high center
401 delta_radius += (d*drinc);
402 }
fc7b9a7b
JM
403 return true;
404}
405
88443c6b
JM
406void ZProbe::on_gcode_received(void *argument)
407{
408 Gcode *gcode = static_cast<Gcode *>(argument);
88443c6b
JM
409
410 if( gcode->has_g) {
411 // G code processing
681a62d7 412 if( gcode->g == 30 ) { // simple Z probe
bd96f4d7 413 gcode->mark_as_taken();
88443c6b
JM
414 // first wait for an empty queue i.e. no moves left
415 THEKERNEL->conveyor->wait_for_empty_queue();
416
681a62d7
JM
417 int steps;
418 if(run_probe(steps)) {
56ce2b5a 419 gcode->stream->printf("Z:%1.4f C:%d\n", steps / Z_STEPS_PER_MM, steps);
bd96f4d7
JM
420 // move back to where it started, unless a Z is specified
421 if(gcode->has_letter('Z')) {
422 // set Z to the specified value, and leave probe where it is
423 THEKERNEL->robot->reset_axis_position(gcode->get_value('Z'), Z_AXIS);
681a62d7
JM
424 } else {
425 return_probe(steps);
bd96f4d7 426 }
681a62d7 427 } else {
bd96f4d7 428 gcode->stream->printf("ZProbe not triggered\n");
88443c6b 429 }
fc7b9a7b 430
681a62d7
JM
431 } else if( gcode->g == 32 ) { // auto calibration for delta, Z bed mapping for cartesian
432 // first wait for an empty queue i.e. no moves left
433 THEKERNEL->conveyor->wait_for_empty_queue();
fc7b9a7b
JM
434 gcode->mark_as_taken();
435 if(is_delta) {
037c350d
JM
436 if(!gcode->has_letter('R')){
437 if(!calibrate_delta_endstops(gcode)) {
438 gcode->stream->printf("Calibration failed to complete, probe not triggered\n");
439 return;
440 }
681a62d7 441 }
037c350d
JM
442 if(!gcode->has_letter('E')){
443 if(!calibrate_delta_radius(gcode)) {
444 gcode->stream->printf("Calibration failed to complete, probe not triggered\n");
445 return;
446 }
447 }
448 gcode->stream->printf("Calibration complete, save settings with M500\n");
449
681a62d7
JM
450 } else {
451 // TODO create Z height map for bed
452 gcode->stream->printf("Not supported yet\n");
fc7b9a7b 453 }
88443c6b
JM
454 }
455
456 } else if(gcode->has_m) {
457 // M code processing here
bd96f4d7 458 if(gcode->m == 119) {
681a62d7 459 int c = this->pin.get();
bd96f4d7
JM
460 gcode->stream->printf(" Probe: %d", c);
461 gcode->add_nl = true;
462 gcode->mark_as_taken();
681a62d7 463
bd96f4d7 464 }
88443c6b
JM
465 }
466}
467
468#define max(a,b) (((a) > (b)) ? (a) : (b))
469// Called periodically to change the speed to match acceleration
470uint32_t ZProbe::acceleration_tick(uint32_t dummy)
471{
472 if(!this->running) return(0); // nothing to do
473
474 // foreach stepper that is moving
b7cd847e 475 for ( int c = X_AXIS; c <= Z_AXIS; c++ ) {
88443c6b
JM
476 if( !this->steppers[c]->moving ) continue;
477
478 uint32_t current_rate = this->steppers[c]->steps_per_second;
681a62d7 479 uint32_t target_rate = int(floor(this->current_feedrate));
88443c6b 480
681a62d7 481 if( current_rate < target_rate ) {
56ce2b5a 482 uint32_t rate_increase = int(floor((THEKERNEL->planner->acceleration / THEKERNEL->stepper->acceleration_ticks_per_second) * STEPS_PER_MM(c)));
88443c6b
JM
483 current_rate = min( target_rate, current_rate + rate_increase );
484 }
681a62d7
JM
485 if( current_rate > target_rate ) {
486 current_rate = target_rate;
487 }
88443c6b
JM
488
489 // steps per second
490 this->steppers[c]->set_speed(max(current_rate, THEKERNEL->stepper->minimum_steps_per_second));
491 }
492
493 return 0;
494}
681a62d7
JM
495
496// issue a coordinated move directly to robot, and return when done
497// Only move the coordinates that are passed in as not nan
037c350d 498void ZProbe::coordinated_move(float x, float y, float z, float feedrate, bool relative)
681a62d7
JM
499{
500 char buf[32];
037c350d
JM
501 char cmd[64];
502
503 if(relative) strcpy(cmd, "G91 G0 ");
504 else strcpy(cmd, "G0 ");
505
681a62d7 506 if(!isnan(x)) {
037c350d 507 int n = snprintf(buf, sizeof(buf), " X%1.3f", x);
681a62d7
JM
508 strncat(cmd, buf, n);
509 }
510 if(!isnan(y)) {
037c350d 511 int n = snprintf(buf, sizeof(buf), " Y%1.3f", y);
681a62d7
JM
512 strncat(cmd, buf, n);
513 }
514 if(!isnan(z)) {
037c350d 515 int n = snprintf(buf, sizeof(buf), " Z%1.3f", z);
681a62d7
JM
516 strncat(cmd, buf, n);
517 }
518
519 // use specified feedrate (mm/sec)
037c350d 520 int n = snprintf(buf, sizeof(buf), " F%1.1f", feedrate * 60); // feed rate is converted to mm/min
681a62d7 521 strncat(cmd, buf, n);
037c350d
JM
522 if(relative) strcat(cmd, " G90");
523
524 //THEKERNEL->streams->printf("DEBUG: move: %s\n", cmd);
681a62d7 525
037c350d
JM
526 // send as a command line as may have multiple G codes in it
527 struct SerialMessage message;
528 message.message = cmd;
529 message.stream = &(StreamOutput::NullStream);
530 THEKERNEL->call_event(ON_CONSOLE_LINE_RECEIVED, &message );
681a62d7
JM
531 THEKERNEL->conveyor->wait_for_empty_queue();
532}
533
534// issue home command
535void ZProbe::home()
536{
537 Gcode gc("G28", &(StreamOutput::NullStream));
538 THEKERNEL->call_event(ON_GCODE_RECEIVED, &gc);
539}
540
541void ZProbe::set_trim(float x, float y, float z, StreamOutput *stream)
542{
543 char buf[40];
544 int n = snprintf(buf, sizeof(buf), "M666 X%1.8f Y%1.8f Z%1.8f", x, y, z);
545 Gcode gc(string(buf, n), stream);
546 THEKERNEL->call_event(ON_GCODE_RECEIVED, &gc);
547}