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.logging.Level; import java.util.logging.Logger; import org.json.JSONException; import org.json.JSONObject; 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.ParseException; 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 = Logger.getLogger(GPSdEndpoint.class.getName()); private final Socket socket; private final BufferedReader in; private final BufferedWriter out; private SocketThread listenThread; private final List<IObjectListener> listeners = new ArrayList<IObjectListener>(1); private IGPSObject asnycResult = null; private final Object asyncMutex = new Object(); private final Object asyncWaitMutex = new Object(); private final AbstractResultParser resultParser; /** * 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 { 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.log(Level.FINE, null, e); } } /** * Stops the endpoint. */ public void stop() { try { this.listenThread.halt(); } catch (final Exception e) { GPSdEndpoint.LOG.log(Level.FINE, null, 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 { final 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 * @throws ParseException * on illegal response */ public PollObject poll() throws IOException, ParseException { return this.syncCommand("?POLL;", PollObject.class); } /** * Poll GPSd version * * @return {@link VersionObject} * @throws IOException * on IO error in socket * @throws ParseException * on illegal response */ public VersionObject version() throws IOException, ParseException { return this.syncCommand("?VERSION;", VersionObject.class); } // ######################################################## /** * @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(); 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) { this.asnycResult = null; try { this.asyncWaitMutex.wait(1000); } catch (final InterruptedException e) { GPSdEndpoint.LOG.log(Level.INFO, null, e); } if (this.asnycResult != null) { return this.asnycResult; } } return null; } /* * 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.asnycResult = object; this.asyncWaitMutex.notifyAll(); } } } }