Parsing Arduino GRBL CNC Verbose String

Hi all,

i tried to find a way of searching the forum but unfortunately the only search I found was top right of web page which appears to search google not the forum, so if the question or similar has been asked before I apologise. A hint of how to search forum posts wouldn’t go amiss either.

Im looking for help parsing a serial string.

For those of you who know GRBL is code written by Sonny which will operate milling machines, routers etc

Grbl sends a data string back to the pc which looks like

<Idle|MPos:0.000,0.000,0.000|Bf:15,128|FS:0,1000|Ov:100,100,100|A:SF>

occasionally it outputs

<Idle|MPos:0.000,0.000,0.000|Bf:15,128|FS:0,1000|WCO:0.000,0.000,0.000>

Im wanting to parse the string using delimiters and store the results in an array which i can call back values from.

The data i’m interested in parsing is the spindle speed and the command acknowledgement.
FS:0,1000 is the feedrate and spindle speed (delimited by a comma) and A:SF is the indication that the coolant pump and spindle are running, where S represents the coolant/suds pump and F or C (snapshot was taken with spindle running in forward “F” - C being counterclockwise). I have worked through Robin2’s examples and zoomcat? but not been able to get exactly what i wanted. I have attached code which AWOL wrote which i can get to parse some of the data but it has 2 restrictions for me. The occasional change of string input from the serial port and secondly the code doesn’t use an array it just seems to dump data to serial port, I suspect because it is stepping through each character at a time.
The string length appears to stay the same length (apart from the occasional additional line which happens every fifth line for arguments sake).

I’m wanting to get the Spindle speed as an absolute minimum as i can read the grbl pins for The direction and coolant but it would be nice if i can get it all from the string.
For obvious reasons i cant go rewriting GRBL to provide the string in a more usable format.

//Input string example
//<Idle|MPos:0.000,0.000,0.000|Bf:15,128|FS:0,1000|Ov:100,100,100|A:SF>
//& occasionally
//<Idle|MPos:0.000,0.000,0.000|Bf:15,128|FS:0,1000|WCO:0.000,0.000,0.000> 

//code from AWOL-
//[quote author=AWOL link=msg=2515146 date=1449735549]

const int MAX_LEN = 69;
const char lineEnding = '>'; // whatever marks the end of your input.
char inputSentence [MAX_LEN + 1];
int inputIndex;
bool newInput;
int spindleSpeed=0;

const byte MAX_TOKENS = 20;
const char* delimiters = "|,:"; // whatever characters delimit your input string
char* tokens [MAX_TOKENS + 1];
enum indexName {angle, fuel, speed, altitude,and1,and2,and3,and4,and5,and6,and7,and8,and9,and10,and11,and12,and13,and14,and15};
#define PRINT_ITEM(x) printItem (x, #x)

void setup ()
{
  Serial.begin (115200);
}

void loop ()
{
  while (Serial.available() )
  {
    char readChar = Serial.read ();
    if (readChar == lineEnding)
    {
      newInput = true;
    }
    else
    {
      if (inputIndex < MAX_LEN)
      {
        inputSentence [inputIndex++] = readChar;
        inputSentence [inputIndex] = '\0';
      }
    }
  }

  if (newInput && strlen (inputSentence))
  {
    int tokenIndex = 0;
    //Serial.println (inputSentence); // tell 'em what you've got
    tokens [tokenIndex] = strtok (inputSentence, delimiters);
    while ((tokenIndex < MAX_TOKENS - 1) && tokens [tokenIndex])
    {
      tokenIndex++;
      tokens [tokenIndex] = strtok (NULL, delimiters);
    }

    //PRINT_ITEM (angle);
    //PRINT_ITEM (fuel);
    //PRINT_ITEM (speed);
    //PRINT_ITEM (altitude);
    //PRINT_ITEM (and1);
    //PRINT_ITEM (and2);
    //PRINT_ITEM (and3);
    //PRINT_ITEM (and4);
    //PRINT_ITEM (and5);
    //PRINT_ITEM (and6);
    PRINT_ITEM (and7);
    //PRINT_ITEM (and8);
    //PRINT_ITEM (and9);
    //PRINT_ITEM (and10);
    //PRINT_ITEM (and11);
    //PRINT_ITEM (and12);
    //PRINT_ITEM (and13);
   //PRINT_ITEM (and14);
   //PRINT_ITEM (and15);

    // reset things for the next lot.
    newInput = false;
    inputIndex = 0;
    inputSentence [0] = '\0';
  }
  //Serial.println (spindleSpeed);
}

void printItem (int index, char* name)
{
   // Serial.print (name);
  Serial.print (F(" "));
  Serial.println (tokens [index]);
  

  
}

PRINT_ITEM (and13) is the position of the A:SF command, but it disappears when the occasional data string is sent and returns A: when no commands are sent ie, coolant and spindle are off.

As you can appreciate im more mechanical than a programmer, i’m one of those cut and paste coders but i can do just about everything else from space to underwater so please be kind at my attempts of programming - especially you PaulS.

My ideas going forward are to read the entire string into a buffer based on the <> delimiters and then somehow split the string using first the “|” delimiter and then “,”. Using a switch/case statement to then look in the array at [13] and see if the ascii character are there if its decimal 0 then its the odd string and i can ignore acting upon it. If the command characters are awkward to split out then i will use hardware pins instead for direction and coolant.

The hardware setup is a pc connected to an arduino running grbl which operates a CNC machine. A second arduino (remote) is used to sniff data from the first arduinos (GRBL) TX pin. This works fantastically well - Trust me. and does not interfere with the PC-GRBL link or operation. There are many soothsayers here who cringe when you mention sniffing RX/TX lines. We used to do it all the time when computers were operated using DOS and still used magnetic tape as storage medium, for all the millennial’s that’s how the old boys checked for data on serial busses. Its still a valid tool.

Any help or direction would be very much appreciated

The end goal is to use the second arduino to send the Spindle and command data over an RS485 communication link (Re Nick gammon) to yet another arduino which will be connected to a variable frequency drive which drives a 3 phase spindle motor on my milling machine. The RS485 link is required as the VFD upsets the signals from grbl due to high frequency noise on the signal path. - Its a shame not to get grbl running my spindle as well as my CNC milling machine.

JT007:
Grbl sends a data string back to the pc which looks like

<Idle|MPos:0.000,0.000,0.000|Bf:15,128|FS:0,1000|Ov:100,100,100|A:SF>

Am I correct to think that you are looking for assistance with writing a PC program rather than an Arduino program?

If so there are many websites that can help you. This Forum is for help with writing Arduino programs.

…R

Hey Robin, star. you are the god of parse himself. I’m not worthy.

The code is for arduino, it is to run on the board I will use to listen to the GRBL messages. Ive looked through as many examples as i could find - mostly yours to be fair. I had found a comma delimited example which almost got there but for some reason stopped after about 8 iterations and it wasn’t the number of characters expected as i changed it to 150 for laughs and giggles but i still only got 8 values parsed out ?

The PC/GUI is connected to another arduino board running Grbl, it passes the G code messages to the grbl (arduino board) It in return passes coordinate positions, alarms & echo back to the PC. Its these messages im sniffing using an arduino board I’m calling the remote - The outcome will be that the remote will send messages to another Arduino board over RS485 that we can call the VFD interface board. It sounds bizare and a hole heap of Arduinos but to be accurate they are all self built circuits using ATmega328’s which are flashed with firmware and programmed via the IDE (why reinvent the wheel). The only way to get the data out of the PC/GRBL link is to sniff it. The uP on the GRBL board is maxed out in memory.

system setup.png

This text (from the Original Post)
<Idle|MPos:0.000,0.000,0.000|Bf:15,128|FS:0,1000|Ov:100,100,100|A:SF>
has two type of delimiter the | and a comma.

You need to parse the data twice. First break it into chunks based on the | and then break each of those chunks based on the comma.

If you can’t get that to work then post the program where you tried it.

…R
Serial Input Basics - simple reliable ways to receive data.

Hi Robin
From your Example 3

I achieved the following result (this being the string read into an array and then reprinted back through the serial port)

see image.

working my way through your excellent instructions, i have managed to parse the data based on the “|” delimiter.

// Example 5 - Receive with start- and end-markers combined with parsing

const byte numChars =71;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

      // variables to hold the parsed data
char messageFromPC[numChars] = {0};
int integerFromPC = 0;
float floatFromPC = 0.0;
// Variables to hold 
char GRBL_msg1 [numChars] = {0};
char GRBL_msg2 [numChars] = {0};
char GRBL_msg3 [numChars] = {0};
char GRBL_msg4 [numChars] = {0};
char GRBL_msg5 [numChars] = {0};
char GRBL_msg6 [numChars] = {0};
char GRBL_msg7 [numChars] = {0};
char GRBL_msg8 [numChars] = {0};

boolean newData = false;

//============

void setup() {
    Serial.begin(115200);
    //Serial.println("This demo expects 3 pieces of data - text, an integer and a floating point value");
    //Serial.println("Enter data in this style <HelloWorld, 12, 24.7>  ");
    Serial.println("Format of data read from GRBL");
    Serial.println("<Idle|MPos:0.000,0.000,0.000|Bf:15,128|FS:0,1000|Ov:100,100,100|A:SF>");
    Serial.println();
}

//============

void loop() {
    recvWithStartEndMarkers();
    if (newData == true) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        showParsedData();
        newData = false;
    }
}

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//============

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

  //parse for "|" first
  
  // machine state
    strtokIndx = strtok(tempChars,"|");      // get the first part - the string
    strcpy(GRBL_msg1, strtokIndx); // copy it to messageFromPC
  // machine position   
    strtokIndx = strtok(NULL,"|");      // get the first part - the string
    strcpy(GRBL_msg2, strtokIndx); // copy it to messageFromPC   
  // BF god knows - alarms ?
    strtokIndx = strtok(NULL,"|");      // get the first part - the string
    strcpy(GRBL_msg3, strtokIndx); // copy it to messageFromPC
  // feed and spindle speed
    strtokIndx = strtok(NULL,"|");      // get the first part - the string
    strcpy(GRBL_msg4, strtokIndx); // copy it to messageFromPC
  //Ov Overides 1% to 200%
    strtokIndx = strtok(NULL,"|");      // get the first part - the string
    strcpy(GRBL_msg5, strtokIndx); // copy it to messageFromPC
  //machine command status
    strtokIndx = strtok(NULL,"|");      // get the first part - the string
    strcpy(GRBL_msg6, strtokIndx); // copy it to messageFromPC
  // anything else
    strtokIndx = strtok(NULL,"|");      // get the first part - the string
    strcpy(GRBL_msg7, strtokIndx); // copy it to messageFromPC
    strtokIndx = strtok(NULL,"|");      // get the first part - the string
    strcpy(GRBL_msg8, strtokIndx); // copy it to messageFromPC
    
 
//    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
//    integerFromPC = atoi(strtokIndx);     // convert this part to an integer
//
//    strtokIndx = strtok(NULL, ",");
//    floatFromPC = atof(strtokIndx);     // convert this part to a float

}

//============

void showParsedData() {
    Serial.println("Message ");
    Serial.println(GRBL_msg1);
    //Serial.print("Integer ");
    Serial.println(GRBL_msg2);
    //Serial.print("Float ");
    Serial.println(GRBL_msg3);
    Serial.println(GRBL_msg4);
    Serial.println(GRBL_msg5);
    Serial.println(GRBL_msg6);
    Serial.println(GRBL_msg7);
    Serial.println(GRBL_msg8);
}

The results are quite promising

There are a lot of null messages displayed however when an external device ie the spindle or the coolant pump is “ON” I get
Idle
MPos:0.000,0.000,0.000
Bf:15,128
FS:0,999
Ov:100,100,100
A:SF
This pattern always comes together
Ov:100,100,100 is the machine overrides for spindle feedrate and rapid moves in a 1% to 200%
A:SF Is “A” don’t know why sonny uses A and SF is the is the spindle direction and coolant controls.

So if I look for Ov in its array and when true (not null) look at the next array for A:SF

But
If coolant is “ON” on its own = A:F
When spindle is “ON” on its own = A:C counter clockwise or A:S forward

If the spindle and coolant are on together the coolant takes the 2nd character but when its “ON” on its own it’s the 1st character – this is a royal pain in the butt.

Despite the two message format from the serial string and the amount of null values stored in the array at least I can use the
Ov:100,100,100
A:SF
Pattern to find the functions.

When the coolant and spindle commands are off
The pattern is

Ov:100,100,100
(and null) = blank array

I will now parse the individual parts of the grbl message
Mainly the (FS) feed and speed array and the Ov to see if I can get the machine functions displayed.

Being a rough git, im just going to repeat the same method used to parse the string into blocks to parse each block out - or at least the two blocks (arrays) i’m interested in.

We can keep working on my coding education as i get more of an idea what i’m doing.
mechanical engineers must be a nightmare to you guys.

JT007:
The results are quite promising

If I am to help you need to post the complete incoming message followed by the result of parsing it.

...R

H Robin

Woohoo it works real good.

For reference - the two example lines i gave at the start of the post are the strings received from the serial port.

I’m sure its possible for you to knock great big holes in my code and would appreciate any hints you can offer to improve it. It still needs commented and abandoned lines removed but for all intense and purposes its usable for generating commands for me to transmit over RS485. i will update this post with the full code including the RS485 Comms on completion.

many thanks for your assistance.
J.

// From Robin2
// Example 5 - Receive with start- and end-markers combined with parsing

//====================================================================
// Parse code for GRBL CNC
// 13/11/2018
// Ver 0.2
//====================================================================

const byte numChars =71;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

//Feed and Speed variables
char FSMsgGRBL[12];
int FS_FeedMsg = 0;
int FS_SpeedMsg = 0;

//Overide variables
bool OvMsg = false; //Ov message being transmitted
int CmdCoolant = 0;
int CmdSpindleCW = 0;


// Variables to hold Message blocks

char GRBL_msg1 [numChars] = {0};
char GRBL_msg2 [numChars] = {0};
char GRBL_msg3 [numChars] = {0};
char GRBL_msg4 [numChars] = {0};
char GRBL_msg5 [numChars] = {0};
char GRBL_msg6 [numChars] = {0};


boolean newData = false;

//============

void setup() {
    Serial.begin(115200);
    //Serial.println("This demo expects 3 pieces of data - text, an integer and a floating point value");
    //Serial.println("Enter data in this style <HelloWorld, 12, 24.7>  ");
    Serial.println("Format of data read from GRBL");
    Serial.println("<Idle|MPos:0.000,0.000,0.000|Bf:15,128|FS:0,1000|Ov:100,100,100|A:SF>");
    Serial.println();
}

//============

void loop() {
    recvWithStartEndMarkers();
    if (newData == true) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        showParsedData();
        newData = false;
    }
}

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}

//============

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

  //Parse into Blocks based on "|" Delimiter
  
        // machine state
            strtokIndx = strtok(tempChars,"|"); // Find first part of string (Machine State Idle, Run, Alarm) 
            strcpy(GRBL_msg1, strtokIndx);      // write characters from string to machine state variable
            
        // machine position   
            strtokIndx = strtok(NULL,"|"); // look for next data block in string  
            strcpy(GRBL_msg2, strtokIndx); // write characters from string to machine position variable 
             
        // BF god knows - alarms ?
            strtokIndx = strtok(NULL,"|"); // look for next data block in string      
            strcpy(GRBL_msg3, strtokIndx); // write characters from string to BF variable  
            
        // feed and spindle speed
            strtokIndx = strtok(NULL,"|"); // look for next data block in string     
            strcpy(GRBL_msg4, strtokIndx); // write characters from string to Feed&Speed variable 
             
        //Ov Overides 1% to 200%
            strtokIndx = strtok(NULL,"|"); // look for next data block in string  
            strcpy(GRBL_msg5, strtokIndx); // write characters from string to Overide variable 
             
        //machine command status
            strtokIndx = strtok(NULL,"|"); // look for next data block in string  
            strcpy(GRBL_msg6, strtokIndx); // write characters from string to machine command status variable 
            
        //anything else
   
}

 
//============

void showParsedData() {
  
    ParseFS ();
    ParseOv_A ();  
    
  //Parse into Blocks based on "|" Delimiter
    //Serial.println("Message ");
    //Serial.println(GRBL_msg1);
    //Serial.println(GRBL_msg2);   
    //Serial.println(GRBL_msg3);
    //Serial.println(GRBL_msg4);
    //Serial.println(GRBL_msg5);
    //Serial.println(GRBL_msg6);
   // Serial.println(" ========== ");
   // Serial.println(FSMsgGRBL);
    //Serial.println(FS_FeedMsg);
    //Serial.println(FS_SpeedMsg);

//    Serial.println(inputInt);
//    Serial.println(inputFloat);
//    Serial.println(inputCsvString);

  

//    if (CmdCoolant ==0 ){
//     Serial.println("Coolant Off");
//     
//    }
//    else {
//     Serial.println("Coolant On"); 
//    }
//    
//    if (CmdSpindleCW ==0) {
//     Serial.println("Reverse");
//     
//    }
//    else {
//     Serial.println("Forward"); 
//    }
    
                   Serial.print("Spindle Speed ");
                   Serial.print(FS_SpeedMsg);
                   Serial.print(" Spindle ");
                   Serial.print(CmdSpindleCW);
                   Serial.print("  Coolant ");
                   Serial.println(CmdCoolant);      

    

}


//Feeds and speeds  FS Message GRBL ////////////////////////////////////////////////////////////
void ParseFS ()
{
//GRBL_msg4 array containing Feed & Speed message characters
   
    char * partOfString; // this is used by strtok_r() as an index
    
    partOfString = strtok (GRBL_msg4,":"); // get the first part - the string
    strcpy(FSMsgGRBL, partOfString);     // copy it to inputCsvString
    
    partOfString = strtok (NULL, ","); // this continues where the previous call left off
    FS_FeedMsg = atoi(partOfString);     // convert this part to an integer
    
    partOfString = strtok (NULL, ","); // this continues where the previous call left off
    FS_SpeedMsg = atoi(partOfString);   // convert this part to a float// actually an int
 
}

// Parse Overide Ov Message GRBL ////////////////////////////////////////////////////////////
void ParseOv_A ()
{
  //GRBL_msg5 array containing Overide message characters  
   
      if (GRBL_msg5[0] == 'O')// Ov statement when true check machine command instructions
      
      {
              if (GRBL_msg6[0] == 'A')
               {
                
              Serial.print("Got A ");
              Serial.println(GRBL_msg6);
              
                      
              if (GRBL_msg6[2] == 'S')
                    {
                    CmdSpindleCW =1;
                 
                    }
              if(GRBL_msg6[2] == 'C')
                    {
                    CmdSpindleCW = 0;
                            
                    }
                    
         
                if (GRBL_msg6[2] == 'F'|(GRBL_msg6[3] == 'F'))
                    {
                    CmdCoolant = 1;           
                    }                                                    
                else
                   {
                   (CmdCoolant = 0);      
                                          
                   }
                                                         
              }//End - seeking "A" in GRBL_msg6
              
              if(strlen(GRBL_msg6) == 0) // makes sure spindle direction and coolant conditions are off woth no signal present
              {
              CmdSpindleCW = 0;  
              CmdCoolant = 0;  
              }                                
      }
      
}

JT007:
Woohoo it works real good.

Good to know,

...R

The Transmitter code which is sniffed from grbl serial comms

//==========
// parseGRBLRS485.ino
// Original GRBL Parse code with RS485 comms added
// Parse code for GRBL CNC
// 13/11/2018
// Ver 1.0
//=========

#include "RS485_protocol.h"
#include <SoftwareSerial.h>

//  RS485 Communication variables 

SoftwareSerial rs485 (2, 3);  // receive pin, transmit pin
 
const byte ENABLE_PIN = 4;
int channel = 0;
int deviceID = 1;
int function = 2;
int data = 0;

const byte LED_PIN = 13;

// Parsing GRBL message variables

const byte numChars =71;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

//Feed and Speed variables
char FSMsgGRBL[12];
int FS_FeedMsg = 0;
int FS_SpeedMsg = 0;
byte FS_speed1=0;
byte FS_speed2=0;


//Overide variables
bool OvMsg = false; //Ov message being transmitted
int CmdCoolant = 0;
int CmdSpindleCW = 0;

// Variables to hold Message blocks

char GRBL_msg1 [numChars] = {0};
char GRBL_msg2 [numChars] = {0};
char GRBL_msg3 [numChars] = {0};
char GRBL_msg4 [numChars] = {0};
char GRBL_msg5 [numChars] = {0};
char GRBL_msg6 [numChars] = {0};


boolean newData = false;

//=========

// callback routines RS485 Comms
  
void fWrite (const byte what)

      {
      rs485.write (what);  
      }
      
    int fAvailable ()
      {
      return rs485.available ();  
      }
    
    int fRead ()
      {
      return rs485.read ();  
      }


void setup() {
    
    
    Serial.begin(115200);
    rs485.begin (28800);
    pinMode (ENABLE_PIN, OUTPUT);  // RS485 IC driver output enable
    pinMode (LED_PIN, OUTPUT);  // built-in LED - lights on transmission error

}  // end of setup

//============

void loop() {

  byte level = (data);
  
    recvWithStartEndMarkers();
    if (newData == true) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        outputParsedData();
        newData = false;
        
        
    }
}  // End of loop

//============

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
}// End of recvWithStartEndMarkers

//============

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

  //Parse into Blocks based on "|" Delimiter
  
        // machine state
            strtokIndx = strtok(tempChars,"|"); // Find first part of string (Machine State Idle, Run, Alarm) 
            strcpy(GRBL_msg1, strtokIndx);      // write characters from string to machine state variable
            
        // machine position   
            strtokIndx = strtok(NULL,"|"); // look for next data block in string  
            strcpy(GRBL_msg2, strtokIndx); // write characters from string to machine position variable 
             
        // BF god knows - alarms ?
            strtokIndx = strtok(NULL,"|"); // look for next data block in string      
            strcpy(GRBL_msg3, strtokIndx); // write characters from string to BF variable  
            
        // feed and spindle speed
            strtokIndx = strtok(NULL,"|"); // look for next data block in string     
            strcpy(GRBL_msg4, strtokIndx); // write characters from string to Feed&Speed variable 
             
        //Ov Overides 1% to 200%
            strtokIndx = strtok(NULL,"|"); // look for next data block in string  
            strcpy(GRBL_msg5, strtokIndx); // write characters from string to Overide variable 
             
        //machine command status
            strtokIndx = strtok(NULL,"|"); // look for next data block in string  
            strcpy(GRBL_msg6, strtokIndx); // write characters from string to machine command status variable 
            
        //anything else
   
} //End of parseData

 
//============

void outputParsedData() 
     {
  
          ParseFS (); // Parse feed and speed values from FS Block
          ParseOv_A (); //Reads first character in buffer looking for "O" then knows commands follow "A:" (A:SF Buffer)         

                     
         DoBusComs ();//Transmit values over RS485 link - Order of precedence goes here as parse has to happen first  

    }//end of outputParsedData


//Feeds and speeds  FS Message GRBL 


void ParseFS ()
{
  
//GRBL_msg4 array containing Feed & Speed message characters
   
    char * partOfString; // this is used by strtok_r() as an index
    FS_SpeedMsg = 0;
    FS_speed1 = 0;
    FS_speed2 = 0;
    
    partOfString = strtok (GRBL_msg4,":"); // get the first part - the string
    strcpy(FSMsgGRBL, partOfString);     // copy it to inputCsvString
    
    partOfString = strtok (NULL, ","); // this continues where the previous call left off
    FS_FeedMsg = atoi(partOfString);     // convert this part to an integer
    
    partOfString = strtok (NULL, ","); // this continues where the previous call left off
    FS_SpeedMsg = atoi(partOfString);   // convert this part to a float// actually an int

    
        FS_speed1 = (FS_SpeedMsg>>8);
        FS_speed2 = (FS_SpeedMsg & 0xFF);
 
}//End of ParseFS

// Parse Overide Ov Message GRBL 

void ParseOv_A ()
{
  //GRBL_msg5 array containing Overide message characters  
   
      if (GRBL_msg5[0] == 'O')// Ov statement when true check machine command instructions
      
      {
              if (GRBL_msg6[0] == 'A')
               {
                
              Serial.print("Got A ");
              Serial.println(GRBL_msg6);
              
                      
              if (GRBL_msg6[2] == 'S')
                    {
                    CmdSpindleCW =1;
                 
                    }
              if(GRBL_msg6[2] == 'C')
                    {
                    CmdSpindleCW = 0;
                            
                    }
                    
         
                if (GRBL_msg6[2] == 'F'|(GRBL_msg6[3] == 'F'))
                    {
                    CmdCoolant = 1;           
                    }                                                    
                else
                   {
                   (CmdCoolant = 0);      
                                          
                   }
                                                         
              }//End - seeking "A" in GRBL_msg6
              
              if(strlen(GRBL_msg6) == 0) // makes sure spindle direction and coolant conditions are off woth no signal present
              {
              CmdSpindleCW = 0;  
              CmdCoolant = 0;  
              }                                
      }
      
} // end of ParseOv_A 

// RS485 Communication command structure 
void DoBusComs ()
  {
        
    // assemble message - what to send to Variable Frequency Drive /Spindle Drive
    byte msg [] = { 
       deviceID,     // device 1
       function,     // Used to expand future messages fixed at 2 (currently type 2 = master transmit message - future could be type 3 ask for analogue response etc)
       FS_speed1,  // spindle speed High byte
       FS_speed2,  // spindle speed Low byte
       CmdSpindleCW, // spindle direction
       CmdCoolant,   // activate suds pump
       data          // Spare data block
    };
  
    // send to slave  
    digitalWrite (ENABLE_PIN, HIGH);  // enable sending
    sendMsg (fWrite, msg, sizeof msg);
    digitalWrite (ENABLE_PIN, LOW);  // disable sending
     }

And for the receive:-

#include <SoftwareSerial.h>
#include "RS485_protocol.h"
#include <Wire.h>
#include <Adafruit_MCP4725.h>
      
Adafruit_MCP4725 dac;

// RS485 Communications driver ////////////////////////////////////////////////

SoftwareSerial rs485 (2, 3);  // receive pin, transmit pin
const byte ENABLE_PIN = 4;
int channel = 1;// Device ID
int OutVal =99;
int OutVal2 =100;
int Inputvalue =0; // Input value from Master

// Mechanical states //////////////////////////////////////////////////////////
      
const int SudsPump =  10;
const int SpinFWD =  9;
const int SpinREV =  8;

// Incoming messages  /////////////////////////////////////////////////////////

int FS_SpeedMsg = 0;  // spindle speed
int CmdSpindleCW = 0; // spindle direction
int CmdCoolant = 0;   // activate suds pump
int data  = 0;        // Spare data block

// Variables will change: ////////////////////////////////////////////////////

int SudsPumpState = LOW;
int SpinFWDState = LOW;
int SpinREVState = LOW;
int dummy;
int PWM = 9;
int inDIR = 10;
int Suds = 11;
bool Fwd_Rev;
int ch1;//spindle pwm
//int sensorValue = 100;        // value read from the pot
int outputValue = 0;        // value output to the PWM (analog out)

// ===============================================================

void fWrite (const byte what)
  {
  rs485.print (what);  
  }
  
int fAvailable ()
  {
  return rs485.available ();  
  }

int fRead ()
  {
  return rs485.read ();  
  }

// =======================================================================
    
void setup()
{
  rs485.begin (28800);
 //Serial.begin(9600);
  pinMode (ENABLE_PIN, OUTPUT);  // driver output enable
  
 dac.begin(0x60);
      
      
        // initialize digital output.
        pinMode(SudsPump, OUTPUT);
        pinMode(SpinFWD, OUTPUT);
        pinMode(SpinREV, OUTPUT);
//        pinMode(PWM, INPUT);
//        pinMode(Suds, INPUT);
//        pinMode(inDIR, INPUT);
}

// =======================================================================

void loop()
{
  byte buf [20];
  FS_SpeedMsg=0;
  byte received = recvMsg (fAvailable, fRead, buf, sizeof (buf) - 1);
  
  if (received)
    {
       //Serial.println ("Got Sonthing");
    if (buf [0] != channel)
      return;  // not my device
      
    if (buf [1] != 2)//Function
      return;  // unknown command


FS_SpeedMsg |= ((int)buf [2]) << 8;
FS_SpeedMsg |= ((int)buf [3]) ;
CmdSpindleCW = buf [4];
CmdCoolant = buf [5];

     // comment out on completion - sending serial messages not required, debug only 
//     Serial.print("Spindle Speed ");
//     Serial.print(FS_SpeedMsg);
//     Serial.print(" Spindle ");
//     Serial.print(CmdSpindleCW);
//     Serial.print("  Coolant ");
//     Serial.println(CmdCoolant); 

     InverterOutput ();

    // Sending master a return message - No required/used
//    byte msg [] = {
//       0,  // device 0 (master)
//       3,  // turn light on command received
//      OutVal,
//      OutVal2,
//    };
// 
//    delay (1);  // give the master a moment to prepare to receive
//    digitalWrite (ENABLE_PIN, HIGH);  // enable sending
//    sendMsg (fWrite, msg, sizeof msg);
//    //Serial.print (sizeof msg);
//     //Serial.print (int(msg[0]));
//    delayMicroseconds (660);
//    digitalWrite (ENABLE_PIN, LOW);  // disable sending
    
   

  
   
   }  // end if something received
   
}  // end of loop

void InverterOutput () 
{
  
// RPM

        ch1= FS_SpeedMsg; // Spindle speed value fom incomming RS485 message  
        //Serial.println (ch1); 
        dac.setVoltage((ch1 * 0.55), false); //17~4095 full range Generates 4.1 x ch to give 4095 max o/p
        //o/p is clipped for some reason outputing 8.67 volts only not 10v as expected
        //need to check VFD output

// Direction Relay Output Conditions       
      
        // 10 is minimum RPM - if a value less than 10 is sent the spindle will shut off
        if (ch1 > 10) {
        Fwd_Rev = CmdSpindleCW; // direction value fom incomming RS485 message
      
          SpinFWDState = Fwd_Rev;
          SpinREVState = !Fwd_Rev;
          //Serial.println (SpinFWDState);
          //Serial.println (SpinREVState);
      
        }
        else {
          SpinFWDState = 0;
          SpinREVState = 0;
        }
      
// Coolant Machine Commands
      
      SudsPumpState = CmdCoolant;// value fom incomming RS485 message
      
        if (SudsPumpState == 1) {
          digitalWrite(SudsPump, HIGH);   // turn the LED on (HIGH is the voltage level)
      
        }
        else {
          //Energise the reset relay
      
          digitalWrite(SudsPump, LOW);    // turn the LED off by making the voltage LOW
          //                //delay by so many m/s then
          //                //deenergise reset relay
        }

// Direction Relay Outputs
        
        //
        if (SpinFWDState == 1) {
          //
          digitalWrite(SpinFWD, HIGH);   // turn the LED on (HIGH is the voltage level)
        }
        else {
          digitalWrite(SpinFWD, LOW);    // turn the LED off by making the voltage LOW
          //
        }
        //
        if (SpinREVState == 1) {
          digitalWrite(SpinREV, HIGH);   // turn the LED on (HIGH is the voltage level)
        }
        else {
          digitalWrite(SpinREV, LOW);    // turn the LED off by making the voltage LOW
          //
        }  
}

Cheers Robin sent Karma