Introduction
Nous avons déjà évoqué l'architecture d'AOSP et les modifications possibles lors de précédents articles. Android présente de nombreux avantages, à commencer par la relative facilité de développer une application en Java. Un inconvénient d'AOSP est qu'il ne supporte pas les interfaces tels que I2C, GPIO, SPI. Nous allons voir dans cet article comment il est possible d'utiliser un port GPIO sous Android.
Pour utiliser des appels bas niveau en C sur Android, il est possible d'utiliser le NDK ou bien de modifier directement le framework. Cette seconde approche implique de compiler sa propre ROM. Linaro, qui travaille sur le projet de téléphone modulaire ARA avec l'aide de Google, à déjà travaillé sur le sujet. Les développeurs d'applications pour ARA doivent en effet pouvoir intéragir avec les différents modules qui constituent le téléphone via GPIO et I2C.
Les sources d'Android 5.1 pour ARA sont disponibles sur le dépot git suivant : https://git-ara-mdk.linaro.org. Nous allons passer en revue les modifications importantes effectuées dans le framework. Les manipulations seront réalisées sur une Beaglebone Black sous Android 6.0. Elles peuvent être adaptées pour toute carte de développement fonctionnant sur Android. L'objectif n'est pas d'explorer en détail le code source. Nous allons nous concentrer uniquement sur les points importants. Le but est de démontrer qu'ajouter le support d'interfaces matérielles à Android est tout à fait réalisable.
echo 60 > /sys/class/gpio/export echo out >/sys/class/gpio/gpio60/direction echo 1 > /sys/class/gpio/gpio60/value #la led s'allume!
Modifications dans AOSP
static jobject android_GpioService_openDevice(JNIEnv *env, jobject thiz, jint gpio, jstring direction,jstring pid,jstring vid) { // exporter le gpio si besoin // Ecrire dans direction soit in soit out [...] char buf[BUFFER_SIZE]; memset(buf,'',sizeof(buf)); sprintf(buf, "/sys/class/gpio/gpio%d/value", gpio); //check permissions fd = open(buf, O_WRONLY); if(fd < 0){ ALOGE("%s", "Error opening value file in write mode"); return NULL; } //creating one duplicate file pointer int newFD = dup(fd); close(fd); // on va retourner un ParcelFileDescriptor à l'application Java // pour qu'elle manipule le fichier value. jobject fileDescriptor = jniCreateFileDescriptor(env, newFD); if (fileDescriptor == NULL) { ALOGE("%s fileDescriptor ",fileDescriptor); return NULL; } jclass clazz = env->FindClass("android/os/ParcelFileDescriptor"); LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor"); gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz); gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V"); LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL, "Unable to find constructor for android.os.ParcelFileDescriptor"); return env->NewObject(gParcelFileDescriptorOffsets.mClass, gParcelFileDescriptorOffsets.mConstructor, fileDescriptor); }
public class GpioService extends IGpioManager.Stub { public GpioService(Context context) { super(); mContext = context; mPackageManager = context.getPackageManager(); Log.i(TAG, "GpioManager Service Started"); } /* Ouvre le GPIO et retourne un ParcelFileDescriptor à l'application android */ @Override public ParcelFileDescriptor openGpio(int gpio ,String direction,String pkgName) { Log.i(TAG,"OpenGPIO, Package Name: " + pkgName); GpioDeviceFilter filter = getPkgInfo(pkgName); if (filter != null) { return open_gpiodevice(gpio ,direction,filter.mProductID,filter.mVendorID); } Log.e(TAG, "Product name not found in package!!" + pkgName); return null; } /* Close the specified gpio device */ @Override public void closeGpio(int gpio ) { Log.i(TAG, "GpioManager Service CLOSE GPIO"); close_gpiodevice(gpio ); } private native ParcelFileDescriptor open_gpiodevice(int gpio ,String direction,String pid,String vid); private native void close_gpiodevice(int gpio ); [...] static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ { "open_gpiodevice", "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;", (void*)android_GpioService_openDevice }, { "close_gpiodevice", "(I)V",(void*)android_GpioService_closeDevice }, }; int register_android_server_GpioService(JNIEnv* env) { return jniRegisterNativeMethods(env, "com/android/server/GpioService", sMethods, NELEM(sMethods)); } }
interface IGpioManager { /* retourne un descriptor pour ecrire/lire des données */ ParcelFileDescriptor openGpio(int gpio ,String direction,String packageName); void closeGpio(int gpio); }
public final class GpioManager { [...] private final IGpioManager mGpio; private final Context mContext; /* Ouvre le GPIO et retourne un ParcelFileDescriptor à l'application android */ public ParcelFileDescriptor openGpio(int gpio , String direction) { try { return mGpio.openGpio(gpio,direction,mContext.getPackageName()); } catch (RemoteException e) { return null; } } public void closeGpio(int gpio) { try { mGpio.closeGpio(gpio); } catch (RemoteException e) { Slog.e("GpioManager", "Unable to contact the remote Gpio Service"); } } public GpioManager(Context context,IGpioManager service) { mContext = context; mGpio = service; } }
Ajout du service au démarrage
registerService(Context.GPIO_SERVICE, GpioManager.class, new CachedServiceFetcher<GpioManager>() { @Override public GpioManager createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(Context.GPIO_SERVICE); return new GpioManager(ctx, IGpioManager.Stub.asInterface(b)); }}); }
Compilation image et SDK
gpioManager = (GpioManager) getSystemService(GPIO_SERVICE); mPfd = gpioManager.openGpio(GPIO, "out"); fd = mPfd.getFileDescriptor(); fos = new FileOutputStream(fd);
public void toggleLED(View view) { private static final String ON = "1"; private static final String OFF = "0"; Log.d(TAG, "toggleLED"); try { if(toggled) { fos.write(OFF.getBytes()); toggled = false; button.setText("Enable LED"); } else { fos.write(ON.getBytes()); toggled = true; button.setText("Disable LED"); } fos.flush(); } catch (IOException e) { e.printStackTrace(); } }