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.
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
}
// IOIO support
<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>
--- /dev/null
+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);
+ }
+ }
+ }
+ };
+ }
+ };
+ }
+}
--- /dev/null
+// 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
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;
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
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();
}
};
} 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);
--- /dev/null
+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;
+ }
+}
+++ /dev/null
-// 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
apply plugin: 'com.android.application'
-
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
}
// IOIO support