package com.geeksville.location;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.hardware.SensorManager;
import android.location.Location;
import android.util.Log;
import com.geeksville.android.PreferenceUtil;
import com.geeksville.util.LinearRegression;
/**
* A client for the FlyNet bluetooth vario
*
* @author relet
*/
public class FlynetBarometerClient extends Observable implements
IBarometerClient, Runnable {
private static final String TAG = "FlynetBarometerClient";
// / What device name do we look for?
// private static final String myName = "FlyNet";
// / What device type
private static final int myClass = 7936;
// Commands recognized by the FlyNet device
private final String CMD_PRESSURE = "_PRS";
private final String CMD_BATTERY = "_BAT";
@SuppressWarnings("unused")
private final String CMD_DEVICENAME = "_USR";
/** A unique ID for our app */
@SuppressWarnings("unused")
private UUID uuid = UUID.fromString("b00d0c47-899b-4484-810a-5b27a514e906");
private BluetoothDevice device;
private Thread thread;
private String status = "FlyNet";
private float batPercentage, pressure, altitude;
private boolean isCharging = false;
private Calibration calibration = Calibration.UNCALIBRATED;
// / Defaults to 1013.25 hPa
private float reference = SensorManager.PRESSURE_STANDARD_ATMOSPHERE;
LinearRegression regression = new LinearRegression();
public FlynetBarometerClient(Context context) {
this.device = findDevice();
long xspan = (long) (PreferenceUtil.getFloat(context,
"integration_period2", 0.7f) * 1000);
regression.setXspan(xspan);
}
public void addObserver(Observer observer) {
super.addObserver(observer);
// We do all the real work in a background thread, so we don't stall and can
// handle reboots of the bluetooth device
if ((thread==null)||(thread.isAlive()==false)) {
thread = new Thread(this, "FlyNet");
thread.setDaemon(true);
thread.start();
}
}
static boolean isAvailable() {
BluetoothDevice found = findDevice();
return found != null;
}
private static BluetoothDevice findDevice() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null && adapter.isEnabled()) {
Set<BluetoothDevice> pairedDevices = adapter.getBondedDevices();
for (BluetoothDevice device : pairedDevices) {
if ((device.getBluetoothClass().getDeviceClass() == myClass)) {
Log.d(TAG,
"Connected to " + device.getName() + "@" + device.getAddress() + " which has device ID " + device.getBluetoothClass().getDeviceClass());
return device;
}
}
}
return null;
}
@Override
public void setAltitude(float meters, Calibration calibration) {
// float p0 = 1013.25f; // Pressure at sea level (hPa)
// float p = p0 * (float) Math.pow((1 - meters / 44330), 5.255);
float p0 = pressure / (float) Math.pow((1 - meters / 44330), 5.255);
reference = p0;
altitude = SensorManager.getAltitude(reference, pressure);
Log.w(TAG, "Setting baro reference to " + reference + " alt=" + meters);
calibration = calibration;
}
@Override
public float getAltitude() {
return altitude;
}
public float getPressure() {
return pressure;
}
public float getBattery() {
return Float.NaN;
// FIXME - if we know the battery size, we can calculate this from the percentage.
}
public float getBatteryPercent() {
return batPercentage;
}
public boolean isCharging() {
return isCharging;
}
public String getStatus() {
return status;
}
public void setStatus(String s) {
status = s;
}
public Calibration getCalibration()
{
return Calibration.UNCALIBRATED;
}
@Override
public float getVerticalSpeed() {
try {
return (float)(regression.getSlope() * 1000);
} catch (ArithmeticException divByZero) {
return Float.NaN;
}
}
@Override
public void improveLocation(Location l) {
if (calibration != Calibration.UNCALIBRATED)
l.setAltitude(altitude);
}
private void handleMessage(String m) {
if (m.length()>4) {
String cmd = m.substring(0,4);
if (cmd.equals(CMD_PRESSURE)) {
// "_PRS 17CBA\n" corresponds to 0x17CBA Pa
pressure = Integer.parseInt(m.substring(5,10), 16) / 100.f;
altitude = SensorManager.getAltitude(reference, pressure);
regression.addSample(System.currentTimeMillis(), altitude);
//Log.d(TAG, "-> pressure = " + pressure + "\t altitude = "+altitude);
} else
if (cmd.equals(CMD_BATTERY)) {
// "_BAT 9\n" corresponds to 90%
// "_BAT *\n" signals charging status
if (m.charAt(5) == '*') {
isCharging = true;
} else {
batPercentage = Integer.parseInt(m.substring(5,6), 16) / 16.f;
isCharging = false; // FIXME - may need a timeout if it actually alternates with the * message when charging
}
}
// Tell the GUI/audio vario we have new state
setChanged();
notifyObservers(pressure);
}
}
/** The background thread that talks to device */
@Override
public void run() {
BluetoothSocket socket = null;
do {
setStatus("? FlyNet");
try {
//socket = device.createRfcommSocketToServiceRecord(uuid); /* NOTE - does not work in Android 2.1/2.2 */
BluetoothDevice hxm = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(device.getAddress());
Method m;
try {
m = hxm.getClass().getMethod("createRfcommSocket", new Class[]{int.class});
socket = (BluetoothSocket)m.invoke(hxm, Integer.valueOf(1));
} catch (Exception e) {
Log.d(TAG, "Error while creating socket", e);
}
if (socket != null) {
// Connect the device through the socket. This will block
// until it succeeds or throws an exception
socket.connect();
// Read messages
BufferedReader reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
String line;
setStatus("+ FlyNet");
while ((line = reader.readLine()) != null)
handleMessage(line);
reader.close();
setStatus("- FlyNet");
socket.close();
socket = null;
}
} catch (IOException connectException) {
// close the socket and get out
Log.d(TAG, "Error while connecting", connectException);
try {
if (socket != null)
socket.close();
} catch (IOException closeException) {
// Ignore errors on close
}
}
} while (this.countObservers() > 0);
}
}