mirror of https://github.com/vitalif/GPX
5560 lines
196 KiB
C
5560 lines
196 KiB
C
//
|
|
// gpx.c
|
|
//
|
|
// Created by WHPThomas <me(at)henri(dot)net> on 1/04/13.
|
|
//
|
|
// Copyright (c) 2013 WHPThomas, All rights reserved.
|
|
//
|
|
// gpx references ReplicatorG sources from /src/replicatorg/drivers
|
|
// which are part of the ReplicatorG project - http://www.replicat.org
|
|
// Copyright (c) 2008 Zach Smith
|
|
// and Makerbot4GSailfish.java Copyright (C) 2012 Jetty / Dan Newman
|
|
//
|
|
// 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 2 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, write to the Free Software Foundation,
|
|
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <float.h>
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <strings.h>
|
|
#include <sys/types.h>
|
|
#include <sys/uio.h>
|
|
#include <unistd.h>
|
|
|
|
#include "gpx.h"
|
|
|
|
#define A 0
|
|
#define B 1
|
|
|
|
#define SHOW(FN) if(gpx->flag.logMessages) {FN;}
|
|
#define VERBOSE(FN) if(gpx->flag.verboseMode && gpx->flag.logMessages) {FN;}
|
|
#define CALL(FN) if((rval = FN) != SUCCESS) return rval
|
|
|
|
// Machine definitions
|
|
|
|
// Axis - max_feedrate, home_feedrate, steps_per_mm, endstop;
|
|
// Extruder - max_feedrate, steps_per_mm, motor_steps, has_heated_build_platform;
|
|
|
|
static Machine cupcake_G3 = {
|
|
{9600, 500, 11.767463, ENDSTOP_IS_MIN}, // x axis
|
|
{9600, 500, 11.767463, ENDSTOP_IS_MIN}, // y axis
|
|
{450, 450, 320, ENDSTOP_IS_MIN}, // z axis
|
|
{7200, 50.235478806907409, 400, 1}, // a extruder
|
|
{7200, 50.235478806907409, 400, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
1, // extruder count
|
|
20, // timeout
|
|
1,
|
|
};
|
|
|
|
static Machine cupcake_G4 = {
|
|
{9600, 500, 47.069852, ENDSTOP_IS_MIN}, // x axis
|
|
{9600, 500, 47.069852, ENDSTOP_IS_MIN}, // y axis
|
|
{450, 450, 1280, ENDSTOP_IS_MIN}, // z axis
|
|
{7200, 50.235478806907409, 400, 1}, // a extruder
|
|
{7200, 50.235478806907409, 400, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
1, // extruder count
|
|
20, // timeout
|
|
2,
|
|
};
|
|
|
|
static Machine cupcake_P4 = {
|
|
{9600, 500, 94.13970462, ENDSTOP_IS_MIN}, // x axis
|
|
{9600, 500, 94.13970462, ENDSTOP_IS_MIN}, // y axis
|
|
{450, 450, 2560, ENDSTOP_IS_MIN}, // z axis
|
|
{7200, 50.235478806907409, 400, 1}, // a extruder
|
|
{7200, 50.235478806907409, 400, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
1, // extruder count
|
|
20, // timeout
|
|
3,
|
|
};
|
|
|
|
static Machine cupcake_PP = {
|
|
{9600, 500, 47.069852, ENDSTOP_IS_MIN}, // x axis
|
|
{9600, 500, 47.069852, ENDSTOP_IS_MIN}, // y axis
|
|
{450, 450, 1280, ENDSTOP_IS_MIN}, // z axis
|
|
{7200, 100.470957613814818, 400, 1}, // a extruder
|
|
{7200, 100.470957613814818, 400, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
1, // extruder count
|
|
20, // timeout
|
|
4,
|
|
};
|
|
|
|
// Axis - max_feedrate, home_feedrate, steps_per_mm, endstop;
|
|
// Extruder - max_feedrate, steps_per_mm, motor_steps, has_heated_build_platform;
|
|
|
|
static Machine thing_o_matic_7 = {
|
|
{9600, 500, 47.069852, ENDSTOP_IS_MIN}, // x axis
|
|
{9600, 500, 47.069852, ENDSTOP_IS_MIN}, // y axis
|
|
{1000, 500, 200, ENDSTOP_IS_MAX}, // z axis
|
|
{1600, 50.235478806907409, 1600, 1}, // a extruder
|
|
{1600, 50.235478806907409, 1600, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
1, // extruder count
|
|
20, // timeout
|
|
5,
|
|
};
|
|
|
|
static Machine thing_o_matic_7D = {
|
|
{9600, 500, 47.069852, ENDSTOP_IS_MIN}, // x axis
|
|
{9600, 500, 47.069852, ENDSTOP_IS_MIN}, // y axis
|
|
{1000, 500, 200, ENDSTOP_IS_MAX}, // z axis
|
|
{1600, 50.235478806907409, 1600, 0}, // a extruder
|
|
{1600, 50.235478806907409, 1600, 1}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
2, // extruder count
|
|
20, // timeout
|
|
6,
|
|
};
|
|
|
|
// Axis - max_feedrate, home_feedrate, steps_per_mm, endstop;
|
|
// Extruder - max_feedrate, steps_per_mm, motor_steps, has_heated_build_platform;
|
|
|
|
static Machine replicator_1 = {
|
|
{18000, 2500, 94.139704, ENDSTOP_IS_MAX}, // x axis
|
|
{18000, 2500, 94.139704, ENDSTOP_IS_MAX}, // y axis
|
|
{1170, 1100, 400, ENDSTOP_IS_MIN}, // z axis
|
|
{1600, 96.275201870333662468889989185642, 3200, 1}, // a extruder
|
|
{1600, 96.275201870333662468889989185642, 3200, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
1, // extruder count
|
|
20, // timeout
|
|
7,
|
|
};
|
|
|
|
static Machine replicator_1D = {
|
|
{18000, 2500, 94.139704, ENDSTOP_IS_MAX}, // x axis
|
|
{18000, 2500, 94.139704, ENDSTOP_IS_MAX}, // y axis
|
|
{1170, 1100, 400, ENDSTOP_IS_MIN}, // z axis
|
|
{1600, 96.275201870333662468889989185642, 3200, 1}, // a extruder
|
|
{1600, 96.275201870333662468889989185642, 3200, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
2, // extruder count
|
|
20, // timeout
|
|
8,
|
|
};
|
|
|
|
// Axis - max_feedrate, home_feedrate, steps_per_mm, endstop;
|
|
// Extruder - max_feedrate, steps_per_mm, motor_steps, has_heated_build_platform;
|
|
|
|
static Machine replicator_2 = {
|
|
{18000, 2500, 88.573186, ENDSTOP_IS_MAX}, // x axis
|
|
{18000, 2500, 88.573186, ENDSTOP_IS_MAX}, // y axis
|
|
{1170, 1100, 400, ENDSTOP_IS_MIN}, // z axis
|
|
{1600, 96.275201870333662468889989185642, 3200, 0}, // a extruder
|
|
{1600, 96.275201870333662468889989185642, 3200, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.97, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
1, // extruder count
|
|
20, // timeout
|
|
9,
|
|
};
|
|
|
|
static Machine replicator_2H = {
|
|
{18000, 2500, 88.573186, ENDSTOP_IS_MAX}, // x axis
|
|
{18000, 2500, 88.573186, ENDSTOP_IS_MAX}, // y axis
|
|
{1170, 1100, 400, ENDSTOP_IS_MIN}, // z axis
|
|
{1600, 96.275201870333662468889989185642, 3200, 1}, // a extruder
|
|
{1600, 96.275201870333662468889989185642, 3200, 0}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.97, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
1, // extruder count
|
|
20, // timeout
|
|
10,
|
|
};
|
|
|
|
static Machine replicator_2X = {
|
|
{18000, 2500, 88.573186, ENDSTOP_IS_MAX}, // x axis
|
|
{18000, 2500, 88.573186, ENDSTOP_IS_MAX}, // y axis
|
|
{1170, 1100, 400, ENDSTOP_IS_MIN}, // z axis
|
|
{1600, 96.275201870333662468889989185642, 3200, 1}, // a extruder
|
|
{1600, 96.275201870333662468889989185642, 3200, 1}, // b extruder
|
|
1.75, // nominal filament diameter
|
|
0.85, // nominal packing density
|
|
0.4, // nozzle diameter
|
|
2, // extruder count
|
|
20, // timeout
|
|
11,
|
|
};
|
|
|
|
#define MACHINE_IS(m) strcasecmp(machine, m) == 0
|
|
|
|
int gpx_set_machine(Gpx *gpx, char *machine)
|
|
{
|
|
// only load/clobber the on-board machine definition if the one specified is different
|
|
if(MACHINE_IS("c3")) {
|
|
if(gpx->machine.type != 1) {
|
|
gpx->machine = cupcake_G3;
|
|
VERBOSE( fputs("Loading machine definition: Cupcake Gen3 XYZ, Mk5/6 + Gen4 Extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m c3" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("c4")) {
|
|
if(gpx->machine.type != 2) {
|
|
gpx->machine = cupcake_G4;
|
|
VERBOSE( fputs("Loading machine definition: Cupcake Gen4 XYZ, Mk5/6 + Gen4 Extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m c4" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("cp4")) {
|
|
if(gpx->machine.type != 3) {
|
|
gpx->machine = cupcake_P4;
|
|
VERBOSE( fputs("Loading machine definition: Cupcake Pololu XYZ, Mk5/6 + Gen4 Extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m cp4" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("cpp")) {
|
|
if(gpx->machine.type != 4) {
|
|
gpx->machine = cupcake_PP;
|
|
VERBOSE( fputs("Loading machine definition: Cupcake Pololu XYZ, Mk5/6 + Pololu Extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m cpp" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("t6")) {
|
|
if(gpx->machine.type != 5) {
|
|
gpx->machine = thing_o_matic_7;
|
|
VERBOSE( fputs("Loading machine definition: TOM Mk6 - single extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m t6" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("t7")) {
|
|
if(gpx->machine.type != 5) {
|
|
gpx->machine = thing_o_matic_7;
|
|
VERBOSE( fputs("Loading machine definition: TOM Mk7 - single extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m t7" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("t7d")) {
|
|
if(gpx->machine.type != 6) {
|
|
gpx->machine = thing_o_matic_7D;
|
|
VERBOSE( fputs("Loading machine definition: TOM Mk7 - dual extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m t7d" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("r1")) {
|
|
if(gpx->machine.type != 7) {
|
|
gpx->machine = replicator_1;
|
|
VERBOSE( fputs("Loading machine definition: Replicator 1 - single extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m r1" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("r1d")) {
|
|
if(gpx->machine.type != 8) {
|
|
gpx->machine = replicator_1D;
|
|
VERBOSE( fputs("Loading machine definition: Replicator 1 - dual extruder" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m r1d" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("r2")) {
|
|
if(gpx->machine.type != 9) {
|
|
gpx->machine = replicator_2;
|
|
VERBOSE( fputs("Loading machine definition: Replicator 2" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m r2" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("r2h")) {
|
|
if(gpx->machine.type != 10) {
|
|
gpx->machine = replicator_2H;
|
|
VERBOSE( fputs("Loading machine definition: Replicator 2 with HBP" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m r2h" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else if(MACHINE_IS("r2x")) {
|
|
if(gpx->machine.type != 11) {
|
|
gpx->machine = replicator_2X;
|
|
VERBOSE( fputs("Loading machine definition: Replicator 2X" EOL, gpx->log) );
|
|
}
|
|
else {
|
|
VERBOSE( fputs("Ignoring duplicate machine definition: -m r2x" EOL, gpx->log) );
|
|
}
|
|
}
|
|
else {
|
|
return ERROR;
|
|
}
|
|
// update known position mask
|
|
gpx->axis.mask = gpx->machine.extruder_count == 1 ? (XYZ_BIT_MASK | A_IS_SET) : AXES_BIT_MASK;;
|
|
return SUCCESS;
|
|
}
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES
|
|
|
|
static double get_home_feedrate(Gpx *gpx, int flag);
|
|
static int pause_at_zpos(Gpx *gpx, float z_positon);
|
|
|
|
// initialization of global variables
|
|
|
|
void gpx_initialize(Gpx *gpx, int firstTime)
|
|
{
|
|
int i;
|
|
gpx->buffer.ptr = gpx->buffer.out;
|
|
// we default to using pipes
|
|
|
|
// initialise machine
|
|
if(firstTime) gpx->machine = replicator_2;
|
|
|
|
// initialise command
|
|
gpx->command.x = 0.0;
|
|
gpx->command.y = 0.0;
|
|
gpx->command.z = 0.0;
|
|
gpx->command.a = 0.0;
|
|
gpx->command.b = 0.0;
|
|
|
|
gpx->command.e = 0.0;
|
|
gpx->command.f = 0.0;
|
|
|
|
gpx->command.p = 0.0;
|
|
gpx->command.r = 0.0;
|
|
gpx->command.s = 0.0;
|
|
|
|
|
|
gpx->command.g = 0.0;
|
|
gpx->command.m = 0.0;
|
|
gpx->command.t = 0.0;
|
|
|
|
gpx->command.comment = "";
|
|
|
|
gpx->command.flag = 0;
|
|
|
|
// initialize target position
|
|
gpx->target.position.x = 0.0;
|
|
gpx->target.position.y = 0.0;
|
|
gpx->target.position.z = 0.0;
|
|
|
|
gpx->target.position.a = 0.0;
|
|
gpx->target.position.b = 0.0;
|
|
|
|
gpx->target.extruder = 0;
|
|
|
|
// initialize current position
|
|
gpx->current.position.x = 0.0;
|
|
gpx->current.position.y = 0.0;
|
|
gpx->current.position.z = 0.0;
|
|
|
|
gpx->current.position.a = 0.0;
|
|
gpx->current.position.b = 0.0;
|
|
|
|
gpx->current.feedrate = get_home_feedrate(gpx, XYZ_BIT_MASK);
|
|
gpx->current.extruder = 0;
|
|
gpx->current.offset = 0;
|
|
gpx->current.percent = 0;
|
|
|
|
gpx->axis.positionKnown = 0;
|
|
gpx->axis.mask = gpx->machine.extruder_count == 1 ? (XYZ_BIT_MASK | A_IS_SET) : AXES_BIT_MASK;;
|
|
|
|
// initialize the accumulated rounding error
|
|
gpx->excess.a = 0.0;
|
|
gpx->excess.b = 0.0;
|
|
|
|
// initialize the G10 offsets
|
|
for(i = 0; i < 7; i++) {
|
|
gpx->offset[i].x = 0.0;
|
|
gpx->offset[i].y = 0.0;
|
|
gpx->offset[i].z = 0.0;
|
|
}
|
|
|
|
// initialize the command line offset
|
|
if(firstTime) {
|
|
gpx->user.offset.x = 0.0;
|
|
gpx->user.offset.y = 0.0;
|
|
gpx->user.offset.z = 0.0;
|
|
gpx->user.scale = 1.0;
|
|
}
|
|
|
|
for(i = 0; i < 2; i++) {
|
|
gpx->tool[i].motor_enabled = 0;
|
|
#if ENABLE_SIMULATED_RPM
|
|
gpx->tool[i].rpm = 0;
|
|
#endif
|
|
gpx->tool[i].nozzle_temperature = 0;
|
|
gpx->tool[i].build_platform_temperature = 0;
|
|
|
|
gpx->override[i].actual_filament_diameter = 0;
|
|
gpx->override[i].filament_scale = 1.0;
|
|
gpx->override[i].packing_density = 1.0;
|
|
gpx->override[i].standby_temperature = 0;
|
|
gpx->override[i].active_temperature = 0;
|
|
gpx->override[i].build_platform_temperature = 0;
|
|
}
|
|
|
|
if(firstTime) {
|
|
gpx->filament[0].colour = "_null_";
|
|
gpx->filament[0].diameter = 0.0;
|
|
gpx->filament[0].temperature = 0;
|
|
gpx->filament[0].LED = 0;
|
|
gpx->filamentLength = 1;
|
|
}
|
|
|
|
if(firstTime) {
|
|
gpx->commandAtIndex = 0;
|
|
gpx->commandAtLength = 0;
|
|
}
|
|
gpx->commandAtZ = 0.0;
|
|
|
|
// SETTINGS
|
|
|
|
if(firstTime) {
|
|
gpx->sdCardPath = NULL;
|
|
gpx->buildName = "GPX " GPX_VERSION;
|
|
}
|
|
|
|
gpx->flag.relativeCoordinates = 0;
|
|
gpx->flag.extruderIsRelative = 0;
|
|
|
|
if(firstTime) {
|
|
gpx->flag.reprapFlavor = 1; // reprap flavor is enabled by default
|
|
gpx->flag.dittoPrinting = 0;
|
|
gpx->flag.buildProgress = 0;
|
|
gpx->flag.verboseMode = 0;
|
|
gpx->flag.logMessages = 1; // logging is enabled by default
|
|
gpx->flag.rewrite5D = 0;
|
|
}
|
|
|
|
// STATE
|
|
|
|
gpx->flag.programState = 0;
|
|
gpx->flag.doPauseAtZPos = 0;
|
|
gpx->flag.pausePending = 0;
|
|
gpx->flag.macrosEnabled = 0;
|
|
if(firstTime) {
|
|
gpx->flag.loadMacros = 1;
|
|
gpx->flag.runMacros = 1;
|
|
}
|
|
gpx->flag.framingEnabled = 0;
|
|
|
|
gpx->longestDDA = 0;
|
|
gpx->layerHeight = 0.34;
|
|
gpx->lineNumber = 1;
|
|
|
|
|
|
// STATISTICS
|
|
|
|
gpx->accumulated.a = 0.0;
|
|
gpx->accumulated.b = 0.0;
|
|
gpx->accumulated.time = 0.0;
|
|
gpx->accumulated.bytes = 0;
|
|
|
|
if(firstTime) {
|
|
gpx->total.length = 0.0;
|
|
gpx->total.time = 0.0;
|
|
gpx->total.bytes = 0;
|
|
}
|
|
|
|
// CALLBACK
|
|
|
|
gpx->callbackHandler = NULL;
|
|
gpx->callbackData = NULL;
|
|
|
|
// LOGGING
|
|
|
|
if(firstTime) gpx->log = stderr;
|
|
}
|
|
|
|
// PRINT STATE
|
|
|
|
#define start_program() gpx->flag.programState = RUNNING_STATE
|
|
#define end_program() gpx->flag.programState = ENDED_STATE
|
|
|
|
#define program_is_ready() gpx->flag.programState < RUNNING_STATE
|
|
#define program_is_running() gpx->flag.programState < ENDED_STATE
|
|
|
|
// IO FUNCTIONS
|
|
|
|
static void write_8(Gpx *gpx, unsigned char value)
|
|
{
|
|
*gpx->buffer.ptr++ = value;
|
|
}
|
|
|
|
static unsigned char read_8(Gpx *gpx)
|
|
{
|
|
return *gpx->buffer.ptr++;
|
|
}
|
|
|
|
static void write_16(Gpx *gpx, unsigned short value)
|
|
{
|
|
union {
|
|
unsigned short s;
|
|
unsigned char b[2];
|
|
} u;
|
|
u.s = value;
|
|
*gpx->buffer.ptr++ = u.b[0];
|
|
*gpx->buffer.ptr++ = u.b[1];
|
|
}
|
|
|
|
static unsigned short read_16(Gpx *gpx)
|
|
{
|
|
union {
|
|
unsigned short s;
|
|
unsigned char b[2];
|
|
} u;
|
|
u.b[0] = *gpx->buffer.ptr++;
|
|
u.b[1] = *gpx->buffer.ptr++;
|
|
return u.s;
|
|
}
|
|
|
|
static void write_32(Gpx *gpx, unsigned int value)
|
|
{
|
|
union {
|
|
unsigned int i;
|
|
unsigned char b[4];
|
|
} u;
|
|
u.i = value;
|
|
*gpx->buffer.ptr++ = u.b[0];
|
|
*gpx->buffer.ptr++ = u.b[1];
|
|
*gpx->buffer.ptr++ = u.b[2];
|
|
*gpx->buffer.ptr++ = u.b[3];
|
|
}
|
|
|
|
static unsigned int read_32(Gpx *gpx)
|
|
{
|
|
union {
|
|
unsigned int i;
|
|
unsigned char b[4];
|
|
} u;
|
|
u.b[0] = *gpx->buffer.ptr++;
|
|
u.b[1] = *gpx->buffer.ptr++;
|
|
u.b[2] = *gpx->buffer.ptr++;
|
|
u.b[3] = *gpx->buffer.ptr++;
|
|
return u.i;
|
|
}
|
|
|
|
static void write_float(Gpx *gpx, float value)
|
|
{
|
|
union {
|
|
float f;
|
|
unsigned char b[4];
|
|
} u;
|
|
u.f = value;
|
|
*gpx->buffer.ptr++ = u.b[0];
|
|
*gpx->buffer.ptr++ = u.b[1];
|
|
*gpx->buffer.ptr++ = u.b[2];
|
|
*gpx->buffer.ptr++ = u.b[3];
|
|
}
|
|
|
|
static float read_float(Gpx *gpx)
|
|
{
|
|
union {
|
|
float f;
|
|
unsigned char b[4];
|
|
} u;
|
|
u.b[0] = *gpx->buffer.ptr++;
|
|
u.b[1] = *gpx->buffer.ptr++;
|
|
u.b[2] = *gpx->buffer.ptr++;
|
|
u.b[3] = *gpx->buffer.ptr++;
|
|
return u.f;
|
|
}
|
|
|
|
static long write_bytes(Gpx *gpx, char *data, long length)
|
|
{
|
|
long l = length;
|
|
while(l--) {
|
|
*gpx->buffer.ptr++ = *data++;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
static long read_bytes(Gpx *gpx, char *data, long length)
|
|
{
|
|
long l = length;
|
|
while(l--) {
|
|
*data++ = *gpx->buffer.ptr++;
|
|
}
|
|
return length;
|
|
}
|
|
|
|
static long write_string(Gpx *gpx, char *string, long length)
|
|
{
|
|
long l = length;
|
|
while(l--) {
|
|
*gpx->buffer.ptr++ = *string++;
|
|
}
|
|
*gpx->buffer.ptr++ = '\0';
|
|
return length;
|
|
}
|
|
|
|
// FRAMING
|
|
|
|
static unsigned char calculate_crc(unsigned char *addr, long len)
|
|
{
|
|
unsigned char data, crc = 0;
|
|
while(len--) {
|
|
data = *addr++;
|
|
// 8-bit iButton/Maxim/Dallas CRC loop unrolled
|
|
crc = crc ^ data;
|
|
// 1
|
|
if (crc & 0x01) crc = (crc >> 1) ^ 0x8C;
|
|
else crc >>= 1;
|
|
|
|
// 2
|
|
if (crc & 0x01) crc = (crc >> 1) ^ 0x8C;
|
|
else crc >>= 1;
|
|
|
|
// 3
|
|
if (crc & 0x01) crc = (crc >> 1) ^ 0x8C;
|
|
else crc >>= 1;
|
|
|
|
// 4
|
|
if (crc & 0x01) crc = (crc >> 1) ^ 0x8C;
|
|
else crc >>= 1;
|
|
|
|
// 5
|
|
if (crc & 0x01) crc = (crc >> 1) ^ 0x8C;
|
|
else crc >>= 1;
|
|
|
|
// 6
|
|
if (crc & 0x01) crc = (crc >> 1) ^ 0x8C;
|
|
else crc >>= 1;
|
|
|
|
// 7
|
|
if (crc & 0x01) crc = (crc >> 1) ^ 0x8C;
|
|
else crc >>= 1;
|
|
|
|
// 8
|
|
if (crc & 0x01) crc = (crc >> 1) ^ 0x8C;
|
|
else crc >>= 1;
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
static void begin_frame(Gpx *gpx)
|
|
{
|
|
gpx->buffer.ptr = gpx->buffer.out;
|
|
if(gpx->flag.framingEnabled) {
|
|
gpx->buffer.out[0] = 0xD5; // synchronization byte
|
|
gpx->buffer.ptr += 2;
|
|
}
|
|
}
|
|
|
|
static int end_frame(Gpx *gpx)
|
|
{
|
|
if(gpx->flag.framingEnabled) {
|
|
unsigned char *start = (unsigned char *)gpx->buffer.out + 2;
|
|
unsigned char *end = (unsigned char *)gpx->buffer.ptr;
|
|
size_t payload_length = end - start;
|
|
gpx->buffer.out[1] = (unsigned char)payload_length;
|
|
*gpx->buffer.ptr++ = calculate_crc(start, payload_length);
|
|
}
|
|
size_t length = gpx->buffer.ptr - gpx->buffer.out;
|
|
gpx->accumulated.bytes += length;
|
|
if(gpx->callbackHandler) return gpx->callbackHandler(gpx, gpx->callbackData, gpx->buffer.out, length);
|
|
return SUCCESS;
|
|
}
|
|
|
|
// 5D VECTOR FUNCTIONS
|
|
|
|
// compute the filament scaling factor
|
|
|
|
static void set_filament_scale(Gpx *gpx, unsigned extruder_id, double filament_diameter)
|
|
{
|
|
double actual_radius = filament_diameter / 2;
|
|
double nominal_radius = gpx->machine.nominal_filament_diameter / 2;
|
|
gpx->override[extruder_id].filament_scale = (nominal_radius * nominal_radius) / (actual_radius * actual_radius);
|
|
}
|
|
|
|
// return the magnitude (length) of the 5D vector
|
|
|
|
static double magnitude(int flag, Ptr5d vector)
|
|
{
|
|
double acc = 0.0;
|
|
if(flag & X_IS_SET) {
|
|
acc = vector->x * vector->x;
|
|
}
|
|
if(flag & Y_IS_SET) {
|
|
acc += vector->y * vector->y;
|
|
}
|
|
if(flag & Z_IS_SET) {
|
|
acc += vector->z * vector->z;
|
|
}
|
|
if(flag & A_IS_SET) {
|
|
acc += vector->a * vector->a;
|
|
}
|
|
if(flag & B_IS_SET) {
|
|
acc += vector->b * vector->b;
|
|
}
|
|
return sqrt(acc);
|
|
}
|
|
|
|
// return the largest axis in the vector
|
|
|
|
static double largest_axis(int flag, Ptr5d vector)
|
|
{
|
|
double length, result = 0.0;
|
|
if(flag & X_IS_SET) {
|
|
result = fabs(vector->x);
|
|
}
|
|
if(flag & Y_IS_SET) {
|
|
length = fabs(vector->y);
|
|
if(result < length) result = length;
|
|
}
|
|
if(flag & Z_IS_SET) {
|
|
length = fabs(vector->z);
|
|
if(result < length) result = length;
|
|
}
|
|
if(flag & A_IS_SET) {
|
|
length = fabs(vector->a);
|
|
if(result < length) result = length;
|
|
}
|
|
if(flag & B_IS_SET) {
|
|
length = fabs(vector->b);
|
|
if(result < length) result = length;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// calculate the dda for the longest axis for the current machine definition
|
|
|
|
static int get_longest_dda(Gpx *gpx)
|
|
{
|
|
// calculate once
|
|
int longestDDA = gpx->longestDDA;
|
|
if(longestDDA == 0) {
|
|
longestDDA = (int)(60 * 1000000.0 / (gpx->machine.x.max_feedrate * gpx->machine.x.steps_per_mm));
|
|
|
|
int axisDDA = (int)(60 * 1000000.0 / (gpx->machine.y.max_feedrate * gpx->machine.y.steps_per_mm));
|
|
if(longestDDA < axisDDA) longestDDA = axisDDA;
|
|
|
|
axisDDA = (int)(60 * 1000000.0 / (gpx->machine.z.max_feedrate * gpx->machine.z.steps_per_mm));
|
|
if(longestDDA < axisDDA) longestDDA = axisDDA;
|
|
gpx->longestDDA = longestDDA;
|
|
}
|
|
return longestDDA;
|
|
}
|
|
|
|
// return the maximum home feedrate
|
|
|
|
static double get_home_feedrate(Gpx *gpx, int flag) {
|
|
double feedrate = 0.0;
|
|
if(flag & X_IS_SET) {
|
|
feedrate = gpx->machine.x.home_feedrate;
|
|
}
|
|
if(flag & Y_IS_SET && feedrate < gpx->machine.y.home_feedrate) {
|
|
feedrate = gpx->machine.y.home_feedrate;
|
|
}
|
|
if(flag & Z_IS_SET && feedrate < gpx->machine.z.home_feedrate) {
|
|
feedrate = gpx->machine.z.home_feedrate;
|
|
}
|
|
return feedrate;
|
|
}
|
|
|
|
// return the maximum safe feedrate
|
|
|
|
static double get_safe_feedrate(Gpx *gpx, int flag, Ptr5d delta) {
|
|
|
|
double feedrate = gpx->current.feedrate;
|
|
if(feedrate == 0.0) {
|
|
feedrate = gpx->machine.x.max_feedrate;
|
|
if(feedrate < gpx->machine.y.max_feedrate) {
|
|
feedrate = gpx->machine.y.max_feedrate;
|
|
}
|
|
if(feedrate < gpx->machine.z.max_feedrate) {
|
|
feedrate = gpx->machine.z.max_feedrate;
|
|
}
|
|
if(feedrate < gpx->machine.a.max_feedrate) {
|
|
feedrate = gpx->machine.a.max_feedrate;
|
|
}
|
|
if(feedrate < gpx->machine.b.max_feedrate) {
|
|
feedrate = gpx->machine.b.max_feedrate;
|
|
}
|
|
}
|
|
|
|
double distance = magnitude(flag & XYZ_BIT_MASK, delta);
|
|
if(flag & X_IS_SET && (feedrate * delta->x / distance) > gpx->machine.x.max_feedrate) {
|
|
feedrate = gpx->machine.x.max_feedrate * distance / delta->x;
|
|
}
|
|
if(flag & Y_IS_SET && (feedrate * delta->y / distance) > gpx->machine.y.max_feedrate) {
|
|
feedrate = gpx->machine.y.max_feedrate * distance / delta->y;
|
|
}
|
|
if(flag & Z_IS_SET && (feedrate * delta->z / distance) > gpx->machine.z.max_feedrate) {
|
|
feedrate = gpx->machine.z.max_feedrate * distance / delta->z;
|
|
}
|
|
|
|
if(distance == 0) {
|
|
if(flag & A_IS_SET && feedrate > gpx->machine.a.max_feedrate) {
|
|
feedrate = gpx->machine.a.max_feedrate;
|
|
}
|
|
if(flag & B_IS_SET && feedrate > gpx->machine.b.max_feedrate) {
|
|
feedrate = gpx->machine.b.max_feedrate;
|
|
}
|
|
}
|
|
else {
|
|
if(flag & A_IS_SET && (feedrate * delta->a / distance) > gpx->machine.a.max_feedrate) {
|
|
feedrate = gpx->machine.a.max_feedrate * distance / delta->a;
|
|
}
|
|
if(flag & B_IS_SET && (feedrate * delta->b / distance) > gpx->machine.b.max_feedrate) {
|
|
feedrate = gpx->machine.b.max_feedrate * distance / delta->b;
|
|
}
|
|
}
|
|
return feedrate;
|
|
}
|
|
|
|
// convert mm to steps using the current machine definition
|
|
|
|
// IMPORTANT: this command changes the global excess value which accumulates the rounding remainder
|
|
|
|
static Point5d mm_to_steps(Gpx *gpx, Ptr5d mm, Ptr2d excess)
|
|
{
|
|
double value;
|
|
Point5d result;
|
|
result.x = round(mm->x * gpx->machine.x.steps_per_mm);
|
|
result.y = round(mm->y * gpx->machine.y.steps_per_mm);
|
|
result.z = round(mm->z * gpx->machine.z.steps_per_mm);
|
|
if(excess) {
|
|
// accumulate rounding remainder
|
|
value = (mm->a * gpx->machine.a.steps_per_mm) + excess->a;
|
|
result.a = round(value);
|
|
// changes to excess
|
|
excess->a = value - result.a;
|
|
|
|
value = (mm->b * gpx->machine.b.steps_per_mm) + excess->b;
|
|
result.b = round(value);
|
|
// changes to excess
|
|
excess->b = value - result.b;
|
|
}
|
|
else {
|
|
result.a = round(mm->a * gpx->machine.a.steps_per_mm);
|
|
result.b = round(mm->b * gpx->machine.b.steps_per_mm);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static Point5d delta_mm(Gpx *gpx)
|
|
{
|
|
Point5d deltaMM;
|
|
// compute the relative distance traveled along each axis and convert to steps
|
|
if(gpx->command.flag & X_IS_SET) deltaMM.x = gpx->target.position.x - gpx->current.position.x; else deltaMM.x = 0;
|
|
if(gpx->command.flag & Y_IS_SET) deltaMM.y = gpx->target.position.y - gpx->current.position.y; else deltaMM.y = 0;
|
|
if(gpx->command.flag & Z_IS_SET) deltaMM.z = gpx->target.position.z - gpx->current.position.z; else deltaMM.z = 0;
|
|
if(gpx->command.flag & A_IS_SET) deltaMM.a = gpx->target.position.a - gpx->current.position.a; else deltaMM.a = 0;
|
|
if(gpx->command.flag & B_IS_SET) deltaMM.b = gpx->target.position.b - gpx->current.position.b; else deltaMM.b = 0;
|
|
return deltaMM;
|
|
}
|
|
|
|
static Point5d delta_steps(Gpx *gpx,Point5d deltaMM)
|
|
{
|
|
Point5d deltaSteps;
|
|
// compute the relative distance traveled along each axis and convert to steps
|
|
if(gpx->command.flag & X_IS_SET) deltaSteps.x = round(fabs(deltaMM.x) * gpx->machine.x.steps_per_mm); else deltaSteps.x = 0;
|
|
if(gpx->command.flag & Y_IS_SET) deltaSteps.y = round(fabs(deltaMM.y) * gpx->machine.y.steps_per_mm); else deltaSteps.y = 0;
|
|
if(gpx->command.flag & Z_IS_SET) deltaSteps.z = round(fabs(deltaMM.z) * gpx->machine.z.steps_per_mm); else deltaSteps.z = 0;
|
|
if(gpx->command.flag & A_IS_SET) deltaSteps.a = round(fabs(deltaMM.a) * gpx->machine.a.steps_per_mm); else deltaSteps.a = 0;
|
|
if(gpx->command.flag & B_IS_SET) deltaSteps.b = round(fabs(deltaMM.b) * gpx->machine.b.steps_per_mm); else deltaSteps.b = 0;
|
|
return deltaSteps;
|
|
}
|
|
|
|
// X3G QUERIES
|
|
|
|
#define COMMAND_OFFSET 2
|
|
#define EXTRUDER_ID_OFFSET 3
|
|
#define QUERY_COMMAND_OFFSET 4
|
|
#define EEPROM_LENGTH_OFFSET 8
|
|
|
|
// 00 - Get version
|
|
|
|
static int get_version(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 0);
|
|
|
|
// uint16: host version
|
|
write_16(gpx, HOST_VERSION);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
/* 01 - Initialize firmware to boot state
|
|
This is treated as a NOOP in the Sailfish firmware. */
|
|
|
|
static int initialize_firmware(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 1);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 02 - Get available buffer size
|
|
|
|
static int get_buffer_size(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 2);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 03 - Clear buffer (same as 07 and 17)
|
|
|
|
static int clear_buffer(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 3);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 07 - Abort immediately
|
|
|
|
static int abort_immediately(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 7);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 08 - Pause/Resume
|
|
|
|
static int pause_resume(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 8);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 10 - Extruder Query Commands
|
|
|
|
// Query 00 - Query firmware version information
|
|
|
|
static int get_extruder_version(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 0);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 2);
|
|
|
|
// uint16: host version
|
|
write_16(gpx, HOST_VERSION);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Query 02 - Get extruder temperature
|
|
|
|
static int get_extruder_temperature(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 2);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Query 22 - Is extruder ready
|
|
|
|
static int is_extruder_ready(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 22);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Query 30 - Get build platform temperature
|
|
|
|
static int get_build_platform_temperature(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 30);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Query 32 - Get extruder target temperature
|
|
|
|
static int get_extruder_target_temperature(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 32);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Query 33 - Get build platform target temperature
|
|
|
|
static int get_build_platform_target_temperature(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 33);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Query 35 - Is build platform ready?
|
|
|
|
static int is_build_platform_ready(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 35);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Query 36 - Get extruder status
|
|
|
|
static int get_extruder_status(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 36);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Query 37 - Get PID state
|
|
|
|
static int get_PID_state(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 10);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Query command to send to the extruder
|
|
write_8(gpx, 37);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 11 - Is ready
|
|
|
|
static int is_ready(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 11);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 12 - Read from EEPROM
|
|
|
|
static int read_eeprom(Gpx *gpx, unsigned address, unsigned length)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 12);
|
|
|
|
// uint16: EEPROM memory offset to begin reading from
|
|
write_16(gpx, address);
|
|
|
|
// uint8: Number of bytes to read, N.
|
|
write_8(gpx, length);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 13 - Write to EEPROM
|
|
|
|
static int write_eeprom(Gpx *gpx, unsigned address, char *data, unsigned length)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 13);
|
|
|
|
// uint16: EEPROM memory offset to begin writing to
|
|
write_16(gpx, address);
|
|
|
|
// uint8: Number of bytes to write
|
|
write_8(gpx, length);
|
|
|
|
// N bytes: Data to write to EEPROM
|
|
write_bytes(gpx, data, length);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 14 - Capture to file
|
|
|
|
static int capture_to_file(Gpx *gpx, char *filename)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 14);
|
|
|
|
/* 1+N bytes: Filename to write to, in ASCII, terminated with a null character.
|
|
N can be 1-12 bytes long, not including the null character. */
|
|
write_string(gpx, filename, strlen(filename));
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 15 - End capture to file
|
|
|
|
static int end_capture_to_file(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 15);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 16 - Play back capture
|
|
|
|
static int play_back_capture(Gpx *gpx, char *filename)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 16);
|
|
|
|
/* 1+N bytes: Filename to write to, in ASCII, terminated with a null character.
|
|
N can be 1-12 bytes long, not including the null character. */
|
|
write_string(gpx, filename, strlen(filename));
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 17 - Reset
|
|
|
|
static int reset(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 17);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 18 - Get next filename
|
|
|
|
static int get_next_filename(Gpx *gpx, unsigned restart)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 18);
|
|
|
|
// uint8: 0 if file listing should continue, 1 to restart listing.
|
|
write_8(gpx, restart);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 20 - Get build name
|
|
|
|
static int get_build_name(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 20);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 21 - Get extended position
|
|
|
|
static int get_extended_position(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 21);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 22 - Extended stop
|
|
|
|
static int extended_stop(Gpx *gpx, unsigned halt_steppers, unsigned clear_queue)
|
|
{
|
|
unsigned flag = 0;
|
|
if(halt_steppers) flag |= 0x1;
|
|
if(clear_queue) flag |= 0x2;
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 22);
|
|
|
|
/* uint8: Bitfield indicating which subsystems to shut down.
|
|
If bit 0 is set, halt all stepper motion.
|
|
If bit 1 is set, clear the command queue. */
|
|
write_8(gpx, flag);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 23 - Get motherboard status
|
|
|
|
static int get_motherboard_status(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 23);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 24 - Get build statistics
|
|
|
|
static int get_build_statistics(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 24);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 27 - Get advanced version number
|
|
|
|
static int get_advanced_version_number(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 27);
|
|
|
|
// uint16: Host version
|
|
write_16(gpx, HOST_VERSION);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// X3G COMMANDS
|
|
|
|
// 131 - Find axes minimums
|
|
// 132 - Find axes maximums
|
|
|
|
static int home_axes(Gpx *gpx, unsigned axes, unsigned direction)
|
|
{
|
|
Point5d unitVector;
|
|
double feedrate = gpx->command.flag & F_IS_SET ? gpx->current.feedrate : get_home_feedrate(gpx, gpx->command.flag);
|
|
double longestAxis = 0.0;
|
|
assert(direction <= 1);
|
|
|
|
// compute the slowest feedrate
|
|
if(axes & X_IS_SET) {
|
|
if(gpx->machine.x.home_feedrate < feedrate) {
|
|
feedrate = gpx->machine.x.home_feedrate;
|
|
}
|
|
unitVector.x = 1;
|
|
longestAxis = gpx->machine.x.steps_per_mm;
|
|
// confirm machine compatibility
|
|
if(direction != gpx->machine.x.endstop) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: X axis homing to %s endstop" EOL, gpx->lineNumber, direction ? "maximum" : "minimum") );
|
|
}
|
|
}
|
|
if(axes & Y_IS_SET) {
|
|
if(gpx->machine.y.home_feedrate < feedrate) {
|
|
feedrate = gpx->machine.y.home_feedrate;
|
|
}
|
|
unitVector.y = 1;
|
|
if(longestAxis < gpx->machine.y.steps_per_mm) {
|
|
longestAxis = gpx->machine.y.steps_per_mm;
|
|
}
|
|
if(direction != gpx->machine.y.endstop) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: Y axis homing to %s endstop" EOL, gpx->lineNumber, direction ? "maximum" : "minimum") );
|
|
}
|
|
}
|
|
if(axes & Z_IS_SET) {
|
|
if(gpx->machine.z.home_feedrate < feedrate) {
|
|
feedrate = gpx->machine.z.home_feedrate;
|
|
}
|
|
unitVector.z = 1;
|
|
if(longestAxis < gpx->machine.z.steps_per_mm) {
|
|
longestAxis = gpx->machine.z.steps_per_mm;
|
|
}
|
|
if(direction != gpx->machine.z.endstop) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: Z axis homing to %s endstop" EOL, gpx->lineNumber, direction ? "maximum" : "minimum") );
|
|
}
|
|
}
|
|
|
|
// unit vector distance in mm
|
|
double distance = magnitude(axes, &unitVector);
|
|
// move duration in microseconds = distance / feedrate * 60,000,000
|
|
double microseconds = distance / feedrate * 60000000.0;
|
|
// time between steps for longest axis = microseconds / longestStep
|
|
unsigned step_delay = (unsigned)round(microseconds / longestAxis);
|
|
|
|
//gpx->accumulated.time += distance / feedrate * 60;
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, direction == ENDSTOP_IS_MIN ? 131 :132);
|
|
|
|
// uint8: Axes bitfield. Axes whose bits are set will be moved.
|
|
write_8(gpx, axes);
|
|
|
|
// uint32: Feedrate, in microseconds between steps on the max delta. (DDA)
|
|
write_32(gpx, step_delay);
|
|
|
|
// uint16: Timeout, in seconds.
|
|
write_16(gpx, gpx->machine.timeout);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 133 - delay
|
|
|
|
static int delay(Gpx *gpx, unsigned milliseconds)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 133);
|
|
|
|
// uint32: delay, in milliseconds
|
|
write_32(gpx, milliseconds);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 134 - Change extruder offset
|
|
|
|
// This is important to use on dual-head Replicators, because the machine needs to know
|
|
// the current toolhead in order to apply a calibration offset.
|
|
|
|
static int change_extruder_offset(Gpx *gpx, unsigned extruder_id)
|
|
{
|
|
assert(extruder_id < gpx->machine.extruder_count);
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 134);
|
|
|
|
// uint8: ID of the extruder to switch to
|
|
write_8(gpx, extruder_id);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 135 - Wait for extruder ready
|
|
|
|
static int wait_for_extruder(Gpx *gpx, unsigned extruder_id, unsigned timeout)
|
|
{
|
|
assert(extruder_id < gpx->machine.extruder_count);
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 135);
|
|
|
|
// uint8: ID of the extruder to wait for
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint16: delay between query packets sent to the extruder, in ms (nominally 100 ms)
|
|
write_16(gpx, 100);
|
|
|
|
// uint16: Timeout before continuing without extruder ready, in seconds (nominally 1 minute)
|
|
write_16(gpx, timeout);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 136 - extruder action command
|
|
|
|
// Action 03 - Set extruder target temperature
|
|
|
|
static int set_nozzle_temperature(Gpx *gpx, unsigned extruder_id, unsigned temperature)
|
|
{
|
|
assert(extruder_id < gpx->machine.extruder_count);
|
|
|
|
double tDelta = (double)temperature - (double)gpx->tool[extruder_id].nozzle_temperature - AMBIENT_TEMP;
|
|
if(tDelta > 0.0) {
|
|
//gpx->accumulated.time += tDelta * NOZZLE_TIME;
|
|
}
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 136);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Action command to send to the extruder
|
|
write_8(gpx, 3);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 2);
|
|
|
|
// int16: Desired target temperature, in Celsius
|
|
write_16(gpx, temperature);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Action 12 - Enable / Disable fan
|
|
|
|
static int set_fan(Gpx *gpx, unsigned extruder_id, unsigned state)
|
|
{
|
|
assert(extruder_id < gpx->machine.extruder_count);
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 136);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Action command to send to the extruder
|
|
write_8(gpx, 12);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 1);
|
|
|
|
// uint8: 1 to enable, 0 to disable
|
|
write_8(gpx, state);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// Action 13 - Enable / Disable extra output (blower fan)
|
|
|
|
/*
|
|
WARNING: If you are using Gen 4 electronics (e.g. a Thing-o-Matic or a
|
|
heavily modified Cupcake), THEN DO NOT USE M126 / M127. It can trigger
|
|
a bug in the Gen 4 Extruder Controller firmware that will cause the
|
|
HBP temperature to go wild. Note that the Extruder Controller is a
|
|
separate uprocessor on a separate board. It has it's own firmware.
|
|
It's not clear if the bug is firmware-only or if there is a problem
|
|
with electronics as well (e.g. the HBP FET sees some residual current
|
|
from the EXTRA FET and its Vgs/Igs threshold is met and it activates).
|
|
But, there's no fix for the bug since no one has invested the time in
|
|
diagnosing this Extruder Controller issue.
|
|
|
|
- dnewman 22/11/2013
|
|
*/
|
|
|
|
static int set_valve(Gpx *gpx, unsigned extruder_id, unsigned state)
|
|
{
|
|
assert(extruder_id < gpx->machine.extruder_count);
|
|
if(gpx->machine.type >= MACHINE_TYPE_REPLICATOR_1) {
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 136);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Action command to send to the extruder
|
|
write_8(gpx, 13);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 1);
|
|
|
|
// uint8: 1 to enable, 0 to disable
|
|
write_8(gpx, state);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
else if(gpx->flag.logMessages) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: ignoring M126/M127 with Gen 4 extruder electronics" EOL, gpx->lineNumber) );
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
// Action 31 - Set build platform target temperature
|
|
|
|
static int set_build_platform_temperature(Gpx *gpx, unsigned extruder_id, unsigned temperature)
|
|
{
|
|
assert(extruder_id < gpx->machine.extruder_count);
|
|
|
|
double tDelta = (double)temperature - (double)gpx->tool[extruder_id].build_platform_temperature - AMBIENT_TEMP;
|
|
if(tDelta > 0.0) {
|
|
//gpx->accumulated.time += tDelta * HBP_TIME;
|
|
}
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 136);
|
|
|
|
// uint8: ID of the extruder to query
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint8: Action command to send to the extruder
|
|
write_8(gpx, 31);
|
|
|
|
// uint8: Length of the extruder command payload (N)
|
|
write_8(gpx, 2);
|
|
|
|
// int16: Desired target temperature, in Celsius
|
|
write_16(gpx, temperature);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 137 - Enable / Disable axes steppers
|
|
|
|
static int set_steppers(Gpx *gpx, unsigned axes, unsigned state)
|
|
{
|
|
unsigned bitfield = axes & AXES_BIT_MASK;
|
|
if(state) {
|
|
bitfield |= 0x80;
|
|
}
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 137);
|
|
|
|
// uint8: Bitfield codifying the command (see below)
|
|
write_8(gpx, bitfield);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 139 - Queue absolute point
|
|
|
|
static int queue_absolute_point(Gpx *gpx)
|
|
{
|
|
long longestDDA = gpx->longestDDA ? gpx->longestDDA : get_longest_dda(gpx);
|
|
Point5d steps = mm_to_steps(gpx, &gpx->target.position, &gpx->excess);
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 139);
|
|
|
|
// int32: X coordinate, in steps
|
|
write_32(gpx, (int)steps.x);
|
|
|
|
// int32: Y coordinate, in steps
|
|
write_32(gpx, (int)steps.y);
|
|
|
|
// int32: Z coordinate, in steps
|
|
write_32(gpx, (int)steps.z);
|
|
|
|
// int32: A coordinate, in steps
|
|
write_32(gpx, -(int)steps.a);
|
|
|
|
// int32: B coordinate, in steps
|
|
write_32(gpx, -(int)steps.b);
|
|
|
|
// uint32: Feedrate, in microseconds between steps on the max delta. (DDA)
|
|
write_32(gpx, (int)longestDDA);
|
|
|
|
// reset current position
|
|
gpx->axis.positionKnown = gpx->axis.mask;
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 140 - Set extended position
|
|
|
|
static int set_position(Gpx *gpx)
|
|
{
|
|
Point5d steps = mm_to_steps(gpx, &gpx->current.position, NULL);
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 140);
|
|
|
|
// int32: X position, in steps
|
|
write_32(gpx, (int)steps.x);
|
|
|
|
// int32: Y position, in steps
|
|
write_32(gpx, (int)steps.y);
|
|
|
|
// int32: Z position, in steps
|
|
write_32(gpx, (int)steps.z);
|
|
|
|
// int32: A position, in steps
|
|
write_32(gpx, (int)steps.a);
|
|
|
|
// int32: B position, in steps
|
|
write_32(gpx, (int)steps.b);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 141 - Wait for build platform ready
|
|
|
|
static int wait_for_build_platform(Gpx *gpx, unsigned extruder_id, int timeout)
|
|
{
|
|
assert(extruder_id < gpx->machine.extruder_count);
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 141);
|
|
|
|
// uint8: ID of the extruder platform to wait for
|
|
write_8(gpx, extruder_id);
|
|
|
|
// uint16: delay between query packets sent to the extruder, in ms (nominally 100 ms)
|
|
write_16(gpx, 100);
|
|
|
|
// uint16: Timeout before continuing without extruder ready, in seconds (nominally 1 minute)
|
|
write_16(gpx, timeout);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 142 - Queue extended point, new style
|
|
|
|
#if ENABLE_SIMULATED_RPM
|
|
static int queue_new_point(Gpx *gpx, unsigned milliseconds)
|
|
{
|
|
Point5d target;
|
|
|
|
// the function is only called by dwell, which is by definition stationary,
|
|
// so set zero relitive position change
|
|
|
|
target.x = 0;
|
|
target.y = 0;
|
|
target.z = 0;
|
|
target.a = 0;
|
|
target.b = 0;
|
|
|
|
// if we have a G4 dwell and either the a or b motor is on, 'simulate' a 5D extrusion distance
|
|
if(gpx->tool[A].motor_enabled && gpx->tool[A].rpm) {
|
|
double maxrpm = gpx->machine.a.max_feedrate * gpx->machine.a.steps_per_mm / gpx->machine.a.motor_steps;
|
|
double rpm = gpx->tool[A].rpm > maxrpm ? maxrpm : gpx->tool[A].rpm;
|
|
double minutes = milliseconds / 60000.0;
|
|
// minute * revolution/minute
|
|
double numRevolutions = minutes * (gpx->tool[A].motor_enabled > 0 ? rpm : -rpm);
|
|
// steps/revolution * mm/steps
|
|
double mmPerRevolution = gpx->machine.a.motor_steps * (1 / gpx->machine.a.steps_per_mm);
|
|
target.a = -(numRevolutions * mmPerRevolution);
|
|
gpx->command.flag |= A_IS_SET;
|
|
gpx->accumulated.a += fabs(target.a);
|
|
}
|
|
|
|
if(gpx->tool[B].motor_enabled && gpx->tool[B].rpm) {
|
|
double maxrpm = gpx->machine.b.max_feedrate * gpx->machine.b.steps_per_mm / gpx->machine.b.motor_steps;
|
|
double rpm = gpx->tool[B].rpm > maxrpm ? maxrpm : gpx->tool[B].rpm;
|
|
double minutes = milliseconds / 60000.0;
|
|
// minute * revolution/minute
|
|
double numRevolutions = minutes * (gpx->tool[B].motor_enabled > 0 ? rpm : -rpm);
|
|
// steps/revolution * mm/steps
|
|
double mmPerRevolution = gpx->machine.b.motor_steps * (1 / gpx->machine.b.steps_per_mm);
|
|
target.b = -(numRevolutions * mmPerRevolution);
|
|
gpx->command.flag |= B_IS_SET;
|
|
gpx->accumulated.b += fabs(target.a);
|
|
}
|
|
|
|
Point5d steps = mm_to_steps(gpx, &target, &gpx->excess);
|
|
|
|
gpx->accumulated.time += (milliseconds / 1000.0) * ACCELERATION_TIME;
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 142);
|
|
|
|
// int32: X coordinate, in steps
|
|
write_32(gpx, (int)steps.x);
|
|
|
|
// int32: Y coordinate, in steps
|
|
write_32(gpx, (int)steps.y);
|
|
|
|
// int32: Z coordinate, in steps
|
|
write_32(gpx, (int)steps.z);
|
|
|
|
// int32: A coordinate, in steps
|
|
write_32(gpx, (int)steps.a);
|
|
|
|
// int32: B coordinate, in steps
|
|
write_32(gpx, (int)steps.b);
|
|
|
|
// uint32: Duration of the movement, in microseconds
|
|
write_32(gpx, milliseconds * 1000.0);
|
|
|
|
// uint8: Axes bitfield to specify which axes are relative. Any axis with a bit set should make a relative movement.
|
|
write_8(gpx, AXES_BIT_MASK);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
#endif
|
|
|
|
// 143 - Store home positions
|
|
|
|
static int store_home_positions(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 143);
|
|
|
|
// uint8: Axes bitfield to specify which axes' positions to store.
|
|
// Any axis with a bit set should have its position stored.
|
|
write_8(gpx, gpx->command.flag & AXES_BIT_MASK);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 144 - Recall home positions
|
|
|
|
static int recall_home_positions(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 144);
|
|
|
|
// uint8: Axes bitfield to specify which axes' positions to recall.
|
|
// Any axis with a bit set should have its position recalled.
|
|
write_8(gpx, gpx->command.flag & AXES_BIT_MASK);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 145 - Set digital potentiometer value
|
|
|
|
static int set_pot_value(Gpx *gpx, unsigned axis, unsigned value)
|
|
{
|
|
assert(axis <= 4);
|
|
assert(value <= 127);
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 145);
|
|
|
|
// uint8: axis value (valid range 0-4) which axis pot to set
|
|
write_8(gpx, axis);
|
|
|
|
// uint8: value (valid range 0-127), values over max will be capped at max
|
|
write_8(gpx, value);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 146 - Set RGB LED value
|
|
|
|
static int set_LED(Gpx *gpx, unsigned red, unsigned green, unsigned blue, unsigned blink)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 146);
|
|
|
|
// uint8: red value (all pix are 0-255)
|
|
write_8(gpx, red);
|
|
|
|
// uint8: green
|
|
write_8(gpx, green);
|
|
|
|
// uint8: blue
|
|
write_8(gpx, blue);
|
|
|
|
// uint8: blink rate (0-255 valid)
|
|
write_8(gpx, blink);
|
|
|
|
// uint8: 0 (reserved for future use)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
static int set_LED_RGB(Gpx *gpx, unsigned rgb, unsigned blink)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 146);
|
|
|
|
// uint8: red value (all pix are 0-255)
|
|
write_8(gpx, (rgb >> 16) & 0xFF);
|
|
|
|
// uint8: green
|
|
write_8(gpx, (rgb >> 8) & 0xFF);
|
|
|
|
// uint8: blue
|
|
write_8(gpx, rgb & 0xFF);
|
|
|
|
// uint8: blink rate (0-255 valid)
|
|
write_8(gpx, blink);
|
|
|
|
// uint8: 0 (reserved for future use)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 147 - Set Beep
|
|
|
|
static int set_beep(Gpx *gpx, unsigned frequency, unsigned milliseconds)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 147);
|
|
|
|
// uint16: frequency
|
|
write_16(gpx, frequency);
|
|
|
|
// uint16: buzz length in ms
|
|
write_16(gpx, milliseconds);
|
|
|
|
// uint8: 0 (reserved for future use)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 148 - Pause for button
|
|
|
|
#define BUTTON_CENTER 0x01
|
|
#define BUTTON_RIGHT 0x02
|
|
#define BUTTON_LEFT 0x04
|
|
#define BUTTON_DOWN 0x08
|
|
#define BUTTON_UP 0x10
|
|
#define BUTTON_RESET 0x20
|
|
|
|
// Button options
|
|
|
|
#define READY_ON_TIMEOUT 0x01 // change to ready state on timeout
|
|
#define RESET_ON_TIMEOUT 0x02 // reset on timeout
|
|
#define CLEAR_ON_PRESS 0x04 // clear screen on button press
|
|
|
|
static int wait_for_button(Gpx *gpx, int button, unsigned timeout, int button_options)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 148);
|
|
|
|
// uint8: Bit field of buttons to wait for
|
|
write_8(gpx, button);
|
|
|
|
// uint16: Timeout, in seconds. A value of 0 indicates that the command should not time out.
|
|
write_16(gpx, timeout);
|
|
|
|
// uint8: Options bitfield
|
|
write_8(gpx, button_options);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 149 - Display message to LCD
|
|
|
|
static int display_message(Gpx *gpx, char *message, unsigned vPos, unsigned hPos, unsigned timeout, int wait_for_button)
|
|
{
|
|
assert(vPos < 4);
|
|
assert(hPos < 20);
|
|
|
|
int rval;
|
|
long bytesSent = 0;
|
|
unsigned bitfield = 0;
|
|
unsigned seconds = 0;
|
|
|
|
unsigned maxLength = hPos ? 20 - hPos : 20;
|
|
|
|
// clip string so it fits in 4 x 20 lcd display buffer
|
|
long length = strlen(message);
|
|
if(vPos || hPos) {
|
|
if(length > maxLength) length = maxLength;
|
|
bitfield |= 0x01; //do not clear flag
|
|
}
|
|
else {
|
|
if(length > 80) length = 80;
|
|
}
|
|
|
|
while(bytesSent < length) {
|
|
if(bytesSent + maxLength >= length) {
|
|
seconds = timeout;
|
|
bitfield |= 0x02; // last message in group
|
|
if(wait_for_button) {
|
|
bitfield |= 0x04;
|
|
}
|
|
}
|
|
if(bytesSent > 0) {
|
|
bitfield |= 0x01; //do not clear flag
|
|
}
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 149);
|
|
|
|
// uint8: Options bitfield (see below)
|
|
write_8(gpx, bitfield);
|
|
// uint8: Horizontal position to display the message at (commonly 0-19)
|
|
write_8(gpx, hPos);
|
|
// uint8: Vertical position to display the message at (commonly 0-3)
|
|
write_8(gpx, vPos);
|
|
// uint8: Timeout, in seconds. If 0, this message will left on the screen
|
|
write_8(gpx, seconds);
|
|
// 1+N bytes: Message to write to the screen, in ASCII, terminated with a null character.
|
|
long rowLength = length - bytesSent;
|
|
bytesSent += write_string(gpx, message + bytesSent, rowLength < maxLength ? rowLength : maxLength);
|
|
|
|
CALL( end_frame(gpx) );
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
// 150 - Set Build Percentage
|
|
|
|
static int set_build_progress(Gpx *gpx, unsigned percent)
|
|
{
|
|
if(percent > 100) percent = 100;
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 150);
|
|
|
|
// uint8: percent (0-100)
|
|
write_8(gpx, percent);
|
|
|
|
// uint8: 0 (reserved for future use)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 151 - Queue Song
|
|
|
|
static int queue_song(Gpx *gpx, unsigned song_id)
|
|
{
|
|
// song ID 0: error tone with 4 cycles
|
|
// song ID 1: done tone
|
|
// song ID 2: error tone with 2 cycles
|
|
|
|
assert(song_id <= 2);
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 151);
|
|
|
|
// uint8: songID: select from a predefined list of songs
|
|
write_8(gpx, song_id);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 152 - Reset to factory defaults
|
|
|
|
static int factory_defaults(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 152);
|
|
|
|
// uint8: 0 (reserved for future use)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
|
|
// 153 - Build start notification
|
|
|
|
static int start_build(Gpx *gpx, char * filename)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 153);
|
|
|
|
// uint32: 0 (reserved for future use)
|
|
write_32(gpx, 0);
|
|
|
|
// 1+N bytes: Name of the build, in ASCII, null terminated
|
|
write_string(gpx, filename, strlen(filename));
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 154 - Build end notification
|
|
|
|
static int end_build(Gpx *gpx)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 154);
|
|
|
|
// uint8: 0 (reserved for future use)
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 155 - Queue extended point x3g
|
|
|
|
// IMPORTANT: this command updates the parser state
|
|
|
|
static int queue_ext_point(Gpx *gpx, double feedrate)
|
|
{
|
|
/* If we don't know our previous position on a command axis, we can't calculate the feedrate
|
|
or distance correctly, so we use an unaccelerated command with a fixed DDA. */
|
|
unsigned mask = gpx->command.flag & gpx->axis.mask;
|
|
if((gpx->axis.positionKnown & mask) != mask) {
|
|
return queue_absolute_point(gpx);
|
|
}
|
|
Point5d deltaMM = delta_mm(gpx);
|
|
Point5d deltaSteps = delta_steps(gpx, deltaMM);
|
|
|
|
// check that we have actually moved on at least one axis when the move is
|
|
// rounded down to the nearest step
|
|
if(magnitude(gpx->command.flag, &deltaSteps) > 0) {
|
|
double distance = magnitude(gpx->command.flag & XYZ_BIT_MASK, &deltaMM);
|
|
// are we moving and extruding?
|
|
if(gpx->flag.rewrite5D && (gpx->command.flag & (A_IS_SET|B_IS_SET)) && distance > 0.0001) {
|
|
double filament_radius, packing_area, packing_scale;
|
|
if(A_IS_SET && deltaMM.a > 0.0001) {
|
|
if(gpx->override[A].actual_filament_diameter > 0.0001) {
|
|
filament_radius = gpx->override[A].actual_filament_diameter / 2;
|
|
packing_area = M_PI * filament_radius * filament_radius * gpx->override[A].packing_density;
|
|
}
|
|
else {
|
|
filament_radius = gpx->machine.nominal_filament_diameter / 2;
|
|
packing_area = M_PI * filament_radius * filament_radius * gpx->machine.nominal_packing_density;
|
|
}
|
|
packing_scale = gpx->machine.nozzle_diameter * gpx->layerHeight / packing_area;
|
|
if(deltaMM.a > 0) {
|
|
deltaMM.a = distance * packing_scale;
|
|
}
|
|
else {
|
|
deltaMM.a = -(distance * packing_scale);
|
|
}
|
|
gpx->target.position.a = gpx->current.position.a + deltaMM.a;
|
|
deltaSteps.a = round(fabs(deltaMM.a) * gpx->machine.a.steps_per_mm);
|
|
}
|
|
if(B_IS_SET && deltaMM.b > 0.0001) {
|
|
if(gpx->override[B].actual_filament_diameter > 0.0001) {
|
|
filament_radius = gpx->override[B].actual_filament_diameter / 2;
|
|
packing_area = M_PI * filament_radius * filament_radius * gpx->override[A].packing_density;
|
|
}
|
|
else {
|
|
filament_radius = gpx->machine.nominal_filament_diameter / 2;
|
|
packing_area = M_PI * filament_radius * filament_radius * gpx->machine.nominal_packing_density;
|
|
}
|
|
packing_scale = gpx->machine.nozzle_diameter * gpx->layerHeight / packing_area;
|
|
if(deltaMM.b > 0) {
|
|
deltaMM.b = distance * packing_scale;
|
|
}
|
|
else {
|
|
deltaMM.b = -(distance * packing_scale);
|
|
}
|
|
gpx->target.position.b = gpx->current.position.b + deltaMM.b;
|
|
deltaSteps.b = round(fabs(deltaMM.b) * gpx->machine.b.steps_per_mm);
|
|
}
|
|
}
|
|
Point5d target = gpx->target.position;
|
|
|
|
target.a = -deltaMM.a;
|
|
target.b = -deltaMM.b;
|
|
|
|
gpx->accumulated.a += deltaMM.a;
|
|
gpx->accumulated.b += deltaMM.b;
|
|
|
|
deltaMM.x = fabs(deltaMM.x);
|
|
deltaMM.y = fabs(deltaMM.y);
|
|
deltaMM.z = fabs(deltaMM.z);
|
|
deltaMM.a = fabs(deltaMM.a);
|
|
deltaMM.b = fabs(deltaMM.b);
|
|
|
|
feedrate = get_safe_feedrate(gpx, gpx->command.flag, &deltaMM);
|
|
double minutes = distance / feedrate;
|
|
|
|
if(minutes == 0) {
|
|
distance = 0;
|
|
if(gpx->command.flag & A_IS_SET) {
|
|
distance = deltaMM.a;
|
|
}
|
|
if(gpx->command.flag & B_IS_SET && distance < deltaMM.b) {
|
|
distance = deltaMM.b;
|
|
}
|
|
minutes = distance / feedrate;
|
|
}
|
|
|
|
//convert feedrate to mm/sec
|
|
feedrate /= 60.0;
|
|
|
|
#if ENABLE_SIMULATED_RPM
|
|
// if either a or b is 0, but their motor is on and turning, 'simulate' a 5D extrusion distance
|
|
if(deltaMM.a == 0.0 && gpx->tool[A].motor_enabled && gpx->tool[A].rpm) {
|
|
double maxrpm = gpx->machine.a.max_feedrate * gpx->machine.a.steps_per_mm / gpx->machine.a.motor_steps;
|
|
double rpm = gpx->tool[A].rpm > maxrpm ? maxrpm : gpx->tool[A].rpm;
|
|
// minute * revolution/minute
|
|
double numRevolutions = minutes * (gpx->tool[A].motor_enabled > 0 ? rpm : -rpm);
|
|
// steps/revolution * mm/steps
|
|
double mmPerRevolution = gpx->machine.a.motor_steps * (1 / gpx->machine.a.steps_per_mm);
|
|
// set distance
|
|
deltaMM.a = numRevolutions * mmPerRevolution;
|
|
deltaSteps.a = round(fabs(deltaMM.a) * gpx->machine.a.steps_per_mm);
|
|
target.a = -deltaMM.a;
|
|
}
|
|
else {
|
|
// disable RPM as soon as we begin 5D printing
|
|
gpx->tool[A].rpm = 0;
|
|
}
|
|
if(deltaMM.b == 0.0 && gpx->tool[B].motor_enabled && gpx->tool[B].rpm) {
|
|
double maxrpm = gpx->machine.b.max_feedrate * gpx->machine.b.steps_per_mm / gpx->machine.b.motor_steps;
|
|
double rpm = gpx->tool[B].rpm > maxrpm ? maxrpm : gpx->tool[B].rpm;
|
|
// minute * revolution/minute
|
|
double numRevolutions = minutes * (gpx->tool[B].motor_enabled > 0 ? rpm : -rpm);
|
|
// steps/revolution * mm/steps
|
|
double mmPerRevolution = gpx->machine.b.motor_steps * (1 / gpx->machine.b.steps_per_mm);
|
|
// set distance
|
|
deltaMM.b = numRevolutions * mmPerRevolution;
|
|
deltaSteps.b = round(fabs(deltaMM.b) * gpx->machine.b.steps_per_mm);
|
|
target.b = -deltaMM.b;
|
|
}
|
|
else {
|
|
// disable RPM as soon as we begin 5D printing
|
|
gpx->tool[B].rpm = 0;
|
|
}
|
|
#endif
|
|
|
|
Point5d steps = mm_to_steps(gpx, &target, &gpx->excess);
|
|
|
|
double usec = (60000000.0 * minutes);
|
|
|
|
double dda_interval = usec / largest_axis(gpx->command.flag, &deltaSteps);
|
|
|
|
// Convert dda_interval into dda_rate (dda steps per second on the longest axis)
|
|
double dda_rate = 1000000.0 / dda_interval;
|
|
|
|
gpx->accumulated.time += (minutes * 60) * ACCELERATION_TIME;
|
|
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 155);
|
|
|
|
// int32: X coordinate, in steps
|
|
write_32(gpx, (int)steps.x);
|
|
|
|
// int32: Y coordinate, in steps
|
|
write_32(gpx, (int)steps.y);
|
|
|
|
// int32: Z coordinate, in steps
|
|
write_32(gpx, (int)steps.z);
|
|
|
|
// int32: A coordinate, in steps
|
|
write_32(gpx, (int)steps.a);
|
|
|
|
// int32: B coordinate, in steps
|
|
write_32(gpx, (int)steps.b);
|
|
|
|
// uint32: DDA Feedrate, in steps/s
|
|
write_32(gpx, (unsigned)dda_rate);
|
|
|
|
// uint8: Axes bitfield to specify which axes are relative. Any axis with a bit set should make a relative movement.
|
|
write_8(gpx, A_IS_SET|B_IS_SET);
|
|
|
|
// float (single precision, 32 bit): mm distance for this move. normal of XYZ if any of these axes are active, and AB for extruder only moves
|
|
write_float(gpx, (float)distance);
|
|
|
|
// uint16: feedrate in mm/s, multiplied by 64 to assist fixed point calculation on the bot
|
|
write_16(gpx, (unsigned)(feedrate * 64.0));
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
// 156 - Set segment acceleration
|
|
|
|
static int set_acceleration(Gpx *gpx, int state)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 156);
|
|
|
|
// uint8: 1 to enable, 0 to disable
|
|
write_8(gpx, state);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// 157 - Stream Version
|
|
|
|
static int stream_version(Gpx *gpx)
|
|
{
|
|
if(gpx->machine.type >= MACHINE_TYPE_REPLICATOR_1) {
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 157);
|
|
|
|
// uint8: x3g version high byte
|
|
write_8(gpx, STREAM_VERSION_HIGH);
|
|
|
|
// uint8: x3g version low byte
|
|
write_8(gpx, STREAM_VERSION_LOW);
|
|
|
|
// uint8: not implemented
|
|
write_8(gpx, 0);
|
|
|
|
// uint32: not implemented
|
|
write_32(gpx, 0);
|
|
|
|
// uint16: bot type: PID for the intended bot is sent
|
|
// Repliator 2/2X (Might Two)
|
|
if(gpx->machine.type >= MACHINE_TYPE_REPLICATOR_2) {
|
|
write_16(gpx, 0xB015);
|
|
}
|
|
// Replicator (Might One)
|
|
else {
|
|
write_16(gpx, 0xD314);
|
|
}
|
|
|
|
// uint16: not implemented
|
|
write_16(gpx, 0);
|
|
|
|
// uint32: not implemented
|
|
write_32(gpx, 0);
|
|
|
|
// uint32: not implemented
|
|
write_32(gpx, 0);
|
|
|
|
// uint8: not implemented
|
|
write_8(gpx, 0);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
// 158 - Pause @ zPos
|
|
|
|
static int pause_at_zpos(Gpx *gpx, float z_positon)
|
|
{
|
|
begin_frame(gpx);
|
|
|
|
write_8(gpx, 158);
|
|
|
|
// uint8: pause at Z coordinate or 0.0 to disable
|
|
write_float(gpx, z_positon);
|
|
|
|
return end_frame(gpx);
|
|
}
|
|
|
|
// COMMAND @ ZPOS FUNCTIONS
|
|
|
|
// find an existing filament definition
|
|
|
|
static int find_filament(Gpx *gpx, char *filament_id)
|
|
{
|
|
int i, index = -1;
|
|
int l = gpx->filamentLength;
|
|
// a brute force search is generally fastest for low n
|
|
for(i = 0; i < l; i++) {
|
|
if(strcmp(filament_id, gpx->filament[i].colour) == 0) {
|
|
index = i;
|
|
break;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
// add a new filament definition
|
|
|
|
static int add_filament(Gpx *gpx, char *filament_id, double diameter, unsigned temperature, unsigned LED)
|
|
{
|
|
int index = find_filament(gpx, filament_id);
|
|
if(index < 0) {
|
|
if(gpx->filamentLength < FILAMENT_MAX) {
|
|
index = gpx->filamentLength++;
|
|
gpx->filament[index].colour = strdup(filament_id);
|
|
gpx->filament[index].diameter = diameter;
|
|
gpx->filament[index].temperature = temperature;
|
|
gpx->filament[index].LED = LED;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Buffer overflow: too many @filament definitions (maximum = %i)" EOL, gpx->lineNumber, FILAMENT_MAX - 1) );
|
|
index = 0;
|
|
}
|
|
}
|
|
return index;
|
|
}
|
|
|
|
// append a new command at z function
|
|
|
|
static int add_command_at(Gpx *gpx, double z, char *filament_id, unsigned nozzle_temperature, unsigned build_platform_temperature)
|
|
{
|
|
int rval;
|
|
int index = filament_id ? find_filament(gpx, filament_id) : 0;
|
|
if(index < 0) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: @pause macro with undefined filament name '%s', use a @filament macro to define it" EOL, gpx->lineNumber, filament_id) );
|
|
index = 0;
|
|
}
|
|
// insert command
|
|
if(gpx->commandAtLength < COMMAND_AT_MAX) {
|
|
if(gpx->flag.loadMacros) {
|
|
int i = gpx->commandAtLength;
|
|
if(z <= gpx->commandAtZ) {
|
|
// make a space
|
|
while(i > 0 && z <= gpx->commandAt[i - 1].z) {
|
|
gpx->commandAt[i] = gpx->commandAt[i - 1];
|
|
i--;
|
|
}
|
|
gpx->commandAt[i].z = z;
|
|
gpx->commandAt[i].filament_index = index;
|
|
gpx->commandAt[i].nozzle_temperature = nozzle_temperature;
|
|
gpx->commandAt[i].build_platform_temperature = build_platform_temperature;
|
|
gpx->commandAtZ = gpx->commandAt[gpx->commandAtLength].z;
|
|
}
|
|
// append command
|
|
else {
|
|
gpx->commandAt[i].z = z;
|
|
gpx->commandAt[i].filament_index = index;
|
|
gpx->commandAt[i].nozzle_temperature = nozzle_temperature;
|
|
gpx->commandAt[i].build_platform_temperature = build_platform_temperature;
|
|
gpx->commandAtZ = z;
|
|
}
|
|
// nonzero temperature signals a temperature change, not a pause @ zPos
|
|
// so if its the first pause @ zPos que it up
|
|
if(nozzle_temperature == 0 && build_platform_temperature == 0 && gpx->commandAtLength == 0) {
|
|
if(gpx->flag.macrosEnabled) {
|
|
CALL( pause_at_zpos(gpx, z) );
|
|
}
|
|
else {
|
|
gpx->flag.pausePending = 1;
|
|
}
|
|
}
|
|
gpx->commandAtLength++;
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Buffer overflow: too many @pause definitions (maximum = %i)" EOL, gpx->lineNumber, COMMAND_AT_MAX) );
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int display_tag(Gpx *gpx) {
|
|
int rval;
|
|
CALL( display_message(gpx, "GPX " GPX_VERSION, 0, 0, 2, 0) );
|
|
return SUCCESS;
|
|
}
|
|
|
|
// TARGET POSITION
|
|
|
|
// calculate target position
|
|
|
|
static int calculate_target_position(Gpx *gpx)
|
|
{
|
|
int rval;
|
|
// G10 ofset
|
|
Point3d userOffset = gpx->offset[gpx->current.offset];
|
|
double userScale = 1.0;
|
|
|
|
if(gpx->flag.macrosEnabled) {
|
|
// plus command line offset
|
|
userOffset.x += gpx->user.offset.x;
|
|
userOffset.y += gpx->user.offset.y;
|
|
userOffset.z += gpx->user.offset.z;
|
|
// multiply by command line scale
|
|
userScale = gpx->user.scale;
|
|
}
|
|
|
|
// CALCULATE TARGET POSITION
|
|
|
|
// x
|
|
if(gpx->command.flag & X_IS_SET) {
|
|
gpx->target.position.x = gpx->flag.relativeCoordinates ? (gpx->current.position.x + (gpx->command.x * userScale)) : ((gpx->command.x + userOffset.x) * userScale);
|
|
}
|
|
else {
|
|
gpx->target.position.x = gpx->current.position.x;
|
|
}
|
|
|
|
// y
|
|
if(gpx->command.flag & Y_IS_SET) {
|
|
gpx->target.position.y = gpx->flag.relativeCoordinates ? (gpx->current.position.y + (gpx->command.y * userScale)) : ((gpx->command.y + userOffset.y) * userScale);
|
|
}
|
|
else {
|
|
gpx->target.position.y = gpx->current.position.y;
|
|
}
|
|
|
|
// z
|
|
if(gpx->command.flag & Z_IS_SET) {
|
|
gpx->target.position.z = gpx->flag.relativeCoordinates ? (gpx->current.position.z + (gpx->command.z * userScale)) : ((gpx->command.z + userOffset.z) * userScale);
|
|
}
|
|
else {
|
|
gpx->target.position.z = gpx->current.position.z;
|
|
}
|
|
|
|
// a
|
|
if(gpx->command.flag & A_IS_SET) {
|
|
double a = (gpx->override[A].filament_scale == 1.0) ? gpx->command.a : (gpx->command.a * gpx->override[A].filament_scale);
|
|
gpx->target.position.a = (gpx->flag.relativeCoordinates || gpx->flag.extruderIsRelative) ? (gpx->current.position.a + a) : a;
|
|
}
|
|
else {
|
|
gpx->target.position.a = gpx->current.position.a;
|
|
}
|
|
|
|
// b
|
|
if(gpx->command.flag & B_IS_SET) {
|
|
double b = (gpx->override[B].filament_scale == 1.0) ? gpx->command.b : (gpx->command.b * gpx->override[B].filament_scale);
|
|
gpx->target.position.b = (gpx->flag.relativeCoordinates || gpx->flag.extruderIsRelative) ? (gpx->current.position.b + b) : b;
|
|
}
|
|
else {
|
|
gpx->target.position.b = gpx->current.position.b;
|
|
}
|
|
|
|
// update current feedrate
|
|
if(gpx->command.flag & F_IS_SET) {
|
|
gpx->current.feedrate = gpx->command.f;
|
|
}
|
|
|
|
// DITTO PRINTING
|
|
|
|
if(gpx->flag.dittoPrinting) {
|
|
if(gpx->command.flag & A_IS_SET) {
|
|
gpx->target.position.b = gpx->target.position.a;
|
|
gpx->command.flag |= B_IS_SET;
|
|
}
|
|
else if(gpx->command.flag & B_IS_SET) {
|
|
gpx->target.position.a = gpx->target.position.b;
|
|
gpx->command.flag |= A_IS_SET;
|
|
}
|
|
}
|
|
|
|
// CHECK FOR COMMAND @ Z POS
|
|
|
|
// check if there are more commands on the stack
|
|
if(gpx->flag.macrosEnabled && gpx->flag.runMacros && gpx->commandAtIndex < gpx->commandAtLength) {
|
|
// check if the next command will cross the z threshold
|
|
if(gpx->commandAt[gpx->commandAtIndex].z <= gpx->target.position.z) {
|
|
// is this a temperature change macro?
|
|
if(gpx->commandAt[gpx->commandAtIndex].nozzle_temperature || gpx->commandAt[gpx->commandAtIndex].build_platform_temperature) {
|
|
unsigned nozzle_temperature = gpx->commandAt[gpx->commandAtIndex].nozzle_temperature;
|
|
unsigned build_platform_temperature = gpx->commandAt[gpx->commandAtIndex].build_platform_temperature;
|
|
// make sure the temperature has changed
|
|
if(nozzle_temperature) {
|
|
if((gpx->current.extruder == A || gpx->tool[A].nozzle_temperature) && gpx->tool[A].nozzle_temperature != nozzle_temperature) {
|
|
CALL( set_nozzle_temperature(gpx, A, nozzle_temperature) );
|
|
gpx->tool[A].nozzle_temperature = gpx->override[A].active_temperature = nozzle_temperature;
|
|
VERBOSE( fprintf(gpx->log, "(@zPos %0.2f) Nozzle[A] temperature %uc" EOL,
|
|
gpx->commandAt[gpx->commandAtIndex].z,
|
|
nozzle_temperature) );
|
|
}
|
|
if((gpx->current.extruder == B || gpx->tool[B].nozzle_temperature) && gpx->tool[B].nozzle_temperature != nozzle_temperature) {
|
|
CALL( set_nozzle_temperature(gpx, B, nozzle_temperature) );
|
|
gpx->tool[B].nozzle_temperature = gpx->override[B].active_temperature = nozzle_temperature;
|
|
VERBOSE( fprintf(gpx->log, "(@zPos %0.2f) Nozzle[B] temperature %uc" EOL,
|
|
gpx->commandAt[gpx->commandAtIndex].z,
|
|
nozzle_temperature) );
|
|
}
|
|
}
|
|
if(build_platform_temperature) {
|
|
if(gpx->machine.a.has_heated_build_platform && gpx->tool[A].build_platform_temperature && gpx->tool[A].build_platform_temperature != build_platform_temperature) {
|
|
CALL( set_build_platform_temperature(gpx, A, build_platform_temperature) );
|
|
gpx->tool[A].build_platform_temperature = gpx->override[A].build_platform_temperature = build_platform_temperature;
|
|
VERBOSE( fprintf(gpx->log, "(@zPos %0.2f) Build platform[A] temperature %uc" EOL,
|
|
gpx->commandAt[gpx->commandAtIndex].z,
|
|
build_platform_temperature) );
|
|
}
|
|
else if(gpx->machine.b.has_heated_build_platform && gpx->tool[B].build_platform_temperature && gpx->tool[B].build_platform_temperature != build_platform_temperature) {
|
|
CALL( set_build_platform_temperature(gpx, B, build_platform_temperature) );
|
|
gpx->tool[B].build_platform_temperature = gpx->override[B].build_platform_temperature = build_platform_temperature;
|
|
VERBOSE( fprintf(gpx->log, "(@zPos %0.2f) Build platform[B] temperature %uc" EOL,
|
|
gpx->commandAt[gpx->commandAtIndex].z,
|
|
build_platform_temperature) );
|
|
}
|
|
}
|
|
gpx->commandAtIndex++;
|
|
}
|
|
// no its a pause macro
|
|
else if(gpx->commandAt[gpx->commandAtIndex].z <= gpx->target.position.z) {
|
|
int index = gpx->commandAt[gpx->commandAtIndex].filament_index;
|
|
VERBOSE( fprintf(gpx->log, "(@zPos %0.2f) %s",
|
|
gpx->commandAt[gpx->commandAtIndex].z,
|
|
gpx->filament[index].colour) );
|
|
// override filament diameter
|
|
double filament_diameter = gpx->filament[index].diameter;
|
|
if(filament_diameter > 0.0001) {
|
|
VERBOSE( fprintf(gpx->log, ", %0.2fmm", filament_diameter) );
|
|
if(gpx->flag.dittoPrinting) {
|
|
set_filament_scale(gpx, B, filament_diameter);
|
|
set_filament_scale(gpx, A, filament_diameter);
|
|
}
|
|
else {
|
|
set_filament_scale(gpx, gpx->current.extruder, filament_diameter);
|
|
}
|
|
}
|
|
unsigned temperature = gpx->filament[index].temperature;
|
|
// override nozzle temperature
|
|
if(temperature) {
|
|
VERBOSE( fprintf(gpx->log, ", %uc", temperature) );
|
|
if(gpx->tool[gpx->current.extruder].nozzle_temperature != temperature) {
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_nozzle_temperature(gpx, B, temperature) );
|
|
CALL( set_nozzle_temperature(gpx, A, temperature));
|
|
gpx->tool[A].nozzle_temperature = gpx->tool[B].nozzle_temperature = temperature;
|
|
}
|
|
else {
|
|
CALL( set_nozzle_temperature(gpx, gpx->current.extruder, temperature) );
|
|
gpx->tool[gpx->current.extruder].nozzle_temperature = temperature;
|
|
}
|
|
}
|
|
}
|
|
// override LED colour
|
|
if(gpx->filament[index].LED) {
|
|
CALL( set_LED_RGB(gpx, gpx->filament[index].LED, 0) );
|
|
}
|
|
gpx->commandAtIndex++;
|
|
if(gpx->commandAtIndex < gpx->commandAtLength) {
|
|
gpx->flag.doPauseAtZPos = COMMAND_QUE_MAX;
|
|
}
|
|
VERBOSE( fputs(EOL, gpx->log) );
|
|
}
|
|
}
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
static void update_current_position(Gpx *gpx)
|
|
{
|
|
// the current position to tracks where the print head currently is
|
|
if(gpx->target.position.z != gpx->current.position.z) {
|
|
// calculate layer height
|
|
gpx->layerHeight = fabs(gpx->target.position.z - gpx->current.position.z);
|
|
// check upper bounds
|
|
if(gpx->layerHeight > (gpx->machine.nozzle_diameter * 0.85)) {
|
|
gpx->layerHeight = gpx->machine.nozzle_diameter * 0.85;
|
|
}
|
|
}
|
|
gpx->current.position = gpx->target.position;
|
|
if(!gpx->flag.relativeCoordinates) gpx->axis.positionKnown |= gpx->command.flag & gpx->axis.mask;
|
|
}
|
|
|
|
// TOOL CHANGE
|
|
|
|
static int do_tool_change(Gpx *gpx, int timeout) {
|
|
int rval;
|
|
// set the temperature of current tool to standby (if standby is different to active)
|
|
if(gpx->override[gpx->current.extruder].standby_temperature
|
|
&& gpx->override[gpx->current.extruder].standby_temperature != gpx->tool[gpx->current.extruder].nozzle_temperature) {
|
|
unsigned temperature = gpx->override[gpx->current.extruder].standby_temperature;
|
|
CALL( set_nozzle_temperature(gpx, gpx->current.extruder, temperature) );
|
|
gpx->tool[gpx->current.extruder].nozzle_temperature = temperature;
|
|
}
|
|
// set the temperature of selected tool to active (if active is different to standby)
|
|
if(gpx->override[gpx->target.extruder].active_temperature
|
|
&& gpx->override[gpx->target.extruder].active_temperature != gpx->tool[gpx->target.extruder].nozzle_temperature) {
|
|
unsigned temperature = gpx->override[gpx->target.extruder].active_temperature;
|
|
CALL( set_nozzle_temperature(gpx, gpx->target.extruder, temperature) );
|
|
gpx->tool[gpx->target.extruder].nozzle_temperature = temperature;
|
|
// wait for nozzle to head up
|
|
// CALL( wait_for_extruder(gpx, gpx->target.extruder, timeout) );
|
|
}
|
|
// switch any active G10 offset (G54 or G55)
|
|
if(gpx->current.offset == gpx->current.extruder + 1) {
|
|
gpx->current.offset = gpx->target.extruder + 1;
|
|
}
|
|
// change current toolhead in order to apply the calibration offset
|
|
CALL( change_extruder_offset(gpx, gpx->target.extruder) );
|
|
// set current extruder so changes in E are expressed as changes to A or B
|
|
gpx->current.extruder = gpx->target.extruder;
|
|
return SUCCESS;
|
|
}
|
|
|
|
// PARSER PRE-PROCESSOR
|
|
|
|
// return the length of the given file in bytes
|
|
|
|
static long get_filesize(FILE *file)
|
|
{
|
|
long filesize = -1;
|
|
fseek(file, 0L, SEEK_END);
|
|
filesize = ftell(file);
|
|
fseek(file, 0L, SEEK_SET);
|
|
return filesize;
|
|
}
|
|
|
|
// clean up the gcode command for processing
|
|
|
|
static char *normalize_word(char* p)
|
|
{
|
|
// we expect a letter followed by a digit
|
|
// [ a-zA-Z] [ +-]? [ 0-9]+ ('.' [ 0-9]*)?
|
|
char *s = p + 1;
|
|
char *e = p;
|
|
while(isspace(*s)) s++;
|
|
if(*s == '+' || *s == '-') {
|
|
*e++ = *s++;
|
|
}
|
|
while(1) {
|
|
// skip spaces
|
|
if(isspace(*s)) {
|
|
s++;
|
|
}
|
|
// append digits
|
|
else if(isdigit(*s)) {
|
|
*e++ = *s++;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
if(*s == '.') {
|
|
*e++ = *s++;
|
|
while(1) {
|
|
// skip spaces
|
|
if(isspace(*s)) {
|
|
s++;
|
|
}
|
|
// append digits
|
|
else if(isdigit(*s)) {
|
|
*e++ = *s++;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
*e = 0;
|
|
return s;
|
|
}
|
|
|
|
// clean up the gcode comment for processing
|
|
|
|
static char *normalize_comment(char *p) {
|
|
// strip white space from the end of comment
|
|
char *e = p + strlen(p);
|
|
while (e > p && isspace((unsigned char)(*--e))) *e = '\0';
|
|
// strip white space from the beginning of comment.
|
|
while(isspace(*p)) p++;
|
|
return p;
|
|
}
|
|
|
|
// MACRO PARSER
|
|
|
|
/* format
|
|
|
|
;@<STRING> <STRING> <FLOAT> <FLOAT>mm <INTEGER>c #<HEX> (<STRING>)
|
|
|
|
MACRO:= ';' '@' COMMAND COMMENT EOL
|
|
COMMAND:= PRINTER | ENABLE | FILAMENT | EXTRUDER | SLICER | START| PAUSE
|
|
COMMENT:= S+ '(' [^)]* ')' S+
|
|
PRINTER:= ('printer' | 'machine' | 'slicer') (TYPE | PACKING_DENSITY | DIAMETER | TEMP | RGB)+
|
|
TYPE:= S+ ('c3' | 'c4' | 'cp4' | 'cpp' | 't6' | 't7' | 't7d' | 'r1' | 'r1d' | 'r2' | 'r2h' | 'r2x')
|
|
PACKING_DENSITY:= S+ DIGIT+ ('.' DIGIT+)?
|
|
DIAMETER:= S+ DIGIT+ ('.' DIGIT+)? 'm' 'm'?
|
|
TEMP:= S+ DIGIT+ 'c'
|
|
RGB:= S+ '#' HEX HEX HEX HEX HEX HEX ; LED colour
|
|
ENABLE:= 'enable' (DITTO | PROGRESS)
|
|
DITTO:= S+ 'ditto' ; Simulated ditto printing
|
|
PROGRESS:= S+ 'progress' ; Override build progress
|
|
FILAMENT:= 'filament' FILAMENT_ID (DIAMETER | TEMP | RGB)+
|
|
FILAMENT_ID:= S+ ALPHA+ ALPHA_NUMERIC*
|
|
EXTRUDER:= ('right' | 'left') (FILAMENT_ID | DIAMETER | TEMP)+
|
|
SLICER:= 'slicer' DIAMETER ; Nominal filament diameter
|
|
START:= 'start' (FILAMENT_ID | TEMPERATURE)
|
|
PAUSE:= 'pause' (ZPOS | FILAMENT_ID | TEMPERATURE)+
|
|
ZPOS:= S+ DIGIT+ ('.' DIGIT+)?
|
|
|
|
*/
|
|
|
|
#define MACRO_IS(token) strcmp(token, macro) == 0
|
|
#define NAME_IS(n) strcasecmp(name, n) == 0
|
|
|
|
static int parse_macro(Gpx *gpx, const char* macro, char *p)
|
|
{
|
|
int rval;
|
|
char *name = NULL;
|
|
double z = 0.0;
|
|
double diameter = 0.0;
|
|
unsigned nozzle_temperature = 0;
|
|
unsigned build_platform_temperature = 0;
|
|
unsigned LED = 0;
|
|
|
|
while(*p != 0) {
|
|
// trim any leading white space
|
|
while(isspace(*p)) p++;
|
|
if(isalpha(*p)) {
|
|
name = p;
|
|
while(*p && isalnum(*p)) p++;
|
|
if(*p) *p++ = 0;
|
|
}
|
|
else if(isdigit(*p)) {
|
|
char *t = p;
|
|
while(*p && !isspace(*p)) p++;
|
|
if(*(p - 1) == 'm') {
|
|
diameter = strtod(t, NULL);
|
|
}
|
|
else if(*(p - 1) == 'c') {
|
|
unsigned temperature = atoi(t);
|
|
if(temperature > HBP_MAX) {
|
|
nozzle_temperature = temperature;
|
|
}
|
|
else {
|
|
build_platform_temperature = temperature;
|
|
}
|
|
}
|
|
else {
|
|
z = strtod(t, NULL);
|
|
}
|
|
if(*p) *p++ = 0;
|
|
}
|
|
else if(*p == '#') {
|
|
char *t = ++p;
|
|
while(*p && !isspace(*p)) p++;
|
|
if(*p) *p++ = 0;
|
|
LED = (unsigned)strtol(t, NULL, 16);
|
|
}
|
|
else if(*p == '(') {
|
|
char *t = strchr(p + 1, ')');
|
|
if(t) {
|
|
*t = 0;
|
|
p = t + 1;
|
|
}
|
|
else {
|
|
*p = 0;
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: unrecognised macro parameter" EOL, gpx->lineNumber) );
|
|
break;
|
|
}
|
|
}
|
|
// ;@printer <TYPE> <PACKING_DENSITY> <DIAMETER>mm <HBP-TEMP>c #<LED-COLOUR>
|
|
if(MACRO_IS("machine") || MACRO_IS("printer") || MACRO_IS("slicer")) {
|
|
if(name) {
|
|
if(gpx_set_machine(gpx, name)) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: @%s macro with unrecognised type '%s'" EOL, gpx->lineNumber, macro, name) );
|
|
}
|
|
gpx->override[A].packing_density = gpx->machine.nominal_packing_density;
|
|
gpx->override[B].packing_density = gpx->machine.nominal_packing_density;
|
|
}
|
|
if(z > 0.0001) {
|
|
gpx->machine.nominal_packing_density = z;
|
|
}
|
|
if(diameter > 0.0001) gpx->machine.nominal_filament_diameter = diameter;
|
|
if(build_platform_temperature) {
|
|
if(gpx->machine.a.has_heated_build_platform) gpx->override[A].build_platform_temperature = build_platform_temperature;
|
|
else if(gpx->machine.b.has_heated_build_platform) gpx->override[B].build_platform_temperature = build_platform_temperature;
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: @%s macro cannot override non-existant heated build platform" EOL, gpx->lineNumber, macro) );
|
|
}
|
|
}
|
|
if(LED) {
|
|
CALL( set_LED_RGB(gpx, LED, 0) );
|
|
}
|
|
}
|
|
// ;@enable ditto
|
|
// ;@enable progress
|
|
else if(MACRO_IS("enable")) {
|
|
if(name) {
|
|
if(NAME_IS("ditto")) {
|
|
if(gpx->machine.extruder_count == 1) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: ditto printing cannot access non-existant second extruder" EOL, gpx->lineNumber) );
|
|
gpx->flag.dittoPrinting = 0;
|
|
}
|
|
else {
|
|
gpx->flag.dittoPrinting = 1;
|
|
}
|
|
}
|
|
else if(NAME_IS("progress")) gpx->flag.buildProgress = 1;
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: @enable macro with unrecognised parameter '%s'" EOL, gpx->lineNumber, name) );
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: @enable macro with missing parameter" EOL, gpx->lineNumber) );
|
|
}
|
|
}
|
|
// ;@filament <NAME> <DIAMETER>mm <TEMP>c #<LED-COLOUR>
|
|
else if(MACRO_IS("filament")) {
|
|
if(name) {
|
|
add_filament(gpx, name, diameter, nozzle_temperature, LED);
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: @filament macro with missing name" EOL, gpx->lineNumber) );
|
|
}
|
|
}
|
|
// ;@right <NAME> <PACKING_DENSITY> <DIAMETER>mm <TEMP>c
|
|
else if(MACRO_IS("right")) {
|
|
if(name) {
|
|
int index = find_filament(gpx, name);
|
|
if(index > 0) {
|
|
if(gpx->filament[index].diameter > 0.0001) set_filament_scale(gpx, A, gpx->filament[index].diameter);
|
|
if(gpx->filament[index].temperature) gpx->override[A].active_temperature = gpx->filament[index].temperature;
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
if(z > 0.0001) gpx->override[A].packing_density = z;
|
|
if(diameter > 0.0001) set_filament_scale(gpx, A, diameter);
|
|
if(nozzle_temperature) gpx->override[A].active_temperature = nozzle_temperature;
|
|
}
|
|
// ;@left <NAME> <PACKING_DENSITY> <DIAMETER>mm <TEMP>c
|
|
else if(MACRO_IS("left")) {
|
|
if(name) {
|
|
int index = find_filament(gpx, name);
|
|
if(index > 0) {
|
|
if(gpx->filament[index].diameter > 0.0001) set_filament_scale(gpx, B, gpx->filament[index].diameter);
|
|
if(gpx->filament[index].temperature) gpx->override[B].active_temperature = gpx->filament[index].temperature;
|
|
return SUCCESS;
|
|
}
|
|
}
|
|
if(z > 0.0001) gpx->override[A].packing_density = z;
|
|
if(diameter > 0.0001) set_filament_scale(gpx, B, diameter);
|
|
if(nozzle_temperature) gpx->override[B].active_temperature = nozzle_temperature;
|
|
}
|
|
// ;@pause <ZPOS> <NAME>
|
|
else if(MACRO_IS("pause")) {
|
|
if(z > 0.0001) {
|
|
CALL( add_command_at(gpx, z, name, 0, 0) );
|
|
}
|
|
else if(diameter > 0.0001) {
|
|
CALL( add_command_at(gpx, diameter, name, 0, 0) );
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: @pause macro with missing zPos" EOL, gpx->lineNumber) );
|
|
}
|
|
}
|
|
// ;@temp <ZPOS> <TEMP>c
|
|
// ;@temperature <ZPOS> <TEMP>c
|
|
else if(MACRO_IS("temp") || MACRO_IS("temperature")) {
|
|
if(nozzle_temperature || build_platform_temperature) {
|
|
if(z > 0.0001) {
|
|
CALL( add_command_at(gpx, z, NULL, nozzle_temperature, build_platform_temperature) );
|
|
}
|
|
else if(diameter > 0.0001) {
|
|
CALL( add_command_at(gpx, diameter, NULL, nozzle_temperature, build_platform_temperature) );
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: @%s macro with missing zPos" EOL, gpx->lineNumber, macro) );
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: @%s macro with missing temperature" EOL, gpx->lineNumber, macro) );
|
|
}
|
|
}
|
|
// ;@start <NAME> <TEMP>c
|
|
else if(MACRO_IS("start")) {
|
|
if(nozzle_temperature || build_platform_temperature) {
|
|
if(nozzle_temperature) {
|
|
VERBOSE( fprintf(gpx->log, "(@start) Nozzle temperature %uc" EOL, nozzle_temperature) );
|
|
if(gpx->tool[A].nozzle_temperature && gpx->tool[A].nozzle_temperature != nozzle_temperature) {
|
|
if(program_is_running()) {
|
|
CALL( set_nozzle_temperature(gpx, A, nozzle_temperature) );
|
|
}
|
|
gpx->tool[A].nozzle_temperature = gpx->override[A].active_temperature = nozzle_temperature;
|
|
}
|
|
else {
|
|
gpx->override[A].active_temperature = nozzle_temperature;
|
|
}
|
|
if(gpx->tool[B].nozzle_temperature && gpx->tool[B].nozzle_temperature != nozzle_temperature) {
|
|
if(program_is_running()) {
|
|
CALL( set_nozzle_temperature(gpx, B, nozzle_temperature) );
|
|
}
|
|
gpx->tool[B].nozzle_temperature = gpx->override[B].active_temperature = nozzle_temperature;
|
|
}
|
|
else {
|
|
gpx->override[B].active_temperature = nozzle_temperature;
|
|
}
|
|
}
|
|
if(build_platform_temperature) {
|
|
VERBOSE( fprintf(gpx->log, "(@start) Build platform temperature %uc" EOL, build_platform_temperature) );
|
|
if(gpx->machine.a.has_heated_build_platform && gpx->tool[A].build_platform_temperature && gpx->tool[A].build_platform_temperature != build_platform_temperature) {
|
|
if(program_is_running()) {
|
|
CALL( set_build_platform_temperature(gpx, A, build_platform_temperature) );
|
|
}
|
|
gpx->tool[A].build_platform_temperature = gpx->override[A].build_platform_temperature = build_platform_temperature;
|
|
}
|
|
else if(gpx->machine.b.has_heated_build_platform && gpx->tool[B].build_platform_temperature && gpx->tool[B].build_platform_temperature != build_platform_temperature) {
|
|
if(program_is_running()) {
|
|
CALL( set_build_platform_temperature(gpx, B, build_platform_temperature) );
|
|
}
|
|
gpx->tool[B].build_platform_temperature = gpx->override[B].build_platform_temperature = build_platform_temperature;
|
|
}
|
|
}
|
|
}
|
|
else if(name) {
|
|
int index = find_filament(gpx, name);
|
|
if(index > 0) {
|
|
VERBOSE( fprintf(gpx->log, "(@start) %s", name) );
|
|
if(gpx->filament[index].diameter > 0.0001) {
|
|
VERBOSE( fprintf(gpx->log, ", %0.2fmm", gpx->filament[index].diameter) );
|
|
if(gpx->flag.dittoPrinting) {
|
|
set_filament_scale(gpx, B, gpx->filament[index].diameter);
|
|
set_filament_scale(gpx, A, gpx->filament[index].diameter);
|
|
}
|
|
else {
|
|
set_filament_scale(gpx, gpx->current.extruder, gpx->filament[index].diameter);
|
|
}
|
|
}
|
|
if(gpx->filament[index].LED) {
|
|
CALL( set_LED_RGB(gpx, gpx->filament[index].LED, 0) );
|
|
}
|
|
nozzle_temperature = gpx->filament[index].temperature;
|
|
if(nozzle_temperature) {
|
|
VERBOSE( fprintf(gpx->log, ", %uc", nozzle_temperature) );
|
|
if(gpx->tool[A].nozzle_temperature && gpx->tool[A].nozzle_temperature != nozzle_temperature) {
|
|
if(program_is_running()) {
|
|
CALL( set_nozzle_temperature(gpx, A, nozzle_temperature) );
|
|
}
|
|
gpx->tool[A].nozzle_temperature = gpx->override[A].active_temperature = nozzle_temperature;
|
|
}
|
|
else {
|
|
gpx->override[A].active_temperature = nozzle_temperature;
|
|
}
|
|
if(gpx->tool[B].nozzle_temperature && gpx->tool[B].nozzle_temperature != nozzle_temperature) {
|
|
if(program_is_running()) {
|
|
CALL( set_nozzle_temperature(gpx, B, nozzle_temperature) );
|
|
}
|
|
gpx->tool[B].nozzle_temperature = gpx->override[B].active_temperature = nozzle_temperature;
|
|
}
|
|
else {
|
|
gpx->override[B].active_temperature = nozzle_temperature;
|
|
}
|
|
}
|
|
VERBOSE( fputs(EOL, gpx->log) );
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: @start with undefined filament name '%s', use a @filament macro to define it" EOL, gpx->lineNumber, name ? name : "") );
|
|
}
|
|
}
|
|
}
|
|
// ;@body
|
|
else if(MACRO_IS("body")) {
|
|
if(gpx->flag.pausePending) {
|
|
CALL( pause_at_zpos(gpx, gpx->commandAt[0].z) );
|
|
gpx->flag.pausePending = 0;
|
|
}
|
|
gpx->flag.macrosEnabled = 1;
|
|
}
|
|
// ;@header
|
|
// ;@footer
|
|
else if(MACRO_IS("header") && MACRO_IS("footer")) {
|
|
gpx->flag.macrosEnabled = 0;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
/*
|
|
|
|
SIMPLE .INI FILE PARSER
|
|
|
|
ini.c is released under the New BSD license (see LICENSE.txt). Go to the project
|
|
home page for more info: http://code.google.com/p/inih/
|
|
|
|
Parse given INI-style file. May have [section]s, name=value pairs
|
|
(whitespace stripped), and comments starting with ';' (semicolon). Section
|
|
is "" if name=value pair parsed before any section heading. name:value
|
|
pairs are also supported as a concession to Python's ConfigParser.
|
|
|
|
For each name=value pair parsed, call handler function with given user
|
|
pointer as well as section, name, and value (data only valid for duration
|
|
of handler call). Handler should return 0 on success, nonzero on error.
|
|
|
|
Returns 0 on success, line number of first error on parse error (doesn't
|
|
stop on first error), -1 on file open error.
|
|
|
|
*/
|
|
|
|
#define INI_SECTION_MAX 64
|
|
#define INI_NAME_MAX 64
|
|
|
|
/* Nonzero to allow multi-line value parsing, in the style of Python's
|
|
ConfigParser. If allowed, ini_parse() will call the handler with the same
|
|
name for each subsequent line parsed. */
|
|
|
|
#ifndef INI_ALLOW_MULTILINE
|
|
#define INI_ALLOW_MULTILINE 1
|
|
#endif
|
|
|
|
/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
|
|
the file. See http://code.google.com/p/inih/issues/detail?id=21 */
|
|
|
|
#ifndef INI_ALLOW_BOM
|
|
#define INI_ALLOW_BOM 1
|
|
#endif
|
|
|
|
/* Strip whitespace chars off end of given string, in place. Return s. */
|
|
static char* rstrip(char* s)
|
|
{
|
|
char* p = s + strlen(s);
|
|
while (p > s && isspace((unsigned char)(*--p))) *p = '\0';
|
|
return s;
|
|
}
|
|
|
|
/* Return pointer to first non-whitespace char in given string. */
|
|
static char* lskip(const char* s)
|
|
{
|
|
while (*s && isspace((unsigned char)(*s))) s++;
|
|
return (char*)s;
|
|
}
|
|
|
|
/* Return pointer to first char c or ';' comment in given string, or pointer to
|
|
null at end of string if neither found. ';' must be prefixed by a whitespace
|
|
character to register as a comment. */
|
|
static char* find_char_or_comment(const char* s, char c)
|
|
{
|
|
int was_whitespace = 0;
|
|
while (*s && *s != c && !(was_whitespace && *s == ';')) {
|
|
was_whitespace = isspace((unsigned char)(*s));
|
|
s++;
|
|
}
|
|
return (char*)s;
|
|
}
|
|
|
|
/* Version of strncpy that ensures dest (size bytes) is null-terminated. */
|
|
static char* strncpy0(char* dest, const char* src, size_t size)
|
|
{
|
|
strncpy(dest, src, size);
|
|
dest[size - 1] = '\0';
|
|
return dest;
|
|
}
|
|
|
|
/* See documentation in header file. */
|
|
static int ini_parse_file(Gpx* gpx, FILE* file, int (*handler)(Gpx*, const char*, const char*, char*))
|
|
{
|
|
/* Uses a fair bit of stack (use heap instead if you need to) */
|
|
char section[INI_SECTION_MAX] = "";
|
|
char prev_name[INI_NAME_MAX] = "";
|
|
|
|
char* start;
|
|
char* end;
|
|
char* name;
|
|
char* value;
|
|
int error = 0;
|
|
gpx->lineNumber = 0;
|
|
|
|
/* Scan through file line by line */
|
|
while(fgets(gpx->buffer.in, BUFFER_MAX, file) != NULL) {
|
|
gpx->lineNumber++;
|
|
|
|
start = gpx->buffer.in;
|
|
#if INI_ALLOW_BOM
|
|
if(gpx->lineNumber == 1 && (unsigned char)start[0] == 0xEF &&
|
|
(unsigned char)start[1] == 0xBB &&
|
|
(unsigned char)start[2] == 0xBF) {
|
|
start += 3;
|
|
}
|
|
#endif
|
|
start = lskip(rstrip(start));
|
|
|
|
if(*start == ';' || *start == '#') {
|
|
/* Per Python ConfigParser, allow '#' comments at start of line */
|
|
}
|
|
#if INI_ALLOW_MULTILINE
|
|
else if(*prev_name && *start && start > gpx->buffer.in) {
|
|
/* Non-black line with leading whitespace, treat as continuation
|
|
of previous name's value (as per Python ConfigParser). */
|
|
if (handler(gpx, section, prev_name, start) && !error)
|
|
error = gpx->lineNumber;
|
|
}
|
|
#endif
|
|
else if(*start == '[') {
|
|
/* A "[section]" line */
|
|
end = find_char_or_comment(start + 1, ']');
|
|
if(*end == ']') {
|
|
*end = '\0';
|
|
strncpy0(section, start + 1, sizeof(section));
|
|
*prev_name = '\0';
|
|
}
|
|
else if(!error) {
|
|
/* No ']' found on section line */
|
|
error = gpx->lineNumber;
|
|
}
|
|
}
|
|
else if(*start && *start != ';') {
|
|
/* Not a comment, must be a name[=:]value pair */
|
|
end = find_char_or_comment(start, '=');
|
|
if(*end != '=') {
|
|
end = find_char_or_comment(start, ':');
|
|
}
|
|
if(*end == '=' || *end == ':') {
|
|
*end = '\0';
|
|
name = rstrip(start);
|
|
value = lskip(end + 1);
|
|
end = find_char_or_comment(value, '\0');
|
|
if (*end == ';')
|
|
*end = '\0';
|
|
rstrip(value);
|
|
|
|
/* Valid name[=:]value pair found, call handler */
|
|
strncpy0(prev_name, name, sizeof(prev_name));
|
|
if(handler(gpx, section, name, value) && !error)
|
|
error = gpx->lineNumber;
|
|
}
|
|
else if(!error) {
|
|
/* No '=' or ':' found on name[=:]value line */
|
|
error = gpx->lineNumber;
|
|
}
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/* See documentation in header file. */
|
|
|
|
static int ini_parse(Gpx* gpx, const char* filename,
|
|
int (*handler)(Gpx*, const char*, const char*, char*))
|
|
{
|
|
FILE* file;
|
|
int error;
|
|
unsigned ln = gpx->lineNumber;
|
|
file = fopen(filename, "r");
|
|
if(!file) return ERROR;
|
|
error = ini_parse_file(gpx, file, handler);
|
|
gpx->lineNumber = ln;
|
|
fclose(file);
|
|
return error;
|
|
}
|
|
|
|
// Custom machine definition ini handler
|
|
|
|
#define SECTION_IS(s) strcasecmp(section, s) == 0
|
|
#define PROPERTY_IS(n) strcasecmp(property, n) == 0
|
|
#define VALUE_IS(v) strcasecmp(value, v) == 0
|
|
|
|
int gpx_set_property(Gpx *gpx, const char* section, const char* property, char* value)
|
|
{
|
|
int rval;
|
|
if(SECTION_IS("") || SECTION_IS("macro")) {
|
|
if(PROPERTY_IS("slicer")
|
|
|| PROPERTY_IS("filament")
|
|
|| PROPERTY_IS("pause")
|
|
|| PROPERTY_IS("start")
|
|
|| PROPERTY_IS("temp")
|
|
|| PROPERTY_IS("temperature")) {
|
|
CALL( parse_macro(gpx, property, value) );
|
|
}
|
|
else if(PROPERTY_IS("verbose")) {
|
|
gpx->flag.verboseMode = atoi(value);
|
|
}
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("printer") || SECTION_IS("slicer")) {
|
|
if(PROPERTY_IS("ditto_printing")) gpx->flag.dittoPrinting = atoi(value);
|
|
else if(PROPERTY_IS("build_progress")) gpx->flag.buildProgress = atoi(value);
|
|
else if(PROPERTY_IS("packing_density")) gpx->machine.nominal_packing_density = strtod(value, NULL);
|
|
else if(PROPERTY_IS("recalculate_5d")) gpx->flag.rewrite5D = atoi(value);
|
|
else if(PROPERTY_IS("nominal_filament_diameter")
|
|
|| PROPERTY_IS("slicer_filament_diameter")
|
|
|| PROPERTY_IS("filament_diameter")) {
|
|
gpx->machine.nominal_filament_diameter = strtod(value, NULL);
|
|
}
|
|
else if(PROPERTY_IS("machine_type")) {
|
|
// only load/clobber the on-board machine definition if the one specified is different
|
|
if(gpx_set_machine(gpx, value)) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Configuration error: unrecognised machine type '%s'" EOL, gpx->lineNumber, value) );
|
|
return gpx->lineNumber;
|
|
}
|
|
gpx->override[A].packing_density = gpx->machine.nominal_packing_density;
|
|
gpx->override[B].packing_density = gpx->machine.nominal_packing_density;
|
|
}
|
|
else if(PROPERTY_IS("gcode_flavor")) {
|
|
// use on-board machine definition
|
|
if(VALUE_IS("reprap")) gpx->flag.reprapFlavor = 1;
|
|
else if(VALUE_IS("makerbot")) gpx->flag.reprapFlavor = 0;
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Configuration error: unrecognised GCODE flavor '%s'" EOL, gpx->lineNumber, value) );
|
|
return gpx->lineNumber;
|
|
}
|
|
}
|
|
else if(PROPERTY_IS("build_platform_temperature")) {
|
|
if(gpx->machine.a.has_heated_build_platform) gpx->override[A].build_platform_temperature = atoi(value);
|
|
else if(gpx->machine.b.has_heated_build_platform) gpx->override[B].build_platform_temperature = atoi(value);
|
|
}
|
|
else if(PROPERTY_IS("sd_card_path")) {
|
|
gpx->sdCardPath = strdup(value);
|
|
}
|
|
else if(PROPERTY_IS("verbose")) {
|
|
gpx->flag.verboseMode = atoi(value);
|
|
}
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("x")) {
|
|
if(PROPERTY_IS("max_feedrate")) gpx->machine.x.max_feedrate = strtod(value, NULL);
|
|
else if(PROPERTY_IS("home_feedrate")) gpx->machine.x.home_feedrate = strtod(value, NULL);
|
|
else if(PROPERTY_IS("steps_per_mm")) gpx->machine.x.steps_per_mm = strtod(value, NULL);
|
|
else if(PROPERTY_IS("endstop")) gpx->machine.x.endstop = atoi(value);
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("y")) {
|
|
if(PROPERTY_IS("max_feedrate")) gpx->machine.y.max_feedrate = strtod(value, NULL);
|
|
else if(PROPERTY_IS("home_feedrate")) gpx->machine.y.home_feedrate = strtod(value, NULL);
|
|
else if(PROPERTY_IS("steps_per_mm")) gpx->machine.y.steps_per_mm = strtod(value, NULL);
|
|
else if(PROPERTY_IS("endstop")) gpx->machine.y.endstop = atoi(value);
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("z")) {
|
|
if(PROPERTY_IS("max_feedrate")) gpx->machine.z.max_feedrate = strtod(value, NULL);
|
|
else if(PROPERTY_IS("home_feedrate")) gpx->machine.z.home_feedrate = strtod(value, NULL);
|
|
else if(PROPERTY_IS("steps_per_mm")) gpx->machine.z.steps_per_mm = strtod(value, NULL);
|
|
else if(PROPERTY_IS("endstop")) gpx->machine.z.endstop = atoi(value);
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("a")) {
|
|
if(PROPERTY_IS("max_feedrate")) gpx->machine.a.max_feedrate = strtod(value, NULL);
|
|
else if(PROPERTY_IS("steps_per_mm")) gpx->machine.a.steps_per_mm = strtod(value, NULL);
|
|
else if(PROPERTY_IS("motor_steps")) gpx->machine.a.motor_steps = strtod(value, NULL);
|
|
else if(PROPERTY_IS("has_heated_build_platform")) gpx->machine.a.has_heated_build_platform = atoi(value);
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("right")) {
|
|
if(PROPERTY_IS("active_temperature")
|
|
|| PROPERTY_IS("nozzle_temperature")) gpx->override[A].active_temperature = atoi(value);
|
|
else if(PROPERTY_IS("standby_temperature")) gpx->override[A].standby_temperature = atoi(value);
|
|
else if(PROPERTY_IS("build_platform_temperature")) gpx->override[A].build_platform_temperature = atoi(value);
|
|
else if(PROPERTY_IS("actual_filament_diameter")) gpx->override[A].actual_filament_diameter = strtod(value, NULL);
|
|
else if(PROPERTY_IS("packing_density")) gpx->override[A].packing_density = strtod(value, NULL);
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("b")) {
|
|
if(PROPERTY_IS("max_feedrate")) gpx->machine.b.max_feedrate = strtod(value, NULL);
|
|
else if(PROPERTY_IS("steps_per_mm")) gpx->machine.b.steps_per_mm = strtod(value, NULL);
|
|
else if(PROPERTY_IS("motor_steps")) gpx->machine.b.motor_steps = strtod(value, NULL);
|
|
else if(PROPERTY_IS("has_heated_build_platform")) gpx->machine.b.has_heated_build_platform = atoi(value);
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("left")) {
|
|
if(PROPERTY_IS("active_temperature")
|
|
|| PROPERTY_IS("nozzle_temperature")) gpx->override[B].active_temperature = atoi(value);
|
|
else if(PROPERTY_IS("standby_temperature")) gpx->override[B].standby_temperature = atoi(value);
|
|
else if(PROPERTY_IS("build_platform_temperature")) gpx->override[B].build_platform_temperature = atoi(value);
|
|
else if(PROPERTY_IS("actual_filament_diameter")) gpx->override[B].actual_filament_diameter = strtod(value, NULL);
|
|
else if(PROPERTY_IS("packing_density")) gpx->override[B].packing_density = strtod(value, NULL);
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else if(SECTION_IS("machine")) {
|
|
if(PROPERTY_IS("nominal_filament_diameter")
|
|
|| PROPERTY_IS("slicer_filament_diameter")) gpx->machine.nominal_filament_diameter = strtod(value, NULL);
|
|
else if(PROPERTY_IS("packing_density")) gpx->machine.nominal_packing_density = strtod(value, NULL);
|
|
else if(PROPERTY_IS("nozzle_diameter")) gpx->machine.nozzle_diameter = strtod(value, NULL);
|
|
else if(PROPERTY_IS("extruder_count")) {
|
|
gpx->machine.extruder_count = atoi(value);
|
|
gpx->axis.mask = gpx->machine.extruder_count == 1 ? (XYZ_BIT_MASK | A_IS_SET) : AXES_BIT_MASK;;
|
|
}
|
|
else if(PROPERTY_IS("timeout")) gpx->machine.timeout = atoi(value);
|
|
else goto SECTION_ERROR;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Configuration error: unrecognised section [%s]" EOL, gpx->lineNumber, section) );
|
|
return gpx->lineNumber;
|
|
}
|
|
return SUCCESS;
|
|
|
|
SECTION_ERROR:
|
|
SHOW( fprintf(gpx->log, "(line %u) Configuration error: [%s] section contains unrecognised property %s = %s" EOL, gpx->lineNumber, section, property, value) );
|
|
return gpx->lineNumber;
|
|
}
|
|
|
|
int gpx_load_config(Gpx *gpx, const char *filename)
|
|
{
|
|
return ini_parse(gpx, filename, gpx_set_property);
|
|
}
|
|
|
|
void gpx_register_callback(Gpx *gpx, int (*callbackHandler)(Gpx*, void*, char*, size_t), void *callbackData)
|
|
{
|
|
gpx->callbackHandler = callbackHandler;
|
|
gpx->callbackData = callbackData;
|
|
}
|
|
|
|
void gpx_start_convert(Gpx *gpx, char *buildName)
|
|
{
|
|
if(buildName) gpx->buildName = buildName;
|
|
|
|
if(gpx->flag.dittoPrinting && gpx->machine.extruder_count == 1) {
|
|
SHOW( fputs("Configuration error: ditto printing cannot access non-existant second extruder" EOL, gpx->log) );
|
|
gpx->flag.dittoPrinting = 0;
|
|
}
|
|
|
|
// CALCULATE FILAMENT SCALING
|
|
|
|
if(gpx->override[A].actual_filament_diameter > 0.0001
|
|
&& gpx->override[A].actual_filament_diameter != gpx->machine.nominal_filament_diameter) {
|
|
set_filament_scale(gpx, A, gpx->override[A].actual_filament_diameter);
|
|
}
|
|
|
|
if(gpx->override[B].actual_filament_diameter > 0.0001
|
|
&& gpx->override[B].actual_filament_diameter != gpx->machine.nominal_filament_diameter) {
|
|
set_filament_scale(gpx, B, gpx->override[B].actual_filament_diameter);
|
|
}
|
|
}
|
|
|
|
int gpx_convert_line(Gpx *gpx, char *gcode_line)
|
|
{
|
|
int i, rval;
|
|
int next_line = 0;
|
|
int command_emitted = 0;
|
|
|
|
// reset flag state
|
|
gpx->command.flag = 0;
|
|
char *digits;
|
|
char *p = gcode_line; // current parser location
|
|
while(isspace(*p)) p++;
|
|
// check for line number
|
|
if(*p == 'n' || *p == 'N') {
|
|
digits = p;
|
|
p = normalize_word(p);
|
|
if(*p == 0) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: line number command word 'N' is missing digits" EOL, gpx->lineNumber) );
|
|
next_line = gpx->lineNumber + 1;
|
|
}
|
|
else {
|
|
next_line = gpx->lineNumber = atoi(digits);
|
|
}
|
|
}
|
|
else {
|
|
next_line = gpx->lineNumber + 1;
|
|
}
|
|
// parse command words in command line
|
|
while(*p != 0) {
|
|
if(isalpha(*p)) {
|
|
int c = *p;
|
|
digits = p;
|
|
p = normalize_word(p);
|
|
switch(c) {
|
|
|
|
// PARAMETERS
|
|
|
|
// Xnnn X coordinate, usually to move to
|
|
case 'x':
|
|
case 'X':
|
|
gpx->command.x = strtod(digits, NULL);
|
|
gpx->command.flag |= X_IS_SET;
|
|
break;
|
|
|
|
// Ynnn Y coordinate, usually to move to
|
|
case 'y':
|
|
case 'Y':
|
|
gpx->command.y = strtod(digits, NULL);
|
|
gpx->command.flag |= Y_IS_SET;
|
|
break;
|
|
|
|
// Znnn Z coordinate, usually to move to
|
|
case 'z':
|
|
case 'Z':
|
|
gpx->command.z = strtod(digits, NULL);
|
|
gpx->command.flag |= Z_IS_SET;
|
|
break;
|
|
|
|
// Annn Length of extrudate in mm.
|
|
case 'a':
|
|
case 'A':
|
|
gpx->command.a = strtod(digits, NULL);
|
|
gpx->command.flag |= A_IS_SET;
|
|
break;
|
|
|
|
// Bnnn Length of extrudate in mm.
|
|
case 'b':
|
|
case 'B':
|
|
gpx->command.b = strtod(digits, NULL);
|
|
gpx->command.flag |= B_IS_SET;
|
|
break;
|
|
|
|
// Ennn Length of extrudate in mm.
|
|
case 'e':
|
|
case 'E':
|
|
gpx->command.e = strtod(digits, NULL);
|
|
gpx->command.flag |= E_IS_SET;
|
|
break;
|
|
|
|
// Fnnn Feedrate in mm per minute.
|
|
case 'f':
|
|
case 'F':
|
|
gpx->command.f = strtod(digits, NULL);
|
|
gpx->command.flag |= F_IS_SET;
|
|
break;
|
|
|
|
// Pnnn Command parameter, such as a time in milliseconds
|
|
case 'p':
|
|
case 'P':
|
|
gpx->command.p = strtod(digits, NULL);
|
|
gpx->command.flag |= P_IS_SET;
|
|
break;
|
|
|
|
// Rnnn Command Parameter, such as RPM
|
|
case 'r':
|
|
case 'R':
|
|
gpx->command.r = strtod(digits, NULL);
|
|
gpx->command.flag |= R_IS_SET;
|
|
break;
|
|
|
|
// Snnn Command parameter, such as temperature
|
|
case 's':
|
|
case 'S':
|
|
gpx->command.s = strtod(digits, NULL);
|
|
gpx->command.flag |= S_IS_SET;
|
|
break;
|
|
|
|
// COMMANDS
|
|
|
|
// Gnnn GCode command, such as move to a point
|
|
case 'g':
|
|
case 'G':
|
|
gpx->command.g = atoi(digits);
|
|
gpx->command.flag |= G_IS_SET;
|
|
break;
|
|
// Mnnn RepRap-defined command
|
|
case 'm':
|
|
case 'M':
|
|
gpx->command.m = atoi(digits);
|
|
gpx->command.flag |= M_IS_SET;
|
|
break;
|
|
// Tnnn Select extruder nnn.
|
|
case 't':
|
|
case 'T':
|
|
gpx->command.t = atoi(digits);
|
|
gpx->command.flag |= T_IS_SET;
|
|
break;
|
|
|
|
default:
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax warning: unrecognised command word '%c'" EOL, gpx->lineNumber, c) );
|
|
}
|
|
}
|
|
else if(*p == ';') {
|
|
if(*(p + 1) == '@') {
|
|
char *s = p + 2;
|
|
if(isalpha(*s)) {
|
|
char *macro = s;
|
|
// skip any no space characters
|
|
while(*s && !isspace(*s)) s++;
|
|
// null terminate
|
|
if(*s) *s++ = 0;
|
|
CALL( parse_macro(gpx, macro, normalize_comment(s)) );
|
|
*p = 0;
|
|
break;
|
|
}
|
|
}
|
|
// Comment
|
|
gpx->command.comment = normalize_comment(p + 1);
|
|
gpx->command.flag |= COMMENT_IS_SET;
|
|
*p = 0;
|
|
break;
|
|
}
|
|
else if(*p == '(') {
|
|
if(*(p + 1) == '@') {
|
|
char *s = p + 2;
|
|
if(isalpha(*s)) {
|
|
char *macro = s;
|
|
char *e = strrchr(p + 1, ')');
|
|
// skip any no space characters
|
|
while(*s && !isspace(*s)) s++;
|
|
// null terminate
|
|
if(*s) *s++ = 0;
|
|
if(e) *e = 0;
|
|
CALL( parse_macro(gpx, macro, normalize_comment(s)) );
|
|
*p = 0;
|
|
break;
|
|
}
|
|
}
|
|
// Comment
|
|
char *s = strchr(p + 1, '(');
|
|
char *e = strchr(p + 1, ')');
|
|
// check for nested comment
|
|
if(s && e && s < e) {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax warning: nested comment detected" EOL, gpx->lineNumber) );
|
|
e = strrchr(p + 1, ')');
|
|
}
|
|
if(e) {
|
|
*e = 0;
|
|
gpx->command.comment = normalize_comment(p + 1);
|
|
gpx->command.flag |= COMMENT_IS_SET;
|
|
p = e + 1;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax warning: comment is missing closing ')'" EOL, gpx->lineNumber) );
|
|
gpx->command.comment = normalize_comment(p + 1);
|
|
gpx->command.flag |= COMMENT_IS_SET;
|
|
*p = 0;
|
|
break;
|
|
}
|
|
}
|
|
else if(*p == '*') {
|
|
// Checksum
|
|
*p = 0;
|
|
break;
|
|
}
|
|
else if(iscntrl(*p)) {
|
|
break;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: unrecognised gcode '%s'" EOL, gpx->lineNumber, p) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
// revert tool selection to current extruder (Makerbot Tn is not sticky)
|
|
if(!gpx->flag.reprapFlavor) gpx->target.extruder = gpx->current.extruder;
|
|
|
|
// change the extruder selection (in the virtual tool carosel)
|
|
if(gpx->command.flag & T_IS_SET && !gpx->flag.dittoPrinting) {
|
|
unsigned tool_id = (unsigned)gpx->command.t;
|
|
if(tool_id < gpx->machine.extruder_count) {
|
|
gpx->target.extruder = tool_id;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: T%u cannot select non-existant extruder" EOL, gpx->lineNumber, tool_id) );
|
|
}
|
|
}
|
|
|
|
// we treat E as short hand for A or B being set, depending on the state of the gpx->current.extruder
|
|
|
|
if(gpx->command.flag & E_IS_SET) {
|
|
if(gpx->current.extruder == 0) {
|
|
// a = e
|
|
gpx->command.flag |= A_IS_SET;
|
|
gpx->command.a = gpx->command.e;
|
|
}
|
|
else {
|
|
// b = e
|
|
gpx->command.flag |= B_IS_SET;
|
|
gpx->command.b = gpx->command.e;
|
|
}
|
|
}
|
|
|
|
// INTERPRET COMMAND
|
|
|
|
if(gpx->command.flag & G_IS_SET) {
|
|
switch(gpx->command.g) {
|
|
// G0 - Rapid Positioning
|
|
case 0:
|
|
if(gpx->command.flag & F_IS_SET) {
|
|
CALL( calculate_target_position(gpx) );
|
|
CALL( queue_ext_point(gpx, 0.0) );
|
|
update_current_position(gpx);
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
Point3d delta;
|
|
CALL( calculate_target_position(gpx) );
|
|
if(gpx->command.flag & X_IS_SET) delta.x = fabs(gpx->target.position.x - gpx->current.position.x);
|
|
if(gpx->command.flag & Y_IS_SET) delta.y = fabs(gpx->target.position.y - gpx->current.position.y);
|
|
if(gpx->command.flag & Z_IS_SET) delta.z = fabs(gpx->target.position.z - gpx->current.position.z);
|
|
double length = magnitude(gpx->command.flag & XYZ_BIT_MASK, (Ptr5d)&delta);
|
|
double candidate, feedrate = DBL_MAX;
|
|
if(gpx->command.flag & X_IS_SET && delta.x != 0.0) {
|
|
feedrate = gpx->machine.x.max_feedrate * length / delta.x;
|
|
}
|
|
if(gpx->command.flag & Y_IS_SET && delta.y != 0.0) {
|
|
candidate = gpx->machine.y.max_feedrate * length / delta.y;
|
|
if(feedrate > candidate) {
|
|
feedrate = candidate;
|
|
}
|
|
}
|
|
if(gpx->command.flag & Z_IS_SET && delta.z != 0.0) {
|
|
candidate = gpx->machine.z.max_feedrate * length / delta.z;
|
|
if(feedrate > candidate) {
|
|
feedrate = candidate;
|
|
}
|
|
}
|
|
if(feedrate == DBL_MAX) {
|
|
feedrate = gpx->machine.x.max_feedrate;
|
|
}
|
|
CALL( queue_ext_point(gpx, feedrate) );
|
|
update_current_position(gpx);
|
|
command_emitted++;
|
|
}
|
|
break;
|
|
|
|
// G1 - Coordinated Motion
|
|
case 1:
|
|
CALL( calculate_target_position(gpx) );
|
|
CALL( queue_ext_point(gpx, 0.0) );
|
|
update_current_position(gpx);
|
|
command_emitted++;
|
|
break;
|
|
|
|
// G2 - Clockwise Arc
|
|
// G3 - Counter Clockwise Arc
|
|
|
|
// G4 - Dwell
|
|
case 4:
|
|
if(gpx->command.flag & P_IS_SET) {
|
|
#if ENABLE_SIMULATED_RPM
|
|
if(gpx->tool[gpx->current.extruder].motor_enabled && gpx->tool[gpx->current.extruder].rpm) {
|
|
CALL( calculate_target_position(gpx) );
|
|
CALL( queue_new_point(gpx, gpx->command.p) );
|
|
command_emitted++;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
CALL( delay(gpx, gpx->command.p) );
|
|
command_emitted++;
|
|
}
|
|
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: G4 is missing delay parameter, use Pn where n is milliseconds" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
|
|
// G10 - Create Coordinate System Offset from the Absolute one
|
|
case 10:
|
|
if(gpx->command.flag & P_IS_SET && gpx->command.p >= 1.0 && gpx->command.p <= 6.0) {
|
|
i = (int)gpx->command.p;
|
|
if(gpx->command.flag & X_IS_SET) gpx->offset[i].x = gpx->command.x;
|
|
if(gpx->command.flag & Y_IS_SET) gpx->offset[i].y = gpx->command.y;
|
|
if(gpx->command.flag & Z_IS_SET) gpx->offset[i].z = gpx->command.z;
|
|
// set standby temperature
|
|
if(gpx->command.flag & R_IS_SET) {
|
|
unsigned temperature = (unsigned)gpx->command.r;
|
|
if(temperature > NOZZLE_MAX) temperature = NOZZLE_MAX;
|
|
switch(i) {
|
|
case 1:
|
|
gpx->override[A].standby_temperature = temperature;
|
|
break;
|
|
case 2:
|
|
gpx->override[B].standby_temperature = temperature;
|
|
break;
|
|
}
|
|
}
|
|
// set tool temperature
|
|
if(gpx->command.flag & S_IS_SET) {
|
|
unsigned temperature = (unsigned)gpx->command.s;
|
|
if(temperature > NOZZLE_MAX) temperature = NOZZLE_MAX;
|
|
switch(i) {
|
|
case 1:
|
|
gpx->override[A].active_temperature = temperature;
|
|
break;
|
|
case 2:
|
|
gpx->override[B].active_temperature = temperature;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: G10 is missing coordiante system, use Pn where n is 1-6" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
|
|
// G21 - Use Milimeters as Units (IGNORED)
|
|
// G71 - Use Milimeters as Units (IGNORED)
|
|
case 21:
|
|
case 71:
|
|
break;
|
|
|
|
// G28 - Home given axes to machine defined endstop
|
|
case 28: {
|
|
unsigned endstop_max = 0;
|
|
unsigned endstop_min = 0;
|
|
if(gpx->command.flag & F_IS_SET) gpx->current.feedrate = gpx->command.f;
|
|
|
|
if(gpx->command.flag & X_IS_SET) {
|
|
if(gpx->machine.x.endstop) {
|
|
endstop_max |= X_IS_SET;
|
|
}
|
|
else {
|
|
endstop_min |= X_IS_SET;
|
|
}
|
|
}
|
|
|
|
if(gpx->command.flag & Y_IS_SET) {
|
|
if(gpx->machine.y.endstop) {
|
|
endstop_max |= Y_IS_SET;
|
|
}
|
|
else {
|
|
endstop_min |= Y_IS_SET;
|
|
}
|
|
}
|
|
|
|
if(gpx->command.flag & Z_IS_SET) {
|
|
if(gpx->machine.z.endstop) {
|
|
endstop_max |= Z_IS_SET;
|
|
}
|
|
else {
|
|
endstop_min |= Z_IS_SET;
|
|
}
|
|
}
|
|
// home xy before z
|
|
if(gpx->machine.x.endstop) {
|
|
if(endstop_max) {
|
|
CALL( home_axes(gpx, endstop_max, ENDSTOP_IS_MAX) );
|
|
}
|
|
if(endstop_min) {
|
|
CALL( home_axes(gpx, endstop_min, ENDSTOP_IS_MIN) );
|
|
}
|
|
}
|
|
else {
|
|
if(endstop_min) {
|
|
CALL( home_axes(gpx, endstop_min, ENDSTOP_IS_MAX) );
|
|
}
|
|
if(endstop_max) {
|
|
CALL( home_axes(gpx, endstop_max, ENDSTOP_IS_MIN) );
|
|
}
|
|
}
|
|
command_emitted++;
|
|
gpx->axis.positionKnown &= ~(gpx->command.flag & gpx->axis.mask);
|
|
gpx->excess.a = 0;
|
|
gpx->excess.b = 0;
|
|
break;
|
|
}
|
|
|
|
// G53 - Set absolute coordinate system
|
|
case 53:
|
|
gpx->current.offset = 0;
|
|
break;
|
|
|
|
// G54 - Use coordinate system from G10 P1
|
|
case 54:
|
|
gpx->current.offset = 1;
|
|
break;
|
|
|
|
// G55 - Use coordinate system from G10 P2
|
|
case 55:
|
|
gpx->current.offset = 2;
|
|
break;
|
|
|
|
// G56 - Use coordinate system from G10 P3
|
|
case 56:
|
|
gpx->current.offset = 3;
|
|
break;
|
|
|
|
// G57 - Use coordinate system from G10 P4
|
|
case 57:
|
|
gpx->current.offset = 4;
|
|
break;
|
|
|
|
// G58 - Use coordinate system from G10 P5
|
|
case 58:
|
|
gpx->current.offset = 5;
|
|
break;
|
|
|
|
// G59 - Use coordinate system from G10 P6
|
|
case 59:
|
|
gpx->current.offset = 6;
|
|
break;
|
|
|
|
// G90 - Absolute Positioning
|
|
case 90:
|
|
gpx->flag.relativeCoordinates = 0;
|
|
break;
|
|
|
|
// G91 - Relative Positioning
|
|
case 91:
|
|
if((gpx->axis.positionKnown & XYZ_BIT_MASK) == XYZ_BIT_MASK) {
|
|
gpx->flag.relativeCoordinates = 1;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic error: G91 switch to relitive positioning prior to first absolute move" EOL, gpx->lineNumber) );
|
|
return ERROR;
|
|
}
|
|
break;
|
|
|
|
// G92 - Define current position on axes
|
|
case 92: {
|
|
double userScale = gpx->flag.macrosEnabled ? gpx->user.scale : 1.0;
|
|
if(gpx->command.flag & X_IS_SET) gpx->current.position.x = gpx->command.x * userScale;
|
|
if(gpx->command.flag & Y_IS_SET) gpx->current.position.y = gpx->command.y * userScale;
|
|
if(gpx->command.flag & Z_IS_SET) gpx->current.position.z = gpx->command.z * userScale;
|
|
if(gpx->command.flag & A_IS_SET) gpx->current.position.a = gpx->command.a;
|
|
if(gpx->command.flag & B_IS_SET) gpx->current.position.b = gpx->command.b;
|
|
CALL( set_position(gpx) );
|
|
command_emitted++;
|
|
// flag axes that are known
|
|
gpx->axis.positionKnown |= (gpx->command.flag & gpx->axis.mask);
|
|
break;
|
|
}
|
|
|
|
// G130 - Set given axes potentiometer Value
|
|
case 130:
|
|
if(gpx->command.flag & X_IS_SET) {
|
|
CALL( set_pot_value(gpx, 0, gpx->command.x < 0 ? 0 : gpx->command.x > 127 ? 127 : (unsigned)gpx->command.x) );
|
|
}
|
|
|
|
if(gpx->command.flag & Y_IS_SET) {
|
|
CALL( set_pot_value(gpx, 1, gpx->command.y < 0 ? 0 : gpx->command.y > 127 ? 127 : (unsigned)gpx->command.y) );
|
|
}
|
|
|
|
if(gpx->command.flag & Z_IS_SET) {
|
|
CALL( set_pot_value(gpx, 2, gpx->command.z < 0 ? 0 : gpx->command.z > 127 ? 127 : (unsigned)gpx->command.z) );
|
|
}
|
|
|
|
if(gpx->command.flag & A_IS_SET) {
|
|
CALL( set_pot_value(gpx, 3, gpx->command.a < 0 ? 0 : gpx->command.a > 127 ? 127 : (unsigned)gpx->command.a) );
|
|
}
|
|
|
|
if(gpx->command.flag & B_IS_SET) {
|
|
CALL( set_pot_value(gpx, 4, gpx->command.b < 0 ? 0 : gpx->command.b > 127 ? 127 : (unsigned)gpx->command.b) );
|
|
}
|
|
break;
|
|
|
|
// G161 - Home given axes to minimum
|
|
case 161:
|
|
if(gpx->command.flag & F_IS_SET) gpx->current.feedrate = gpx->command.f;
|
|
CALL( home_axes(gpx, gpx->command.flag & XYZ_BIT_MASK, ENDSTOP_IS_MIN) );
|
|
command_emitted++;
|
|
// clear homed axes
|
|
gpx->axis.positionKnown &= ~(gpx->command.flag & gpx->axis.mask);
|
|
gpx->excess.a = 0;
|
|
gpx->excess.b = 0;
|
|
break;
|
|
// G162 - Home given axes to maximum
|
|
case 162:
|
|
if(gpx->command.flag & F_IS_SET) gpx->current.feedrate = gpx->command.f;
|
|
CALL( home_axes(gpx, gpx->command.flag & XYZ_BIT_MASK, ENDSTOP_IS_MAX) );
|
|
command_emitted++;
|
|
// clear homed axes
|
|
gpx->axis.positionKnown &= ~(gpx->command.flag & gpx->axis.mask);
|
|
gpx->excess.a = 0;
|
|
gpx->excess.b = 0;
|
|
break;
|
|
default:
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax warning: unsupported gcode command 'G%u'" EOL, gpx->lineNumber, gpx->command.g) );
|
|
}
|
|
}
|
|
else if(gpx->command.flag & M_IS_SET) {
|
|
switch(gpx->command.m) {
|
|
// M0 - Program stop
|
|
case 0:
|
|
break;
|
|
// M1 - Program pause
|
|
case 1:
|
|
break;
|
|
// M2 - Program end
|
|
case 2:
|
|
if(program_is_running()) {
|
|
end_program();
|
|
CALL( set_build_progress(gpx, 100) );
|
|
CALL( end_build(gpx) );
|
|
}
|
|
return END_OF_FILE;
|
|
|
|
// M6 - Automatic tool change (AND)
|
|
// M116 - Wait for extruder AND build platfrom to reach (or exceed) temperature
|
|
case 6:
|
|
case 116: {
|
|
int timeout = gpx->command.flag & P_IS_SET ? (int)gpx->command.p : MAX_TIMEOUT;
|
|
if(!gpx->flag.dittoPrinting &&
|
|
#if !ENABLE_TOOL_CHANGE_ON_WAIT
|
|
gpx->command.m == 6 &&
|
|
#endif
|
|
gpx->target.extruder != gpx->current.extruder) {
|
|
CALL( do_tool_change(gpx, timeout) );
|
|
command_emitted++;
|
|
}
|
|
// wait for heated build platform
|
|
if(gpx->machine.a.has_heated_build_platform && gpx->tool[A].build_platform_temperature > 0) {
|
|
CALL( wait_for_build_platform(gpx, A, timeout) );
|
|
command_emitted++;
|
|
}
|
|
else if(gpx->machine.b.has_heated_build_platform && gpx->tool[B].build_platform_temperature > 0) {
|
|
CALL( wait_for_build_platform(gpx, B, timeout) );
|
|
command_emitted++;
|
|
}
|
|
// wait for extruder
|
|
if(gpx->flag.dittoPrinting) {
|
|
if(gpx->tool[B].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, B, timeout) );
|
|
}
|
|
if(gpx->tool[A].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, A, timeout) );
|
|
if(gpx->flag.verboseMode) {
|
|
CALL( display_tag(gpx) );
|
|
}
|
|
}
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
if(gpx->tool[gpx->target.extruder].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, gpx->target.extruder, timeout) );
|
|
command_emitted++;
|
|
if(gpx->flag.verboseMode) {
|
|
CALL( display_tag(gpx) );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// M17 - Enable axes steppers
|
|
case 17:
|
|
if(gpx->command.flag & AXES_BIT_MASK) {
|
|
CALL( set_steppers(gpx, gpx->command.flag & AXES_BIT_MASK, 1) );
|
|
command_emitted++;
|
|
if(gpx->command.flag & A_IS_SET) gpx->tool[A].motor_enabled = 1;
|
|
if(gpx->command.flag & B_IS_SET) gpx->tool[B].motor_enabled = 1;
|
|
}
|
|
else {
|
|
CALL( set_steppers(gpx, gpx->machine.extruder_count == 1 ? (XYZ_BIT_MASK | A_IS_SET) : AXES_BIT_MASK, 1) );
|
|
command_emitted++;
|
|
gpx->tool[A].motor_enabled = 1;
|
|
if(gpx->machine.extruder_count == 2) gpx->tool[B].motor_enabled = 1;
|
|
}
|
|
break;
|
|
|
|
// M18 - Disable axes steppers
|
|
case 18:
|
|
if(gpx->command.flag & AXES_BIT_MASK) {
|
|
CALL( set_steppers(gpx, gpx->command.flag & AXES_BIT_MASK, 0) );
|
|
command_emitted++;
|
|
if(gpx->command.flag & A_IS_SET) gpx->tool[A].motor_enabled = 0;
|
|
if(gpx->command.flag & B_IS_SET) gpx->tool[B].motor_enabled = 0;
|
|
}
|
|
else {
|
|
CALL( set_steppers(gpx, gpx->machine.extruder_count == 1 ? (XYZ_BIT_MASK | A_IS_SET) : AXES_BIT_MASK, 0) );
|
|
command_emitted++;
|
|
gpx->tool[A].motor_enabled = 0;
|
|
if(gpx->machine.extruder_count == 2) gpx->tool[B].motor_enabled = 0;
|
|
}
|
|
break;
|
|
|
|
// M20 - List SD card
|
|
case 20:
|
|
break;
|
|
|
|
// M21 - Init SD card
|
|
case 21:
|
|
break;
|
|
|
|
// M22 - Release SD card
|
|
case 22:
|
|
break;
|
|
|
|
// M23 - Select SD file
|
|
case 23:
|
|
break;
|
|
|
|
// M24 - Start/resume SD print
|
|
case 24:
|
|
break;
|
|
|
|
// M25 - Pause SD print
|
|
case 25:
|
|
break;
|
|
|
|
// M26 - Set SD position
|
|
case 26:
|
|
break;
|
|
|
|
// M27 - Report SD print status
|
|
case 27:
|
|
break;
|
|
|
|
// M28 - Begin write to SD card
|
|
case 28:
|
|
break;
|
|
|
|
// M29 - Stop writing to SD card
|
|
case 29:
|
|
break;
|
|
|
|
// M30 - Delete file from SD card
|
|
case 30:
|
|
break;
|
|
|
|
// M31 - Output time since last M109 or SD card start to serial
|
|
case 31:
|
|
break;
|
|
|
|
// M70 - Display message on LCD
|
|
case 70:
|
|
if(gpx->command.flag & COMMENT_IS_SET) {
|
|
unsigned vPos = gpx->command.flag & Y_IS_SET ? (unsigned)gpx->command.y : 0;
|
|
if(vPos > 3) vPos = 3;
|
|
unsigned hPos = gpx->command.flag & X_IS_SET ? (unsigned)gpx->command.x : 0;
|
|
if(hPos > 19) hPos = 19;
|
|
int timeout = gpx->command.flag & P_IS_SET ? gpx->command.p : 0;
|
|
CALL( display_message(gpx, gpx->command.comment, vPos, hPos, timeout, 0) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: M70 is missing message text, use (text) where text is message" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
|
|
// M71 - Display message and wait for button press
|
|
case 71: {
|
|
char *message = gpx->command.flag & COMMENT_IS_SET ? gpx->command.comment : "Press M to continue";
|
|
unsigned vPos = gpx->command.flag & Y_IS_SET ? (unsigned)gpx->command.y : 0;
|
|
if(vPos > 3) vPos = 3;
|
|
unsigned hPos = gpx->command.flag & X_IS_SET ? (unsigned)gpx->command.x : 0;
|
|
if(hPos > 19) hPos = 19;
|
|
int timeout = gpx->command.flag & P_IS_SET ? gpx->command.p : 0;
|
|
CALL( display_message(gpx, message, vPos, hPos, timeout, 1) );
|
|
command_emitted++;
|
|
break;
|
|
}
|
|
|
|
// M72 - Queue a song or play a tone
|
|
case 72:
|
|
if(gpx->command.flag & P_IS_SET) {
|
|
unsigned song_id = (unsigned)gpx->command.p;
|
|
if(song_id > 2) song_id = 2;
|
|
CALL( queue_song(gpx, song_id) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax warning: M72 is missing song number, use Pn where n is 0-2" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
|
|
// M73 - Manual set build percentage
|
|
case 73:
|
|
if(gpx->command.flag & P_IS_SET) {
|
|
unsigned percent = (unsigned)gpx->command.p;
|
|
if(percent > 100) percent = 100;
|
|
if(program_is_ready()) {
|
|
start_program();
|
|
CALL( start_build(gpx, gpx->buildName) );
|
|
CALL( set_build_progress(gpx, 0) );
|
|
// start extruder in a known state
|
|
CALL( change_extruder_offset(gpx, gpx->current.extruder) );
|
|
}
|
|
else if(program_is_running()) {
|
|
if(percent == 100) {
|
|
// disable macros in footer
|
|
gpx->flag.macrosEnabled = 0;
|
|
end_program();
|
|
CALL( set_build_progress(gpx, 100) );
|
|
CALL( end_build(gpx) );
|
|
gpx->current.percent = 100;
|
|
}
|
|
else {
|
|
// enable macros in object body
|
|
if(!gpx->flag.macrosEnabled && percent > 0) {
|
|
if(gpx->flag.pausePending) {
|
|
CALL( pause_at_zpos(gpx, gpx->commandAt[0].z) );
|
|
gpx->flag.pausePending = 0;
|
|
}
|
|
gpx->flag.macrosEnabled = 1;
|
|
}
|
|
if(gpx->current.percent < percent && (percent == 1 || gpx->total.time == 0.0 || gpx->flag.buildProgress == 0)) {
|
|
CALL( set_build_progress(gpx, percent) );
|
|
gpx->current.percent = percent;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax warning: M73 is missing build percentage, use Pn where n is 0-100" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
|
|
// M82 - set extruder to absolute mode
|
|
case 82:
|
|
gpx->flag.extruderIsRelative = 0;
|
|
break;
|
|
|
|
// M83 - set extruder to relative mode
|
|
case 83:
|
|
gpx->flag.extruderIsRelative = 1;
|
|
break;
|
|
|
|
// M84 - Disable steppers until next move
|
|
case 84:
|
|
CALL( set_steppers(gpx, gpx->machine.extruder_count == 1 ? (XYZ_BIT_MASK | A_IS_SET) : AXES_BIT_MASK, 0) );
|
|
command_emitted++;
|
|
gpx->tool[A].motor_enabled = 0;
|
|
if(gpx->machine.extruder_count == 2) gpx->tool[B].motor_enabled = 0;
|
|
break;
|
|
|
|
// M101 - Turn extruder on, forward
|
|
// M102 - Turn extruder on, reverse
|
|
case 101:
|
|
case 102:
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_steppers(gpx, A_IS_SET|B_IS_SET, 1) );
|
|
command_emitted++;
|
|
gpx->tool[A].motor_enabled = gpx->tool[B].motor_enabled = gpx->command.m == 101 ? 1 : -1;
|
|
}
|
|
else {
|
|
CALL( set_steppers(gpx, gpx->target.extruder == 0 ? A_IS_SET : B_IS_SET, 1) );
|
|
command_emitted++;
|
|
gpx->tool[gpx->target.extruder].motor_enabled = gpx->command.m == 101 ? 1 : -1;
|
|
}
|
|
break;
|
|
|
|
// M103 - Turn extruder off
|
|
case 103:
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_steppers(gpx, A_IS_SET|B_IS_SET, 1) );
|
|
command_emitted++;
|
|
gpx->tool[A].motor_enabled = gpx->tool[B].motor_enabled = 0;
|
|
}
|
|
else {
|
|
CALL( set_steppers(gpx, gpx->target.extruder == 0 ? A_IS_SET : B_IS_SET, 0) );
|
|
command_emitted++;
|
|
gpx->tool[gpx->target.extruder].motor_enabled = 0;
|
|
}
|
|
break;
|
|
|
|
// M104 - Set extruder temperature
|
|
case 104:
|
|
if(gpx->command.flag & S_IS_SET) {
|
|
unsigned temperature = (unsigned)gpx->command.s;
|
|
if(temperature > NOZZLE_MAX) temperature = NOZZLE_MAX;
|
|
if(gpx->flag.dittoPrinting) {
|
|
if(temperature && gpx->override[gpx->current.extruder].active_temperature) {
|
|
temperature = gpx->override[gpx->current.extruder].active_temperature;
|
|
}
|
|
CALL( set_nozzle_temperature(gpx, B, temperature) );
|
|
CALL( set_nozzle_temperature(gpx, A, temperature) );
|
|
command_emitted++;
|
|
gpx->tool[A].nozzle_temperature = gpx->tool[B].nozzle_temperature = temperature;
|
|
}
|
|
else {
|
|
if(temperature && gpx->override[gpx->target.extruder].active_temperature) {
|
|
temperature = gpx->override[gpx->target.extruder].active_temperature;
|
|
}
|
|
CALL( set_nozzle_temperature(gpx, gpx->target.extruder, temperature) );
|
|
command_emitted++;
|
|
gpx->tool[gpx->target.extruder].nozzle_temperature = temperature;
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: M104 is missing temperature, use Sn where n is 0-280" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
|
|
// M105 - Get extruder temperature
|
|
case 105:
|
|
break;
|
|
|
|
// M106 - Turn cooling fan on
|
|
case 106: {
|
|
int state = (gpx->command.flag & S_IS_SET) ? ((unsigned)gpx->command.s ? 1 : 0) : 1;
|
|
if(gpx->flag.reprapFlavor && gpx->machine.type >= MACHINE_TYPE_REPLICATOR_1) {
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_valve(gpx, B, state) );
|
|
CALL( set_valve(gpx, A, state) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
CALL( set_valve(gpx, gpx->target.extruder, state) );
|
|
command_emitted++;
|
|
}
|
|
}
|
|
else {
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_fan(gpx, B, state) );
|
|
CALL( set_fan(gpx, A, state) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
CALL( set_fan(gpx, gpx->target.extruder, state) );
|
|
command_emitted++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// M107 - Turn cooling fan off
|
|
case 107:
|
|
if(gpx->flag.reprapFlavor && gpx->machine.type >= MACHINE_TYPE_REPLICATOR_1) {
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_valve(gpx, B, 0) );
|
|
CALL( set_valve(gpx, A, 0) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
CALL( set_valve(gpx, gpx->target.extruder, 0) );
|
|
command_emitted++;
|
|
}
|
|
}
|
|
else {
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_fan(gpx, B, 0) );
|
|
CALL( set_fan(gpx, A, 0) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
CALL( set_fan(gpx, gpx->target.extruder, 0) );
|
|
command_emitted++;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// M108 - Set extruder motor 5D 'simulated' RPM
|
|
case 108:
|
|
#if ENABLE_SIMULATED_RPM
|
|
if(gpx->command.flag & R_IS_SET) {
|
|
if(gpx->flag.dittoPrinting) {
|
|
gpx->tool[A].rpm = gpx->tool[B].rpm = gpx->command.r;
|
|
}
|
|
else {
|
|
gpx->tool[gpx->target.extruder].rpm = gpx->command.r;
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: M108 is missing motor RPM, use Rn where n is 0-5" EOL, gpx->lineNumber) );
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
|
|
// M109 - Set extruder temperature and wait
|
|
case 109:
|
|
if(gpx->flag.reprapFlavor) {
|
|
if(gpx->command.flag & S_IS_SET) {
|
|
int timeout = gpx->command.flag & P_IS_SET ? (int)gpx->command.p : MAX_TIMEOUT;
|
|
unsigned temperature = (unsigned)gpx->command.s;
|
|
if(temperature > NOZZLE_MAX) temperature = NOZZLE_MAX;
|
|
if(gpx->flag.dittoPrinting) {
|
|
unsigned tempB = temperature;
|
|
// set extruder temperatures
|
|
if(temperature) {
|
|
if(gpx->override[B].active_temperature) {
|
|
tempB = gpx->override[B].active_temperature;
|
|
}
|
|
if(gpx->override[A].active_temperature) {
|
|
temperature = gpx->override[A].active_temperature;
|
|
}
|
|
}
|
|
CALL( set_nozzle_temperature(gpx, B, tempB) );
|
|
CALL( set_nozzle_temperature(gpx, A, temperature) );
|
|
gpx->tool[B].nozzle_temperature = tempB;
|
|
gpx->tool[A].nozzle_temperature = temperature;
|
|
// wait for extruders to reach (or exceed) temperature
|
|
if(gpx->tool[B].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, B, timeout) );
|
|
}
|
|
if(gpx->tool[A].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, A, timeout) );
|
|
if(gpx->flag.verboseMode) {
|
|
CALL( display_tag(gpx) );
|
|
}
|
|
}
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
#if ENABLE_TOOL_CHANGE_ON_WAIT
|
|
// because there is a wait we do a tool change
|
|
if(gpx->target.extruder != gpx->current.extruder) {
|
|
CALL( do_tool_change(gpx, timeout) );
|
|
}
|
|
#endif
|
|
// set extruder temperature
|
|
if(temperature && gpx->override[gpx->target.extruder].active_temperature) {
|
|
temperature = gpx->override[gpx->target.extruder].active_temperature;
|
|
}
|
|
CALL( set_nozzle_temperature(gpx, gpx->target.extruder, temperature) );
|
|
gpx->tool[gpx->target.extruder].nozzle_temperature = temperature;
|
|
// wait for extruder to reach (or exceed) temperature
|
|
if(gpx->tool[gpx->target.extruder].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, gpx->target.extruder, timeout) );
|
|
if(gpx->flag.verboseMode) {
|
|
CALL( display_tag(gpx) );
|
|
}
|
|
}
|
|
command_emitted++;
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: M109 is missing temperature, use Sn where n is 0-280" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
}
|
|
// fall through to M140 for Makerbot/ReplicatorG flavor
|
|
|
|
// M140 - Set build platform temperature
|
|
case 140:
|
|
if(gpx->machine.a.has_heated_build_platform || gpx->machine.b.has_heated_build_platform) {
|
|
if(gpx->command.flag & S_IS_SET) {
|
|
unsigned temperature = (unsigned)gpx->command.s;
|
|
if(temperature > HBP_MAX) temperature = HBP_MAX;
|
|
unsigned tool_id = gpx->machine.a.has_heated_build_platform ? A : B;
|
|
if(gpx->command.flag & T_IS_SET) {
|
|
tool_id = gpx->target.extruder;
|
|
}
|
|
if(tool_id ? gpx->machine.b.has_heated_build_platform : gpx->machine.a.has_heated_build_platform) {
|
|
if(temperature && gpx->override[tool_id].build_platform_temperature) {
|
|
temperature = gpx->override[tool_id].build_platform_temperature;
|
|
}
|
|
CALL( set_build_platform_temperature(gpx, tool_id, temperature) );
|
|
command_emitted++;
|
|
gpx->tool[tool_id].build_platform_temperature = temperature;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: M%u cannot select non-existant heated build platform T%u" EOL, gpx->lineNumber, gpx->command.m, tool_id) );
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: M%u is missing temperature, use Sn where n is 0-120" EOL, gpx->lineNumber, gpx->command.m) );
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: M%u cannot select non-existant heated build platform" EOL, gpx->lineNumber, gpx->command.m) );
|
|
}
|
|
break;
|
|
|
|
// M110 - Set current line number
|
|
case 110:
|
|
break;
|
|
|
|
// M111 - Set debug level
|
|
case 111:
|
|
|
|
// M126 - Turn blower fan on (valve open)
|
|
case 126: {
|
|
int state = (gpx->command.flag & S_IS_SET) ? ((unsigned)gpx->command.s ? 1 : 0) : 1;
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_valve(gpx, B, state) );
|
|
CALL( set_valve(gpx, A, state) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
CALL( set_valve(gpx, gpx->target.extruder, state) );
|
|
command_emitted++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// M127 - Turn blower fan off (valve close)
|
|
case 127:
|
|
if(gpx->flag.dittoPrinting) {
|
|
CALL( set_valve(gpx, B, 0) );
|
|
CALL( set_valve(gpx, A, 0) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
CALL( set_valve(gpx, gpx->target.extruder, 0) );
|
|
command_emitted++;
|
|
}
|
|
break;
|
|
|
|
// M131 - Store Current Position to EEPROM
|
|
case 131:
|
|
if(gpx->command.flag & AXES_BIT_MASK) {
|
|
CALL( store_home_positions(gpx) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: M131 is missing axes, use X Y Z A B" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
|
|
// M132 - Load Current Position from EEPROM
|
|
case 132:
|
|
if(gpx->command.flag & AXES_BIT_MASK) {
|
|
CALL( recall_home_positions(gpx) );
|
|
command_emitted++;
|
|
// clear loaded axes
|
|
gpx->axis.positionKnown &= ~(gpx->command.flag & gpx->axis.mask);;
|
|
gpx->excess.a = 0;
|
|
gpx->excess.b = 0;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax error: M132 is missing axes, use X Y Z A B" EOL, gpx->lineNumber) );
|
|
}
|
|
break;
|
|
|
|
// M133 - Wait for extruder
|
|
case 133: {
|
|
int timeout = gpx->command.flag & P_IS_SET ? (int)gpx->command.p : MAX_TIMEOUT;
|
|
// changing the
|
|
if(gpx->flag.dittoPrinting) {
|
|
if(gpx->tool[B].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, B, timeout) );
|
|
}
|
|
if(gpx->tool[A].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, A, timeout) );
|
|
if(gpx->flag.verboseMode) {
|
|
CALL( display_tag(gpx) );
|
|
}
|
|
}
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
#if ENABLE_TOOL_CHANGE_ON_WAIT
|
|
// because there is a wait we do a tool change
|
|
if(gpx->target.extruder != gpx->current.extruder) {
|
|
CALL( do_tool_change(gpx, timeout) );
|
|
}
|
|
#endif
|
|
// any tool changes have already occured
|
|
if(gpx->tool[gpx->target.extruder].nozzle_temperature > 0) {
|
|
CALL( wait_for_extruder(gpx, gpx->target.extruder, timeout) );
|
|
if(gpx->flag.verboseMode) {
|
|
CALL( display_tag(gpx) );
|
|
}
|
|
}
|
|
command_emitted++;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// M134
|
|
// M190 - Wait for build platform to reach (or exceed) temperature
|
|
case 134:
|
|
case 190: {
|
|
if(gpx->machine.a.has_heated_build_platform || gpx->machine.b.has_heated_build_platform) {
|
|
int timeout = gpx->command.flag & P_IS_SET ? (int)gpx->command.p : MAX_TIMEOUT;
|
|
unsigned tool_id = gpx->machine.a.has_heated_build_platform ? A : B;
|
|
if(gpx->command.flag & T_IS_SET) {
|
|
tool_id = gpx->target.extruder;
|
|
}
|
|
if(tool_id ? gpx->machine.b.has_heated_build_platform : gpx->machine.a.has_heated_build_platform
|
|
&& gpx->tool[tool_id].build_platform_temperature > 0) {
|
|
CALL( wait_for_build_platform(gpx, tool_id, timeout) );
|
|
command_emitted++;
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: M%u cannot select non-existant heated build platform T%u" EOL, gpx->lineNumber, gpx->command.m, tool_id) );
|
|
}
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Semantic warning: M%u cannot select non-existant heated build platform" EOL, gpx->lineNumber, gpx->command.m) );
|
|
}
|
|
break;
|
|
}
|
|
|
|
// M135 - Change tool
|
|
case 135:
|
|
if(!gpx->flag.dittoPrinting && gpx->target.extruder != gpx->current.extruder) {
|
|
int timeout = gpx->command.flag & P_IS_SET ? (int)gpx->command.p : MAX_TIMEOUT;
|
|
CALL( do_tool_change(gpx, timeout) );
|
|
command_emitted++;
|
|
}
|
|
break;
|
|
|
|
// M136 - Build start notification
|
|
case 136:
|
|
if(program_is_ready()) {
|
|
start_program();
|
|
CALL( start_build(gpx, gpx->buildName) );
|
|
CALL( set_build_progress(gpx, 0) );
|
|
// start extruder in a known state
|
|
CALL( change_extruder_offset(gpx, gpx->current.extruder) );
|
|
}
|
|
break;
|
|
|
|
// M137 - Build end notification
|
|
case 137:
|
|
if(program_is_running()) {
|
|
// disable macros in footer
|
|
gpx->flag.macrosEnabled = 0;
|
|
end_program();
|
|
CALL( set_build_progress(gpx, 100) );
|
|
CALL( end_build(gpx) );
|
|
gpx->current.percent = 100;
|
|
}
|
|
break;
|
|
|
|
// M300 - Set Beep (SP)
|
|
case 300: {
|
|
unsigned frequency = 300;
|
|
if(gpx->command.flag & S_IS_SET) frequency = (unsigned)gpx->command.s & 0xFFFF;
|
|
unsigned milliseconds = 1000;
|
|
if(gpx->command.flag & P_IS_SET) milliseconds = (unsigned)gpx->command.p & 0xFFFF;
|
|
CALL( set_beep(gpx, frequency, milliseconds) );
|
|
command_emitted++;
|
|
break;
|
|
}
|
|
|
|
// M320 - Acceleration on for subsequent instructions
|
|
case 320:
|
|
CALL( set_acceleration(gpx, 1) );
|
|
command_emitted++;
|
|
break;
|
|
|
|
// M321 - Acceleration off for subsequent instructions
|
|
case 321:
|
|
CALL( set_acceleration(gpx, 0) );
|
|
command_emitted++;
|
|
break;
|
|
|
|
// M322 - Pause @ zPos
|
|
case 322:
|
|
if(gpx->command.flag & Z_IS_SET) {
|
|
float conditional_z = gpx->offset[gpx->current.offset].z;
|
|
|
|
if(gpx->flag.macrosEnabled) {
|
|
conditional_z += gpx->user.offset.z;
|
|
}
|
|
|
|
double z = gpx->flag.relativeCoordinates ? (gpx->current.position.z + gpx->command.z) : (gpx->command.z + conditional_z);
|
|
CALL( pause_at_zpos(gpx, z) );
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax warning: M322 is missing Z axis" EOL, gpx->lineNumber) );
|
|
}
|
|
command_emitted++;
|
|
break;
|
|
|
|
// M420 - Set RGB LED value (REB - P)
|
|
case 420: {
|
|
unsigned red = 0;
|
|
if(gpx->command.flag & R_IS_SET) red = (unsigned)gpx->command.r & 0xFF;
|
|
unsigned green = 0;
|
|
if(gpx->command.flag & E_IS_SET) green = (unsigned)gpx->command.e & 0xFF;
|
|
unsigned blue = 0;
|
|
if(gpx->command.flag & B_IS_SET) blue = (unsigned)gpx->command.b & 0xFF;
|
|
unsigned blink = 0;
|
|
if(gpx->command.flag & P_IS_SET) blink = (unsigned)gpx->command.p & 0xFF;
|
|
CALL( set_LED(gpx, red, green, blue, blink) );
|
|
command_emitted++;
|
|
break;
|
|
}
|
|
|
|
// M500 - Write paramters to EEPROM
|
|
// M501 - Read parameters from EEPROM
|
|
// M502 - Revert to default "factory settings"
|
|
// M503 - Print/log current settings
|
|
default:
|
|
SHOW( fprintf(gpx->log, "(line %u) Syntax warning: unsupported mcode command 'M%u'" EOL, gpx->lineNumber, gpx->command.m) );
|
|
}
|
|
}
|
|
else {
|
|
// X,Y,Z,A,B,E,F
|
|
if(gpx->command.flag & (AXES_BIT_MASK | F_IS_SET)) {
|
|
CALL( calculate_target_position(gpx) );
|
|
CALL( queue_ext_point(gpx, 0.0) );
|
|
update_current_position(gpx);
|
|
command_emitted++;
|
|
}
|
|
// Tn
|
|
else if(!gpx->flag.dittoPrinting && gpx->target.extruder != gpx->current.extruder) {
|
|
int timeout = gpx->command.flag & P_IS_SET ? (int)gpx->command.p : MAX_TIMEOUT;
|
|
CALL( do_tool_change(gpx, timeout) );
|
|
command_emitted++;
|
|
}
|
|
}
|
|
// check for pending pause @ zPos
|
|
if(gpx->flag.doPauseAtZPos) {
|
|
gpx->flag.doPauseAtZPos--;
|
|
// issue next pause @ zPos after command buffer is flushed
|
|
if(gpx->flag.doPauseAtZPos == 0) {
|
|
CALL( pause_at_zpos(gpx, gpx->commandAt[gpx->commandAtIndex].z) );
|
|
}
|
|
}
|
|
// update progress
|
|
if(gpx->total.time > 0.0001 && gpx->accumulated.time > 0.0001 && gpx->flag.buildProgress && command_emitted) {
|
|
unsigned percent = (unsigned)round(100.0 * gpx->accumulated.time / gpx->total.time);
|
|
if(percent > gpx->current.percent) {
|
|
if(program_is_ready()) {
|
|
start_program();
|
|
CALL( start_build(gpx, gpx->buildName) );
|
|
CALL( set_build_progress(gpx, 0) );
|
|
// start extruder in a known state
|
|
CALL( change_extruder_offset(gpx, gpx->current.extruder) );
|
|
}
|
|
else if(percent < 100 && program_is_running()) {
|
|
if(gpx->current.percent) {
|
|
CALL( set_build_progress(gpx, percent) );
|
|
gpx->current.percent = percent;
|
|
}
|
|
// force 1%
|
|
else {
|
|
CALL( set_build_progress(gpx, 1) );
|
|
gpx->current.percent = 1;
|
|
}
|
|
}
|
|
command_emitted = 0;
|
|
}
|
|
}
|
|
gpx->lineNumber = next_line;
|
|
return SUCCESS;
|
|
}
|
|
|
|
typedef struct tFile {
|
|
FILE *in;
|
|
FILE *out;
|
|
FILE *out2;
|
|
} File;
|
|
|
|
static int file_handler(Gpx *gpx, File *file, char *buffer, size_t length)
|
|
{
|
|
if(length) {
|
|
ssize_t bytes = fwrite(buffer, 1, length, file->out);
|
|
if(bytes != length) return ERROR;
|
|
if(file->out2) {
|
|
bytes = fwrite(buffer, 1, length, file->out2);
|
|
if(bytes != length) return ERROR;
|
|
}
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
int gpx_convert(Gpx *gpx, FILE *file_in, FILE *file_out, FILE *file_out2)
|
|
{
|
|
int i, rval;
|
|
File file;
|
|
file.in = stdin;
|
|
file.out = stdout;
|
|
file.out2 = NULL;
|
|
int logMessages = gpx->flag.logMessages;
|
|
|
|
if(file_in && file_in != stdin) {
|
|
// Multi-pass
|
|
file.in = file_in;
|
|
i = 0;
|
|
gpx->flag.runMacros = 0;
|
|
gpx->callbackHandler = NULL;
|
|
gpx->callbackData = NULL;
|
|
}
|
|
else {
|
|
// Single-pass
|
|
i = 1;
|
|
gpx->callbackHandler = (int (*)(Gpx*, void*, char*, size_t))file_handler;;
|
|
gpx->callbackData = &file;
|
|
}
|
|
|
|
if(file_out) {
|
|
file.out = file_out;
|
|
}
|
|
|
|
file.out2 = file_out2;
|
|
|
|
for(;;) {
|
|
int overflow = 0;
|
|
|
|
while(fgets(gpx->buffer.in, BUFFER_MAX, file.in) != NULL) {
|
|
// detect input buffer overflow and ignore overflow input
|
|
if(overflow) {
|
|
if(strlen(gpx->buffer.in) != BUFFER_MAX - 1) {
|
|
overflow = 0;
|
|
}
|
|
continue;
|
|
}
|
|
if(strlen(gpx->buffer.in) == BUFFER_MAX - 1) {
|
|
overflow = 1;
|
|
SHOW( fprintf(gpx->log, "(line %u) Buffer overflow: input exceeds %u character limit, remaining characters in line will be ignored" EOL, gpx->lineNumber, BUFFER_MAX) );
|
|
}
|
|
|
|
rval = gpx_convert_line(gpx, gpx->buffer.in);
|
|
// normal exit
|
|
if(rval == END_OF_FILE) break;
|
|
// error
|
|
if(rval < 0) return rval;
|
|
}
|
|
|
|
if(program_is_running()) {
|
|
end_program();
|
|
CALL( set_build_progress(gpx, 100) );
|
|
CALL( end_build(gpx) );
|
|
}
|
|
|
|
CALL( set_steppers(gpx, AXES_BIT_MASK, 0) );
|
|
|
|
gpx->total.length = gpx->accumulated.a + gpx->accumulated.b;
|
|
gpx->total.time = gpx->accumulated.time;
|
|
gpx->total.bytes = gpx->accumulated.bytes;
|
|
|
|
if(++i > 1) break;
|
|
|
|
// rewind for second pass
|
|
fseek(file.in, 0L, SEEK_SET);
|
|
gpx_initialize(gpx, 0);
|
|
gpx->flag.loadMacros = 0;
|
|
gpx->flag.runMacros = 1;
|
|
//gpx->flag.logMessages = 0;
|
|
gpx->callbackHandler = (int (*)(Gpx*, void*, char*, size_t))file_handler;
|
|
gpx->callbackData = &file;
|
|
}
|
|
gpx->flag.logMessages = logMessages;;
|
|
return SUCCESS;
|
|
}
|
|
|
|
typedef struct tSio {
|
|
FILE *in;
|
|
int port;
|
|
unsigned bytes_out;
|
|
unsigned bytes_in;
|
|
|
|
union {
|
|
struct {
|
|
unsigned short version;
|
|
unsigned char variant;
|
|
} firmware;
|
|
|
|
unsigned int bufferSize;
|
|
unsigned short temperature;
|
|
unsigned int isReady;
|
|
|
|
union {
|
|
unsigned char bitfield;
|
|
struct {
|
|
unsigned char ready: 1; // The extruder has reached target temperature
|
|
unsigned char notPluggedIn: 1; // The tool or platform is not plugged in.
|
|
unsigned char softwareCutoff: 1; // Temperature was recorded above maximum allowable.
|
|
unsigned char notHeating: 1; // Heater is not heating up as expected.
|
|
unsigned char temperatureDropping: 1; // Heater temperature dropped below target temp.
|
|
unsigned char reserved: 1;
|
|
unsigned char buildPlateError: 1; // An error was detected with the platform heater.
|
|
unsigned char extruderError: 1; // An error was detected with the extruder heater.
|
|
} flag;
|
|
} extruder;
|
|
|
|
struct {
|
|
char buffer[31];
|
|
unsigned char length;
|
|
} eeprom;
|
|
|
|
struct {
|
|
short extruderError;
|
|
short extruderDelta;
|
|
short extruderOutput;
|
|
|
|
short buildPlateError;
|
|
short buildPlateDelta;
|
|
short buildPlateOutput;
|
|
} pid;
|
|
|
|
struct {
|
|
unsigned int length;
|
|
char filename[65];
|
|
unsigned char status;
|
|
} sd;
|
|
|
|
struct {
|
|
int x;
|
|
int y;
|
|
int z;
|
|
int a;
|
|
int b;
|
|
|
|
union {
|
|
unsigned short bitfield;
|
|
struct {
|
|
unsigned short xMin: 1; // X min switch pressed
|
|
unsigned short xMax: 1; // X max switch pressed
|
|
|
|
unsigned short yMin: 1; // Y min switch pressed
|
|
unsigned short yMax: 1; // Y max switch pressed
|
|
|
|
unsigned short zMin: 1; // Z min switch pressed
|
|
unsigned short zMax: 1; // Z max switch pressed
|
|
|
|
unsigned short aMin: 1; // A min switch pressed
|
|
unsigned short aMax: 1; // A max switch pressed
|
|
|
|
unsigned short bMin: 1; // B min switch pressed
|
|
unsigned short bMax: 1; // B max switch pressed
|
|
} flag;
|
|
} endstop;
|
|
} position;
|
|
|
|
union {
|
|
unsigned char bitfield;
|
|
struct {
|
|
unsigned char preheat: 1; // Onboard preheat active
|
|
unsigned char manualMode: 1; // Manual move mode active
|
|
unsigned char onboardScript: 1; // Bot is running an onboard script
|
|
unsigned char onboardProcess: 1; // Bot is running an onboard process
|
|
unsigned char waitForButton: 1; // Bot is waiting for button press
|
|
unsigned char buildCancelling: 1; // Watchdog reset flag was set at restart
|
|
unsigned char heatShutdown: 1; // Heaters were shutdown after 30 minutes of inactivity
|
|
unsigned char powerError: 1; // An error was detected with the system power.
|
|
} flag;
|
|
} motherboard;
|
|
|
|
struct {
|
|
unsigned lineNumber;
|
|
unsigned char status;
|
|
unsigned char hours;
|
|
unsigned char minutes;
|
|
} build;
|
|
|
|
} response;
|
|
|
|
} Sio;
|
|
|
|
char *sd_status[] = {
|
|
"operation successful",
|
|
"SD Card not present",
|
|
"SD Card initialization failed",
|
|
"partition table could not be read",
|
|
"filesystem could not be opened",
|
|
"root directory could not be opened",
|
|
"SD Card is locked",
|
|
"unknown status"
|
|
};
|
|
|
|
static char *get_sd_status(unsigned int status)
|
|
{
|
|
return sd_status[status < 7 ? status : 7];
|
|
}
|
|
|
|
char *build_status[] = {
|
|
"no build initialized (boot state)",
|
|
"build running",
|
|
"build finished normally",
|
|
"build paused",
|
|
"build cancelled",
|
|
"build sleeping",
|
|
"unknown status"
|
|
};
|
|
|
|
static char *get_build_status(unsigned int status)
|
|
{
|
|
return sd_status[status < 6 ? status : 6];
|
|
}
|
|
|
|
static void read_extruder_query_response(Gpx *gpx, Sio *sio, unsigned command, char *buffer)
|
|
{
|
|
unsigned extruder_id = buffer[EXTRUDER_ID_OFFSET];
|
|
|
|
switch(command) {
|
|
// Query 00 - Query firmware version information
|
|
case 0:
|
|
// uint16: Firmware Version
|
|
sio->response.firmware.version = read_16(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Extruder T%u firmware v%u.%u" EOL,
|
|
extruder_id,
|
|
sio->response.firmware.version / 100,
|
|
sio->response.firmware.version % 100) );
|
|
break;
|
|
|
|
// Query 02 - Get extruder temperature
|
|
case 2:
|
|
// int16: Current temperature, in Celsius
|
|
sio->response.temperature = read_16(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Extruder T%u temperature: %uc" EOL,
|
|
extruder_id,
|
|
sio->response.temperature) );
|
|
break;
|
|
|
|
// Query 22 - Is extruder ready
|
|
case 22:
|
|
// uint8: 1 if ready, 0 otherwise.
|
|
sio->response.isReady = read_8(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Extruder T%u is%sready" EOL,
|
|
extruder_id,
|
|
sio->response.isReady ? " " : " not ") );
|
|
break;
|
|
|
|
// Query 30 - Get build platform temperature
|
|
case 30:
|
|
// int16: Current temperature, in Celsius
|
|
sio->response.temperature = read_16(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Build platform T%u temperature: %uc" EOL,
|
|
extruder_id,
|
|
sio->response.temperature) );
|
|
break;
|
|
|
|
// Query 32 - Get extruder target temperature
|
|
case 32:
|
|
// int16: Current temperature, in Celsius
|
|
sio->response.temperature = read_16(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Extruder T%u target temperature: %uc" EOL,
|
|
extruder_id,
|
|
sio->response.temperature) );
|
|
break;
|
|
|
|
// Query 33 - Get build platform target temperature
|
|
case 33:
|
|
// int16: Current temperature, in Celsius
|
|
sio->response.temperature = read_16(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Build platform T%u target temperature: %uc" EOL,
|
|
extruder_id,
|
|
sio->response.temperature) );
|
|
break;
|
|
|
|
// Query 35 - Is build platform ready?
|
|
case 35:
|
|
// uint8: 1 if ready, 0 otherwise.
|
|
sio->response.isReady = read_8(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Build platform T%u is%sready" EOL,
|
|
extruder_id,
|
|
sio->response.isReady ? " " : " not ") );
|
|
break;
|
|
|
|
// Query 36 - Get extruder status
|
|
case 36:
|
|
// uint8: Bitfield containing status information
|
|
sio->response.extruder.bitfield = read_8(gpx);
|
|
|
|
if(gpx->flag.verboseMode && gpx->flag.logMessages) {
|
|
fprintf(gpx->log, "Extruder T%u status" EOL, extruder_id);
|
|
if(sio->response.extruder.flag.ready) fputs("Target temperature reached" EOL, gpx->log);
|
|
if(sio->response.extruder.flag.notPluggedIn) fputs("The extruder or build plate is not plugged in" EOL, gpx->log);
|
|
if(sio->response.extruder.flag.softwareCutoff) fputs("Above maximum allowable temperature recorded: heater shutdown for safety" EOL, gpx->log);
|
|
if(sio->response.extruder.flag.temperatureDropping) fputs("Heater temperature dropped below target temperature" EOL, gpx->log);
|
|
if(sio->response.extruder.flag.buildPlateError) fputs("An error was detected with the build plate heater or sensor" EOL, gpx->log);
|
|
if(sio->response.extruder.flag.extruderError) fputs("An error was detected with the extruder heater or sensor" EOL, gpx->log);
|
|
}
|
|
break;
|
|
|
|
// Query 37 - Get PID state
|
|
case 37:
|
|
// int16: Extruder heater error term
|
|
sio->response.pid.extruderError = read_16(gpx);
|
|
|
|
// int16: Extruder heater delta term
|
|
sio->response.pid.extruderDelta = read_16(gpx);
|
|
|
|
// int16: Extruder heater last output
|
|
sio->response.pid.extruderOutput = read_16(gpx);
|
|
|
|
// int16: Platform heater error term
|
|
sio->response.pid.buildPlateError = read_16(gpx);
|
|
|
|
// int16: Platform heater delta term
|
|
sio->response.pid.buildPlateDelta = read_16(gpx);
|
|
|
|
// int16: Platform heater last output
|
|
sio->response.pid.buildPlateOutput = read_16(gpx);
|
|
|
|
break;
|
|
|
|
default:
|
|
abort();
|
|
}
|
|
}
|
|
|
|
static void read_query_response(Gpx *gpx, Sio *sio, unsigned command, char *buffer)
|
|
{
|
|
gpx->buffer.ptr = gpx->buffer.in + 2;
|
|
switch(command) {
|
|
// 00 - Query firmware version information
|
|
case 0:
|
|
// uint16: Firmware Version
|
|
sio->response.firmware.version = read_16(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Motherboard firmware v%u.%u" EOL,
|
|
sio->response.firmware.version / 100, sio->response.firmware.version % 100) );
|
|
break;
|
|
|
|
// 02 - Get available buffer size
|
|
case 2:
|
|
// uint32: Number of bytes availabe in the command buffer
|
|
sio->response.bufferSize = read_32(gpx);
|
|
break;
|
|
|
|
// 10 - Extruder query command
|
|
case 10: {
|
|
unsigned query_command = buffer[QUERY_COMMAND_OFFSET];
|
|
read_extruder_query_response(gpx, sio, query_command, buffer);
|
|
break;
|
|
}
|
|
|
|
// 11 - Is ready
|
|
case 11:
|
|
// uint8: 1 if ready, 0 otherwise.
|
|
sio->response.isReady = read_8(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Printer is%sready" EOL,
|
|
sio->response.isReady ? " " : " not ") );
|
|
break;
|
|
|
|
// 12 - Read from EEPROM
|
|
case 12:
|
|
// N bytes: Data read from the EEPROM
|
|
sio->response.eeprom.length = buffer[EEPROM_LENGTH_OFFSET];
|
|
read_bytes(gpx, sio->response.eeprom.buffer, sio->response.eeprom.length);
|
|
break;
|
|
|
|
// 13 - Write to EEPROM
|
|
case 13:
|
|
// uint8: Number of bytes successfully written to the EEPROM
|
|
sio->response.eeprom.length = read_8(gpx);
|
|
break;
|
|
|
|
// 14 - Capture to file
|
|
case 14:
|
|
// uint8: SD response code
|
|
sio->response.sd.status = read_8(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Capture to file: %s" EOL,
|
|
get_sd_status(sio->response.sd.status)) );
|
|
break;
|
|
|
|
// 15 - End capture to file
|
|
case 15:
|
|
// uint32: Number of bytes captured to file.
|
|
sio->response.sd.length = read_32(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Capture to file ended: %u bytes written" EOL,
|
|
sio->response.sd.length) );
|
|
break;
|
|
|
|
// 16 - Play back capture
|
|
case 16:
|
|
// uint8: SD response code
|
|
sio->response.sd.status = read_8(gpx);
|
|
VERBOSE( fprintf(gpx->log, "Play back captured file: %s" EOL,
|
|
get_sd_status(sio->response.sd.status)) );
|
|
break;
|
|
|
|
// 18 - Get next filename
|
|
case 18:
|
|
// uint8: SD Response code.
|
|
sio->response.sd.status = read_8(gpx);
|
|
/* 1+N bytes: Name of the next file, in ASCII, terminated with a null character.
|
|
If the operation was unsuccessful, this will be a null character */
|
|
strncpy0(sio->response.sd.filename, gpx->buffer.ptr, 65);
|
|
VERBOSE( fprintf(gpx->log, "Get next filename: '%s' %s" EOL,
|
|
sio->response.sd.filename,
|
|
get_sd_status(sio->response.sd.status)) );
|
|
break;
|
|
|
|
// 20 - Get build name
|
|
case 20:
|
|
// 1+N bytes: A null terminated string representing the filename of the current build.
|
|
strncpy0(sio->response.sd.filename, gpx->buffer.ptr, 65);
|
|
VERBOSE( fprintf(gpx->log, "Get build name: '%s'" EOL, sio->response.sd.filename) );
|
|
break;
|
|
|
|
// 21 - Get extended position
|
|
case 21:
|
|
// int32: X position, in steps
|
|
sio->response.position.x = read_32(gpx);
|
|
|
|
// int32: Y position, in steps
|
|
sio->response.position.y = read_32(gpx);
|
|
|
|
// int32: Z position, in steps
|
|
sio->response.position.z = read_32(gpx);
|
|
|
|
// int32: A position, in steps
|
|
sio->response.position.a = read_32(gpx);
|
|
|
|
// int32: B position, in steps
|
|
sio->response.position.b = read_32(gpx);
|
|
|
|
// uint16: bitfield corresponding to the endstop status:
|
|
sio->response.position.endstop.bitfield = read_16(gpx);
|
|
|
|
if(gpx->flag.verboseMode && gpx->flag.logMessages) {
|
|
fputs("Current position" EOL, gpx->log);
|
|
fprintf(gpx->log, "X = %0.2fmm%s%s" EOL,
|
|
(double)sio->response.position.x / gpx->machine.x.steps_per_mm,
|
|
sio->response.position.endstop.flag.xMax ? ", at max endstop" : "",
|
|
sio->response.position.endstop.flag.xMin ? ", at min endstop" : "");
|
|
fprintf(gpx->log, "Y = %0.2fmm%s%s" EOL,
|
|
(double)sio->response.position.y / gpx->machine.y.steps_per_mm,
|
|
sio->response.position.endstop.flag.yMax ? ", at max endstop" : "",
|
|
sio->response.position.endstop.flag.yMin ? ", at min endstop" : "");
|
|
fprintf(gpx->log, "Z = %0.2fmm%s%s" EOL,
|
|
(double)sio->response.position.z / gpx->machine.z.steps_per_mm,
|
|
sio->response.position.endstop.flag.zMax ? ", at max endstop" : "",
|
|
sio->response.position.endstop.flag.zMin ? ", at min endstop" : "");
|
|
fprintf(gpx->log, "A = %0.2fmm%s%s" EOL,
|
|
(double)sio->response.position.a / gpx->machine.a.steps_per_mm,
|
|
sio->response.position.endstop.flag.aMax ? ", at max endstop" : "",
|
|
sio->response.position.endstop.flag.aMin ? ", at min endstop" : "");
|
|
fprintf(gpx->log, "B = %0.2fmm%s%s" EOL,
|
|
(double)sio->response.position.b / gpx->machine.b.steps_per_mm,
|
|
sio->response.position.endstop.flag.bMax ? ", at max endstop" : "",
|
|
sio->response.position.endstop.flag.bMin ? ", at min endstop" : "");
|
|
}
|
|
break;
|
|
|
|
// 22 - Extended stop
|
|
case 22:
|
|
// int8: 0 (reserved for future use)
|
|
read_8(gpx);
|
|
VERBOSE( fputs("Build stopped" EOL, gpx->log) );
|
|
break;
|
|
|
|
// 23 - Get motherboard status
|
|
case 23:
|
|
// uint8: bitfield containing status information
|
|
sio->response.motherboard.bitfield = read_8(gpx);
|
|
if(gpx->flag.verboseMode && gpx->flag.logMessages) {
|
|
fputs("Motherboard status" EOL, gpx->log);
|
|
if(sio->response.motherboard.flag.preheat) fputs("Onboard preheat active" EOL, gpx->log);
|
|
if(sio->response.motherboard.flag.manualMode) fputs("Manual move active" EOL, gpx->log);
|
|
if(sio->response.motherboard.flag.onboardScript) fputs("Running onboard script" EOL, gpx->log);
|
|
if(sio->response.motherboard.flag.onboardProcess) fputs("Running onboard process" EOL, gpx->log);
|
|
if(sio->response.motherboard.flag.waitForButton) fputs("Waiting for buttons press" EOL, gpx->log);
|
|
if(sio->response.motherboard.flag.buildCancelling) fputs("Build cancelling" EOL, gpx->log);
|
|
if(sio->response.motherboard.flag.heatShutdown) fputs("Heaters were shutdown after 30 minutes of inactivity" EOL, gpx->log);
|
|
if(sio->response.motherboard.flag.powerError) fputs("Error detected in system power" EOL, gpx->log);
|
|
}
|
|
break;
|
|
|
|
// 24 - Get build statistics
|
|
case 24:
|
|
// uint8 : Build status
|
|
sio->response.build.status = read_8(gpx);
|
|
|
|
// uint8 : Hours elapsed on print
|
|
sio->response.build.hours = read_8(gpx);
|
|
|
|
// uint8 : Minutes elapsed on print (add hours for total time)
|
|
sio->response.build.minutes = read_8(gpx);
|
|
|
|
// uint32: Line number (number of commands processed)
|
|
sio->response.build.lineNumber = read_32(gpx);
|
|
|
|
// uint32: Reserved for future use
|
|
read_32(gpx);
|
|
|
|
VERBOSE( fprintf(gpx->log, "(line %u) Build status: %s, %u hours, %u minutes" EOL,
|
|
sio->response.build.lineNumber,
|
|
get_build_status(sio->response.build.status),
|
|
sio->response.build.hours,
|
|
sio->response.build.minutes) );
|
|
break;
|
|
|
|
// 27 - Get advanced version number
|
|
case 27:
|
|
// uint16_t Firmware version
|
|
sio->response.firmware.version = read_16(gpx);
|
|
|
|
// uint16_t Internal version
|
|
read_16(gpx);
|
|
|
|
// uint8_t Software variant (0x01 MBI Official, 0x80 Sailfish)
|
|
sio->response.firmware.variant = read_8(gpx);
|
|
|
|
// uint8_t Reserved for future use
|
|
read_8(gpx);
|
|
|
|
// uint16_t Reserved for future use
|
|
read_16(gpx);
|
|
|
|
if(gpx->flag.verboseMode && gpx->flag.logMessages) {
|
|
char *varient = "Unknown";
|
|
switch(sio->response.firmware.variant) {
|
|
case 0x01:
|
|
varient = "Makerbot";
|
|
break;
|
|
case 0x80:
|
|
varient = "Sailfish";
|
|
break;
|
|
}
|
|
fprintf(gpx->log, "%s firmware v%u.%u" EOL, varient, sio->response.firmware.version / 100, sio->response.firmware.version % 100);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 02 - Get available buffer size
|
|
|
|
char buffer_size_query[] = {
|
|
0xD5, // start byte
|
|
1, // length
|
|
2, // query command
|
|
0 // crc
|
|
};
|
|
|
|
static int port_handler(Gpx *gpx, Sio *sio, char *buffer, size_t length)
|
|
{
|
|
int rval = SUCCESS;
|
|
if(length) {
|
|
ssize_t bytes;
|
|
int retry_count = 0;
|
|
do {
|
|
// send the packet
|
|
if((bytes = write(sio->port, buffer, length)) == -1) {
|
|
return errno;
|
|
}
|
|
else if(bytes != length) {
|
|
return ESIOWRITE;
|
|
}
|
|
sio->bytes_out += length;
|
|
|
|
if(sio->bytes_in) {
|
|
// recieve the response
|
|
if((bytes = read(sio->port, gpx->buffer.in, 2)) == -1) {
|
|
return errno;
|
|
}
|
|
else if(bytes != 2) {
|
|
return ESIOREAD;
|
|
}
|
|
// invalid start byte
|
|
if(gpx->buffer.in[0] != 0xD5) {
|
|
return ESIOFRAME;
|
|
}
|
|
}
|
|
else {
|
|
// first read
|
|
for(;;) {
|
|
// read start byte
|
|
if((bytes = read(sio->port, gpx->buffer.in, 1)) == -1) {
|
|
return errno;
|
|
}
|
|
else if(bytes != 1) {
|
|
return ESIOREAD;
|
|
}
|
|
// loop until we get a valid start byte
|
|
if(gpx->buffer.in[0] == 0xD5) break;
|
|
}
|
|
// read length
|
|
if((bytes = read(sio->port, gpx->buffer.in + 1, 1)) == -1) {
|
|
return errno;
|
|
}
|
|
else if(bytes != 1) {
|
|
return ESIOREAD;
|
|
}
|
|
}
|
|
size_t payload_length = gpx->buffer.in[1];
|
|
// recieve payload
|
|
if((bytes = read(sio->port, gpx->buffer.in + 2, payload_length + 1)) == -1) {
|
|
return errno;
|
|
}
|
|
else if(bytes != payload_length + 1) {
|
|
return ESIOREAD;
|
|
}
|
|
// check CRC
|
|
unsigned crc = (unsigned char)gpx->buffer.in[2 + payload_length];
|
|
if(crc != calculate_crc((unsigned char*)gpx->buffer.in + 2, payload_length)) {
|
|
fprintf(gpx->log, "(retry %u) Input CRC mismatch: packet discarded" EOL, retry_count);
|
|
rval = ESIOCRC;
|
|
goto L_RETRY;
|
|
}
|
|
// check response code
|
|
rval = gpx->buffer.in[2];
|
|
switch((unsigned)gpx->buffer.in[2]) {
|
|
// 0x80 - Generic Packet error, packet discarded (retry)
|
|
case 0x80:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Generic Packet error: packet discarded" EOL, retry_count) );
|
|
break;
|
|
|
|
// 0x81 - Success
|
|
case 0x81: {
|
|
unsigned command = (unsigned)buffer[COMMAND_OFFSET];
|
|
if ((command & 0x80) == 0) {
|
|
read_query_response(gpx, sio, command, buffer);
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
// 0x82 - Action buffer overflow, entire packet discarded
|
|
case 0x82:
|
|
do {
|
|
// wait for 1/10 seconds
|
|
usleep(100000);
|
|
// query buffer size
|
|
buffer_size_query[3] = calculate_crc((unsigned char *)buffer_size_query + 2, 1);
|
|
CALL( port_handler(gpx, sio, buffer_size_query, 4) );
|
|
// loop until buffer has space for the next command
|
|
} while(sio->response.bufferSize < length);
|
|
break;
|
|
|
|
// 0x83 - CRC mismatch, packet discarded. (retry)
|
|
case 0x83:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Output CRC mismatch: packet discarded" EOL, retry_count) );
|
|
break;
|
|
|
|
// 0x84 - Query packet too big, packet discarded
|
|
case 0x84:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Query packet too big: packet discarded" EOL, retry_count) );
|
|
goto L_ABORT;
|
|
|
|
// 0x85 - Command not supported/recognized
|
|
case 0x85:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Command not supported or recognized" EOL, retry_count) );
|
|
goto L_ABORT;
|
|
|
|
// 0x87 - Downstream timeout
|
|
case 0x87:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Downstream timeout" EOL, retry_count) );
|
|
goto L_ABORT;
|
|
|
|
// 0x88 - Tool lock timeout (retry)
|
|
case 0x88:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Tool lock timeout" EOL, retry_count) );
|
|
break;
|
|
|
|
// 0x89 - Cancel build (retry)
|
|
case 0x89:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Cancel build" EOL, retry_count) );
|
|
break;
|
|
|
|
// 0x8A - Bot is building from SD
|
|
case 0x8A:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Bot is Building from SD card" EOL, retry_count) );
|
|
goto L_ABORT;
|
|
|
|
// 0x8B - Bot is shutdown due to overheating
|
|
case 0x8B:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Bot is shutdown due to overheating" EOL, retry_count) );
|
|
goto L_ABORT;
|
|
|
|
// 0x8C - Packet timeout error, packet discarded (retry)
|
|
case 0x8C:
|
|
VERBOSE( fprintf(gpx->log, "(retry %u) Packet timeout error: packet discarded" EOL, retry_count) );
|
|
break;
|
|
}
|
|
L_RETRY:
|
|
// wait for 2 seconds
|
|
sleep(2);
|
|
} while(++retry_count < 5);
|
|
}
|
|
|
|
L_ABORT:
|
|
return rval;
|
|
}
|
|
|
|
int gpx_convert_and_send(Gpx *gpx, FILE *file_in, int sio_port)
|
|
{
|
|
int i, rval;
|
|
Sio sio;
|
|
sio.in = stdin;
|
|
sio.port = -1;
|
|
sio.bytes_out = 0;
|
|
sio.bytes_in = 0;
|
|
int logMessages = gpx->flag.logMessages;
|
|
|
|
if(file_in && file_in != stdin) {
|
|
// Multi-pass
|
|
sio.in = file_in;
|
|
i = 0;
|
|
gpx->flag.logMessages = 0;
|
|
gpx->flag.framingEnabled = 0;
|
|
gpx->callbackHandler = NULL;
|
|
gpx->callbackData = NULL;
|
|
}
|
|
else {
|
|
// Single-pass
|
|
i = 1;
|
|
gpx->flag.framingEnabled = 1;
|
|
gpx->callbackHandler = (int (*)(Gpx*, void*, char*, size_t))port_handler;;
|
|
gpx->callbackData = &sio;
|
|
}
|
|
|
|
if(sio_port > 2) {
|
|
sio.port = sio_port;
|
|
}
|
|
|
|
for(;;) {
|
|
int overflow = 0;
|
|
|
|
while(fgets(gpx->buffer.in, BUFFER_MAX, sio.in) != NULL) {
|
|
// detect input buffer overflow and ignore overflow input
|
|
if(overflow) {
|
|
if(strlen(gpx->buffer.in) != BUFFER_MAX - 1) {
|
|
overflow = 0;
|
|
}
|
|
continue;
|
|
}
|
|
if(strlen(gpx->buffer.in) == BUFFER_MAX - 1) {
|
|
overflow = 1;
|
|
SHOW( fprintf(gpx->log, "(line %u) Buffer overflow: input exceeds %u character limit, remaining characters in line will be ignored" EOL, gpx->lineNumber, BUFFER_MAX) );
|
|
}
|
|
|
|
rval = gpx_convert_line(gpx, gpx->buffer.in);
|
|
// normal exit
|
|
if(rval > 0) break;
|
|
// error
|
|
if(rval < 0) return rval;
|
|
}
|
|
|
|
if(program_is_running()) {
|
|
end_program();
|
|
CALL( set_build_progress(gpx, 100) );
|
|
CALL( end_build(gpx) );
|
|
}
|
|
|
|
CALL( set_steppers(gpx, AXES_BIT_MASK, 0) );
|
|
|
|
gpx->total.length = gpx->accumulated.a + gpx->accumulated.b;
|
|
gpx->total.time = gpx->accumulated.time;
|
|
gpx->total.bytes = gpx->accumulated.bytes;
|
|
|
|
if(++i > 1) break;
|
|
|
|
// rewind for second pass
|
|
fseek(sio.in, 0L, SEEK_SET);
|
|
gpx_initialize(gpx, 0);
|
|
|
|
gpx->flag.logMessages = 1;
|
|
gpx->flag.framingEnabled = 1;
|
|
gpx->callbackHandler = (int (*)(Gpx*, void*, char*, size_t))port_handler;;
|
|
gpx->callbackData = &sio;
|
|
}
|
|
gpx->flag.logMessages = logMessages;;
|
|
return SUCCESS;
|
|
}
|
|
|
|
void gpx_end_convert(Gpx *gpx)
|
|
{
|
|
if(gpx->flag.verboseMode && gpx->flag.logMessages) {
|
|
long seconds = round(gpx->accumulated.time);
|
|
long minutes = seconds / 60;
|
|
long hours = minutes / 60;
|
|
minutes %= 60;
|
|
seconds %= 60;
|
|
fprintf(gpx->log, "Extrusion length: %#0.3f metres" EOL, round(gpx->accumulated.a + gpx->accumulated.b) / 1000);
|
|
fputs("Estimated print time: ", gpx->log);
|
|
if(hours) fprintf(gpx->log, "%lu hours ", hours);
|
|
if(minutes) fprintf(gpx->log, "%lu minutes ", minutes);
|
|
fprintf(gpx->log, "%lu seconds" EOL, seconds);
|
|
fprintf(gpx->log, "X3G output filesize: %lu bytes" EOL, gpx->accumulated.bytes);
|
|
}
|
|
}
|
|
|
|
// EEPROM
|
|
|
|
static int write_eeprom_8(Gpx *gpx, Sio *sio, unsigned address, unsigned char value)
|
|
{
|
|
int rval;
|
|
gpx->buffer.ptr = sio->response.eeprom.buffer;
|
|
write_8(gpx, value);
|
|
CALL( write_eeprom(gpx, address, sio->response.eeprom.buffer, 1) );
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int read_eeprom_8(Gpx *gpx, Sio *sio, unsigned address, unsigned char *value)
|
|
{
|
|
int rval;
|
|
CALL( read_eeprom(gpx, address, 1) );
|
|
gpx->buffer.ptr = sio->response.eeprom.buffer;
|
|
*value = read_8(gpx);
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int write_eeprom_32(Gpx *gpx, Sio *sio, unsigned address, unsigned value)
|
|
{
|
|
int rval;
|
|
gpx->buffer.ptr = sio->response.eeprom.buffer;
|
|
write_32(gpx, value);
|
|
CALL( write_eeprom(gpx, address, sio->response.eeprom.buffer, 4) );
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int read_eeprom_32(Gpx *gpx, Sio *sio, unsigned address, unsigned *value)
|
|
{
|
|
int rval;
|
|
CALL( read_eeprom(gpx, address, 4) );
|
|
gpx->buffer.ptr = sio->response.eeprom.buffer;
|
|
*value = read_32(gpx);
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int write_eeprom_float(Gpx *gpx, Sio *sio, unsigned address, float value)
|
|
{
|
|
int rval;
|
|
gpx->buffer.ptr = sio->response.eeprom.buffer;
|
|
write_float(gpx, value);
|
|
CALL( write_eeprom(gpx, address, sio->response.eeprom.buffer, 4) );
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int read_eeprom_float(Gpx *gpx, Sio *sio, unsigned address, float *value)
|
|
{
|
|
int rval;
|
|
CALL( read_eeprom(gpx, address, 4) );
|
|
gpx->buffer.ptr = sio->response.eeprom.buffer;
|
|
*value = read_float(gpx);
|
|
return SUCCESS;
|
|
}
|
|
|
|
static int eeprom_set_property(Gpx *gpx, const char* section, const char* property, char* value)
|
|
{
|
|
int rval;
|
|
unsigned int address = (unsigned int)strtol(property, NULL, 0);
|
|
if(SECTION_IS("byte")) {
|
|
unsigned char b = (unsigned char)strtol(value, NULL, 0);
|
|
CALL( write_eeprom_8(gpx, (Sio *)gpx->callbackData, address, b) );
|
|
}
|
|
else if(SECTION_IS("integer")) {
|
|
unsigned int i = (unsigned int)strtol(value, NULL, 0);
|
|
CALL( write_eeprom_32(gpx, (Sio *)gpx->callbackData, address, i) );
|
|
}
|
|
else if(SECTION_IS("hex") || SECTION_IS("hexadecimal")) {
|
|
unsigned int h = (unsigned int)strtol(value, NULL, 16);
|
|
unsigned length = (unsigned)strlen(value) / 2;
|
|
if(length > 4) length = 4;
|
|
gpx->buffer.ptr = ((Sio *)gpx->callbackData)->response.eeprom.buffer;
|
|
write_32(gpx, h);
|
|
CALL( write_eeprom(gpx, address, ((Sio *)gpx->callbackData)->response.eeprom.buffer, length) );
|
|
}
|
|
else if(SECTION_IS("float")) {
|
|
float f = strtof(value, NULL);
|
|
CALL( write_eeprom_float(gpx, (Sio *)gpx->callbackData, address, f) );
|
|
}
|
|
else if(SECTION_IS("string")) {
|
|
unsigned length = (unsigned)strlen(value);
|
|
CALL( write_eeprom(gpx, address, value, length) );
|
|
}
|
|
else {
|
|
SHOW( fprintf(gpx->log, "(line %u) Configuration error: unrecognised section [%s]" EOL, gpx->lineNumber, section) );
|
|
return gpx->lineNumber;
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
|
|
int eeprom_load_config(Gpx *gpx, const char *filename)
|
|
{
|
|
return ini_parse(gpx, filename, eeprom_set_property);
|
|
}
|
|
|