Code for GPS

The following is the code for the node-red function to parse the $GPGGA sentence. There is a similar function for the $GPVTG sentence, but it is a little simpler. This code is still in beta, so some other improvements are likely.

if ( isValid(msg.payload) ) {
    
    let degrees = 0;
    let garminTimeOfFix = 0;
    let garminLatitude = 0;
    let garminLongitude = 0;
    let garminQuality = 0;
    let garminAltitude = 0;
    let garminSatellites = 0;
    let garminHDOP = 0;
    
    let sentence = msg.payload.split(",");

    if ((sentence.length > 10) && (sentence[0] == "$GPGGA" )) {

        if (sentence[1] !== "" && !isNaN(parseInt(sentence[1]))) {
            garminTimeOfFix = sentence[1];
        } else {
            garminTimeOfFix = "n/a";
        }

        if (sentence[2] !== "" && sentence[3] !== "" && !isNaN(parseFloat(sentence[2]))) {
            degrees = Math.floor(parseFloat(sentence[2]) / 100);
            if (sentence[3] == "N") {
                garminLatitude = degrees + (parseFloat(sentence[2]) / 100 - degrees) / 60 * 100;
            }
            if (sentence[3] == "S") {
                garminLatitude = - degrees - (parseFloat(sentence[2]) / 100 - degrees) / 60 * 100;
            }
        } else {
            garminLatitude = 0;
        }
        
       if (sentence[4] !== "" && sentence[5] !== "" && !isNaN(parseFloat(sentence[4]))) {
            degrees = Math.floor(parseFloat(sentence[4]) / 100);
            if (sentence[5] == "E") {
                garminLongitude = degrees + (parseFloat(sentence[4]) / 100 - degrees) / 60 * 100;
            }
            if (sentence[5] == "W") {
                garminLongitude = - degrees - (parseFloat(sentence[4]) / 100 - degrees) / 60 * 100;
            }
        } else {
            garminLongitude = 0;
        }
        
        if (sentence[6] !== "" && !isNaN(parseInt(sentence[6]))) {
            garminQuality = parseFloat(sentence[6]);
        } else {
            garminQuality = 0;
        }
    
        if (sentence[7] !== "" && !isNaN(parseInt(sentence[7]))) {
            garminSatellites = parseFloat(sentence[7]);
                if ( garminSatellites > 30) {
                    garminSatellites = 0;
                }
        } else {
            garminSatellites = 0;
        }
    
        if (sentence[8] !== "" && !isNaN(parseFloat(sentence[8]))) {
            garminHDOP = parseFloat(sentence[8]);
        } else {
            garminHDOP = 0;
        }
    
        if (sentence[9] !== "" && sentence[10] == "M" && !isNaN(parseFloat(sentence[9]))) {
            garminAltitude = parseFloat(sentence[9]);
        } else {
            garminAltitude = 0;
        }
        
        msg.payload = [{
            fixtime: garminTimeOfFix,
            latitude: garminLatitude,
            longitude: garminLongitude,
            quality: garminQuality,
            satellites: garminSatellites,
            hdop: garminHDOP,
            altitude: garminAltitude,
        },
        {
            source: "GPS",
            sensor: "garmin18x-GGA"
        }];

        return msg;
    }

}

function checksum(_command) {
    var command = _command[0] === '$' ? _command.slice(1) : _command;

    var checksum = 0;
    for(var i = 0; i < command.length; i++) {
        checksum = checksum ^ command.charCodeAt(i);
    }

    var hex = Number(checksum).toString(16).toUpperCase();
    if (hex.length < 2) {
        hex = ("00" + hex).slice(-2);
    }

    return hex;
}

function isValid(_command) {
    var _checksum = _command.slice(-4,-2);
    var command = _command.slice(1).slice(0, -5);
    
    return _checksum === checksum(command);
}

Many thanks to the following open source project (https://github.com/vesteraas/nmea-checksum)