/*******************************************************************************
* 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.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Dictionary;
import java.util.List;
import java.util.Timer;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
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.bmi.api.AbstractBUGModlet;
import com.buglabs.bug.bmi.sysfs.BMIDevice;
import com.buglabs.bug.dragonfly.module.IModuleControl;
import com.buglabs.bug.dragonfly.module.IModuleLEDController;
import com.buglabs.bug.dragonfly.module.IModuleProperty;
import com.buglabs.bug.dragonfly.module.ModuleProperty;
import com.buglabs.bug.jni.common.CharDeviceUtils;
import com.buglabs.bug.jni.common.FCNTL_H;
import com.buglabs.bug.jni.gps.GPS;
import com.buglabs.bug.jni.gps.GPSControl;
import com.buglabs.bug.module.gps.pub.IGPSModuleControl;
import com.buglabs.bug.module.gps.pub.INMEARawFeed;
import com.buglabs.bug.module.gps.pub.INMEASentenceProvider;
import com.buglabs.bug.module.gps.pub.INMEASentenceSubscriber;
import com.buglabs.bug.module.gps.pub.IPositionProvider;
import com.buglabs.bug.module.gps.pub.IPositionSubscriber;
import com.buglabs.bug.module.gps.pub.LatLon;
import com.buglabs.nmea.DegreesMinutesSeconds;
import com.buglabs.nmea2.RMC;
import com.buglabs.services.ws.IWSResponse;
import com.buglabs.services.ws.PublicWSDefinition;
import com.buglabs.services.ws.PublicWSProvider;
import com.buglabs.services.ws.PublicWSProvider2;
import com.buglabs.services.ws.WSResponse;
import com.buglabs.util.osgi.FilterUtil;
import com.buglabs.util.xml.XmlNode;
/**
* The Modlet exports the hardware-level services to the OSGi runtime.
*
* @author kgilmer
*
*/
public class GPSModlet extends AbstractBUGModlet implements IGPSModuleControl, PublicWSProvider2, IPositionProvider, IModuleLEDController {
private ServiceRegistration moduleReg;
protected static final String PROPERTY_IOX = "IOX";
protected static final String PROPERTY_GPS_FIX = "GPS Fix";
protected static final String PROPERTY_ANTENNA = "Antenna";
protected static final String PROPERTY_ANTENNA_PASSIVE = "Passive";
protected static final String PROPERTY_ANTENNA_ACTIVE = "Active";
/**
* Service property that defines active Antenna type.
*/
private static final String EXTERNAL_ANTENNA_PROPERTY = "gps.antenna.external";
/**
* Number of millis to wait before checking the GPS sync status.
*/
private static final long GPS_STATUS_SCAN_INTERVAL = 5000;
/**
* Milliseconds to wait before retrying setting the antenna.
*/
private static final long ANTENNA_SET_RETRY_INTERVAL_MILLIS = 200;
/**
* Maximum number of retries to set antenna.
*/
private static final int GPS_SET_ANTENNA_MAX_RETRIES = 10;
private NMEASentenceProvider nmeaProvider;
private GPSControl gpsControl;
private Timer timer;
private String serviceName = "Location";
private boolean suspended;
private InputStream gpsInputStream;
/**
* @param context
* @param slotId
* @param moduleId
* @param moduleName
*/
public GPSModlet(BundleContext context, int slotId, String moduleId, String moduleName, BMIDevice properties2) {
super(context, moduleId, properties2, moduleName);
}
/*
* (non-Javadoc)
*
* @see com.buglabs.bug.module.pub.IModlet#start()
*/
public void start() throws Exception {
boolean retry = false;
int count = 0;
do {
getLog().log(LogService.LOG_INFO, "GPSModlet setting active (external) antenna");
try {
setActiveAntenna();
retry = false;
} catch (IOException e) {
getLog().log(LogService.LOG_ERROR, "Failed to set GPS antenna to active (external) antenna", e);
retry = true;
Thread.sleep(ANTENNA_SET_RETRY_INTERVAL_MILLIS);
count++;
}
} while (retry && count < GPS_SET_ANTENNA_MAX_RETRIES);
//gpsd.start();
nmeaProvider.start();
Dictionary properties = createBasicServiceProperties();
properties.put("Power State", suspended ? "Suspended": "Active");
moduleReg = context.registerService(IModuleControl.class.getName(), this, properties);
registerService(IModuleLEDController.class.getName(), this, createBasicServiceProperties());
registerService(IGPSModuleControl.class.getName(), this, createBasicServiceProperties());
registerService(INMEARawFeed.class.getName(), new NMEARawFeed(gpsInputStream), createBasicServiceProperties());
registerService(INMEASentenceProvider.class.getName(), nmeaProvider, createBasicServiceProperties());
registerService(PublicWSProvider.class.getName(), this, null);
timer = new Timer();
timer.schedule(new GPSFIXLEDStatusTask(this, getLog()), GPS_STATUS_SCAN_INTERVAL, GPS_STATUS_SCAN_INTERVAL);
registerService(IPositionProvider.class.getName(), this, createBasicServiceProperties());
context.addServiceListener(nmeaProvider, FilterUtil.generateServiceFilter(
INMEASentenceSubscriber.class.getName(),
IPositionSubscriber.class.getName()));
}
/**
* @return A dictionary of properties for the OSGi service registry.
*/
private Dictionary createBasicServiceProperties() {
Dictionary d = getCommonProperties();
try {
d.remove(EXTERNAL_ANTENNA_PROPERTY);
d.put(EXTERNAL_ANTENNA_PROPERTY, "" + isAntennaExternal());
} catch (IOException e) {
getLog().log(LogService.LOG_ERROR, "Unable to access GPS antenna state.", e);
}
return d;
}
/**
* Update module properties based on internal state.
*/
private void updateIModuleControlProperties(){
if (moduleReg!=null){
Dictionary modProperties = createBasicServiceProperties();
modProperties.put("Power State", suspended ? "Suspended": "Active");
moduleReg.setProperties(modProperties);
}
}
/**
* @return true if antenna mode is 'external', false otherwise.
* @throws IOException on device I/O error
*/
private boolean isAntennaExternal() throws IOException {
return (getStatus() & 0xC0) == 0x40;
}
/*
* (non-Javadoc)
*
* @see com.buglabs.bug.module.pub.IModlet#stop()
*/
public void stop() throws Exception {
timer.cancel();
context.removeServiceListener(nmeaProvider);
moduleReg.unregister();
nmeaProvider.interrupt();
gpsInputStream.close();
gpsControl.close();
super.stop();
}
/*
* (non-Javadoc)
*
* @see com.buglabs.bug.module.gps.pub.IPositionProvider#getPosition()
*/
public Position getPosition() {
RMC rmc = nmeaProvider.getLastRMC();
if (rmc != null) {
try {
Position pos = 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);
return pos;
} catch (NumberFormatException e) {
getLog().log(LogService.LOG_ERROR, "Unable to parse position.", e);
return null;
}
} else {
return null;
}
}
/*
* (non-Javadoc)
*
* @see com.buglabs.module.IModuleControl#getModuleProperties()
*/
public List<IModuleProperty> getModuleProperties() {
List<IModuleProperty> mprops = super.getModuleProperties();
try {
int status = getStatus();
status &= 0xFF;
String statusValue = Integer.toHexString(status);
mprops.add(new ModuleProperty(PROPERTY_IOX, "0x" + statusValue, "Integer", false));
mprops.add(new ModuleProperty(PROPERTY_GPS_FIX, Boolean.toString((status &= 0x1) == 0)));
String antenna = PROPERTY_ANTENNA_ACTIVE;
if ((status & 0xC0) == IGPSModuleControl.STATUS_PASSIVE_ANTENNA) {
antenna = PROPERTY_ANTENNA_PASSIVE;
}
mprops.add(new ModuleProperty(PROPERTY_ANTENNA, antenna, "String", false));
} catch (IOException e) {
getLog().log(LogService.LOG_ERROR, "Exception occured while getting module properties.", e);
}
return mprops;
}
/* (non-Javadoc)
* @see com.buglabs.bug.bmi.api.AbstractBUGModlet#setModuleProperty(com.buglabs.bug.dragonfly.module.IModuleProperty)
*/
public boolean setModuleProperty(IModuleProperty property) {
if (!property.isMutable()) {
return false;
}
if (property.getName().equals("State")) {
return true;
} else if (property.getName().equals(PROPERTY_ANTENNA)) {
if (property.getValue().toString().equals(PROPERTY_ANTENNA_PASSIVE)) {
gpsControl.ioctl_BMI_GPS_PASSIVE_ANT();
logService.log(LogService.LOG_DEBUG, "Set antenna to passive.");
} else {
gpsControl.ioctl_BMI_GPS_ACTIVE_ANT();
logService.log(LogService.LOG_DEBUG, "Set antenna to active.");
}
return true;
} else {
return super.setModuleProperty(property);
}
}
/* (non-Javadoc)
* @see com.buglabs.bug.dragonfly.module.IModuleControl#resume()
*/
public int resume() throws IOException {
getBMIDevice().resume();
suspended = false;
updateIModuleControlProperties();
return 0;
}
/* (non-Javadoc)
* @see com.buglabs.bug.dragonfly.module.IModuleControl#suspend()
*/
public int suspend() throws IOException {
getBMIDevice().suspend();
suspended = true;
updateIModuleControlProperties();
return 0;
}
/* (non-Javadoc)
* @see com.buglabs.services.ws.PublicWSProvider#discover(int)
*/
public PublicWSDefinition discover(int operation) {
if (operation == PublicWSProvider2.GET) {
return new PublicWSDefinition() {
public List<String> getParameters() {
return null;
}
public String getReturnType() {
return "text/xml";
}
};
}
return null;
}
/* (non-Javadoc)
* @see com.buglabs.services.ws.PublicWSProvider#execute(int, java.lang.String)
*/
public IWSResponse execute(int operation, String input) {
if (operation == PublicWSProvider2.GET) {
return new WSResponse(getPositionXml(), "text/xml");
}
return null;
}
/**
* @return a String containing the location retrieved from GPS device in XML format.
*/
private String getPositionXml() {
Position p = getPosition();
XmlNode root = new XmlNode("Location");
if (p != null) {
if (p.getLatitude() != null) {
root.addChild(new XmlNode("Latitude", p.getLatitude().toString()));
}
if (p.getLongitude() != null) {
root.addChild(new XmlNode("Longitude", p.getLongitude().toString()));
}
if (p.getAltitude() != null) {
root.addChild(new XmlNode("Altitude", p.getAltitude().toString()));
}
RMC rmc = nmeaProvider.getLastRMC();
DegreesMinutesSeconds dmsLat = rmc.getLatitudeAsDMS();
DegreesMinutesSeconds dmsLon = rmc.getLongitudeAsDMS();
if (dmsLat != null) {
root.addChild(new XmlNode("LatitudeDegrees", Double.toString(dmsLat.toDecimalDegrees())));
}
if (dmsLon != null) {
root.addChild(new XmlNode("LongitudeDegrees", Double.toString(dmsLon.toDecimalDegrees())));
}
}
return root.toString();
}
/* (non-Javadoc)
* @see com.buglabs.services.ws.PublicWSProvider#getPublicName()
*/
public String getPublicName() {
return serviceName;
}
/* (non-Javadoc)
* @see com.buglabs.services.ws.PublicWSProvider#getDescription()
*/
public String getDescription() {
return "Returns location as provided by GPS module.";
}
/* (non-Javadoc)
* @see com.buglabs.bug.bmi.api.AbstractBUGModlet#setup()
*/
public void setup() throws Exception {
String devnode_gps = "/dev/ttyBMI" + Integer.toString(getSlotId());
String devnode_gpscontrol = "/dev/bmi_gps_control_m" + Integer.toString(getSlotId());
//Creation and initialization of this device is necessary to access the control device, below.
GPS gps = new GPS();
CharDeviceUtils.openDeviceWithRetry(gps, devnode_gps, FCNTL_H.O_RDWR | FCNTL_H.O_NONBLOCK, 2);
int result = gps.init();
if (result < 0) {
throw new RuntimeException("Unable to initialize gps device: " + devnode_gpscontrol);
}
gpsControl = new GPSControl();
getLog().log(LogService.LOG_DEBUG, "Opening GPS control port: " + devnode_gpscontrol);
CharDeviceUtils.openDeviceWithRetry(gpsControl, devnode_gpscontrol, 2);
gps.close();
getLog().log(LogService.LOG_DEBUG, "Opening GPS data port: " + devnode_gps);
gpsInputStream = new FileInputStream(devnode_gps);
nmeaProvider = new NMEASentenceProvider(gpsInputStream, context, getLog());
}
/* (non-Javadoc)
* @see com.buglabs.bug.module.gps.pub.IPositionProvider#getLatitudeLongitude()
*/
public LatLon getLatitudeLongitude() {
com.buglabs.nmea2.RMC rmc = nmeaProvider.getLastRMC();
if (rmc != null) {
return new LatLon(rmc.getLatitudeAsDMS().toDecimalDegrees(), rmc.getLongitudeAsDMS().toDecimalDegrees());
}
return null;
}
/**
* @return
* @throws IOException
*/
public int LEDGreenOff() throws IOException {
int result = -1;
if (gpsControl != null) {
result = gpsControl.ioctl_BMI_GPS_GLEDOFF();
}
if (result < 0) {
throw new IOException("ioctl BMI_GPS_GLEDOFF failed");
}
return result;
}
/**
* @return
* @throws IOException
*/
public int LEDGreenOn() throws IOException {
int result = -1;
if (gpsControl != null) {
result = gpsControl.ioctl_BMI_GPS_GLEDON();
}
if (result < 0) {
throw new IOException("ioctl BMI_GPS_GLEDON failed");
}
return result;
}
/**
* @return
* @throws IOException
*/
public int LEDRedOff() throws IOException {
int result = -1;
if (gpsControl != null) {
result = gpsControl.ioctl_BMI_GPS_RLEDOFF();
}
if (result < 0) {
throw new IOException("ioctl BMI_GPS_RLEDOFF failed");
}
return result;
}
/**
* @return
* @throws IOException
*/
public int LEDRedOn() throws IOException {
int result = -1;
if (gpsControl != null) {
result = gpsControl.ioctl_BMI_GPS_RLEDON();
}
if (result < 0) {
throw new IOException("ioctl BMI_GPS_RLEDON failed");
}
return result;
}
/* (non-Javadoc)
* @see com.buglabs.bug.module.gps.pub.IGPSModuleControl#getStatus()
*/
public int getStatus() throws IOException {
int result = -1;
if (gpsControl != null) {
result = gpsControl.ioctl_BMI_GPS_GETSTAT();
}
if (result < 0) {
throw new IOException("ioctl BMI_GPS_GETSTAT failed");
}
return result;
}
/* (non-Javadoc)
* @see com.buglabs.bug.module.gps.pub.IGPSModuleControl#setActiveAntenna()
*/
public int setActiveAntenna() throws IOException {
int result = -1;
if (gpsControl != null) {
result = gpsControl.ioctl_BMI_GPS_ACTIVE_ANT();
logService.log(LogService.LOG_DEBUG, "Result of call to gpsControl.ioctl_BMI_GPS_ACTIVE_ANT(): " + result);
}
if (result < 0) {
throw new IOException("ioctl BMI_GPS_ACTIVE_ANT failed");
}
return result;
}
/* (non-Javadoc)
* @see com.buglabs.bug.module.gps.pub.IGPSModuleControl#setPassiveAntenna()
*/
public int setPassiveAntenna() throws IOException {
int result = -1;
if (gpsControl != null) {
result = gpsControl.ioctl_BMI_GPS_PASSIVE_ANT();
logService.log(LogService.LOG_DEBUG, "Result of call to gpsControl.ioctl_BMI_GPS_PASSIVE_ANT(): " + result);
}
if (result < 0) {
throw new IOException("ioctl BMI_GPS_PASSIVE_ANT failed");
}
return result;
}
/* (non-Javadoc)
* @see com.buglabs.bug.dragonfly.module.IModuleLEDController#setLEDGreen(boolean)
*/
public int setLEDGreen(boolean state) throws IOException {
int result = -1;
if (gpsControl != null) {
if (state) {
return LEDGreenOn();
} else {
return LEDGreenOff();
}
}
return result;
}
/* (non-Javadoc)
* @see com.buglabs.bug.dragonfly.module.IModuleLEDController#setLEDRed(boolean)
*/
public int setLEDRed(boolean state) throws IOException {
int result = -1;
if (gpsControl != null) {
if (gpsControl != null) {
if (state) {
return LEDRedOn();
} else {
return LEDRedOff();
}
}
}
return result;
}
/* (non-Javadoc)
* @see com.buglabs.services.ws.PublicWSProvider2#setPublicName(java.lang.String)
*/
public void setPublicName(String name) {
serviceName = name;
}
/* (non-Javadoc)
* @see com.buglabs.bug.bmi.api.AbstractBUGModlet#isSuspended()
*/
public boolean isSuspended() {
return suspended;
}
}