From fb7d5c2f5dd3c92f441fc7b302f56a0b1b81eaba Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Fri, 29 Jan 2016 02:31:04 -0500 Subject: [PATCH] Some initial progress? Learning Android is hard. --- mobile/build.gradle | 10 +- mobile/src/main/AndroidManifest.xml | 18 ++ .../android/teled/MainActivity.java | 46 ++++- .../acmetensortoys/android/teled/SMSRecv.java | 31 +++ .../android/teled/TeleDIOIOService.java | 184 ++++++++++++++++++ .../com/acmetensortoys/android/teled/Thoughts | 75 +++++++ wear/build.gradle | 12 +- 7 files changed, 371 insertions(+), 5 deletions(-) create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/SMSRecv.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/TeleDIOIOService.java create mode 100644 mobile/src/main/java/com/acmetensortoys/android/teled/Thoughts diff --git a/mobile/build.gradle b/mobile/build.gradle index 11d0d79..416738e 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -19,11 +19,19 @@ android { } } +// IOIO support +dependencies { + compile 'com.github.ytai.ioio:IOIOLibAndroid:5.07' + compile 'com.github.ytai.ioio:IOIOLibAndroidBluetooth:5.07' + compile 'com.github.ytai.ioio:IOIOLibAndroidAccessory:5.07' + compile 'com.github.ytai.ioio:IOIOLibAndroidDevice:5.07' +} + dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) wearApp project(':wear') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' - compile 'com.google.android.gms:play-services:8.4.0' + compile 'com.google.android.gms:play-services:+' compile 'com.android.support:design:23.1.1' } diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index ad8d914..201b22f 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -3,6 +3,10 @@ package="com.acmetensortoys.android.teled"> + + + + + + + + + + + + diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java b/mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java index 284a3ab..d96a71d 100644 --- a/mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/MainActivity.java @@ -1,5 +1,10 @@ package com.acmetensortoys.android.teled; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.ComponentName; +import android.os.IBinder; import android.os.Bundle; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; @@ -12,6 +17,7 @@ import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; +import android.util.Log; public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { @@ -74,6 +80,20 @@ public class MainActivity extends AppCompatActivity return super.onOptionsItemSelected(item); } + private TeleDIOIOService tdis = 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(); + } + @Override + public void onServiceDisconnected(ComponentName cn) { + Log.d("Main", "TDIS discon"); + tdis = null; + } + }; + @SuppressWarnings("StatementWithEmptyBody") @Override public boolean onNavigationItemSelected(MenuItem item) { @@ -89,13 +109,35 @@ public class MainActivity extends AppCompatActivity } else if (id == R.id.nav_manage) { } else if (id == R.id.nav_share) { - + if(tdis != null) { + Log.d("Main", "Share w/ non-null..."); + tdis.setForceFeedbackBehavior(true); + } } 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); drawer.closeDrawer(GravityCompat.START); return true; } + + @Override + public void onDestroy(){ + unbindService(tdisSC); + super.onDestroy(); + } } + +/* TODO: + * + * * A settings activity with SMS configuration, including authentication + * information and an enable button. + * + * * A settings activity with Bluetooth configuration and maybe USB? + * + * * Spawn the IOIO service on startup if it isn't and see it work. + */ diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/SMSRecv.java b/mobile/src/main/java/com/acmetensortoys/android/teled/SMSRecv.java new file mode 100644 index 0000000..4a4b5b0 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/SMSRecv.java @@ -0,0 +1,31 @@ +package com.acmetensortoys.android.teled; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class SMSRecv extends BroadcastReceiver { + public SMSRecv() { + } + + @Override + public void onReceive(Context context, Intent intent) { + // TODO: This method is called when the BroadcastReceiver is receiving + // an Intent broadcast. + throw new UnsupportedOperationException("Not yet implemented"); + + /* NWF planning aloud here. + * + * We should check that we should even be paying attention to SMS; + * they may be disabled. + * + * We're going to check some authentication present in the incoming + * message -- source number, signed message hash, etc. -- and are then + * going to decode it in full. + * + * The message should contain a sequence number that we echo back, + * Since SMS's may reorder on the wire, let's just not support + * multiple outstanding messages; simpler on us this way... + */ + } +} diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/TeleDIOIOService.java b/mobile/src/main/java/com/acmetensortoys/android/teled/TeleDIOIOService.java new file mode 100644 index 0000000..5d34acc --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/TeleDIOIOService.java @@ -0,0 +1,184 @@ +// 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 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 diff --git a/mobile/src/main/java/com/acmetensortoys/android/teled/Thoughts b/mobile/src/main/java/com/acmetensortoys/android/teled/Thoughts new file mode 100644 index 0000000..c48c803 --- /dev/null +++ b/mobile/src/main/java/com/acmetensortoys/android/teled/Thoughts @@ -0,0 +1,75 @@ +Service Design +############## + +The TeleD IOIO management service is slave to several masters: + + - The IOIO library and connectivity events + - Any messages sent its way via the UI or via SMS or... + +The IOIO library provides a pretty decent vocabulary of low-level behaviors +in a relatively generic way. The TeleD service should probably use a +higher-level notion of behavior of specific components of the system; +that is, it should know about the hardware at hand (or elsewhere) and +expose only high-level directives. + +Failure cases are important to consider. Right now, the policy is likely +to be thus: + + - If the IOIO goes away, all behaviors are cancelled and callbacks are + fired, informing whoever cares to know (SMS, UI, ...). We cannot + shut down due to how Android works, thoug + + - If a behavior times out, that behavior will be stopped (and again + callbacks will be fired?) + +Protocol Notes +############## + +We should assume that SMS is high-latency and out-of-order. + +Every message should have an identifier at its top and be a series +of commands which will be replied to in order (as a single message, +after the whole thing has been executed?) + +That also means we should probably require that every command, other than those +that quiesce some activity, have a duration of up to a minute or so? explicitly +contained within them. (If you want longer commands, send many messages?) +Let's see.... what do we want in the control stream? + +One answer, I suppose, is straightfoward on/off commands and some kind of readback:: + + PWM_SET + WAIT + READ_VAR + + PWM_OK ? + VAR_READ + +Perhaps a little better, rather than something quite that simple, is an +IOIO-Sequencer like approach, where we can send over a stream of motion +commands at once, which are *enqueued*, with the head taking effect either +immediately (and the rest of the queue being jettisoned) or are appended +to the command queue. :: + + SEQ_STALL + SEQ_CMD { }* + READ_VAR + + SEQ_OK ? + VAR_READ + +An answer I kind of like better is, of course, to borrow from the +hyperbola-chiptunes project of years gone by and to have a notion of +instruments / tracks / measures / scores with channels and conditionals. + +* Instruments are like (pre-loaded) sequencer command chains at + a fine level (they're like individual waveforms) + +* Tracks are like sequencer command chains at a coarser level + and reference instruments for their moment-to-moment behavior. + That is, a track is a sequence of instruments (and other data?) + +* Measures step several tracks in tandem. + +* Scores are a sequence of frames together with a channel-to-actuator + map and conditional control flow and some vague notion of interrupts... \ No newline at end of file diff --git a/wear/build.gradle b/wear/build.gradle index 8c198d3..327aad2 100644 --- a/wear/build.gradle +++ b/wear/build.gradle @@ -20,8 +20,16 @@ android { } } +// IOIO support +dependencies { + compile 'com.github.ytai.ioio:IOIOLibAndroid:5.07' + compile 'com.github.ytai.ioio:IOIOLibAndroidBluetooth:5.07' + compile 'com.github.ytai.ioio:IOIOLibAndroidAccessory:5.07' + compile 'com.github.ytai.ioio:IOIOLibAndroidDevice:5.07' +} + dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.google.android.support:wearable:1.3.0' - compile 'com.google.android.gms:play-services-wearable:8.4.0' + compile 'com.google.android.support:wearable:+' + compile 'com.google.android.gms:play-services-wearable:+' } -- 2.50.1