diff --git a/app/src/main/java/com/platypus/android/server/Controller.java b/app/src/main/java/com/platypus/android/server/Controller.java index 0d4d9fa..03f7202 100644 --- a/app/src/main/java/com/platypus/android/server/Controller.java +++ b/app/src/main/java/com/platypus/android/server/Controller.java @@ -57,6 +57,7 @@ public void onReceive(Context context, Intent intent) { private ParcelFileDescriptor mUsbDescriptor = null; private FileInputStream mUsbInputStream = null; private FileOutputStream mUsbOutputStream = null; + /** * Listen for disconnection events for accessory and close connection if we were using it. */ diff --git a/app/src/main/java/com/platypus/android/server/LauncherFragment.java b/app/src/main/java/com/platypus/android/server/LauncherFragment.java index d37dd6a..a779aab 100644 --- a/app/src/main/java/com/platypus/android/server/LauncherFragment.java +++ b/app/src/main/java/com/platypus/android/server/LauncherFragment.java @@ -2,14 +2,17 @@ import android.app.ActivityManager; import android.app.Fragment; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.SharedPreferences; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.Handler; +import android.os.IBinder; import android.preference.PreferenceManager; import android.util.Log; import android.view.LayoutInflater; @@ -24,6 +27,9 @@ import com.platypus.android.server.gui.SwipeOnlySwitch; +import org.json.JSONException; +import org.json.JSONObject; + import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; @@ -42,12 +48,33 @@ public class LauncherFragment extends Fragment private static final String TAG = LauncherFragment.class.getSimpleName(); + // bind the VehicleService so we can send sensor type JSON commands + // https://developer.android.com/guide/components/bound-services.html + VehicleService mService; + boolean mBound = false; + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + Log.d(TAG, "LauncherFragment: onServiceConnected() called..."); + VehicleService.LocalBinder binder = (VehicleService.LocalBinder) service; + mService = binder.getService(); + mBound = true; + sendSensorsJSON(); + } + @Override + public void onServiceDisconnected(ComponentName arg0) { + Log.d(TAG, "LauncherFragment: onServiceDisconnected() called..."); + mBound= false; + } + }; + final Handler mHandler = new Handler(); protected TextView mHomeText; protected TextView mIpAddressText; protected Switch mLaunchSwitch; protected Button mSetHomeButton; + protected Button mSetSensorsButton; protected ImageView mVehicleImage; protected LocationManager mLocationManager; /** @@ -60,10 +87,14 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean b) { mLaunchSwitch.setEnabled(false); if (!isVehicleServiceRunning()) { // If the service is not running, start it. - getActivity().startService(new Intent(getActivity(), VehicleService.class)); + Log.d(TAG, "LauncherFragment: slider listener called..."); + Intent intent = new Intent(getActivity(), VehicleService.class); + getActivity().startService(intent); + getActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE); Log.i(TAG, "Vehicle service started."); } else { // If the service is running, stop it. + getActivity().unbindService(mConnection); getActivity().stopService(new Intent(getActivity(), VehicleService.class)); Log.i(TAG, "Vehicle service stopped."); } @@ -97,6 +128,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mIpAddressText = (TextView) view.findViewById(R.id.ip_address_text); mLaunchSwitch = (SwipeOnlySwitch) view.findViewById(R.id.launcher_launch_switch); mSetHomeButton = (Button) view.findViewById(R.id.launcher_home_button); + mSetSensorsButton = (Button) view.findViewById(R.id.launcher_sensors_button); mVehicleImage = (ImageView) view.findViewById(R.id.launcher_vehicle_image); // Add listener for starting/stopping vehicle service. @@ -105,6 +137,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, // Add listener for home button click. mSetHomeButton.setOnLongClickListener(new SetHomeListener()); + // Add listener for sensors update button click + mSetSensorsButton.setOnLongClickListener(new UpdateSensorsListener()); + return view; } @@ -244,6 +279,91 @@ public void updateServerAddress() { String port = sharedPreferences.getString("pref_server_port", "11411"); mIpAddressText.setText(getLocalIpAddress() + ":" + port); } + + /** + * Listens for long-click events on "Update Sensors" button and updates sensor types + */ + class UpdateSensorsListener implements View.OnLongClickListener { + @Override + public boolean onLongClick(View v) { + sendSensorsJSON(); + return true; + } + } + void sendSensorsJSON() + { + Log.d(TAG, "LauncherFragment: sendSensorsJSON() called..."); + + if (mBound) + { + JSONObject sensors_JSON = generateSensorsJSON(); + Log.d(TAG, " sensor JSON: "); + Log.d(TAG, sensors_JSON.toString()); + mService.send(sensors_JSON); + mService.getServer().reset_expected_sensors(); // reset sensor type checks + } + else + { + Log.w(TAG, " mBound = false, sending nothing"); + } + } + JSONObject generateSensorsJSON() + { + JSONObject sensors_JSON = new JSONObject(); + for (int i = 1; i < 4; i++) + { + insertSensorJSON(i, sensors_JSON); + } + return sensors_JSON; + } + void insertSensorJSON(int sensorID, JSONObject sensors_JSON) + { + SharedPreferences sharedPreferences = + PreferenceManager.getDefaultSharedPreferences(getActivity()); + String sensor_array_name = "pref_sensor_" + Integer.toString(sensorID) + "_type"; + try + { + switch (sharedPreferences.getString(sensor_array_name, "NONE")) + { + case "NONE": + sensors_JSON.put(String.format("i%d", sensorID), "None"); + break; + case "ATLAS_DO": + sensors_JSON.put(String.format("i%d", sensorID), "AtlasDO"); + break; + case "ATLAS_PH": + sensors_JSON.put(String.format("i%d", sensorID), "AtlasPH"); + break; + case "ES2": + sensors_JSON.put(String.format("i%d", sensorID), "ES2"); + break; + case "HDS": + sensors_JSON.put(String.format("i%d", sensorID), "HDS"); + break; + case "ADAFRUIT_GPS": + sensors_JSON.put(String.format("i%d", sensorID), "AdaGPS"); + break; + case "AHRS_PLUS": + sensors_JSON.put(String.format("i%d", sensorID), "AHRSp"); + break; + case "BLUEBOX": + sensors_JSON.put(String.format("i%d", sensorID), "BlueBox"); + break; + case "WINCH": + sensors_JSON.put(String.format("i%d", sensorID), "Winch"); + break; + case "SAMPLER": + sensors_JSON.put(String.format("i%d", sensorID), "Sampler"); + break; + default: + break; + } + } + catch (JSONException e) + { + Log.w(TAG, "Failed to serialize sensor type.", e); + } + } /** * Listens for long-click events on "Set Home" button and updates home location. diff --git a/app/src/main/java/com/platypus/android/server/VehicleServerImpl.java b/app/src/main/java/com/platypus/android/server/VehicleServerImpl.java index a35c832..225d4dd 100644 --- a/app/src/main/java/com/platypus/android/server/VehicleServerImpl.java +++ b/app/src/main/java/com/platypus/android/server/VehicleServerImpl.java @@ -1,8 +1,12 @@ package com.platypus.android.server; +import android.app.NotificationManager; import android.content.Context; import android.content.SharedPreferences; +import android.media.RingtoneManager; +import android.net.Uri; import android.preference.PreferenceManager; +import android.support.v4.app.NotificationCompat; import android.util.Log; import com.platypus.crw.AbstractVehicleServer; @@ -105,6 +109,53 @@ public class VehicleServerImpl extends AbstractVehicleServer { // Last known temperature and EC values for sensor compensation private double _lastTemp = 20.0; // Deg C private double _lastEC = 0.0; // uS/cm + + //Define Notification Manager + NotificationManager notificationManager; + //Define sound URI + Uri soundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + + boolean[] received_expected_sensor_type = {false, false, false}; + public void reset_expected_sensors() + { + for (int i = 0; i < 3; i++) + { + received_expected_sensor_type[i] = false; + } + } + + private final Timer _sensorTypeTimer = new Timer(); + private TimerTask expect_sensor_type_task = new TimerTask() { + @Override + public void run() { + for (int i = 0; i < 3; i++) + { + try { + Thread.sleep(1000); // sleep for all sensor slots, even if empty + } catch(InterruptedException ex) { + Thread.currentThread().interrupt(); + } + if (!received_expected_sensor_type[i]) + { + String sensor_array_name = "pref_sensor_" + Integer.toString(i+1) + "_type"; + String expected_type = mPrefs.getString(sensor_array_name, "NONE"); + if (expected_type.equals("NONE")) + { + continue; + } + String message = "s" + (i+1) + " expects " + expected_type + " not received yet"; + Log.w(TAG, message); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(_context) + .setSmallIcon(R.drawable.camera_icon) //just some random icon placeholder + .setContentTitle("Sensor Warning") + .setContentText(message) + .setSound(soundUri); //This sets the sound to play + notificationManager.notify(0, mBuilder.build()); + } + } + } + }; + /** * Internal update function called at regular intervals to process command * and control events. @@ -224,6 +275,9 @@ protected VehicleServerImpl(Context context, VehicleLogger logger, Controller co // Connect to the Shared Preferences for this process. mPrefs = PreferenceManager.getDefaultSharedPreferences(_context); + notificationManager = (NotificationManager) _context.getSystemService(Context.NOTIFICATION_SERVICE); + _sensorTypeTimer.scheduleAtFixedRate(expect_sensor_type_task, 0, 100); + // Load PID values from SharedPreferences. // Use hard-coded defaults if not specified. r_PID[0] = mPrefs.getFloat("gain_rP", 1.0f); @@ -404,9 +458,40 @@ protected void onCommand(JSONObject cmd) { } else if (name.startsWith("s")) { int sensor = name.charAt(1) - 48; + // check sensor type expected in the preferences + String sensor_array_name = "pref_sensor_" + Integer.toString(sensor) + "_type"; + String expected_type = mPrefs.getString(sensor_array_name, "NONE"); + // Hacks to send sensor information if (value.has("type")) { String type = value.getString("type"); + + // check if received type matches expected type + if (!type.equalsIgnoreCase("battery")) { + if (type.equalsIgnoreCase(expected_type)) { + received_expected_sensor_type[sensor - 1] = true; + /* + String message = "s" + sensor + ": expected = " + expected_type + " received = " + type; + Log.w(TAG, message); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(_context) + .setSmallIcon(R.drawable.camera_icon) //just some random icon placeholder + .setContentTitle("Sensor Success") + .setContentText(message) + .setSound(soundUri); //This sets the sound to play + notificationManager.notify(0, mBuilder.build()); + */ + } else { + String message = "s" + sensor + ": expected = " + expected_type + " received = " + type; + Log.w(TAG, message); + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(_context) + .setSmallIcon(R.drawable.camera_icon) //just some random icon placeholder + .setContentTitle("Sensor Warning") + .setContentText(message) + .setSound(soundUri); //This sets the sound to play + notificationManager.notify(0, mBuilder.build()); + } + } + SensorData reading = new SensorData(); if (type.equalsIgnoreCase("es2")) { diff --git a/app/src/main/java/com/platypus/android/server/VehicleService.java b/app/src/main/java/com/platypus/android/server/VehicleService.java index 1009539..9e2a5b4 100644 --- a/app/src/main/java/com/platypus/android/server/VehicleService.java +++ b/app/src/main/java/com/platypus/android/server/VehicleService.java @@ -17,6 +17,7 @@ import android.location.LocationManager; import android.net.wifi.WifiManager; import android.net.wifi.WifiManager.WifiLock; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Debug; @@ -41,7 +42,9 @@ import org.jscience.geography.coordinates.LatLong; import org.jscience.geography.coordinates.UTM; import org.jscience.geography.coordinates.crs.ReferenceEllipsoid; +import org.json.JSONObject; +import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import javax.measure.unit.NonSI; @@ -75,6 +78,43 @@ public class VehicleService extends Service { // Reference to vehicle controller; private Controller mController; + public void send(JSONObject jsonObject) + { + Log.d(TAG, "VehicleService.send() called..."); + if (mController != null) + { + try + { + if (mController.isConnected()) + { + Log.d(TAG, " sending this JSON: "); + Log.d(TAG, jsonObject.toString()); + mController.send(jsonObject); + } + else + { + Log.w(TAG, " mController is NOT connected"); + } + } + catch (IOException e) + { + Log.w(TAG, "Failed to send command.", e); + } + } + else + { + Log.w(TAG, " mController is null"); + } + } + + + public class LocalBinder extends Binder { + VehicleService getService() { + Log.d(TAG, "VehicleService: binding in process..."); + return VehicleService.this; + } + } + private final IBinder mBinder = new LocalBinder(); // Objects implementing actual functionality private VehicleServerImpl _vehicleServerImpl; @@ -507,10 +547,9 @@ public void onFailure(@NonNull Exception e) { super.onDestroy(); } - @Nullable @Override public IBinder onBind(Intent intent) { - return null; + return mBinder; } public void sendNotification(CharSequence text) { diff --git a/app/src/main/res/layout/fragment_launcher.xml b/app/src/main/res/layout/fragment_launcher.xml index d018b06..a6d6177 100644 --- a/app/src/main/res/layout/fragment_launcher.xml +++ b/app/src/main/res/layout/fragment_launcher.xml @@ -93,4 +93,13 @@ android:singleLine="true" android:text="@string/launcher_swipe_right_text_content" /> + +