org.shampoo.goldenembed.parser.GoldenEmbedParserMain.java Source code

Java tutorial

Introduction

Here is the source code for org.shampoo.goldenembed.parser.GoldenEmbedParserMain.java

Source

/*
 * Copyright (c) 2009 Justin F. Knotzke (jknotzke@shampoo.ca)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

package org.shampoo.goldenembed.parser;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.shampoo.goldenembed.elevation.AltitudePressure;
import org.shampoo.goldenembed.elevation.GoogleElevation;
import org.shampoo.goldenembed.tools.FusionTables;
import org.shampoo.goldenembed.tools.GnuPlot;
import org.shampoo.goldenembed.tools.IntervalBean;
import org.shampoo.goldenembed.tools.Intervals;
import org.shampoo.goldenembed.tools.RideWithGPS;
import org.shampoo.goldenembed.tools.Strava;

public class GoldenEmbedParserMain {
    static final byte MESG_RESPONSE_EVENT_ID = 0x40;
    static final byte MESG_CAPABILITIES_ID = 0x54;
    static final byte MESG_BROADCAST_DATA_ID = 0x4E;
    static final byte MESG_TX_SYNC = (byte) 0xA4;
    static final byte MESG_CHANNEL_SEARCH_TIMEOUT_ID = 0x44;
    static final byte MESG_ASSIGN_CHANNEL_ID = 0x42;
    static final byte MESG_CHANNEL_RADIO_FREQ_ID = 0x45;
    static final byte MESG_CHANNEL_MESG_PERIOD_ID = 0x43;
    static final byte MESG_OPEN_CHANNEL_ID = (byte) 0x4B;
    static final byte MESG_CHANNEL_ID_ID = (byte) 0x51;
    static final byte MESG_NETWORK_KEY_ID = 0x46;
    static final byte MESG_CHANNEL_EVENT_ERROR = 0x01;
    static final byte NEW_LINE = (byte) 0x0A;
    static final double PI = 3.14159265;
    static final double KNOTS_TO_KILOMETERS = 1.85200;

    File outFile = null;

    boolean isFirstRecordedTime = true;
    long firstRecordedTime = 0;

    public float totalTrans = 0;
    public float totalChecksumError = 0;
    public float totalGPSError = 0;
    public long totalSpikes = 0;
    boolean noGSC = false;
    int startTime = 0;

    private static final String spacer1 = "    ";
    private static final String spacer2 = "        ";

    String rideDate;

    List<GoldenCheetah> gcArray = new ArrayList<GoldenCheetah>();

    Power power;
    SpeedCad speedCad;
    GoldenCheetah gc = new GoldenCheetah();
    int pos = 0; // Main Buffer Position

    boolean debug = false;
    boolean megaDebug = false;
    PrintWriter fout;
    String serElevationPath;

    GoogleElevation googleElevation;
    AltitudePressure altiPressure;
    Options options = new Options();

    String outGCFilePath;
    String outGnuPlotPath;
    String intervalParam;
    boolean wantsGoogleElevation = false;
    String serializedElevationPath = null;

    LogManager lm = LogManager.getLogManager();
    Logger logger = null;
    String logFilePath = "";
    String username = null;
    String password = null;
    String stravaUsername = null;
    String stravaPassword = null;
    String rideWithGPSUsername;
    String rideWithGPSPassword;

    boolean isGPS = false;
    long smoothFactor = 0;

    /**
     * @param args
     */

    public static void main(String[] args) {

        new GoldenEmbedParserMain(args);
    }

    public static int unsignedByteToInt(byte b) {
        return b & 0xFF;
    }

    public GoldenEmbedParserMain() {
        power = new Power(debug, megaDebug, this);
    }

    private void initGCFile(int year, int month, int day, int hour, int minute, int second) {
        fout.write("<!DOCTYPE GoldenCheetah>\n");
        fout.write("<ride>\n");
        fout.write(spacer1 + "<attributes>\n");
        fout.write(spacer2 + "<attribute key=\"Start time\" value=\"" + formatDate(year) + "/" + formatDate(++month)
                + "/" + formatDate(day) + " " + formatDate(hour) + ":" + formatDate(minute) + ":"
                + formatDate(second) + " UTC\" />\n");
        fout.write(spacer2 + "<attribute key=\"Device type\" value=\"Golden Embed GPS\" />\n");
        fout.write(spacer1 + "</attributes>\n");
        fout.write("<samples>\n");
    }

    private String formatDate(int _toFormat) {

        String toFormat = String.valueOf(_toFormat);
        if (toFormat.length() < 2)
            toFormat = "0" + toFormat;
        return toFormat;

    }

    public GoldenEmbedParserMain(String[] args) {

        Option inputFile = OptionBuilder.withArgName("inputfile").hasArg()
                .withDescription("Input file GoldenEmbedGPS").create("inputfile");

        Option outputGCFile = OptionBuilder.withArgName("outgc").hasArg()
                .withDescription("Directory of where to write the Golden Cheetah File").create("outgc");

        Option outputGnuPlotFile = OptionBuilder.withArgName("outgnuplot").hasArg()
                .withDescription("Directory of where to write the gnuplot file").create("outgnuplot");

        Option debugOption = OptionBuilder.withArgName("debug").hasArg().withDescription("Level of debug")
                .create("debug");

        Option intervalOption = OptionBuilder.withArgName("interval").hasArg()
                .withDescription("Interval Format: MM:SS+MM ex: 62:00+20").create("interval");

        Option usernameOption = OptionBuilder.withArgName("username").hasArg()
                .withDescription("Username for Fusion Tables").create("username");

        Option passwordOption = OptionBuilder.withArgName("password").hasArg()
                .withDescription("Password for Fusion Tables").create("password");

        Option serializedElevationOption = OptionBuilder.withArgName("serelevation").hasArg()
                .withDescription("To use Google Elevation Service set path to serialized elevation file")
                .create("serelevation");

        Option gpsOption = OptionBuilder.withArgName("gps").hasArg().withDescription("true for GEGPS false for GE")
                .create("gps");

        Option smoothOption = OptionBuilder.withArgName("smooth").hasArg()
                .withDescription("Enter amount in seconds for smoothing factor").create("smooth");

        Option stravaUser = OptionBuilder.withArgName("strava_user").hasArg()
                .withDescription("Enter your Strava Username (email)").create("strava_user");

        Option stravaPass = OptionBuilder.withArgName("strava_pass").hasArg()
                .withDescription("Enter your Strava Password").create("strava_pass");

        Option rideWithGPSUser = OptionBuilder.withArgName("ridewithgps_user").hasArg()
                .withDescription("Ride with Gps Username").create("ridewithgps_user");

        Option rideWithGPSPass = OptionBuilder.withArgName("ridewithgps_pass").hasArg()
                .withDescription("Ride with Gps Password").create("ridewithgps_pass");

        options.addOption(smoothOption);
        options.addOption(inputFile);
        options.addOption(outputGCFile);
        options.addOption(outputGnuPlotFile);
        options.addOption(debugOption);
        options.addOption(intervalOption);
        options.addOption(usernameOption);
        options.addOption(passwordOption);
        options.addOption(serializedElevationOption);
        options.addOption(gpsOption);
        options.addOption(stravaPass);
        options.addOption(stravaUser);
        options.addOption(rideWithGPSUser);
        options.addOption(rideWithGPSPass);

        // create the parser
        CommandLineParser parser = new GnuParser();
        try {
            // parse the command line arguments
            CommandLine line = parser.parse(options, args);

            // Load up the file
            File file = null;
            power = new Power(debug, megaDebug, this);
            speedCad = new SpeedCad();

            // has the buildfile argument been passed?
            if (line.hasOption("debug")) {
                // initialise the member variable
                String strDebug = line.getOptionValue("debug");
                if (strDebug.equalsIgnoreCase("debug"))
                    debug = true;
                else if (strDebug.equalsIgnoreCase("megaDebug"))
                    megaDebug = true;
            }

            if (!line.hasOption("inputfile")) {
                printUsage();
                System.exit(1);
            } else
                file = new File(line.getOptionValue("inputfile"));

            if (line.hasOption("outgc"))
                outGCFilePath = line.getOptionValue("outgc");

            if (line.hasOption("outgnuplot"))
                outGnuPlotPath = line.getOptionValue("outgnuplot");

            if (line.hasOption("interval"))
                intervalParam = line.getOptionValue("interval");

            if (line.hasOption("serelevation"))
                serializedElevationPath = line.getOptionValue("serelevation");

            if (line.hasOption("gps"))
                isGPS = true;

            if ((line.hasOption("username") == true || line.hasOption("password") == true)) {
                username = line.getOptionValue("username");
                password = line.getOptionValue("password");

                if (username.length() == 0 || password.length() == 0) {
                    printUsage();
                    System.exit(1);
                }
            }

            if ((line.hasOption("strava_user") == true || line.hasOption("strava_pass") == true)) {
                stravaUsername = line.getOptionValue("strava_user");
                stravaPassword = line.getOptionValue("strava_pass");

                if (stravaUsername.length() == 0 || stravaPassword.length() == 0) {
                    printUsage();
                    System.exit(1);
                }
            }

            if ((line.hasOption("ridewithgps_user") == true || line.hasOption("ridewithgps_pass") == true)) {
                rideWithGPSUsername = line.getOptionValue("ridewithgps_user");
                rideWithGPSPassword = line.getOptionValue("ridewithgps_pass");

                if (rideWithGPSUsername.length() == 0 || rideWithGPSPassword.length() == 0) {
                    printUsage();
                    System.exit(1);
                }
            }

            try {
                if (line.hasOption("smooth"))
                    smoothFactor = Long.parseLong(line.getOptionValue("smooth"));
            } catch (NumberFormatException ex) {
                printUsage();
                System.exit(1);
            }

            System.out.println("Input File: " + file.getAbsolutePath());
            byte[] readBytes;
            try {
                readBytes = getBytesFromFile(file);
                while (pos != file.length())
                    pos = readBuffer(readBytes, file.getParent());

                System.out.println("\n\nTotal Failed Checksums: " + totalChecksumError
                        + " Out of Total ANT Messages: " + totalTrans);
                System.out.println("% ANT Failure: " + (totalChecksumError / totalTrans) * 100.0);
                System.out.println(
                        "Total Failed GPS: " + totalGPSError + " Out of Total ANT Messages: " + totalTrans);
                System.out.println("% GPS Failure: " + (totalGPSError / totalTrans) * 100.0);
                System.out.println("Total Failed Messages: " + (totalChecksumError + totalGPSError)
                        + " Out of Total ANT Messages: " + totalTrans);
                System.out.println("% Failure: " + ((totalChecksumError + totalGPSError) / totalTrans) * 100.0);
                writeOutGCRecords();
                System.exit(0);

            } catch (IOException e) {
                if (logger != null)
                    logger.log(Level.SEVERE, e.toString());
                else
                    System.out.println(e);
            }
        } catch (ParseException exp) {
            // oops, something went wrong
            System.out.println("Parsing failed.  Reason: " + exp.getMessage());
            printUsage();
        } catch (SecurityException e) {
            logger.log(Level.SEVERE, e.toString());
        }

    }

    public void printUsage() {
        HelpFormatter formatter = new HelpFormatter();
        formatter.printHelp("java -jar GoldenEmbedParser.jar", options);
    }

    private void ANTparseHRM(byte[] msgData, GoldenCheetah gc) {
        int i = 5;
        byte aByte;
        int end = i + 8;
        int hrCountFinder = 0;
        int hr = 0;

        for (; i < end; i++) {
            aByte = msgData[i];
            if (megaDebug)
                System.out.println("Converting 0x" + UnicodeFormatter.byteToHex(msgData[i]));
            if (hrCountFinder == 6) { // HR is the sixth byte
                if (megaDebug)
                    System.out.println("Converting 0x" + UnicodeFormatter.byteToHex(msgData[i]));
                hr = unsignedByteToInt(aByte);
                if (debug)
                    System.out.println("Heart Rate is: " + hr);
            } else if (megaDebug)
                System.out.println("o" + i + "=" + unsignedByteToInt(aByte));
            hrCountFinder++;
        }

        gc.setHr(hr);
    }

    private void ANTrxMsg(byte[] rxIN, int size, GoldenCheetah gc) {
        int i = 2;
        if (megaDebug)
            System.out.println("Converting 0x" + UnicodeFormatter.byteToHex(rxIN[i]));
        switch (rxIN[i]) {
        case MESG_RESPONSE_EVENT_ID:
            if (debug)
                System.out.println("ID: MESG_RESPONSE_EVENT_ID\n");
            ANTresponseHandler(rxIN, size, gc);
            break;
        case MESG_CAPABILITIES_ID:
            if (debug)
                System.out.println("ID: MESG_CAPABILITIES_ID\n");
            i = ANTCfgCapabilties(i, size); // rxBuf[3] .. skip sync, size, msg
            break;
        case MESG_BROADCAST_DATA_ID:
            if (debug)
                System.out.println("ID: MESG_BROADCAST_DATA_ID\n");
            Byte aByte = new Byte(rxIN[++i]);
            int chan = aByte.intValue();
            if (chan == 0)
                ANTparseHRM(rxIN, gc);
            else if (chan == 1)
                power.ANTParsePower(rxIN, size, gc, gcArray);
            break;

        case MESG_CHANNEL_ID_ID:
            if (debug)
                System.out.println("ID: MESG_CHANNEL_ID_ID\n");
            ANTChannelID(rxIN, gc);
            break;
        default:
            if (debug)
                System.out.println("ID: Unknown 0x" + UnicodeFormatter.byteToHex(rxIN[i]));
        }
        return;
    }

    public void ANTChannelID(byte[] msgIN, GoldenCheetah gc) {
        byte[] devNo = new byte[2];

        int i = 2;
        devNo[0] = msgIN[i];
        if (megaDebug)
            System.out.println("Device Type is: 0x" + UnicodeFormatter.byteToHex(msgIN[i]));

        devNo[1] = msgIN[i];
        if (megaDebug)
            System.out.println("Device Type is: 0x" + UnicodeFormatter.byteToHex(msgIN[i]));

        int deviceNum = byteArrayToInt(devNo, 0, 2);
        if (debug)
            System.out.println("Device Number is: " + deviceNum);
        if (debug)
            System.out.println("Device Type is: 0x" + UnicodeFormatter.byteToHex(msgIN[i]));
        if (debug)
            System.out.println("Man ID is: 0x" + UnicodeFormatter.byteToHex(msgIN[i]) + "\n");

        return;
    }

    public byte[] getBytesFromFile(File file) throws IOException {
        InputStream is = new FileInputStream(file);

        // Get the size of the file
        long length = file.length();

        // You cannot create an array using a long type.
        // It needs to be an int type.
        // Before converting to an int type, check
        // to ensure that file is not larger than Integer.MAX_VALUE.
        if (length > Integer.MAX_VALUE) {
            // File is too large
        }

        // Create the byte array to hold the data
        byte[] bytes = new byte[(int) length];

        // Read in the bytes
        int offset = 0;
        int numRead = 0;
        while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
            offset += numRead;
        }

        // Ensure all the bytes have been read in
        if (offset < bytes.length) {
            throw new IOException("Could not completely read file " + file.getName());
        }

        // Close the input stream and return bytes
        is.close();
        if (megaDebug)
            System.out.println("Total Bytes: " + bytes.length);
        return bytes;
    }

    private boolean ANTrxHandler(byte[] rxBuf, GoldenCheetah gc) {
        int msgN = 0;
        int i;
        int size = 0;
        boolean inMsg = true;
        boolean errorFlag = true;

        for (i = 0; i < rxBuf.length; i++) {
            if (rxBuf[i] == MESG_TX_SYNC && inMsg) {
                inMsg = false;
                msgN = 0; // Always reset msg count if we get a sync
                msgN++;
                errorFlag = false;
                totalTrans++;
                if (megaDebug)
                    System.out.println("RX: [sync]");
            } else if (msgN == 1) {
                Byte aByte = new Byte(rxBuf[i]);
                msgN++; // Size
                size = aByte.intValue();
            } else if (msgN == 2) {
                byte checksum = checkSum(rxBuf, size);
                if (checksum == rxBuf[size + 3]) // Check if chksum = msg
                // chksum
                {
                    inMsg = true;
                    // Handle Message
                    ANTrxMsg(rxBuf, size, gc);
                    msgN++;
                    break;
                } else {
                    if (megaDebug)
                        System.out.println("CheckSum Mismatch 0x" + UnicodeFormatter.byteToHex(rxBuf[size + 3])
                                + "!=: 0x" + UnicodeFormatter.byteToHex(checksum));
                    msgN = 0;
                    inMsg = true;
                    totalChecksumError++;
                    errorFlag = true;
                    return errorFlag;
                }
            } else
                return errorFlag;
        }
        return errorFlag;
    }

    private void closeGCFile() {
        fout.write(spacer1 + "</samples>\n");
        fout.write("</ride>\n");
        fout.flush();
        fout.close();
    }

    private byte checkSum(byte data[], int length) {
        byte chksum = 0x0;

        for (int i = 0; i < length + 3; i++) {
            chksum ^= data[i]; // +1 since skip prefix sync code, we already
            // counted it
        }

        return chksum;
    }

    private void ANTresponseHandler(byte rxBuf[], int size, GoldenCheetah gc) {
        byte ch = rxBuf[3];
        byte id = rxBuf[4];
        byte code = rxBuf[5];

        if (debug) {
            System.out.println("Channel Num:" + UnicodeFormatter.byteToHex(ch));
            System.out.println("Message ID: " + UnicodeFormatter.byteToHex(id));
            System.out.println("Code: " + UnicodeFormatter.byteToHex(code));
        }

        switch (id) {
        case MESG_CHANNEL_SEARCH_TIMEOUT_ID:
            if (debug)
                System.out.println("[MESG_CHANNEL_SEARCH_TIMEOUT_ID]");
            break;
        case MESG_ASSIGN_CHANNEL_ID:
            if (debug)
                System.out.println("[MESG_ASSIGN_CHANNEL_ID]");
            break;
        case MESG_CHANNEL_RADIO_FREQ_ID:
            if (debug)
                System.out.println("[MESG_CHANNEL_RADIO_FREQ_ID]");
            break;
        case MESG_CHANNEL_MESG_PERIOD_ID:
            if (debug)
                System.out.println("[MESG_CHANNEL_MESG_PERIOD_ID]");
            break;
        case MESG_OPEN_CHANNEL_ID:
            if (debug)
                System.out.println("[MESG_OPEN_CHANNEL_ID]");
            break;
        case MESG_CHANNEL_ID_ID:
            if (debug)
                System.out.println("[MESG_CHANNEL_ID_ID]");
            break;
        case MESG_NETWORK_KEY_ID:
            if (debug)
                System.out.println("[MESG_NETWORK_KEY_ID]");
            break;
        case MESG_CHANNEL_EVENT_ERROR:
            if (code == 0x01) {
                if (ch == 0)
                    System.out.println("Dropped HRM");
                else if (ch == 1)
                    System.out.println("Dropped Power");
                else if (ch == 2)
                    System.out.println("Dropped Cadence/Speed");
            }
            break;
        default:
            if (debug)
                System.out.println("[unknown]: " + UnicodeFormatter.byteToHex(id));
            break;
        }

        return; // For Loop will move 1 forward
    }

    private int ANTCfgCapabilties(int i, int size) {
        return i + size + 4;
    }

    public static int byteArrayToInt(byte[] b, int offset, int size) {
        uShort uint = new uShort(b, offset);
        return uint.getValue();
    }

    private void writeGCRecord(GoldenCheetah gc) {

        fout.write(spacer1 + "<sample cad=\"" + gc.getCad() + "\" watts=\"" + gc.getWatts() + "\" kph=\""
                + Round(gc.getSpeed(), 1) + "\" km=\"" + Round(gc.getDistance(), 2) + "\" secs=\"" + gc.getSecs()
                + "\" hr=\"" + gc.getHr() + "\" lon=\"" + gc.getLongitude() + "\" lat=\"" + gc.getLatitude()
                + "\" alt=\"" + gc.getElevation() + "\" len=\"1\"/>\n");
    }

    public static double Round(double Rval, int Rpl) {
        double p = Math.pow(10, Rpl);
        Rval = Rval * p;
        double tmp = Math.round(Rval);
        return tmp / p;
    }

    public static String convertBytesToString(byte[] bytes) {
        return new String(bytes).trim();
    }

    private int readBuffer(byte[] readBytes, String filePath) {
        int bufPos = 0;
        GPS gps = new GPS();
        byte[] bufToSend;
        byte[] timeStamp;
        long secs = 0;

        if ((pos + bufPos + 64) >= readBytes.length - 1) {
            System.out.println("\n\nTotal Failed Checksums: " + totalChecksumError + " Out of Total ANT Messages: "
                    + totalTrans);
            System.out.println("% ANT Failure: " + (totalChecksumError / totalTrans) * 100.0);
            System.out.println("Total Failed GPS: " + totalGPSError + " Out of Total ANT Messages: " + totalTrans);
            System.out.println("% GPS Failure: " + (totalGPSError / totalTrans) * 100.0);
            System.out.println("Total Failed Messages: " + (totalChecksumError + totalGPSError)
                    + " Out of Total ANT Messages: " + totalTrans);
            System.out.println("% Failure: " + ((totalChecksumError + totalGPSError) / totalTrans) * 100.0);

            System.out.println("Total CAD or Watt Spikes: " + totalSpikes);
            writeOutGCRecords();
            System.exit(0);
        }

        Byte aByte = new Byte(readBytes[pos + bufPos + 1]);
        int size = aByte.intValue();
        if (size < 0) {
            pos++;
            // We failed a checksum skip..
            while (readBytes[pos] != MESG_TX_SYNC) {
                pos++;
            }
            totalChecksumError++;
            return pos;

        }
        bufToSend = new byte[size + 4];

        for (; bufPos < bufToSend.length; bufPos++)
            bufToSend[bufPos] = readBytes[bufPos + pos];
        if (ANTrxHandler(bufToSend, gc) == true) {
            pos++;
            // We failed a checksum skip..
            while (readBytes[pos] != MESG_TX_SYNC)
                pos++;
            return pos;
        }

        pos = (pos + size + 4);

        try {

            if (isGPS) {

                // Now Parse GPS
                gps = new GPS().GPSHandler(readBytes, pos);

                pos += 22; // All GPS data

                // Get the intial elevation from Google use Lat and Lon

                if (altiPressure == null && serializedElevationPath == null) {
                    altiPressure = new AltitudePressure(GoogleElevation
                            .getElevation(Float.valueOf(gps.getLatitude()), Float.valueOf(gps.getLongitude())));
                }
                int pressureCounter = 0;
                byte[] pressureByte = new byte[16];

                while (readBytes[pos] != 0x07) {
                    if (pressureCounter == 15)
                        throw new NumberFormatException();
                    pressureByte[pressureCounter++] = readBytes[pos++];
                }
                pos++; // skip the delimeter
                String strPressure = convertBytesToString(pressureByte);
                float pressure = Float.parseFloat(strPressure);

                strPressure = convertBytesToString(pressureByte);
                pressure = Float.parseFloat(strPressure);

                if (serializedElevationPath == null)
                    gc.setElevation(altiPressure.altiCalc(pressure / 100.0f));

                timeStamp = new byte[6];

                for (int i = 0; i < 6; i++)
                    timeStamp[i] = readBytes[pos++];
                secs = parseTimeStamp(timeStamp, gc);
            } else {
                timeStamp = new byte[3];
                for (int i = 0; i < 3; i++)
                    timeStamp[i] = readBytes[pos++];
                secs = parseTimeStamp(timeStamp, gc);
            }

            if (rideDate == null && isGPS == true)
                createRideDate(gps, timeStamp);
            else if (rideDate == null)
                createRideDate();

            gc.setLatitude(gps.getLatitude());
            gc.setLongitude(gps.getLongitude());
            gc.setSpeed(gps.getSpeed() * KNOTS_TO_KILOMETERS);

            gc.setDistance(gc.getDistance() + (gc.getSpeed() * (gc.getSecs() - gc.getPrevSpeedSecs()) / 3600.0));

            gc.setPrevSpeedSecs(gc.getSecs());
            if (secs > 86400) // Only fools ride for more then 24hrs a time..
                throw new NumberFormatException();
            gc.setSecs(secs);
            gc.setDate(gps.getDate());

            // If we haven't created the file, create it
            if (outFile == null && outGCFilePath != null) {
                if (isGPS)
                    initOutFile(gps, outGCFilePath, timeStamp);
                else
                    createGCOutFile();
            }
            if (gc.getPrevsecs() != gc.getSecs()) {
                gc.setWatts((int) Round(power.getWatts() / power.getTotalWattCounter(), 0));
                gc.setCad((int) Round(power.getRpm() / power.getTotalCadCounter(), 0));
                GoldenCheetah _gc = gc.clone(gc);
                gcArray.add(_gc);
                gc.setPrevsecs(gc.getSecs());
                gc.newWatts = false;
            }

        } catch (NumberFormatException e) {
            while (readBytes[pos] != MESG_TX_SYNC)
                pos++;
            totalGPSError++;
            return pos;
        } catch (StringIndexOutOfBoundsException ex) {
            while (readBytes[pos] != MESG_TX_SYNC)
                pos++;
            totalGPSError++;
        }
        return pos;

    }

    private void initGCFile() {
        fout.write("<!DOCTYPE GoldenCheetah>\n");
        fout.write("<ride>\n");
        fout.write(spacer1 + "<attributes>\n");
        fout.write(spacer2 + "<attribute key=\"Start time\" value=\"2010/01/01 00:00:00 UTC\" />\n");
        fout.write(spacer2 + "<attribute key=\"Device type\" value=\"Golden Embed\" />\n");
        fout.write(spacer1 + "</attributes>\n");
        fout.write("<samples>\n");
    }

    private void createRideDate() {

        Calendar cal = Calendar.getInstance();
        int date = cal.get(Calendar.MONTH);
        date++;

        String strMonth = formatDate(date);
        date = cal.get(Calendar.DAY_OF_MONTH);
        String strDay = formatDate(date);

        date = cal.get(Calendar.HOUR_OF_DAY);
        String strHour = formatDate(date);

        date = cal.get(Calendar.MINUTE);
        String strMin = formatDate(date);

        date = cal.get(Calendar.SECOND);
        String strSec = formatDate(date);

        rideDate = cal.get(Calendar.YEAR) + "/" + strMonth + "/" + strDay + " " + strHour + ":" + strMin + ":"
                + strSec;

    }

    private File createGCFileName() {

        Calendar cal = Calendar.getInstance();
        int date = cal.get(Calendar.MONTH);
        date++;

        String strMonth = formatDate(date);
        date = cal.get(Calendar.DAY_OF_MONTH);
        String strDay = formatDate(date);

        date = cal.get(Calendar.HOUR_OF_DAY);
        String strHour = formatDate(date);

        date = cal.get(Calendar.MINUTE);
        String strMin = formatDate(date);

        date = cal.get(Calendar.SECOND);
        String strSec = formatDate(date);

        rideDate = cal.get(Calendar.YEAR) + "/" + strMonth + "/" + strDay + " " + strHour + ":" + strMin + ":"
                + strSec;

        File outFile = new File(outGCFilePath + "/" + cal.get(Calendar.YEAR) + "_" + strMonth + "_" + strDay + "_"
                + strHour + "_" + strMin + "_" + strSec + ".gc");

        return outFile;

    }

    private void createGCOutFile() {

        outFile = createGCFileName();
        try {
            fout = new PrintWriter(new FileOutputStream(outFile));
            initGCFile();
        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
            System.exit(1);
        }

    }

    private String createRideDate(GPS gps, byte[] timeStamp) {

        String strYear = "20" + timeStamp[0];
        int year = Integer.valueOf(strYear);
        int month = timeStamp[1];
        month--; // Zero based
        int day = timeStamp[2];

        int hr = timeStamp[3];
        int min = timeStamp[4];
        int sec = timeStamp[5];

        Calendar rideCal = new GregorianCalendar();
        rideCal.set(year, month, day, hr, min, sec);

        SimpleDateFormat rideFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        rideFormat.setTimeZone(rideCal.getTimeZone());
        rideDate = rideFormat.format(rideCal.getTime());

        return rideDate;

    }

    private void initOutFile(GPS gps, String filePath, byte[] timeStamp) {
        if (outFile == null) {

            String strYear = "20" + timeStamp[0];
            int year = Integer.valueOf(strYear);
            int month = timeStamp[1];
            month--; // Zero based
            int day = timeStamp[2];

            int hr = timeStamp[3];
            int min = timeStamp[4];
            int sec = timeStamp[5];

            Calendar rideCal = new GregorianCalendar();

            rideCal.set(year, month, day, hr, min, sec);

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
            outFile = new File(filePath + "/" + sdf.format(rideCal.getTime()) + ".gc");

            try {
                fout = new PrintWriter(new FileOutputStream(outFile));
                initGCFile(year, month, day, hr, min, sec);
            } catch (FileNotFoundException e1) {
                e1.printStackTrace();
                System.exit(1);
            }
        }
    }

    private long parseTimeStamp(byte[] timeStamp, GoldenCheetah gc) throws NumberFormatException {

        Calendar cal = new GregorianCalendar();

        try {

            int year = 0;
            int month = 0;
            int day = 0;

            int hour;
            int min;
            int sec;
            int i = 0;

            if (isGPS) {
                year = new Byte(timeStamp[i++]);
                month = new Byte(timeStamp[i++]);
                day = new Byte(timeStamp[i++]);
            }

            if (year >= 0 && year <= 99 && month >= 0 && month <= 12 && day >= 0 && day <= 31) {
                hour = new Byte(timeStamp[i++]);
                min = new Byte(timeStamp[i++]);
                sec = new Byte(timeStamp[i++]);

                year += 2000;
            } else
                throw new NumberFormatException();

            if (hour <= 24 && hour >= 0 && min <= 60 && min >= 0 && sec <= 60 && sec >= 0) {

                cal.set(year, --month, day, hour, min, sec);
                gc.setCurrentTime(cal);
            } else
                throw new NumberFormatException();

            long totalSecs = cal.getTimeInMillis() / 1000;

            if (firstRecordedTime == 0)
                firstRecordedTime = totalSecs;

            return totalSecs - firstRecordedTime;

        } catch (NumberFormatException e) {
            throw new NumberFormatException();

        }

    }

    public boolean isDouble(String input) {
        try {
            Double.parseDouble(input);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private void writeOutGCRecords() {
        Collections.sort(gcArray, new SortBySeconds());
        List<IntervalBean> gcIntervals = new ArrayList<IntervalBean>();

        printDupes(gcArray);

        if (serializedElevationPath != null) {
            googleElevation = new GoogleElevation(serializedElevationPath);
            gcArray = googleElevation.getGCElevations(gcArray);
        }

        if (smoothFactor != 0)
            gcArray = smooth(gcArray, smoothFactor);

        if (outGCFilePath != null) {
            Iterator<GoldenCheetah> iter = gcArray.iterator();
            while (iter.hasNext()) {
                GoldenCheetah _gc = iter.next();
                writeGCRecord(_gc);
            }
            closeGCFile();
        }

        if (outGnuPlotPath != null) {
            GnuPlot plot = new GnuPlot();
            plot.writeOutGnuPlot(gcArray, outGnuPlotPath);
        }

        if (intervalParam != null) {
            Intervals interval = new Intervals();
            gcIntervals = interval.createInterval(gcArray, intervalParam);
        }

        if (username != null) {
            if (rideDate == null) {
                Calendar rideCal = Calendar.getInstance();
                SimpleDateFormat rideFormat = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
                rideDate = rideFormat.format(rideCal.getTime());
            }
            FusionTables ft = new FusionTables(username, password);
            ft.uploadToFusionTables("Golden Embed", gcArray, rideDate, gcIntervals);
        }

        if (stravaUsername != null)
            new Strava(gcArray, stravaUsername, stravaPassword, rideDate);

        if (rideWithGPSUsername != null)
            new RideWithGPS(gcArray, rideWithGPSUsername, rideWithGPSPassword, rideDate);

        System.out.println("");
        System.out.println("Finished");
    }

    public GoldenCheetah findGCByTime(long secs) {
        Iterator<GoldenCheetah> iter = gcArray.iterator();
        GoldenCheetah _gc;
        while (iter.hasNext()) {
            _gc = iter.next();
            if (_gc.getSecs() == secs)
                return _gc;
        }

        return null;
    }

    private void printDupes(List<GoldenCheetah> gcArray) {
        double prevDist = 0;
        List<GoldenCheetah> gcDupes = new ArrayList<GoldenCheetah>();
        for (GoldenCheetah gc : gcArray) {
            if (prevDist > gc.getDistance())
                gcDupes.add(gc);
            prevDist = gc.getDistance();
        }
        for (GoldenCheetah gc : gcDupes)
            gcArray.remove(gc);
    }

    public List<GoldenCheetah> smooth(List<GoldenCheetah> gcArray, long secs) {
        ArrayList<GoldenCheetah> gcSmoothedArray = new ArrayList<GoldenCheetah>();

        long totalWatts = 0;
        long totalCad = 0;
        long totalSpeed = 0;
        int totalHR = 0;
        float totalElevation = 0;
        long counter = 0;
        GoldenCheetah smoothedGC;

        for (GoldenCheetah gc : gcArray) {
            if (gc.getSecs() % secs == 0 && counter != 0) {
                smoothedGC = new GoldenCheetah();
                smoothedGC.setCad(totalCad / counter);
                smoothedGC.setWatts(totalWatts / counter);
                smoothedGC.setSpeed(totalSpeed / counter);
                smoothedGC.setHr(totalHR / (int) counter);
                smoothedGC.setElevation(totalElevation / counter);
                smoothedGC.setSecs(gc.getSecs());
                gcSmoothedArray.add(smoothedGC);

                totalWatts = 0;
                totalCad = 0;
                totalSpeed = 0;
                totalHR = 0;
                totalElevation = 0;
                counter = 0;

            } else {
                totalWatts += gc.getWatts();
                totalCad += gc.getCad();
                totalSpeed += gc.getSpeed();
                totalElevation += gc.getElevation();
                totalHR += gc.getHr();
                counter++;
            }
        }

        return gcSmoothedArray;
    }

}