/* $FabBSD$ */
/*
* Copyright (c) 2009-2010 Hypertriton, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* RS274/NGC control code interpreter.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include "ngc.h"
#include "pathnames.h"
int ngc_verbose = 1;
int ngc_warnings = 0;
int ngc_simulate = 0;
struct ngc_progq ngc_progs;
ngc_real_t ngc_params[NGC_NPARAMS];
int ngc_csys_bases[10] = { 0, 5221, 5241, 5261, 5281,
5301, 5321, 5341, 5361, 5381 };
ngc_real_t ngc_csys_offset[6];
#include "addresses.h"
#include "modal.h"
ngc_vec_t ngc_pos;
ngc_real_t ngc_linscale = 400.0; /* mm (XYZ) to steps */
ngc_real_t ngc_rotscale = 100.0; /* degs (ABC) to steps */
ngc_real_t ngc_feedscale = 100.0; /* mm/min to steps/sec */
const int ngc_naxes = 6;
const char *ngc_axis_words = "XYZABC";
const enum ngc_axis_type ngc_axis_types[6] = {
NGC_LINEAR, NGC_LINEAR, NGC_LINEAR,
NGC_ROTARY, NGC_ROTARY, NGC_ROTARY
};
enum ngc_interp_mode ngc_mode = NGC_MILLING;
enum ngc_distance_mode ngc_distmode = NGC_INCREMENTAL;
enum ngc_unit_system ngc_units = NGC_METRIC;
enum ngc_feedrate_mode ngc_feedmode = NGC_UNITS_PER_MIN;
ngc_real_t ngc_feedfast = 100.0; /* mm/min */
ngc_real_t ngc_feedrate = 10.0; /* mm/min or inverse time */
enum ngc_plane_mode ngc_planesel = NGC_PLANE_XY;
enum ngc_cro_mode ngc_cromode = NGC_CUTTER_OFFS_OFF;
int ngc_tlo = 0;
enum ngc_pathctl_mode ngc_pathctl = NGC_EXACT_PATH;
enum ngc_retract_mode ngc_retract = NGC_RETRACT_PREVIOUS;
/* G-commands in effect */
int ngc_Gmodes[NGC_G_LAST] = {
-1,
80, /* 1. G80: Cancel motion */
17, /* 2. G17: X-Y plane */
91, /* 3. G91: Incremental */
-1, /* 4 */
94, /* 5. G95: Units/minute mode */
21, /* 6. G21: Millimeters input */
40, /* 7. G40: Cutter radius compensation off */
49, /* 8. G49: Tool length offset off */
-1, /* 9 */
98, /* 10. G98: Retract to previous position */
-1, /* 11 */
54, /* 12. G54: Use coordinate system #1 */
61 /* 13. G61: Exact path mode */
};
/* M-commands in effect */
int ngc_Mmodes[NGC_G_LAST] = {
-1, /* 0 */
-1, /* 1 */
-1, /* 2 */
-1, /* 3 */
-1, /* 4. Stopping */
-1, /* 5. Set I/O point */
-1, /* 6. Tool changer commands */
-1, /* 7. Spindle commands */
-1, /* 8. Coolant commands */
-1, /* 9. Panel switch overrides */
-1, /* 10 */
-1, /* 11 */
-1, /* 12 */
-1, /* 13 */
};
int ngc_spinspeed = 0;
int ngc_tool = 0;
int ngc_csys = 1;
/*
* Parse a RS274/NGC program line (or "block") into a list of ngc_words.
* Verify that the addresses and values are appropriate to the current
* interpreter mode.
*/
static int
ngc_parse_line(struct ngc_prog *prog, int nLine, char *pLine)
{
struct ngc_block *blk;
struct ngc_word *word = NULL;
char *s, *line = pLine , *ep;
int code, nWord = 0;
if ((blk = realloc(prog->blks, (prog->nBlks+1)*sizeof(struct ngc_block)))
== NULL) {
cnc_set_error("Out of memory");
return (-1);
}
prog->blks = blk;
blk = &prog->blks[prog->nBlks++];
blk->n = 0;
blk->line = nLine;
blk->flags = 0;
blk->prog = prog;
TAILQ_INIT(&blk->words);
if (line[0] == '/') {
blk->flags |= NGC_BLOCK_OPTIONAL_DELETE;
line++;
}
for (s = &line[0]; *s != '\0'; ) {
const struct ngc_address *addr;
int extn;
if (isspace(*s)) { /* Ignore spaces */
s++;
continue;
}
if (!isalpha(*s) || s[1] == '\0') {
goto syntax;
}
code = toupper(*s);
if (code < 'A' || code > 'Z') {
goto syntax;
}
addr = &ngc_addr[code-0x41];
if (addr->type == NGC_UNUSED) {
cnc_set_error("Invalid address for %s mode: %c",
ngc_modenames[ngc_mode], code);
goto fail;
}
if ((word = malloc(sizeof(struct ngc_word))) == NULL) {
cnc_set_error("Out of memory");
goto fail;
}
word->type = code;
switch (addr->type) {
case NGC_INTEGER:
word->data.i = (int)strtoul(&s[1], &ep, 10);
/* Translate [GM]x.y -> [GM]x*1000+y */
if (*ep == '.' &&
((code == 'G') || (code == 'M'))) {
if (!isdigit(ep[1])) {
goto syntax;
}
extn = (int)strtoul(&ep[1], NULL, 10);
if (extn < 0 || extn > 1000) {
goto syntax;
}
word->data.i = word->data.i*1000 + extn;
}
break;
case NGC_SIGNED_REAL:
word->data.f = (ngc_real_t)strtod(&s[1], &ep);
break;
case NGC_UNSIGNED_REAL:
if (s[1] == '-') {
cnc_set_error("%c values are unsigned",
code);
goto fail;
}
word->data.f = (ngc_real_t)strtod(&s[1], &ep);
break;
default:
break;
}
/* Validate program and block numbers. */
switch (code) {
case 'N':
if (nWord > 0) {
goto syntax;
}
if (word->data.i == 0) {
cnc_set_error("Reserved block#: 0");
goto fail;
}
blk->n = word->data.i;
/*
* TODO: Warn of duplicates and non-incremental
* sequences.
*/
break;
case ':': /* ISO format */
case 'O': /* EIA format */
if (nWord > 0) {
goto syntax;
}
if (word->data.i == 0) {
cnc_set_error("Reserved program#: 0");
goto fail;
}
if (prog->name != 0) {
cnc_set_error("Duplicate `%c' tag", code);
goto fail;
}
prog->name = word->data.i;
/*
* TODO: Warn of duplicates.
*/
break;
}
TAILQ_INSERT_TAIL(&blk->words, word, words);
if (*ep == '\0') {
break;
}
s = &ep[1];
nWord++;
}
return (0);
syntax:
cnc_set_error("Syntax error near \"%s\"", pLine);
fail:
if (word != NULL) { free(word); }
free(blk);
return (-1);
}
/* Create a new ngc_program. */
struct ngc_prog *
ngc_prog_new(int name, const char *path)
{
struct ngc_prog *prog;
if ((prog = malloc(sizeof(struct ngc_prog))) == NULL) {
cnc_set_error("Out of memory");
return (NULL);
}
if (strcmp(path, "-") == 0) {
strlcpy(prog->path, "(standard input)", sizeof(prog->path));
} else {
strlcpy(prog->path, path, sizeof(prog->path));
}
prog->name = name;
prog->nRefs = 0;
prog->nCalls = 0;
prog->blks = NULL;
prog->nBlks = 0;
TAILQ_INSERT_TAIL(&ngc_progs, prog, progs);
return (prog);
}
/* Destroy a ngc_program assuming it is not referenced. */
void
ngc_prog_del(struct ngc_prog *prog)
{
TAILQ_REMOVE(&ngc_progs, prog, progs);
free(prog->blks);
free(prog);
}
/*
* Parse the RS274/NGC program(s) contained in the specified file. Programs
* are separated by '%' and identified by 'O' or ':' directives.
*/
static int
ngc_parse_file(const char *path)
{
struct ngc_prog *prog = NULL;
char lineBuf[NGC_LINE_MAX+1], *pLine, *line;
FILE *f;
size_t len;
int nLine = 1, nProg = 0;
int inComment = 0;
char *s, *end;
if (strcmp(path, "-") == 0) {
f = fdopen(STDIN_FILENO, "r");
} else {
f = fopen(path, "r");
}
if (f == NULL) {
cnc_set_error(" %s", strerror(errno));
return (-1);
}
clearerr(f);
while ((pLine = fgetln(f, &len))) {
if (len >= NGC_LINE_MAX) {
cnc_set_error("Line too long");
goto fail;
}
memcpy(lineBuf, pLine, len);
ngc_nltonul(lineBuf, len);
/* Ignore leading whitespace and blank lines */
for (line = &lineBuf[0]; isspace(*line); line++)
;;
if (line[0] == '\0')
goto next_line;
/* Look for the "%" demarcation sign */
if (line[0] == '%') {
if (prog != NULL) {
prog = NULL;
nProg++;
}
if ((prog = ngc_prog_new(0, path)) == NULL) {
goto fail;
}
goto next_line;
} else if (prog == NULL) {
/* Begin program without "%" */
if ((prog = ngc_prog_new(0, path)) == NULL)
goto fail;
}
/* Strip comments and whitespace */
if (inComment) {
if ((s = strchr(line, ')')) == NULL) {
goto next_line;
}
inComment = 0;
line = &s[1];
} else {
s = &line[0];
len = strlen(line);
end = NULL;
while ((s = strchr(line, '(')) != NULL) {
if ((end = strchr(s, ')')) == NULL) {
inComment = 1; /* Multiline */
goto next_line;
}
memmove(s, end+1, len-(end-s));
}
}
for (;;) {
len = strlen(line);
if (isspace(line[len-1]))
line[len-1] = '\0';
else
break;
}
if (line[0] == '\0')
goto next_line;
if (prog != NULL) {
if (ngc_parse_line(prog, nLine, line) == -1)
goto fail;
}
next_line:
nLine++;
}
if (ferror(f)) {
cnc_set_error("Read error");
goto fail;
}
fclose(f);
return (0);
fail:
cnc_set_error("%d: %s", nLine, cnc_get_error());
fclose(f);
return (-1);
}
static __dead void
printusage(void)
{
extern char *__progname;
printf("Usage: %s [-lmnvW] [-t toolfile] [file] ...]\n",
__progname);
exit(1);
}
/* Issue a warning if warnings are enabled. */
void
warning(const char *fmt, ...)
{
va_list args;
if (!ngc_warnings)
return;
va_start(args, fmt);
printf("WARNING: ");
vprintf(fmt, args);
va_end(args);
}
/* Issue a message in verbose mode. */
void
verbose(const char *fmt, ...)
{
va_list args;
if (!ngc_verbose)
return;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
}
/* Restore the RS274/NGC persistent parameters from file. */
static void
loadparams(const char *path)
{
char lineBuf[128], *pLine, *line, nLine = 1;
char *key, *val, *s, *ep;
const char *errstr = NULL;
int param;
size_t len;
FILE *f;
if ((f = fopen(path, "r")) == NULL) {
err(1, "%s", path);
}
clearerr(f);
while ((pLine = fgetln(f, &len))) {
if (len > 128) { len = 128; }
memcpy(lineBuf, pLine, len);
ngc_nltonul(lineBuf, len);
/* Ignore comments and whitespace */
for (line = &lineBuf[0]; isspace(*line); line++)
;;
if ((s = strchr(line, '#')) != NULL)
*s = '\0';
if (line[0] == '\0')
goto next_line;
/* Read entry. */
key = strsep(&line, " \t");
val = strsep(&line, " \t");
if (key == NULL || val == NULL)
errx(1, "%s:%d: Syntax error", path, nLine);
param = (int)strtonum(key, 1, 5400, &errstr);
if (errstr != NULL) {
errx(1, "%s:%d: %s", path, nLine, errstr);
}
ngc_params[param] = (ngc_real_t)strtod(val, &ep);
if (*ep != '\0')
errx(1, "%s:%d: Syntax error near \"%s\"", path,
nLine, ep);
next_line:
nLine++;
}
fclose(f);
/* We use a separate variable for coordinate system index. */
ngc_csys = (int)ngc_params[5220];
if (ngc_csys < 1 || ngc_csys >= NGC_NWORKOFFS)
errx(1, "%s: illegal work offset %d (#5220)", path, ngc_csys);
if (verbose) {
verbose("Current G28 home: %.4f,%.4f,%.4f\n",
ngc_params[5161], ngc_params[5162], ngc_params[5163]);
verbose("Current G30 home: %.4f,%.4f,%.4f\n",
ngc_params[5181], ngc_params[5182], ngc_params[5183]);
verbose("Current G92 offset: %.4f,%.4f,%.4f\n",
ngc_params[5211], ngc_params[5212], ngc_params[5213]);
verbose("Selected Coordinate System: %d\n", ngc_csys);
verbose("Work offset #1: %.4f,%.4f,%.4f\n",
ngc_params[5221], ngc_params[5222], ngc_params[5223]);
}
}
/*
* Fetch the current integer position from the kernel and update ngc_pos
* per the effective scaling factors. ngc can handle a maximum of 6 axes.
*/
static void
loadposition(void)
{
cnc_vec_t cPos;
int i;
cnc_vec_zero(&cPos);
if (cncpos(&cPos) == -1) {
errx(1, "cncpos: %s", strerror(errno));
}
if (CNC_NAXES != NGC_NAXES) {
warning("Kernel compiled for %d-axis support (RS274NGC "
"supports %d)\n", CNC_NAXES, NGC_NAXES);
}
for (i = 0; i < NGC_NAXES; i++)
ngc_pos.v[i] = 0.0;
for (i = 0; i < MIN(NGC_NAXES,CNC_NAXES); i++)
ngc_pos.v[i] = (ngc_real_t)cPos.v[i] /
((i>=3) ? ngc_linscale : ngc_rotscale);
if (ngc_verbose) {
char s[64];
ngc_vec_print(s, sizeof(s), &ngc_pos);
verbose("Current position: %s\n", s);
}
}
int
main(int argc, char *argv[])
{
char *toolfile = NULL;
struct ngc_prog *prog, *progNext;
int i, ch;
TAILQ_INIT(&ngc_progs);
/* Load the persistent numerical parameters. */
for (i = 1; i < NGC_NPARAMS; i++) {
ngc_params[i] = 0.0;
}
for (i = 0; i < 6; i++) {
ngc_csys_offset[i] = 0.0;
}
loadparams(_PATH_NGC_PARAMS);
while ((ch = getopt(argc, argv, "lmt:nvW?h")) != -1) {
switch (ch) {
case 'l':
ngc_mode = NGC_TURNING;
break;
case 'm':
break;
case 't':
if ((toolfile = strdup(optarg)) == NULL) {
errx(1, "Out of memory");
}
break;
case 'n':
ngc_simulate = 1;
break;
case 'v':
ngc_verbose = 1;
break;
case 'W':
ngc_warnings = 1;
break;
default:
printusage();
}
}
switch (ngc_mode) {
case NGC_MILLING:
ngc_addr = ngc_maddr;
break;
case NGC_TURNING:
ngc_addr = ngc_taddr;
break;
default:
break;
}
/* Parse input program code into ngc_progs. */
argv += optind;
argc -= optind;
for (i = 0; i < argc; i++) {
if (ngc_parse_file(argv[i]) == -1)
errx(1, "%s:%s", argv[i], cnc_get_error());
}
for (prog = TAILQ_FIRST(&ngc_progs);
prog != TAILQ_END(&ngc_progs);
prog = progNext) {
progNext = TAILQ_NEXT(prog, progs);
if (prog->nBlks == 0)
ngc_prog_del(prog);
}
if (TAILQ_EMPTY(&ngc_progs)) {
errx(1, "no program to execute");
}
/* Query the current position. */
loadposition();
/* Execute the main program. */
TAILQ_FOREACH(prog, &ngc_progs, progs) {
if (prog->name == 0)
break;
}
if (prog == NULL) {
errx(1, "program main not found\n");
}
verbose("main: %d blocks\n", prog->nBlks);
for (i = 0; i < prog->nBlks; i++) {
struct ngc_block *blk = &prog->blks[i];
struct ngc_word *w;
if (verbose) {
TAILQ_FOREACH(w, &blk->words, words) {
const struct ngc_address *addr =
&ngc_addr[w->type-0x41];
verbose("%c", w->type);
switch (addr->type) {
case NGC_INTEGER:
verbose("%d ", w->data.i);
break;
case NGC_SIGNED_REAL:
case NGC_UNSIGNED_REAL:
verbose("%f ", w->data.f);
break;
default:
break;
}
}
verbose("\n");
}
if (ngc_block_exec(prog, i) == -1)
errx(1, "%s:%d: %s", prog->path,
blk->line, cnc_get_error());
}
return (0);
}