diff options
author | Bdale Garbee <bdale@gag.com> | 2014-09-06 13:41:36 -0600 |
---|---|---|
committer | Bdale Garbee <bdale@gag.com> | 2014-09-06 13:41:36 -0600 |
commit | 8c212cd5bfa03f71a31d84bd0051314e77d88461 (patch) | |
tree | 5be036b3510b8b474ad829caea20fcbc75b56839 | |
parent | e9714e34091abe657aa1b30aeda9466331aa39c1 (diff) | |
parent | dd26ec2e706bdd29090759deeb90090a0e3b74f0 (diff) |
Merge branch 'master' of ssh://git.gag.com/scm/git/fw/altos
Conflicts:
ao-bringup/turnon_telemega
45 files changed, 1288 insertions, 376 deletions
diff --git a/altosdroid/Notebook b/altosdroid/Notebook index ebb3578d..5a4df032 100644 --- a/altosdroid/Notebook +++ b/altosdroid/Notebook @@ -8,6 +8,8 @@ Desired AltosDroid feature list *) Highlight current frequency in the frequency list. + Placed current frequency in title bar + *) Random frequency selection. Provide some mechanism to input arbitrary radio frequencies. Could be like AltosUI which allows you to edit the list of frequencies and assign names to them, @@ -23,10 +25,16 @@ Desired AltosDroid feature list *) Remember most-recently-used TBT and frequency, perhaps auto-connect at startup. + + Done *) Re-loading flight data from .telem file to get back to 'find my rocket' mode after shutting down the application. + Done + *) Imperial Units mode + Done + *) TeleBT battery voltage diff --git a/altosdroid/res/layout/device_list.xml b/altosdroid/res/layout/device_list.xml index 395695f8..93d65517 100644 --- a/altosdroid/res/layout/device_list.xml +++ b/altosdroid/res/layout/device_list.xml @@ -18,39 +18,39 @@ android:layout_width="match_parent" android:layout_height="match_parent" > - <TextView android:id="@+id/title_paired_devices" + <Button android:id="@+id/button_scan" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/title_paired_devices" + android:text="@string/button_scan" + /> + <TextView android:id="@+id/title_new_devices" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/title_other_devices" android:visibility="gone" android:background="#666" android:textColor="#fff" android:paddingLeft="5dp" /> - <ListView android:id="@+id/paired_devices" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:stackFromBottom="true" - android:layout_weight="1" - /> - <TextView android:id="@+id/title_new_devices" + <TextView android:id="@+id/title_paired_devices" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/title_other_devices" + android:text="@string/title_paired_devices" android:visibility="gone" android:background="#666" android:textColor="#fff" android:paddingLeft="5dp" /> - <ListView android:id="@+id/new_devices" + <ListView android:id="@+id/paired_devices" android:layout_width="match_parent" android:layout_height="wrap_content" android:stackFromBottom="true" - android:layout_weight="2" + android:layout_weight="1" /> - <Button android:id="@+id/button_scan" + <ListView android:id="@+id/new_devices" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/button_scan" + android:stackFromBottom="true" + android:layout_weight="2" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/altosdroid/res/menu/option_menu.xml b/altosdroid/res/menu/option_menu.xml index ee9d475f..3bd5a54e 100644 --- a/altosdroid/res/menu/option_menu.xml +++ b/altosdroid/res/menu/option_menu.xml @@ -17,10 +17,16 @@ <item android:id="@+id/connect_scan" android:icon="@android:drawable/ic_menu_search" android:title="@string/connect_device" /> + <item android:id="@+id/quit" + android:icon="@android:drawable/ic_menu_close_clear_cancel" + android:title="@string/quit" /> <item android:id="@+id/select_freq" android:icon="@android:drawable/ic_menu_preferences" android:title="@string/select_freq" /> <item android:id="@+id/select_rate" android:icon="@android:drawable/ic_menu_preferences" android:title="@string/select_rate" /> + <item android:id="@+id/change_units" + android:icon="@android:drawable/ic_menu_view" + android:title="@string/change_units" /> </menu> diff --git a/altosdroid/res/values/strings.xml b/altosdroid/res/values/strings.xml index ce335b76..0cc99349 100644 --- a/altosdroid/res/values/strings.xml +++ b/altosdroid/res/values/strings.xml @@ -22,13 +22,15 @@ <!-- 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 --> <string name="connect_device">Connect a device</string> + <string name="quit">Quit</string> <string name="select_freq">Select radio frequency</string> <string name="select_rate">Select data rate</string> + <string name="change_units">Change units</string> <!-- DeviceListActivity --> <string name="scanning">scanning for devices…</string> diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java index 484efaf8..51ef5e94 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosBluetooth.java @@ -52,6 +52,7 @@ public class AltosBluetooth extends AltosLink { // Constructor public AltosBluetooth(BluetoothDevice in_device, Handler in_handler) { +// set_debug(D); adapter = BluetoothAdapter.getDefaultAdapter(); device = in_device; handler = in_handler; @@ -136,9 +137,27 @@ public class AltosBluetooth extends AltosLink { } } + public double frequency() { + return frequency; + } + + public int telemetry_rate() { + return telemetry_rate; + } + + public void save_frequency() { + AltosPreferences.set_frequency(0, frequency); + } + + public void save_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(); } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java index c9c38d98..f6cceac9 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroid.java @@ -44,32 +44,34 @@ import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.Window; +import android.view.View; import android.widget.TabHost; import android.widget.TextView; +import android.widget.RelativeLayout; import android.widget.Toast; import android.app.AlertDialog; import android.location.Location; import org.altusmetrum.altoslib_5.*; -public class AltosDroid extends FragmentActivity { +public class AltosDroid extends FragmentActivity implements AltosUnitsListener { // Debugging static final String TAG = "AltosDroid"; 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_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; @@ -78,12 +80,16 @@ public class AltosDroid extends FragmentActivity { private TextView mRSSIView; private TextView mSerialView; private TextView mFlightView; + private RelativeLayout mStateLayout; private TextView mStateView; private TextView mAgeView; // field to display the version at the bottom of the screen private TextView mVersion; + private double frequency; + private int telemetry_rate; + // Tabs TabHost mTabHost; AltosViewPager mViewPager; @@ -92,23 +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)); - // Preferences - private AltosDroidPreferences prefs = null; - - // TeleBT Config data - private AltosConfigData mConfigData = null; - // Local Bluetooth adapter - private BluetoothAdapter mBluetoothAdapter = null; - // Text to Speech private AltosVoice mAltosVoice = null; @@ -120,39 +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.mConfigData = (AltosConfigData) msg.obj; - String str = String.format(" %s S/N: %d", ad.mConfigData.product, ad.mConfigData.serial); - ad.mTitle.setText(R.string.title_connected_to); - ad.mTitle.append(str); - Toast.makeText(ad.getApplicationContext(), "Connected to " + str, Toast.LENGTH_SHORT).show(); - 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); - 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)); - } + if(D) Log.d(TAG, "MSG_UPDATE_AGE"); + ad.update_age(); break; } } @@ -208,10 +187,65 @@ 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); + public void units_changed(boolean imperial_units) { + for (AltosDroidTab mTab : mTabs) + mTab.units_changed(imperial_units); + } + + 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 start_timer() { + if (timer == null) { + timer = new Timer(); + timer.scheduleAtFixedRate(new TimerTask(){ public void run() {onTimerTick();}}, 1000L, 1000L); + } + } + + void stop_timer() { + if (timer != null) { + timer.cancel(); + timer.purge(); + timer = null; + } + } + + boolean registered_units_listener; + + void update_state(TelemetryState telemetry_state) { + + if (!registered_units_listener) { + registered_units_listener = true; + AltosPreferences.register_units_listener(this); + } + + 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) { @@ -226,42 +260,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"); - if (state != null && saved_state != null) { - if (saved_state.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; - } - } - } + + int prev_state = AltosLib.ao_flight_invalid; 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 (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 (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; + } + } + } + + 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 (saved_state == null || !same_string(saved_state.callsign, state.callsign)) { Log.d(TAG, "update callsign"); mCallsignView.setText(state.callsign); @@ -276,7 +341,12 @@ public class AltosDroid extends FragmentActivity { } if (saved_state == null || state.state != saved_state.state) { Log.d(TAG, "update state"); - mStateView.setText(state.state_name()); + if (state.state == AltosLib.ao_flight_stateless) { + mStateLayout.setVisibility(View.GONE); + } else { + mStateView.setText(state.state_name()); + mStateLayout.setVisibility(View.VISIBLE); + } } if (saved_state == null || state.rssi != saved_state.rssi) { Log.d(TAG, "update rssi"); @@ -285,10 +355,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; } @@ -330,8 +400,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(); @@ -339,12 +407,9 @@ public class AltosDroid extends FragmentActivity { if (mBluetoothAdapter == null) { Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show(); finish(); - return; } - // Initialise preferences - prefs = new AltosDroidPreferences(this); - AltosPreferences.init(prefs); + fm = getSupportFragmentManager(); // Set up the window layout requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); @@ -387,7 +452,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); @@ -403,11 +467,10 @@ public class AltosDroid extends FragmentActivity { mRSSIView = (TextView) findViewById(R.id.rssi_value); mSerialView = (TextView) findViewById(R.id.serial_value); mFlightView = (TextView) findViewById(R.id.flight_value); + mStateLayout = (RelativeLayout) findViewById(R.id.state_container); 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); } @@ -416,15 +479,16 @@ 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(); + } @Override @@ -453,6 +517,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) { @@ -480,19 +545,21 @@ public class AltosDroid extends FragmentActivity { } } - private void connectDevice(Intent data) { - // Get the device MAC address - String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); - // Get the BLuetoothDevice object - BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); + private void connectDevice(String 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) { } } + private void connectDevice(Intent data) { + // Get the device MAC address + String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); + connectDevice(address); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); @@ -550,6 +617,11 @@ public class AltosDroid extends FragmentActivity { serverIntent = new Intent(this, DeviceListActivity.class); startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE); return true; + case R.id.quit: + Log.d(TAG, "R.id.quit"); + stopService(new Intent(AltosDroid.this, TelemetryService.class)); + finish(); + return true; case R.id.select_freq: // Set the TBT radio frequency @@ -597,6 +669,10 @@ public class AltosDroid extends FragmentActivity { AlertDialog alert_rate = builder_rate.create(); alert_rate.show(); return true; + case R.id.change_units: + boolean imperial = AltosPreferences.imperial_units(); + AltosPreferences.set_imperial_units(!imperial); + return true; } return false; } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java index 70d7def8..9cef1319 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferences.java @@ -1,5 +1,5 @@ /* - * Copyright © 2012 Mike Beattie <mike@ethernal.org> + * Copyright © 2014 Keith Packard <keithp@keithp.com> * * 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 @@ -14,88 +14,38 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ - package org.altusmetrum.AltosDroid; -import java.io.File; -import java.util.Map; import android.content.Context; -import android.content.SharedPreferences; -import android.os.Environment; - import org.altusmetrum.altoslib_5.*; -public class AltosDroidPreferences implements AltosPreferencesBackend { - public final static String NAME = "org.altusmetrum.AltosDroid"; - private Context context = null; - private SharedPreferences prefs = null; - private SharedPreferences.Editor editor = null; - - public AltosDroidPreferences(Context in_context) { - this(in_context, NAME); - } - - public AltosDroidPreferences(Context in_context, String in_prefs) { - context = in_context; - prefs = context.getSharedPreferences(in_prefs, 0); - editor = prefs.edit(); - } - - public String[] keys() { - Map<String, ?> all = prefs.getAll(); - return (String[])all.keySet().toArray(); - } - - public AltosPreferencesBackend node(String key) { - return new AltosDroidPreferences(context, key); - } - - public boolean nodeExists(String key) { - return prefs.contains(key); - } - - public boolean getBoolean(String key, boolean def) { - return prefs.getBoolean(key, def); - } - - public double getDouble(String key, double def) { - Float f = Float.valueOf(prefs.getFloat(key, (float)def)); - return f.doubleValue(); - } - - public int getInt(String key, int def) { - return prefs.getInt(key, def); - } - - public String getString(String key, String def) { - return prefs.getString(key, def); - } +public class AltosDroidPreferences extends AltosPreferences { - public void putBoolean(String key, boolean value) { - editor.putBoolean(key, value); - } + /* Active device preference name */ + final static String activeDevicePreference = "ACTIVE-DEVICE"; - public void putDouble(String key, double value) { - editor.putFloat(key, (float)value); - } + static String active_device_address; - public void putInt(String key, int value) { - editor.putInt(key, value); - } + public static void init(Context context) { + if (backend != null) + return; - public void putString(String key, String value) { - editor.putString(key, value); - } + AltosPreferences.init(new AltosDroidPreferencesBackend(context)); - public void remove(String key) { - editor.remove(key); + active_device_address = backend.getString(activeDevicePreference, null); } - public void flush() { - editor.apply(); + public static void set_active_device(String address) { + synchronized(backend) { + active_device_address = address; + backend.putString(activeDevicePreference, active_device_address); + flush_preferences(); + } } - public File homeDirectory() { - return Environment.getExternalStorageDirectory(); + public static String active_device() { + synchronized(backend) { + return active_device_address; + } } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java new file mode 100644 index 00000000..be41ae7c --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidPreferencesBackend.java @@ -0,0 +1,101 @@ +/* + * Copyright © 2012 Mike Beattie <mike@ethernal.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; version 2 of the License. + * + * 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, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosDroid; + +import java.io.File; +import java.util.Map; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; + +import org.altusmetrum.altoslib_5.*; + +public class AltosDroidPreferencesBackend implements AltosPreferencesBackend { + public final static String NAME = "org.altusmetrum.AltosDroid"; + private Context context = null; + private SharedPreferences prefs = null; + private SharedPreferences.Editor editor = null; + + public AltosDroidPreferencesBackend(Context in_context) { + this(in_context, NAME); + } + + public AltosDroidPreferencesBackend(Context in_context, String in_prefs) { + context = in_context; + prefs = context.getSharedPreferences(in_prefs, 0); + editor = prefs.edit(); + } + + public String[] keys() { + Map<String, ?> all = prefs.getAll(); + return (String[])all.keySet().toArray(); + } + + public AltosPreferencesBackend node(String key) { + return new AltosDroidPreferencesBackend(context, key); + } + + public boolean nodeExists(String key) { + return prefs.contains(key); + } + + public boolean getBoolean(String key, boolean def) { + return prefs.getBoolean(key, def); + } + + public double getDouble(String key, double def) { + Float f = Float.valueOf(prefs.getFloat(key, (float)def)); + return f.doubleValue(); + } + + public int getInt(String key, int def) { + return prefs.getInt(key, def); + } + + public String getString(String key, String def) { + return prefs.getString(key, def); + } + + public void putBoolean(String key, boolean value) { + editor.putBoolean(key, value); + } + + public void putDouble(String key, double value) { + editor.putFloat(key, (float)value); + } + + public void putInt(String key, int value) { + editor.putInt(key, value); + } + + public void putString(String key, String value) { + editor.putString(key, value); + } + + public void remove(String key) { + editor.remove(key); + } + + public void flush() { + editor.apply(); + } + + public File homeDirectory() { + return Environment.getExternalStorageDirectory(); + } +} diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java index b960eb1a..8e625da6 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosDroidTab.java @@ -27,8 +27,9 @@ import android.support.v4.app.FragmentTransaction; import android.support.v4.app.FragmentManager; import android.location.Location; import android.util.Log; +import android.widget.TextView; -public abstract class AltosDroidTab extends Fragment { +public abstract class AltosDroidTab extends Fragment implements AltosUnitsListener { AltosState last_state; AltosGreatCircle last_from_receiver; Location last_receiver; @@ -37,39 +38,46 @@ public abstract class AltosDroidTab extends Fragment { public abstract String tab_name(); + public void units_changed(boolean imperial_units) { + if (!isHidden() && last_state != null) + show(last_state, last_from_receiver, last_receiver); + } + + public void set_value(TextView text_view, + AltosUnits units, + int width, + double value) { + if (value == AltosLib.MISSING) + text_view.setText(""); + else + text_view.setText(units.show(width, value)); + } + public void set_visible(boolean visible) { FragmentTransaction ft = AltosDroid.fm.beginTransaction(); - if (visible) + if (visible) { + AltosState state = last_state; + AltosGreatCircle from_receiver = last_from_receiver; + Location receiver = last_receiver; + + show(state, from_receiver, receiver); ft.show(this); - else + } else ft.hide(this); ft.commit(); } public void update_ui(AltosState state, AltosGreatCircle from_receiver, Location receiver, boolean is_current) { + last_state = state; + last_from_receiver = from_receiver; + last_receiver = receiver; if (is_current) { - Log.d(AltosDroid.TAG, String.format("%s: visible, performing update", tab_name())); + if (AltosDroid.D) Log.d(AltosDroid.TAG, String.format("%s: visible, performing update", tab_name())); show(state, from_receiver, receiver); } else { - Log.d(AltosDroid.TAG, String.format("%s: not visible, skipping update", tab_name())); - last_state = state; - last_from_receiver = from_receiver; - last_receiver = receiver; + if (AltosDroid.D) Log.d(AltosDroid.TAG, String.format("%s: not visible, skipping update", tab_name())); return; } } - - public void onHiddenChanged(boolean hidden) { - if (last_state != null && isVisible()) { - AltosState state = last_state; - AltosGreatCircle from_receiver = last_from_receiver; - Location receiver = last_receiver; - - last_state = null; - last_from_receiver = null; - last_receiver = null; - show(state, from_receiver, receiver); - } - } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java index b05913b6..969992d3 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/AltosVoice.java @@ -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; @@ -68,12 +68,14 @@ public class AltosVoice { if ((old_state == null || old_state.state <= AltosLib.ao_flight_boost) && state.state > AltosLib.ao_flight_boost) { if (state.max_speed() != AltosLib.MISSING) - speak(String.format("max speed: %d meters per second.", (int) (state.max_speed() + 0.5))); + speak(String.format("Max speed: %s.", + AltosConvert.speed.say_units(state.max_speed()))); spoke = true; } else if ((old_state == null || old_state.state < AltosLib.ao_flight_drogue) && state.state >= AltosLib.ao_flight_drogue) { if (state.max_height() != AltosLib.MISSING) - speak(String.format("max height: %d meters.", (int) (state.max_height() + 0.5))); + speak(String.format("Max height: %s.", + AltosConvert.height.say_units(state.max_height()))); spoke = true; } } @@ -88,13 +90,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,29 +115,30 @@ 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) { - speak(String.format("Height %d, bearing %s %d, elevation %d, range %d.\n", - (int) (state.height() + 0.5), - state.from_pad.bearing_words( + AltosGreatCircle position; + + if (from_receiver != null) + position = from_receiver; + else + position = state.from_pad; + + if (position != null) { + speak(String.format("Height %s, bearing %s %d, elevation %d, range %s.\n", + AltosConvert.height.say_units(state.height()), + 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), + AltosConvert.distance.say_units(position.range))); } } else if (state.state > AltosLib.ao_flight_pad) { if (state.height() != AltosLib.MISSING) - speak(String.format("%d meters", (int) (state.height() + 0.5))); + speak(AltosConvert.height.say_units(state.height())); } else { reported_landing = 0; } @@ -153,9 +157,9 @@ public class AltosVoice { else speak("rocket may have crashed"); if (state.from_pad != null) - speak(String.format("Bearing %d degrees, range %d meters.", + speak(String.format("Bearing %d degrees, range %s.", (int) (state.from_pad.bearing + 0.5), - (int) (state.from_pad.distance + 0.5))); + AltosConvert.distance.say_units(state.from_pad.distance))); ++reported_landing; } } @@ -186,9 +190,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(); diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java b/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java index 8e8d9c03..267c90f8 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/GoNoGoLights.java @@ -20,6 +20,7 @@ package org.altusmetrum.AltosDroid; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.widget.ImageView; +import android.view.View; public class GoNoGoLights { private Boolean state; @@ -51,14 +52,27 @@ public class GoNoGoLights { missing = m; set = true; if (missing) { + hide(); red.setImageDrawable(dGray); green.setImageDrawable(dGray); } else if (state) { red.setImageDrawable(dGray); green.setImageDrawable(dGreen); + show(); } else { red.setImageDrawable(dRed); green.setImageDrawable(dGray); + show(); } } + + public void show() { + red.setVisibility(View.VISIBLE); + green.setVisibility(View.VISIBLE); + } + + public void hide() { + red.setVisibility(View.GONE); + green.setVisibility(View.GONE); + } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java index c146c277..fa4e3c8b 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabAscent.java @@ -91,12 +91,13 @@ public class TabAscent extends AltosDroidTab { public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { if (state != null) { - mHeightView.setText(AltosDroid.number("%6.0f m", state.height())); - mMaxHeightView.setText(AltosDroid.number("%6.0f m", state.max_height())); - mSpeedView.setText(AltosDroid.number("%6.0f m/s", state.speed())); - mMaxSpeedView.setText(AltosDroid.number("%6.0f m/s", state.max_speed())); - mAccelView.setText(AltosDroid.number("%6.0f m/s²", state.acceleration())); - mMaxAccelView.setText(AltosDroid.number("%6.0f m/s²", state.max_acceleration())); + set_value(mHeightView, AltosConvert.height, 6, state.height()); + set_value(mHeightView, AltosConvert.height, 6, state.height()); + set_value(mMaxHeightView, AltosConvert.height, 6, state.max_height()); + set_value(mSpeedView, AltosConvert.speed, 6, state.speed()); + set_value(mMaxSpeedView, AltosConvert.speed, 6, state.max_speed()); + set_value(mAccelView, AltosConvert.accel, 6, state.acceleration()); + set_value(mMaxAccelView, AltosConvert.accel, 6, state.max_acceleration()); if (state.gps != null) { mLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java index 6d781efd..28068666 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabDescent.java @@ -93,14 +93,14 @@ public class TabDescent extends AltosDroidTab { public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { if (state != null) { - mSpeedView.setText(AltosDroid.number("%6.0f m/s", state.speed())); - mHeightView.setText(AltosDroid.number("%6.0f m", state.height())); + set_value(mSpeedView, AltosConvert.speed, 6, state.speed()); + set_value(mHeightView, AltosConvert.height, 6, state.height()); if (from_receiver != null) { mElevationView.setText(AltosDroid.number("%3.0f°", from_receiver.elevation)); - mRangeView.setText(AltosDroid.number("%6.0f m", from_receiver.range)); + set_value(mRangeView, AltosConvert.distance, 6, from_receiver.range); mBearingView.setText(AltosDroid.number("%3.0f°", from_receiver.bearing)); mCompassView.setText(from_receiver.bearing_words(AltosGreatCircle.BEARING_LONG)); - mDistanceView.setText(AltosDroid.number("%6.0f m", from_receiver.distance)); + set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance); } else { mElevationView.setText("<unknown>"); mRangeView.setText("<unknown>"); diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java index 16427d8b..b257b936 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabLanded.java @@ -78,7 +78,7 @@ public class TabLanded extends AltosDroidTab { public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { if (from_receiver != null) { mBearingView.setText(String.format("%3.0f°", from_receiver.bearing)); - mDistanceView.setText(String.format("%6.0f m", from_receiver.distance)); + set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance); } if (state != null && state.gps != null) { mTargetLatitudeView.setText(AltosDroid.pos(state.gps.lat, "N", "S")); @@ -91,9 +91,9 @@ public class TabLanded extends AltosDroidTab { } if (state != null) { - mMaxHeightView.setText(String.format("%6.0f m", state.max_height())); - mMaxAccelView.setText(String.format("%6.0f m/s²", state.max_acceleration())); - mMaxSpeedView.setText(String.format("%6.0f m/s", state.max_speed())); + set_value(mMaxHeightView, AltosConvert.height, 6, state.max_height()); + set_value(mMaxAccelView, AltosConvert.accel, 6, state.max_acceleration()); + set_value(mMaxSpeedView, AltosConvert.speed, 6, state.max_speed()); } } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java index 811e5482..ab338ac2 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabMap.java @@ -158,7 +158,7 @@ public class TabMap extends AltosDroidTab { public void show(AltosState state, AltosGreatCircle from_receiver, Location receiver) { if (from_receiver != null) { mBearingView.setText(String.format("%3.0f°", from_receiver.bearing)); - mDistanceView.setText(String.format("%6.0f m", from_receiver.distance)); + set_value(mDistanceView, AltosConvert.distance, 6, from_receiver.distance); } if (state != null) { diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java b/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java index 03b78b75..32df71d7 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TabPad.java @@ -33,10 +33,13 @@ public class TabPad extends AltosDroidTab { AltosDroid mAltosDroid; private TextView mBatteryVoltageView; + private TextView mBatteryVoltageLabel; private GoNoGoLights mBatteryLights; private TextView mApogeeVoltageView; + private TextView mApogeeVoltageLabel; private GoNoGoLights mApogeeLights; private TextView mMainVoltageView; + private TextView mMainVoltageLabel; private GoNoGoLights mMainLights; private TextView mDataLoggingView; private GoNoGoLights mDataLoggingLights; @@ -59,16 +62,19 @@ public class TabPad extends AltosDroidTab { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.tab_pad, container, false); mBatteryVoltageView = (TextView) v.findViewById(R.id.battery_voltage_value); + mBatteryVoltageLabel = (TextView) v.findViewById(R.id.battery_voltage_label); mBatteryLights = new GoNoGoLights((ImageView) v.findViewById(R.id.battery_redled), (ImageView) v.findViewById(R.id.battery_greenled), getResources()); mApogeeVoltageView = (TextView) v.findViewById(R.id.apogee_voltage_value); + mApogeeVoltageLabel = (TextView) v.findViewById(R.id.apogee_voltage_label); mApogeeLights = new GoNoGoLights((ImageView) v.findViewById(R.id.apogee_redled), (ImageView) v.findViewById(R.id.apogee_greenled), getResources()); mMainVoltageView = (TextView) v.findViewById(R.id.main_voltage_value); + mMainVoltageLabel = (TextView) v.findViewById(R.id.main_voltage_label); mMainLights = new GoNoGoLights((ImageView) v.findViewById(R.id.main_redled), (ImageView) v.findViewById(R.id.main_greenled), getResources()); @@ -107,11 +113,23 @@ public class TabPad extends AltosDroidTab { if (state != null) { mBatteryVoltageView.setText(AltosDroid.number("%4.2f V", state.battery_voltage)); mBatteryLights.set(state.battery_voltage >= AltosLib.ao_battery_good, state.battery_voltage == AltosLib.MISSING); - - mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.apogee_voltage)); + if (state.apogee_voltage == AltosLib.MISSING) { + mApogeeVoltageView.setVisibility(View.GONE); + mApogeeVoltageLabel.setVisibility(View.GONE); + } else { + mApogeeVoltageView.setText(AltosDroid.number("%4.2f V", state.apogee_voltage)); + mApogeeVoltageView.setVisibility(View.VISIBLE); + mApogeeVoltageLabel.setVisibility(View.VISIBLE); + } mApogeeLights.set(state.apogee_voltage >= AltosLib.ao_igniter_good, state.apogee_voltage == AltosLib.MISSING); - - mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_voltage)); + if (state.main_voltage == AltosLib.MISSING) { + mMainVoltageView.setVisibility(View.GONE); + mMainVoltageLabel.setVisibility(View.GONE); + } else { + mMainVoltageView.setText(AltosDroid.number("%4.2f V", state.main_voltage)); + mMainVoltageView.setVisibility(View.VISIBLE); + mMainVoltageLabel.setVisibility(View.VISIBLE); + } mMainLights.set(state.main_voltage >= AltosLib.ao_igniter_good, state.main_voltage == AltosLib.MISSING); if (state.flight != 0) { @@ -141,12 +159,12 @@ public class TabPad extends AltosDroidTab { } if (receiver != null) { - double altitude = 0; + double altitude = AltosLib.MISSING; if (receiver.hasAltitude()) altitude = receiver.getAltitude(); mPadLatitudeView.setText(AltosDroid.pos(receiver.getLatitude(), "N", "S")); mPadLongitudeView.setText(AltosDroid.pos(receiver.getLongitude(), "W", "E")); - mPadAltitudeView.setText(AltosDroid.number("%4.0f m", altitude)); + set_value(mPadAltitudeView, AltosConvert.height, 6, altitude); } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java index 3ba5afa9..971c3e80 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryReader.java @@ -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; @@ -39,6 +40,8 @@ public class TelemetryReader extends Thread { AltosLink link; AltosState state = null; + AltosFlightReader stacked; + LinkedBlockingQueue<AltosLine> telemQueue; public AltosState read() throws ParseException, AltosCRCException, InterruptedException, IOException { @@ -56,6 +59,10 @@ public class TelemetryReader extends Thread { public void close() { state = null; + if (stacked != null) { + stacked.close(false); + stacked = null; + } link.remove_monitor(telemQueue); link = null; telemQueue.clear(); @@ -66,7 +73,27 @@ public class TelemetryReader extends Thread { AltosState state = null; try { - for (;;) { + 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(); handler.obtainMessage(TelemetryService.MSG_TELEMETRY, state).sendToTarget(); @@ -84,12 +111,34 @@ public class TelemetryReader extends Thread { } } - public TelemetryReader (AltosLink in_link, Handler in_handler) { + 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; state = null; telemQueue = new LinkedBlockingQueue<AltosLine>(); link.add_monitor(telemQueue); + link.set_telemetry(AltosLib.ao_telemetry_standard); + + if (D) Log.d(TAG, "connected TelemetryReader created"); + } + + private static AltosFlightReader existing_data(AltosLink link) { + if (link == null) + return null; + + File file = AltosPreferences.logfile(link.serial); + if (file != null) { + AltosStateIterable iterable = AltosStateIterable.iterable(file); + if (iterable != null) + return new AltosReplayReader(iterable.iterator(), file, false); + } + return null; + } + + public TelemetryReader(AltosLink link, Handler handler) { + this(link, handler, existing_data(link)); } } diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java index 4ec353e3..30d94409 100644 --- a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryService.java @@ -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; @@ -104,17 +96,15 @@ public class TelemetryService extends Service implements LocationListener { @Override public void handleMessage(Message msg) { TelemetryService s = service.get(); + if (s == null) + return; switch (msg.what) { case MSG_REGISTER_CLIENT: 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)); + // we're talking to + msg.replyTo.send(s.message()); } catch (RemoteException e) { s.mClients.remove(msg.replyTo); } @@ -126,47 +116,59 @@ 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"); - s.connected(); + try { + s.connected(); + } catch (InterruptedException ie) { + } 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: - // 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(); - } + Log.d(TAG, "MSG_DISCONNECTED"); + 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(); } 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(); break; default: super.handleMessage(msg); @@ -174,9 +176,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); @@ -186,7 +197,7 @@ public class TelemetryService extends Service implements LocationListener { 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(); @@ -206,18 +217,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 @@ -225,28 +239,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() { + 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(); - } catch (InterruptedException e) { + 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. @@ -254,19 +259,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); - } + 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) { @@ -277,19 +288,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 @@ -345,8 +372,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) { diff --git a/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java new file mode 100644 index 00000000..d3341739 --- /dev/null +++ b/altosdroid/src/org/altusmetrum/AltosDroid/TelemetryState.java @@ -0,0 +1,46 @@ +/* + * Copyright © 2012 Mike Beattie <mike@ethernal.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; version 2 of the License. + * + * 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, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +package org.altusmetrum.AltosDroid; + +import org.altusmetrum.altoslib_5.*; +import android.location.Location; + +public class TelemetryState { + public static final int CONNECT_NONE = 0; + public static final int CONNECT_READY = 1; + public static final int CONNECT_CONNECTING = 2; + public static final int CONNECT_CONNECTED = 3; + + int connect; + AltosConfigData config; + AltosState state; + Location location; + int crc_errors; + double frequency; + int telemetry_rate; + + public TelemetryState() { + connect = CONNECT_NONE; + config = null; + state = null; + location = null; + crc_errors = 0; + frequency = AltosPreferences.frequency(0); + telemetry_rate = AltosPreferences.telemetry_rate(0); + } +} diff --git a/altoslib/AltosEepromGPS.java b/altoslib/AltosEepromGPS.java index 482f0b5f..8c991a6e 100644 --- a/altoslib/AltosEepromGPS.java +++ b/altoslib/AltosEepromGPS.java @@ -91,8 +91,10 @@ public class AltosEepromGPS extends AltosEeprom { switch (cmd) { case AltosLib.AO_LOG_FLIGHT: - state.set_boost_tick(tick); - state.set_flight(flight()); + if (state.flight == AltosLib.MISSING) { + state.set_boost_tick(tick); + state.set_flight(flight()); + } /* no place to log start lat/lon yet */ break; case AltosLib.AO_LOG_GPS_TIME: diff --git a/altoslib/AltosFlightReader.java b/altoslib/AltosFlightReader.java index c0565e88..be103838 100644 --- a/altoslib/AltosFlightReader.java +++ b/altoslib/AltosFlightReader.java @@ -21,16 +21,16 @@ import java.text.*; import java.io.*; import java.util.concurrent.*; -public class AltosFlightReader { +public abstract class AltosFlightReader { public String name; public int serial; - public void init() { } + public void init() {} - public AltosState read() throws InterruptedException, ParseException, AltosCRCException, IOException { return null; } + public abstract AltosState read() throws InterruptedException, ParseException, AltosCRCException, IOException; - public void close(boolean interrupted) { } + public abstract void close(boolean interrupted); public void set_frequency(double frequency) throws InterruptedException, TimeoutException { } diff --git a/altoslib/AltosLog.java b/altoslib/AltosLog.java index 1cac6b52..28116e3d 100644 --- a/altoslib/AltosLog.java +++ b/altoslib/AltosLog.java @@ -72,6 +72,7 @@ public class AltosLog implements Runnable { } log_file.flush(); file = a; + AltosPreferences.set_logfile(link.serial, file); } return log_file != null; } diff --git a/altoslib/AltosPreferences.java b/altoslib/AltosPreferences.java index 72cfeb4b..dba57dcb 100644 --- a/altoslib/AltosPreferences.java +++ b/altoslib/AltosPreferences.java @@ -38,6 +38,9 @@ public class AltosPreferences { /* telemetry rate format preference name */ public final static String telemetryRatePreferenceFormat = "RATE-%d"; + /* log file format preference name */ + public final static String logfilePreferenceFormat = "LOGFILE-%d"; + /* voice preference name */ public final static String voicePreference = "VOICE"; @@ -83,6 +86,9 @@ public class AltosPreferences { /* Telemetry rate (map serial to telemetry format) */ public static Hashtable<Integer, Integer> telemetry_rates; + /* Log file (map serial to logfile name) */ + public static Hashtable<Integer, File> logfiles; + /* Voice preference */ public static boolean voice; @@ -151,6 +157,10 @@ public class AltosPreferences { public static int launcher_channel; public static void init(AltosPreferencesBackend in_backend) { + + if (backend != null) + return; + backend = in_backend; /* Initialize logdir from preferences */ @@ -172,6 +182,8 @@ public class AltosPreferences { telemetry_rates = new Hashtable<Integer,Integer>(); + logfiles = new Hashtable<Integer,File>(); + voice = backend.getBoolean(voicePreference, true); callsign = backend.getString(callsignPreference,"N0CALL"); @@ -300,6 +312,27 @@ public class AltosPreferences { } } + public static void set_logfile(int serial, File new_logfile) { + synchronized(backend) { + logfiles.put(serial, new_logfile); + backend.putString(String.format(logfilePreferenceFormat, serial), new_logfile.getPath()); + flush_preferences(); + } + } + + public static File logfile(int serial) { + synchronized(backend) { + if (logfiles.containsKey(serial)) + return logfiles.get(serial); + String logfile_string = backend.getString(String.format(logfilePreferenceFormat, serial), null); + if (logfile_string == null) + return null; + File logfile = new File(logfile_string); + logfiles.put(serial, logfile); + return logfile; + } + } + public static void set_scanning_telemetry(int new_scanning_telemetry) { synchronized (backend) { scanning_telemetry = new_scanning_telemetry; diff --git a/altoslib/AltosReplayReader.java b/altoslib/AltosReplayReader.java index 15093af1..2864e02a 100644 --- a/altoslib/AltosReplayReader.java +++ b/altoslib/AltosReplayReader.java @@ -27,6 +27,7 @@ import java.util.*; public class AltosReplayReader extends AltosFlightReader { Iterator<AltosState> iterator; File file; + boolean real_time; public AltosState read() { if (iterator.hasNext()) @@ -39,16 +40,22 @@ public class AltosReplayReader extends AltosFlightReader { public void update(AltosState state) throws InterruptedException { /* Make it run in realtime after the rocket leaves the pad */ - if (state.state > AltosLib.ao_flight_pad && state.time_change > 0) + if (real_time && state.state > AltosLib.ao_flight_pad && state.time_change > 0) Thread.sleep((int) (Math.min(state.time_change,10) * 1000)); state.set_received_time(System.currentTimeMillis()); } public File backing_file() { return file; } - public AltosReplayReader(Iterator<AltosState> in_iterator, File in_file) { + public AltosReplayReader(Iterator<AltosState> in_iterator, File in_file, + boolean in_real_time) { iterator = in_iterator; file = in_file; + real_time = in_real_time; name = file.getName(); } + + public AltosReplayReader(Iterator<AltosState> in_iterator, File in_file) { + this(in_iterator, in_file, false); + } } diff --git a/altoslib/AltosStateIterable.java b/altoslib/AltosStateIterable.java index f7cd424d..4154b71c 100644 --- a/altoslib/AltosStateIterable.java +++ b/altoslib/AltosStateIterable.java @@ -26,4 +26,18 @@ public abstract class AltosStateIterable implements Iterable<AltosState> { } public abstract void write(PrintStream out); + + public static AltosStateIterable iterable(File file) { + FileInputStream in; + try { + in = new FileInputStream(file); + } catch (Exception e) { + System.out.printf("Failed to open file '%s'\n", file); + return null; + } + if (file.getName().endsWith("telem")) + return new AltosTelemetryFile(in); + else + return new AltosEepromFile(in); + } } diff --git a/altoslib/AltosTelemetryMegaData.java b/altoslib/AltosTelemetryMegaData.java index 8b1869bb..d949c02f 100644 --- a/altoslib/AltosTelemetryMegaData.java +++ b/altoslib/AltosTelemetryMegaData.java @@ -36,7 +36,7 @@ public class AltosTelemetryMegaData extends AltosTelemetryStandard { public AltosTelemetryMegaData(int[] bytes) { super(bytes); - state = int8(5); + state = uint8(5); v_batt = int16(6); v_pyro = int16(8); @@ -44,7 +44,7 @@ public class AltosTelemetryMegaData extends AltosTelemetryStandard { sense = new int[6]; for (int i = 0; i < 6; i++) { - sense[i] = int8(10 + i) << 4; + sense[i] = uint8(10 + i) << 4; sense[i] |= sense[i] >> 8; } diff --git a/altoslib/AltosTelemetryReader.java b/altoslib/AltosTelemetryReader.java index 5ed50134..7539452d 100644 --- a/altoslib/AltosTelemetryReader.java +++ b/altoslib/AltosTelemetryReader.java @@ -28,10 +28,17 @@ public class AltosTelemetryReader extends AltosFlightReader { int telemetry; int telemetry_rate; AltosState state = null; + AltosFlightReader stacked; LinkedBlockingQueue<AltosLine> telem; public AltosState read() throws InterruptedException, ParseException, AltosCRCException, IOException { + if (stacked != null) { + state = stacked.read(); + if (state != null) + return state; + stacked = null; + } AltosLine l = telem.take(); if (l.line == null) throw new IOException("IO error"); @@ -53,6 +60,12 @@ public class AltosTelemetryReader extends AltosFlightReader { } public void close(boolean interrupted) { + + if (stacked != null) { + stacked.close(interrupted); + stacked = null; + } + link.remove_monitor(telem); log.close(); try { @@ -148,9 +161,10 @@ public class AltosTelemetryReader extends AltosFlightReader { return link.monitor_battery(); } - public AltosTelemetryReader (AltosLink in_link) + public AltosTelemetryReader (AltosLink in_link, AltosFlightReader in_stacked) throws IOException, InterruptedException, TimeoutException { link = in_link; + stacked = in_stacked; boolean success = false; try { log = new AltosLog(link); @@ -169,4 +183,22 @@ public class AltosTelemetryReader extends AltosFlightReader { close(true); } } + + private static AltosFlightReader existing_data(AltosLink link) { + if (link == null) + return null; + + File file = AltosPreferences.logfile(link.serial); + if (file != null) { + AltosStateIterable iterable = AltosStateIterable.iterable(file); + if (iterable != null) + return new AltosReplayReader(iterable.iterator(), file, false); + } + return null; + } + + public AltosTelemetryReader(AltosLink link) + throws IOException, InterruptedException, TimeoutException { + this(link, existing_data(link)); + } } diff --git a/altosui/AltosConfigPyroUI.java b/altosui/AltosConfigPyroUI.java index dd4fb505..64336c8e 100644 --- a/altosui/AltosConfigPyroUI.java +++ b/altosui/AltosConfigPyroUI.java @@ -285,9 +285,13 @@ public class AltosConfigPyroUI "0.050", "0.100", "0.250", "0.500", "1.0", "2.0" }; + boolean initializing; + public void set_pyro_firing_time(double new_pyro_firing_time) { + initializing = true; pyro_firing_time_value.setSelectedItem(Double.toString(new_pyro_firing_time)); pyro_firing_time_value.setEnabled(new_pyro_firing_time >= 0); + initializing = false; } public double get_pyro_firing_time() throws AltosConfigDataException { @@ -301,23 +305,28 @@ public class AltosConfigPyroUI } public void set_dirty() { - owner.set_dirty(); + if (!initializing) + owner.set_dirty(); } public void itemStateChanged(ItemEvent e) { - owner.set_dirty(); + if (!initializing) + owner.set_dirty(); } public void changedUpdate(DocumentEvent e) { - owner.set_dirty(); + if (!initializing) + owner.set_dirty(); } public void insertUpdate(DocumentEvent e) { - owner.set_dirty(); + if (!initializing) + owner.set_dirty(); } public void removeUpdate(DocumentEvent e) { - owner.set_dirty(); + if (!initializing) + owner.set_dirty(); } public void units_changed(boolean imperial_units) { diff --git a/altosui/AltosConfigTD.java b/altosui/AltosConfigTD.java index 4389e49b..9020ed9d 100644 --- a/altosui/AltosConfigTD.java +++ b/altosui/AltosConfigTD.java @@ -223,8 +223,10 @@ public class AltosConfigTD implements ActionListener { if (!config_version.get().equals("0.0")) break; been_there = true; - config.serial_line.printf("C\n "); - config.serial_line.flush_input(); + if (config != null && config.serial_line != null) { + config.serial_line.printf("C\n "); + config.serial_line.flush_input(); + } } } catch (InterruptedException ie) { } @@ -277,8 +279,10 @@ public class AltosConfigTD implements ActionListener { } void abort() { - serial_line.close(); - serial_line = null; + if (serial_line != null) { + serial_line.close(); + serial_line = null; + } JOptionPane.showMessageDialog(owner, String.format("Connection to \"%s\" failed", device.toShortString()), diff --git a/altosui/AltosPad.java b/altosui/AltosPad.java index 5c33fd16..eb0c5644 100644 --- a/altosui/AltosPad.java +++ b/altosui/AltosPad.java @@ -117,6 +117,17 @@ public class AltosPad extends AltosUIFlightTab { } } + boolean report_pad(AltosState state) { + if ((state.state == AltosLib.ao_flight_stateless || + state.state < AltosLib.ao_flight_pad) && + state.gps != null && + state.gps.lat != AltosLib.MISSING) + { + return false; + } + return true; + } + class PadLat extends AltosUIIndicator { double last_lat = AltosLib.MISSING - 1; @@ -126,12 +137,12 @@ public class AltosPad extends AltosUIFlightTab { String label = null; if (state != null) { - if (state.state < AltosLib.ao_flight_pad && state.gps != null && state.gps.lat != AltosLib.MISSING) { - lat = state.gps.lat; - label = "Latitude"; - } else { + if (report_pad(state)) { lat = state.pad_lat; label = "Pad Latitude"; + } else { + lat = state.gps.lat; + label = "Latitude"; } } if (lat != last_lat) { @@ -163,12 +174,12 @@ public class AltosPad extends AltosUIFlightTab { String label = null; if (state != null) { - if (state.state < AltosLib.ao_flight_pad && state.gps != null && state.gps.lon != AltosLib.MISSING) { - lon = state.gps.lon; - label = "Longitude"; - } else { + if (report_pad(state)) { lon = state.pad_lon; label = "Pad Longitude"; + } else { + lon = state.gps.lon; + label = "Longitude"; } } if (lon != last_lon) { @@ -200,12 +211,12 @@ public class AltosPad extends AltosUIFlightTab { String label = null; if (state != null) { - if (state.state < AltosLib.ao_flight_pad && state.gps != null && state.gps.alt != AltosLib.MISSING) { - alt = state.gps.alt; - label = "Altitude"; - } else { + if (report_pad(state)) { alt = state.pad_alt; label = "Pad Altitude"; + } else { + alt = state.gps.alt; + label = "Altitude"; } } if (alt != last_alt) { diff --git a/ao-bringup/test-easymega b/ao-bringup/test-easymega new file mode 100755 index 00000000..eabe1ee5 --- /dev/null +++ b/ao-bringup/test-easymega @@ -0,0 +1,68 @@ +#!/bin/sh + +VERSION=1.0 +PRODUCT=EasyMega +BASE=`echo $PRODUCT | tr 'A-Z' 'a-z'` + +echo "$PRODUCT-v$VERSION Test Program" +echo "Copyright 2014 by Keith Packard. Released under GPL v2" +echo +echo "Expectations:" +echo "\t$PRODUCT v$VERSION powered from USB" +echo + +ret=1 +ao-list | while read product serial dev; do + case "$product" in + "$PRODUCT-v$VERSION") + + echo "Testing $product $serial $dev" + + for igniter in drogue main 0 1 2 3; do + echo "Testing $igniter igniter." + echo -n "Press enter to continue..." + read foo < /dev/tty + ../ao-tools/ao-test-igniter/ao-test-igniter --tty="$dev" $igniter + + case $? in + 0) + ;; + *) + echo "failed" + exit 1 + ;; + esac + done + + echo "Testing baro sensor" + ../ao-tools/ao-test-baro/ao-test-baro --tty="$dev" + + case $? in + 0) + ;; + *) + echo "failed" + exit 1 + esac + + FLASHSIZE=8388608 + + echo "Testing flash" + ../ao-tools/ao-test-flash/ao-test-flash --tty="$dev" "$FLASHSIZE" + + case $? in + 0) + ;; + *) + echo "failed" + exit 1 + esac + + echo "$PRODUCT-v$VERSION" serial "$serial" is ready to ship + ret=0 + ;; + *) + echo "Skipping $product $serial $dev" + ;; + esac +done diff --git a/ao-bringup/turnon_easymega b/ao-bringup/turnon_easymega index 7d23f5f7..b313e162 100755 --- a/ao-bringup/turnon_easymega +++ b/ao-bringup/turnon_easymega @@ -52,6 +52,10 @@ esac echo 'E 0' > $dev -./cal-accel $dev +../ao-tools/ao-cal-accel/ao-cal-accel $dev echo 'E 1' > $dev + +./test-easymega + +exit $? diff --git a/ao-bringup/turnon_telemega b/ao-bringup/turnon_telemega index 46c254b6..7745a8e5 100755 --- a/ao-bringup/turnon_telemega +++ b/ao-bringup/turnon_telemega @@ -54,6 +54,6 @@ echo 'E 0' > $dev SERIAL=$SERIAL ./cal-freq $dev -./cal-accel $dev +../ao-tools/ao-cal-accel/ao-cal-accel $dev echo 'E 1' > $dev diff --git a/ao-bringup/turnon_telemetrum b/ao-bringup/turnon_telemetrum index c6e7d1cc..48ff1e27 100755 --- a/ao-bringup/turnon_telemetrum +++ b/ao-bringup/turnon_telemetrum @@ -20,7 +20,7 @@ echo "TeleMetrum v$VERSION Turn-On and Calibration Program" echo "Copyright 2014 by Bdale Garbee. Released under GPL v2" echo echo "Expectations:" -echo "\tTeleMetrum v$VERSIOn powered from USB" +echo "\tTeleMetrum v$VERSION powered from USB" echo "\t\twith ST-Link-V2 cabled to debug header" echo "\t\twith coax from UHF to frequency counter" echo @@ -53,6 +53,6 @@ echo 'E 0' > $dev SERIAL=$SERIAL ./cal-freq $dev -./cal-accel $dev +../ao-tools/ao-cal-accel/ao-cal-accel $dev echo 'E 1' > $dev diff --git a/ao-tools/Makefile.am b/ao-tools/Makefile.am index 4526f514..2d873e50 100644 --- a/ao-tools/Makefile.am +++ b/ao-tools/Makefile.am @@ -2,7 +2,7 @@ SUBDIRS=lib ao-rawload ao-dbg ao-bitbang ao-eeprom ao-list \ ao-load ao-telem ao-send-telem ao-sky-flash \ ao-dumpflash ao-edit-telem ao-dump-up ao-elftohex \ ao-flash ao-usbload ao-test-igniter ao-test-baro \ - ao-test-flash + ao-test-flash ao-cal-accel if LIBSTLINK SUBDIRS += ao-stmload endif diff --git a/ao-tools/ao-cal-accel/.gitignore b/ao-tools/ao-cal-accel/.gitignore new file mode 100644 index 00000000..73402b2b --- /dev/null +++ b/ao-tools/ao-cal-accel/.gitignore @@ -0,0 +1 @@ +ao-cal-accel diff --git a/ao-tools/ao-cal-accel/Makefile.am b/ao-tools/ao-cal-accel/Makefile.am new file mode 100644 index 00000000..d278097a --- /dev/null +++ b/ao-tools/ao-cal-accel/Makefile.am @@ -0,0 +1,11 @@ +bin_PROGRAMS=ao-cal-accel + +AM_CFLAGS=-I$(top_srcdir)/ao-tools/lib $(LIBUSB_CFLAGS) + +ao_cal_accel_DEPENDENCIES = $(top_builddir)/ao-tools/lib/libao-tools.a + +ao_cal_accel_LDADD=$(top_builddir)/ao-tools/lib/libao-tools.a $(LIBUSB_LIBS) + +ao_cal_accel_SOURCES=ao-cal-accel.c + +man_MANS = ao-cal-accel.1 diff --git a/ao-tools/ao-cal-accel/ao-cal-accel.1 b/ao-tools/ao-cal-accel/ao-cal-accel.1 new file mode 100644 index 00000000..eb75d7c7 --- /dev/null +++ b/ao-tools/ao-cal-accel/ao-cal-accel.1 @@ -0,0 +1,58 @@ +.\" +.\" Copyright © 2009 Keith Packard <keithp@keithp.com> +.\" +.\" 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 2 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, write to the Free Software Foundation, Inc., +.\" 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. +.\" +.\" +.TH AO-LOAD 1 "ao-cal-accel" "" +.SH NAME +ao-cal-accel \- Calibrate AltOS flight computer accelerometers +.SH SYNOPSIS +.B "ao-cal-accel" +[\-T \fItty-device\fP] +[\--tty \fItty-device\fP] +[\-D \fIaltos-device\fP] +[\--device \fIaltos-device\fP] +.SH DESCRIPTION +.I ao-cal-accel +drives the built-in accelerometer calibration and validates the results. +.SH OPTIONS +.TP +\-T tty-device | --tty tty-device +This selects which tty device the debugger uses to communicate with +the target device. The special name 'BITBANG' directs ao-dbg to use +the cp2103 connection, otherwise this should be a usb serial port +connected to a suitable cc1111 debug node. +.TP +\-D AltOS-device | --device AltOS-device +Search for a connected device. This requires an argument of one of the +following forms: +.IP +TeleMega:2 +.br +TeleMega +.br +2 +.IP +Leaving out the product name will cause the tool to select a suitable +product, leaving out the serial number will cause the tool to match +one of the available devices. +.SH USAGE +.I ao-cal-accel +opens the target device, executes the accelerometer calibration +command, verifies that it executed correctly, then shows the resulting +calibration values and saves them to configuration memory. +.SH AUTHOR +Keith Packard diff --git a/ao-tools/ao-cal-accel/ao-cal-accel.c b/ao-tools/ao-cal-accel/ao-cal-accel.c new file mode 100644 index 00000000..9a988648 --- /dev/null +++ b/ao-tools/ao-cal-accel/ao-cal-accel.c @@ -0,0 +1,284 @@ +/* + * Copyright © 2014 Keith Packard <keithp@keithp.com> + * + * 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; version 2 of the License. + * + * 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, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include <err.h> +#include <fcntl.h> +#include <gelf.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <sysexits.h> +#include <unistd.h> +#include <getopt.h> +#include <string.h> +#include <stdbool.h> +#include <termios.h> +#include "ao-elf.h" +#include "ccdbg.h" +#include "cc-usb.h" +#include "cc.h" +#include "ao-verbose.h" + +static const struct option options[] = { + { .name = "tty", .has_arg = 1, .val = 'T' }, + { .name = "device", .has_arg = 1, .val = 'D' }, + { .name = "raw", .has_arg = 0, .val = 'r' }, + { .name = "verbose", .has_arg = 1, .val = 'v' }, + { 0, 0, 0, 0}, +}; + +static void usage(char *program) +{ + fprintf(stderr, "usage: %s [--verbose=<verbose>] [--device=<device>] [-tty=<tty>]\n", program); + exit(1); +} + +void +done(struct cc_usb *cc, int code) +{ + cc_usb_close(cc); + exit (code); +} + +static int +ends_with(char *whole, char *suffix) +{ + int whole_len = strlen(whole); + int suffix_len = strlen(suffix); + + if (suffix_len > whole_len) + return 0; + return strcmp(whole + whole_len - suffix_len, suffix) == 0; +} + +static int +starts_with(char *whole, char *prefix) +{ + int whole_len = strlen(whole); + int prefix_len = strlen(prefix); + + if (prefix_len > whole_len) + return 0; + return strncmp(whole, prefix, prefix_len) == 0; +} + +static char ** +tok(char *line) { + char **strs = malloc (sizeof (char *)), *str; + int n = 0; + + while ((str = strtok(line, " \t"))) { + line = NULL; + strs = realloc(strs, (n + 2) * sizeof (char *)); + strs[n] = strdup(str); + n++; + } + strs[n] = '\0'; + return strs; +} + +static void +free_strs(char **strs) { + char *str; + int i; + + for (i = 0; (str = strs[i]) != NULL; i++) + free(str); + free(strs); +} + +struct flash { + struct flash *next; + char line[512]; + char **strs; +}; + +static struct flash * +flash(struct cc_usb *usb) +{ + struct flash *head = NULL, **tail = &head; + cc_usb_printf(usb, "c s\nv\n"); + for (;;) { + char line[512]; + struct flash *b; + + cc_usb_getline(usb, line, sizeof (line)); + b = malloc (sizeof (struct flash)); + strcpy(b->line, line); + b->strs = tok(line); + b->next = NULL; + *tail = b; + tail = &b->next; + if (strstr(line, "software-version")) + break; + } + return head; +} + +static void +free_flash(struct flash *b) { + struct flash *n; + + while (b) { + n = b->next; + free_strs(b->strs); + free(b); + b = n; + } +} + +char ** +find_flash(struct flash *b, char *word0) { + int i; + for (;b; b = b->next) { + if (strstr(b->line, word0)) + return b->strs; + } + return NULL; +} + +void +await_key(void) +{ + struct termios termios, termios_save; + char buf[512]; + + tcgetattr(0, &termios); + termios_save = termios; + cfmakeraw(&termios); + tcsetattr(0, TCSAFLUSH, &termios); + read(0, buf, sizeof (buf)); + tcsetattr(0, TCSAFLUSH, &termios_save); +} + +int +do_cal(struct cc_usb *usb) { + struct flash *b; + char **accel; + char line[1024]; + int l; + int running = 0; + int worked = 1; + + cc_usb_printf(usb, "E 1\nc a 0\n"); + + for (;;) { + int c = cc_usb_getchar_timeout(usb, 20*1000); + + if (c == '\n') + l = 0; + else if (l < sizeof (line) - 1) + line[l++] = c; + line[l] = '\0'; + putchar(c); fflush(stdout); + if (strstr(line, "press a key...")) { + await_key(); + cc_usb_printf(usb, " "); + l = 0; + running = 1; + } + else if (strstr(line, "Invalid")) + worked = 0; + if (running && strstr(line, ">")) { + printf("\n"); + break; + } + } + cc_usb_printf(usb, "E 0\n"); + + if (!worked) { + printf("Calibration failed\n"); + return 0; + } + + b = flash(usb); + + accel = find_flash(b, "Accel cal"); + if (!accel) { + printf("no response\n"); + return 0; + } + + printf ("Accel cal +1g: %s -1g: %s\n", + accel[3], accel[5]); + + printf ("Saving..."); fflush(stdout); + cc_usb_printf (usb, "c w\n"); + cc_usb_sync(usb); + b = flash(usb); + printf ("done\n"); + + return worked; +} + +int +main (int argc, char **argv) +{ + char *device = NULL; + char *filename; + Elf *e; + unsigned int s; + int i; + int c; + int tries; + struct cc_usb *cc = NULL; + char *tty = NULL; + int success; + int verbose = 0; + int ret = 0; + int expected_size; + + while ((c = getopt_long(argc, argv, "rT:D:c:s:v:", options, NULL)) != -1) { + switch (c) { + case 'T': + tty = optarg; + break; + case 'D': + device = optarg; + break; + case 'v': + verbose++; + break; + default: + usage(argv[0]); + break; + } + } + + ao_verbose = verbose; + + if (verbose > 1) + ccdbg_add_debug(CC_DEBUG_BITBANG); + + if (!tty) + tty = cc_usbdevs_find_by_arg(device, "AltosFlash"); + if (!tty) + tty = cc_usbdevs_find_by_arg(device, "TeleMega"); + if (!tty) + tty = getenv("ALTOS_TTY"); + if (!tty) + tty="/dev/ttyACM0"; + + cc = cc_usb_open(tty); + + if (!cc) + exit(1); + + if (!do_cal(cc)) + ret = 1; + done(cc, ret); +} diff --git a/configure.ac b/configure.ac index 38bab8e1..683da846 100644 --- a/configure.ac +++ b/configure.ac @@ -537,6 +537,7 @@ ao-tools/ao-flash/Makefile ao-tools/ao-test-igniter/Makefile ao-tools/ao-test-baro/Makefile ao-tools/ao-test-flash/Makefile +ao-tools/ao-cal-accel/Makefile ao-utils/Makefile src/Version ]) diff --git a/src/drivers/ao_pad.c b/src/drivers/ao_pad.c index 144cbd70..dc2c83fe 100644 --- a/src/drivers/ao_pad.c +++ b/src/drivers/ao_pad.c @@ -362,14 +362,26 @@ ao_pad_test(void) void ao_pad_manual(void) { + uint8_t ignite; + int repeat; ao_cmd_white(); if (!ao_match_word("DoIt")) return; ao_cmd_decimal(); if (ao_cmd_status != ao_cmd_success) return; - ao_pad_ignite = 1 << ao_cmd_lex_i; - ao_wakeup(&ao_pad_ignite); + ignite = 1 << ao_cmd_lex_i; + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) { + repeat = 1; + ao_cmd_status = ao_cmd_success; + } else + repeat = ao_cmd_lex_i; + while (repeat-- > 0) { + ao_pad_ignite = ignite; + ao_wakeup(&ao_pad_ignite); + ao_delay(AO_PAD_FIRE_TIME>>1); + } } static __xdata struct ao_task ao_pad_task; diff --git a/src/kernel/ao_log.c b/src/kernel/ao_log.c index dc3b6486..40a96ef7 100644 --- a/src/kernel/ao_log.c +++ b/src/kernel/ao_log.c @@ -192,12 +192,14 @@ ao_log_find_max_erase_flight(void) __reentrant ao_flight_number = 1; } -void +uint8_t ao_log_scan(void) __reentrant { uint8_t log_slot; uint8_t log_slots; -#if !FLIGHT_LOG_APPEND +#if FLIGHT_LOG_APPEND + uint8_t ret; +#else uint8_t log_want; #endif @@ -248,9 +250,13 @@ ao_log_scan(void) __reentrant empty = ao_log_current_pos; } } + ret = 1; } else { ao_log_find_max_erase_flight(); + ret = 0; } + ao_wakeup(&ao_flight_number); + return ret; #else if (ao_flight_number) @@ -278,8 +284,9 @@ ao_log_scan(void) __reentrant if (++log_slot >= log_slots) log_slot = 0; } while (log_slot != log_want); -#endif ao_wakeup(&ao_flight_number); + return 0; +#endif } void diff --git a/src/kernel/ao_log.h b/src/kernel/ao_log.h index c5fa7fab..c13a2580 100644 --- a/src/kernel/ao_log.h +++ b/src/kernel/ao_log.h @@ -72,7 +72,7 @@ ao_log(void); /* functions provided in ao_log.c */ /* Figure out the current flight number */ -void +uint8_t ao_log_scan(void) __reentrant; /* Return the position of the start of the given log slot */ diff --git a/src/kernel/ao_pyro.c b/src/kernel/ao_pyro.c index 0b286466..3044d565 100644 --- a/src/kernel/ao_pyro.c +++ b/src/kernel/ao_pyro.c @@ -252,7 +252,7 @@ ao_pyro_check(void) struct ao_pyro *pyro; uint8_t p, any_waiting; uint16_t fire = 0; - + any_waiting = 0; for (p = 0; p < AO_PYRO_NUM; p++) { pyro = &ao_config.pyro[p]; @@ -288,6 +288,16 @@ ao_pyro_check(void) * the delay to expire */ if (pyro->delay_done) { + + /* Check to make sure the required conditions + * remain valid. If not, inhibit the channel + * by setting the fired bit + */ + if (!ao_pyro_ready(pyro)) { + pyro->fired = 1; + continue; + } + if ((int16_t) (ao_time() - pyro->delay_done) < 0) continue; } @@ -465,7 +475,7 @@ ao_pyro_set(void) printf ("invalid pyro channel %d\n", p); return; } - pyro_tmp.flags = 0; + memset(&pyro_tmp, '\0', sizeof (pyro_tmp)); for (;;) { ao_cmd_white(); if (ao_cmd_lex_c == '\n') @@ -489,13 +499,26 @@ ao_pyro_set(void) } pyro_tmp.flags |= ao_pyro_values[v].flag; if (ao_pyro_values[v].offset != NO_VALUE) { + uint8_t negative = 0; + ao_cmd_white(); + if (ao_cmd_lex_c == '-') { + negative = 1; + ao_cmd_lex(); + } ao_cmd_decimal(); if (ao_cmd_status != ao_cmd_success) return; - if (ao_pyro_values[v].flag & AO_PYRO_8_BIT_VALUE) + if (ao_pyro_values[v].flag & AO_PYRO_8_BIT_VALUE) { + if (negative) { + ao_cmd_status = ao_cmd_syntax_error; + return; + } *((uint8_t *) ((char *) &pyro_tmp + ao_pyro_values[v].offset)) = ao_cmd_lex_i; - else + } else { + if (negative) + ao_cmd_lex_i = -ao_cmd_lex_i; *((int16_t *) ((char *) &pyro_tmp + ao_pyro_values[v].offset)) = ao_cmd_lex_i; + } } } _ao_config_edit_start(); diff --git a/src/kernel/ao_tracker.c b/src/kernel/ao_tracker.c index d9434048..9b007af8 100644 --- a/src/kernel/ao_tracker.c +++ b/src/kernel/ao_tracker.c @@ -72,7 +72,7 @@ ao_tracker(void) #if !HAS_USB_CONNECT ao_tracker_force_telem = 1; #endif - ao_log_scan(); + log_started = ao_log_scan(); ao_rdf_set(1); @@ -181,8 +181,7 @@ void ao_tracker_erase_end(void) { if (erasing_current) { - ao_log_scan(); - log_started = 0; + log_started = ao_log_scan(); ao_mutex_put(&tracker_mutex); } } |