Ok Glass, when is the next Bus ?

Share Button

iD.apps étudie depuis quelques temps déjà le développement pour les Google Glass.

Nul besoin de présenter les Google Glass (si c’est le cas aller voir ici), en revanche le développement sur les Glass est un peu moins connu. Nous vous proposons donc un exemple concret de développement sur les Glass. Pour comprendre tous les points techniques, des connaissances en développement Android sont conseillées.

Pour rappel, d’un point de vue technique, les Google Glass sont un smartphone Android embarqué dans un prisme équipé des principaux capteurs suivants :

  • Une caméra;
  • Un micro;
  • Un accéléromètre/gyroscope (pour le mouvement de la tête);
  • Un capteur de luminosité;
  • Une surface tactile : la branche droite des Google Glass.

Les Glass sont connectées à internet soit via la connexion de votre smartphone qu’elles exploitent par bluetooth, soit directement via wifi.

Cet article vous décrit la mise en oeuvre d’une application utilisant la reconnaissance vocale, la caméra et la connexion internet. Celle-ci permet de récupérer les horaires des prochains bus rennais d’un arrêt. Pour cela nous allons nous appuyer sur les données ouvertes qui fournissent en temps réel les temps d’attente aux arrêts. Enfin pour identifier les arrêts nous allons nous appuyer sur les QR Codes qui sont affichés dessus.

Concrètement, au lancement de l’application l’utilisateur est dirigé sur une vue affichant ce que capte la caméra. Cela, afin de lui permettre de viser le QR code à scanner. Sur cette vue,  l’utilisateur scanne le QR code en prenant une photo de celui-ci par une action de tap. Lorsqu’il a scanné le QR code, et que celui-ci est détecté, l’utilisateur est dirigé sur une LiveCard ajoutée à la timeline des Glass. Sur cette Livecard l’utilisateur voit les horaires des prochains bus.

Pour développer cette application, nous nous appuyons sur le site de développement Google Glass (Glass developpement overview) et sur les références Android.

Avant toute chose

Le développement sur Google Glass se fait dans un environnement Android. Il est donc nécessaire d’avoir un SDK à jour jusqu’à la version Android 4.4.2 (API19) ainsi que le Glass Development Kit Preview (cf. Quick Start). Lors de la création d’un projet pour Glass il faut régler la version cible du SDK et la version minimum de celui-ci sur la version 19 de l’API. C’est la seule supportant le GDK Preview. Ensuite, pour la compilation, il faut régler le paramètre de compilation sur GDK Preview. Il est aussi conseillé d’enlever toutes indications de thème.

Lancer une application sur Google Glass

Toute application sur Google Glass doit commencer par une commande vocale. La création de celle-ci permet aussi un accès à l’application via le menu principal des Glass accessible en utilisant le touch pad. Les commandes vocales utilisables sont limitées aux commandes officielles (VoiceTriggers). Si aucune des commandes ne correspond à ce dont vous avez besoin, il est possible d’en faire enregistrer une nouvelle mais cela prend du temps et elle doit être conforme à la checklist de Google (VoiceTrigger checklist). Il est donc conseillé d’entamer les démarches très tôt si vous voulez que votre GlassWare puisse être disponible sur le store MyGlass. Toutefois, il est aussi possible d’autoriser ses propres commandes dans le manifeste pour des besoins de développement ; c’est  ce que nous utilisons actuellement.

Pour ajouter une commande personnalisée, il faut d’abord ajouter une string en ressource pour définir le nom de la commande vocale.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="glass_voice_trigger">When is the next bus ?</string>
</resources>

Ensuite, il faut créer une ressource xml dans res/xml/<ma_commande_vocale>.xml, dans laquelle on ajoute une composante trigger avec un attribut keyword référençant la ressource string définie plus tôt.

<?xml version="1.0" encoding="utf-8"?>
<trigger keyword="@string/glass_voice_trigger"/>

Si la commande vocale est une commande déjà enregistrée, correspondant à une Voice Triggers, l’attribut n’est plus keyword mais command (pour plus d’information cf. Starting Glassware). Ensuite, il faut enregistrer, dans le manifeste, l’intention associée à la commande vocale et autoriser la commande.

<activity | service ...>
        <intent-filter>
            <actionandroid:name=
                    "com.google.android.glass.action.VOICE_TRIGGER"/>
        </intent-filter>
        <meta-dataandroid:name="com.google.android.glass.VoiceTrigger"
            android:resource="@xml/ma_commande_vocale"/>
    </activity | service>
<uses-permission
     android:name="com.google.android.glass.permission.DEVELOPMENT"/>

When is the next bus ? icon

Une fois l’application installée sur les Glass, vous devriez voir une nouvelle commande vocale et une nouvelle application dans le menu d’applications.

Scanner un QR code avec des Google Glass

Maintenant que nous pouvons lancer notre application, nous devons pouvoir scanner le QR code présent sur l’arrêt de bus.

La prise de photo avec les Glass peut se faire de deux façons : soit avec l’activité native Google Glass qui ne permet pas d’aperçu, soit grâce à la Camera API d’Android qui permet de fonctionner de la même façon qu’une application photo sur un smartphone Android.

Ici, une preview nous semblait nécessaire et devait donner la possibilité à l’utilisateur de viser le QR code à scanner avec la caméra intégrée. Pour cela, on crée donc un SurfaceHolder pour afficher cette preview.

private final SurfaceHolder.Callback mSurfaceHolderCallback =
              new SurfaceHolder.Callback() {

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		try {
			mCamera.setPreviewDisplay(holder);
			mCamera.startPreview();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		// Nothing to do here.
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		// Nothing to do here.
	}
};

Attention, lors de l’utilisation intensive de la caméra des Glass veillez à bien libérer celle-ci après avoir pris une photo pour éviter de bloquer les Glass.

private void releaseCamera() {
	if (mCamera != null) {
		if (mSurfaceHolderCallback != null) {
			mPreview.getHolder().removeCallback(mSurfaceHolderCallback);
			mCamera.release();
		}
	}
}

Ensuite, si vous souhaitez la réutiliser, il faut réinitialiser complètement la caméra.

Ici, nous avons besoin d’effectuer un zoom pour pouvoir scanner un QR code sans que l’utilisateur soit obligé de coller sa tête devant.

private void initCamera() {
	mCamera = getCameraInstance();
	mPreview.getHolder().addCallback(mSurfaceHolderCallback);
	Camera.Parameters parameters = mCamera.getParameters();
	int maxZoom = parameters.getMaxZoom();
	if (parameters.isZoomSupported()) {
		if (mCamera.getParameters().getZoom() >= 0
				&& mCamera.getParameters().getZoom() < maxZoom) {
			int i = maxZoom / 2;
			parameters.setZoom(i);
		} else {
			// zoom parameter is incorrect
		}
	}
	mCamera.setParameters(parameters);
}

Ainsi, après avoir initialisé la preview et saisi la caméra, nous pouvons effectuer un takePicture et définir les opérations de CallBack nécessaires à la prise de photo proprement dite. Nous n’avons utilisé que le CallBack JPEG dans lequel nous affichons le résultat de la prise de photo avant de scanner l’image à la recherche du QR code. Enfin nous démarrons la LiveCard permettant d’afficher les horaires des prochains bus.

private final PictureCallback mJPEGCallback = new PictureCallback() {

	@Override
	public void onPictureTaken(byte[] data, Camera camera) {

		mPreview.setVisibility(View.GONE);
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inSampleSize = 5;

		mPicture = BitmapFactory.decodeByteArray(data, 0, data.length,
				options);
		mImageTaken.setImageBitmap(mPicture);
		mImageTaken.setVisibility(View.VISIBLE);

		String result = scanQRCode(data);

		if (!TextUtils.isEmpty(result)) {
			Toast.makeText(ScanQrCodePictureActivity.this, result,
					Toast.LENGTH_SHORT).show();
			Intent startPublishIntent = new Intent(
					ScanQrCodePictureActivity.this,
					PublishScheduleIntoLiveCardService.class);
			startPublishIntent.putExtra(Poc_Horaires_Constants.URL, result);
			startService(startPublishIntent);
		} else {
			Toast.makeText(ScanQrCodePictureActivity.this,
					getString(R.string.detection_failed),
					Toast.LENGTH_SHORT).show();
		}
		releaseCamera();
		finish();
	}
};

Un scan avec le scanner Zbar ne se fait que sur une image au format Y800.

private String scanQRCode(byte[] data) {
	Bitmap imageRes = BitmapFactory.decodeByteArray(data, 0, data.length);
	String symbol = null;
	int width = imageRes.getWidth();
	int height = imageRes.getHeight();
	int[] pixels = new int[width * height];

	imageRes.getPixels(pixels, 0, width, 0, 0, width, height);
	Image qrcode = new Image(width, height, "RGB4");
	qrcode.setData(pixels);

	int result = mScanner.scanImage(qrcode.convert("Y800"));

	if (result != 0) {
		SymbolSet syms = mScanner.getResults();
		for (Symbol sym : syms) {
			symbol = sym.getData();
		}
	}
	return symbol;
}

Les horaires de bus dans une LiveCard

Le résultat du scan du QR code se présente sous la forme d’une URL de laquelle on extrait le numéro de l’arrêt. Celui-ci nous sert ensuite dans l’appel à l’API Keolis. Dans notre application, l’extraction, l’appel à l’API et l’affichage du résultat se font dans un service qui va mettre à jour une LiveCard au travers d’une RemoteView.

Une LiveCard est une carte Google Glass donnant des informations pertinentes dans l’instant. Les LiveCards se placent dans la Timeline à gauche de l’écran d’accueil des Glass et sont régulièrement mises à jour pour le temps où leur utilisation est pertinente.

private LiveCard mLiveCard;
private RemoteViews mLiveCardView;
...
public int onStartCommand(Intent intent, int flags, int startId) {
	if (mLiveCard == null) {
		
		URL = intent.getStringExtra(Poc_Horaires_Constants.URL);
		busStop = extractBusStop(URL);

		mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
		mLiveCardView = new RemoteViews(getPackageName(),
				R.layout.service_live_card);
		...
		mLiveCard.publish(PublishMode.REVEAL);

		// Queue the update text runnable
        mHandler.post(mUpdateLiveCardRunnable);
	}
	return START_STICKY;
}

La mise à jour de notre LiveCard se fait via un thread qui va appeler l’API Keolis au travers de Retrofit toutes les minutes. Retrofit permet de transformer une interface java en objet permettant l’appel d’une API REST.

public interface KeolisApiCalls {
	@GET("/?cmd=getbusnextdepartures")
	void getHoraires(@Query("version") String version,
			@Query("key") String key, @Query("param[mode]") String mode,
			@Query("param[stop][]") String stop, Callback<OpenData> cb);
}

Interface permettant de faire appel à l’API Keolis dans notre application.

private RestAdapter mKeolisRestAdapter;
private KeolisApiCalls mKeolisApiCalls;
...
public void run() {
	if (!isStopped()) {

		mKeolisRestAdapter = new RestAdapter.Builder()
				.setEndpoint(KeolisApiCalls.ENDPOINT)
				.setConverter(new SimpleXMLConverter()).build();
		mKeolisApiCalls = mKeolisRestAdapter
				.create(KeolisApiCalls.class);

		Callback<OpenData> cb = new Callback<OpenData>() {
			@Override
				public void success(OpenData retour, Response qResponse) {
				String scheduleToDisplay = "";

				if (retour.getAnswer().getStatus().getCode() != 0) {
					scheduleToDisplay = ""
							+ retour.getAnswer().getStatus().getCode()
							+ " : "
							+ retour.getAnswer().getStatus()
									.getMessage();

					updateScheduleError(scheduleToDisplay);
				} else {
					updateSchedule(FormatBusSchedule
							.listSchedules(retour));
				}
			}
			...
		};

		mKeolisApiCalls.getHoraires(KeolisApiCalls.VERSION,
				KeolisApiCalls.API_KEY, Poc_Horaires_Constants.STOP,
				busStop, cb);

		// Queue another schedule update in 60 seconds.
		mHandler.postDelayed(mUpdateLiveCardRunnable, DELAY_MILLIS);
	}
}

Dans le thread de mise à jour de notre LiveCard, on initialise un objet de type RestAdapter implémentant l’interface Retrofit que nous avons définie. Cet objet permet d’appeler l’API Keolis. L’appel du WebService prend en paramètre une callBack dans laquelle nous récupérons le résultat de notre requête. Nous pouvons ensuite l’afficher dans notre LiveCard.

Et voilà, notre utilisateur sait quels sont les prochains passages de Bus à son arrêt.

Conclusion

Au travers de cette application, nous mettons en oeuvre un bon exemple de développement sur Google Glass.  On se familiarise ainsi avec l’utilisation de la caméra, des LiveCards, de Retrofit et enfin de Zbar. Vous pouvez constater que le développement sur Glass est très similaire à celui pour Android, du moins pour ce qui est du code, car pour les usages c’est une autre histoire que nous développerons dans de prochaines applications 😉

Share Button

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *