-
Notifications
You must be signed in to change notification settings - Fork 355
IOIOLib Basics
IOIOLib is an Android library, which enables your Android application to control the IOIO board. It exposes a set of Java interfaces, covering the various features of the board. When you build your application, IOIOLib gets packaged into your target .apk
file, so your application is stand-alone and does not require any further installation on the Android device that runs it. The entire code is pure Java, depending solely on the standard Android 1.5 (or later) framework.
IOIOLib is all documented in standard Javadoc format, and this documentation is intended to be comprehensive and be used as reference while coding. In this User Guide, we try to cover usage of the library from a common-use-cases approach rather than be 100% formal.
The library is organized in several Java packages. The ioio.api
package contains all the public API for controlling the IOIO. This is the package your application will be using. Within it, the ioio.api.exception
package contains some exceptions thrown by the IOIO API. The ioio.impl
package contains the implementation of these interfaces and is not intended to be used directly. The ioio.util
package contains useful utilities that may make your life a little easier when writing IOIO applications, but do not provide core functionality. At the time of writing this, this package only contains a single class, serving as a base class for your IOIO-based application, which automatically handles proper connection / disconnection to the IOIO board in response for application events.
If you are not familiar with Android application development and/or have not yet setup your development environment, the IOIO Beginner's Guide page may provide useful information.
The latest version IOIOLib can be downloaded from the Downloads page. It is a part of the "Android Software" zip. This zip also includes some sample applications.
We're only going to cover usage with Eclipse here, with the assumption that users not using Eclipse for Android application development probably know what they're doing :)
- Extract
IOIOLib
to somewhere where you normally want to keep your Android projects. - Import it into Eclipse using File > Import... > General > Existing Projects into Workspace..., then choose the IOIOLib directory you just created and click Finish.
- Reference it from your application project, according to these instructions
- Make sure your application declares using the
android.permission.INTERNET
permission. This can set by opening theAndroidManifest.xml
file found at your project's root, going to the Permissions tab > Add... > Uses Permission > Selectandroid.permission.INTERNET
under "Name". - Make sure you enabled USB debugging on your Android device, by going to Settings > Applications > Development > Enable USB Debugging.
The IOIO
interface is the heart of the IOIOLib. An instance of this interface represents a physical IOIO board, and through its methods you can control its various functions, such as reading or writing values to individual physical pins.
Note: It is not very common to have to create the IOIO instance yourself. Refer to the utility classes described in the section below for a simpler, more common usage.
In order to create an instance of IOIO
, you should use the IOIOFactory.create()
method.
Simply call:
IOIO ioio = IOIOFactory.create();
The call will return immediately, providing you with a valid instance. However, this instance is not yet usable, and any attempt to use it will throw an exception. A connection with the board must first be created, by calling:
ioio.waitForConnect();
This call will block until the board is powered, physically connected via USB and establishes communications with your application (if it doesn't, make sure you've got USB debugging turned on).
Once the IOIO is connected, you can start using it (well, that's what you bought it for, probably)! Its various functions are normally accessed via sub-instances obtained from the openXYZ()
methods. These sub-instances remain associated with the IOIO
instance which created them throughout their lifetime. They all extend the Closeable
interface, which simply means your can call close()
on them. Calling close()
will invalidate the instance on which it was called, and will free and resources (e.g. pins) it used for future use. For documentation on specific functions, please refer to:
In order to disconnect from the board (automatically resetting its state), or abort an on-going waitForConnect()
, simply call:
ioio.disconnect();
You can do this, for example, when your application gets paused, or from a TimerTask
, if you want to timeout on a connection attempt.
The call is asynchronous. The disconnect process is normally very fast, but if you want to make sure all resources have been freed, you can use:
ioio.waitForDisconnect();
This call will block until the disconnect process completes. You can also use this call to implement a listener that does something upon disconnect.
From the moment a disconnection occurs, whether due to the client's explicit call to disconnect()
or due to a physical disconnection, the instance, as well as any other instances obtained from it, will throw a ConnectionLostException
upon every call. A IOIO
instance is one-time use. This means that once a connection is lost, the instance is as good as dead. It cannot be recycled. Create a new instance if you want to re-establish connection, and preferably attempt to do some after calling ioio.waitForDisconnect
on the old instance, to make sure you are waiting for all resources (such as the underlying network socket used for communication) to be freed.
There are two kinds of resets, soft and hard.
A soft reset means "return everything to its initial state". This includes closing all interfaces obtained from this IOIO
instance, and in turn freeing all resources. All pins (save the stat LED) will become floating inputs. All modules will be powered off. These operations are done without dropping the connection with the IOIO, thus a soft reset is very fast. It is generally not a good practice to replace proper closing of sub-instances with a soft reset. However, under some circumstances, such as tests, this might make sense.
A soft reset is performed by calling:
ioio.softReset();
A hard reset is exactly like physically powering off the IOIO board and powering it back on. As a result, the connection with the IOIO will drop and the IOIO
instance will become disconnected and unusable. The board will perform a full reboot, including going through the bootloader sequence, i.e. an attempt to update the board's firmware will be made. Hard reset is mostly useful for this very purpose - your application wants to initiate a firmware upgrade. In the future, applications (or IOIOLib) will be able to request an install of ad-hoc firmware on the board. A hard reset will then enable the board to pick up the new firmware and start executing it.
A hard reset is performed by calling:
ioio.hardReset();
In some cases, your application uses a high rate of write operations to the IOIO. An example might be changing a bunch of digital outputs immediately one after the other to achieve a parallel-like interface. Doing these operations naively, will cause a message to be sent to the IOIO for every operation. Every such message incurs a certain latency and this latency adds up and may become significant for large amount of writes and latency-sensitive applications.
For that purpose, an optimization method is available in the IOIO
interface, which enables grouping a number of such write operations into a single message that is sent to the IOIO, thus taking the latency hit only once per group rather than once per operation. This is strictly an advanced optimization and will not change functionality. It is recommended to use this technique only after everything works without it and improving latency is desired. Note that trying to group operations that block until response from the IOIO is obtained (such as SPI or TWI writeRead()
) will hang your thread!
Grouping is achieved simply by surrounding your group of write operations with a
ioio.beginBatch()
and
ioio.endBatch()
calls. The former will cause the IOIO instance to delay all messages until the latter is called. Note that this is strictly a hint. The IOIO instance may decide to send a message nevertheless, if, for example, the buffered messages become too long. Also note that it is legal to nest such batch blocks. The messages to the IOIO will be delayed until the outermost block exits.
Take extra care about ending a batch cleanly in the presence of exceptions. Possibly, put it in a finally
block immediately surrounding all the block contents, such as:
ioio.beginBatch();
try {
pin1.write(false);
pin2.write(true);
...
} finally {
ioio.endBatch();
}
Note: If you're looking for the old documentation of the now-deprecated
AbstractIOIOActivity
class, they are [here](Using-AbstractIOIOActivity-(deprecated)). It is recommended that you switch toIOIOActivity
- the migration process is close to trivial.
Very often IOIO-based applications will have the following traits:
- A single thread is dedicated to creating the
IOIO
instance, establishing connection and then controlling the IOIO. This is possibly done while communicating with other threads, such as the UI thread. In more advanced scenarios, one thread will be created per-possible communication channel, such as USB, Bluetooth, etc. - If the connection drops (as a result of a physical disconect), create a new IOIO instance and try to reconnect.
- This thread will be created in
onStart()
and aborted inonStop()
of anActivity
, or similarly when a certain aspect of the application state is "started" and "stopped", in case of a non-Activity
application. - Aborting the thread involves disconnecting from the IOIO or otherwise cancelling an on-going attempt to connect.
- In this thread, there will be some operations done once, right after a connection is established. Typically, these will include creating sub-instances of the
IOIO
object, by calling some of theopenXYZ()
methods. - Then, there will be on-going tasks done repetitively.
- And finally, we may want to do some operations when the connection is lost or when some problem is detected.
While this behavior surely doesn't cover every possible application, it does cover quite a few of them. So in order to save the boiler-plate code for achieving just that, a utility class has been created, called IOIOActivity
, under the ioio.lib.util.android
package. This is simply an abstract class, extending Activity
which does exactly what's written above.
It leaves you, the application author, to:
- Create your
Activity
class by extendingIOIOActivity
. - Create a (possibly inner-, possibly inline-) "Looper" class implementing the
IOIOLooper
interface. You can extendBaseIOIOLooper
for some convenience of having theIOIO
instance stored as a protected field. - In your Looper class, implement the
setup()
method, which gets called once the IOIO connection is established. It could use theioio_
field as itsIOIO
instance. - In addition, implement the
loop()
method, which gets called repetitively as long as IOIO is connected. It could use theioio_
field as itsIOIO
instance. - You may let any
ConnectionLostException
orInterruptedException
get thrown from either of these methods - it will be caught and properly handled automatically, causing abortion of the IOIO connection and the thread running your Looper. - You may implement the
disconnected()
method for operations that should be done when the connection is lost. This method may no longer use theioio_
instance, as it is already invalid. But it is still very useful, for example, for disabling some of the GUI widgets, or presenting a message to your user. - You may implement the
incompatible()
method if you want to somehow handle the case when a IOIO whose firmware version is incompatible with the version of IOIOLib your application was compiled against. Again, this is useful mainly for presenting an informative message to the user. - Finally, implement the
createIOIOLooper()
method in your main class, which simply returns a new instance of your Looper class.
The HelloIOIO example, provided along with IOIOLib, is a good example of using IOIOActivity
. Here's a snippet of this example:
public class MainActivity extends IOIOActivity {
...
class Looper extends BaseIOIOLooper {
private DigitalOutput led_;
@Override
protected void setup() throws ConnectionLostException {
led_ = ioio_.openDigitalOutput(0, true);
}
public void loop() throws ConnectionLostException {
led_.write(!button_.isChecked());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
@Override
protected IOIOLooper createIOIOLooper() {
return new Looper();
}
}
Similarly, there is a IOIOService
base class, which simplifies authoring a service that uses IOIO. Check out the Javadocs of this class, or the HelloIOIOService example.
In more advanced cases, extending either of the aforementioned classes is not an option. But you may still want to have the basic behavior of the framework mentioned above. This can be achieved using the IOIOAndroidApplicationHelper
class, found in the ioio.lib.util.android
package. This class is typically not extended, but rather used as a field of your class. The rules for using it are: call create()
after construction. Call start()
and stop()
alternately whenever you want to start/stop the threads used for IOIO operations. Finally, call destroy()
. FYI, internally, both IOIOActivity
and IOIOService
use this class.
If you want to use this behavior outside the scope of Android, ioio.lib.util.IOIOApplicationHelper
is your friend, since it is platform independent. But take into account that some Android-specific functionality (such as open-accessory) would not work when using this class, so it is not recommended for direct usage for Android applications.
IOIOActivity
and the other utilities support writing applications with multiple IOIO boards connected. Each can connect over one of the available connection channels (e.g. USB, Bluetooth). In such a case, IOIOActivity
will call your createIOIOLooper()
once for every possible connection channel. Your implementation can then create a looper for each IOIO, or possibly return null
if the given channel is irrelevant. In order to distinguish between the different channels (e.g. in order to refuse creation for some connection types), implement createIOIOLooper(String, Object)
instead. The arguments will provide information on the connection types.
For more information, please see the Javadocs of IOIOActivity
.