Logo Search packages:      
Sourcecode: qlandkarte version File versions  Download package

CDevice.cpp

/* -*-mode:c++; c-style:k&r; c-basic-offset:4; -*- */
/*******************************************************************************
    Copyright (C) 2007 Oliver Eichler oliver.eichler@gmx.de

    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., 59 Temple Place - Suite 330, Boston, MA 02111 USA

  Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd.
  or one of its subsidiaries.

--------------------------------------------------------------------------------

Sources:

   "Garmin Device Interface Specification," available on the Garmin web site
   list protocols and other data.  However, that data is incomplete even at
   65 pages.

   Listening in on the conversations between MapSource and your GPS can be
   useful for finding out how undocumented tasks are done at least for your
   GPS.

Comparison with intercepted MapSource communications (Legend bought 2007):

   Comparison of this driver with a SnoopyPro log of MapSource for Windows
   communication with the GPS:

   Mapsource transmits an undocumented PL (physical layer) packet with Pid
   equal to 16 immediately after receipt of the Pid_Session_started PL packet
   and receives a PL packet with Pid 17 and a payload of 4 bytes in return.
   Without documentation of what info is in the returned packet, there does
   not seem a reason to reproduce it.

   More interestingly, immediately before sending a Pid_Records packet,
   Mapsource transmits a similar packet, but with Pid_Records (27) replaced by
   28, and that reports zero packets to follow.  The suspicion is obviously
   that there are Garmin units out there that have chips with Pid_Records
   mistyped as 28 instead of 27.  However, such a Pid 28 packet is also
   transmitted before the map memory inquiry, so this is probably not correct.

   Transmitting a packet with a Pid of 28 in general may not be a problem since
   the specification says to ignore packets you do not understand.  However,
   it is not quite clear whether this extends only to the host or also to
   Garmin devices.  I assume however it does.  See 5.1, "Undocumented
   application packets" and 6.3.  Currently the packet Pid 28 is not
   transmitted

Notes about pthreads and mutexs:

   A pthread is a compact way of creating a separate, but linked, computer
   process (thread).  It orginated in Unix, but has been adopted widely.
   The library data <pthread.h> is included in IDeviceDefault.h.

   Mutex stands for mutual-exclusive.  It means getting hold of a device for
   your exclusive use.

*******************************************************************************/

#include "CDevice.h"
#include "../../Platform.h"

#include <Garmin.h>

#include <errno.h>
#include <iostream>
#include <sstream>

using namespace EtrexLegendCx;
using namespace Garmin;
using namespace std;

#undef DBG_LIVE_LOG

namespace EtrexLegendCx
{

    // GPS screenshot color table
    static const char _clrtbl[1024]= {
        0,0,0,0,32,0,0,0,65,0,0,0,106,0,0,0,-117,0,0,0,
        -76,0,0,0,-43,0,0,0,-1,0,0,0,0,48,0,0,32,48,0,0,
        65,48,0,0,106,48,0,0,-117,48,0,0,-76,48,0,0,-43,48,0,0,
        -1,48,0,0,0,101,0,0,32,101,0,0,65,101,0,0,106,101,0,0,
        -117,101,0,0,-76,101,0,0,-43,101,0,0,-1,101,0,0,0,-107,0,0,
        32,-107,0,0,65,-107,0,0,106,-107,0,0,-117,-107,0,0,-76,-107,0,0,
        -43,-107,0,0,-1,-107,0,0,0,-54,0,0,32,-54,0,0,65,-54,0,0,
        106,-54,0,0,-117,-54,0,0,-76,-54,0,0,-43,-54,0,0,-1,-54,0,0,
        0,-1,0,0,32,-1,0,0,65,-1,0,0,106,-1,0,0,-117,-1,0,0,
        -76,-1,0,0,-43,-1,0,0,-1,-1,0,0,0,0,57,0,32,0,57,0,
        65,0,57,0,106,0,57,0,-117,0,57,0,-76,0,57,0,-43,0,57,0,
        -1,0,57,0,0,48,57,0,32,48,57,0,65,48,57,0,106,48,57,0,
        -117,48,57,0,-76,48,57,0,-43,48,57,0,-1,48,57,0,0,101,57,0,
        32,101,57,0,65,101,57,0,106,101,57,0,-117,101,57,0,-76,101,57,0,
        -43,101,57,0,-1,101,57,0,0,-107,57,0,32,-107,57,0,65,-107,57,0,
        106,-107,57,0,-117,-107,57,0,-76,-107,57,0,-43,-107,57,0,-1,-107,57,0,
        0,-54,57,0,32,-54,57,0,65,-54,57,0,106,-54,57,0,-117,-54,57,0,
        -76,-54,57,0,-43,-54,57,0,-1,-54,57,0,0,-1,57,0,32,-1,57,0,
        65,-1,57,0,106,-1,57,0,-117,-1,57,0,-76,-1,57,0,-43,-1,57,0,
        -1,-1,57,0,0,0,123,0,32,0,123,0,65,0,123,0,106,0,123,0,
        -117,0,123,0,-76,0,123,0,-43,0,123,0,-1,0,123,0,0,48,123,0,
        32,48,123,0,65,48,123,0,106,48,123,0,-117,48,123,0,-76,48,123,0,
        -43,48,123,0,-1,48,123,0,0,101,123,0,32,101,123,0,65,101,123,0,
        106,101,123,0,-117,101,123,0,-76,101,123,0,-43,101,123,0,-1,101,123,0,
        0,-107,123,0,32,-107,123,0,65,-107,123,0,106,-107,123,0,-117,-107,123,0,
        -76,-107,123,0,-43,-107,123,0,-1,-107,123,0,0,-54,123,0,32,-54,123,0,
        65,-54,123,0,106,-54,123,0,-117,-54,123,0,-76,-54,123,0,-43,-54,123,0,
        -1,-54,123,0,0,-1,123,0,32,-1,123,0,65,-1,123,0,106,-1,123,0,
        -117,-1,123,0,-76,-1,123,0,-43,-1,123,0,-1,-1,123,0,0,0,-67,0,
        32,0,-67,0,65,0,-67,0,106,0,-67,0,-117,0,-67,0,-76,0,-67,0,
        -43,0,-67,0,-1,0,-67,0,0,48,-67,0,32,48,-67,0,65,48,-67,0,
        106,48,-67,0,-117,48,-67,0,-76,48,-67,0,-43,48,-67,0,-1,48,-67,0,
        0,101,-67,0,32,101,-67,0,65,101,-67,0,106,101,-67,0,-117,101,-67,0,
        -76,101,-67,0,-43,101,-67,0,-1,101,-67,0,0,-107,-67,0,32,-107,-67,0,
        65,-107,-67,0,106,-107,-67,0,-117,-107,-67,0,-76,-107,-67,0,-43,-107,-67,0,
        -1,-107,-67,0,0,-54,-67,0,32,-54,-67,0,65,-54,-67,0,106,-54,-67,0,
        -117,-54,-67,0,-76,-54,-67,0,-43,-54,-67,0,-1,-54,-67,0,0,-1,-67,0,
        32,-1,-67,0,65,-1,-67,0,106,-1,-67,0,-117,-1,-67,0,-76,-1,-67,0,
        -43,-1,-67,0,-1,-1,-67,0,0,0,-1,0,32,0,-1,0,65,0,-1,0,
        106,0,-1,0,-117,0,-1,0,-76,0,-1,0,-43,0,-1,0,-1,0,-1,0,
        0,48,-1,0,32,48,-1,0,65,48,-1,0,106,48,-1,0,-117,48,-1,0,
        -76,48,-1,0,-43,48,-1,0,-1,48,-1,0,0,101,-1,0,32,101,-1,0,
        65,101,-1,0,106,101,-1,0,-117,101,-1,0,-76,101,-1,0,-43,101,-1,0,
        -1,101,-1,0,0,-107,-1,0,32,-107,-1,0,65,-107,-1,0,106,-107,-1,0,
        -117,-107,-1,0,-76,-107,-1,0,-43,-107,-1,0,-1,-107,-1,0,0,-54,-1,0,
        32,-54,-1,0,65,-54,-1,0,106,-54,-1,0,-117,-54,-1,0,-76,-54,-1,0,
        -43,-54,-1,0,-1,-54,-1,0,0,-1,-1,0,32,-1,-1,0,65,-1,-1,0,
        106,-1,-1,0,-117,-1,-1,0,-76,-1,-1,0,-43,-1,-1,0,-1,-1,-1,0,
        -1,-1,-1,0,-26,-26,-26,0,-43,-43,-43,0,-59,-59,-59,0,-76,-76,-76,0,
        -92,-92,-92,0,-108,-108,-108,0,-125,-125,-125,0,115,115,115,0,98,98,98,0,
        82,82,82,0,65,65,65,0,49,49,49,0,32,32,32,0,16,16,16,0,
        0,0,0,0
    };

    // get exclusive access to the GPS
    class CMutexLocker
    {
        public:
            CMutexLocker(pthread_mutex_t& mutex)
            : mutex(mutex) {
                pthread_mutex_lock(&mutex);
            }

            ~CMutexLocker() {
                pthread_mutex_unlock(&mutex);
            }
        private:
            pthread_mutex_t& mutex;

    };

    // rtThread runs as an independent process, updating the PVT field
    void * rtThread(void *ptr) { // input: points to the originating object (this)
        Packet_t command;
        Packet_t response;

#ifdef DBG_LIVE_LOG
        cout << "start thread" << endl;
#endif
        CDevice * dev = (CDevice*)ptr;
        CMutexLocker lock(dev->mutex);

        // try communicating with the device
        try
        {
            // lock the device for our use
            pthread_mutex_lock(&dev->dataMutex);
            // start communication
            dev->_acquire();

            // tell the GPS to start sending real time data
            command.type = GUSB_APPLICATION_LAYER;
            command.id   = Pid_Command_Data;
            command.size = 2;
            *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Start_Pvt_Data);
            dev->usb->write(command);

            // keep reading the GPS until the device boolean is set to false
            while(dev->doRealtimeThread) {

                // unlock the device
                pthread_mutex_unlock(&dev->dataMutex);

                // process the Position/Velocity/Time (PVT) packets from the GPS
                if(dev->usb->read(response)) {
                    if(response.id == Pid_Pvt_Data) {
                        // create a pointer to the packet's data area
                        D800_Pvt_Data_t * srcPvt = (D800_Pvt_Data_t*)response.payload;
                        // lock the device
                        pthread_mutex_lock(&dev->dataMutex);
                        // copy to shared memory
                        dev->PositionVelocityTime << *srcPvt;
                        // unlock the device
                        pthread_mutex_unlock(&dev->dataMutex);
                    }
                }

                // lock the device (don't ask *me*)
                pthread_mutex_lock(&dev->dataMutex);
            }

            // tell the GPS to stop sending real time data
            command.type = GUSB_APPLICATION_LAYER;
            command.id   = Pid_Command_Data;
            command.size = 2;
            *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Stop_Pvt_Data);
            dev->usb->write(command);

            // release the device
            dev->_release();
            // unlock the device
            pthread_mutex_unlock(&dev->dataMutex);
        }

        // catch errors in the communication
        catch(exce_t& e) {
            pthread_mutex_trylock(&dev->dataMutex);
            dev->lasterror = "Realtime thread failed. " + e.msg;
            dev->doRealtimeThread = false;
            pthread_mutex_unlock(&dev->dataMutex);
        }

        // done
#ifdef DBG_LIVE_LOG
        cout << "stop thread" << endl;
#endif
        return 0;
    }

    // Helper: nicely format a size in bytes
    class byteSizeStr: public string
    {
        public:
            byteSizeStr(uint32_t bytes) {
                int n;
                float pr_bytes = float(bytes);
                static const char * ext = "_kMGT";

                for (n = 0; pr_bytes > 2048.0; pr_bytes /= 1024.0, n++);
                stringstream msg;
                msg << pr_bytes;
                assign(msg.str());
                if (n > 0)
                    push_back(ext[n]);
            }

            ~byteSizeStr() {}
    };
}


// Device object initialization

CDevice::CDevice()
: usb(0)
, doRealtimeThread(false)
, pScreen(0)
{
    // set copyright info
    copyright = "<h1>QLandkarte Device Driver for EtrexLegendCx</h1>"
        "<h2>Driver I/F Ver. " INTERFACE_VERSION "</h2>"
        "<p>&#169; 2007 by Oliver Eichler (oliver.eichler@gmx.de)</p>"
        "<p>&#169; 2007 eTrex Legend Cx specific portions Bob Heise (heise2k@gmail.com)</p>"
        "<p>&#169; 2007 Edits by Leon van Dommelen (dommelen@eng.fsu.edu)</p>"
        "<p>This driver 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. </p>";

    // initialize the real time threading
    pthread_mutex_init(&dataMutex, NULL);
}


// Device object termination

CDevice::~CDevice()
{
    // delete any screenshot storage
    if(pScreen) delete [] pScreen;
}


// Establish contact with the device

void CDevice::_acquire()
{
    // activate the driver
    usb = new CUSB();
    usb->open();
    usb->syncup();

    // Terminate if the requirements are not met
                                 // waypoint transfer
    if (usb->getDataType(0,'A',(uint16_t)100) != 110 ||
                                 // proximity waypoint
        usb->getDataType(0,'A',(uint16_t)400) != 110 ||
                                 // route transfer
        usb->getDataType(0,'A',(uint16_t)201) != 202 ||
        usb->getDataType(1,'A',(uint16_t)201) != 110 ||
        usb->getDataType(2,'A',(uint16_t)201) != 210 ||
                                 // track log transfer
        usb->getDataType(0,'A',(uint16_t)301) != 312 ||
        usb->getDataType(1,'A',(uint16_t)301) != 302 ||
                                 // PVT transfer
    usb->getDataType(0,'A',(uint16_t)800) != 800) {
        if(strncmp(usb->getProductString().c_str(), "eTrex LegendCx", 14) == 0) {
            throw exce_t(errSync,"This eTrex Legend Cx GPS does not support the expected protocols?!?  Please contact the developers!");
        }
        else {
            throw exce_t(errSync,"This GPS is not eTrex Legend Cx compatible. Please try to select another device driver.");
        }
    }

    // check the product ID
    if(usb->getProductId() != 0x0124 &&
        usb->getProductId() != 0x01a5 &&
        usb->getProductId() != 0x02b6 &&
    usb->getProductId() != 0x0312) {
        int ok = false;
        int cancel;

        callback(-1, &ok, &cancel,
            "Unrecognized Product ID",
            "The Product ID of this GPS is unrecognized.  Proceed at your own risk?");
        if (ok == false)
            throw exce_t(errSync,"Transaction aborted.");
    }

    // initialize the properties block
    properties.set.all = 0;

    // put some data in the data block to see whether it works
    properties.set.item.product_ID = 1;
    properties.product_ID = usb->getProductId();
    properties.set.item.product_string = 1;
    properties.product_string = usb->getProductString().c_str();
}


// Upload maps to the device

                                 // input: gmapsupp.img
void CDevice::_uploadMap(const uint8_t * mapdata,
uint32_t size,                   // input: size of gmapsupp.img
const char * key                 // input: key(s) concatenated
)
{
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;
    int cancel = 0;

    // ask for SD Ram capacity
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Command_Data;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Transfer_Mem);
    usb->write(command);

    // try to read SD Ram capacity
    uint32_t memory = 0;
    while(usb->read(response)) {
        if(response.id == Pid_Capacity_Data) {
            memory = gar_ptr_load(uint32_t, response.payload + 4);
        }
    }
    if(memory == 0) {
        throw exce_t(errRuntime,"Failed to find the available memory of the GPS");
    }
    if(memory < size) {
        stringstream msg;
        msg << "Failed to send map: GPS has not enough memory (available/needed): "
            << byteSizeStr(memory) << "/" << byteSizeStr(size) << " bytes.";
        throw exce_t(errRuntime,msg.str());
    }

    // send unlock key(s) if present
    int key_cnt = 0;
    while (*key) {
        if(++key_cnt == 2) {
            int ok = false;

            callback(-1, &ok, &cancel,
                "Multiple keys is unverified",
                "Upload of multiple keys is unverified!  Proceed at your own risk?");
            if (ok == false)
                throw exce_t(errRuntime,"Upload aborted.");
        }
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Tx_Unlock_Key;
        command.size = strlen(key) + 1;
        memcpy(command.payload,key,command.size);
        usb->write(command);
        int received = 0;
        while(usb->read(response)) {
            if(response.id == Pid_Ack_Unlock_key) {
                //TODO read data
                received = 1;
            }
        }
        if(!received) {
            int ok = false;

            callback(-1, &ok, &cancel,
                "Unacknowledged key",
                "Receipt of the unlock key was not confirmed.  Proceed at your own risk?");
            if (ok == false)
                throw exce_t(errRuntime,"Upload aborted.");
        }
        key += command.size;
    }

    // switch to map transfer mode
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = 75;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, 0x000A);
    usb->write(command);

    // read incoming data
    while(usb->read(response)) {
        if(response.id == 74) {
            //TODO read data
        }
    }

    callback(0,0,&cancel,"Upload maps ...",0);

    // initialize file transfer
    uint32_t total  = size;      // save the total upload size
    uint32_t offset = 0;         // amount of data that has been uploaded
    uint32_t chunkSize;          // amount of data in a packet
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = 36;           // Pid_Map_Data?

    // transfer the data in chunks
    while(size && !cancel) {
        // transfer what will fit in a packet or whatever is left, if less
        chunkSize    = (size < (GUSB_PAYLOAD_SIZE - sizeof(offset))) ?
            size : (GUSB_PAYLOAD_SIZE - sizeof(offset));
        command.size = chunkSize + sizeof(offset);
        // put the offset in the data in the packet
        *(uint32_t*)command.payload = gar_endian(uint32_t, offset);
        // put the chunk of data in the packet
        memcpy(command.payload + sizeof(offset),mapdata,chunkSize);
        // write the packet
        usb->write(command);
        // update the amount of data left
        size    -= chunkSize;
        // increase the position in the data
        mapdata += chunkSize;
        // update the amount of uploaded data
        offset  += chunkSize;
        // periodically update the progress dialog
        double progress = ((total - size) * 100.0) / total;
        callback(int(progress),0,&cancel,0,"Transfering map data.");

    }

    callback(100,0,&cancel,0,"done");

    // terminate map transfer mode (?)
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = 45;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, 0x000A);
    usb->write(command);
}


// Get map info from the device

                                 // output: maps in the device
void CDevice::_queryMap(std::list<Map_t>& maps)
{
    maps.clear();
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;

    // Request map overview table MAPSOURC.MPS
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = 0x59;
    command.size = 19;
    Map_Request_t * req = (Map_Request_t*)command.payload;
    req->dummy1 = gar_endian(uint32_t, 0);
    req->dummy2 = gar_endian(uint16_t, 10);
    strcpy(req->section,"MAPSOURC.MPS");
    usb->write(command);

    // initialize the reading of MAPSOURC.MPS
    uint32_t size   = 1024;      // initial amount of storage
    uint32_t fill   = 0;         // the amount of data read

    // allocate some initial storage space for the incoming data
    char * pData    = (char*)calloc(size,1);
    if (!pData) {
        throw exce_t(errRuntime,"Out of memory.");
    }

    // read the returned packets
    while(usb->read(response)) {

        // acknowledge request (???)
        if(response.id == 0x5B) {
            //TODO: read data
        }

        // read in a chunk of the MAPSOURC.MPS section
        if(response.id == 0x5A) {
            // double the allocated memory if the chunk does not fit
            while((fill + response.size) > size) {
                size += size;
                pData = (char*)realloc(pData,size);
                if (!pData) {
                    throw exce_t(errRuntime,"Out of memory.");
                }
            }
            // store the data except for the starting uint8_t chunk counter
            memcpy(&pData[fill], response.payload + 1, response.size - 1);
            // update the amount of stored data
            fill += response.size - 1;
        }
    }
    *(pData + fill) = '\0';

    // create a pointer to the map tile fields
    Map_Info_t * pInfo = (Map_Info_t*)pData;

    // read the map tile records; they start with 'L', 0x4C (see gmapsupp.cpp)
    while(pInfo->tok == 0x4C) {
        // the current map
        Map_t m;
        // fill info, and get the entry size
        int mSize = m << *pInfo;
        // add to the list
        maps.push_back(m);
        // increment the tile data pointer
        pInfo =  (Map_Info_t*)(((char*)pInfo) + mSize);
    }

    // free the allocated storage
    free(pData);
}


// Download the waypoints from the device

                                 // output
void CDevice::_downloadWaypoints(list<Garmin::Wpt_t>& waypoints)
{
    waypoints.clear();
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;

    // request the waypoints
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Command_Data;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Transfer_Wpt);
    usb->write(command);

    // read the returned packets
    while(1) {

        // keep trying to read packets until Pid_Xfer_Cmplt is received
        if(!usb->read(response)) continue;

        // just ignore Pid_Records starting the transfer
        if(response.id == Pid_Records) {
            //TODO read data
#ifdef DBG_SHOW_WAYPOINT
            cout << "number of waypoints:"
                << gar_ptr_load_uint16(response.payload) << endl;
#endif
        }

        // read all waypoint packets
        if(response.id == Pid_Wpt_Data) {
            // create a pointer to the read-in waypoint info
            D110_Wpt_t * srcWpt = (D110_Wpt_t*)response.payload;
            // create space for the new waypoint
            waypoints.push_back(Wpt_t());
            Wpt_t& tarWpt = waypoints.back();
            // get the data using the overloaded << operator (Garmin.cpp)
            tarWpt << *srcWpt;
        }

        // terminate on Pid_Xfer_Cmplt
        if(response.id == Pid_Xfer_Cmplt) {
            break;
        }

    }

    // request proximity waypoints
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Command_Data;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Transfer_Prx);
    usb->write(command);

    // read the returned packets
    while(1) {

        // keep trying to read packets until Pid_Xfer_Cmplt is received
        if(!usb->read(response)) continue;

        // just ignore Pid_Records starting the transfer
        if(response.id == Pid_Records) {
            //TODO read data
#ifdef DBG_SHOW_WAYPOINT
            cout << "number of proximity waypoints:"
                << gar_ptr_load_uint16(response.payload) << endl;
#endif
        }

        // read all proximity waypoint packets
        if(response.id == Pid_Prx_Wpt_Data) {
            // create a pointer to the read-in proximity waypoint info
            D110_Wpt_t * srcWpt = (D110_Wpt_t*)response.payload;
            // create space for the new waypoint
            waypoints.push_back(Wpt_t());
            Wpt_t& tarWpt = waypoints.back();
            // get the data using the overloaded << operator (Garmin.cpp)
            tarWpt << *srcWpt;
        }

        // terminate on Pid_Xfer_Cmplt
        if(response.id == Pid_Xfer_Cmplt) {
            break;
        }
    }

#ifdef DBG_SHOW_WAYPOINT
    list<Wpt_t>::const_iterator wpt = waypoints.begin();
    while(wpt != waypoints.end()) {
        cout << "-------------------------" << endl;
        cout << "class      " << hex << (int)wpt->wpt_class << endl;
        cout << "dspl_color " << hex << (int)wpt->dspl_color << endl;
        cout << "dspl_attr  " << hex << (int)wpt->dspl_attr << endl;
        cout << "smbl       " << dec <<(int)wpt->smbl << endl;
        cout << "lat        " << wpt->lat << endl;
        cout << "lon        " << wpt->lon << endl;
        cout << "alt        " << wpt->alt << endl;
        cout << "dpth       " << wpt->dpth << endl;
        cout << "dist       " << wpt->dist << endl;
        cout << "state      " << wpt->state << endl;
        cout << "cc         " << wpt->cc << endl;
        cout << "ete        " << wpt->ete << endl;
        cout << "temp       " << wpt->temp << endl;
        cout << "time       " << wpt->time << endl;
        cout << "category   " << wpt->wpt_cat << endl;
        cout << "ident      " << wpt->ident << endl;
        cout << "comment    " << wpt->comment << endl;
        cout << "facility   " << wpt->facility << endl;
        cout << "city       " << wpt->city << endl;
        cout << "addr       " << wpt->addr << endl;
        cout << "crossroad  " << wpt->crossroad << endl;

        ++wpt;
    }
#endif
}


// Upload waypoints to the device

                                 // input
void CDevice::_uploadWaypoints(std::list<Garmin::Wpt_t>& waypoints)
{
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;

    // count the proximity waypoints; they have the proximity distance set
    uint16_t prx_wpt_cnt = 0;
    list<Wpt_t>::const_iterator wpt = waypoints.begin();
    while(wpt != waypoints.end()) {
        if(wpt->dist != 1e25f) ++prx_wpt_cnt;
        ++wpt;
    }

    // transmit the proximity waypoints first
    if(prx_wpt_cnt) {

        // announce the number of proximity waypoints to follow
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Records;
        command.size = 2;
        *(uint16_t*)command.payload =  gar_endian(uint16_t, prx_wpt_cnt);
        usb->write(command);

        // transfer them one a packet
        wpt = waypoints.begin();
        while(wpt != waypoints.end()) {
            if(wpt->dist != 1e25f) {
                // initialize the packet
                command.type = GUSB_APPLICATION_LAYER;
                command.id   = Pid_Prx_Wpt_Data;
                // create a pointer to the data area
                D110_Wpt_t * p = (D110_Wpt_t *)command.payload;
                // put in the data using the overloaded >> operator (Garmin.cpp)
                command.size = *wpt >> *p;
                // write the packet
                usb->write(command);
            }
            ++wpt;
        }

        // report the end
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Xfer_Cmplt;
        command.size = 2;
        *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Transfer_Prx);
        usb->write(command);

    }

    //transmit _all_ waypoints

    // announce the number of waypoints to follow
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Records;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, waypoints.size());
    usb->write(command);

    // transfer them one a packet
    wpt = waypoints.begin();
    while(wpt != waypoints.end()) {
        // initialize the packet
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Wpt_Data;
        // create a pointer to the data area
        D110_Wpt_t * p = (D110_Wpt_t *)command.payload;
        // put in the data using the overloaded >> operator (Garmin.cpp)
        command.size = *wpt >> *p;
        // write the packet
        usb->write(command);
        // next waypoint
        ++wpt;
    }

    // report the end
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Xfer_Cmplt;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Transfer_Wpt);
    usb->write(command);
}


// Download tracks from the device

                                 // output
void CDevice::_downloadTracks(std::list<Garmin::Track_t>& tracks)
{
    tracks.clear();
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;

    // request the tracks
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Command_Data;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Transfer_Trk);
    usb->write(command);

    // read the returned packets

    // initialize a pointer to the current track
    Track_t * track = 0;
    // name of the track
    string name;
    // segment counter
    int trackidx = 0;

    // keep trying to read packets until Pid_Xfer_Cmplt is received
    while(1) {
        if(!usb->read(response)) continue;

        // read the header of the next track
        if(response.id == Pid_Trk_Hdr) {
            // zero the segment index
            trackidx = 0;
            // create a pointer to the read-in track info
            D312_Trk_Hdr_t * hdr = (D312_Trk_Hdr_t*)response.payload;
            // create space for the new track
            tracks.push_back(Track_t());
            track = &tracks.back();
            // get the track data using the overloaded << operator (Garmin.cpp)
            *track << *hdr;
            // remember the name for subsequent segments
            name  = hdr->ident;
        }

        // read the next track point
        if(response.id == Pid_Trk_Data) {
            // create a pointer to the read-in track point data
            D302_Trk_t * data = (D302_Trk_t*)response.payload;
            // create a pointer to the stored track point data
            TrkPt_t pt;
            // new track segment
            if(data->new_trk) {
                // the first segment requires no special handling
                if(!trackidx) {
                    ++trackidx;
                }
                // for subsequent segments, create a new track entry
                else {
                    tracks.push_back(Track_t());
                    Track_t& t = tracks.back();
                    t.color = track->color;
                    t.dspl = track->dspl;
                    char str[256];
                    sprintf(str, "%s_%d", name.c_str(), trackidx++);
                    t.ident = str;
                    track = &t;
                }
            }
            // get the track point using the overloaded << operator (Garmin.cpp)
            pt << *data;
            // add it to the list
            track->track.push_back(pt);
        }

        // terminate on Pid_Xfer_Cmplt
        if(response.id == Pid_Xfer_Cmplt) {
            break;
        }

        // just ignore other packets such as Pid_Records
    }
}


// *** UNTESTED: Upload routes to the device

                                 // input: the routes
void CDevice::_uploadRoutes(list<Garmin::Route_t>& routes)
{
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;

    // loop over the routes
    list<Garmin::Route_t>::const_iterator route = routes.begin();
    while(route != routes.end()) {

        // check for record overflow
        if (route->route.size() > 0x7FFF) {
            throw exce_t(errRuntime,"Too many route points.");
        }
        // compute number of records to send for this route
        uint16_t nrec = route->route.size() * 2;
        if (nrec < 2) {
            throw exce_t(errRuntime,"No route points to upload.");
        }

        // announce the number of records to follow
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Records;
        command.size = 2;
        *(uint16_t*)command.payload = gar_endian(uint16_t, nrec);
        usb->write(command);

        // initialize the route header packet
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Rte_Hdr;
        // create a pointer to the data area
        D202_Rte_Hdr_t * r = (D202_Rte_Hdr_t *)command.payload;
        // put in the data using the overloaded >> operator (Garmin.cpp)
        command.size = *route >> *r;
        // write the packet
        usb->write(command);

        // create an index for the route waypoints
        vector<RtePt_t>::const_iterator rtept = route->route.begin();

        // initialize the first waypoint packet
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Rte_Wpt_Data;
        // create a pointer to the data area
        D110_Wpt_t * p = (D110_Wpt_t *)command.payload;
        // put in the data using the overloaded >> operator (Garmin.cpp)
        command.size = *rtept >> *p;
        // write the packet
        usb->write(command);
        // next waypoint
        ++rtept;

        // write the other waypoints and their links with the previous point
        while(rtept != route->route.end()) {

            // initialize the packet with the link to the previous waypoint
            command.type = GUSB_APPLICATION_LAYER;
            command.id   = Pid_Rte_Link_Data;
            // create a pointer to the data area
            D210_Rte_Link_t * l = (D210_Rte_Link_t *)command.payload;
            // put in the data using the overloaded >> operator (Garmin.cpp)
            command.size = *rtept >> *l;
            // write the packet
            usb->write(command);

            // initialize the waypoint packet
            command.type = GUSB_APPLICATION_LAYER;
            command.id   = Pid_Rte_Wpt_Data;
            // create a pointer to the data area
            D110_Wpt_t * p = (D110_Wpt_t *)command.payload;
            // put in the data using the overloaded >> operator (Garmin.cpp)
            command.size = *rtept >> *p;
            // write the packet
            usb->write(command);

            // write the packet
            ++rtept;
        }

        // report the end
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Xfer_Cmplt;
        command.size = 2;
        *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Transfer_Rte);
        usb->write(command);

        // next route
        ++route;
    }

}


// Upload custom waypoint icons to the device

                                 // input
void CDevice::_uploadCustomIcons(list<Garmin::Icon_t>& icons)
{
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;

    // upload all icons
    list<Garmin::Icon_t>::const_iterator icon = icons.begin();
    while(icon != icons.end()) {

        // initialize
        uint32_t tan = 0;        // next available icon ID?

        // ask for the icon ID
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Req_Icon_Id;
        command.size = 2;
        *(uint16_t*)command.payload = gar_endian(uint16_t, icon->idx + 1);
        usb->write(command);

        // read the response
        while(usb->read(response)) {
            if(response.id == Pid_Ack_Icon_Id) {
                tan = *(uint32_t*)response.payload;
            }
        }

        // request the color table for this ID?
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Req_Clr_Tbl;
        command.size = 4;
        *(uint32_t*)command.payload = tan;
        usb->write(command);

        // read the response and then send the same color table back
        while(usb->read(response)) {
            if(response.id == Pid_Ack_Clr_Tbl) {
                command = response;
            }
        }
        usb->write(command);

        // ignore any Pid_Req_Clr_Tbl (?)
        while(usb->read(response)) {
            if(response.id == Pid_Req_Clr_Tbl) {
                // TODO: ignore?
            }
        }

        // send the icon data
        command.type = GUSB_APPLICATION_LAYER;
        command.id   = Pid_Icon_Data;
        command.size = 0x104;
        *(uint32_t*)command.payload = tan;
        memcpy(command.payload + sizeof(tan),icon->data,sizeof(icon->data));
        usb->write(command);

        // ignore any Pid_Req_Clr_Tbl (?)
        while(usb->read(response)) {
            if(response.id == Pid_Ack_Icon_Data) {
                // TODO: ignore?
            }
        }

        // next icon
        ++icon;
    }
}


// Get a screen shot from the device

                                 // output: color table
void CDevice::_screenshot(char *& clrtbl,
char *& data,                    // output: screen pixels
int& width,                      // output: screen width
int& height)                     // output: screen height
{
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;

    // set the screen size
    uint16_t screenwidth = 0;
    uint16_t screenheight;
    if(strncmp(usb->getProductString().c_str(), "eTrex LegendCx", 14) == 0 ||
        strncmp(usb->getProductString().c_str(), "eTrex Legend HCx", 16) == 0 ||
        strncmp(usb->getProductString().c_str(), "eTrex VentureCx", 15) == 0 ||
        strncmp(usb->getProductString().c_str(), "eTrex VistaCx", 13) == 0 ||
        strncmp(usb->getProductString().c_str(), "eTrex Venture HC", 16) == 0 ||
    strncmp(usb->getProductString().c_str(), "eTrex Vista HCx", 15) == 0) {
        screenwidth  = 176;
        screenheight = 220;
    }
    if(strncmp(usb->getProductString().c_str(), "GPSMap76CSX", 11) == 0 ||
        strncmp(usb->getProductString().c_str(), "GPSMap76CX", 10) == 0 ||
        strncmp(usb->getProductString().c_str(), "GPSMap60CSX", 11) == 0 ||
    strncmp(usb->getProductString().c_str(), "GPSMap60CX", 10) == 0) {
        screenwidth  = 160;
        screenheight = 240;
    }
    if(!screenwidth) {
        screenwidth  = 176;
        screenheight = 220;
        if(strncmp(usb->getProductString().c_str(), "GPSMap", 6) == 0) {
            screenwidth  = 160;
            screenheight = 240;
        }
        int ok = false;
        int cancel;
        stringstream msg;

        msg << "The screen size of your device is unknown.  Proceed assuming "
            << screenwidth << "x" << screenheight << "?";
        callback(-1, &ok, &cancel, "Unknown screen size", msg.str().c_str());
        if (ok == false)
            throw exce_t(errRuntime,"Screenshot aborted.");
    }

    // initialize
    uint32_t tan = 0;            // next available icon ID?

    // ask for the icon ID
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Req_Icon_Id;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, 0);
    usb->write(command);

    // read the response
    while(usb->read(response)) {
        if(response.id == Pid_Ack_Icon_Id) {
            tan = *(uint32_t*)response.payload;
        }
    }

    // request the color table for this ID
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Req_Clr_Tbl;
    command.size = 4;
    *(uint32_t*)command.payload = tan;
    usb->write(command);

    // read the response and then send the same color table back
    while(usb->read(response)) {
        if(response.id == Pid_Ack_Clr_Tbl) {
            // send the same thing back
            command = response;
            // set the color table from our own data anyway (why?)
            memcpy(aClrtbl,_clrtbl,sizeof(aClrtbl));
        }
    }
    usb->write(command);

    // ignore any Pid_Req_Clr_Tbl (?)
    while(usb->read(response)) {
        if(response.id == Pid_Req_Clr_Tbl) {
            // TODO: ignore?
        }
    }

    // initialize the transfer
    char * buffer;               // buffer for the read in data
    char * pData;                // pointer to the buffer
    uint32_t byteCnt;            // byte count in a packet
    uint32_t byteCntTotal = 0;   // total transferred byte count

    // create the buffer to hold the read in data
    buffer = new char[screenwidth * screenheight];
    pData = buffer;

    // send request for the screen shot
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Ack_Icon_Data;
    command.size = 4;
    *(uint32_t*)command.payload = tan;
    usb->write(command);

    // read the data from the device into the buffer
    while(1) {

        // keep prompting for data
        if(!usb->read(response)) {
            usb->write(command);
            continue;
        }

        // read the next chunk of data
        if(response.id == Pid_Icon_Data) {
            // number of bytes of actual data
            byteCnt = response.size - sizeof(tan);
            // jump ship if the packet does not have any more data
            if(!byteCnt) break;
            // update the total byte count
            byteCntTotal += byteCnt;
            // jump ship if we are out of storage
            if(byteCntTotal > (uint32_t) (screenwidth * screenheight)) {
                delete [] buffer;
                throw exce_t(errRuntime,"Too many pixels received.");
            }
            // copy the data into the buffer
            memcpy(pData,response.payload + sizeof(tan), byteCnt);
            // update the buffer pointer
            pData += byteCnt;
        }
    }

    // send an packet with an unknown ID
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = 0x373;
    command.size = 4;
    *(uint32_t*)command.payload = tan;
    usb->write(command);

    // check that we got the right amount of data
    if(byteCntTotal != (uint32_t) (screenwidth * screenheight)) {
        delete [] buffer;
        throw exce_t(errRuntime,"Too few pixels received.");
    }

    // create a buffer to hold the screen in
    if(pScreen) {
        delete [] pScreen;
    }
    pScreen = new char[screenwidth * screenheight];

    // put the pixels in proper order for eTrex devices
    if(screenwidth == 176) {
        for(int r = 0; r < screenheight; ++r) {
            for(int c = 0; c < screenwidth; ++c) {
                pScreen[r * screenwidth + c] =
                    buffer[(r + 1) * screenwidth - c - 1];
            }
        }
    }
    // put the pixels in proper order for GPSMap devices
    else {
        for(int r = 0; r < screenheight; ++r) {
            for(int c = 0; c < screenwidth; ++c) {
                pScreen[r * screenwidth + c] =
                    buffer[(screenheight - r - 1) * screenwidth + c];
            }
        }
    }
    delete [] buffer;

    // copy to the output parameters
    clrtbl  = aClrtbl;
    data    = pScreen;
    width   = screenwidth;
    height  = screenheight;
}


// Set real time monitoring or turn it off

                                 // input: whether to turn it on or off
void CDevice::_setRealTimeMode(bool on)
{
    CMutexLocker lock(dataMutex);
    // if we are already in the requested state, ignore it
    if(doRealtimeThread == on) return;
    // set the requested state
    doRealtimeThread = on;
    // if we are turning on monitoring, create the thread to do it
    if(doRealtimeThread) {
        pthread_create(&thread,NULL,rtThread, this);
    }

}


// get the real time position/velocity/time (PVT)

                                 // output: the current PVT
void CDevice::_getRealTimePos(Garmin::Pvt_t& pvt)
{
    if(pthread_mutex_trylock(&mutex) != EBUSY) {
        pthread_mutex_unlock(&mutex);
        throw exce_t(errRuntime,lasterror);
    }
    CMutexLocker lock(dataMutex);
    pvt = PositionVelocityTime;
}


// return the device properties

void CDevice::_getDevProperties(Garmin::DevProperties_t& dev_properties)
{
    if(usb == 0) return;
    Packet_t command;
    Packet_t response;

    // We do not know whether this will work
    if(strncmp(usb->getProductString().c_str(), "eTrex LegendCx", 14) != 0 &&
        strncmp(usb->getProductString().c_str(), "eTrex Legend HCx", 16) != 0 &&
        strncmp(usb->getProductString().c_str(), "GPSMap76CSX", 11) != 0 &&
        strncmp(usb->getProductString().c_str(), "GPSMap76CX", 10) != 0 &&
        strncmp(usb->getProductString().c_str(), "GPSMap60CSX", 11) != 0 &&
        strncmp(usb->getProductString().c_str(), "GPSMap60CX", 10) != 0 &&
        strncmp(usb->getProductString().c_str(), "eTrex VentureCx", 15) != 0 &&
        strncmp(usb->getProductString().c_str(), "eTrex VistaCx", 13) != 0 &&
        strncmp(usb->getProductString().c_str(), "eTrex Venture HC", 16) != 0 &&
    strncmp(usb->getProductString().c_str(), "eTrex Vista HCx", 15) != 0) {
        int ok = false;
        int cancel;

        callback(-1, &ok, &cancel,
            "Map upload is unverified",
            "Map upload is unverified for this device and may damage it!  Proceed at your own risk?");
        if (ok == false)
            throw exce_t(errRuntime,"Upload aborted.");
    }

    // ask for SD Ram capacity
    command.type = GUSB_APPLICATION_LAYER;
    command.id   = Pid_Command_Data;
    command.size = 2;
    *(uint16_t*)command.payload = gar_endian(uint16_t, Cmnd_Transfer_Mem);
    usb->write(command);

    // try to read SD Ram capacity
    uint32_t memory = 0;
    uint16_t tile_limit = 0;
    while(usb->read(response)) {
        if(response.id == Pid_Capacity_Data) {
            tile_limit = gar_ptr_load(uint16_t, response.payload + 2);
            memory = gar_ptr_load(uint32_t, response.payload + 4);
        }
    }
    if(tile_limit == 0) {
        throw exce_t(errRuntime,"Failed to send map: Unable to find the tile limit of the GPS");
    }
    if(memory == 0) {
        throw exce_t(errRuntime,"Failed to send map: Unable to find the available memory of the GPS");
    }

    // add to the properties list
    properties.memory_limit = memory;
    properties.set.item.memory_limit = 1;
    properties.maps_limit = tile_limit;
    properties.set.item.maps_limit = 1;

    // return the properties
    dev_properties = properties;
}


// release the device

void CDevice::_release()
{
    if(usb == 0) return;

    // close by resetting device
    usb->close2();

    delete usb;
    usb = 0;
}

Generated by  Doxygen 1.6.0   Back to index