]> hydra-www.ietfng.org Git - acmetensortoys-teled/commitdiff
Some hack and slash
authorNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Mon, 8 Feb 2016 10:11:31 +0000 (05:11 -0500)
committerNathaniel Wesley Filardo <nwf@cs.jhu.edu>
Mon, 8 Feb 2016 11:20:53 +0000 (06:20 -0500)
New behavior API for IOIOService; I kind of like it better.
Push IOIO behavior factories out to their own class to reduce clutter.
Tidy a bunch of Android warnings.
Explicitly delcare desire to have Java 1.7
Skeletal remote-control tracking service.

mobile/build.gradle
mobile/src/main/AndroidManifest.xml
mobile/src/main/java/com/acmetensortoys/android/teled/IOIOBehaviors.java [new file with mode: 0644]
mobile/src/main/java/com/acmetensortoys/android/teled/IOIOService.java [new file with mode: 0644]
mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java
mobile/src/main/java/com/acmetensortoys/android/teled/RemoteControlService.java [new file with mode: 0644]
mobile/src/main/java/com/acmetensortoys/android/teled/TeleDIOIOService.java [deleted file]
wear/build.gradle

index 416738e62262bd345e0dae06fc7a3b31316533af..94efc614af0d48ee1b3975b91ce8fd85a00580e5 100644 (file)
@@ -17,6 +17,11 @@ android {
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
 }
 
 // IOIO support
index 201b22f451e10b2a7e8d3a63f0ad63420f804066..8dbc75987b1ee809d2955645d21188170b5d55cb 100644 (file)
@@ -4,7 +4,7 @@
 
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.SEND_SMS" />
-    <uses-permission android:name="android.permission.BROADCAST_SMS" />
+    <uses-permission android:name="android.permission.RECEIVE_SMS" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.INTERNET" />
 
             android:theme="@style/AppTheme.NoActionBar">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
-
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
 
         <service
-            android:name=".TeleDIOIOService"
-            android:enabled="true"></service>
+            android:name=".IOIOService"
+            android:enabled="true" />
+
+        <service
+            android:name=".RemoteControlService"
+            android:enabled="true" />
 
         <receiver
             android:name=".SMSRecv"
             android:enabled="true"
             android:exported="true"
-            android:permission="android.permission.BROADCAST_SMS">
+            android:permission="android.permission.RECEIVE_SMS">
             <intent-filter>
                 <action android:name="android.provider.Telephony.SMS_RECEIVED" />
             </intent-filter>
diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOBehaviors.java b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOBehaviors.java
new file mode 100644 (file)
index 0000000..2293ae3
--- /dev/null
@@ -0,0 +1,105 @@
+package com.acmetensortoys.android.teled;
+
+import java.util.Collection;
+
+import ioio.lib.api.AnalogInput;
+import ioio.lib.api.Closeable;
+import ioio.lib.api.DigitalOutput;
+import ioio.lib.api.IOIO;
+import ioio.lib.api.PwmOutput;
+
+public class IOIOBehaviors {
+    static abstract class Factory {
+        abstract class Behavior {
+            @SuppressWarnings("EmptyMethod")
+            void shutdown() { }
+            abstract void run() throws Exception;
+        }
+        /*
+        The provided set of Closeable things is initially empty but can be
+        added to to simplify resource management inside the create() method and
+        in the returned Behavior: if create() raises an exception, the contents
+        of cc will be iterated and closed.  When it comes time to shut down the
+        behavior, again, cc will be iterated and closed.  Hooray mutation, I
+        guess; the cause of, and solution to, all of mutations' problems.
+        */
+        abstract Behavior create(IOIO i, Collection<Closeable> cc) throws Exception;
+    }
+
+    static private Factory makeBlinkOnce(final int pinId, final int dur) {
+        return new Factory() {
+            public Behavior create(IOIO ioio_, Collection<Closeable> cc)
+                    throws Exception {
+                final DigitalOutput pin = ioio_.openDigitalOutput(pinId);
+                cc.add(pin);
+                return new Behavior() {
+                    public void run() throws Exception {
+                        pin.write(false);
+                        Thread.sleep(dur);
+                        pin.write(true);
+                    }
+                };
+            }
+        };
+    }
+    static public Factory makeBlinkLEDOnce(int dur) {
+        return makeBlinkOnce(IOIO.LED_PIN, dur);
+    }
+
+    static private Factory makeBlinkMany(final int pinId, final int dur) {
+        return new Factory() {
+            public Behavior create(IOIO ioio_, Collection<Closeable> cc)
+                    throws Exception {
+                final DigitalOutput pin = ioio_.openDigitalOutput(pinId);
+                cc.add(pin);
+                return new Behavior() {
+                    public void run() throws Exception {
+                        while(true) {
+                            pin.write(false);
+                            Thread.sleep(dur);
+                            pin.write(true);
+                            Thread.sleep(dur/2);
+                        }
+                    }
+                };
+            }
+        };
+    }
+    static public Factory makeBlinkLEDMany(int dur) {
+        return makeBlinkMany(IOIO.LED_PIN, dur);
+    }
+
+    public interface ForceFeedbackCallback {
+        void run(float v);
+    }
+    static public Factory makeForceFeedback(final ForceFeedbackCallback cb) {
+        return new Factory() {
+            public Behavior create(IOIO ioio_, Collection<Closeable> cc) throws Exception {
+                final AnalogInput sensor = ioio_.openAnalogInput(31);
+                cc.add(sensor);
+                sensor.setBuffer(50);
+                final DigitalOutput motorEnable = ioio_.openDigitalOutput(1, false); // active low
+                cc.add(motorEnable);
+                final PwmOutput motorPwm = ioio_.openPwmOutput(2, 100);
+                cc.add(motorPwm);
+                return new Behavior() {
+                    public void run() throws Exception {
+                        float setv = 0.0f;
+                        while (!Thread.interrupted()) {
+                            float v = sensor.getVoltageBuffered();
+                            // Try to save some bandwidth by only updating if it matters.
+                            if (Math.abs(v - setv) > 0.1) {
+                                motorPwm.setDutyCycle(v / 3.3f);
+                                setv = v;
+                            }
+                            // Hooray for JITs; I hope they can code-motion this test out.
+                            if (cb != null) {
+                                cb.run(v);
+                            }
+                        }
+                    }
+                };
+            }
+        };
+    }
+}
diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOService.java b/mobile/src/main/java/com/acmetensortoys/android/teled/IOIOService.java
new file mode 100644 (file)
index 0000000..0ef8c85
--- /dev/null
@@ -0,0 +1,143 @@
+// Based, in long past, on IOIOService example from Ytai's repository
+
+package com.acmetensortoys.android.teled;
+
+import android.app.Notification;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Binder;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import java.util.Vector;
+
+import ioio.lib.api.AnalogInput;
+import ioio.lib.api.Closeable;
+import ioio.lib.api.DigitalOutput;
+import ioio.lib.api.IOIO;
+import ioio.lib.api.PwmOutput;
+import ioio.lib.api.exception.ConnectionLostException;
+import ioio.lib.util.BaseIOIOLooper;
+import ioio.lib.util.IOIOLooper;
+
+public class IOIOService extends ioio.lib.util.android.IOIOService {
+    private static final int NOTIFY_ID = 1;
+    private IOIO ioio = null; // If we have one connected, here 'tis.
+    // TODO We should have some state that lets us identify which IOIO we mean
+    // which should be configurable through the UI.
+
+    public interface OnReady {
+        void onReady();
+        void onUnready();
+    }
+    private Vector<OnReady> subscribers = new Vector<>(5);
+
+    @Override
+    public IOIOLooper createIOIOLooper(final String type, Object info) {
+        if (type.equals("ioio.lib.android.bluetooth.BluetoothIOIOConnection")) {
+            Object[] info_ = (Object[])info;
+            Log.d("TDIS", "Looper creating for " + info_[0].toString() + ":" + info_[1].toString());
+        }
+
+        // TODO Check to see if it is the right IOIO.
+
+        return new BaseIOIOLooper() {
+            @Override
+            protected void setup() throws ConnectionLostException,
+                    InterruptedException {
+                synchronized(IOIOService.this) {
+                    if(ioio != null) {
+                        Log.e("TDIS", "Aaaaaaa! Setup with existing IOIO?", new Exception());
+                    }
+                    ioio = ioio_;
+                    for (OnReady l : subscribers) { l.onReady(); }
+                }
+            }
+
+            @Override
+            public void loop() throws ConnectionLostException,
+                    InterruptedException {
+                ioio.waitForDisconnect();
+            }
+
+            @Override
+            public void disconnected() {
+                synchronized(IOIOService.this) {
+                    for (OnReady l : subscribers) { l.onUnready(); }
+                    IOIOService.this.ioio = null;
+                }
+            }
+        };
+    }
+
+    final class LocalBinder extends Binder {
+        // XXX for development only
+        // IOIOService getService() { return IOIOService.this; }
+
+        // Subscribe or unsubscribe to the IOIO being connected;
+        // when we are ready, the behaviors offered below will work; when
+        // not, they won't.  On transition from ready to unready, all the
+        // threads will die of their own accord as their closed-over
+        // objects which reach into the IOIO issue exceptions.
+        public void addOnReady(OnReady l) {
+            subscribers.add(l);
+        }
+        public void removeOnReady(OnReady l) {
+            subscribers.remove(l);
+        }
+
+        @Nullable
+        public Thread makeBehaviorThread(IOIOBehaviors.Factory bf) {
+            IOIO ioio_;
+            // XXX? synchronized(IOIOService.this) {
+                ioio_ = ioio;
+            // }
+            if(ioio_ == null) {
+                return null;
+            }
+            final IOIOBehaviors.Factory.Behavior b;
+            final Vector<Closeable> cc = new Vector<>(5);
+            try {
+                b = bf.create(ioio_, cc);
+            } catch (Exception e) {
+                for(Closeable c : cc) { c.close(); }
+                return null;
+            }
+            return new Thread() {
+                public void run() {
+                    try {
+                        b.run();
+                    } catch (Exception e) {
+                        // ignored; the thread is allowed to just die;
+                        // anyone curious can use .isAlive() or .join().
+                        // They're also welcome to .interrupt()
+                    } finally {
+                        b.shutdown();
+                        for(Closeable c : cc) { c.close(); }
+                    }
+                }
+            };
+        }
+
+    }
+    private final IBinder mBinder = new LocalBinder();
+
+    @Override
+    public IBinder onBind(Intent arg0) {
+        Log.d("TDIS", "onBind");
+        Notification.Builder nb = new Notification.Builder(this);
+        Notification n = nb.setSmallIcon(R.drawable.ic_menu_send)
+                .setContentTitle("TeleD IOIO service running")
+                .setWhen(System.currentTimeMillis())
+                .setOngoing(true)
+                .build();
+        startForeground(NOTIFY_ID, n);
+        return mBinder;
+    }
+
+    @Override
+    public boolean onUnbind(Intent arg0) {
+        stopForeground(true);
+        stopSelf();
+        return false;
+    }
+}
\ No newline at end of file
index d96a71df4048858f9ffec0f87f7bda06e1fc75f7..2f9109e8dcb3715ca7761931536e7c0629afbe31 100644 (file)
@@ -6,6 +6,7 @@ import android.content.ServiceConnection;
 import android.content.ComponentName;
 import android.os.IBinder;
 import android.os.Bundle;
+import android.renderscript.RSInvalidStateException;
 import android.support.design.widget.FloatingActionButton;
 import android.support.design.widget.Snackbar;
 import android.view.View;
@@ -46,6 +47,10 @@ public class MainActivity extends AppCompatActivity
 
         NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
         navigationView.setNavigationItemSelectedListener(this);
+
+        Intent i = new Intent(this, IOIOService.class);
+        startService(i);                                    // start the IOIO service
+        bindService(i, tdisSC, Context.BIND_AUTO_CREATE);   // and get a handle to it, eventually
     }
 
     @Override
@@ -80,17 +85,43 @@ public class MainActivity extends AppCompatActivity
         return super.onOptionsItemSelected(item);
     }
 
-    private TeleDIOIOService tdis = null;
+    private Thread blinker;
+    private void makeBlinker() {
+        blinker = tdis.makeBehaviorThread(IOIOBehaviors.makeBlinkLEDMany(500));
+        if(blinker != null) {
+            blinker.start();
+        } else {
+            Log.d("Main", "onReady unable to start blinker");
+        }
+    }
+
+    private IOIOService.LocalBinder tdis = null;
+    private IOIOService.OnReady tdisOnReady = new IOIOService.OnReady() {
+        public void onReady() {
+            Log.d("Main", "TDIS onReady");
+            if(blinker != null) {
+                throw new RSInvalidStateException("onReady with blinker");
+            }
+            makeBlinker();
+        }
+        public void onUnready() {
+            Log.d("Main", "TDIS onUnready");
+            blinker = null;
+        }
+    };
     private final ServiceConnection tdisSC = new ServiceConnection() {
         @Override
         public void onServiceConnected(ComponentName cn, IBinder service) {
             Log.d("Main", "TDIS conn");
-            tdis = ((TeleDIOIOService.LocalBinder) service).getService();
+            tdis = ((IOIOService.LocalBinder) service);
+            tdis.addOnReady(tdisOnReady);
         }
         @Override
         public void onServiceDisconnected(ComponentName cn) {
+            // Because we're binding to a local service, this should never happen to us
             Log.d("Main", "TDIS discon");
             tdis = null;
+            tdisOnReady.onUnready();
         }
     };
 
@@ -111,13 +142,15 @@ public class MainActivity extends AppCompatActivity
         } else if (id == R.id.nav_share) {
             if(tdis != null) {
                 Log.d("Main", "Share w/ non-null...");
-                tdis.setForceFeedbackBehavior(true);
+                if(blinker == null) {
+                    makeBlinker();
+                } else {
+                    blinker.interrupt();
+                    blinker = null;
+                }
             }
         } else if (id == R.id.nav_send) {
-            Intent i = new Intent(this, TeleDIOIOService.class);
             Log.d("Main", "Send...");
-            startService(i);
-            bindService(i, tdisSC, Context.BIND_AUTO_CREATE);
         }
 
         DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/RemoteControlService.java b/mobile/src/main/java/com/acmetensortoys/android/teled/RemoteControlService.java
new file mode 100644 (file)
index 0000000..a964915
--- /dev/null
@@ -0,0 +1,78 @@
+package com.acmetensortoys.android.teled;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.Vector;
+
+public class RemoteControlService extends Service {
+    private boolean remoteControlEnabled = false;
+    private Vector<Thread> ongoingRemoteBehaviors = new Vector<>(1);
+
+    private void removeOngoing(Thread t) {
+        synchronized(RemoteControlService.this) {
+            ongoingRemoteBehaviors.remove(t);
+        }
+    }
+
+    public interface OnCessation {
+        void run();
+    }
+
+    // Assumes that t has been started already
+    private void _addOngoing(final Thread t, final OnCessation l) {
+        Thread nt = new Thread() {
+            public void run() {
+                while(t.isAlive()) {
+                    //noinspection EmptyCatchBlock
+                    try { t.join(); } catch (InterruptedException e) { }
+                }
+                removeOngoing(t);
+                l.run();
+            }
+        };
+        nt.start();
+        synchronized(this) { ongoingRemoteBehaviors.add(t); }
+    }
+
+    private boolean startAndAddOngoing(Thread t, OnCessation l) {
+        if(!remoteControlEnabled) {
+            return false;
+        }
+        t.start();
+        _addOngoing(t, l);
+        return true;
+    }
+
+    // TODO Should have a similar set of functionality for behaviors that should
+    // resume at some later point.  Consider losing the IOIO connection but wanting
+    // to restart when it comes back.
+
+    class LocalBinder extends Binder {
+        public void stopRemoteControl() {
+            synchronized (this) {
+                remoteControlEnabled = false;
+                // Cause all outstanding behaviors to cease their activities
+                for (Thread t : ongoingRemoteBehaviors) {
+                    t.interrupt();
+                }
+            }
+        }
+    }
+    private final IBinder mBinder = new LocalBinder();
+
+    @Override
+    public IBinder onBind(Intent arg0) {
+        Log.d("RCS", "onBind");
+        return mBinder;
+    }
+
+    @Override
+    public boolean onUnbind(Intent arg0) {
+        Log.d("RCS", "onUnbind");
+        return false;
+    }
+}
diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/TeleDIOIOService.java b/mobile/src/main/java/com/acmetensortoys/android/teled/TeleDIOIOService.java
deleted file mode 100644 (file)
index 5d34acc..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-// Based on IOIOService example from ytai's repository
-
-package com.acmetensortoys.android.teled;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.IBinder;
-import android.os.Binder;
-import android.util.Log;
-
-import ioio.lib.api.AnalogInput;
-import ioio.lib.api.DigitalOutput;
-import ioio.lib.api.IOIO;
-import ioio.lib.api.PwmOutput;
-import ioio.lib.api.exception.ConnectionLostException;
-import ioio.lib.util.BaseIOIOLooper;
-import ioio.lib.util.IOIOLooper;
-import ioio.lib.util.android.IOIOService;
-
-/**
- * An example IOIO service. While this service is alive, it will attempt to
- * connect to a IOIO and blink the LED. A notification will appear on the
- * notification bar, enabling the user to stop the service.
- */
-public class TeleDIOIOService extends IOIOService {
-    private static final int NOTIFY_ID = 0;
-
-
-    /*
-    public abstract class Command {
-        abstract public void behave()
-                throws ConnectionLostException, InterruptedException;
-    };
-
-
-    public class BlinkOnce extends Command {
-        private int duration;
-        public BlinkOnce(int dur) { duration = dur; }
-        public void behave()
-                throws ConnectionLostException, InterruptedException {
-            led_.write(true);
-            Thread.sleep(duration);
-            led_.write(false);
-        }
-    }
-
-    private LinkedBlockingQueue<Command> ioioq;
-    */
-
-    private boolean forceFeedbackBehavior = false;
-    private synchronized void awaitAnyBehavior() {
-        while(!forceFeedbackBehavior) {
-            try {
-                this.wait();
-            } catch (InterruptedException e) {
-                // ignored
-            }
-        }
-    }
-
-    public void setForceFeedbackBehavior(boolean nv) {
-        Log.d("TDIS", "sffb");
-        if(forceFeedbackBehavior && !nv) {
-            synchronized (this) {
-                forceFeedbackBehavior = false;
-            }
-        } else if (!forceFeedbackBehavior && nv) {
-            synchronized(this) {
-                forceFeedbackBehavior = true;
-                this.notifyAll();
-            }
-        }
-    }
-
-    @Override
-    public IOIOLooper createIOIOLooper(String type, Object info) {
-        return new BaseIOIOLooper() {
-            private DigitalOutput boardLED;
-
-            private AnalogInput forceSensor1;
-            private long forceSensor1dix;
-
-            // XXX Warmer controls?
-
-            // XXX This is not how it will be in the final version
-            private DigitalOutput motorEnable;
-            private PwmOutput motorPwm;
-
-            @Override
-            protected void setup() throws ConnectionLostException,
-                    InterruptedException {
-                Log.d("TDIS", "Looper setup");
-
-                boardLED = ioio_.openDigitalOutput(IOIO.LED_PIN, false);
-
-                forceSensor1 = ioio_.openAnalogInput(31);
-                forceSensor1.setBuffer(50);
-
-                motorEnable = ioio_.openDigitalOutput(1,false); // active low
-                motorPwm = ioio_.openPwmOutput(2,100);
-
-                forceSensor1dix = 0;
-
-                Log.d("TDIS", "Looper setup finish");
-            }
-
-            @Override
-            public void loop() throws ConnectionLostException,
-                    InterruptedException {
-
-                awaitAnyBehavior();
-                if (forceFeedbackBehavior) {
-                    boolean did = forceSensor1.available() > 0;
-                    float v = 0.0f;
-                    while (forceSensor1.available() > 0) {
-                        v = forceSensor1.getVoltageBuffered();
-                        forceSensor1dix++;
-                    }
-                    if (did) {
-                        motorPwm.setDutyCycle(v / 3.3f);
-                    }
-                }
-            }
-
-            @Override
-            public void disconnected() { }
-        };
-    }
-
-    @Override
-    public int onStartCommand(Intent intent, int flags, int startId) {
-        Log.d("TDIS", "onStartCommand");
-
-        int result = super.onStartCommand(intent, flags, startId);
-        NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
-        if (intent != null && intent.getAction() != null
-                && intent.getAction().equals("stop")) {
-            // User clicked the notification. Need to stop the service.
-            Log.d("TDIS", "stopping...");
-
-            setForceFeedbackBehavior(false);
-
-            // nm.cancel(0);
-            // stopSelf();
-        } else {
-            Notification.Builder nb = new Notification.Builder(this);
-
-            nm.notify(NOTIFY_ID,
-                    nb.setContentIntent(PendingIntent.getService(this, 0, new Intent(
-                            "stop", null, this, this.getClass()), 0))
-                            .setSmallIcon(R.drawable.ic_menu_send)
-                            .setContentTitle("TeleD IOIO service running")
-                            .setWhen(System.currentTimeMillis())
-                            .setOngoing(true)
-                            .build());
-        }
-        return result;
-    }
-
-    protected final class LocalBinder extends Binder {
-        TeleDIOIOService getService() { return TeleDIOIOService.this; }
-    }
-    private final IBinder mBinder = new LocalBinder();
-
-    @Override
-    public IBinder onBind(Intent arg0) {
-        Log.d("TDIS", "onBind");
-        return mBinder;
-    }
-
-
-
-
-    /*
-    public void doNotify() {
-        try {
-            //ioioq.put(new BlinkOnce(500));
-        } catch (InterruptedException e) {
-        }
-    }
-    */
-}
\ No newline at end of file
index 327aad24776368046a6767414af07f2b3a131747..e616b6817b60c1fadde93193701359272e43dd38 100644 (file)
@@ -1,6 +1,5 @@
 apply plugin: 'com.android.application'
 
-
 android {
     compileSdkVersion 23
     buildToolsVersion "23.0.2"
@@ -18,6 +17,11 @@ android {
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
     }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_7
+        targetCompatibility JavaVersion.VERSION_1_7
+    }
 }
 
 // IOIO support