Learning Android is hard.
}
}
+// 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'
}
package="com.acmetensortoys.android.teled">
<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.BLUETOOTH" />
+ <uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
+
+ <service
+ android:name=".TeleDIOIOService"
+ android:enabled="true"></service>
+
+ <receiver
+ android:name=".SMSRecv"
+ android:enabled="true"
+ android:exported="true"
+ android:permission="android.permission.BROADCAST_SMS">
+ <intent-filter>
+ <action android:name="android.provider.Telephony.SMS_RECEIVED" />
+ </intent-filter>
+ </receiver>
</application>
</manifest>
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;
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 {
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) {
} 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.
+ */
--- /dev/null
+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...
+ */
+ }
+}
--- /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
--- /dev/null
+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 <channel> <level> <duration if level != 0>
+ WAIT <duration>
+ READ_VAR <name>
+
+ PWM_OK ?
+ VAR_READ <value>
+
+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 <channel> <stall behavior>
+ SEQ_CMD <channel> <append?> {<level> <duration>}*
+ READ_VAR <name>
+
+ SEQ_OK ?
+ VAR_READ <name> <value>
+
+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
}
}
+// 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:+'
}