Subversion Repositories public

Rev

Rev 313 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/***************************************************************************
 *   Copyright (C) 2007 - 2013 by Andreas Theofilu                         *
 *   andreas@theosys.at                                                    *
 *                                                                         *
 *   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 version 3 of the License.                *
 *                                                                         *
 *   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.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <time.h>
#include <string.h>
#include <math.h>

#include <iostream>
#include <qlistview.h>
#include <qdatetime.h>
#include <QXmlReader>

#include <klocale.h>

#include "import.h"

#define FLD_NAME                                        1
#define FLD_TOTALTIMESECONDS            2
#define FLD_DISTANCEMETERS                      3
#define FLD_LATITUDEDEGREES                     4
#define FLD_LONGITUDEDEGREES            5
#define FLD_VALUE                                       6
#define FLD_INTENSITY                           7
#define FLD_TIME                                        8
#define FLD_ALTITUDEMETERS                      9
#define FLD_SENSORSTATE                         10
#define FLD_STARTTIME                           11
#define FLD_AVERAGECADENCE                      12
#define FLD_SPORTTYPE                           13
#define FLD_VERSIONMAJOR                        14
#define FLD_VERSIONMINOR                        15
#define FLD_BUILDMAJOR                          16
#define FLD_BUILDMINOR                          17
#define FLD_TYPE                                        18
#define FLD_BUILDER                                     19
#define FLD_LANGID                                      20
#define FLD_PARTNUMBER                          21
#define FLD_CADENCE                                     22
#define FLD_MAXSPEED                            23
#define FLD_CALORIES                            24
#define FLD_ID                                          25
#define FLD_TRIGGERMETHOD                       26
#define FLD_TPX                                         27
#define FLD_UNITID                                      28
#define FLD_PRODUCTID                           29
#define FLD_NOTES                                       30

#define CON_COURSES                                     100
#define CON_COURSE                                      101
#define CON_LAP                                         102
#define CON_BEGINPOSITION                       103
#define CON_ENDPOSITION                         104
#define CON_AVERAGEHEARTRATEBPM         105
#define CON_MAXIMUMHEARTRATEBPM         106
#define CON_TRACK                                       107
#define CON_TRACKPOINT                          108
#define CON_POSITION                            109
#define CON_HEARTRATEBPM                        110
#define CON_AUTHOR                                      111
#define CON_VERSION                                     112
#define CON_BUILD                                       113
#define CON_ACTIVITY                            114
#define CON_EXTENSIONS                          115
#define CON_CREATOR                                     116
#define CON_MULTISPORTSESSION           117
#define CON_FIRSTSPORT                          118
#define CON_NEXTSPORT                           119
#define CON_TRAINING                            120
#define CON_QUICKWORKOUTRESULTS         121

#define ATT_CADENCESENSOR                       200

#define STOPSTOP                                        0

KEYS keys[] =
{
        /* Fields */
        { FLD_NAME,                                     QString("name") },
        { FLD_TOTALTIMESECONDS,         QString("TotalTimeSeconds") },
        { FLD_DISTANCEMETERS,           QString("DistanceMeters") },
        { FLD_LATITUDEDEGREES,          QString("LatitudeDegrees") },
        { FLD_LONGITUDEDEGREES,         QString("LongitudeDegrees") },
        { FLD_VALUE,                            QString("Value") },
        { FLD_INTENSITY,                        QString("Intensity") },
        { FLD_TIME,                                     QString("Time") },
        { FLD_ALTITUDEMETERS,           QString("AltitudeMeters") },
        { FLD_SENSORSTATE,                      QString("SensorState") },
        { FLD_STARTTIME,                        QString("StartTime") },
        { FLD_AVERAGECADENCE,           QString("AverageCadence") },
        { FLD_SPORTTYPE,                        QString("SportType") },
        { FLD_VERSIONMAJOR,                     QString("VersionMajor") },
        { FLD_VERSIONMINOR,                     QString("VersionMinor") },
        { FLD_BUILDMAJOR,                       QString("BuildMajor") },
        { FLD_BUILDMINOR,                       QString("BuildMinor") },
        { FLD_TYPE,                                     QString("Type") },
        { FLD_BUILDER,                          QString("Builder") },
        { FLD_LANGID,                           QString("LangID") },
        { FLD_PARTNUMBER,                       QString("PartNumber") },
        { FLD_CADENCE,                          QString("Cadence") },
        { FLD_CALORIES,                         QString("Calories") },
        { FLD_MAXSPEED,                         QString("MaximumSpeed") },
        { FLD_ID,                                       QString("Id") },
        { FLD_TRIGGERMETHOD,            QString("TriggerMethod") },
        { FLD_TPX,                                      QString("TPX") },
        { FLD_UNITID,                           QString("UnitId") },
        { FLD_PRODUCTID,                        QString("ProductID") },
        { FLD_NOTES,                            QString("Notes") },
        /* Attributes */
        { ATT_CADENCESENSOR,            QString("CadenceSensor") },
        /* Container */
        { CON_COURSES,                          QString("Courses") },
        { CON_COURSE,                           QString("Course") },
        { CON_LAP,                                      QString("Lap") },
        { CON_BEGINPOSITION,            QString("BeginPosition") },
        { CON_ENDPOSITION,                      QString("EndPosition") },
        { CON_AVERAGEHEARTRATEBPM,      QString("AverageHeartRateBpm") },
        { CON_MAXIMUMHEARTRATEBPM,      QString("MaximumHeartRateBpm") },
        { CON_TRACK,                            QString("Track") },
        { CON_TRACKPOINT,                       QString("Trackpoint") },
        { CON_POSITION,                         QString("Position") },
        { CON_HEARTRATEBPM,                     QString("HeartRateBpm") },
        { CON_AUTHOR,                           QString("Author") },
        { CON_VERSION,                          QString("Version") },
        { CON_BUILD,                            QString("Build") },
        { CON_ACTIVITY,                         QString("Activity") },
        { CON_EXTENSIONS,                       QString("Extensions") },
        { CON_CREATOR,                          QString("Creator") },
        { CON_MULTISPORTSESSION,        QString("MultiSportSession") },
        { CON_FIRSTSPORT,                       QString("FirstSport") },
        { CON_NEXTSPORT,                        QString("NextSport") },
        { CON_TRAINING,                         QString("Training") },
        { CON_QUICKWORKOUTRESULTS,      QString("QuickWorkoutResults") },
        /* Errors */
        { ERR_OK,                                       QString("OK") },        // this is no error
        { ERR_NOFILE,                           i18n("IMPORT: No file name to parse XML!") },   // but this
        { ERR_TAGS,                                     i18n("IMPORT: More end tags than open tags! Invalid XML file.") },
        { ERR_ALLOCGMN,                         i18n("IMPORT: Error allocating memory for base Garmin structure.") },
        { ERR_ALLOCLAP,                         i18n("IMPORT: Error allocating memory for a lap.") },
        { ERR_ALLOCPOINT,                       i18n("IMPORT: Error allocating memory for a track point.") },
        { ERR_ALLOCRUN,                         i18n("IMPORT: Error allocating memory for running information.") },
        { ERR_ALLOCLIST,                        i18n("IMPORT: Error allocating memory for a list node.") },
        { STOPSTOP,                                     QString::null }
};

void gmn_import::Initialize()
{
        qfstat = false;
        qfopen = false;
        __error = 0;
        gmn = 0;
        list_lap = list_track = 0;
        ds = 0;
        history = false;
        inst = 0;
        m_cb = 0;
        doCallback = FALSE;
}

gmn_import::gmn_import(const QFile &qfile)
{
        Initialize();
        file.setFileName(qfile.fileName());

        if(file.exists())
                qfstat = true;
}

void gmn_import::setFile(const QFile &qfile)
{
        if(qfopen)
        {
                file.close();
                qfopen = false;
        }

        qfstat = false;
        file.setFileName(qfile.fileName());

        if(file.exists())
                qfstat = true;
}

void gmn_import::setFile(const QString &sfile)
{
        if(qfopen)
        {
                file.close();
                qfopen = false;
                __error = 1;
        }

        qfstat = false;
        file.setFileName(sfile);

        if(file.exists())
                qfstat = true;
}

/*
 * Convert an ISO8601 formated date into garmin epoch.
 */
unsigned int gmn_import::garmin_time(const QString& tstamp)
{
        QDateTime dt;
        time_t tval;

        /*
         * tstamp should contain a valid ISO8601 formated date and time stamp.
         * We will convert it to a kind of epoch, but with a garmin specific
         * offset.
         */
        dt = dt.fromString(tstamp, Qt::ISODate);

        if(!dt.isValid())
                return 0;

        tval = dt.toTime_t() - TIME_OFFSET;
        return (unsigned int)tval;
}

/*
 * This function initializes the XML parser.
 */
bool gmn_import::startDocument()
{
        indent = 0;
        con.clear();
        subCon.clear();
        lpos = tpos = oldLPos = 0;
        history = fakeLap = false;
        first_tpos = 0;
        prun = 0;
        plap = 0;
        return TRUE;
}

/*
 * This is called every time a new start element was parsed.
 */
bool gmn_import::startElement(const QString&, const QString&,
                              const QString& qName,
                              const QXmlAttributes& att)
{
        int i = CON_FIRST;
        int index;

        while(i <= CON_LAST)
        {
                if(qName.toLower() == getKey(i).toLower())
                {
                        if(i == CON_MULTISPORTSESSION)
                                doCallback = TRUE;

                        if(qName.toLower() == QString("course") || qName.toLower() == QString("activity"))
                        {
                                memset(&run, 0, sizeof(D1009));

                                if(i == CON_ACTIVITY)
                                {
                                        con = "activity";
                                        history = true;
                                }
                                else
                                        con = "course";

                                run.track_index = 0;
                                run.first_lap_index = 0;

                                // Find sport type, if there is one
                                if((index = att.index(QString("Sport"))) != -1)
                                {
                                        if(att.value(index).toAscii().toLower() == QString("Running").toLower())
                                                run.sport_type = D1000_running;
                                        else if(att.value(index).toAscii().toLower() == QString("Biking").toLower())
                                                run.sport_type = D1000_biking;
                                        else if(att.value(index).toAscii().toLower() == QString("Other").toLower())
                                                run.sport_type = D1000_other;
                                        else
                                                run.sport_type = D1000_other;
                                }
                                else
                                        run.sport_type = D1000_other;           // Other

                                tk = i;
                        }

                        if(qName.toLower() == QString("lap"))
                        {
                                memset(&lap, 0, sizeof(D1015));
                                lap.index = lpos;
                                lap.begin.lat = 0x7fffffff;
                                lap.begin.lon = 0x7fffffff;
                                lap.end.lat = 0x7fffffff;
                                lap.end.lon = 0x7fffffff;
                                con = "lap";
                                lpos++;
                                tk = i;
                        }

                        if(con == QString("lap") && qName.toLower() == QString("BeginPosition").toLower())
                                subCon = qName.toLower();
                        else if(con == QString("lap") && qName.toLower() == QString("EndPosition").toLower())
                                subCon = qName.toLower();
                        else if(con == QString("lap") && qName.toLower() == QString("AverageHeartRateBpm").toLower())
                                subCon = qName.toLower();
                        else if(con == QString("lap") && qName.toLower() == QString("MaximumHeartRateBpm").toLower())
                                subCon = qName.toLower();

                        if(history && qName.toLower() == QString("track") && con.toLower() == QString("lap"))
                        {
                                first_tpos = tpos + 1;
                                endElement(QString::null, QString::null, QString("Lap"));
                                indent++;
                                con = "lap";
                                fakeLap = true;
                        }

                        if(qName.toLower() == QString("trackpoint"))
                        {
                                memset(&point, 0, sizeof(D304));
                                point.alt = 1.0e24;
                                point.posn.lat = 0x7fffffff;
                                point.posn.lon = 0x7fffffff;
                                con = "trackpoint";
                                tpos++;
                                tk = i;
                        }

                        if(con == QString("trackpoint") && qName.toLower() == QString("position"))
                                subCon = qName.toLower();
                        else if(con == QString("trackpoint") && qName.toLower() == QString("heartratebpm"))
                                subCon = qName.toLower();
                }

                i++;
        }

        i = FLD_FIRST;

        while(i <= FLD_LAST)
        {
                if(qName.toLower() == getKey(i).toLower())
                        tk = i;

                i++;
        }

        indent++;
        return TRUE;
}

/*
 * This is called every time an element is closed.
 */
bool gmn_import::endElement(const QString&, const QString&, const QString& qName)
{
        if(!fakeLap)
                indent--;

        if(qName.toLower() == QString("lap"))
        {
                garmin_data *gdt;
                garmin_list *l;

                if(!fakeLap)
                        con.clear();

                if(history && fakeLap)
                {
                        fakeLap = false;
                        return TRUE;
                }

                if(!gmn)                /* allocating space for first structure */
                {
                        if((gmn = garmin_alloc_data(data_Dlist)) == NULL)
                        {
                                __error = 3;
                                return FALSE;
                        }

                        list = (garmin_list *)gmn->data;

                        /*
                         * This is the first data structure. It contains the total
                         * number of laps and the name of the course, if it was
                         * named.
                         */
                        if((gdt = garmin_alloc_data(data_D1009)) == NULL)
                        {
                                __error = 6;
                                return FALSE;
                        }

                        memmove(gdt->data, &run, sizeof(D1009));
                        prun = (D1009 *)gdt->data;

                        if(ds)
                                ds->garmin_print_data(gdt);

                        if((l = garmin_list_append(list, gdt)) == NULL)
                        {
                                __error = 7;
                                return FALSE;
                        }

                        list = l;
                }

                if(!list_lap)
                {
                        if((gmn_lap = garmin_alloc_data(data_Dlist)) == NULL)
                        {
                                __error = 3;
                                return FALSE;
                        }

                        list_lap = (garmin_list *)gmn_lap->data;

                        if(garmin_list_append(list, gmn_lap) == NULL)
                        {
                                __error = 7;
                                return FALSE;
                        }

                        list = list_lap;
                }
                else
                        list = list_lap;

                if((gdt = garmin_alloc_data(data_D1015)) == NULL)
                {
                        __error = 4;
                        return FALSE;
                }

                memmove(gdt->data, &lap, sizeof(D1015));
                plap = (D1015 *)gdt->data;

                if(ds)
                        ds->garmin_print_data(gdt);

                if((l = garmin_list_append(list, gdt)) == NULL)
                {
                        __error = 7;
                        return FALSE;
                }

                list = l;
        }

        if(history && qName.toLower() == QString("track"))
                first_tpos = 0;

        if(qName.toLower() == QString("trackpoint"))
        {
                garmin_data *gdt;
                garmin_list *l;

                con.clear();

                if(!gmn)                /* allocating space for first structure */
                {
                        if((gmn = garmin_alloc_data(data_Dlist)) == NULL)
                        {
                                __error = 3;
                                return FALSE;
                        }

                        list = (garmin_list *)gmn->data;

                        /*
                         * This is the first data structure. It contains the total
                         * number of laps and the name of the course, if it was
                         * named.
                         */
                        if((gdt = garmin_alloc_data(data_D1009)) == NULL)
                        {
                                __error = 6;
                                return FALSE;
                        }

                        memmove(gdt->data, &run, sizeof(D1009));
                        prun = (D1009 *)gdt->data;

                        if(ds)
                                ds->garmin_print_data(gdt);

                        if((l = garmin_list_append(list, gdt)) == NULL)
                        {
                                __error = 7;
                                return FALSE;
                        }

                        list = l;
                }

                if(!list_track)
                {
                        if((gmn_track = garmin_alloc_data(data_Dlist)) == NULL)
                        {
                                __error = 3;
                                return FALSE;
                        }

                        list_track = (garmin_list *)gmn_track->data;

                        if(garmin_list_append(list, gmn_track) == NULL)
                        {
                                __error = 7;
                                return FALSE;
                        }

                        list = list_track;
                }
                else
                        list = list_track;

                if((gdt = garmin_alloc_data(data_D304)) == NULL)
                {
                        __error = 5;
                        return FALSE;
                }

                memmove(gdt->data, &point, sizeof(D304));

                if(ds)
                        ds->garmin_print_data(gdt);

                if((l = garmin_list_append(list, gdt)) == NULL)
                {
                        __error = 7;
                        return FALSE;
                }

                list = l;
        }

        if(qName.toLower() == QString("course") || qName.toLower() == QString("activity"))
        {
                con.clear();
                run.track_index = tpos - 1;
                run.last_lap_index = lpos - 1;
                memmove(prun, &run, sizeof(D1009));

                if (doCallback && m_cb)
                {
                        m_cb->cbiCallbackFunction((void *)gmn);
                        subCon.clear();
                        lpos = tpos = oldLPos = 0;
                        fakeLap = false;
                        first_tpos = 0;
                        garmin_free_data(gmn);
                        gmn = 0;
                }
                else if (doCallback)
                        std::cerr << "No callback function was defined! Can't save single track!" << std::endl;
        }

        if (qName.toLower() == QString("activities"))
        {
                con.clear();
                subCon.clear();
                history = false;
        }

        if(qName.toLower() == QString("beginposition") || qName.toLower() == QString("endposition") ||
                          qName.toLower() == QString("averageheartratebpm") || qName.toLower() == QString("maximumheartratebpm"))
                subCon.clear();

        if(qName.toLower() == QString("heartratebpm") || qName.toLower() == QString("position"))
                subCon.clear();

        if(indent < 0)
        {
                __error = 2;
                return FALSE;
        }

        return TRUE;
}

/*
 * The reader calls this function when it has parsed a chunk of character data
 * - either normal character data or character data inside a CDATA section.
 */
bool gmn_import::characters(const QString& ch)
{
        if(con == QString("course"))
        {
                if(tk == FLD_NAME)
                {
                        strncpy(run.workout.name, ch.toAscii(), 15);
                        run.workout.name[15] = 0;
                        tk = 0;
                }
        }

        if(history && con == QString("activity"))
        {
                if(tk == FLD_ID)
                {
                        strncpy(run.workout.name, ch.toAscii(), 15);
                        run.workout.name[15] = 0;
                        tk = 0;
                }
        }

        if(con == QString("lap"))
        {
                if(tk == FLD_DISTANCEMETERS)
                {
                        lap.total_dist = (float32)ch.toFloat();
                        tk = 0;
                }

                if(tk == FLD_INTENSITY)
                {
                        lap.intensity = (ch.toLower() == QString("activ")) ? 0 : 1;
                        tk = 0;
                }

                if(tk == FLD_STARTTIME)
                {
                        lap.start_time = garmin_time(ch);
                        tk = 0;
                }

                if(tk == FLD_TOTALTIMESECONDS)
                {
                        lap.total_time = (uint32)(ch.toDouble() * 100.0);
                        tk = 0;
                }

                if(tk == FLD_CADENCE)
                {
                        lap.avg_cadence = (uint8)ch.toUInt();
                        tk = 0;
                }

                if(tk == FLD_CALORIES)
                {
                        lap.calories = (uint16)ch.toUInt();
                        tk = 0;
                }

                if(tk == FLD_MAXSPEED)
                {
                        lap.max_speed = (float32)ch.toFloat();
                        tk = 0;
                }

                if(tk == FLD_TRIGGERMETHOD)
                {
                        if(ch.toLower() == QString("manual"))
                                lap.trigger_method = D1011_manual;
                        else if(ch.toLower() == QString("distance"))
                                lap.trigger_method = D1011_distance;
                        else if(ch.toLower() == QString("location"))
                                lap.trigger_method = D1011_location;
                        else if(ch.toLower() == QString("time"))
                                lap.trigger_method = D1011_time;
                        else if(ch.toLower() == QString("HeartRate"))
                                lap.trigger_method = D1011_heart_rate;
                }

                if(subCon.toLower() == QString("BeginPosition").toLower())
                {
                        if(tk == FLD_LATITUDEDEGREES)
                        {
                                lap.begin.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
                                tk = 0;
                        }

                        if(tk == FLD_LONGITUDEDEGREES)
                        {
                                lap.begin.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
                                tk = 0;
                        }
                }
                else if(subCon.toLower() == QString("EndPosition").toLower())
                {
                        if(tk == FLD_LATITUDEDEGREES)
                        {
                                lap.end.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
                                tk = 0;
                        }

                        if(tk == FLD_LONGITUDEDEGREES)
                        {
                                lap.end.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
                                tk = 0;
                        }
                }
                else if(subCon.toLower() == QString("AverageHeartRateBpm").toLower())
                {
                        if(tk == FLD_VALUE)
                        {
                                lap.avg_heart_rate = (uint8)ch.toInt();
                                tk = 0;
                        }
                }
                else if(subCon.toLower() == QString("MaximumHeartRateBpm").toLower())
                {
                        if(tk == FLD_VALUE)
                        {
                                lap.max_heart_rate = (uint8)ch.toInt();
                                tk = 0;
                        }
                }
        }

        if(con == QString("trackpoint"))
        {
                if(tk == FLD_TIME)
                {
                        point.time = garmin_time(ch);

                        if(history && first_tpos == tpos && plap)
                                plap->start_time = point.time;

                        tk = 0;
                }

                if(tk == FLD_ALTITUDEMETERS)
                {
                        point.alt = (ch.toFloat() >= 1.0e24) ? 1.0e24 : (float32)ch.toFloat();
                        tk = 0;
                }

                if(tk == FLD_DISTANCEMETERS)
                {
                        point.distance = (ch.toFloat() >= 1.0e24) ? 1.0e24 : (float32)ch.toFloat();
                        tk = 0;
                }

                if(tk == FLD_SENSORSTATE)
                {
                        point.sensor = (ch.toLower() == QString("absent")) ? false : true;
                        tk = 0;
                }

                if(subCon.toLower() == QString("position"))
                {
                        if(tk == FLD_LATITUDEDEGREES)
                        {
                                point.posn.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());

                                if(history && tpos == first_tpos && plap)       // Add mising information to first lap
                                        plap->begin.lat = point.posn.lat;
                                else if(history && point.posn.lat != 0x7fffffff && plap)
                                        plap->end.lat = point.posn.lat;

                                tk = 0;
                        }

                        if(tk == FLD_LONGITUDEDEGREES)
                        {
                                point.posn.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());

                                if(history && tpos == first_tpos && plap)       // Add mising information to first lap
                                        plap->begin.lon = point.posn.lon;
                                else if(history && point.posn.lon != 0x7fffffff && plap)
                                        plap->end.lat = point.posn.lon;

                                tk = 0;
                        }
                }
                else if(subCon.toLower() == QString("heartratebpm"))
                {
                        if(tk == FLD_VALUE)
                        {
                                point.heart_rate = (unsigned char)ch.toInt();
                                tk = 0;
                        }
                }
        }

        return TRUE;
}

/*
 * The following function reads a Garmin HST file, parses the XML
 * content and creates the gmn files. If there already exists a file,
 * it's not overwritten.
 */
int gmn_import::import()
{
        QXmlSimpleReader reader;

        if(!qfstat)
                return 1;

        QXmlInputSource source(&file);
        reader.setContentHandler(this);
        reader.parse(source);
        return 0;
}

QString gmn_import::getKey(int pos)
{
        int i = 0;

        while(keys[i].id > 0)
        {
                if(keys[i].id == pos)
                        return keys[i].name;

                i++;
        }

        return QString::null;
}

QString gmn_import::getError(int err)
{
        if(err > eMax || err < 1)
                return 0;

        return getKey(ERR_FIRST + err);
}

QString gmn_import::getError()
{
        return getKey(ERR_FIRST + __error);
}