de.taimos.gpsd4java.backend.GPSdEndpoint.java Source code

Java tutorial

Introduction

Here is the source code for de.taimos.gpsd4java.backend.GPSdEndpoint.java

Source

package de.taimos.gpsd4java.backend;

/*
 * #%L
 * GPSd4Java
 * %%
 * Copyright (C) 2011 - 2012 Taimos GmbH
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import de.taimos.gpsd4java.api.IObjectListener;
import de.taimos.gpsd4java.types.ATTObject;
import de.taimos.gpsd4java.types.DeviceObject;
import de.taimos.gpsd4java.types.DevicesObject;
import de.taimos.gpsd4java.types.IGPSObject;
import de.taimos.gpsd4java.types.PollObject;
import de.taimos.gpsd4java.types.SKYObject;
import de.taimos.gpsd4java.types.TPVObject;
import de.taimos.gpsd4java.types.VersionObject;
import de.taimos.gpsd4java.types.WatchObject;
import de.taimos.gpsd4java.types.subframes.SUBFRAMEObject;

/**
 * GPSd client endpoint
 * 
 * @author thoeger
 */
public class GPSdEndpoint {

    private static final Logger LOG = LoggerFactory.getLogger(GPSdEndpoint.class);

    private Socket socket;

    private BufferedReader in;

    private BufferedWriter out;

    private SocketThread listenThread;

    private final List<IObjectListener> listeners = new ArrayList<IObjectListener>(1);

    private IGPSObject asyncResult = null;

    private final Object asyncMutex = new Object();

    private final Object asyncWaitMutex = new Object();

    private final AbstractResultParser resultParser;

    private String server;

    private int port;

    private String lastWatch;

    private AtomicLong retryInterval = new AtomicLong(1000);

    /**
     * Instantiate this class to connect to a GPSd server
     * 
     * @param server
     *            the server name or IP
     * @param port
     *            the server port
     * @param resultParser
     * @throws UnknownHostException
     * @throws IOException
     */
    public GPSdEndpoint(final String server, final int port, final AbstractResultParser resultParser)
            throws UnknownHostException, IOException {
        this.server = server;
        this.port = port;
        if (server == null) {
            throw new IllegalArgumentException("server can not be null!");
        }
        if ((port < 0) || (port > 65535)) {
            throw new IllegalArgumentException("Illegal port number: " + port);
        }
        if (resultParser == null) {
            throw new IllegalArgumentException("resultParser can not be null!");
        }

        this.socket = new Socket(server, port);
        this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
        this.out = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream()));
        this.resultParser = resultParser;
    }

    /**
     * start the endpoint
     */
    public void start() {
        this.listenThread = new SocketThread(this.in, this, this.resultParser);
        this.listenThread.start();

        try {
            Thread.sleep(500);
        } catch (final InterruptedException e) {
            GPSdEndpoint.LOG.debug("Interrupted while sleeping", e);
        }
    }

    /**
     * Stops the endpoint.
     */
    public void stop() {

        try {
            this.socket.close();
        } catch (final IOException e1) {
            GPSdEndpoint.LOG.debug("Close forced: " + e1.getMessage());
        }

        try {
            this.listeners.clear();
            if (this.listenThread != null) {
                this.listenThread.halt();
            }
        } catch (final Exception e) {
            GPSdEndpoint.LOG.debug("Interrupted while waiting for listenThread to stop", e);
        }
        this.listenThread = null;
    }

    /**
     * send WATCH command
     * 
     * @param enable
     *            enable/disable watch mode
     * @param dumpData
     *            enable/disable dumping of data
     * @return {@link WatchObject}
     * @throws IOException
     *             on IO error in socket
     * @throws JSONException
     */
    public WatchObject watch(final boolean enable, final boolean dumpData) throws IOException, JSONException {
        return this.watch(enable, dumpData, null);
    }

    /**
     * send WATCH command
     * 
     * @param enable
     *            enable/disable watch mode
     * @param dumpData
     *            enable/disable dumping of data
     * @param device
     *            If present, enable watching only of the specified device rather than all devices
     * @return {@link WatchObject}
     * @throws IOException
     *             on IO error in socket
     * @throws JSONException
     */
    public WatchObject watch(final boolean enable, final boolean dumpData, final String device)
            throws IOException, JSONException {
        JSONObject watch = new JSONObject();
        watch.put("class", "WATCH");
        watch.put("enable", enable);
        watch.put("json", dumpData);
        if (device != null) {
            watch.put("device", device);
        }
        return this.syncCommand("?WATCH=" + watch.toString(), WatchObject.class);
    }

    /**
     * Poll GPSd for Message
     * 
     * @return {@link PollObject}
     * @throws IOException
     *             on IO error in socket
     */
    public PollObject poll() throws IOException {
        return this.syncCommand("?POLL;", PollObject.class);
    }

    /**
     * Poll GPSd version
     * 
     * @return {@link VersionObject}
     * @throws IOException
     *             on IO error in socket
     */
    public VersionObject version() throws IOException {
        return this.syncCommand("?VERSION;", VersionObject.class);
    }

    // TODO implement rest of commands
    // ########################################################
    /**
     * @param listener
     *            the listener to add
     */
    public void addListener(final IObjectListener listener) {
        this.listeners.add(listener);
    }

    /**
     * @param listener
     *            the listener to remove
     */
    public void removeListener(final IObjectListener listener) {
        this.listeners.remove(listener);
    }

    // ########################################################

    /*
     * send command to GPSd and wait for response
     */
    private <T extends IGPSObject> T syncCommand(final String command, final Class<T> responseClass)
            throws IOException {
        synchronized (this.asyncMutex) {
            this.out.write(command + "\n");
            this.out.flush();
            if (responseClass == WatchObject.class) {
                lastWatch = command;
            }
            while (true) {
                // wait for awaited message
                final IGPSObject result = this.waitForResult();
                if ((result == null) || result.getClass().equals(responseClass)) {
                    return responseClass.cast(result);
                }
            }
        }
    }

    /*
     * send command without response
     */
    @SuppressWarnings("unused")
    private void voidCommand(final String command) throws IOException {
        synchronized (this.asyncMutex) {
            this.out.write(command + "\n");
            this.out.flush();
        }
    }

    /*
     * wait for a response for one second
     */
    private IGPSObject waitForResult() {
        synchronized (this.asyncWaitMutex) {
            if (this.asyncResult == null) {
                try {
                    this.asyncWaitMutex.wait(1000);
                } catch (final InterruptedException e) {
                    GPSdEndpoint.LOG.info("Interrupted while waiting for result", e);
                }
            }
            final IGPSObject result = this.asyncResult;
            this.asyncResult = null;
            return result;
        }
    }

    /*
     * handle incoming messages and dispatch them
     */
    void handle(final IGPSObject object) {
        if (object instanceof TPVObject) {
            for (final IObjectListener l : this.listeners) {
                l.handleTPV((TPVObject) object);
            }
        } else if (object instanceof SKYObject) {
            for (final IObjectListener l : this.listeners) {
                l.handleSKY((SKYObject) object);
            }
        } else if (object instanceof ATTObject) {
            for (final IObjectListener l : this.listeners) {
                l.handleATT((ATTObject) object);
            }
        } else if (object instanceof SUBFRAMEObject) {
            for (final IObjectListener l : this.listeners) {
                l.handleSUBFRAME((SUBFRAMEObject) object);
            }
        } else if (object instanceof DevicesObject) {
            for (final IObjectListener l : this.listeners) {
                l.handleDevices((DevicesObject) object);
            }
        } else if (object instanceof DeviceObject) {
            for (final IObjectListener l : this.listeners) {
                l.handleDevice((DeviceObject) object);
            }
        } else {
            // object was requested, so put it in the response object
            synchronized (this.asyncWaitMutex) {
                this.asyncResult = object;
                this.asyncWaitMutex.notifyAll();
            }
        }
    }

    /**
     * Attempt to kick a failed device back into life on gpsd server.
     * 
     * see: https://lists.gnu.org/archive/html/gpsd-dev/2015-06/msg00001.html
     * 
     * @param path Path of device known to gpsd
     * @throws IOException
     * @throws JSONException
     */
    public void kickDevice(String path) throws IOException, JSONException {
        final JSONObject d = new JSONObject();
        d.put("class", "DEVICE");
        d.put("path", path);
        this.voidCommand("?DEVICE=" + d);
    }

    /**
     * Our socket thread got disconnect and is exiting.
     */
    void handleDisconnected() throws IOException {
        synchronized (this.asyncMutex) {
            socket.close();
            this.socket = new Socket(server, port);
            this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            this.out = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream()));

            this.listenThread = new SocketThread(this.in, this, this.resultParser);
            this.listenThread.start();
            if (lastWatch != null) { // restore watch if we had one.
                this.syncCommand("?WATCH=" + lastWatch, WatchObject.class);
            }
        }

    }

    /**
     * Set a retry interval for reconnecting to GPSD if the socket closes.
     * Default value is 1000ms.
     * 
     * @param millis how long to wait between each reconnection attempts.
     */
    public void setRetryInterval(long millis) {
        retryInterval.set(millis);
    }

}