Controller un Roomba avec sa smartwatch

Controller un Roomba avec sa smartwatch

Pour beaucoup, la nouveauté 2015 dans le monde les smartwatchs est l'Apple Watch. Mais une autre smartwatch a fait parler d'elle : la Pebble Time. Notamment grâce au succès sans précédent de sa campagne Kickstarter. La nouvelle version vient d'arriver chez Yoctopuce, et pour fêter ça, on a écrit une application qui permet de contrôler un Roomba depuis sa montre.



Le but de cet article n'est pas de faire un review de la Pebble Time, mais d'illustrer comment interagir avec nos modules depuis une montre Pebble. Cette application Pebble doit permettre de lancer un nettoyage, un nettoyage localisé ou d'envoyer le Roomba sur sa station de charge.

Pour ce faire, nous avons repris le montage que nous avions assemblé dans notre précédent article sur le Roomba et réécrit en Java le code PHP qui décodait le protocole série du Roomba. Pour rappel, nous avions branché un Yocto-Serial et un YoctoHub-Wireless-g sur le Roomba, ce qui permet de contrôler le Roomba depuis n'importe quelle application qui utilise une de nos librairies.

Comme la plupart des smartwatchs, la Pebble ne peut pas se connecter directement à un réseau Wifi, elle communique avec un smartphone qui exécute les connexions réseaux. On a donc besoin d'une application Pebble mais aussi d'une application Android. L'application Android s'occupe de la connexion avec le YoctoHub qui est branché au Roomba. Cette application s'occupe aussi du formatage des instructions à envoyer au Roomba. L’application Pebble Time affiche simplement l’état du Roomba (déterminé par l'application Android) et transmet à l'application Android les commandes à envoyer (nettoyer, retour à la station...) en fonction des boutons pressés.

La communication entre le Roomba et la Pebble passe par le téléphone
La communication entre le Roomba et la Pebble passe par le téléphone



L'application Android est le coeur du système: c'est elle qui implémente le code qui envoie les bonnes instructions au Roomba. L'application Android est parfaitement utilisable sans Pebble, au contraire de l'application Pebble qui ne sert à rien si l'application Android n'est pas installée.


L'application Android


Cette application est découpée en trois parties:

- L'interface de l'application
- Le service qui implémente la logique de contrôle du Roomba
- L'interface de communication avec la Pebble.

L'interface de l'application est très simple: il s'agit de trois boutons pour les trois commandes (nettoyer, nettoyer localement, retour station) et d'un champ texte qui affiche l'état du Roomba. Chaque bouton envoie la commande correspondante au Service qui est responsable d'envoyer les bonnes instructions au Roomba. Pour afficher l'état du Roomba, on écoute les BroadcastIntent envoyés par le Service.

Le service est le coeur de l'application: c'est cette partie qui reçoit les commandes de l'interface de l'application et de la Pebble. L'interface transmet au service les commandes à exécuter à l'aide d'Intent.

Intent intent = new Intent(MainActivity.this, RoombaService.class);
intent.putExtra(RoombaService.INTENT_KEY_COMMAND, RoombaService.CLEAN);
startService(intent);



Pour chaque commande reçue, le service détermine s'il doit envoyer des instructions au Roomba. Si c'est le cas, il établit la communication avec le YoctoHub et envoie les instructions au Roomba. Ensuite, il envoie un BroadcastIntent avec le nouvel état du Roomba pour que l'application et la Pebble puissent mettre à jour leurs interfaces.

L'interface communique avec le service qui effectue tout le travail
L'interface communique avec le service qui effectue tout le travail




L’intérêt d'utiliser un service pour La logique est d’exécuter le code en tâche de fond. Cela permet à la Pebble de communiquer avec notre application même si elle n'est pas lancée. Cela nous permet aussi d'effectuer directement une connexion HTTP avec le YoctoHub sans avoir à utiliser un autre Thread (Android interdit les connexions réseau depuis le thread principal).

Pour finir, cela permet de garder un code bien structuré qui suit le principe MVC. Les instructions qui sont envoyées au Roomba ne sont pas détaillées dans cet article, mais vous pouvez lire notre précédent article pour plus d'information.

Comme d'habitude, tout le code le l'application est disponible sur GitHub.


L'application Pebble Time


L'application Pebble Times est assez simple. L'interface s'inspire très fortement de l'exemple UI Patterns.

L'interface de la montre est très simple
L'interface de la montre est très simple



La communication avec le téléphone se fait à l'aide de Dictionnaire dont les clefs sont des entiers et la valeur correspondante peut être un entier ou une chaîne de caractères. La montre envoie au téléphone un message KEY_CMD à chaque fois qu'un bouton est pressé et au démarrage de l'application. Les valeurs possibles sont:

  • CMD_CLEAN : La commande nettoyer
  • CMD_SPOT : La commande nettoyer localement
  • CMD_DOCK : La commande retourner à la station
  • CMD_GET_STATE : La pseudo commande renvoyer l’état du Roomba


En réponse, la Pebble peut recevoir deux messages: KEY_STATE et KEY_MESSAGE. Les valeurs possible de KEY_STATE sont

  • STATE_CLEANING : Le Roomba nettoie une pièce
  • STATE_SPOTING : Le Roomba nettoie une zone
  • STATE_DOCKING : Le Roomba retourne à sa station
  • STATE_DOCKED :Le Roomba est sur sa station de charge
  • STATE_OFF : Le Roomba est inactif mais hors de sa station de charge
  • STATE_OFFLINE : Le Roomba n'est pas joignable (erreur)


Le message KEY_MESSAGE contient une chaîne de caractères qui décrit en détail l'état du Roomba.

Le code source de l'application est disponible sur GitHub et peut être compilé directement dans CloudPebble.


La communication entre la Pebble et Android


La communication entre la Pebble et l'application Android utilise des Intent et des BroadcastIntent. Il est possible de tout coder à la main, mais Pebble fournit une librairie qui simplifie la tâche. Pour l'inclure, il faut ajouter une dépendance sur la librairie Pebblekit dans script de build gradle:

dependencies {
  compile 'com.getPebble:Pebblekit:3.0.0'
}



Pour envoyer l'état du Roomba à la Pebble, on implémente un BroadcastReceiver qui recoit les messages broadcastés par notre service et les transmet à la Pebble grâce à la méthode PebbleKit.sendDataToPebble.

public class RoombaToPebbleReceiver extends RoombaBroadcastReceiver
{
  private static final int ROOMBA_MESSAGE = 1;
  private static final int ROOMBA_STATE = 2;
  private static final int ROOMBA_STATE_CLEANING = 0;
  private static final int ROOMBA_STATE_SPOTING = 1;
  private static final int ROOMBA_STATE_DOCKING = 2;
  private static final int ROOMBA_STATE_OFF = 3;
  private static final int ROOMBA_STATE_OFFLINE = 4;
  private static final int ROOMBA_STATE_DOCKED = 5;

  @Override
  protected void onSensorChange(Context context, RoombaSensors sensor)
  {

  }

  @Override
  protected void onStateChange(Context context,
                                RoombaControler.State state,
                                String error)
  {
    String stateStr;
    int pb_state = ROOMBA_STATE_OFF;
    // Add a key of 1, and a string value.
    switch (state) {
        case CLEANING:
            stateStr = context.getString(R.string.cleaning);
            pb_state = ROOMBA_STATE_CLEANING;
            break;
        case DOCKING:
            stateStr = context.getString(R.string.docking);
            pb_state = ROOMBA_STATE_DOCKING;
            break;
        case SPOT:
            stateStr = context.getString(R.string.spotting);
            pb_state = ROOMBA_STATE_SPOTING;
            break;
        case OFF:
            stateStr = context.getString(R.string.off);
            pb_state = ROOMBA_STATE_OFF;
            break;
        case DOCKED:
            stateStr = context.getString(R.string.docked);
            pb_state = ROOMBA_STATE_DOCKED;
            break;
        default:
        case DISCONNECTED:
            stateStr = context.getString(R.string.disconnected);
            pb_state = ROOMBA_STATE_OFFLINE;
            break;
    }
    PebbleDictionary data = new PebbleDictionary();
    data.addString(ROOMBA_MESSAGE, stateStr);
    data.addInt16(ROOMBA_STATE, (short) pb_state);
    PebbleKit.sendDataToPebble(context,
            PebbleToRombaReceiver.PEBBLE_APP_UUID, data);
  }
}




Pour recevoir les messages de la Pebble, on utilise le même pricipe: un BroadcastReceiver reçoit les paquets envoyés par la Pebble. Une fois le paquet décodé, on envoie un Intent à notre service avec la commande qui correspond au bouton pressé.

public class PebbleToRombaReceiver extends BroadcastReceiver
{
  private static final String TAG = "PebbleToRombaReceiver";
  public final static UUID PEBBLE_APP_UUID =
          UUID.fromString("3173b512-fe9c-4be9-98c6-db18daee5076");
  private final static int ROOMBA_CMD = 0;
  private final static int CLEAN = 0;
  private final static int SPOT = 1;
  private final static int DOCK = 2;
  private final static int GET_STATE = 3;

  @Override
  public void onReceive(Context context, Intent intent)
  {

    final UUID receivedUuid =
          (UUID) intent.getSerializableExtra(Constants.APP_UUID);
    // Pebble-enabled apps are expected to be good citizens and
    // only inspect broadcasts containing their UUID
    if (!PEBBLE_APP_UUID.equals(receivedUuid)) {
        return;
    }
    final int transactionId = intent.getIntExtra(Constants.TRANSACTION_ID, -1);
    final String jsonData = intent.getStringExtra(Constants.MSG_DATA);
    if (jsonData == null || jsonData.isEmpty()) {
        return;
    }
    try {
        final PebbleDictionary data = PebbleDictionary.fromJson(jsonData);
        receiveData(context, transactionId, data);
    } catch (JSONException e) {
        e.printStackTrace();
    }
  }

  private void receiveData(Context context,
                            int transactionId,
                            PebbleDictionary data)
  {
    int buttonIndex = data.getInteger(ROOMBA_CMD).intValue();
    Intent intent = new Intent(context, RoombaService.class);
    switch (buttonIndex) {
        case CLEAN:
            intent.putExtra(RoombaService.INTENT_KEY_COMMAND,
                            RoombaService.CLEAN);
            break;
        case SPOT:
            intent.putExtra(RoombaService.INTENT_KEY_COMMAND,
                            RoombaService.SPOT);
            break;
        case DOCK:
            intent.putExtra(RoombaService.INTENT_KEY_COMMAND,
                            RoombaService.DOCK);
            break;
        case GET_STATE:
            intent.putExtra(RoombaService.INTENT_KEY_COMMAND,
                            RoombaService.GET_CURRENT_STATE);
            break;
        default:
            return;
    }
    context.startService(intent);
  }
}



Le fait d'avoir toute la complexité de l'application dans le service nous permet d'ajouter facilement une "deuxième interface" sur la Pebble Time avec seulement quelques lignes.

L'autre avantage est que toutes ces classes (les BroadcastReceivers et le service) sont complètement découplées de l'interface graphique de notre application, ce qui permet à l'application de traiter les événements envoyés par la Pebble sans stopper l'application courante. Par exemple, si le téléphone joue une vidéo, il est toujours possible de contrôler le Roomba à l'aide de sa montre sans perturber la vidéo.

Si on désirait ajouter une App Widgets ou permettre à une application tierce de contrôler le Roomba, on pourrait aussi réutiliser tel quel le service.

Conclusion


Pour conclure, l'API PebbleKit est très simple à prendre en main et il est facile d'ajouter le support Pebble à une application Android si on utilise un service.

  
Une petite démo



L'application Pebble Time qui est exécutée sur la montre est un peu plus compliquée (particulièrement si on a jamais fait de C.) Mais c'est compensé par une très bonne documentation et de nombreux exemples disponibles. Nous avons aussi été aussi conquis par leur environnement de développement CloudPebble qui fonctionne parfaitement et qui est très simple à utiliser. C'est incroyablement plus simple et efficace que ce qui était disponible lors de notre premier article.

Commenter un commentaire Retour au blog



1 - seb (Yocto-Team)Jeudi 25 juin 2015 10H06

Après une semaine d'utilisation de la Pebble Time, je suis conquis. La montre est plus confortable que l'ancienne version et l'autonomie est identique à l'ancien modèle (env. 6-7 jours). L’écran couleur permet de rendre l'interface plus lisible et plus jolie. Mais j'ai l’impression que la vitre est plus fragile, en une semaine j'ai déjà autant de rayures qu'en 3 ans avec ma vieille Pebble.

Yoctopuce, get your stuff connected.