#define DEFAULT_TOOL_WIDTH 2.4 #define ZERO_EVERY_TIME 1 // failed experiment. need to remove ifdefs. // TO DO: "install laser" "install spindle" "install 20k spindle" // and refuse to execute commands incompatible with current hardware // my 3018 is actually 270x157mm (previously was 266x154 until two of the endstops broke) // Interesting... MPos seems to be reset to 0 by disconnecting and reconnecting. // didn't expect that. Thought it was reset only by a power-off... // to do - if spindle is on, use g1, otherwise g0 // or use cutto vs goto? // assumes default of $20=0, $21=0, $22=0, $30=20000 // while executing gcode() calls, should check for immediate keyboard input, eg ~ or ! or ? or ^C etc. // cylander: need diameter option as well as radius! (DONE) // need to add a global z step command... /* The $13 Grbl setting only determines the units Grbl sends back to the GUI for status reports (position, offsets, rate) and stored $# parameters. It does not effect how a gcode program is run. Only G20 and G21 alter how the g-code program is interpreted in inches or mm, respectively. These must be set at the start of the g-code program, every time. UGS should refer to G20/21 modal state to determine the units to send to Grbl. Grbl always defaults to G21 mm mode. So, after a power-cycle or reset, it's set back to G21. You can alter this default by programming a startup line by: $N0=G20. This will automatically run and set G20 after a reset or power cycle. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <error.h> #include <errno.h> #include <ctype.h> #include <ncurses.h> #include <unistd.h> #include <fcntl.h> #include <libgen.h> #include <termios.h> #include <sys/stat.h> #include <sys/select.h> #include <sys/types.h> #include <sys/ioctl.h> // This file is a bit of R&D ... it was thrown together from various parts I had // knocking around, so it's far from well structured. Once I've finished experimenting // I'll give it a well-deserved rewrite. // This is a gcode sender - basically a terminal program talking to a serial line, which // is connected to the GRBL controller on a Sainsmart 3018 CNC. However it does a couple // of things extra. // 1) a trivial tweak - the GRBL controller can get in a state where it sends status // updates every few seconds. We display those in a separate window at the top of the // page. // 2) the input line is forced to always be at the foot of the screen, and anything that // comes from the CNC while you are entering a line is displayed in the main window // *above* the input line so that the line you are typing does not get corrupted. // 3) Cursor keys can be used for x/y jogging at any time. // 4) This is the big addition: as well as the G CODE commands accepted by the CNC, // this code also accepts arbitrary commands that were pre-defined in this file. // Those extensions can be of arbitrary complexity - or just short aliases for // harder to remember G CODE commands. // The program is accessed over ssh (Putty, if from windows) with <esc>[n~ selected as // the encoding under Terminal/Keyboard (only needed for curor-key jogging) WINDOW *output, *input, *debugline; // Note: to avoid always having a blank line at the foot of every window, // we do the ugly old fashioned thing of printing '\nline' instead of 'line\n' ... // simple low-level calls static int gcode_errnum = 0; extern void append_error (char *line, int gcode_errnum); extern int gcode (char *format, ...); extern int gcode_sendfile (char *fname); extern int gcode_jog (char *direction, char *quantity, char *units); extern int gcode_default_units (char *units); extern int gcode_probe (void); extern int gcode_display_info (void); extern int gcode_gotoxy (float x, float y); extern int gcode_cuttoxy (float x, float y); extern int gcode_gotoz (float z); extern int gcode_cuttoz (float z); extern int gcode_spindle_on (void); extern int gcode_spindle_off (void); // slightly higher level procedures extern void laser_test(void); extern void laser_focus(void); extern void laser_strength(void); extern void laser_mode(void); extern void cnc_mode(void); extern void cnc_hammer_jig_peck_drill (float dz); extern void cnc_rectangle (float from_x, float from_y, float to_x, float to_y, float step_x, float step_y, int want_dogbones); extern void cnc_pocket (float from_x, float from_y, float from_z, float to_x, float to_y, float to_z, float step_x, float step_y, float step_z, float tool_width, int want_dogbones); // e.g. // pocket // 0,0,-30 // to // 79,28,-32.5 // step // 1.4,1.4,0.5 extern void cnc_cylinder (float radius, float depth); extern void cnc_pitrex_cartridge (void); extern void cnc_pitrex_mailer (void); extern void cnc_hammer_jig (void); // CNC parameters here: /* $$ $# $G $I $N $x=val $Nx=line $J=line $SLP $C $X $H ~ ! ? ctrl-x $$ (view Grbl settings) $# (view # parameters) $G (view parser state) $I (view build info) $N (view startup blocks) $x=value (save Grbl setting) $Nx=line (save startup block) $J=line (Run jogging motion) (Immediate command char 0x85 to cancel queued jogging) $SLP (Enable sleep mode) $RST=$, $RST=#, and $RST=* (Restore Grbl settings and data to defaults) $C (check gcode mode) $X (kill alarm lock) $H (run homing cycle) ~ (cycle start) ! (feed hold) ? (current status) ctrlx (reset Grbl) https://github.com/gnea/grbl/wiki/Grbl-v1.1-Commands#grbl--commands */ typedef enum UNITS { undefined, mm, in } UNITS; UNITS Default_Units = mm; float xy_jog_default = 1.0; float z_jog_default = 1.0; // End of CNC data section. #define Boolean int Boolean expecting_ok = FALSE, status_requested = FALSE; #define LOCAL_ECHO 1 #ifndef MAX_LINE #define MAX_LINE 1024 #endif #define BUF_SIZE 500 #define MAX_SNAME 1000 struct termios ttyOrig; #include <unistd.h> #include <stdarg.h> fd_set inFds; void ttyReset (void) { wrefresh (debugline); wrefresh (output); wrefresh (input); echo (); delwin (output); delwin (input); endwin (); (void) tcsetattr (STDIN_FILENO, TCSANOW, &ttyOrig); // restore // keyboard/stdin fflush (stderr); exit (EXIT_SUCCESS); } static char *ename[] = { /* 0 */ "", /* 1 */ "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", /* 7 */ "E2BIG", "ENOEXEC", "EBADF", "ECHILD", /* 11 */ "EAGAIN/EWOULDBLOCK", "ENOMEM", "EACCES", "EFAULT", /* 15 */ "ENOTBLK", "EBUSY", "EEXIST", "EXDEV", "ENODEV", /* 20 */ "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", "EMFILE", /* 25 */ "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", /* 30 */ "EROFS", "EMLINK", "EPIPE", "EDOM", "ERANGE", /* 35 */ "EDEADLK/EDEADLOCK", "ENAMETOOLONG", "ENOLCK", "ENOSYS", /* 39 */ "ENOTEMPTY", "ELOOP", "", "ENOMSG", "EIDRM", "ECHRNG", /* 45 */ "EL2NSYNC", "EL3HLT", "EL3RST", "ELNRNG", "EUNATCH", /* 50 */ "ENOCSI", "EL2HLT", "EBADE", "EBADR", "EXFULL", "ENOANO", /* 56 */ "EBADRQC", "EBADSLT", "", "EBFONT", "ENOSTR", "ENODATA", /* 62 */ "ETIME", "ENOSR", "ENONET", "ENOPKG", "EREMOTE", /* 67 */ "ENOLINK", "EADV", "ESRMNT", "ECOMM", "EPROTO", /* 72 */ "EMULTIHOP", "EDOTDOT", "EBADMSG", "EOVERFLOW", /* 76 */ "ENOTUNIQ", "EBADFD", "EREMCHG", "ELIBACC", "ELIBBAD", /* 81 */ "ELIBSCN", "ELIBMAX", "ELIBEXEC", "EILSEQ", "ERESTART", /* 86 */ "ESTRPIPE", "EUSERS", "ENOTSOCK", "EDESTADDRREQ", /* 90 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", /* 93 */ "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", /* 95 */ "EOPNOTSUPP/ENOTSUP", "EPFNOSUPPORT", "EAFNOSUPPORT", /* 98 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", /* 102 */ "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS", /* 106 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", /* 110 */ "ETIMEDOUT", "ECONNREFUSED", "EHOSTDOWN", "EHOSTUNREACH", /* 114 */ "EALREADY", "EINPROGRESS", "ESTALE", "EUCLEAN", /* 118 */ "ENOTNAM", "ENAVAIL", "EISNAM", "EREMOTEIO", "EDQUOT", /* 123 */ "ENOMEDIUM", "EMEDIUMTYPE", "ECANCELED", "ENOKEY", /* 127 */ "EKEYEXPIRED", "EKEYREVOKED", "EKEYREJECTED", /* 130 */ "EOWNERDEAD", "ENOTRECOVERABLE", "ERFKILL", "EHWPOISON" }; #define MAX_ENAME 133 __attribute__ ((__noreturn__)) static void terminate (Boolean useExit3) { char *s; /* Dump core if EF_DUMPCORE environment variable is defined and is a nonempty string; otherwise call exit(3) or _exit(2), depending on the value of 'useExit3'. */ s = getenv ("EF_DUMPCORE"); if (s != NULL && *s != '\0') abort (); else if (useExit3) exit (EXIT_FAILURE); else _exit (EXIT_FAILURE); } /* Diagnose 'errno' error by: * outputting a string containing the error name (if available in 'ename' array) corresponding to the value in 'err', along with the corresponding error message from strerror(), and * outputting the caller-supplied error message specified in 'format' and 'ap'. */ static void outputError (Boolean useErr, int err, Boolean flushStdout, const char *format, va_list ap) { char buf[BUF_SIZE], userMsg[BUF_SIZE], errText[BUF_SIZE]; vsnprintf (userMsg, BUF_SIZE, format, ap); if (useErr) snprintf (errText, BUF_SIZE, " [%s %s]", (err > 0 && err <= MAX_ENAME) ? ename[err] : "?UNKNOWN?", strerror (err)); else snprintf (errText, BUF_SIZE, ":"); snprintf (buf, BUF_SIZE, "ERROR%s %s\n", errText, userMsg); #ifdef OLDSTYLE if (flushStdout) fflush (stdout); /* Flush any pending stdout */ fputs (buf, stderr); fflush (stderr); /* In case stderr is not line-buffered */ #else wprintw (output, "\nERROR%s %s", errText, userMsg); wrefresh (output); wrefresh (input); #endif } /* Display error message including 'errno' diagnostic, and terminate the process */ static void errExit (const char *format, ...) { va_list argList; va_start (argList, format); outputError (TRUE, errno, TRUE, format, argList); va_end (argList); terminate (TRUE); } /* Print an error message (without an 'errno' diagnostic) */ static void fatal (const char *format, ...) { va_list argList; va_start (argList, format); outputError (FALSE, 0, TRUE, format, argList); va_end (argList); terminate (TRUE); } int Data_From_CNC_Fd, Data_To_CNC_Fd; int interpret (char *command); Boolean local_command (char *command) { int rc; // contemplating using https://github.com/orangeduck/mpc rather than // rolling my own for once...? rc = interpret (command); if (rc < 0) { // was not able to parse a valid extended command // fprintf(stderr, "Can't\n"); return FALSE; } else if (rc > 0) { // parsed, executed, failed :-( wprintw (output, "\nWon't"); wrefresh (output); wrefresh (input); return FALSE; } else { // wprintw(output, "\nok"); wrefresh(output); wrefresh(input); } return TRUE; } // This is a quick & dirty top-down recursive-descent table-driven parser // that I hacked out of another project. The most generic version of the // parser can support arbitrary languages, but I've cut this down to just // enough to be useful for this project without a lot of extra features // that would not by used here. // Skip down to 'Phrase definitions' for the command language. char *indents (int depth) { static char indent[128]; int i; for (i = 0; i < depth * 2 + 1; i++) indent[i] = ' '; indent[depth * 2 + 1] = '\0'; return indent; } #define DEBUG_PARSER #define DEBUG_RUNTIME #include "gcode4.h" // GENERATED GRAMMAR FROM takeon.c and // gcode4.g #ifndef FALSE #define FALSE (0!=0) #define TRUE (0==0) #endif char *progname; int debug_parser = FALSE; int debug_runtime = FALSE; // Built-in phrase codes. Must match with grammar file. #define TYPE_EOF 0 #define TYPE_CHAR 1 #define TYPE_KEYWORD 2 typedef struct sourceinfo { // ATOMS for processed input stream char *s; // string contents int l; // lineno int col; // column int t; // type - tag, char, or keyword char *f; // source or includefile name } sourceinfo; int bestparse = -1; // for error reporting. char *looking_for = NULL; // C is the source character token stream static sourceinfo *c = NULL; int nextfree = 0, arraysize = 0; char *onecharstr = NULL; // A is the Analysis record. Contents point to a C[] structure when the item is a BIP, // otherwise the format is [phraseno] [altno] [no-of-subphrases] [subphrases and/or BIPs...] // eg A[25] = 10 means that A[25] is C[10]. static int *A = NULL; int next_free_a = 0, a_size = 0; static void *makespace_ (void *c, int nextfree, int *arraysize, int objsize) { if (nextfree >= *arraysize) { *arraysize = (*arraysize * 2) + 1; c = (void *) realloc (c, (*arraysize + 1) * objsize); // 0:arraysize, // inclusive. // eg 0:15, // 0:127 etc if (c == NULL) { #ifdef OLDSTYLE fprintf (stderr, "parser: %s\n", strerror (errno)); #else wprintw (debugline, "\nparser: %s", strerror (errno)); wrefresh (debugline); #endif exit (errno); } } return c; } #define makespace(c, nextfree, arraysize) c = (typeof(c))makespace_(c, nextfree, &arraysize, sizeof(c[0])) static void stores (char *s, int lineno, int col, int type, char *fname) { makespace (c, nextfree, arraysize); c[nextfree].s = s; c[nextfree].l = lineno; c[nextfree].col = col; c[nextfree].f = fname; c[nextfree].t = type; nextfree++; } //static void storec(int ch, int lineno, int col, int type, char *fname) { // onecharstr[ch*2] = ch; onecharstr[ch*2+1] = '\0'; // convert char to 1-char string before saving. // stores(&onecharstr[ch*2], lineno, col, type, fname); //} static int iskeyword (char *s) { int i; for (i = 0; i < MAX_KEYWORD; i++) if (strcmp (s, keyword[i]) == 0) return TRUE; return FALSE; } //-------------------------------------------------------------------------- FILE *sourcefile; char *curfile; int startline = TRUE, lineno = 1, col = 0, ch; static void line_reconstruction (char *string) { // PRE-PROCESS INPUT READY FOR PARSING // \n and \r removed. All input restricted to single line ending in EOF token. // done away with all built-in phrases. Everything is a character now. Not even // handling spaces specially. They're explicitly in the grammar... #define fgetc(x) (*string == '\0' ? EOF : *string++) #define ungetc(ch, x) if (ch != EOF) string--; else for (;;) { char *character = NULL; int nextfreech = 0, chsize = 0; ch = fgetc (sourcefile); if (ch == EOF) break; ch &= 255; // int, positive. makespace (character, nextfreech, chsize); character[nextfreech++] = ch; character[nextfreech] = '\0'; stores (strdup (character), lineno, col++, TYPE_CHAR, curfile); free (character); character = NULL; } // set up a dummy at the end because we sometimes look ahead by 1 // in the parsing code and don't want to hit uninitialised data. makespace (c, nextfree, arraysize); c[nextfree].t = TYPE_EOF; c[nextfree].s = "<EOF>"; c[nextfree].l = lineno++; c[nextfree].col = col; #undef fgetc #undef ungetc } //-------------------------------------------------------------------------- int cp = 0; // code pointer. Has to be global state. int ap = 0; // Analysis record pointer. static int parse (int pp, int depth) // depth is only for indentation in // diags { int saved_cp, saved_ap, i, gp, alts, match; gp = phrase_start[pp - 512 - MAX_BIP]; alts = gram[gp]; if (debug_parser) { #ifdef OLDSTYLE fprintf (stderr, "\n%sPhrase %s/%d (%d alternatives) = ", indents (depth), phrasename[pp - 512], pp, alts); fflush (stderr); #else wprintw (output, "\n%sPhrase %s/%d (%d alternatives) = ", indents (depth), phrasename[pp - 512], pp, alts); wrefresh (output); wrefresh (input); #endif } gp++; // gp now points to first element (length) of // first alt saved_cp = cp; saved_ap = ap; for (i = 0; i < alts; i++) { int each, phrases = gram[gp++], phrase_count, gap = 0; cp = saved_cp; ap = saved_ap; if (ap + 3 > next_free_a) next_free_a = ap + 3; makespace (A, next_free_a, a_size); A[ap++] = pp; // record which phrase (could be done outside // loop) A[ap++] = i; // and which alt. // Count slots needed. *Could* be precalculated and stored // in the grammar, either embedded (after the ALT) or as a // separate table for (each = 0; each < phrases; each++) if (gram[gp + each] >= 512) gap++; A[ap++] = gap; // Count of alts (gap) // ap+gap now points to the slot after the space required, which // is where the first subphrase will be stored. ap = ap + gap; // recursive subphrases are stored after this // phrase. // ap is left updated if successful. // successfully parsed phrases are stored in A[saved_ap+3+n] if (saved_ap + 3 + gap > next_free_a) next_free_a = saved_ap + 3 + gap; makespace (A, next_free_a, a_size); // this loop is only for diagnostics if (debug_parser) { wprintw (output, "\n%sAlternative %d: (%d phrases) ", indents (depth), i + 1, phrases); for (each = 0; each < phrases; each++) { int phrase = gram[gp + each]; if (phrase < 256) { wprintw (output, " '%c'", phrase); } else if (phrase < 512) { wprintw (output, " \"%s\"/%d", keyword[phrase - 256], phrase - 256); } else if (phrase < 512 + MAX_BIP) { wprintw (output, " {%s/BIP%d}", phrasename[phrase - 512], BIP[phrase - 512]); } else { wprintw (output, " <%s/%d>", phrasename[phrase - 512], phrase); } } if (debug_parser) { wprintw (output, " [[cp=%d: '%s'%d '%s'%d '%s'%d '%s'%d '%s'%d ... ]]", cp, (cp < nextfree ? c[cp].s : "EOF"), (cp < nextfree ? c[cp].t : 0), (cp + 1 < nextfree ? c[cp + 1].s : "EOF"), (cp + 1 < nextfree ? c[cp + 1].t : 0), (cp + 2 < nextfree ? c[cp + 2].s : "EOF"), (cp + 2 < nextfree ? c[cp + 2].t : 0), (cp + 3 < nextfree ? c[cp + 3].s : "EOF"), (cp + 3 < nextfree ? c[cp + 3].t : 0), (cp + 4 < nextfree ? c[cp + 4].s : "EOF"), (cp + 4 < nextfree ? c[cp + 4].t : 0) ); } wrefresh (output); wrefresh (input); // fprintf(stderr, "\n"); fflush(stderr); } match = TRUE; // stays true if all subphrases match phrase_count = 0; // only phrases which make it into the A // record, // i.e. excluding literals and keywords for (each = 0; each < phrases; each++) { int phrase = gram[gp + each]; if (debug_parser) { wprintw (debugline, "\n%sInput token stream = '%s' '%s' '%s' ...", indents (depth), (cp < nextfree ? c[cp].s : "EOF"), (cp + 1 < nextfree ? c[cp + 1].s : "EOF"), (cp + 2 < nextfree ? c[cp + 2].s : "EOF")); wrefresh (debugline); wrefresh (input); } if (cp > bestparse) { static char s[128]; if (phrase < 256) { sprintf (s, "'%c'", phrase); } else if (phrase < 512) { sprintf (s, "\"%s\"", keyword[phrase - 256]); } else if (phrase < 512 + MAX_BIP) { sprintf (s, "{%s}", phrasename[phrase - 512]); } else { sprintf (s, "<%s>", phrasename[phrase - 512]); } looking_for = s; bestparse = cp; } if (phrase < 256) { if (debug_parser) wprintw (output, "\n%s'%c'", indents (depth), phrase); if (debug_parser) { wprintw (output, " [[ '%s'%d '%s'%d '%s'%d '%s'%d '%s'%d ... ]]", (cp < nextfree ? c[cp].s : "EOF"), (cp < nextfree ? c[cp].t : 0), (cp + 1 < nextfree ? c[cp + 1].s : "EOF"), (cp + 1 < nextfree ? c[cp + 1].t : 0), (cp + 2 < nextfree ? c[cp + 2].s : "EOF"), (cp + 2 < nextfree ? c[cp + 2].t : 0), (cp + 3 < nextfree ? c[cp + 3].s : "EOF"), (cp + 3 < nextfree ? c[cp + 3].t : 0), (cp + 4 < nextfree ? c[cp + 4].s : "EOF"), (cp + 4 < nextfree ? c[cp + 4].t : 0) ); } if (c[cp].s[0] != phrase) match = FALSE; else cp++; // Don't record literals } else if (phrase < 512) { if (debug_parser) wprintw (output, "\n%s\"%s\"/%d", indents (depth), keyword[phrase - 256], phrase - 256); if (debug_parser) { wprintw (output, " [[ '%s'%d '%s'%d '%s'%d '%s'%d '%s'%d ... ]]", (cp < nextfree ? c[cp].s : "EOF"), (cp < nextfree ? c[cp].t : 0), (cp + 1 < nextfree ? c[cp + 1].s : "EOF"), (cp + 1 < nextfree ? c[cp + 1].t : 0), (cp + 2 < nextfree ? c[cp + 2].s : "EOF"), (cp + 2 < nextfree ? c[cp + 2].t : 0), (cp + 3 < nextfree ? c[cp + 3].s : "EOF"), (cp + 3 < nextfree ? c[cp + 3].t : 0), (cp + 4 < nextfree ? c[cp + 4].s : "EOF"), (cp + 4 < nextfree ? c[cp + 4].t : 0) ); } if (strcmp (keyword[phrase - 256], c[cp].s) != 0) match = FALSE; else cp++; // Don't record keywords } else if (phrase < 512 + MAX_BIP) { int where = ap; // next phrase to be parsed will be stored at // current 'ap'. if (debug_parser) wprintw (output, "\n%s{%s/BIP%d}", indents (depth), phrasename[phrase - 512], BIP[phrase - 512]); if (debug_parser) { wprintw (output, " [[ '%s'%d '%s'%d '%s'%d '%s'%d '%s'%d ... ]]", (cp < nextfree ? c[cp].s : "EOF"), (cp < nextfree ? c[cp].t : 0), (cp + 1 < nextfree ? c[cp + 1].s : "EOF"), (cp + 1 < nextfree ? c[cp + 1].t : 0), (cp + 2 < nextfree ? c[cp + 2].s : "EOF"), (cp + 2 < nextfree ? c[cp + 2].t : 0), (cp + 3 < nextfree ? c[cp + 3].s : "EOF"), (cp + 3 < nextfree ? c[cp + 3].t : 0), (cp + 4 < nextfree ? c[cp + 4].s : "EOF"), (cp + 4 < nextfree ? c[cp + 4].t : 0) ); } if (c[cp].t != BIP[phrase - 512]) match = FALSE; else { A[ap++] = phrase; A[ap++] = 1; A[ap++] = 1; A[ap++] = cp++; A[saved_ap + 3 + phrase_count++] = where; // Record BIP } } else { int where = ap; // next phrase to be parsed will be stored at // current 'ap'. if (debug_parser) wprintw (output, "\n%s<%s/%d>", indents (depth), phrasename[phrase - 512], phrase); if (!parse (phrase, depth + 1)) match = FALSE; else { A[saved_ap + 3 + phrase_count++] = where; } } if (debug_parser) { wprintw (output, "\n%sTried alternative %d: result was %s", indents (depth), each + 1, (match ? "TRUE" : "FALSE")); } if (!match) break; } gp += phrases; // move over all phrases, to next alt if (match) break; else if (debug_parser) { wprintw (output, "\n%s** Alternative %d FAILED.", indents (depth), i + 1); } // gp now points to first element (length) of next alt, or start of next // phrase } wrefresh (output); wrefresh (input); return (match); } //-------------------------------------------------------------------------- typedef int TRIP; //------------------------------------------------------------------------------------ /* In previous compilers, I had to write custom code for every tree-based optimisation, in order to walk down the tree to the right place to find the leaves to be optimised. In this one, I have a generic tree-walking procedure which can walk the entire program, but it can be customised so that it takes action only on specific phrases. This is possible in this design only because each set of subphrases stores the count of subphrases before it - thus allowing a generic tree-walking procedure that doesn't have to know what each node consists of until it happens across a node of the type it is looking for. */ static void walk (int ap, int depth, int wanted (int phraseno), void perform (int ap, int before, int depth)) { int i; if (wanted (A[ap])) perform (ap, TRUE, depth); for (i = 3; i < A[ap + 2] + 3; i++) { if (A[A[ap + i]] >= 512 + MAX_BIP) walk (A[ap + i], depth + 1, wanted, perform); } if (wanted (A[ap])) perform (ap, FALSE, depth); } static int want_all (int phraseno) { return TRUE; } static void print_all (int ap, int before, int depth) { int saved_ap = ap; int phrase = A[ap++]; int alt = A[ap++]; int phrases = A[ap++]; // defined subphrases int i; wprintw (output, "\n%s<%s%s/%d> ", indents (depth), (before ? "" : "/"), phrasename[phrase - 512], alt); for (i = 0; i < (3 + phrases); i++) { wprintw (output, "A[%d] = %d, ", saved_ap + i, A[saved_ap + i]); } } //-------------------------------------------------------------------------- int execute (int ap, int depth); // Global state, only valid for one call before it is overwritten char *last_units_ptr = "", *last_direction_ptr = "", *last_decimal_ptr = "", *last_decimal_endp, last_num[MAX_LINE], last_char[MAX_LINE], last_ident[MAX_LINE], last_quantity[MAX_LINE], fname[MAX_LINE], *fname_endp; static float current_tool_width = 0.0; // unknown until set. static int current_feedrate = 0; // make this global static int current_spindle_speed = 20000; // make this global too int interpret (char *command) { curfile = command; bestparse = -1; // for error reporting. looking_for = "<UNKNOWN>"; // 'while looking for <PHRASENAME>' (or // literal) ... (Never freed) nextfree = 0, arraysize = 0; if (c != NULL) { free (c); c = NULL; } if (A != NULL) { free (A); A = NULL; } next_free_a = 0, a_size = 0; cp = ap = 0; startline = TRUE; if (onecharstr == NULL) onecharstr = (char *) malloc (512); // if (debug_parser) fprintf(stderr, "Entering line_reconstruction\n"); line_reconstruction (command); if (c[0].t == TYPE_EOF) return -2; if (!parse (PHRASE_BASE, 0)) { #ifdef DO_PARSER_DIAGS if (bestparse == nextfree) { wprintw (output, "\n\"%s\", Line %d, Col %d: Premature end of file while looking for %s\n", curfile, c[bestparse].l, c[bestparse].col + 1, looking_for); } else { int i; wprintw (output, "\n\"%s\", Line %d, Col %d: Syntax error while looking for %s near: \"", curfile, c[bestparse].l, c[bestparse].col + 1, looking_for); for (i = bestparse; i < bestparse + 3; i++) { if (i == nextfree) { wprintw (output, "<End of line>"); break; } switch (c[i].t) { case TYPE_TAG: case TYPE_CHAR: case TYPE_KEYWORD: wprintw (output, "%s", c[i].s); break; } wprintw (output, (i == (bestparse + 2) ? " ..." : " ")); } wprintw (output, "\"\n"); wrefresh (output); wrefresh (input); } #endif return -1; // parse error } if (debug_parser) walk (0, 0, want_all, print_all); // Diags: print final parse tree #ifdef NEVER { int i; wprintw (output, "\nExecuting: "); for (i = 0; i < nextfree; i++) { wprintw (output, "%s ", c[i].s); } wprintw (output, "\n"); wrefresh (output); wrefresh (input); } #endif return execute (0, 0); // 0 (for OK), or runtime error } // If the parser were being used in a compiler, we would probably want to build // an AST here, but for this project it is simple enough to call the execution // code directly. int execute (int ap, int depth) { int saved_ap = ap; int phrase = A[ap++]; // A[ap] is the phrase number. A[ap+1] is the // alt. int alt = A[ap++]; // For consistency, in BIPs, the Alt should // always be 1 // although that may not be the case at the moment :-( #ifdef NEVER int phrases = A[ap++]; // defined subphrases #else ap += 1; #endif // int i; // The following ecce command executed on this file will generate // commands.g: // ecce -c "(v.//\\.s..(v/ /e)?m,k)0;%c" commands.c commands.g // May later tweak takeon.c to read from commands.c rather than commands.g // thus simplifying build process and becoming more like yacc. #ifdef DEBUG_RUNTIME if (debug_runtime) { wprintw (output, "\n%s>> %d: execute(%s)", indents (depth), saved_ap, (A[saved_ap] >= 512 ? phrasename[A[saved_ap] - 512] : "???")); wrefresh (output); wrefresh (input); } #endif switch (phrase) { //\\ # BIPS (Built-in Phrases)are linked to the type-code returned //\\ # by the line-reconstruction code (aka lexer) //\\# //\\ # These *must* come first. // See TYPE_* in first page for the values to use. //\\ B<EOF>=0; case P_EOF: // fprintf(stderr, "%s", c[A[ap]].s); break; //\\ B<CHAR>=1; case P_CHAR: // Built-in-phrase 2 is "TYPE_CHAR". // fprintf(stderr, "%s", c[A[ap]].s); strcpy (last_char, c[A[ap]].s); break; //\\# //\\ # Phrase definitions. PROGRAM is the main entry point. //\\# case P_PROGRAM: //\\ P<PROGRAM> = <SIMPLE> <S> <EOF> ; // wprintw(debugline, "\nEntered P_PROGRAM"); wrefresh(debugline); execute (A[ap], depth + 1); break; case P_S: //\\ P<S> = ' ' <S> | ; break; case P_INTEGER: //\\ P<INTEGER> = <DIGIT> <OPT_DIGITS> ; execute (A[ap], depth + 1); // wprintw(output, "\n%sINTEGER recursing with another call to // OPT_DIGITS", indents(depth)); wrefresh(output); wrefresh(input); execute (A[ap + 1], depth + 1); break; case P_SNUM: //\\ P<SNUM> = <S> <OPT_MINUS> <NUM> <S> ; { execute (A[ap + 2], depth + 1); if (A[A[ap + 1] + 1] == 0) { // alt of OPT_MINUS memmove (last_num + 1, last_num, strlen (last_num) + 1); last_num[0] = '-'; } } break; case P_OPT_MINUS: //\\ P<OPT_MINUS> = '-' | ; // checked by parent break; case P_NUM: //\\ P<NUM> = <INTEGER> <OPT_DECIMAL> ; { static char *p, tmp1[MAX_LINE], tmp2[MAX_LINE]; *(last_decimal_ptr = last_decimal_endp = tmp1) = '\0'; execute (A[ap], depth + 1); strcpy (last_num, last_decimal_ptr); *(last_decimal_ptr = last_decimal_endp = tmp2) = '\0'; *last_decimal_endp++ = '.'; *last_decimal_endp = '\0'; execute (A[ap + 1], depth + 1); strcat (last_num, last_decimal_ptr); if (*(p = &last_num[strlen (last_num) - 1]) == '.') *p = '\0'; // strip trailing . if "xxx." } break; case P_OPT_DECIMAL: //\\ P<OPT_DECIMAL> = '.' <OPT_DIGITS> | ; if (alt == 0) { // wprintw(output, "\n%sOPT_DECIMAL recursing with a call to // OPT_DIGITS", indents(depth)); wrefresh(output); wrefresh(input); execute (A[ap], depth + 1); } else { // wprintw(output, "\n%sOPT_DECIMAL: NON-ZERO ALT %d", indents(depth), // alt); wrefresh(output); wrefresh(input); } break; case P_OPT_DIGITS: //\\ P<OPT_DIGITS> = <DIGIT> <OPT_DIGITS> | ; if (alt == 0) { execute (A[ap], depth + 1); // wprintw(output, "\n%sOPT_DIGITS recursing with another call to // OPT_DIGITS", indents(depth)); wrefresh(output); wrefresh(input); execute (A[ap + 1], depth + 1); } else { // wprintw(output, "\n%sOPT_DIGITS: NON-ZERO ALT %d", indents(depth), // alt); wrefresh(output); wrefresh(input); } break; case P_DIGIT: //\\ P<DIGIT> = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' ; *last_decimal_endp++ = alt + '0'; *last_decimal_endp = '\0'; // wprintw(output, "\n%sDIGIT: ALT %d", indents(depth), alt); // wrefresh(output); wrefresh(input); break; case P_OPT_SUBJECT: //\\ P<OPT_SUBJECT> = <FILENAME> | ; break; case P_HELP: //\\ P<HELP> = 'help' <S> <OPT_SUBJECT> ; wprintw (output, "\nYou may enter any GCODE commands, immediate commands, or extended commands:"); wprintw (output, "\n\nA jog command: left/right/nearer/farther/up/down <distance> [units: mm or in]"); wprintw (output, "\nuse in/mm"); wprintw (output, "\nfeedrate <rpm>"); wprintw (output, "\nspindle speed <rpm>"); wprintw (output, "\nspindle on/off"); wprintw (output, "\nsend <filename>"); wprintw (output, "\ncylinder diameter/radius <distance> depth <distance>"); wprintw (output, "\npocket from n,n,n to n,n,n step n,n,n [toolwidth n]"); wprintw (output, "\ndrill <distance>"); wprintw (output, "\nprobe"); wprintw (output, "\ngpio"); wprintw (output, "\nhome"); wprintw (output, "\nreset xy"); wprintw (output, "\ngoto x,y"); wprintw (output, "\n[no]limits"); wprintw (output, "\n"); wrefresh (output); wrefresh (input); break; case P_SIMPLE: // wprintw(debugline, "\nEntered P_SIMPLE"); wrefresh(debugline); //\\ P<SIMPLE> = <JOG> | <CANNED> | <USE> <S> <UNITS> | <TESTCODE> | <POCKET> | <SPINDLE> | <FEEDRATE> | <SENDER> | <HELP> | 'laser' ' ' <LASER> ; if (alt == 0) { // jog execute (A[ap], depth + 1); } else if (alt == 1) { // canned execute (A[ap], depth + 1); } else if (alt == 2) { // use units char *u[] = { "mm", "in" }; int units = A[ap + 2]; wprintw (output, "\nDefault units: %s\nok", u[A[units + 1]]); // wrefresh (output); wrefresh (input); gcode_default_units (u[A[A[ap + 1] + 1]]); } else if (alt == 3) { // testcode execute (A[ap], depth + 1); } else if (alt == 4) { // pocket execute (A[ap], depth + 1); } else if (alt == 5) { // spindle execute (A[ap], depth + 1); } else if (alt == 6) { // feedrate execute (A[ap], depth + 1); } else if (alt == 7) { // sender execute (A[ap], depth + 1); } else if (alt == 8) { // help execute (A[ap], depth + 1); } else if (alt == 9) { // laser execute (A[ap], depth + 1); } break; case P_SENDER: // unfortunately due to super-hacky parser, // can't have filenames with spaces in them! //\\ P<SENDER> = 'send' <S> <FILENAME> ; fname_endp = fname; execute (A[ap + 1], depth + 1); *fname_endp = '\0'; wprintw (debugline, "\nSending %s", fname); wrefresh (debugline); wrefresh (input); gcode_sendfile (fname); break; case P_FILENAME: // everything up to the end of line... //\\ P<FILENAME> = <CHAR> <FILENAME> | ; if (alt == 0) { execute (A[ap], depth + 1); *fname_endp++ = *last_char; execute (A[ap + 1], depth + 1); } break; case P_FEEDRATE: //\\ P<FEEDRATE> = 'feedrate' <S> <INTEGER> ; { static char tmp1[MAX_LINE]; *(last_decimal_ptr = last_decimal_endp = tmp1) = '\0'; execute (A[ap + 1], depth + 1); strcpy (last_num, last_decimal_ptr); current_feedrate = atoi (last_num); gcode ("F%d", current_feedrate); } break; case P_SPINDLE: //\\ P<SPINDLE> = 'spindle' <S> 'speed' <S> <INTEGER> | 'spindle' <S> 'on' | 'spindle' <S> 'off' ; { static char tmp1[MAX_LINE]; switch (alt) { case 0: // "spindle" <S> "speed" <S> <INTEGER> *(last_decimal_ptr = last_decimal_endp = tmp1) = '\0'; execute (A[ap + 2], depth + 1); strcpy (last_num, last_decimal_ptr); current_spindle_speed = atoi (last_num); if (current_spindle_speed > 10000) { gcode ("$30=20000"); // just in case... gcode ("S4000"); gcode ("G4 P.25"); gcode ("S6000"); gcode ("G4 P.25"); gcode ("S8000"); gcode ("G4 P.25"); gcode ("S10000"); gcode ("G4 P.25"); } gcode ("S%d", current_spindle_speed); break; case 1: // "spindle" <S> "on" // I'm using the 20,000 RPM spindle so ramp it up slowly. ish. // Have already set $30=20000 parameter to say that 20000 == 100% // G4 delay? if (current_spindle_speed > 10000) { gcode ("$30=20000"); // just in case... gcode ("S4000"); gcode ("M3"); gcode ("G4 P.25"); gcode ("S6000"); gcode ("M3"); gcode ("G4 P.25"); gcode ("S8000"); gcode ("M3"); gcode ("G4 P.25"); gcode ("S10000"); gcode ("M3"); gcode ("G4 P.25"); } gcode ("S%d", current_spindle_speed); gcode ("M3"); break; case 2: // "spindle" <S> "off" gcode ("M5"); break; } } break; #define child(parent, n) (A[(parent)+(n)]+3) case P_POCKET: //\\ P<POCKET> = 'pocket' <SNUM> ',' <SNUM> ',' <SNUM> 'to' <SNUM> ',' <SNUM> ',' <SNUM> 'step' <SNUM> ',' <SNUM> ',' <SNUM> <OPT_TOOLWIDTH> ; { float from_x, from_y, from_z, to_x, to_y, to_z, step_x, step_y, step_z; execute (A[ap], depth + 1); from_x = (float) atof (last_num); execute (A[ap + 1], depth + 1); from_y = (float) atof (last_num); execute (A[ap + 2], depth + 1); from_z = (float) atof (last_num); execute (A[ap + 3], depth + 1); to_x = (float) atof (last_num); execute (A[ap + 4], depth + 1); to_y = (float) atof (last_num); execute (A[ap + 5], depth + 1); to_z = (float) atof (last_num); execute (A[ap + 6], depth + 1); step_x = (float) atof (last_num); execute (A[ap + 7], depth + 1); step_y = (float) atof (last_num); execute (A[ap + 8], depth + 1); step_z = (float) atof (last_num); current_tool_width = 0.0; execute (A[ap + 9], depth + 1); wprintw (debugline, "\nCutting a pocket from (%f,%f,%f) to (%f,%f,%f) in steps of (%f,%f,%f)...\n", from_x, from_y, from_z, to_x, to_y, to_z, step_x, step_y, step_z); wrefresh (debugline); wrefresh (input); // coords are absolute, ie top z is > bottom z - but steps are all // positive, e.g. z step might be 0.2mm - NOT -0.2mm // I know this is all inconsistent and I *will* clean it up after I've // finished exploring the interface... cnc_pocket (from_x, from_y, from_z, to_x, to_y, to_z, step_x, step_y, step_z, current_tool_width, TRUE); // TO // DO: // add // dogbone // parameter // to // syntax } break; //\\ P<OPT_TOOLWIDTH> = 'toolwidth' <S> <NUM> | ; case P_OPT_TOOLWIDTH: if (alt == 0) { execute (A[ap + 1], depth + 1); current_tool_width = (float) atof (last_num); } break; //\\ P<TESTCODE> = 'goto' <S> <NUM> <S> ',' <S> <NUM> ; // add an extra optional Z? case P_TESTCODE: { char X[MAX_LINE], Y[MAX_LINE]; execute (A[ap + 1], depth + 1); strcpy (X, last_num); execute (A[ap + 4], depth + 1); strcpy (Y, last_num); gcode_gotoxy ((float) atof (X), (float) atof (Y)); } break; case P_JOG: //\\ P<JOG> = <DIRECTION> <S> <QUANTITY> <S> <OPT_UNITS> ; // Units cannot be omitted unless a default unit has already been // selected. // wprintw(debugline, "\nEntered P_JOG for real"); wrefresh(debugline); { execute (A[ap], depth + 1); execute (A[ap + 2], depth + 1); execute (A[ap + 4], depth + 1); gcode_jog (last_direction_ptr, last_quantity, last_units_ptr); } break; case P_USE: //\\ P<USE> = 'use' | 'units' ; break; case P_OPT_UNITS: //\\ P<OPT_UNITS> = <UNITS> | ; if (alt == 0) { execute (A[ap], depth + 1); } break; case P_DIRECTION: //\\ P<DIRECTION> = <UP> | <DOWN> | <LEFT> | <RIGHT> | 'farther' | 'nearer' ; // add closer, near, far, away? { char *directions[6] = { "up", "down", "left", "right", "farther", "nearer" }; last_direction_ptr = directions[alt]; } break; //\\ P<UP> = 'up' | 'u' ; case P_UP: break; //\\ P<DOWN> = 'down' | 'd' ; case P_DOWN: break; //\\ P<LEFT> = 'left' | 'l' ; case P_LEFT: break; //\\ P<RIGHT> = 'right' | 'r' ; case P_RIGHT: break; case P_QUANTITY: //\\ P<QUANTITY> = <NUM> ; execute (A[ap], depth + 1); strcpy (last_quantity, last_num); break; case P_UNITS: //\\ P<UNITS> = <mm> | <in> ; execute (A[ap], depth + 1); break; case P_mm: //\\ P<mm> = 'mm' | 'mms' | 'millimeters' | 'millimetres' ; last_units_ptr = "mm"; break; case P_in: //\\ P<in> = 'in' | 'ins' | 'inches' ; last_units_ptr = "in"; break; case P_SET_ORIGIN: //\\ P<SET_ORIGIN> = 'zeroxy' | 'resetxy' | 'reset' <S> 'xy' | 'reset' <S> 'origin' ; // just in case I forget which command I defined... break; case P_CYLINDER: //\\ P<CYLINDER> = 'cylinder' <S> 'diameter' <S> <NUM> <S> 'depth' <S> <NUM> | 'cylinder' <S> 'radius' <S> <NUM> <S> 'depth' <S> <NUM> ; if (alt == 0) { { // cylinder float diameter, cutting_depth; execute (A[ap + 2], depth + 1); diameter = (float) atof (last_num); execute (A[ap + 5], depth + 1); cutting_depth = (float) atof (last_num); cnc_cylinder (diameter / 2, cutting_depth); // as of 8/9/2020, calling these procedures executes them OK but does // not return to command loop. // Looking into why at the moment... } } else if (alt == 1) { { // cylinder float radius, cutting_depth; execute (A[ap + 2], depth + 1); radius = (float) atof (last_num); execute (A[ap + 5], depth + 1); cutting_depth = (float) atof (last_num); cnc_cylinder (radius, cutting_depth); } } break; case P_LASER: //\\ P<LASER> = 'test' | 'focus' | 'strength' ; if (alt == 0) { // wprintw(debugline, "\nEntered 'laser test'"); wrefresh(debugline); laser_mode(); wprintw(output, "\nLASER TEST\nok"); wrefresh(output); wrefresh(input); laser_test(); cnc_mode(); } else if (alt == 1) { laser_mode(); wprintw(output, "\nLASER FOCUS CHART AT 0,0\nok"); wrefresh(output); wrefresh(input); laser_focus(); cnc_mode(); } else if (alt == 2) { laser_mode(); wprintw(output, "\nLASER CUT CHART AROUND Y=0\nok"); wrefresh(output); wrefresh(input); laser_strength(); cnc_mode(); } break; case P_CANNED: //\\ P<CANNED> = 'probe' | <CYLINDER> | 'gpio' | 'home' | <SET_ORIGIN> | 'limits' | 'nolimits' | 'drill' <S> <NUM> | 'test' <SNUM> ',' <SNUM> | <PITREX>; if (alt == 0) { // probe gcode_probe (); } else if (alt == 1) { // cylinder execute (A[ap + 0], depth + 1); } else if (alt == 2) { // gpio cnc_hammer_jig (); } else if (alt == 3) { // home // enable limits: gcode ("$20=1"); gcode ("$21=1"); gcode ("$22=1"); // home: gcode ("$H"); // reset x y 0 0: gcode ("G10 L2 P1 X0 Y0"); gcode ("G92 X0 Y0"); // turn off limits: gcode ("$20=0"); gcode ("$21=0"); gcode ("$22=0"); } else if (alt == 4) { // zeroxy - not sure which is the best way to do this? Will try both... gcode ("G10 L2 P1 X0 Y0"); gcode ("G92 X0 Y0"); } else if (alt == 5) { // limits gcode ("$20=1"); gcode ("$21=1"); gcode ("$22=1"); } else if (alt == 6) { // nolimits gcode ("$20=0"); gcode ("$21=0"); gcode ("$22=0"); } else if (alt == 7) { // drill <S> <NUM> static char tmp1[MAX_LINE]; float drill_depth; *(last_decimal_ptr = last_decimal_endp = tmp1) = '\0'; execute (A[ap + 1], depth + 1); strcpy (tmp1, last_num); drill_depth = (float) atof (tmp1); wprintw (output, "\ndrill: %fmm", drill_depth); wrefresh (output); wrefresh (input); cnc_hammer_jig_peck_drill (drill_depth); } else if (alt == 8) { // test <SNUM> ',' <SNUM> { char num1[MAX_LINE], num2[MAX_LINE]; execute (A[ap], depth + 1); strcpy (num1, last_num); execute (A[ap + 1], depth + 1); strcpy (num2, last_num); wprintw (output, "\ntest: %f %f", (float) atof (num1), (float) atof (num2)); wrefresh (output); wrefresh (input); } } else if (alt == 9) { execute (A[ap], depth + 1); } break; case P_PITREX: //\\# //\\P<PITREX> = 'pitrex' ' ' 'mailer' | 'pitrex' <CART>; if (alt == 0) { // cut 3 rectangles of coroplast (covering platen) to // ship pitrex cartridge in (two halves) cnc_pitrex_mailer(); } else if (alt == 1) { // cut hole in standard vectrex case for pitrex cnc_pitrex_cartridge(); } break; case P_CART: //\\# //\\P<CART> = ' ' 'cart' | ' ' 'hole' | ; break; //\\# //\\ E //\\ # 'E' is end of grammar. Everything after this is ignored. default: wprintw (debugline, "\n*** Internal error at line %d. ap=%d phrase=%d\n", __LINE__, ap, phrase); wrefresh (debugline); exit (2); } #ifdef DEBUG_RUNTIME if (debug_runtime) { wprintw (output, "\n%s<< %d: execute(%s)", indents (depth), saved_ap, (A[saved_ap] >= 512 ? phrasename[A[saved_ap] - 512] : "???")); wrefresh (output); wrefresh (input); } #endif return 0; } void append_error (char *line, int gcode_errnum) { switch (gcode_errnum) { case 1: strcat (line, " G-code words consist of a letter and a value. Letter was not found."); break; case 2: strcat (line, " Numeric value format is not valid or missing an expected value."); break; case 3: strcat (line, " Grbl ‘$’ system command was not recognized or supported."); break; case 4: strcat (line, " Negative value received for an expected positive value."); break; case 5: strcat (line, " Homing cycle is not enabled via settings."); break; case 6: strcat (line, " Minimum step pulse time must be greater than 3usec."); break; case 7: strcat (line, " EEPROM read failed. Reset and restored to default values."); break; case 8: strcat (line, " Grbl ‘$’ command cannot be used unless Grbl is IDLE. Ensures smooth operation during a job."); break; case 9: strcat (line, " G-code locked out during alarm or jog state"); break; case 10: strcat (line, " Soft limits cannot be enabled without homing also enabled."); break; case 11: strcat (line, " Max characters per line exceeded. Line was not processed and executed."); break; case 12: strcat (line, " [Compile Option] Grbl ‘$’ setting value exceeds the maximum step rate supported."); break; case 13: strcat (line, " Safety door detected as opened and door state initiated."); break; case 14: strcat (line, " [Grbl-Mega Only] Build info or startup line exceeded EEPROM line length limit."); break; case 15: strcat (line, " Jog target exceeds machine travel. Command ignored."); break; case 16: strcat (line, " Jog command with no ‘=’ or contains prohibited g-code."); break; case 20: strcat (line, " Unsupported or invalid g-code command found in block."); break; case 21: strcat (line, " More than one g-code command from same modal group found in block."); break; case 22: strcat (line, " Feed rate has not yet been set or is undefined."); break; case 23: strcat (line, " G-code command in block requires an integer value."); break; case 24: strcat (line, " Two G-code commands that both require the use of the XYZ axis words were detected in the block."); break; case 25: strcat (line, " A G-code word was repeated in the block."); break; case 26: strcat (line, " A G-code command implicitly or explicitly requires XYZ axis words in the block, but none were detected."); break; case 27: strcat (line, " N line number value is not within the valid range of 1 – 9,999,999."); break; case 28: strcat (line, " A G-code command was sent, but is missing some required P or L value words in the line."); break; case 29: strcat (line, " Grbl supports six work coordinate systems G54-G59. G59.1, G59.2, and G59.3 are not supported."); break; case 30: strcat (line, " The G53 G-code command requires either a G0 seek or G1 feed motion mode to be active. A different motion was active."); break; case 31: strcat (line, " There are unused axis words in the block and G80 motion mode cancel is active."); break; case 32: strcat (line, " A G2 or G3 arc was commanded but there are no XYZ axis words in the selected plane to trace the arc."); break; case 33: strcat (line, " The motion command has an invalid target. G2, G3, and G38.2 generates this error, if the arc is impossible to generate or if the probe target is the current position."); break; case 34: strcat (line, " A G2 or G3 arc, traced with the radius definition, had a mathematical error when computing the arc geometry. Try either breaking up the arc into semi-circles or quadrants, or redefine them with the arc offset definition."); break; case 35: strcat (line, " A G2 or G3 arc, traced with the offset definition, is missing the IJK offset word in the selected plane to trace the arc."); break; case 36: strcat (line, " There are unused, leftover G-code words that aren’t used by any command in the block."); break; case 37: strcat (line, " The G43.1 dynamic tool length offset command cannot apply an offset to an axis other than its configured axis. The Grbl default axis is the Z-axis."); break; case 38: strcat (line, " An invalid tool number sent to the parser"); break; default: break; } wprintw (debugline, "\n%s", line); wrefresh (debugline); wrefresh (input); /* ALARM:1: strcat(line, " Hard limit triggered. Machine position is likely lost due to sudden and immediate halt. Re-homing is highly recommended."); ALARM:2: strcat(line, " G-code motion target exceeds machine travel. Machine position safely retained. Alarm may be unlocked."); ALARM:3: strcat(line, " Reset while in motion. Grbl cannot guarantee position. Lost steps are likely. Re-homing is highly recommended."); ALARM:4: strcat(line, " Probe fail. The probe is not in the expected initial state before starting probe cycle, where G38.2 and G38.3 is not triggered and G38.4 and G38.5 is triggered."); ALARM:5: strcat(line, " Probe fail. Probe did not contact the workpiece within the programmed travel for G38.2 and G38.4."); ALARM:6: strcat(line, " Homing fail. Reset during active homing cycle."); ALARM:7: strcat(line, " Homing fail. Safety door was opened during active homing cycle."); ALARM:8: strcat(line, " Homing fail. Cycle failed to clear limit switch when pulling off. Try increasing pull-off setting or check wiring."); ALARM:9: strcat(line, " Homing fail. Could not find limit switch within search distance. Defined as 1.5 * max_travel on search and 5 * pulloff on locate phases."); Hold:0: strcat(line, " Hold complete. Ready to resume."); Hold:1: strcat(line, " Hold in-progress. Reset will throw an alarm."); Door:0: strcat(line, " Door closed. Ready to resume."); Door:1: strcat(line, " Machine stopped. Door still ajar. Can’t resume until closed."); Door:2: strcat(line, " Door opened. Hold (or parking retract) in-progress. Reset will throw an alarm."); Door:3: strcat(line, " Door closed and resuming. Restoring from park, if applicable. Reset will throw an alarm."); */ } int gcode (char *format, ...) { // FALSE on error char commands[MAX_LINE], line[MAX_LINE]; va_list argList; char *s; int i; size_t numRead; char c; va_start (argList, format); vsnprintf (commands, MAX_LINE, format, argList); va_end (argList); wprintw (output, "\n# %s", commands); wrefresh (output); wrefresh (input); strcat (commands, "\n"); if (write (Data_To_CNC_Fd, commands, strlen (commands)) != strlen (commands)) fatal ("partial/failed write (To_CNC)"); // Now we read everything returned up to the 'ok' or 'error' response. for (;;) { // readline(); s = line; i = 1; for (;;) { #ifdef ZERO_EVERY_TIME FD_ZERO (&inFds); FD_SET (STDIN_FILENO, &inFds); FD_SET (Data_From_CNC_Fd, &inFds); #endif if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) { if (errno != EINTR) errExit ("select"); // EINTR happens on window resize } do { } while (!FD_ISSET (Data_From_CNC_Fd, &inFds)); /* CNC --> screen */ numRead = read (Data_From_CNC_Fd, &c, 1); if (numRead <= 0) break; if (c == '\r') continue; if (c == '\n') break; *s++ = c; i++; if (i == MAX_LINE) break; } *s = '\0'; if (strncmp (line, "error:", 6) == 0) { gcode_errnum = atoi (line + 6); append_error (line, gcode_errnum); } wprintw (output, "\n%s", line); wrefresh (output); wrefresh (input); if (strcmp (line, "ok") == 0) break; if ((strcmp (line, "ok") == 0) || (strncasecmp (line, "error:", 6) == 0) || (strncasecmp (line, "ALARM:", 6) == 0) || (strncasecmp (line, "hold:", 5) == 0) || (strncasecmp (line, "door:", 5) == 0) || (strcmp (line, "[MSG:Reset to continue]") == 0) ) { return (strcmp (line, "ok") == 0); } } return TRUE; } int gcode_sendfile (char *fname) { char codeline[MAX_LINE], *code_endp; FILE *gc = fopen (fname, "r"); if (gc == NULL) { // bad! Print diag. wprintw (output, "\nCannot open file '%s' - %s", fname, strerror (errno)); wrefresh (debugline); wrefresh (output); wrefresh (input); return FALSE; } code_endp = codeline; for (;;) { int c; c = fgetc (gc); if (c == '\r') continue; if (c == EOF) break; if (c == '\n') { *code_endp = '\0'; if (!gcode ("%s", codeline)) { fclose (gc); return FALSE; } code_endp = codeline; } else { *code_endp++ = c; } } fclose (gc); return TRUE; } int gcode_gotoxy (float x, float y) { return gcode ("G0 X%f Y%f", x, y); } int gcode_cuttoxy (float x, float y) { return gcode ("G1 X%f Y%f", x, y); } int gcode_gotoz (float z) { return gcode ("G0 Z%f", z); } int gcode_cuttoz (float z) { return gcode ("G1 Z%f", z); } int gcode_spindle_on (void) { if (current_spindle_speed > 10000) { // gcode("$30=20000"); // doesn't work unless GRBL is idle. gcode ("S4000"); gcode ("M3"); gcode ("G4 P.25"); gcode ("S6000"); gcode ("M3"); gcode ("G4 P.25"); gcode ("S8000"); gcode ("M3"); gcode ("G4 P.25"); gcode ("S10000"); gcode ("M3"); gcode ("G4 P.25"); } gcode ("S%d", current_spindle_speed); return gcode ("M3"); } int gcode_spindle_off (void) { return gcode ("M5"); } void laser_mode(void) { // The hookup: https://www.facebook.com/photo.php?fbid=10224223205907555&set=p.10224223205907555&type=3&theater // wait until '?' status shows 'idle' // probably will be already, at start of job gcode("$30=1000"); // 1000 is full laser power gcode("$31=0"); // min laser power 0 gcode("$32=1"); // laser-mode enabled } void cnc_mode(void) { // wait until '?' status shows 'idle' // unlikely to be so at end of job gcode("G4 P0.7"); // 0.7s pause - G4 P200 (Dwell for a moment - [P<time in ms>] [S<time in sec>]) <-- not true. P in seconds gcode("$32=0"); // laser mode disabled gcode("$30=20000"); // max spindle speed is 20000 gcode("$31=0"); // min spindle speed is 0 } void laser_test(void) { gcode("G94"); // perhaps redundant - speed in units per time step, not pulses per distance step, maybe gcode("G17"); // also likely redundant... - XY plane gcode("G21"); // use mm gcode("G91"); // Relative coords gcode("M3"); // PWM mode is 'Constant Power Laser On' vs M4 - M4 works really well at preventing overburns, // but with slow accelerating rigs, you may get unburnt areas gcode("F300");// burn movement speed gcode("S50");// 5% power // blip the laser at low power just as a confidence test for now gcode("G1 Y10"); // 1mm ticks gcode("G0 Y-10"); // 1mm ticks // G4 P200 (Dwell for a moment - [P<time in ms>] [S<time in sec>]) gcode("G90"); // Absolute co-ords // gcode("M2"); // terminate and reset defaults } void laser_focus(void) { int i; // https://www.facebook.com/groups/SainSmart.GenmitsuCNC/permalink/2573475146296637/ gcode("G94"); // perhaps redundant - speed in units per time step, not pulses per distance step, maybe gcode("G17"); // also likely redundant... - XY plane gcode("G21"); // use mm gcode("G90"); gcode("G0 X0 Y0 "); // absolute move to 0,0 origin to draw ruler gcode("G91"); // Relative coords gcode("M3"); // PWM mode is 'Constant Power Laser On' vs M4 - M4 works really well at preventing overburns, // but with slow accelerating rigs, you may get unburnt areas // M3 is really 'laser on while moving with G1' gcode("F300");// burn movement speed gcode("S350");// 35% power for drawing ticks gcode("(Draw Reference Marks)"); for (i = 0; i < 5; i++) { gcode("G1 Y1"); // 1mm ticks gcode("G0 X2 Y-1"); // 2mm apart } gcode("G1 Y3"); // big tick in the middle gcode("G0 X2 Y-3"); for (i = 0; i < 5; i++) { gcode("G1 Y1"); // 1mm ticks gcode("G0 X2 Y-1"); // 2mm apart } gcode("G90"); gcode("G0 X0 Y4"); gcode("G91"); // absolute move to reposition for focus test gcode("G0 Z-5"); // 5mm below start pos, 5 ticks left of center gcode("S350"); // 35% power for drawing focus test bars for (i = 0; i < 10; i++) { gcode("G1 Y10"); gcode("G0 X2 Y-10 Z1"); // 10 different heights, 1mm up for each line, lines 2mm apart } gcode("G1 Y10"); gcode("G0 Z-5"); // drop back 5mm to original Z, corresp to center tick gcode("M5"); // laser off gcode("G90"); // Absolute co-ords gcode("G0 X0 Y0"); // return to origin // gcode("M2"); // terminate and reset defaults gcode("(END OF PROGRAM)"); } void laser_strength(void) { int i, burn; // https://www.facebook.com/groups/SainSmart.GenmitsuCNC/permalink/2573475146296637/ gcode("G94"); // perhaps redundant - speed in units per time step, not pulses per distance step, maybe gcode("G17"); // also likely redundant... - XY plane gcode("G21"); // use mm gcode("G90"); gcode("G0 X-100 Y0"); // absolute move to 0,0 origin to draw ruler gcode("G91"); // Relative coords // try M4 instead of M3 - see if itburns cardboard rather than just scoring it... gcode("M4"); // PWM mode is 'Constant Power Laser On' vs M4 - M4 works really well at preventing overburns, // but with slow accelerating rigs, you may get unburnt areas // M3 is really 'laser on while moving with G1' /* cardboard shipping box (monster sodas): S900 or less did not break surface at F300 for 3 levels of grey use S400 S650 S900 at F300 S1000 F100 - doesn't cut through 90 - can be pushed through, no charring on back 70 - cut out, including paper label on back, light charring on back 60 - pushed out, including paper label on back, charring on back 50 - fell out, including paper label on back, heavy charring on back Still to test - coroplast */ for (burn = 100; burn >= 50; burn -= 10) { gcode("F%0d", burn);// burn movement speed gcode("S1000"); // anything less doesn't cut cardboard // still to try... lower depth and cut again... // burn a 1cm square at increasing strengths gcode("G1 Y10"); // burn gcode("G1 X10"); // burn gcode("G1 Y-10"); // burn gcode("G1 X-10"); // burn gcode("G0 X20"); // move } gcode("M5"); // laser off gcode("F500"); gcode("G90"); // Absolute co-ords gcode("G0 X0 Y0"); // return to origin } void cnc_rectangle (float from_x, float from_y, float to_x, float to_y, float step_x, float step_y, int want_dogbones) { // dogbones currently ignored // starts at from_x,from_y - don't care (for now) where it finishes float x, y; if (to_x - from_x > to_y - from_y) { // fewer cuts assuming step_x==step_y y = from_y; for (;;) { gcode_cuttoxy (to_x, y); // across if (y == to_y) break; y += step_y; if (y > to_y) { y = to_y; } gcode_cuttoxy (to_x, y); // up gcode_cuttoxy (from_x, y); // back if (y == to_y) break; y += step_y; if (y > to_y) { y = to_y; } gcode_cuttoxy (from_x, y); // up } } else { // fill in after testing x = from_x; for (;;) { gcode_cuttoxy (x, to_y); // across if (x == to_x) break; x += step_x; if (x > to_x) { x = to_x; } gcode_cuttoxy (x, to_y); // up gcode_cuttoxy (x, from_y); // back if (x == to_x) break; x += step_x; if (x > to_x) { x = to_x; } gcode_cuttoxy (x, from_y); // up } } } void cnc_pocket (float from_x, float from_y, float from_z, float to_x, float to_y, float to_z, float step_x, float step_y, float step_z, float current_tool_width, int want_dogbones) { // TO DO: add dogbones boolean // TO DO: step_z should be small. Do a sanity check on it. float z; if (current_tool_width == 0.0) { // TO DO: ask user for tool width current_tool_width = DEFAULT_TOOL_WIDTH; // largest of the sainsmart // end mills } // force ordering so that inset for toolwidth comes out OK! if (from_x > to_x) { // from_x has to be left of to_x float tmp = to_x; to_x = from_x; from_x = tmp; } if (from_y > to_y) { // from_y has to be nearer than to_y float tmp = to_y; to_y = from_y; from_y = tmp; } if (from_z < to_z) { // from_z has to be above to_z float tmp = to_z; to_z = from_z; from_z = tmp; } wprintw (output, "\n( Cutting a pocket from (%f,%f,%f) down to (%f,%f,%f) in steps of (%f,%f,%f)... ) with a %fmm tool\n", from_x, from_y, from_z, to_x, to_y, to_z, step_x, step_y, step_z, current_tool_width); wrefresh (output); wrefresh (input); gcode_gotoxy (from_x + current_tool_width / 2, from_y + current_tool_width / 2); gcode_spindle_on (); z = from_z; // higher than to_z for (;;) { // z loop if (z < to_z) break; gcode_cuttoz (z); cnc_rectangle (from_x + current_tool_width / 2, from_y + current_tool_width / 2, to_x - current_tool_width / 2, to_y - current_tool_width / 2, step_x, step_y, want_dogbones); gcode_gotoz (z + 1.0); // raise a little before move gcode_gotoxy (from_x + current_tool_width / 2, from_y + current_tool_width / 2); // move // back // to // origin z -= step_z; // steps are currently unsigned quantities } gcode_gotoz (from_z); gcode_spindle_off (); gcode_gotoxy ((from_x + to_x) / 2, (from_y + to_y) / 2); } static float tiny = 0.001; int equals (float a, float b) { // because floating point equality // comparisons are affected by accuracy and // rounding return ((a > (b - tiny)) && (a < (b + tiny))); } int notequals (float a, float b) { return !equals (a, b); } // http://www.helmancnc.com/how-to-mill-full-circle-cnc-program-example-code/ // i,j specify center of circle - you don't need to move there too! int gcode_circle_rel (float radius) { // at current center with current tool width. // Compensate elsewhere! // during testing, NO DEPTH GIVEN ASSUMES RELATIVE MODE if (radius < 0.0) return 0; gcode ("G0 X0 Y%f (center to top radius)", radius); gcode ("G2 X0 Y0 I0 J-%f (draw circle)", radius); return gcode ("G0 X0 Y-%f (top radius to center)", radius); } int gcode_circle_abs (float x, float y, float radius, float move_height, float cut_height) { if (radius < 0.0) return 0; // in abs mode on entry gcode ("G90 (absolute moves)"); // raise to move height gcode_gotoz (move_height); // move to abs coords of center of circle gcode_gotoxy (x,y); // move to cut height gcode_gotoz (cut_height); // switch to rel mode gcode ("G91 (relative moves)"); // cut circle gcode ("G0 X0 Y%f (center to top radius)", radius); gcode ("G2 X0 Y0 I0 J-%f (draw circle)", radius); // return to center gcode ("G0 X0 Y-%f (top radius to center)", radius); // switch to abs mode gcode ("G90 (absolute moves)"); // raise to move height return gcode_gotoz (move_height); } int gcode_disc (float max_radius) { // at current center with current tool width. // Compensate elsewhere! float radius = current_tool_width / 2, // ASSUMES RELATIVE MODE step = 0.6; do { gcode_circle_rel (radius); radius += step; } while (radius < max_radius); return gcode_circle_rel (max_radius); } void cnc_cylinder (float radius, float depth) { float current_depth = 0.0, z_step = 0.5; char y; if (current_tool_width == 0.0) { // TO DO: ask user for tool width current_tool_width = DEFAULT_TOOL_WIDTH; // largest of the sainsmart // end mills } wprintw (output, "\nCutting a cylinder at the current tool position\n"); wprintw (output, "\nPress 'y' when you are ready to cut, or 'n' to cancel."); wrefresh (output); wrefresh (input); for (;;) { // loop over reading single characters, // looking for 'y' or 'n' - // any other character stays in this loop - we don't want an accidental // lean on the keyboard to cause ANY machine movement while changing the // tool! #ifdef ZERO_EVERY_TIME FD_ZERO (&inFds); FD_SET (STDIN_FILENO, &inFds); FD_SET (Data_From_CNC_Fd, &inFds); #endif if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) { if (errno != EINTR) errExit ("select"); // EINTR happens on window resize } do { } while (!FD_ISSET (STDIN_FILENO, &inFds)); y = 'n'; (void) read (STDIN_FILENO, &y, 1); if (y == 'y') { gcode_spindle_on (); gcode ("G91 (relative moves)"); current_depth = 0.0; for (;;) { current_depth += z_step; if (current_depth > depth) break; gcode ("G1 Z-%f", z_step); gcode_disc (radius - current_tool_width / 2); } gcode ("G1 Z-%f", depth - (current_depth - z_step)); gcode_disc (radius - current_tool_width / 2); gcode_spindle_off (); gcode ("G1 Z%f", depth); // return to starting point gcode ("G90 (absolute moves)"); } else if (y == 'n') { wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input); return; } } /* filled circle discussion (cylinder): Bertalan Fábián It may look somewhat simpler with relative coordinates, just repeat with incrementing I: G90 (absolute coordinates) ... G00 X10.0 Y10 (absolute coordinates of the center of the hole) G00 Z0.0 (absolute coordinate of the top of the stock) G90 (switch to relative coordinates) G01 Z-0.5 (cut 0.5mm deep at once, relative coordinate!) G01 X-1.0 (go left 1mm) G02 X0.0 Y0.0 I1.0 J0.0 (arc with center (1.0, 0.0) from current position, full circle as we return to the same position) G01 X-1.0 (another mm to left, now we are (-2.0,0.0) from the center) G02 X0.0 Y10.0 I2.0 J0.0 (arc with center (2.0, 0.0) from current position, full circle as we return to the same position) G01 X-1.0 (another mm to left, now we are (-3.0,0.0) from the center) G02 X0.0 Y0.0 I3.0 J0.0 (arc with center (3.0, 0.0) from current position, full circle as we return to the same position) G01 X-1.0 (another mm to left, now we are (-4.0,0.0) from the center) G02 X0.0 Y0.0 I4.0 J0.0 (arc with center (4.0, 0.0) from current position, full circle as we return to the same position) => now you have a circle with radius of (4mm+radius of the bit) repeat until you reach the desired radius Then sum the amounts you stepped left to go back to the center: G01 X4.0 repeat the from G01 Z-0.5 until reaching the desired depth G90 (back to absolute coordinates) G00 Z5.0 (lift to safe position and then continue with whatever) */ } void cnc_hammer_jig_peck_drill (float dz) { // in inches, delta movements gcode_cuttoz (dz * 1 / 5); gcode ("G4 P.1"); gcode_cuttoz (-dz * 1 / 5); gcode ("G4 P.1"); gcode_cuttoz (dz * 2 / 5); gcode ("G4 P.1"); gcode_cuttoz (-dz * 2 / 5); gcode ("G4 P.1"); gcode_cuttoz (dz * 3 / 5); gcode ("G4 P.1"); gcode_cuttoz (-dz * 3 / 5); gcode ("G4 P.1"); gcode_cuttoz (dz * 4 / 5); gcode ("G4 P.1"); gcode_cuttoz (-dz * 4 / 5); gcode ("G4 P.1"); gcode_cuttoz (dz); gcode ("G4 P.1"); gcode_cuttoz (-dz); } void cnc_hammer_jig (void) { int y; wprintw (output, "\nCutting jig for GPIO hammer header\n"); wrefresh (output); wrefresh (input); if (Default_Units == undefined) { // don't } wprintw (output, "\nChange the tool for the appropriate PCB drill."); wprintw (output, "\nThe drill tool should already be placed exactly where you want the"); wprintw (output, "\nlower-left hole of the :::::::::::::::::::: to start, then drop the"); wprintw (output, "\ndrill down so it is touching the surface and lock the collet."); wprintw (output, "\nPress 'y' when you are ready to drill, or 'n' to cancel."); wrefresh (output); wrefresh (input); for (;;) { // loop over reading single characters, // looking for 'y' or 'n' - // any other character stays in this loop - we don't want an accidental // lean on the keyboard to cause ANY machine movement while changing the // tool! #ifdef ZERO_EVERY_TIME FD_ZERO (&inFds); FD_SET (STDIN_FILENO, &inFds); FD_SET (Data_From_CNC_Fd, &inFds); #endif if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) { if (errno != EINTR) errExit ("select"); // EINTR happens on window resize } do { } while (!FD_ISSET (STDIN_FILENO, &inFds)); y = 'n'; (void) read (STDIN_FILENO, &y, 1); if (y == 'y') { int x; gcode ("G20"); // inches gcode ("G91 (Relative moves)"); gcode_spindle_on (); for (x = 0; x < 20; x++) { cnc_hammer_jig_peck_drill (-0.3); // leaves back at 0. gcode_gotoz (0.05); // raise above zero gcode_gotoxy (0.1, 0.0); // move to next pin gcode_gotoz (-0.05); // lower to zero again } #ifdef NEVER gcode_gotoz (0.1); // raise above zero gcode_gotoxy (-2.0, 0.1); // start of second row gcode_gotoz (-0.1); // back to 0 level for (x = 0; x < 20; x++) { cnc_hammer_jig_peck_drill (-0.3); // leaves back at 0. gcode_gotoz (0.1); // raise above zero gcode_gotoxy (0.1, 0.0); // move to next pin gcode_gotoz (-0.1); // lower to zero again } gcode_gotoz (0.1); // raise above zero gcode_gotoxy (-2.0, -0.1); // back to starting x,y position gcode_gotoz (-0.1); // back to z0 #else gcode_spindle_off (); gcode_gotoz (0.1); // raise above zero gcode_gotoxy (-2.0, 0.0); // start of second row gcode_gotoz (-0.1); // back to 0 level #endif gcode ("G21"); // back to mm gcode ("G90 (Absolute moves)"); } else if (y == 'n') { wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input); return; } } } void cnc_pitrex_mailer(void) { // top/lhs mid bot/rhs // +--------------------------+ // | | | | // | +----+ | +----+ | o | // | | | | | | | | o | bot shell // | | | | | | | | o | // | +----+ | +----+ | o | // | | | | // | +__--+ | +_---+ | +----+ | // | | | | | | | | | | top shell // | | | | | | | | | | // | +~~--+ | +~---+ | +----+ | // | | | | // +--------------------------+ // int y; // TO DO: save a copy of this, redo tomorrow as laser, use script to generate ifdefs... gcode_spindle_off (); current_spindle_speed = 20000; current_feedrate = 1000; gcode ("F%d", current_feedrate); wprintw (output, "\nCut three layers of packing material to ship pitrex cases\n"); wrefresh (output); wrefresh (input); if (Default_Units == undefined) { // don't } wprintw (output, "\nPlease center the cutting tool in the middle of the plate"); wprintw (output, "\ntouching the top coroplast - this will be (0,0,0)."); wprintw (output, "\nPress 'y' when you are ready to cut, or 'n' to cancel. ('a' for air cut)"); wrefresh (output); wrefresh (input); for (;;) { // loop over reading single characters, // looking for 'y' or 'n' - // any other character stays in this loop - we don't want an accidental // lean on the keyboard to cause ANY machine movement while changing the // tool! #ifdef ZERO_EVERY_TIME FD_ZERO (&inFds); FD_SET (STDIN_FILENO, &inFds); FD_SET (Data_From_CNC_Fd, &inFds); #endif if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) { if (errno != EINTR) errExit ("select"); // EINTR happens on window resize } do { } while (!FD_ISSET (STDIN_FILENO, &inFds)); y = 'n'; (void) read (STDIN_FILENO, &y, 1); if ((y == 'y') || (y == 'a')) { int loops; float depth; const float move_depth = 10.0; const float NECK_LONG = 27.0; const float NECK_SHORT = 3.0; const float HALFGAP = 4.5; // vertical seperation const float SPACING = 52.0; // horizontal separation const float SHELL_WIDTH = 73.0; // increased from 71 left-to-right of shell const float SHELL_DEPTH = 71.0; // increased from 69 front-to-back of shell const float SHORT_TO_BAR = 13.5; // from front of shell to crossbar const float LONG_TO_BAR = 57.5; // from back of shell to crossbar const float tabgap = 6.0; // leave to cut with scissors so does not drop out float cut_depth = -6.5; // -4 to cut if flat. no more than 8! Wide margin for flexing if (y == 'a') cut_depth = 3.0; // can tweak this down to -0.5 for marking cut? // set to 0.0 if no retaining tabs wanted gcode ("G21"); // mm gcode ("G90 (absolute moves)"); gcode ("G10 L2 P1 X0 Y0"); // reset x,y // MUST START OUT AT CENTER! gcode ("G92 X0 Y0"); // same! gcode_gotoz (move_depth); // safe retract if (y == 'y') gcode_spindle_on (); // no spindle for air cuts! // center layer, upper area (lower half of shell) // bar: gcode_gotoxy (-SHELL_DEPTH/2.0+SHORT_TO_BAR, 0+HALFGAP+tabgap); // lower gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (-SHELL_DEPTH/2.0+SHORT_TO_BAR, SHELL_WIDTH+HALFGAP-tabgap); // cut bar, lower to upper gcode_gotoz (move_depth); // safe retract gcode_gotoxy (-SHELL_DEPTH/2.0, SHELL_WIDTH+HALFGAP); // move to top-left gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (+SHELL_DEPTH/2.0, SHELL_WIDTH+HALFGAP); // cut to top-right gcode_gotoxy (+SHELL_DEPTH/2.0, 0+HALFGAP); // cut to lower-right gcode_gotoxy (-SHELL_DEPTH/2.0, 0+HALFGAP); // cut to lower-left // should drop out now unless we leave a tab gap gcode_gotoz (move_depth); // safe retract // center layer, lower area (upper half of shell) // bar gcode_gotoxy (-SHELL_DEPTH/2.0+SHORT_TO_BAR, 0-HALFGAP-tabgap); // move gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (-SHELL_DEPTH/2.0+SHORT_TO_BAR, -SHELL_WIDTH-HALFGAP+tabgap); // cut bar, upper to lower gcode_gotoz (move_depth); // safe retract // TO DO: narrow neck on insertion part gcode_gotoxy (-SHELL_DEPTH/2.0+NECK_LONG, -SHELL_WIDTH-HALFGAP); // on edge gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (-SHELL_DEPTH/2.0+NECK_LONG, -SHELL_WIDTH-HALFGAP+NECK_SHORT); // up gcode_gotoxy (-SHELL_DEPTH/2.0, -SHELL_WIDTH-HALFGAP+NECK_SHORT); // across gcode_gotoxy (-SHELL_DEPTH/2.0, -SHELL_WIDTH-HALFGAP); // back down to lower left, normal starting point gcode_gotoxy (+SHELL_DEPTH/2.0, -SHELL_WIDTH-HALFGAP); // cut to lower right gcode_gotoxy (+SHELL_DEPTH/2.0, 0-HALFGAP); // cut to upper right gcode_gotoxy (-SHELL_DEPTH/2.0, 0-HALFGAP); // cut to upper left // would drop out except for tab gap // fix last part of neck gcode_gotoxy (-SHELL_DEPTH/2.0, 0-HALFGAP-NECK_SHORT); // down gcode_gotoxy (-SHELL_DEPTH/2.0+NECK_LONG, 0-HALFGAP-NECK_SHORT); // right gcode_gotoxy (-SHELL_DEPTH/2.0+NECK_LONG, 0-HALFGAP); // back up - neck is now cut out gcode_gotoz (move_depth); // safe retract // RHS (lowest layer) upper area (lower half of shell) // for the two lugs on crossbar: gcode_gotoxy (SPACING-0+SHORT_TO_BAR, SHELL_WIDTH+HALFGAP); gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (SPACING-0+SHORT_TO_BAR, 0+HALFGAP); // cut gcode_gotoz (move_depth); // safe retract // now 5 holes for supports (bottom half of shell) gcode_circle_abs (SPACING-0+SHORT_TO_BAR+38.0, (SHELL_WIDTH/2.0)+HALFGAP+12.0, 2.0, move_depth, cut_depth); gcode_circle_abs (SPACING-0+SHORT_TO_BAR+38.0, (SHELL_WIDTH/2.0)+HALFGAP-12.0, 2.0, move_depth, cut_depth); gcode_circle_abs (SPACING-0+SHORT_TO_BAR+8.0, (SHELL_WIDTH/2.0)+HALFGAP+20.0, 2.0, move_depth, cut_depth); gcode_circle_abs (SPACING-0+SHORT_TO_BAR+8.0, (SHELL_WIDTH/2.0)+HALFGAP, 3.0, move_depth, cut_depth); gcode_circle_abs (SPACING-0+SHORT_TO_BAR+8.0, (SHELL_WIDTH/2.0)+HALFGAP-20.0, 2.0, move_depth, cut_depth); // RHS (lowest layer), lower area (top half of shell) // bar: gcode_gotoz (move_depth); // safe retract gcode_gotoxy (SPACING-0+SHORT_TO_BAR, 0-HALFGAP-tabgap); gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (SPACING-0+SHORT_TO_BAR, -SHELL_WIDTH-HALFGAP+tabgap); // cut bar, top to low gcode_gotoz (move_depth); // safe retract gcode_gotoxy (SPACING-0, -SHELL_WIDTH-HALFGAP); // move to lower left corner gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (SPACING+SHELL_DEPTH, -SHELL_WIDTH-HALFGAP); // cut to lower right gcode_gotoxy (SPACING+SHELL_DEPTH, 0-HALFGAP); // cut to upper right gcode_gotoxy (SPACING-0, 0-HALFGAP); // cut to upper left // would drop out except for tab gap gcode_gotoz (move_depth); // safe retract // LHS (top layer) upper area (lower half of shell) // bar: gcode_gotoxy (-SPACING-SHELL_DEPTH+SHORT_TO_BAR, 0+HALFGAP); // move gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (-SPACING-SHELL_DEPTH+SHORT_TO_BAR, SHELL_WIDTH+HALFGAP); // move gcode_gotoz (move_depth); // safe retract gcode_gotoxy (-SPACING-SHELL_DEPTH, SHELL_WIDTH+HALFGAP); // start at top-left gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (-SPACING+0, SHELL_WIDTH+HALFGAP); // cut to top-right gcode_gotoxy (-SPACING+0, 0+HALFGAP); // cut to lower-right gcode_gotoxy (-SPACING-SHELL_DEPTH, 0+HALFGAP); // cut to lower-left // would drop out except for tab gap gcode_gotoz (move_depth); // safe retract // lhs (top layer) lower area (upper half of shell) // bar gcode_gotoxy (-SPACING-SHELL_DEPTH+SHORT_TO_BAR, 0-HALFGAP-tabgap); // move gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (-SPACING-SHELL_DEPTH+SHORT_TO_BAR, -SHELL_WIDTH-HALFGAP+tabgap); // cut bar top to bot gcode_gotoz (move_depth); // safe retract // TO DO: narrow neck on insertion part gcode_gotoxy (-SPACING-SHELL_DEPTH+NECK_LONG, -SHELL_WIDTH-HALFGAP); // move gcode_gotoz (cut_depth); // cut depth gcode_gotoxy (-SPACING-SHELL_DEPTH+NECK_LONG, -SHELL_WIDTH-HALFGAP+NECK_SHORT); // down gcode_gotoxy (-SPACING-SHELL_DEPTH, -SHELL_WIDTH-HALFGAP+NECK_SHORT); // left gcode_gotoxy (-SPACING-SHELL_DEPTH, -SHELL_WIDTH-HALFGAP); // up, to start at lower left - normal starting point gcode_gotoxy (-SPACING+0, -SHELL_WIDTH-HALFGAP); // cut to lower right gcode_gotoxy (-SPACING+0, 0-HALFGAP); // cut to upper right gcode_gotoxy (-SPACING-SHELL_DEPTH, 0-HALFGAP); // cut to upper left // would drop out here except for tab gap // fix other neck: gcode_gotoxy (-SPACING-SHELL_DEPTH, 0-HALFGAP-NECK_SHORT); // down gcode_gotoxy (-SPACING-SHELL_DEPTH+NECK_LONG, 0-HALFGAP-NECK_SHORT); // back gcode_gotoxy (-SPACING-SHELL_DEPTH+NECK_LONG, 0-HALFGAP); // up gcode_gotoz (move_depth); // safe retract gcode_spindle_off(); gcode_gotoxy (0,0); // park gcode ("?"); wprintw (output, "\nReturning to command mode.\n\nok"); wrefresh (output); wrefresh (input); } else if (y == 'n') { wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input); return; } } } void cnc_pitrex_cartridge (void) { int y; gcode_spindle_off (); current_spindle_speed = 20000; current_feedrate = 1000; gcode ("F%d", current_feedrate); wprintw (output, "\nCut a hole in a standard Vectrex cart for the Pi Zero WH\n"); wrefresh (output); wrefresh (input); if (Default_Units == undefined) { // don't } wprintw (output, "\nBy this point you should have carefully moved the cutting tool"); wprintw (output, "\nso that it is exactly touching the point of the 'insert' arrow, and"); wprintw (output, "\nhave quit and restarted this program (possibly also the proxy) so that"); wprintw (output, "\nthe point is true 0,0,0"); wprintw (output, "\nPress 'y' when you are ready to cut, or 'n' to cancel."); wrefresh (output); wrefresh (input); for (;;) { // loop over reading single characters, // looking for 'y' or 'n' - // any other character stays in this loop - we don't want an accidental // lean on the keyboard to cause ANY machine movement while changing the // tool! #ifdef ZERO_EVERY_TIME FD_ZERO (&inFds); FD_SET (STDIN_FILENO, &inFds); FD_SET (Data_From_CNC_Fd, &inFds); #endif if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) { if (errno != EINTR) errExit ("select"); // EINTR happens on window resize } do { } while (!FD_ISSET (STDIN_FILENO, &inFds)); y = 'n'; (void) read (STDIN_FILENO, &y, 1); if (y == 'y') { int loops; float depth; gcode ("G21"); // mm gcode ("G90 (absolute moves)"); gcode_gotoz (10.0); // raise above zero gcode_gotoxy (-26.300,60.500); // air cut to confirm, better be ready to hit ESTOP :-) gcode_gotoz (2.0); // raise above zero gcode_gotoxy (27.700,60.500); gcode_gotoxy (27.700,54.000); gcode_gotoxy (-26.300,54.000); gcode_gotoxy (-26.300,60.500); // back to start, ready to cut... gcode_spindle_on (); depth = 0.0; for (loops = 0; loops < 40; loops++) { gcode_gotoz(depth); gcode_gotoxy (27.700,60.500); gcode_gotoxy (27.700,54.000); gcode_gotoxy (-26.300,54.000); gcode_gotoxy (-26.300,60.500); // back to start, ready to cut... depth -= 0.1; // 4mm total } gcode_gotoz (2.0); // raise above zero gcode_spindle_off (); // retract gcode_gotoz (10.0); // raise above zero gcode_gotoxy (0.0,70.500); gcode ("?"); wprintw (output, "\nReturning to command mode.\n\nok"); wrefresh (output); wrefresh (input); } else if (y == 'n') { wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input); return; } } } int gcode_jog (char *direction, char *quantity, char *units) { // We'll use G1 to allow feedrate control, just in case this is // used for cutting by hand... char empty[MAX_LINE] = { '\0' }; if (Default_Units == undefined) { if (*units == '\0') { wprintw (output, "\nDefault units not set?"); wrefresh (debugline); wrefresh (output); wrefresh (input); return FALSE; } wprintw (output, "\nSetting default units to %s", units); wrefresh (debugline); wrefresh (output); wrefresh (input); if (strcmp (units, "mm") == 0) Default_Units = mm; else if (strcmp (units, "in") == 0) Default_Units = in; } else { if (*units == '\0') units = (Default_Units == mm ? "mm" : "in"); } wprintw (debugline, "\ndirection \"%s\" quantity \"%s\" units \"%s\"", direction, quantity, units); wrefresh (debugline); wrefresh (input); // override default units if (strcmp (units, (Default_Units == mm ? "mm" : "in")) != 0) { gcode (strcmp (units, "in") == 0 ? "G20 (inches)" : "G21 (mm)"); } gcode ("G91 (Relative moves)"); if (strcmp (direction, "left") == 0) { gcode ("G1 X-%s", quantity); } else if (strcmp (direction, "right") == 0) { gcode ("G1 X%s", quantity); } else if (strcmp (direction, "nearer") == 0) { gcode ("G1 Y-%s", quantity); } else if (strcmp (direction, "farther") == 0) { gcode ("G1 Y%s", quantity); } else if (strcmp (direction, "up") == 0) { gcode ("G1 Z%s", quantity); } else if (strcmp (direction, "down") == 0) { gcode ("G1 Z-%s", quantity); } gcode ("G90 (Absolute moves)"); // if units were overriden, restore the default here if (strcmp (units, (Default_Units == mm ? "mm" : "in")) != 0) { gcode (Default_Units == in ? "G20 (inches)" : "G21 (mm)"); } wrefresh (debugline); wrefresh (output); wrefresh (input); return TRUE; // for now } int gcode_default_units (char *units) { if (strcmp (units, "mm") == 0) { Default_Units = mm; gcode ("G21 (mm)"); } else if (strcmp (units, "in") == 0) { Default_Units = in; gcode ("G20 (inches)"); } else { Default_Units = undefined; gcode ("G21 (mm)"); // Only mm or in supported. but we'll use mm to keep unexpected moves to // a minimum } return TRUE; // for now } int gcode_probe (void) { char y; // G10 L2 P1 X0 Y0 Z0 (Redefine WPos zero position) wprintw (output, "\nMake sure the probe is below the tool and the clip is attached."); wprintw (output, "\nPress 'y' when you are ready to probe. Any other input cancels."); wrefresh (output); wrefresh (input); #ifdef ZERO_EVERY_TIME FD_ZERO (&inFds); FD_SET (STDIN_FILENO, &inFds); FD_SET (Data_From_CNC_Fd, &inFds); #endif if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) { if (errno != EINTR) errExit ("select"); // EINTR happens on window resize } do { } while (!FD_ISSET (STDIN_FILENO, &inFds)); y = 'n'; (void) read (STDIN_FILENO, &y, 1); if (y == 'y') { gcode ("S20"); // 20 mm/mi TO DO: if 'in' selected + restore gcode ("G38.2 F10 Z-10"); // probe Z, set z 0 gcode ("G92 Z19.7"); // set x y 0 } else { wprintw (output, "\nCancelled.\n\nok"); wrefresh (output); wrefresh (input); } wrefresh (output); wrefresh (input); return TRUE; // for now } int gcode_display_info (void) { // After homing, x,y,z = -499,-399,-1 i.e. backed off 1mm from end stops at // -500, -400, 0 // z is fully retracted. // $10=1 report MPos // $10=0 report WPos // gcode("$10=0"); // spindle speed on my machine. Probably 10000 on yours. // gcode("$30=20000"); // set maximum travel // gcode("$130=300"); // gcode("$131=180"); if (Default_Units == undefined) { wprintw (output, "\nNo default unit set - will not cut!"); wrefresh (output); wrefresh (input); } else { wprintw (output, "\nDefault units set to %s", (Default_Units == mm ? "mm" : "in")); wrefresh (output); wrefresh (input); } return TRUE; // for now } int main (int argc, char **argv) { int serial_port; // This is the curses version... // Input is always on the bottom line, while any asynchronous // data from the CNC scrolls immediately above it. int i = 2, height, width, ch, new_width, new_height; char *keyboard_line, *received_line, *debug_line, cnc_buf[MAX_LINE], keyboard_raw_input_buf[MAX_LINE], keyboard_line_buf[MAX_LINE], received_line_buf[MAX_LINE], debug_buf[MAX_LINE]; struct winsize ws; size_t numRead; char *s; // Get clean version of executable name. Should work on most existing // systems (2006) progname = argv[0]; if ((s = strrchr (progname, '/')) != NULL) progname = s + 1; // Unix if ((s = strrchr (progname, '\\')) != NULL) progname = s + 1; // M$ if ((s = strrchr (progname, ']')) != NULL) progname = s + 1; // Dec if ((s = strrchr (progname, ';')) != NULL) *s = '\0'; // Version no's if (((s = strrchr (progname, '.')) != NULL) && (strcasecmp (s, ".exe") == 0)) *s = '\0'; if (((s = strrchr (progname, '.')) != NULL) && (strcasecmp (s, ".com") == 0)) *s = '\0'; serial_port = 0; while ((argc > 1) && (*argv[1] == '-')) { if ((argc > 1) && (strcmp (argv[1], "-1") == 0)) { argv++; argc--; serial_port = 1; } if ((argc > 1) && (strcmp (argv[1], "-d") == 0)) { argv++; argc--; #ifdef DEBUG_PARSER // Tables included in header file? debug_parser = TRUE; #endif debug_runtime = TRUE; } if ((argc > 1) && (strcmp (argv[1], "-dp") == 0)) { argv++; argc--; #ifdef DEBUG_PARSER // Tables included in header file? debug_parser = TRUE; #endif } if ((argc > 1) && (strcmp (argv[1], "-dr") == 0)) { argv++; argc--; debug_runtime = TRUE; } if ((argc > 1) && (*argv[1] == '-') && (strcmp (argv[1], "-d") != 0) && (strcmp (argv[1], "-dp") != 0) && (strcmp (argv[1], "-dr") != 0)) { fprintf (stderr, "syntax: %s [-d[pr]]\n", progname); exit (1); } } #ifdef NEVER if (argc == 2) { rc = interpret (argv[1]); if (rc < 0) { fprintf (stderr, "Can't\n"); } else if (rc > 0) { fprintf (stderr, "Won't\n"); } else fprintf (stderr, "ok\n"); } else { fprintf (stderr, "syntax: %s [-d[pr]] 'command line'\n", progname); exit (1); } #endif // system("stty -F /dev/ttyUSB0 speed 115200 -brkint -icrnl -imaxbel -opost // -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke"); if (serial_port == 0) { system ("stty -F /dev/ttyUSB0 speed 115200 -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke > /dev/null"); } else { system ("stty -F /dev/ttyUSB1 speed 115200 -brkint -icrnl -imaxbel -opost -onlcr -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke > /dev/null"); } #ifdef NEVER Data_From_CNC_Fd = open ((serial_port == 0 ? "/dev/ttyUSB0" : "/dev/ttyUSB1"), O_RDONLY); if (Data_From_CNC_Fd < 0) { errExit ("open"); } #else Data_From_CNC_Fd = #endif Data_To_CNC_Fd = open ((serial_port == 0 ? "/dev/ttyUSB0" : "/dev/ttyUSB1"), O_RDWR /* O_WRONLY */ ); if (Data_From_CNC_Fd < 0) { errExit ("open"); } tcsendbreak (Data_To_CNC_Fd, 0); { char tmp = 'X' & 31; write (Data_To_CNC_Fd, &tmp, 1); } if (tcgetattr (STDIN_FILENO, &ttyOrig) == -1) errExit ("tcgetattr"); if (ioctl (STDIN_FILENO, TIOCGWINSZ, &ws) < 0) errExit ("ioctl-TIOCGWINSZ"); /* relay data between terminal and CNC */ /* Place terminal in raw mode so that we can pass all terminal input to the pseudoterminal master untouched - important for ^X (though we may use cooked mode later) */ /* Place terminal keyboard in raw mode (noncanonical mode with all input and output processing disabled). Save previous terminal settings. */ { struct termios t; if (tcgetattr (STDIN_FILENO, &t) == -1) /* return -1 */ ; ttyOrig = t; t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO); /* Noncanonical mode, disable signals, extended input processing, and echoing */ t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK); /* Disable special handling of CR, NL, and BREAK. No 8th-bit stripping or parity error handling. Disable START/STOP output flow control. */ t.c_oflag &= ~OPOST; /* Disable all output processing */ t.c_cc[VMIN] = 1; /* Character-at-a-time input */ t.c_cc[VTIME] = 0; /* with blocking */ if (tcsetattr (STDIN_FILENO, TCSAFLUSH, &t) == -1) /* return -1 */ ; } if (atexit (ttyReset) != 0) errExit ("atexit"); /* Loop, monitoring the terminal and serial port for input. If the terminal is ready for input, then read some bytes and write them to the CNC. If the CNC is replying with status information, then read some bytes and write them to the terminal. */ initscr (); getmaxyx (stdscr, height, width); debugline = newwin (4, width - 1, 0, 0); // one blank line as a separator output = newwin (height - 6, width - 1, 5, 0); input = newwin (1, width - 1, height - 1, 0); scrollok (debugline, TRUE); scrollok (output, TRUE); scrollok (input, TRUE); // keypad (stdscr, TRUE); noecho (); raw (); // cbreak (); // disable line-buffering // timeout (1); // wait 1 milliseconds for input wprintw (debugline, "\nAsynchronous and debugging messages will appear here."); wrefresh (debugline); for (i = 0; i < height; i++) wprintw (output, "\n"); wrefresh (output); wrefresh (input); // to move the cursor *(keyboard_line = keyboard_line_buf) = '\0'; *(received_line = received_line_buf) = '\0'; *(debug_line = debug_buf) = '\0'; gcode_display_info (); for (;;) { getmaxyx (stdscr, new_height, new_width); if (new_width != width || new_height != height) { // this isn't perfect detection of a window size change but it will do. height = new_height; width = new_width; delwin (output); delwin (input); output = newwin (height - 1, width - 1, 0, 0); input = newwin (1, width - 1, height - 1, 0); scrollok (debugline, TRUE); scrollok (output, TRUE); scrollok (input, TRUE); clear (); for (i = 0; i < height; i++) wprintw (output, "\n"); refresh (); wrefresh (output); wrefresh (input); // to move the cursor wprintw (input, "\n%s", keyboard_line_buf); wrefresh (input); // restore input line } // only ever do this once? FD_ZERO (&inFds); FD_SET (STDIN_FILENO, &inFds); FD_SET (Data_From_CNC_Fd, &inFds); if (select (Data_From_CNC_Fd + 1, &inFds, NULL, NULL, NULL) == -1) { if (errno != EINTR) errExit ("select"); // EINTR happens on window resize } // this is where we receive the results of executing a gcode command: if (FD_ISSET (Data_From_CNC_Fd, &inFds)) { /* CNC --> screen */ numRead = read (Data_From_CNC_Fd, cnc_buf, BUF_SIZE); if (numRead > 0) // exit(EXIT_SUCCESS); for (i = 0; i < numRead; i++) { ch = cnc_buf[i]; if (ch == '\r') continue; if (ch == '\n') { if ((*received_line_buf == '<') || (strncmp (received_line_buf, "[GC:", 4) == 0)) { if (status_requested) { wprintw (output, "\n%s", received_line_buf); wrefresh (output); status_requested = FALSE; } else { wprintw (debugline, "\n%s", received_line_buf); wrefresh (debugline); } wrefresh (input); } else if ((!expecting_ok) && (strcmp (received_line_buf, "ok") == 0)) { wprintw (debugline, "\n%s", received_line_buf); wrefresh (debugline); // don't forget to cancel 'expecting_ok' if we received either 'ok' or 'error' ... } else { if (strncasecmp (received_line_buf, "error:", 6) == 0) { // also need hold: and door: gcode_errnum = atoi (received_line_buf + 6); append_error (received_line_buf, gcode_errnum); } wprintw (output, "\n%s", received_line_buf); wrefresh (output); } if (expecting_ok) { if ((strcmp (received_line_buf, "ok") == 0) || (strncasecmp (received_line_buf, "error:", 6) == 0) || (strncasecmp (received_line_buf, "ALARM:", 6) == 0) || (strncasecmp (received_line_buf, "hold:", 5) == 0) || (strncasecmp (received_line_buf, "door:", 5) == 0) ) { expecting_ok = FALSE; } } *(received_line = received_line_buf) = '\0'; continue; } /* if (write (STDOUT_FILENO, cnc_buf, numRead) != numRead) fatal ("partial/failed write (STDOUT_FILENO)"); */ *received_line++ = ch; *received_line = '\0'; } } wprintw (input, "\n%s", keyboard_line_buf); // optimise this later by // only printing each // character as it arrives. wrefresh (input); if (FD_ISSET (STDIN_FILENO, &inFds)) { /* keyboard --> CNC */ int escapeFlag = 0; numRead = read (STDIN_FILENO, keyboard_raw_input_buf, BUF_SIZE); if (numRead <= 0) exit (EXIT_SUCCESS); i = -1; for (;;) { i = i + 1; if (i >= numRead) break; ch = keyboard_raw_input_buf[i]; if (escapeFlag == 2) { // has to come first as any of the special // characters might be a legit part of a // cursor sequence. escapeFlag = 0; switch (ch) { case 'A': // up wprintw (output, "\nfarther %d", (int) xy_jog_default); // defaults // not // really // implemented // yet gcode_jog ("farther", "1", ""); break; case 'B': // down wprintw (output, "\nnearer %d", (int) xy_jog_default); gcode_jog ("nearer", "1", ""); break; case 'C': // right wprintw (output, "\nright %d", (int) xy_jog_default); gcode_jog ("right", "1", ""); break; case 'D': // left wprintw (output, "\nleft %d", (int) xy_jog_default); gcode_jog ("left", "1", ""); break; default: wprintw (output, "\nunknown key sequence ^[[%c"); break; } wrefresh (output); wrefresh (input); continue; } else if ((ch == ('[' & 31)) && (escapeFlag == 0)) { // tiny state // machine // for cursor // key // sequence escapeFlag = 1; continue; // ideally would also include a timeout in // case user pressed ESC key... } else if ((ch == '[') && (escapeFlag == 1)) { escapeFlag = 2; continue; } else if ((keyboard_line == keyboard_line_buf) && (ch == ('X' & 31))) { // These // tests // for // immediate // characters // are // restricted // to // start // of // line // only // if (write (Data_To_CNC_Fd, &ch, 1) != 1) fatal ("partial/failed write (To_CNC)"); *(keyboard_line = keyboard_line_buf) = '\0'; wprintw (output, "\n^X (reset GRBL)"); wrefresh (output); wrefresh (input); } else if ((keyboard_line == keyboard_line_buf) && (ch == '~')) { if (write (Data_To_CNC_Fd, &ch, 1) != 1) fatal ("partial/failed write (To_CNC)"); *(keyboard_line = keyboard_line_buf) = '\0'; wprintw (output, "\n~ (cycle start)"); wrefresh (output); wrefresh (input); } else if ((keyboard_line == keyboard_line_buf) && (ch == '!')) { if (write (Data_To_CNC_Fd, &ch, 1) != 1) fatal ("partial/failed write (To_CNC)"); *(keyboard_line = keyboard_line_buf) = '\0'; wprintw (output, "\n! (feed hold)"); wrefresh (output); wrefresh (input); } else if ((keyboard_line == keyboard_line_buf) && (ch == '?')) { status_requested = TRUE; if (write (Data_To_CNC_Fd, &ch, 1) != 1) fatal ("partial/failed write (To_CNC)"); *(keyboard_line = keyboard_line_buf) = '\0'; wprintw (output, "\n? (current status)"); wrefresh (output); wrefresh (input); } else if ((keyboard_line == keyboard_line_buf) && (ch == '>')) { // extension to request MPos // $10=1 report MPos // $10=0 report WPos status_requested = TRUE; gcode ("$10=1"); wrefresh (output); wrefresh (input); ch = '?'; if (write (Data_To_CNC_Fd, &ch, 1) != 1) fatal ("partial/failed write (To_CNC)"); *(keyboard_line = keyboard_line_buf) = '\0'; wprintw (output, "\n? (current status)"); wrefresh (output); wrefresh (input); gcode ("$10=0"); wrefresh (output); wrefresh (input); } else if ((keyboard_line == keyboard_line_buf) && (ch == ' ')) { // non-standard // command // added // - // space // bar // is // easier // to // hit // in // an // emergency! ch = 'X' & 31; // (in addition to a real e-stop button. Just // in case.) // or should I be sending '!' ? if (write (Data_To_CNC_Fd, &ch, 1) != 1) fatal ("partial/failed write (To_CNC)"); *(keyboard_line = keyboard_line_buf) = '\0'; wprintw (output, "\n' ' (PAUSED!)"); // do more than 'feed hold'. // Treat like an extra // e-stop... wrefresh (output); wrefresh (input); } else if (ch == ('U' & 31)) { // cancel line. *(keyboard_line = keyboard_line_buf) = '\0'; wprintw (input, "\n"); wrefresh (input); } else if (ch == ('C' & 31)) { exit (0); } else if (ch == ('D' & 31)) { exit (0); } else if ((ch == '\r') || (ch == '\n')) { // \r from keyboard, // \n from a file... *keyboard_line = '\0'; // (should already be done) // move completed line from input window to output window as a log // of the echo. wprintw (output, "\n%s", keyboard_line_buf); wrefresh (output); wprintw (input, "\n"); wrefresh (input); if (local_command (keyboard_line_buf)) { // Check to see if we recognise this line as a special command // before defaulting it to a GCODE command. *(keyboard_line = keyboard_line_buf) = '\0'; } else { *keyboard_line++ = '\n'; *keyboard_line = '\0'; expecting_ok = TRUE; // regular gcode commands are sent here. Really need to // restructure this whole thing! if (write (Data_To_CNC_Fd, keyboard_line_buf, strlen (keyboard_line_buf)) != strlen (keyboard_line_buf)) fatal ("partial/failed write (To_CNC)"); *(keyboard_line = keyboard_line_buf) = '\0'; } } else if ((ch == ('H' & 31)) || (ch == 127)) { // erase if (keyboard_line > keyboard_line_buf) { *--keyboard_line = '\0'; *keyboard_line++ = ' '; *keyboard_line = '\0'; wprintw (input, "%s", keyboard_line_buf); wprintw (input, "\n"); wrefresh (input); *--keyboard_line = '\0'; wprintw (input, "%s", keyboard_line_buf); wrefresh (input); } } else if ((keyboard_line - keyboard_line_buf) < ((sizeof (keyboard_line_buf)) - 1)) { // regular buffered characters come through here *keyboard_line++ = ch; *keyboard_line = 0; wprintw (input, "\n%s", keyboard_line_buf); // we don't really // need to redraw the // whole line, but it // doesn't hurt. wrefresh (input); // Old one scrolls off. Easiest way to handle // it. Single-line window. } else { // buffer full perhaps?, do bell()? } } } } ttyReset (); return (EXIT_FAILURE); }