diff --git a/res/layout/fragment_launcher.xml b/res/layout/fragment_launcher.xml
index 6a38ac8..bc3f30b 100644
--- a/res/layout/fragment_launcher.xml
+++ b/res/layout/fragment_launcher.xml
@@ -77,4 +77,16 @@
android:singleLine="true"
android:text="@string/launcher_swipe_right_text_content" />
+
+
diff --git a/res/values/strings_launcher_fragment.xml b/res/values/strings_launcher_fragment.xml
index 02e80f1..2003eed 100644
--- a/res/values/strings_launcher_fragment.xml
+++ b/res/values/strings_launcher_fragment.xml
@@ -7,6 +7,7 @@
[No Home Position]
[IP Address]
[HOME] Lat: %1$f, Lon: %2$f
+ Failsafe Engaged
Swipe left for settings.
Swipe right for debugging.
diff --git a/res/values/strings_preferences.xml b/res/values/strings_preferences.xml
index c231355..486cf97 100644
--- a/res/values/strings_preferences.xml
+++ b/res/values/strings_preferences.xml
@@ -17,6 +17,7 @@
Set the maximum time without connectivity until failsafe activates.
120
+ - 15 seconds
- 30 seconds
- 1 minute
- 2 minutes
@@ -24,11 +25,12 @@
- 10 minutes
- - 30
- - 60
- - 120
- - 300
- - 600
+ - 15000
+ - 30000
+ - 60000
+ - 120000
+ - 300000
+ - 600000
Vehicle Configuration
Vehicle Type
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index 99a5f9c..dc2a6d4 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -43,6 +43,7 @@
android:title="@string/pref_failsafe_addr_title" />
0 && waypoints[0].equals(failsafeDestination))
+ return;
+
+ // Start navigating to the failsafe destination.
+ mVehicleServer.startWaypoints(new UtmPose[]{failsafeDestination}, null);
+ mVehicleServer.setAutonomous(true);
+ Log.w(TAG, "Engaged failsafe behavior to: " + failsafeDestination);
+ }
+
+ /**
+ * Updates all member variables from shared preference settings.
+ */
+ void updatePreferences(final SharedPreferences sharedPreferences) {
+ synchronized (mUpdateLock) {
+
+ // Update the timeout used for the failsafe server (stored as a numeric string).
+ mTimeoutMs = Long.parseLong(
+ sharedPreferences.getString("pref_failsafe_timeout",
+ mContext.getString(R.string.pref_failsafe_timeout_default)));
+
+ // Update the hostname used for the failsafe server.
+ String failsafeHostname =
+ sharedPreferences.getString("pref_failsafe_addr", "localhost");
+ try {
+ mFailsafeServer = InetAddress.getByName(failsafeHostname);
+ } catch (UnknownHostException e) {
+ Log.w(TAG, "Unable to resolve failsafe server: " +
+ failsafeHostname);
+ }
+
+ // Enable/disable update task.
+ if (sharedPreferences.getBoolean("pref_failsafe_enable", false)) {
+ if (mUpdateFuture == null) {
+ // Reset last-known location as for failsafe.
+ mLastConnectedLocation = null;
+ mLastConnectedTime = System.currentTimeMillis();
+ mStatus = FailsafeStatus.CONNECTED;
+
+ // Start update task.
+ mUpdateFuture = mUpdateExecutor.scheduleAtFixedRate(
+ mUpdateRunnable, 0,
+ UPDATE_PERIOD_MS, TimeUnit.MILLISECONDS);
+ Log.i(TAG, "Failsafe service enabled.");
+ }
+ } else {
+ if (mUpdateFuture != null) {
+ // Stop update task.
+ try {
+ mUpdateFuture.cancel(true);
+ mUpdateFuture.get();
+ } catch (InterruptedException | CancellationException | ExecutionException e) {
+ // Do nothing.
+ }
+ mUpdateFuture = null;
+ Log.i(TAG, "Failsafe service disabled.");
+
+ // Broadcast that the failsafe behavior is no longer running.
+ sendFailsafeIntent(false);
+ }
+ }
+ }
+ }
+
+ /**
+ * A shared preference listener that changes settings on the failsafe server when
+ * settings are changed.
+ */
+ private SharedPreferences.OnSharedPreferenceChangeListener mPreferenceListener =
+ new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ // If any of the keys related to this service changed, update them.
+ switch (key) {
+ case "pref_failsafe_enable":
+ case "pref_failsafe_addr":
+ case "pref_failsafe_timeout":
+ updatePreferences(sharedPreferences);
+ break;
+ }
+ }
+ };
+}
diff --git a/src/com/platypus/android/server/LauncherFragment.java b/src/com/platypus/android/server/LauncherFragment.java
index 22199fd..629443e 100644
--- a/src/com/platypus/android/server/LauncherFragment.java
+++ b/src/com/platypus/android/server/LauncherFragment.java
@@ -2,8 +2,10 @@
import android.app.ActivityManager;
import android.app.Fragment;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.location.LocationListener;
@@ -41,11 +43,12 @@ public class LauncherFragment extends Fragment
final Handler mHandler = new Handler();
- protected TextView mHomeText;
- protected TextView mIpAddressText;
- protected Button mLaunchButton;
- protected Button mSetHomeButton;
- protected LocationManager mLocationManager;
+ TextView mHomeText;
+ TextView mFailsafeText;
+ TextView mIpAddressText;
+ Button mLaunchButton;
+ Button mSetHomeButton;
+ LocationManager mLocationManager;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -62,6 +65,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
// Get references to UI elements.
mHomeText = (TextView) view.findViewById(R.id.launcher_home_text);
+ mFailsafeText = (TextView) view.findViewById(R.id.launcher_failsafe_text);
mIpAddressText = (TextView) view.findViewById(R.id.ip_address_text);
mLaunchButton = (Button) view.findViewById(R.id.launcher_launch_button);
mSetHomeButton = (Button) view.findViewById(R.id.launcher_home_button);
@@ -72,9 +76,22 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,
// Add listener for home button click.
mSetHomeButton.setOnLongClickListener(new SetHomeListener());
+ // Add listener for failsafe behavior events.
+ getActivity().registerReceiver(
+ mFailsafeBroadcastReceiver,
+ new IntentFilter(Failsafe.ACTION_FAILSAFE_UPDATE));
+
return view;
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ // Remove listener for failsafe behavior events.
+ getActivity().unregisterReceiver(mFailsafeBroadcastReceiver);
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -113,6 +130,22 @@ protected void updateHomeLocation() {
}
}
+ /**
+ * Receives failsafe broadcast intents and updates the launcher UI accordingly.
+ */
+ final BroadcastReceiver mFailsafeBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ LauncherFragment.this.getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ boolean isRunning = intent.getBooleanExtra("isRunning", false);
+ mFailsafeText.setVisibility(isRunning ? View.VISIBLE : View.INVISIBLE);
+ }
+ });
+ }
+ };
+
/**
* Updates the launch button depending on whether the service is running or not.
*/
@@ -126,6 +159,7 @@ protected void updateLaunchStatus() {
mLaunchButton.setBackground(getResources().getDrawable(
R.drawable.fragment_launcher_launch_button_background_red));
mLaunchButton.setText(getResources().getString(R.string.launcher_launch_button_start));
+ mFailsafeText.setVisibility(View.INVISIBLE);
}
}
diff --git a/src/com/platypus/android/server/VehicleServerImpl.java b/src/com/platypus/android/server/VehicleServerImpl.java
index d8fc022..f9a354f 100644
--- a/src/com/platypus/android/server/VehicleServerImpl.java
+++ b/src/com/platypus/android/server/VehicleServerImpl.java
@@ -657,87 +657,78 @@ public void setPose(UtmPose pose) {
}
@Override
- public void startWaypoints(final UtmPose[] waypoints,
- final String controller) {
- Log.i(TAG, "Starting waypoints with " + controller + ": "
- + Arrays.toString(waypoints));
- if (controller.equalsIgnoreCase("PRIMITIVES")) {
- _waypoints = new UtmPose[waypoints.length];
- System.arraycopy(waypoints, 0, _waypoints, 0, _waypoints.length);
- VehicleController vc = AirboatController.valueOf(controller).controller;
- vc.update(VehicleServerImpl.this, (double) UPDATE_INTERVAL_MS / 1000.0);
- Log.i(TAG, "Waypoint Status: PRIMITIVES");
- } else {
- // Create a waypoint navigation task
- TimerTask newNavigationTask = new TimerTask() {
- final double dt = (double) UPDATE_INTERVAL_MS / 1000.0;
-
- // Retrieve the appropriate controller in initializer
- VehicleController vc = DEFAULT_CONTROLLER;
-
- {
- try {
- vc = (controller == null) ? vc : AirboatController.valueOf(controller).controller;
- //Log.i(TAG, "vc:"+ vc.toString() + "controller" + controller );
- } catch (IllegalArgumentException e) {
- Log.w(TAG, "Unknown controller specified (using " + vc
- + " instead): " + controller);
- }
- }
+ public void startWaypoints(final UtmPose[] waypoints, final String controller) {
- @Override
- public void run() {
- synchronized (_navigationLock) {
- //Log.i(TAG, "Synchronized");
-
- if (!_isAutonomous.get()) {
- // If we are not autonomous, do nothing
- Log.i(TAG, "Paused");
- sendWaypointUpdate(WaypointState.PAUSED);
- } else if (_waypoints.length == 0) {
- // If we are finished with waypoints, stop in place
- Log.i(TAG, "Done");
- sendWaypointUpdate(WaypointState.DONE);
- setVelocity(new Twist(DEFAULT_TWIST));
- this.cancel();
- _navigationTask = null;
-
- } else {
- // If we are still executing waypoints, use a
- // controller to figure out how to get to waypoint
- // TODO: measure dt directly instead of approximating
- Log.i(TAG, "controller :" + controller);
- vc.update(VehicleServerImpl.this, dt);
- sendWaypointUpdate(WaypointState.GOING);
- //Log.i(TAG, "Waypoint Status: POINT_AND_SHOOT");
- }
+ // Determine the appropriate controller to use from the name.
+ VehicleController parsedVehicleController = DEFAULT_CONTROLLER;
+ try {
+ parsedVehicleController = (controller == null) ?
+ DEFAULT_CONTROLLER : AirboatController.valueOf(controller).controller;
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Unknown controller specified (using " + parsedVehicleController
+ + " instead): " + controller);
+ }
+ Log.i(TAG, "Starting waypoints with " + parsedVehicleController + ": " +
+ Arrays.toString(waypoints));
+
+ // Create a waypoint navigation task
+ final VehicleController vc = parsedVehicleController;
+ TimerTask newNavigationTask = new TimerTask() {
+ long lastUpdateTime = System.currentTimeMillis();
+
+ @Override
+ public void run() {
+ synchronized (_navigationLock) {
+ if (!_isAutonomous.get()) {
+ // If we are not autonomous, do nothing
+ Log.i(TAG, "Paused");
+ sendWaypointUpdate(WaypointState.PAUSED);
+
+ } else if (_waypoints.length == 0) {
+ // If we are finished with waypoints, stop in place
+ Log.i(TAG, "Done");
+ sendWaypointUpdate(WaypointState.DONE);
+ setVelocity(new Twist(DEFAULT_TWIST));
+ this.cancel();
+ _navigationTask = null;
+
+ } else {
+ // Compute the time since the last update.
+ long currentTime = System.currentTimeMillis();
+ double dt = (currentTime - lastUpdateTime) / 1000.0;
+ lastUpdateTime = currentTime;
+
+ // If we are still executing waypoints, use a
+ // controller to figure out how to complete the waypoint.
+ vc.update(VehicleServerImpl.this, dt);
+ sendWaypointUpdate(WaypointState.GOING);
}
}
- };
+ }
+ };
- synchronized (_navigationLock) {
- // Change waypoints to new set of waypoints
- _waypoints = new UtmPose[waypoints.length];
- System.arraycopy(waypoints, 0, _waypoints, 0, _waypoints.length);
+ synchronized (_navigationLock) {
+ // Change waypoints to new set of waypoints
+ _waypoints = new UtmPose[waypoints.length];
+ System.arraycopy(waypoints, 0, _waypoints, 0, _waypoints.length);
- // Cancel any previous navigation tasks
- if (_navigationTask != null)
- _navigationTask.cancel();
+ // Cancel any previous navigation tasks
+ if (_navigationTask != null)
+ _navigationTask.cancel();
- // Schedule this task for execution
- _navigationTask = newNavigationTask;
- _navigationTimer.scheduleAtFixedRate(_navigationTask, 0, UPDATE_INTERVAL_MS);
- }
+ // Schedule this task for execution
+ _navigationTask = newNavigationTask;
+ _navigationTimer.scheduleAtFixedRate(_navigationTask, 0, UPDATE_INTERVAL_MS);
+ }
- // Report the new waypoint in the log file.
- try {
- mLogger.info(new JSONObject()
- .put("nav", new JSONObject()
- .put("controller", controller)
- .put("waypoints", new JSONArray(waypoints))));
- } catch (JSONException e) {
- Log.w(TAG, "Unable to serialize waypoints.");
- }
+ // Report the new waypoint in the log file.
+ try {
+ mLogger.info(new JSONObject()
+ .put("nav", new JSONObject()
+ .put("controller", controller)
+ .put("waypoints", new JSONArray(waypoints))));
+ } catch (JSONException e) {
+ Log.w(TAG, "Unable to serialize waypoints.");
}
}
diff --git a/src/com/platypus/android/server/VehicleService.java b/src/com/platypus/android/server/VehicleService.java
index 6be5821..80c8cb2 100644
--- a/src/com/platypus/android/server/VehicleService.java
+++ b/src/com/platypus/android/server/VehicleService.java
@@ -68,9 +68,12 @@ public class VehicleService extends Service {
// Reference to vehicle logfile.
private VehicleLogger mLogger;
- // Reference to vehicle controller;
+ // Reference to vehicle controller.
private Controller mController;
+ // Reference to vehicle failsafe behavior.
+ private Failsafe mFailsafe;
+
// Objects implementing actual functionality
private VehicleServerImpl _vehicleServerImpl;
private UdpVehicleService _udpService;
@@ -285,6 +288,9 @@ public int onStartCommand(final Intent intent, int flags, int startId) {
// Create the internal vehicle server implementation.
_vehicleServerImpl = new VehicleServerImpl(this, mLogger, mController);
+ // Initialize failsafe service.
+ mFailsafe = new Failsafe(this, _vehicleServerImpl);
+
// Start up UDP vehicle service in the background
new Thread(new Runnable() {
@Override
@@ -399,6 +405,12 @@ public void onDestroy() {
mController = null;
}
+ // Shutdown the failsafe service.
+ if (mFailsafe != null) {
+ mFailsafe.shutdown();
+ mFailsafe = null;
+ }
+
// Unregister shared preference listener to listen for updates.
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(mPreferenceListener);