qtgpsdqtlocation

Getting positions from gpsd in a Qt quick program


I have a computer with a GPS connected to a serial port that is running gpsd with a pretty basic configuration. Here is the contents of /etc/default/gpsd:

START_DAEMON="true"
USBAUTO="false"
DEVICES="/dev/ttyS0"
GPSD_OPTIONS="-n -G"
GPSD_SOCKET="/var/run/gpsd.sock"

With this config, gpsd runs fine and all gpsd client utilities, e.g. cgps, gpspipe, gpsmon, can get data from the GPS.

I am trying to access GPS data from a Qt QML program using the PositionSource element with the following syntax but lat and long show as NaN so it doesn't work:

    PositionSource {
        id: gpsPos
        updateInterval: 500
        active: true
        nmeaSource: "socket://localhost:2947"

        onPositionChanged: {
            myMap.update( gpsPos.position )
        }
     }

I tried piping the NMEA data from the GPS to another port using gpspipe -r | nc -l 6000 and specifying nmeaSource: "socket://localhost:6000 and everything works fine!

How do I make Qt talk to gpsd directly?


Solution

  • After tinkering (i.e. compiling from source, installing, configuring, testing, etc.) with gps-share, Gypsy, geoclue2, serialnmea and other ways to access data from a GPS connected to a serial port (thanks to Pa_ for all the suggestions), but all with no results while gpsd was working perfectly for other apps, I decided to make Qt support gpsd by making a very crude change to the QDeclarativePositionSource class to implement support for a gpsd scheme in the URL for the nmeaSource property. With this change, a gpsd source can now be defined as nmeaSource: "gpsd://hostname:2947" (2947 is the standard gpsd port).

    The changed code is shown below. I would suggest this should be added to Qt at some point but in the meantime, I guess I need to derive this class to implement my change in a new QML component but, being new to QML, I have no idea how that is done. I suppose it would also probably be a good idea to stop and start the NMEA stream from gpsd based on the active property of the PositionSource item... I will get to it at some point but would appreciate pointers on how to do this in a more elegant way.

    void QDeclarativePositionSource::setNmeaSource(const QUrl &nmeaSource)
    {
        if ((nmeaSource.scheme() == QLatin1String("socket") ) 
               || (nmeaSource.scheme() == QLatin1String("gpsd"))) {
            if (m_nmeaSocket
                    && nmeaSource.host() == m_nmeaSocket->peerName()
                    && nmeaSource.port() == m_nmeaSocket->peerPort()) {
                return;
            }
    
            delete m_nmeaSocket;
            m_nmeaSocket = new QTcpSocket();
    
            connect(m_nmeaSocket, static_cast<void (QTcpSocket::*)(QAbstractSocket::SocketError)> (&QAbstractSocket::error),
                    this, &QDeclarativePositionSource::socketError);
            connect(m_nmeaSocket, &QTcpSocket::connected,
                    this, &QDeclarativePositionSource::socketConnected);
    
            // If scheme is gpsd, NMEA stream must be initiated by writing a command
            // on the socket (gpsd WATCH_ENABLE | WATCH_NMEA flags)
            // (ref.: gps_sock_stream function in gpsd source file libgps_sock.c)
            if( nmeaSource.scheme() == QLatin1String("gpsd")) {
                m_nmeaSocket->connectToHost(nmeaSource.host(), 
                    nmeaSource.port(), 
                    QTcpSocket::ReadWrite);
                char const *gpsdInit = "?WATCH={\"enable\":true,\"nmea\":true}";
                m_nmeaSocket->write( gpsdInit, strlen(gpsdInit);
            } else {
                m_nmeaSocket->connectToHost(nmeaSource.host(), nmeaSource.port(), QTcpSocket::ReadOnly);
            }
        } else {
    ...