NMEA serial sentence converter

I have a sailboat with a rather old wind instrument package on it. The instrument processor talks to the display instruments using NMEA codes. I recently bought a new autopilot and it accepts NMEA codes and can use them to do things like steer to a relative wind heading. However, the autopilot was expecting a different format than the old instrument package puts out. Thus, I needed to make a translator that could listen to the data going to the instruments, and when it saw the right sentence, convert it and send it back out in the new format.

All that said, there are NMEA multiplexers out there already that will do this (like the one from Brookhouse) but I had wanted to play with something like an Arduino for some time.

Please note that I am NOT a programmer of any sort. I hack at what others have done until it sort of does what I want it to do. So I am sure that there were better ways of doing what I did. Somebody has probably written a library that will do this in three function calls, but there you go. At least I can read my code and figure out what it does.

If you have suggestions for how I could have made this better, please feel free to post improvements or let me know!

 /*

Code intended to listen to NMEA data stream and convert any sentance that is in the MWV format to the VWR format

Currently Is:                                            Should Be:                                
$--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh                        $--MWV,x.x,a,x.x,a*hh                    
1) Wind direction magnitude in degrees 0-180            1) Wind Angle, 0 to 360 degrees          
2) Wind direction Left/Right of bow                      2) Reference, R = Relative, T = True     
3) Speed                                                 3) Wind Speed                            
4) N = Knots                                             4) Wind Speed Units, K/M/N                
5) Speed                                                 5) Status, A = Data Valid                
6) M = Meters Per Second                                 6) Checksum                              
7) Speed                                         
8) K = Kilometers Per Hour                                         
9) Checksum                                         

Code written by Paul Carroll, pogcarr at yahoo.com, 4-28-2009
Liberally borrowed from code found on Arduino Playground: 
   Parallax GPS interpreter by igor.gonzalez.martin@gmail.com 05-04-2007

 */ 
 #include <string.h>
 #include <ctype.h>

 int ledPin = 13;                  // LED test pin (insert where needed to test code)
 int rxPin = 0;                    // RX PIN 
 int txPin = 1;                    // TX PIN
 int byteNMEA=-1;                  // a byte of the NMEA stream cominig in on serial port
 char linea[300] = "";             // a string buffer that we write each byteNMEA to
 char HdrStr[5] = "VWR";           // the header of the sentence we are trying to find to convert
 char Str1[10] = "";               // string for use in manipulating the wind heading info
 char Str2[10] = "";               // string for use in manipulating the wind velocity info
 char CSSentence[85] = "" ;        //string for sentance assembly so we can checksum it
 int count=0;
 int check=0;
 int counta=0;
 int index[10];
 int relhdg = 0;                    // relative heading in degrees
 int cs ;                            //checksum




 void setup() {
   pinMode(ledPin, OUTPUT);       // Initialize LED pin
   pinMode(rxPin, INPUT);
   pinMode(txPin, OUTPUT);
   Serial.begin(4800);
   for (int i=0;i<300;i++){       // Initialize a buffer for received data
     linea[i]=' ';
   }   
 }

 void loop() {
   digitalWrite(ledPin, HIGH);    // Program got to here (loop began)
   byteNMEA=Serial.read();         // Read a byte of the serial port
   if (byteNMEA == -1) {           // See if the port is empty yet
     delay(100); 
   } else {
     linea[counta]=byteNMEA;        // If there is serial port data, it is put in the buffer
     counta++;
//     Serial.print(byteNMEA,BYTE);  //  comment out to just add in corrected statements, best to hook output of this into input channel of NMEA multiplexer.
//     if left in, echo mode if you will, we send each byte on down the serial line.  However, I don't think there is anything implemented to prevent crashes with data we will generate, so need to test.
     if (byteNMEA==13){            // If the received byte is = to 13 (CR), end of transmission
       digitalWrite(ledPin, LOW); // Program found an end of transmission character in datastream
       counta=0;
       count=0;
       check=0;
       for (int i=4;i<7;i++){     // Check characters of string to verify if the received command starts with $VWMWV
         if (linea[i]==HdrStr[i-4]){
           check++;
         }
       }
          if(check==3){                         // If we reached 3 it is a match, continue and process the data
         for (int j=0;j<strlen(linea);j++){    //now we need to figure out how to parse the string
           if (linea[j]==','){                // check for the position of the  "," separators
             index[count]=j;
             count++;
           }
           if (linea[j]=='*'){                // ... and the "*"
             index[8]=j;
             count++;
           }
         }
//Now that we know there the break points in the sentance are, we need to convert the numerical portion to numbers
//Relative heading is between indices0-1 and velocity in knots between indecise2-3


        strncpy(Str1,linea+index[0]+1,index[1]-index[0]-1);      //copy the section between the indices to Str1
        Str1[strlen(Str1)+1]='\0';                                // add the pesky \0 to keep C programming language happy
        relhdg= atoi(Str1);
        if(linea[index[1]+1]=='L'){                  //if the hdg indicator is L for measured from left bow, switch it to R
          relhdg = 360 - relhdg;
        }
        itoa(relhdg,Str1,10);                       //converting  integer to ascii text (base 10)

          
        strncpy(Str2,linea+index[2]+1,index[3]-index[2]-1);   //copy section between these vertices for Str2  (wind velocity) which we assume is coming in in knots!!!
        Str2[strlen(Str2)+1]='\0';

        
      //Now assemble a sentance of the various parts so that we can calculate the proper checksum
          strcpy(CSSentence,"PIMWV,");                    //The string could be kept the same as what it came in as, but I chose to use the P (prefix for all proprietary devices) and I for instrument
          strcat(CSSentence,Str1);
          strcat(CSSentence, ".0,R,");                      //the atoi command truncated the decimals, might as well stick .0 back on in case something is expecting this field to have a decimal
          strcat(CSSentence,Str2);
          strcat(CSSentence, ",N,A*");                  //Our instruments get the wind velocity in knots, we will be putting it back out in knots
          
        //Now that we have a string fully assembled, we can walk through it and calculate the checksum that will be required for any NMEA sentance
        
         cs=0;                                            //clear any old checksum
         for (int n=0; n < strlen(CSSentence); n++) {      
          cs ^= CSSentence[n];                            //calculates the checksum
         }
           Serial.print('

);                             // Assemble the final message and send it out the serial port
          Serial.print(CSSentence);
          Serial.print(cs, HEX);
          Serial.println();
          strcpy(CSSentence,"");            //clears the strings for reuse
          strcpy(Str1,"");
          strcpy(Str2,"");
          strcpy(linea,"");

}
      }        
    }  
}

Well, I wanted to post the updated version of this code. Originally, it didn’t work quite as planned and the system would really do bad things when one or more sentences would get mangled for whatever reason, so I went back in and did some improvements to the code to be sure that the values were valid numbers and the like.
My previous comments about my programming skills still apply so take this for what it is worth (probably exactly what you paid for it…)

/* Final Version, need to get working with NMEA hardware

Code intended to listen to NMEA data stream and convert any sentence that is in the MWV format to the VWR format

Currently Is:                                            Should Be:                                
$--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh                        $--MWV,x.x,a,x.x,a*hh                    
1) Wind direction magnitude in degrees 0-180            1) Wind Angle, 0 to 360 degrees          
2) Wind direction Left/Right of bow                      2) Reference, R = Relative, T = True    
3) Speed                                                 3) Wind Speed                            
4) N = Knots                                             4) Wind Speed Units, K/M/N                
5) Speed                                                 5) Status, A = Data Valid                
6) M = Meters Per Second                                 6) Checksum
7) Speed                                        
8) K = Kilometers Per Hour                                        
9) Checksum                                        

Code written by Paul Carroll, pogcarr at yahoo.com, 9-25-2010
Liberally borrowed from code found on Arduino Playground:
   Parallax GPS interpreter by igor.gonzalez.martin@gmail.com 05-04-2007

 */
 #include <string.h>
 #include <ctype.h>

 int ledPin = 13;                  // LED test pin (insert where needed to test code)
 int rxPin = 0;                    // RX PIN
 int txPin = 1;                    // TX PIN
 int byteNMEA=-1;                  // a byte of the NMEA stream cominig in on serial port
 char linea[300] = "";             // a string buffer that we write each byteNMEA to
 char HdrStr[5] = "VWR";           // the header of the sentence we are trying to find to convert
 char Str1[10] = "";               // string for use in manipulating the wind heading info
 char Str2[10] = "";               // string for use in manipulating the wind velocity info
 char OldWindSpd[10] = "";          //variable to keep previous known wind speed
 char CSSentence[85] = "" ;        //string for sentance assembly so we can checksum it
 int count=0;
 int check=0;
 int counta=0;
 int n=0;
 int m=0;
 int index[10];
 int relhdg = 0;                    // relative heading in degrees
 int lastHDG = 0;                         // variable to hold last known heading value
 int cs ;                            //checksum




 void setup() {
   pinMode(ledPin, OUTPUT);       // Initialize LED pin
   pinMode(rxPin, INPUT);
   pinMode(txPin, OUTPUT);
   Serial.begin(4800);
   for (int i=0;i<300;i++){       // Initialize a buffer for received data
     linea[i]=' ';
   }  
 }

 void loop() {
   digitalWrite(ledPin, HIGH);    // Program got to here (loop began)
   byteNMEA=Serial.read();         // Read a byte of the serial port
   if (byteNMEA == -1) {           // See if the port is still empty
     delay(100);
   } else {
     linea[counta]=byteNMEA;        // If there is serial port data, it is put in the buffer
     counta++;
//     Serial.print(byteNMEA,BYTE);  //  comment out to just add in corrected statements, best to hook output of this into input channel of NMEA multiplexer.
//     if left in, echo mode if you will, we send each byte on down the serial line.  However, I don't think there is anything implemented to prevent crashes with data we will generate, so need to test.
     if (byteNMEA==36){            // If the received byte is = to 36 ($), beginning of transmission 
       digitalWrite(ledPin, LOW); // Program found a beginning of transmission character in datastream
       counta=1;
       count=0;
       check=0;
         linea[0]=byteNMEA;
       for (int i=3;i<6;i++){     // Check characters of string to verify if the received command starts with $xxVWR
/*             Serial.print(i);
  //           Serial.print("  linea = ");      //This section was used for debugging
    //         Serial.print(linea[i]);
      //       Serial.print("   HdrStr = ");
        //     Serial.println(HdrStr[i-3]);
*/
         if (linea[i]==HdrStr[i-3]){
           check++;                            //increment a flag for every correct character in the string
//             Serial.println(check);
         }
       }
          if(check==3){                         // If we reached 3 it is a match, continue and process the data
         for (int j=0;j<strlen(linea);j++){    //now we need to figure out how to parse the string
           if (linea[j]==','){                // check for the position of the  "," separators
             index[count]=j;
             count++;
           }
           if (linea[j]=='*'){                // ... and the "*"
             index[8]=j;
             count++;
           }
         }
//Now that we know there the break points in the sentance are, we need to convert the numerical portion to numbers
//Relative heading is between indices0-1 and velocity in knots between indecise2-3
         m=0;
         n=0;
        strncpy(Str1,linea+index[0]+1,index[1]-index[0]-1);      //copy the section between the indices to Str1
//          Serial.println(Str1);                               //Debugging checkpoint
        for (int k=0;k<strlen(Str1);k++){
            if (!isdigit(Str1[k])){            //walk through Str1 and check that each chacter is a numeric digit
               n++;                                //if not, set a flag
            }
          }

          if(linea[index[1]+1]!='R'&& linea[index[1]+1]!='L'){
            n++;                                                  //set a flag if the relative heading flag is not either an R or an L
          }
 
          if (atoi(Str1)<0 || atoi(Str1)>180){                        //This checks to be sure the heading number is a reasonable one
              n++;
            }
          
          if (n!=0){                                                // if the flag go set, then we will simply use the last known good value
           relhdg=lastHDG;
          } else { 
            
             Str1[strlen(Str1)+1]='\0';                          // add the pesky \0 to keep C programming language happy        
            relhdg= atoi(Str1);                                    // if we got to here things are looking good, so now we need to be able to manipulate the numbers so convert to integer
        
            if(linea[index[1]+1]=='L'){                  //if the hdg indicator is L for measured from left bow, switch it to R
            relhdg = 360 - relhdg;
          }
            lastHDG=relhdg;                            //And we will reset the lastHDG variable since we got a new good one
        }
          itoa(relhdg,Str1,10);                       //converting  integer to ascii text (base 10)

        strncpy(Str2,linea+index[2]+1,index[3]-index[2]-1);   //copy section between these vertices for Str2  (wind velocity) which we assume is coming in in knots!!!
        for (int k=0;k<strlen(Str2);k++){
            if (isdigit(Str2[k]) || Str2[k]=='.'){            // similarly, we need to walk through the wind speed string to make sure it only has numerics and a period
            } else {
              m++;                                                // set a flag if it has anything else
 //             Serial.println("non-numeric");
            }
          }
          if (m!=0){
            strcpy(Str2,OldWindSpd);                          // if there were bad characters, copy OldWindSpd to the Str2 string
          } else {
            strcpy(OldWindSpd,Str2);                          //otherwise, reset the OldWindStr to the new good value
          }

        Str2[strlen(Str2)+1]='\0';                          //add the pesky /0

        
      //Now assemble a sentance of the various parts so that we can calculate the proper checksum
          strcpy(CSSentence,"IIMWV,");                    //The string could be kept the same as what it came in as, but I chose to use the P (prefix for all proprietary devices) and I for instrument
          strcat(CSSentence,Str1);
          strcat(CSSentence, ".0,R,");                      //the atoi command truncated the decimals, might as well stick .0 back on in case something is expecting this field to have a decimal
          strcat(CSSentence,Str2);
          strcat(CSSentence, ",N,A*");                  //Our instruments get the wind velocity in knots, we will be putting it back out in knots
          
        //Now that we have a string fully assembled, we can walk through it and calculate the checksum that will be required for any NMEA sentance
        
         cs=0;                                            //clear any old checksum
         for (int n=0; n < strlen(CSSentence); n++) {
          cs ^= CSSentence[n];                            //calculates the checksum
         }
           Serial.print('

);                             // Assemble the final message and send it out the serial port
          Serial.print(CSSentence);
          Serial.print(cs, HEX);
          Serial.println();
//             delay(1000);                      // not really sure what value this brought and it slowed the program a bunch
          clearStr(CSSentence);            //clears the strings for reuse
          clearStr(Str1);
          clearStr(Str2);
          strcpy(linea,"");

}
      }        
    }  
}

// Function to clear a string
void clearStr (char* str) {
  int len = strlen(str);
  for (int c = 0; c < len; c++) {
     str[c] = 0;
  }
}