altosdroid: Use single object to pass data to UI
authorKeith Packard <keithp@keithp.com>
Thu, 4 Sep 2014 05:32:49 +0000 (22:32 -0700)
committerKeith Packard <keithp@keithp.com>
Thu, 4 Sep 2014 05:32:49 +0000 (22:32 -0700)
Instead of having separate messages for each piece of telemetry state,
package the whole mess up in one object and send it for any
change. This simplifies tracking within the UI by avoiding corner
cases during reconnect.

Signed-off-by: Keith Packard <keithp@keithp.com>
altosdroid/res/values/strings.xml
altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java
altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java
altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java
altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java
altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java

index 0384b9b8e12e59e1cb92b4a6a6e751ef36b28099..6ea9fec21af615664c9971bfe6fa1a0b823238f7 100644 (file)
@@ -22,7 +22,7 @@
        <!-- AltosDroid -->
        <string name="bt_not_enabled">Bluetooth was not enabled.</string>
        <string name="title_connecting">connecting…</string>
-       <string name="title_connected_to">connected</string>
+       <string name="title_connected_to">connected</string>
        <string name="title_not_connected">not connected</string>
 
        <!-- Options Menu -->
index 4a1fc3716c9320d6cbf8bca44990832d1edce89c..51ef5e94652d2c2dfe47a3d0b774497d873a1f7c 100644 (file)
@@ -146,16 +146,18 @@ public class AltosBluetooth extends AltosLink {
        }
 
        public void save_frequency() {
-               AltosPreferences.set_frequency(serial, frequency);
+               AltosPreferences.set_frequency(0, frequency);
        }
 
        public void save_telemetry_rate() {
-               AltosPreferences.set_telemetry_rate(serial, telemetry_rate);
+               AltosPreferences.set_telemetry_rate(0, telemetry_rate);
        }
 
        private synchronized void wait_connected() throws InterruptedException, IOException {
                if (input == null) {
+                       if (D) Log.d(TAG, "wait_connected...");
                        wait();
+                       if (D) Log.d(TAG, "wait_connected done");
                        if (input == null) throw new IOException();
                }
        }
index db1ca6916c643fc448ad69639e860fdf8e0f058c..d276798eb929e0665e3ea416d4b3abfda0380a31 100644 (file)
@@ -60,20 +60,18 @@ public class AltosDroid extends FragmentActivity {
        static final boolean D = true;
 
        // Message types received by our Handler
-       public static final int MSG_STATE_CHANGE    = 1;
-       public static final int MSG_TELEMETRY       = 2;
-       public static final int MSG_UPDATE_AGE      = 3;
-       public static final int MSG_LOCATION        = 4;
-       public static final int MSG_CRC_ERROR       = 5;
-       public static final int MSG_FREQUENCY       = 6;
-       public static final int MSG_TELEMETRY_RATE  = 7;
+
+       public static final int MSG_STATE           = 1;
+       public static final int MSG_UPDATE_AGE      = 2;
 
        // Intent request codes
-       private static final int REQUEST_CONNECT_DEVICE = 1;
-       private static final int REQUEST_ENABLE_BT      = 2;
+       public static final int REQUEST_CONNECT_DEVICE = 1;
+       public static final int REQUEST_ENABLE_BT      = 2;
 
        public static FragmentManager   fm;
 
+       private BluetoothAdapter mBluetoothAdapter = null;
+
        // Layout Views
        private TextView mTitle;
 
@@ -100,20 +98,14 @@ public class AltosDroid extends FragmentActivity {
        int             tabHeight;
 
        // Timer and Saved flight state for Age calculation
-       private Timer timer = new Timer();
+       private Timer timer;
        AltosState saved_state;
-       Location saved_location;
 
        // Service
        private boolean mIsBound   = false;
        private Messenger mService = null;
        final Messenger mMessenger = new Messenger(new IncomingHandler(this));
 
-       // TeleBT Config data
-       private AltosConfigData mConfigData = null;
-       // Local Bluetooth adapter
-       private BluetoothAdapter mBluetoothAdapter = null;
-
        // Text to Speech
        private AltosVoice mAltosVoice = null;
 
@@ -125,44 +117,21 @@ public class AltosDroid extends FragmentActivity {
                @Override
                public void handleMessage(Message msg) {
                        AltosDroid ad = mAltosDroid.get();
+
                        switch (msg.what) {
-                       case MSG_STATE_CHANGE:
-                               if(D) Log.d(TAG, "MSG_STATE_CHANGE: " + msg.arg1);
-                               switch (msg.arg1) {
-                               case TelemetryService.STATE_CONNECTED:
-                                       ad.set_config_data((AltosConfigData) msg.obj);
-                                       break;
-                               case TelemetryService.STATE_CONNECTING:
-                                       ad.mTitle.setText(R.string.title_connecting);
-                                       break;
-                               case TelemetryService.STATE_READY:
-                               case TelemetryService.STATE_NONE:
-                                       ad.mConfigData = null;
-                                       ad.mTitle.setText(R.string.title_not_connected);
-                                       String  active_device = AltosDroidPreferences.active_device();
-                                       if (active_device != null)
-                                               ad.connectDevice(active_device);
-                                       break;
+                       case MSG_STATE:
+                               if(D) Log.d(TAG, "MSG_STATE");
+                               TelemetryState telemetry_state = (TelemetryState) msg.obj;
+                               if (telemetry_state == null) {
+                                       Log.d(TAG, "telemetry_state null!");
+                                       return;
                                }
-                               break;
-                       case MSG_TELEMETRY:
-                               ad.update_ui((AltosState) msg.obj);
-                               break;
-                       case MSG_LOCATION:
-                               ad.set_location((Location) msg.obj);
-                               break;
-                       case MSG_CRC_ERROR:
+
+                               ad.update_state(telemetry_state);
                                break;
                        case MSG_UPDATE_AGE:
-                               if (ad.saved_state != null) {
-                                       ad.mAgeView.setText(String.format("%d", (System.currentTimeMillis() - ad.saved_state.received_time + 500) / 1000));
-                               }
-                               break;
-                       case MSG_FREQUENCY:
-                               ad.set_frequency((Double) msg.obj);
-                               break;
-                       case MSG_TELEMETRY_RATE:
-                               ad.set_telemetry_rate((Integer) msg.obj);
+                               if(D) Log.d(TAG, "MSG_UPDATE_AGE");
+                               ad.update_age();
                                break;
                        }
                }
@@ -218,37 +187,52 @@ public class AltosDroid extends FragmentActivity {
                mTabs.remove(mTab);
        }
 
-       void set_location(Location location) {
-               saved_location = location;
-               Log.d(TAG, "set_location");
-               update_ui(saved_state);
-       }
-
-       void set_title() {
-               if (mConfigData != null) {
-                       String str = String.format("S/N %d %6.3f MHz", mConfigData.serial, frequency);
-
-                       if (telemetry_rate != AltosLib.ao_telemetry_rate_38400)
-                               str = str.concat(String.format(" %d bps", AltosLib.ao_telemetry_rate_values[telemetry_rate]));
-                       mTitle.setText(str);
+       void update_title(TelemetryState telemetry_state) {
+               switch (telemetry_state.connect) {
+               case TelemetryState.CONNECT_CONNECTED:
+                       if (telemetry_state.config != null) {
+                               String str = String.format("S/N %d %6.3f MHz", telemetry_state.config.serial,
+                                                          telemetry_state.frequency);
+                               if (telemetry_state.telemetry_rate != AltosLib.ao_telemetry_rate_38400)
+                                       str = str.concat(String.format(" %d bps",
+                                                                      AltosLib.ao_telemetry_rate_values[telemetry_state.telemetry_rate]));
+                               mTitle.setText(str);
+                       } else {
+                               mTitle.setText(R.string.title_connected_to);
+                       }
+                       break;
+               case TelemetryState.CONNECT_CONNECTING:
+                       mTitle.setText(R.string.title_connecting);
+                       break;
+               case TelemetryState.CONNECT_READY:
+               case TelemetryState.CONNECT_NONE:
+                       mTitle.setText(R.string.title_not_connected);
+                       break;
                }
        }
 
-       void set_frequency(double frequency) {
-               if (D) Log.d(TAG, String.format("AltosDroid: set_frequency %f\n", frequency));
-               this.frequency = frequency;
-               set_title();
+       void start_timer() {
+               if (timer == null) {
+                       timer = new Timer();
+                       timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 1000L, 1000L);
+               }
        }
 
-       void set_telemetry_rate(int telemetry_rate) {
-               if (D) Log.d(TAG, String.format("AltosDroid: set_telemetry_rate %d\n", telemetry_rate));
-               this.telemetry_rate = telemetry_rate;
-               set_title();
+       void stop_timer() {
+               if (timer != null) {
+                       timer.cancel();
+                       timer.purge();
+                       timer = null;
+               }
        }
 
-       void set_config_data(AltosConfigData config_data) {
-               mConfigData = config_data;
-               set_title();
+       void update_state(TelemetryState telemetry_state) {
+               update_title(telemetry_state);
+               update_ui(telemetry_state.state, telemetry_state.location);
+               if (telemetry_state.connect == TelemetryState.CONNECT_CONNECTED)
+                       start_timer();
+               else
+                       stop_timer();
        }
 
        boolean same_string(String a, String b) {
@@ -263,52 +247,73 @@ public class AltosDroid extends FragmentActivity {
                }
        }
 
-       void update_ui(AltosState state) {
+       void update_age() {
+               if (saved_state != null)
+                       mAgeView.setText(String.format("%d", (System.currentTimeMillis() - saved_state.received_time + 500) / 1000));
+       }
+
+       void update_ui(AltosState state, Location location) {
 
                Log.d(TAG, "update_ui");
 
                int prev_state = AltosLib.ao_flight_invalid;
 
+               AltosGreatCircle from_receiver = null;
+
                if (saved_state != null)
                        prev_state = saved_state.state;
 
                if (state != null) {
                        Log.d(TAG, String.format("prev state %d new state  %d\n", prev_state, state.state));
-                       if (prev_state != state.state) {
-                               String currentTab = mTabHost.getCurrentTabTag();
-                               Log.d(TAG, "switch state");
-                               switch (state.state) {
-                               case AltosLib.ao_flight_boost:
-                                       if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("ascent");
-                                       break;
-                               case AltosLib.ao_flight_drogue:
-                                       if (currentTab.equals("ascent")) mTabHost.setCurrentTabByTag("descent");
-                                       break;
-                               case AltosLib.ao_flight_landed:
-                                       if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("landed");
-                                       break;
-                               case AltosLib.ao_flight_stateless:
-                                       if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent");
-                                       break;
+                       if (state.state == AltosLib.ao_flight_stateless) {
+                               boolean prev_locked = false;
+                               boolean locked = false;
+
+                               if(state.gps != null)
+                                       locked = state.gps.locked;
+                               if (saved_state != null && saved_state.gps != null)
+                                       prev_locked = saved_state.gps.locked;
+                               if (prev_locked != locked) {
+                                       String currentTab = mTabHost.getCurrentTabTag();
+                                       if (locked) {
+                                               if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent");
+                                       } else {
+                                               if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("pad");
+                                       }
+                               }
+                       } else {
+                               if (prev_state != state.state) {
+                                       String currentTab = mTabHost.getCurrentTabTag();
+                                       Log.d(TAG, "switch state");
+                                       switch (state.state) {
+                                       case AltosLib.ao_flight_boost:
+                                               if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("ascent");
+                                               break;
+                                       case AltosLib.ao_flight_drogue:
+                                               if (currentTab.equals("ascent")) mTabHost.setCurrentTabByTag("descent");
+                                               break;
+                                       case AltosLib.ao_flight_landed:
+                                               if (currentTab.equals("descent")) mTabHost.setCurrentTabByTag("landed");
+                                               break;
+                                       case AltosLib.ao_flight_stateless:
+                                               if (currentTab.equals("pad")) mTabHost.setCurrentTabByTag("descent");
+                                               break;
+                                       }
                                }
                        }
-               }
-
-               AltosGreatCircle from_receiver = null;
 
-               if (state != null && saved_location != null && state.gps != null && state.gps.locked) {
-                       double altitude = 0;
-                       if (saved_location.hasAltitude())
-                               altitude = saved_location.getAltitude();
-                       from_receiver = new AltosGreatCircle(saved_location.getLatitude(),
-                                                            saved_location.getLongitude(),
-                                                            altitude,
-                                                            state.gps.lat,
-                                                            state.gps.lon,
-                                                            state.gps.alt);
-               }
+                       if (location != null && state.gps != null && state.gps.locked) {
+                               double altitude = 0;
+                               if (location.hasAltitude())
+                                       altitude = location.getAltitude();
+                               from_receiver = new AltosGreatCircle(location.getLatitude(),
+                                                                    location.getLongitude(),
+                                                                    altitude,
+                                                                    state.gps.lat,
+                                                                    state.gps.lon,
+                                                                    state.gps.alt);
+                       }
 
-               if (state != null) {
                        if (saved_state == null || !same_string(saved_state.callsign, state.callsign)) {
                                Log.d(TAG, "update callsign");
                                mCallsignView.setText(state.callsign);
@@ -337,10 +342,10 @@ public class AltosDroid extends FragmentActivity {
                }
 
                for (AltosDroidTab mTab : mTabs)
-                       mTab.update_ui(state, from_receiver, saved_location, mTab == mTabsAdapter.currentItem());
+                       mTab.update_ui(state, from_receiver, location, mTab == mTabsAdapter.currentItem());
 
                if (state != null)
-                       mAltosVoice.tell(state);
+                       mAltosVoice.tell(state, from_receiver);
 
                saved_state = state;
        }
@@ -382,8 +387,6 @@ public class AltosDroid extends FragmentActivity {
                super.onCreate(savedInstanceState);
                if(D) Log.e(TAG, "+++ ON CREATE +++");
 
-               fm = getSupportFragmentManager();
-
                // Get local Bluetooth adapter
                mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
 
@@ -391,11 +394,9 @@ public class AltosDroid extends FragmentActivity {
                if (mBluetoothAdapter == null) {
                        Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
                        finish();
-                       return;
                }
 
-               // Initialise preferences
-               AltosDroidPreferences.init(this);
+               fm = getSupportFragmentManager();
 
                // Set up the window layout
                requestWindowFeature(Window.FEATURE_CUSTOM_TITLE);
@@ -438,7 +439,6 @@ public class AltosDroid extends FragmentActivity {
                for (int i = 0; i < 5; i++)
                        mTabHost.getTabWidget().getChildAt(i).getLayoutParams().height = tabHeight;
 
-
                // Set up the custom title
                mTitle = (TextView) findViewById(R.id.title_left_text);
                mTitle.setText(R.string.app_name);
@@ -458,8 +458,6 @@ public class AltosDroid extends FragmentActivity {
                mStateView     = (TextView) findViewById(R.id.state_value);
                mAgeView       = (TextView) findViewById(R.id.age_value);
 
-               timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 1000L, 100L);
-
                mAltosVoice = new AltosVoice(this);
        }
 
@@ -468,14 +466,14 @@ public class AltosDroid extends FragmentActivity {
                super.onStart();
                if(D) Log.e(TAG, "++ ON START ++");
 
+               // Start Telemetry Service
+               startService(new Intent(AltosDroid.this, TelemetryService.class));
+
                if (!mBluetoothAdapter.isEnabled()) {
                        Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
-                       startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
+                       startActivityForResult(enableIntent, AltosDroid.REQUEST_ENABLE_BT);
                }
 
-               // Start Telemetry Service
-               startService(new Intent(AltosDroid.this, TelemetryService.class));
-
                doBindService();
 
        }
@@ -506,6 +504,7 @@ public class AltosDroid extends FragmentActivity {
                if(D) Log.e(TAG, "--- ON DESTROY ---");
 
                if (mAltosVoice != null) mAltosVoice.stop();
+               stop_timer();
        }
 
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
@@ -534,12 +533,10 @@ public class AltosDroid extends FragmentActivity {
        }
 
        private void connectDevice(String address) {
-               // Get the BLuetoothDevice object
-               BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
                // Attempt to connect to the device
                try {
-                       if (D) Log.d(TAG, "Connecting to " + device.getName());
-                       mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, device));
+                       if (D) Log.d(TAG, "Connecting to " + address);
+                       mService.send(Message.obtain(null, TelemetryService.MSG_CONNECT, address));
                } catch (RemoteException e) {
                }
        }
@@ -547,7 +544,6 @@ public class AltosDroid extends FragmentActivity {
        private void connectDevice(Intent data) {
                // Get the device MAC address
                String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
-               AltosDroidPreferences.set_active_device(address);
                connectDevice(address);
        }
 
index b05913b68140ce1224f6c028bd3cba3ff4d304fa..b8def36750c7b83df79622186fa100a80c1e6c56 100644 (file)
@@ -58,7 +58,7 @@ public class AltosVoice {
                }
        }
 
-       public void tell(AltosState state) {
+       public void tell(AltosState state, AltosGreatCircle from_receiver) {
                if (!tts_enabled) return;
 
                boolean spoke = false;
@@ -88,13 +88,14 @@ public class AltosVoice {
                }
                old_state = state;
                if (idle_thread != null)
-                       idle_thread.notice(state, spoke);
+                       idle_thread.notice(state, from_receiver, spoke);
        }
 
 
        class IdleThread extends Thread {
                boolean            started;
                private AltosState state;
+               private AltosGreatCircle from_receiver;
                int                reported_landing;
                int                report_interval;
                long               report_time;
@@ -112,25 +113,26 @@ public class AltosVoice {
                                return;
                        }
 
-                       /* If the rocket isn't on the pad, then report height */
-                       if (((AltosLib.ao_flight_drogue <= state.state &&
+                       /* If the rocket isn't on the pad, then report location */
+                       if ((AltosLib.ao_flight_drogue <= state.state &&
                              state.state < AltosLib.ao_flight_landed) ||
-                            state.state == AltosLib.ao_flight_stateless) &&
-                           state.range >= 0)
+                            state.state == AltosLib.ao_flight_stateless)
                        {
-                               if (state.from_pad != null) {
+                               AltosGreatCircle        position;
+
+                               if (from_receiver != null)
+                                       position = from_receiver;
+                               else
+                                       position = state.from_pad;
+
+                               if (position != null) {
                                        speak(String.format("Height %d, bearing %s %d, elevation %d, range %d.\n",
                                                            (int) (state.height() + 0.5),
-                                                           state.from_pad.bearing_words(
+                                                           position.bearing_words(
                                                                    AltosGreatCircle.BEARING_VOICE),
-                                                           (int) (state.from_pad.bearing + 0.5),
-                                                           (int) (state.elevation + 0.5),
-                                                           (int) (state.range + 0.5)));
-                               } else {
-                                       speak(String.format("Height %d, elevation %d, range %d.\n",
-                                                           (int) (state.height() + 0.5),
-                                                           (int) (state.elevation + 0.5),
-                                                           (int) (state.range + 0.5)));
+                                                           (int) (position.bearing + 0.5),
+                                                           (int) (position.elevation + 0.5),
+                                                           (int) (position.range + 0.5)));
                                }
                        } else if (state.state > AltosLib.ao_flight_pad) {
                                if (state.height() != AltosLib.MISSING)
@@ -186,9 +188,10 @@ public class AltosVoice {
                        }
                }
 
-               public synchronized void notice(AltosState new_state, boolean spoken) {
+               public synchronized void notice(AltosState new_state, AltosGreatCircle new_from_receiver, boolean spoken) {
                        AltosState old_state = state;
                        state = new_state;
+                       from_receiver = new_from_receiver;
                        if (!started && state.state > AltosLib.ao_flight_pad) {
                                started = true;
                                start();
index bec518516f9113ac7b2c5749ba39fd872b97c947..971c3e80ddab085af28e729d9e34e3197c25641c 100644 (file)
@@ -31,6 +31,7 @@ import org.altusmetrum.altoslib_5.*;
 public class TelemetryReader extends Thread {
 
        private static final String TAG = "TelemetryReader";
+       private static final boolean D = true;
 
        int         crc_errors;
 
@@ -44,12 +45,6 @@ public class TelemetryReader extends Thread {
        LinkedBlockingQueue<AltosLine> telemQueue;
 
        public AltosState read() throws ParseException, AltosCRCException, InterruptedException, IOException {
-               if (stacked != null) {
-                       state = stacked.read();
-                       if (state != null)
-                               return state;
-                       stacked = null;
-               }
                AltosLine l = telemQueue.take();
                if (l.line == null)
                        throw new IOException("IO error");
@@ -78,6 +73,26 @@ public class TelemetryReader extends Thread {
                AltosState  state = null;
 
                try {
+                       if (D) Log.d(TAG, "starting reader");
+                       while (stacked != null) {
+                               AltosState      stacked_state = null;
+                               try {
+                                       stacked_state = stacked.read();
+                               } catch (ParseException pe) {
+                                       continue;
+                               } catch (AltosCRCException ce) {
+                                       continue;
+                               }
+                               if (stacked_state != null)
+                                       state = stacked_state;
+                               else
+                                       stacked = null;
+                       }
+                       if (state != null) {
+                               if (D) Log.d(TAG, "Send initial state");
+                               handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget();
+                       }
+                       if (D) Log.d(TAG, "starting loop");
                        while (telemQueue != null) {
                                try {
                                        state = read();
@@ -97,6 +112,7 @@ public class TelemetryReader extends Thread {
        }
 
        public TelemetryReader (AltosLink in_link, Handler in_handler, AltosFlightReader in_stacked) {
+               if (D) Log.d(TAG, "connected TelemetryReader create started");
                link    = in_link;
                handler = in_handler;
                stacked = in_stacked;
@@ -104,15 +120,9 @@ public class TelemetryReader extends Thread {
                state = null;
                telemQueue = new LinkedBlockingQueue<AltosLine>();
                link.add_monitor(telemQueue);
-               try {
-                       link.set_radio_frequency(AltosPreferences.frequency(link.serial));
-                       link.set_telemetry(AltosLib.ao_telemetry_standard);
-                       link.set_telemetry_rate(AltosPreferences.telemetry_rate(link.serial));
-               } catch (InterruptedException ee) {
-                       close();
-               } catch (TimeoutException te) {
-                       close();
-               }
+               link.set_telemetry(AltosLib.ao_telemetry_standard);
+
+               if (D) Log.d(TAG, "connected TelemetryReader created");
        }
 
        private static AltosFlightReader existing_data(AltosLink link) {
index 8e5c7903149c6648ef016ad953d4f878c3ab378b..52fc976a416e8ce3765a0b76c6c1e2ccf9a9c63e 100644 (file)
@@ -28,6 +28,7 @@ import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Service;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothAdapter;
 import android.content.Intent;
 import android.content.Context;
 import android.os.Bundle;
@@ -63,11 +64,6 @@ public class TelemetryService extends Service implements LocationListener {
        static final int MSG_CRC_ERROR         = 9;
        static final int MSG_SETBAUD           = 10;
 
-       public static final int STATE_NONE       = 0;
-       public static final int STATE_READY      = 1;
-       public static final int STATE_CONNECTING = 2;
-       public static final int STATE_CONNECTED  = 3;
-
        // Unique Identification Number for the Notification.
        // We use it on Notification start, and to cancel it.
        private int NOTIFICATION = R.string.telemetry_service_label;
@@ -81,21 +77,17 @@ public class TelemetryService extends Service implements LocationListener {
        final Messenger mMessenger = new Messenger(mHandler); // Target we publish for clients to send messages to IncomingHandler.
 
        // Name of the connected device
-       private BluetoothDevice device           = null;
+       String address;
        private AltosBluetooth  mAltosBluetooth  = null;
-       private AltosConfigData mConfigData      = null;
        private TelemetryReader mTelemetryReader = null;
        private TelemetryLogger mTelemetryLogger = null;
+       // Local Bluetooth adapter
+       private BluetoothAdapter mBluetoothAdapter = null;
 
-       // internally track state of bluetooth connection
-       private int state = STATE_NONE;
+       private TelemetryState  telemetry_state;
 
        // Last data seen; send to UI when it starts
 
-       private AltosState last_state;
-       private Location last_location;
-       private int last_crc_errors;
-
        // Handler of incoming messages from clients.
        static class IncomingHandler extends Handler {
                private final WeakReference<TelemetryService> service;
@@ -109,16 +101,8 @@ public class TelemetryService extends Service implements LocationListener {
                                s.mClients.add(msg.replyTo);
                                try {
                                        // Now we try to send the freshly connected UI any relavant information about what
-                                       // we're talking to - Basically state and Config Data.
-                                       msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_STATE_CHANGE, s.state, -1, s.mConfigData));
-                                       // We also send any recent telemetry or location data that's cached
-                                       if (s.last_state      != null) msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_TELEMETRY, s.last_state     ));
-                                       if (s.last_location   != null) msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_LOCATION , s.last_location  ));
-                                       if (s.last_crc_errors != 0   ) msg.replyTo.send(Message.obtain(null, AltosDroid.MSG_CRC_ERROR, s.last_crc_errors));
-                                       if (s.state == STATE_CONNECTED) {
-                                               msg.replyTo.send(s.frequency_message());
-                                               msg.replyTo.send(s.telemetry_rate_message());
-                                       }
+                                       // we're talking to
+                                       msg.replyTo.send(s.message());
                                } catch (RemoteException e) {
                                        s.mClients.remove(msg.replyTo);
                                }
@@ -130,8 +114,9 @@ public class TelemetryService extends Service implements LocationListener {
                                break;
                        case MSG_CONNECT:
                                if (D) Log.d(TAG, "Connect command received");
-                               s.device = (BluetoothDevice) msg.obj;
-                               s.startAltosBluetooth();
+                               String address = (String) msg.obj;
+                               AltosDroidPreferences.set_active_device(address);
+                               s.startAltosBluetooth(address);
                                break;
                        case MSG_CONNECTED:
                                if (D) Log.d(TAG, "Connected to device");
@@ -142,43 +127,46 @@ public class TelemetryService extends Service implements LocationListener {
                                break;
                        case MSG_CONNECT_FAILED:
                                if (D) Log.d(TAG, "Connection failed... retrying");
-                               s.startAltosBluetooth();
+                               if (s.address != null)
+                                       s.startAltosBluetooth(s.address);
                                break;
                        case MSG_DISCONNECTED:
                                Log.d(TAG, "MSG_DISCONNECTED");
-                               // Only do the following if we haven't been shutdown elsewhere..
-                               if (s.device != null) {
-                                       if (D) Log.d(TAG, "Disconnected from " + s.device.getName());
-                                       s.stopAltosBluetooth();
-                               }
+                               s.stopAltosBluetooth();
                                break;
                        case MSG_TELEMETRY:
                                // forward telemetry messages
-                               s.last_state = (AltosState) msg.obj;
-                               s.sendMessageToClients(Message.obtain(null, AltosDroid.MSG_TELEMETRY, msg.obj));
+                               s.telemetry_state.state = (AltosState) msg.obj;
+                               if (D) Log.d(TAG, "MSG_TELEMETRY");
+                               s.sendMessageToClients();
                                break;
                        case MSG_CRC_ERROR:
                                // forward crc error messages
-                               s.last_crc_errors = (Integer) msg.obj;
-                               s.sendMessageToClients(Message.obtain(null, AltosDroid.MSG_CRC_ERROR, msg.obj));
+                               s.telemetry_state.crc_errors = (Integer) msg.obj;
+                               if (D) Log.d(TAG, "MSG_CRC_ERROR");
+                               s.sendMessageToClients();
                                break;
                        case MSG_SETFREQUENCY:
-                               if (s.state == STATE_CONNECTED) {
+                               if (D) Log.d(TAG, "MSG_SETFREQUENCY");
+                               s.telemetry_state.frequency = (Double) msg.obj;
+                               if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {
                                        try {
-                                               s.mAltosBluetooth.set_radio_frequency((Double) msg.obj);
+                                               s.mAltosBluetooth.set_radio_frequency(s.telemetry_state.frequency);
                                                s.mAltosBluetooth.save_frequency();
-                                               s.sendMessageToClients(s.frequency_message());
                                        } catch (InterruptedException e) {
                                        } catch (TimeoutException e) {
                                        }
                                }
+                               s.sendMessageToClients();
                                break;
                        case MSG_SETBAUD:
-                               if (s.state == STATE_CONNECTED) {
-                                       s.mAltosBluetooth.set_telemetry_rate((Integer) msg.obj);
+                               if (D) Log.d(TAG, "MSG_SETBAUD");
+                               s.telemetry_state.telemetry_rate = (Integer) msg.obj;
+                               if (s.telemetry_state.connect == TelemetryState.CONNECT_CONNECTED) {
+                                       s.mAltosBluetooth.set_telemetry_rate(s.telemetry_state.telemetry_rate);
                                        s.mAltosBluetooth.save_telemetry_rate();
-                                       s.sendMessageToClients(s.telemetry_rate_message());
                                }
+                               s.sendMessageToClients();
                                break;
                        default:
                                super.handleMessage(msg);
@@ -186,9 +174,18 @@ public class TelemetryService extends Service implements LocationListener {
                }
        }
 
-       private void sendMessageToClients(Message m) {
+       private Message message() {
+               if (telemetry_state == null)
+                       Log.d(TAG, "telemetry_state null!");
+               return Message.obtain(null, AltosDroid.MSG_STATE, telemetry_state);
+       }
+
+       private void sendMessageToClients() {
+               Message m = message();
+               if (D) Log.d(TAG, String.format("Send message to %d clients", mClients.size()));
                for (int i=mClients.size()-1; i>=0; i--) {
                        try {
+                               if (D) Log.d(TAG, String.format("Send message to client %d", i));
                                mClients.get(i).send(m);
                        } catch (RemoteException e) {
                                mClients.remove(i);
@@ -196,19 +193,9 @@ public class TelemetryService extends Service implements LocationListener {
                }
        }
 
-       private Message frequency_message() {
-               if (D) Log.d(TAG, String.format("frequency_message %f\n", mAltosBluetooth.frequency()));
-               return Message.obtain(null, AltosDroid.MSG_FREQUENCY, mAltosBluetooth.frequency());
-       }
-
-       private Message telemetry_rate_message() {
-               if (D) Log.d(TAG, String.format("telemetry_rate_message %d\n", mAltosBluetooth.telemetry_rate()));
-               return Message.obtain(null, AltosDroid.MSG_TELEMETRY_RATE, mAltosBluetooth.telemetry_rate());
-       }
-
        private void stopAltosBluetooth() {
                if (D) Log.d(TAG, "stopAltosBluetooth(): begin");
-               setState(STATE_READY);
+               telemetry_state.connect = TelemetryState.CONNECT_READY;
                if (mTelemetryReader != null) {
                        if (D) Log.d(TAG, "stopAltosBluetooth(): stopping TelemetryReader");
                        mTelemetryReader.interrupt();
@@ -228,18 +215,21 @@ public class TelemetryService extends Service implements LocationListener {
                        mAltosBluetooth.close();
                        mAltosBluetooth = null;
                }
-               device = null;
-               mConfigData = null;
+               telemetry_state.config = null;
+               if (D) Log.d(TAG, "stopAltosBluetooth(): send message to clients");
+               sendMessageToClients();
        }
 
-       private void startAltosBluetooth() {
-               if (device == null) {
-                       return;
-               }
+       private void startAltosBluetooth(String address) {
+               // Get the BLuetoothDevice object
+               BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
+
+               this.address = address;
                if (mAltosBluetooth == null) {
                        if (D) Log.d(TAG, String.format("startAltosBluetooth(): Connecting to %s (%s)", device.getName(), device.getAddress()));
                        mAltosBluetooth = new AltosBluetooth(device, mHandler);
-                       setState(STATE_CONNECTING);
+                       telemetry_state.connect = TelemetryState.CONNECT_CONNECTING;
+                       sendMessageToClients();
                } else {
                        // This is a bit of a hack - if it appears we're still connected, we treat this as a restart.
                        // So, to give a suitable delay to teardown/bringup, we just schedule a resend of a message
@@ -247,30 +237,19 @@ public class TelemetryService extends Service implements LocationListener {
                        // ... then we tear down the existing connection.
                        // We do it this way around so that we don't lose a reference to the device when this method
                        // is called on reception of MSG_CONNECT_FAILED in the handler above.
-                       mHandler.sendMessageDelayed(Message.obtain(null, MSG_CONNECT, device), 3000);
+                       mHandler.sendMessageDelayed(Message.obtain(null, MSG_CONNECT, address), 3000);
                        stopAltosBluetooth();
                }
        }
 
-       private synchronized void setState(int s) {
-               if (D) Log.d(TAG, "setState(): " + state + " -> " + s);
-               state = s;
-
-               // This shouldn't be required - mConfigData should be null for any non-connected
-               // state, but to be safe and to reduce message size
-               AltosConfigData acd = (state == STATE_CONNECTED) ? mConfigData : null;
-
-               sendMessageToClients(Message.obtain(null, AltosDroid.MSG_STATE_CHANGE, state, -1, acd));
-       }
-
        private void connected() throws InterruptedException {
+               if (D) Log.d(TAG, "connected top");
                try {
                        if (mAltosBluetooth == null)
                                throw new InterruptedException("no bluetooth");
-                       mConfigData = mAltosBluetooth.config_data();
-                       if (D) Log.d(TAG, "send frequency/rate messages\n");
-                       sendMessageToClients(frequency_message());
-                       sendMessageToClients(telemetry_rate_message());
+                       telemetry_state.config = mAltosBluetooth.config_data();
+                       mAltosBluetooth.set_radio_frequency(telemetry_state.frequency);
+                       mAltosBluetooth.set_telemetry_rate(telemetry_state.telemetry_rate);
                } catch (TimeoutException e) {
                        // If this timed out, then we really want to retry it, but
                        // probably safer to just retry the connection from scratch.
@@ -278,21 +257,25 @@ public class TelemetryService extends Service implements LocationListener {
                        return;
                }
 
-               setState(STATE_CONNECTED);
+               if (D) Log.d(TAG, "connected bluetooth configured");
+               telemetry_state.connect = TelemetryState.CONNECT_CONNECTED;
 
                mTelemetryReader = new TelemetryReader(mAltosBluetooth, mHandler);
                mTelemetryReader.start();
 
+               if (D) Log.d(TAG, "connected TelemetryReader started");
+
                mTelemetryLogger = new TelemetryLogger(this, mAltosBluetooth);
 
-               sendMessageToClients(frequency_message());
-               sendMessageToClients(telemetry_rate_message());
+               if (D) Log.d(TAG, "Notify UI of connection");
+
+               sendMessageToClients();
        }
 
        private void onTimerTick() {
                if (D) Log.d(TAG, "Timer wakeup");
                try {
-                       if (mClients.size() <= 0 && state != STATE_CONNECTED) {
+                       if (mClients.size() <= 0 && telemetry_state.connect != TelemetryState.CONNECT_CONNECTED) {
                                stopSelf();
                        }
                } catch (Throwable t) {
@@ -303,19 +286,35 @@ public class TelemetryService extends Service implements LocationListener {
 
        @Override
        public void onCreate() {
+               // Get local Bluetooth adapter
+               mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+
+               // If the adapter is null, then Bluetooth is not supported
+               if (mBluetoothAdapter == null) {
+                       Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show();
+               }
+
+               // Initialise preferences
+               AltosDroidPreferences.init(this);
+
+               telemetry_state = new TelemetryState();
+
                // Create a reference to the NotificationManager so that we can update our notifcation text later
                //mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
 
-               setState(STATE_READY);
+               telemetry_state.connect = TelemetryState.CONNECT_READY;
 
                // Start our timer - first event in 10 seconds, then every 10 seconds after that.
                timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 10000L, 10000L);
 
                // Listen for GPS and Network position updates
                LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
-               
+
                locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, this);
-//             locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, this);
+
+               String address = AltosDroidPreferences.active_device();
+               if (address != null)
+                       startAltosBluetooth(address);
        }
 
        @Override
@@ -371,8 +370,9 @@ public class TelemetryService extends Service implements LocationListener {
 
 
        public void onLocationChanged(Location location) {
-               last_location = location;
-               sendMessageToClients(Message.obtain(null, AltosDroid.MSG_LOCATION, location));
+               telemetry_state.location = location;
+               if (D) Log.d(TAG, "location changed");
+               sendMessageToClients();
        }
 
        public void onStatusChanged(String provider, int status, Bundle extras) {