Pytelemetry

Communication facile avec des Arduino et Mbed - écriture de variable à distance, affichage des données dans un graphe temps-réel et + encore

a marqué ce sujet comme résolu.

Pytelemetry

Pytelemetry vous permet de faciliter le développement d'applications embarqués, telles que Arduino, Mbed, etc.

Pytelemetry c'est :

  • Un protocole de communication tolérant aux erreurs, disponible en C et en Python
  • Une puissante interface en ligne de commande pour, depuis un pc, contrôler votre application embarquée, récupérer de la télémétrie dessus, l'afficher dans des graphes ouverts à la volée, et plus encore.

Architecture d'une application typique avec Pytelemetry

Un aperçu de la ligne de commande

Objectifs

Pytelemetry est fait pour vous si vous :

  • avez besoin de donner des ordres a distance à une application embarquée
  • avez besoin de configurer des paramètres à distance
  • avez besoin de récupérer des données depuis votre ordinateur
  • utilisez printf pour debugger (et ça commence gentiment à vous taper sur les nerfs de compiler et recompiler)
  • souhaitez affiner les réglages d'une application de manière rapide
  • visualiser à distance et en temps réel des données sur l'application embarquée

Pour tout ça, Pytelemetry est très efficace.

Par exemple, le code pour lire les données des trois canaux de l'accéléromètre et la valeur du slider capacitif de la carte NXP KL25Z prends moins de 40 lignes de code.

Et les données reçues côté ordinateur peuvent êtres affichées dans un graphe à l'aide d'une seule commande. Les données du slider sont envoyées sous le nom touch, d'où la commande (… attention, suspense)

1
:> plot touch

Plot (ouvert depuis la ligne de commande) temps réel du slider capacitif

Une voiture radiocommandée contrôlée a distance peut être codée en moins de 100 lignes de code.

Installation et Contribution

Par souci de modularité, le projet est divisé en trois dépôts github.

  • Le protocole de communication, implémenté en C (pour l'application embarquée), nommé Telemetry- dépôt
  • Le même protocole de communication, implémenté en Python (pour les applications PC), nommé Pytelemetry - dépôt - PyPI version
  • La ligne de commande Pytelemetrycli - dépot - PyPI version

Les packages python sont sur le registre officiel PyPi et peuvent donc s'installer, une fois python installé, en tapant une seule commande dans un terminal. Par exemple pour Pytelemetry

1
pip install pytelemetry

Il y a quelques prérequis pour l'installation de la ligne de commande, le README contient toutes les informations régulièrement mises à jour.

L'installation a été validée sous Windows et Mac OS, si une âme charitable souhaite tenter sous Linux j'en serais très reconnaissant ;)

Côté application embarquée, pour l'instant je supporte de manière officielle la plateforme ARM Mbed, ce qui signifie que Pytelemetry peut être utilisé pour l'instant sur 85 cartes différentes

Une petite préférence personnelle pour la NXP KL25Z qui coute 12$, avec un solide microcontrôleur ARM et une flopée de capteurs déjà sur la carte.

Le support des Arduino est prévu très prochainement, dés que j'aurais remis ma main sur une de ces bestioles.

Les instructions avec et sans Mbed sont sur le Wiki qui est encore en chantier mais va s'améliorer progressivement. Il est aussi possible de me contacter, pour cela lisez la suite.

Alternatives

Le cœur de l'application, la ligne de commande capable d'ouvrir des graphes et de communiquer avec l'application embarquée, n'a à ma connaissance, aucune alternative directe. Et ce n'est pas faute d'avoir cherché. La seule chose qui s'en rapproche, c'est l'outil Freemaster.

Les différences sont les suivantes :

  • Supporte uniquement les plateformes NXP
  • Pas open-source
  • Tres intrusif (les écritures de variables sont faites directement dans la memoire)
  • Limité a lire seulement 8 variables
  • Pas de support pour les structures de données complexes, telles que les tableaux, structures hiérarchiques

Pour le protocole de communication, eh bien, il est possible de trouver comme alternative tout protocole point-à-point de type PubSub. Par contre, il est je pense difficile de trouver une implémentation totalement testée, avec un système de build portable et pouvant être intégrée dans n'importe quelle nouvelle plateforme avec 4 lignes de code.

Bonus pour les lecteurs courageux

Si vous avez lu jusque là, bravo ! ;)

Pour les motivés qui veulent tester cet ensemble d'outils, ou contribuer au projet, vous pouvez me contacter sur le gitter du projet Join the chat at https://gitter.im/Overdrivr/pytelemetry ou observer l'avancement du développement sur le waffle.io. Voire vous-assigner un ticket et contribuer au code vous même !

Si jamais vous avez fait une démo sympa, n'hésitez surtout pas à m'en parler ou à l'ajouter vous même à la liste.

Happy coding !

Salut,

J'ai parcouru ta présentation, et je trouve qu'il manque des trucs importants :

  • techniquement, en quoi consiste (py)telemetry et comment fonctionne-il (où trouver + de détails sur l'architecture, le protocole) ?
  • quels sont les avantages / inconvénients par rapport à ce qui se fait déjà dans le même domaine ? (là, je vais peut-être dire une connerie, mais ton truc me fait un peu penser à MQTT pour l'aspect pub/sub)
  • y a t'il une innovation particulière ici ? si oui, en quoi consiste elle ?
+0 -0

Salut,

Merci pour ton retour. Les trois dépôts github contiennent pour l'instant plus ou moins les informations que tu recherches. Pour l'instant, le projet est jeune (il a moins de 2 moins), et écrire la documentation et les README me bouffe un temps considérable. Dans un futur proche, une documentation unique style readthedocs verra le jour, une fois que j'aurais corrigé quelques bugs qui trainent à droite et a gauche.

MQTT n'est pas un système point à point. MQTT est intéressant pour centraliser des données de diverses sources, et pouvant être lues par divers clients. Dans toute appli MQTT tu as un server central (je crois qu'ils appellent ca un broker), et des clients qui se connectent dessus. Et MQTT n'est pas temps réel, mais alors vraiment pas du tout. Et apparemment mettre en oeuvre un broker est un sacré boulot (a confirmer ça)

Et après en termes de protocoles, il y a Firmata, mais dont la philosophie est un peu différente. C'est un firmware qui reroute immédiatement sur le port de comm les IO de ta puce. Niveaux performances c'est réputé très mauvais, la communication est plutôt lente.

Pytelemetry, a l'origine, avait été développé pour debugger plus efficacement les voitures RC de la Frescale (désormais NXP) CUP.

C'est une compétition de voitures suiveuses de ligne sous steroids dont les meilleures vont très très vite. Dans ce genre d'application, si tu veux aller chercher la performance, sans un outil ultra performant pour débugger, c'est mort. Il y a énormément de paramètres à régler, et recompiler->tester->recompiler->etc et afficher des valeurs avec des printf ca ne marche pas.

Ensuite, pour les drones, ce genre de protocole de communication est taillé pour. Avant de le développer, j'avais bon espoir de pouvoir réutiliser le protocole utilisé sur les Crazyflie de Bitcraze, mais malheureusement le code était profondément intégré dans le code de leur drone, hors de question d'aller dépatouiller ça.

L'innovation ici, c'est de fournir un outil puissant et haute performance pour inspecter ton application embarquée, et de la contrôler à distance, avec un minimum de code.

Je sais qu'il existe des debuggers hors de prix, qui permettent d'afficher des variables du programme en temps réel. Je ne suis même pas sur qu'ils soient capables d'afficher des vecteurs de données (ce qui est le cas ici), et en aucun cas tu ne peux t'en servir pour créer une application finale.

Avec Pytelemetry, tu peux. Avec 80 lignes de python, et 80 lignes de C, tu peux contrôler depuis ton ordinateur une voiture radiocommandée en temps réel, de manière robuste, avec un code testé, clean et compréhensible.

Merci pour ta réponse, ça me permet de m'en poser aussi et de prendre du recul ;)

Je ne saisis pas très bien comment tu conçois un système pub/sub brokerless. Il y a bien une unité qui stocke les subscribers, et qui reçoit les message des publishers pour les dispatcher à qui de droit, non ?

Après avoir regardé un peu dans les présentations des dépôts C et python, si je comprend bien il y a uniquement deux machines en relation, et le pub/sub est simplement utilisé pour identifier les unités de traitement à invoquer lors de la réception d'un message. (d'ailleurs, autant je comprend ce type de code python tlm.subscribe(topic, callback), autant celui-ci en C me laisse perplexe subscribe(callback) : où est passé le topic ?).

Encore une question: les données sont-elles transmises telles quelles (quid des problèmes de portabilité binaire), ou bien as-tu une couche qui gère cet aspect ? Existe t'il une limitation sur ce qu'on peut transmettre (en termes de taille / structure) ?

Sinon, si je comprend bien, les objectifs principaux de ton projet sont la performance et la simplicité d'utilisation, c'est ça ? D'après ce que je comprend, l'utilisation qui en est faite est tout de même assez spécifique, du moins pour l'instant.

+0 -0

Non, pas de broker. Le Pub/Sub en soit n'en requiert pas. Il dicte juste que chaque trame appartient à un canal portant un nom(topic). Donc simplement, chaque trame envoyée que ce soit par le pc ou la carte embarquée contient en plus de sa charge utile (données) la trame a laquelle elle appartient, pour qu'elle puisse être identifiée.

Si j'ai adopté cette approche, c'est parce qu'elle permet une architecture plus clean et beaucoup + de découplage entre les deux éléments communiquants (je n'en suis pas à mon coup d'essai, si tu fouilles mes dépôts tu trouveras le premier essai que j'ai fait dessus, et qui n'était pas basé sur du PubSub).

Après avoir regardé un peu dans les présentations des dépôts C et python, si je comprend bien il y a uniquement deux machines en relation, et le pub/sub est simplement utilisé pour identifier les unités de traitement à invoquer lors de la réception d'un message.

Voila c'est ça. C'est une belle manière de l'exprimer, je m'en inspirerai à l'occasion.

Sur le protocole C, j'ai visé la portabilité. Or pour mettre en oeuvre un subscribe(topic, callback) ça implique derrière d'avoir un dictionnaire topic->callback. Or, pour implémenter un tel dictionnaire proprement, il est quasiment impératif d'avoir recours à l'allocation dynamique. Malheureusement, ça pose quelques soucis. Certaines plateformes embarquées n'ont par défaut pas d'allocateurs dynamique implémentés par défaut. L'allocation dynamique peut fragmenter rapidement le heap sur des systèmes embarqués avec peu de ram, pouvant mener à des bugs très subtils à détecter. Elle est également plus lente, mais bon ça encore passons dessus. Elle peut échouer, et la c'est toujours le même problème, que faire ? Il est globalement conseillé d'éviter de l'utiliser sauf vraiment nécessaire, donc pour une librairie, je considère qu'il est mieux de faire quelques sacrifices, et de ne pas forcer l'utilisateur à utiliser des choses qu'il ne veut pas forcément pas. Quite à gagner derrière en simplicité de code.

Donc, sur le protocole C, pour l'instant, tu ne peux souscrire qu'une seule fonction qui est appelée à chaque trame reçue. Ca changera peut-être dans le futur, ou alors je ferais une version dérivée plus complète pour les systèmes un peu plus performants. Voire déléguer à l'utilisateur de fournir une implémentation de dictionnaire, et telemetry travaillera en abstraction dessus comme je l'ai pour l'abstraction matérielle sur le transport de données.

Malgré tout, l'utilisateur y gagne un peu en flexibilité. Dans la situation actuelle, il est possible d'effectuer des traitements communs en fonctions de topics communs, ou alors en fonction des types de données reçues, ce qui n'est pas le cas avec un dictionnaire de callbacks.

La charge utile est encodée en little-endian, pour l'instant pas de surcouche (enfin, surcouche, ça sera juste quelques fonctions internes rien de bien méchant) mais c'est prévu dans un avenir proche. Disons que si des contributeurs intéressés se manifestent pour aider ça me permettra d'avancer plus vite sur ce point ;)

Sinon, si je comprend bien, les objectifs principaux de ton projet sont la performance et la simplicité d'utilisation, c'est ça ? D'après ce que je comprend, l'utilisation qui en est faite est tout de même assez spécifique, du moins pour l'instant.

Je ne suis pas sûr de comprendre ton point de vue. Il n'y a rien de spécifique. Les trois librairies utilisent des technos ultra-portables (Python, C, Gradle comme system de build configuré pour un toolchain GCC, avec en + un compilo C/C++ recommandé qui tourne sur Unix Mac et Windows). Sur une plateforme embarquée ou tu as une librairie UART décente, il faut littéralement 4 lignes de codes pour rajouter telemetry dessus. Et vu le nombre de projets utilisant Arduino ou Mbed et faisant appel à de la connectivité, le champ d'application est au contraire très, très vaste.

Je développe régulièrement des applications embarquées, et pas plus tard qu'il y a quelques heures je m'en suis servi pour débugger un problème lié à une lecture d'ADC. Je me suis connecté sur cette application et j'ai pu afficher immédiatement dans un graphique les différents paramètres et voir que quelque chose cloche. Rien que ça, pour du debug, ça fait gagner beaucoup de temps.

Je sens yoch que tu es quelqu'un de difficile à convaincre ;) Ce qui est une bonne chose. Mais si tu souhaites tester la libraire et m'apporter un avis supplémentaire en tant qu'utilisateur j'en serais très ravi. Je fournit même des firmwares Mbed de test pour ceux qui souhaitent tester l'interface en ligne de commande sans pour autant vouloir développer dessus.

Je ne m'y connais pas vraiment dans le domaine de l'embarqué, mais je trouve tes explications extrêmement intéressantes.

Par spécifique, je veux simplement dire que ça reste surtout lié au développement embarqué, et que ça ne sera pas forcément adapté à des situations qui demandent du pub/sub plus "classique" (dans un contexte domotique par exemple, on aimerait pouvoir connecter plusieurs clients). Mais le champ d'application n'est pas réduit pour autant.

Un dernier point sans grand rapport : je me demande si coder en C++ ne te pourrait pas te permettre d'éviter de recoder tes fonctions pour chaque type possible (grâce aux templates), et simplifier le code utilisateur par la même occasion. Quelque chose comme ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* je ne sais pas si c'est la bonne façon de faire, 
   mais je n'ai pas trouvé mieux */
inline TM_type typefor(uint8_t)         { return TM_uint8; }
inline TM_type typefor(uint16_t)        { return TM_uint16; }
inline TM_type typefor(uint32_t)        { return TM_uint32; }
inline TM_type typefor(int8_t)          { return TM_int8; }
inline TM_type typefor(int16_t)         { return TM_int16; }
inline TM_type typefor(int32_t)         { return TM_uint32; }
inline TM_type typefor(float)           { return TM_float32; }
inline TM_type typefor(const char*)     { return TM_string; }


template <typename T>
size_t emplace(TM_msg * m, T * dst)
{
    if(m->type != typefor(*dst))
        return 0;
    memcpy(dst, m->buffer, m->size);
    return 1;
}


template <typename T>  // pour tous les types de base supportés
void publish(const char * t, const T msg)
{
    frame(t, typefor(msg), &msg, sizeof(T));
}

template <>  // specialisation pour les chaines
void publish(const char * t, const char * msg)
{
    frame(t, TM_string, msg, strlen(msg));
}

+0 -0

D'accord, je comprends mieux ton point de vue.

En fait, pour l'instant je ne parle que du cas typique ou on communique avec un équipement embarqué.

Mais en fait, et c'est une autre raison et pas des moindres qui m'a fait choisir le PubSub, c'est qu'il n'y a pas de distinction maître/esclave, ou Serveur/Client. C'est le même code qui tourne sur les deux. Par exemple, il est tout a fait possible de faire communiquer deux arduino ensemble avec Telemetry (la version C du protocole), sans changer le code.

Il est également tout a fait possible de faire communiquer deux PC ensemble avec la version python du protocole. Bon, il serait surprenant de les faire se communiquer par RS232, mais si un transport HTTP était implémenté il n'y a strictement rien qui s'y oppose.

Apres, Il est également possible d'imaginer utiliser la bibliothèque sur un réseau maille tel que le zigbee. Mais il est clair que les protocoles zigbee ou autres sont plus adaptés. ;)

Un dernier point sans grand rapport : je me demande si coder en C++ ne te pourrait pas te permettre d'éviter de recoder tes fonctions pour chaque type possible (grâce aux templates), et simplifier le code utilisateur par la même occasion.

Si absolument, utiliser des fonctions template permettrait de réduire le code a écrire. Et même, avant d'en arriver la, ne serait-ce que pouvoir surcharger la fonction publish avec plusieurs signatures est quand même agréable en C++

1
2
void publish(const char * t, float data);
void publish(const cahr * t, uint8_t data);

alors qu'en C tu ne peux pas et tu es oblige de nommer la fonction différemment:

1
2
void publish_f32(const char * t, float data);
void publish_u8(const cahr * t, uint8_t data);

Ça, c'est un peu lourd pour l'utilisateur. Tiens, d'ailleurs je viens de me rendre compte que j'ai oublié de le faire sur l'interface C++ Mbed. :p

Un grand merci a toi yoch pour ta contribution.

Tu as passé un sacré coup de balai et j'ai appris pas mal de choses en faisant la revue.

Et ta participation m'a motivé a rajouter Appveyor pour faire de l’intégration continue, pour pouvoir tourner les tests dans un environnement vierge a chaque nouvelle Pull Request.

Pour ceux que ça intéresse, les builds tournent ici : https://ci.appveyor.com/project/Overdrivr/pytelemetry/history

(je commence déjà a en voir l’intérêt, des builds/tests qui passent sous python 3.5 échouent royalement et pour des trucs bêtes en python 3.3)

pytelemetry log désormais des informations sur les données reçues et envoyées, ce qui va me permettre d'inspecter des problèmes de performance étranges se produisant lorsque l'on se connecte au port série avec une mauvaise vitesse.

A terme, mon but est d'implémenter une fonction de replay/pas-a-pas pour pouvoir re-visualiser une session de communication avec le système embarqué. Ça va pas être de la tarte mais ça sera chouette pour des signaux qui évoluent très rapidement ;)

Good news, le développement et l'écriture de la doc se poursuit.

L'interface C++ fait désormais partie du dépôt. J'ai mis en place un système assez sympa avec Gradle pour automatiquement construire une distribution, que ce soit pour Mbed, Arduino, ou demain je ne sais quelle plateforme. Le script gradle récupère tous les fichiers nécessaires, les place dans un sous dossier dans dist/, compile le code et tourne les tests d'intégration pour s'assurer que tout fonctionne correctement. Ca va me faire gagner du temps par la suite.

Le wiki s'étoffe : https://github.com/Overdrivr/Telemetry/wiki

Et le nombre d'étoiles du dépôt github est passé de zéro à 8 ! Wouhou. Champagne ;)

Telemetry passe en release 1.0.2 qui rajoute notamment.... le support de Arduino !

Je l'ai testé sur un vieux duemilanove, n'ayant pas d'autres arduino sous la main je recherche des volontaires pour tester la librairie sur les arduino/genuino uno et mega. Si jamais vous êtes intéressés n'hésitez pas à vous manifester ;)

Après avoir bossé pas mal sur le protocole C/C++ je vais maintenant me reconcentrer sur le code de la ligne de commande qui commence a accumuler quelques bugs, objectif : améliorer les tests pour l'instant non existants sur celle-ci

Nouvelle release de Telemetry qui passe en 1.0.3 !

Parmi les nouveautés, des fonctions pour faciliter l'écriture à distance dans une variable :

1
2
3
4
float myVar;
void refresh(TM_state * state, TM_msg * msg) {
    update_f32(msg, "throttle", &myVar);
}

Cette fonction update va donc écrire dans la variable myVar la donnéee reçue depuis le PC si et seulement si la donnée a été envoyée sur le canal throttle !

Ca permet de simplifier et de réduire énormément le code dans la fonction de callback.

Côté PC, le protocole codé en Python est désormais capable de surveiller l'activité du port série, de détecter et comptabiliser les données corrompues, et la source de la corruption.

Cette fonctionnalité, bien que relativement simple à implémenter, est à ma connaissance unique et très intéressante. Elle permet par exemple d'analyser et de détecter depuis le PC divers problèmes sur la carte embarquée tels que des buffers d'écriture sous-dimensionnés ou un taux d'envoi de données trop important. Une commande dédiée est en cours d'écriture pour profiter de tout ça depuis la ligne de commande.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte