/*******************************************************************************
* Copyright (c) 2008, 2009 Bug Labs, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Bug Labs, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
package com.buglabs.bug.module.gps;
import java.io.BufferedReader;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.service.log.LogService;
import org.osgi.util.measurement.Measurement;
import org.osgi.util.measurement.Unit;
import org.osgi.util.position.Position;
import com.buglabs.bug.module.gps.pub.INMEASentenceProvider;
import com.buglabs.bug.module.gps.pub.INMEASentenceSubscriber;
import com.buglabs.bug.module.gps.pub.IPositionSubscriber;
import com.buglabs.nmea.sentences.NMEAParserException;
import com.buglabs.nmea2.AbstractNMEASentence;
import com.buglabs.nmea2.NMEASentenceFactory;
import com.buglabs.nmea2.RMC;
import com.buglabs.util.osgi.LogServiceUtil;
/**
* This class is a thread that listens for NMEA sentences on the InputStream
* passed in the constructor, and will present the last parsed sentence to
* clients.
*
* @author aroman
*
*/
public class NMEASentenceProvider extends Thread implements INMEASentenceProvider, ServiceListener {
/**
* Default value for sleep interval between GPS reads.
*/
private static final int DEFAULT_SLEEP_INTERVAL = 100;
/**
* System property to define sleep between GPS reads.
*/
public static final String SLEEP_INTERVAL_PROPERTY_KEY = "bug.gps.sleep_interval";
private InputStream nmeaStream;
private RMC cachedRMC;
private LogService log = null;
private volatile int index = 0;
private List<Object> subscribers;
private final BundleContext context;
private final long readSleepInterval;
private final boolean gpsDebug;
/**
* @param nmeaStream input stream
* @param context BundleContext
* @param log LogService
*/
public NMEASentenceProvider(InputStream nmeaStream, BundleContext context, LogService log) {
this.nmeaStream = nmeaStream;
this.context = context;
this.log = log;
this.gpsDebug = context.getProperty(Activator.GPS_DEBUG_LOGGING_KEY) != null;
if (context.getProperty(SLEEP_INTERVAL_PROPERTY_KEY) == null) {
readSleepInterval = DEFAULT_SLEEP_INTERVAL;
} else {
readSleepInterval = Integer.parseInt(context.getProperty(SLEEP_INTERVAL_PROPERTY_KEY));
}
}
/*
* (non-Javadoc)
*
* @see com.buglabs.bug.module.gps.pub.INMEASentenceProvider#getRMC()
*
* @deprecated
*/
public com.buglabs.nmea.sentences.RMC getRMC() {
// Only return RMC if valid position information is available.
if (cachedRMC == null || cachedRMC.getLatitude() == null || cachedRMC.getLongitude() == null) {
return null;
}
return new com.buglabs.nmea.sentences.RMC(cachedRMC);
}
/* (non-Javadoc)
* @see com.buglabs.bug.module.gps.pub.INMEASentenceProvider#getLastRMC()
*/
public RMC getLastRMC() {
if (cachedRMC == null || cachedRMC.getLatitude() == null || cachedRMC.getLongitude() == null) {
return null;
}
return cachedRMC;
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(nmeaStream, "US-ASCII"));
String sentence;
do {
Thread.sleep(readSleepInterval);
try {
sentence = br.readLine();
if (gpsDebug)
log.log(LogService.LOG_DEBUG, "GPS NMEA DEBUG: " + sentence);
} catch (CharConversionException e) {
sentence = "";
if (gpsDebug)
log.log(LogService.LOG_DEBUG, "A conversion error occured while parsing sentence.", e);
continue;
}
try {
AbstractNMEASentence objSentence = NMEASentenceFactory.getSentence(sentence);
if (objSentence != null && objSentence instanceof RMC) {
cachedRMC = (RMC) objSentence;
index++;
}
if (objSentence != null) {
notifySubscribers(objSentence);
}
} catch (NMEAParserException e) {
if (gpsDebug)
log.log(LogService.LOG_DEBUG, "An error occured while parsing sentence: " + sentence, e);
}
} while (!Thread.currentThread().isInterrupted() && (sentence != null));
} catch (IOException e) {
LogServiceUtil.logBundleException(log, "An IO Error occurred in reading from NMEA stream.", e);
} catch (InterruptedException e) {
// Ignore this exception.
} finally {
org.apache.commons.io.IOUtils.closeQuietly(br);
org.apache.commons.io.IOUtils.closeQuietly(nmeaStream);
}
}
/**
* Notify subscribers of location event.
* @param objSentence NMEASentence
*/
private void notifySubscribers(AbstractNMEASentence objSentence) {
if (subscribers == null || subscribers.size() == 0) {
return;
}
synchronized (subscribers) {
for (Iterator<Object> i = subscribers.iterator(); i.hasNext();) {
Object subscriber = i.next();
try {
if (subscriber instanceof INMEASentenceSubscriber) {
INMEASentenceSubscriber sub = (INMEASentenceSubscriber) subscriber;
sub.sentenceReceived(objSentence);
} else if (subscriber instanceof IPositionSubscriber && objSentence instanceof RMC) {
IPositionSubscriber sub = (IPositionSubscriber) subscriber;
sub.positionUpdate(calculatePosition((RMC) objSentence));
}
} catch (RuntimeException e) {
LogServiceUtil.logBundleException(log
, "GPS Position subscriber threw an unchecked exception in sentenceRecieved()", e);
}
}
}
}
/**
* @param rmc
* @return current Position as converted
*/
private Position calculatePosition(RMC rmc) {
return new Position(new Measurement(rmc.getLatitudeAsDMS().toDecimalDegrees() * Math.PI / 180.0, Unit.rad), new Measurement(rmc.getLongitudeAsDMS().toDecimalDegrees()
* Math.PI / 180.0, Unit.rad), new Measurement(0.0d, Unit.m), null, null);
}
/* (non-Javadoc)
* @see com.buglabs.bug.module.gps.pub.INMEASentenceProvider#getIndex()
*/
public int getIndex() {
return index;
}
/* (non-Javadoc)
* @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
*/
public void serviceChanged(ServiceEvent event) {
switch (event.getType()) {
case ServiceEvent.REGISTERED:
if (subscribers == null) {
subscribers = new ArrayList<Object>();
}
subscribers.add(context.getService(event.getServiceReference()));
break;
case ServiceEvent.UNREGISTERING:
if (subscribers != null)
subscribers.remove(context.getService(event.getServiceReference()));
break;
}
}
}