/* * Geopaparazzi - Digital field mapping on Android based devices * Copyright (C) 2016 HydroloGIS (www.hydrologis.com) * * 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 3 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, see <http://www.gnu.org/licenses/>. */ /* * Geopaparazzi - Digital field mapping on Android based devices * * 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 3 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, see <http://www.gnu.org/licenses/>. */ package eu.geopaparazzi.library.bluetooth; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import android.bluetooth.BluetoothSocket; import android.os.SystemClock; import eu.geopaparazzi.library.database.GPLog; /** * A utility class used to manage the communication with the bluetooth GPS whn the connection has been established. * It is used to read NMEA data from the GPS or to send SIRF III binary commands or SIRF III NMEA commands to the GPS. * You should run the main read loop in one thread and send the commands in a separate one. * * @author Herbert von Broeuschmeul * @author Andrea Antonello (www.hydrologis.com) */ @SuppressWarnings("nls") public class NmeaGpsDevice implements IBluetoothIOHandler { /** * GPS bluetooth socket used for communication. */ private BluetoothSocket socket; /** * GPS InputStream from which we read data. */ private InputStream in; /** * GPS output stream to which we send data (SIRF III binary commands). */ private OutputStream out; /** * GPS output stream to which we send data (SIRF III NMEA commands). */ private PrintStream out2; /** * A boolean which indicates if the GPS is ready to receive data. * In fact we consider that the GPS is ready when it begins to sends data... */ private boolean ready = false; private boolean enabled; private List<IBluetoothListener> bluetoothListeners = new ArrayList<>(); /* (non-Javadoc) * @see eu.geopaparazzi.library.bluetooth_tmp.IBluetoothDevice#prepare(android.bluetooth.BluetoothSocket, eu.geopaparazzi.library.bluetooth_tmp.BluetoothEnablementHandler) */ @Override public void initialize( BluetoothSocket socket ) { this.socket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; PrintStream tmpOut2 = null; try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); if (tmpOut != null) { tmpOut2 = new PrintStream(tmpOut, false, "US-ASCII"); } } catch (IOException e) { error("error while getting socket streams", e); } in = tmpIn; out = tmpOut; out2 = tmpOut2; } private void error( String msg, Exception e ) { GPLog.error(this, msg, e); } private void log( String msg ) { if (GPLog.LOG) GPLog.addLogEntry(this, null, null, msg); } /* (non-Javadoc) * @see eu.geopaparazzi.library.bluetooth_tmp.IBluetoothDevice#isReady() */ @Override public boolean isReady() { return ready; } /* (non-Javadoc) * @see eu.geopaparazzi.library.bluetooth_tmp.IBluetoothDevice#setEnabled(boolean) */ @Override public void setEnabled( boolean enabled ) { this.enabled = enabled; } /* (non-Javadoc) * @see eu.geopaparazzi.library.bluetooth_tmp.IBluetoothDevice#getSocket() */ @Override public BluetoothSocket getSocket() { return socket; } public void run() { try { BufferedReader reader = new BufferedReader(new InputStreamReader(in, "US-ASCII")); String s; long now = SystemClock.uptimeMillis(); long lastRead = now; while( (enabled) && (now < lastRead + 5000) ) { if (reader.ready()) { s = reader.readLine(); // if (Debug.D) // Logger.i(LOG_TAG, "data: " + System.currentTimeMillis() + " " + s); notifySentence(s + "\r\n"); ready = true; lastRead = SystemClock.uptimeMillis(); } else { // if (Debug.D) // Logger.d(LOG_TAG, "data: not ready " + System.currentTimeMillis()); SystemClock.sleep(50); } now = SystemClock.uptimeMillis(); } } catch (IOException e) { error("error while getting data", e); } finally { // cleanly closing everything... this.close(); } } /** * Notifies the reception of a string from the bluetooth device to registered {@link IBluetoothListener}s. * * @param sentence the complete NMEA sentence received from the bluetooth GPS (i.e. $....*XY where XY is the checksum) */ private void notifySentence( final String sentence ) { if (enabled) { final long timestamp = System.currentTimeMillis(); if (sentence != null) { for( final IBluetoothListener listener : bluetoothListeners ) { listener.onDataReceived(timestamp, sentence); } } } } /** * Write to the connected OutStream. * * @param buffer the bytes to write. */ public void write( byte[] buffer ) { try { do { Thread.sleep(100); } while( (enabled) && (!ready) ); if ((enabled) && (ready)) { out.write(buffer); out.flush(); } } catch (IOException | InterruptedException e) { error("Exception during write", e); } } /** * Write to the connected OutStream. * * @param buffer the data to write. */ public void write( String buffer ) { try { do { Thread.sleep(100); } while( (enabled) && (!ready) ); if ((enabled) && (ready)) { out2.print(buffer); out2.flush(); } } catch (InterruptedException e) { error("Exception during write", e); } } /* (non-Javadoc) * @see eu.geopaparazzi.library.bluetooth_tmp.IBluetoothDevice#close() */ @Override public void close() { ready = false; try { log("closing Bluetooth GPS output sream"); in.close(); } catch (IOException e) { error("error while closing GPS NMEA output stream", e); } finally { try { log("closing Bluetooth GPS input streams"); out2.close(); out.close(); } catch (IOException e) { error("error while closing GPS input streams", e); } finally { try { log("closing Bluetooth GPS socket"); socket.close(); } catch (IOException e) { error("error while closing GPS socket", e); } } bluetoothListeners.clear(); } } /* (non-Javadoc) * @see eu.geopaparazzi.library.bluetooth_tmp.IBluetoothDevice#addListener(eu.geopaparazzi.library.bluetooth_tmp.IBluetoothListener) */ @Override public boolean addListener( IBluetoothListener listener ) { if (!bluetoothListeners.contains(listener)) { bluetoothListeners.add(listener); return true; } return false; } /* (non-Javadoc) * @see eu.geopaparazzi.library.bluetooth_tmp.IBluetoothDevice#removeListener(eu.geopaparazzi.library.bluetooth_tmp.IBluetoothListener) */ @Override public void removeListener( IBluetoothListener listener ) { bluetoothListeners.remove(listener); } @Override public String checkRequirements() { return null; } @Override public String getName() { return null; } @Override public <T> T adapt( Class<T> adaptee ) { if (adaptee.isAssignableFrom(NmeaGpsDevice.class)) { return adaptee.cast(this); } return null; } }