/*
* XCTrack - XContest Live Tracking client for J2ME devices
* Copyright (C) 2009 Petr Chromec <petr@xcontest.org>
*
* 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 org.xcontest.xctrack.gps;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InterruptedIOException;
import javax.microedition.io.Connector;
import javax.microedition.io.StreamConnection;
import org.xcontest.xctrack.gps.GpsMessage;
import org.xcontest.xctrack.util.Log;
class LineReader {
DataInputStream _is;
private byte[] _buffer;
int _newLineIndex;
int _bufpos;
LineReader(DataInputStream is, int bufLength) {
_is = is;
_buffer = new byte[bufLength];
_newLineIndex = -1;
}
byte[] getBuffer() {
return _buffer;
}
int getNewLineIndex() {
return _newLineIndex;
}
/**
* @param buf
* @return false on EOF
*/
boolean readNextLine() throws IOException {
DataInputStream is = _is;
byte[] buffer = _buffer;
int len = buffer.length;
// drop old line from buffer
if (_newLineIndex >= 0) {
int idx = _newLineIndex;
// drop also the trailing '\r', '\n'
while (idx < _bufpos && (buffer[idx] == 10 || buffer[idx] == 13))
idx ++;
if (idx < _bufpos)
System.arraycopy(buffer, idx, buffer, 0, _bufpos-idx);
_bufpos -= idx;
}
// check whether we already have new line in buffer
int bufpos = _bufpos; // cache bufpos
for (int i = 0; i < bufpos; i ++) {
if (buffer[i] == 10 || buffer[i] == 13) {
_newLineIndex = i;
return true;
}
}
while (bufpos < len) {
int navail = is.available();
if (navail > 0) {
if (navail > len-bufpos)
navail = len-bufpos;
is.read(buffer,bufpos,navail);
for (int i = 0; i < navail; i ++) {
if (buffer[bufpos+i] == 10 || buffer[bufpos+i] == 13) { // we have another line?
_newLineIndex = bufpos+i;
_bufpos = bufpos+navail;
return true;
}
}
bufpos += navail;
}
else {
int c = is.read(); //block
if (c == -1) { // EOF
if (bufpos > 0) {
_bufpos = bufpos;
_newLineIndex = bufpos;
return true;
}
else {
return false;
}
}
if (c == 10 || c == 13) {
if (bufpos > 0) {
buffer[bufpos++] = (byte)c;
_newLineIndex = bufpos;
_bufpos = bufpos;
return true;
}
}
else {
buffer[bufpos++] = (byte)c;
}
}
}
// buffer full, no CR/LF found
_bufpos = bufpos; // == _buffer.length
_newLineIndex = bufpos;
return true;
}
}
public abstract class NMEADriver extends GpsDriver implements Runnable {
private static final int RECONNECT_TIMEOUT=5000; //ms
private String _deviceAddress;
private StreamConnection _connection;
private DataInputStream _inputStream;
private Thread _thread;
public NMEADriver() {
_deviceAddress = null;
_connection = null;
_inputStream = null;
_thread = null;
}
protected void setDeviceAddress(String addr) {
_deviceAddress = addr;
}
/** Start reader thread */
public synchronized void connect(String address) {
// close previous connection
cleanup();
_deviceAddress = address;
if (_thread == null) {
_thread = new Thread(this);
_thread.start();
}
}
public synchronized void disconnect() {
if (_thread != null) {
_thread.interrupt();
_thread = null;
}
}
public void run() {
//int cnt = 0;
NMEAParser parser = new NMEAParser();
StreamConnection conn;
DataInputStream inputStream;
LineReader reader = null;
GpsMessage msg = new GpsMessage();
Log.info("NMEA thread started");
try {
while (_thread != null) {
synchronized(this) {
inputStream = _inputStream;
conn = _connection;
}
// try to (re)connect
if (inputStream == null) {
try {
conn = (StreamConnection)Connector.open(_deviceAddress);
Log.info("NMEA Connected OK");
try {
conn.openOutputStream().write(new byte[]{36,83,84,65,13,10}); // "$STA\r\n" : start transmitting
Log.info("NMEA Sent $STA message");
}
catch(InterruptedIOException e) {
throw e;
}
catch(IOException e) {}
inputStream = conn.openDataInputStream();
reader = new LineReader(inputStream,1000);
synchronized(this) {
_connection = conn;
_inputStream = inputStream;
deviceConnected();
}
}
catch(InterruptedIOException e) {
throw e;
}
catch(IOException e) {
cleanup();
synchronized(this) {
wait(RECONNECT_TIMEOUT);
}
}
catch(SecurityException e) {
cleanup();
synchronized(this) {
wait(RECONNECT_TIMEOUT);
}
}
}
else { // reader != null
try {
if (reader.readNextLine()) {
if (parser.parse(reader.getBuffer(),reader.getNewLineIndex(),msg)) {
checkGpsPositionAge(msg);
notifyListeners(msg);
}
else {
checkGpsPositionAge(null);
}
}
else { // EOF
Log.info("NMEA read EOF - device disconnected");
deviceDisconnected();
cleanup();
reader = null;
}
}
catch (InterruptedIOException e) {
throw e;
}
catch (IOException e) {
Log.error("NMEA error reading from device",e);
deviceDisconnected();
cleanup();
reader = null;
}
}
} // while(true)
}
catch(InterruptedException e){}
catch(InterruptedIOException e){}
Log.info("NMEA Driver EXIT");
cleanup();
}
protected synchronized void cleanup() {
if (_inputStream != null) {
try {
_inputStream.close();
}
catch (IOException e) {
}
_inputStream = null;
}
if (_connection != null) {
try {
_connection.close();
}
catch (IOException e) {
}
_connection = null;
}
}
}