Il y a quelques semaines, nous avions réalisé un système qui permettait de mesurer le SAG d'une suspension de vélo, et on vous avait fait miroiter une amélioration du logiciel. Chose promise, chose due, : cette semaine, on a réalisé une version graphique de l'application. Et pour cela, on a utilisé la librairie Python Arcade.
On aime beaucoup le langage Python. C'est un langage simple, relativement efficace et qui fonctionne sur presque n'importe quel OS/plateforme. Bref, c'est notre langage de prédilection quand on doit réaliser une petite application.
Python a cependant une faiblesse, il n'est pas prévu pour réaliser des applications graphiques. Il existe quelques solutions comme Tkinter, mais on est loin d'une application native comme celles qu'on pourrait réaliser avec Visual Studio ou Xcode. Parmi ces solutions, il y en a une que l'on avait envie d'essayer : Python Arcade Library.
Comme son nom le laisse penser, "Python Arcade Library" est une libraire graphique prévue pour réaliser des jeux en 2D. C'est un mini-moteur de jeu qui permet les sprites, les animations, les collisions, etc. Mais ce qui est intéressant, c'est que cette libraire fonctionne sur Windows, Linux et macOS.
Nous avons donc voulu savoir si cette bibliothèque pouvait être utilisée pour développer de petites applications en Python.
Installation
Tout comme notre librairie, l'Arcade Library est disponible sur PyPi et son installation est super facile:
pip install arcade
Le code
On a regroupé tout le code qui interagit avec la librairie Yoctopuce dans une seule classe ApplicationConfig. La méthode setup initialise la librairie et fait une liste des Yocto-RangeFinder connectés sur les ports USB. Si l'accès au port USB ne fonctionne pas, on essaie de passer par VirtualHub (127.0.0.1).
La deuxième méthode, check_parameters, est appelée par l'application une fois que l'utilisateur a sélectionné quel Yocto-RangeFinder utiliser et qu'il a entré le débattement des suspensions.
Notez que les Yocto-RangeFinder sont configurés en mode haute précision et associés à un objet SuspStatus qui est un objet simple qui permet de calculer le SAG en fonction de la valeur courante (cf. "Régler ses suspensions avec un Yocto-RangeFinder"). L'utilisation de callback permet de récupérer la valeur mesurée de manière efficace (cf. "Comment lire efficacement un capteur") et de ne pas ralentir l'interface graphique.
def __init__(self):
self.url = "usb"
self.error = ""
self.all_rf = []
self.front_selection = "None"
self.rear_selection = "None"
self.front_rf = None
self.rear_rf = None
self.front_travel = 160
self.rear_travel = 75
self.front = SuspStatus(self.front_travel)
self.rear = SuspStatus(self.rear_travel)
def setup(self) -> None:
errmsg = YRefParam()
if YAPI.RegisterHub(self.url, errmsg) != YAPI.SUCCESS:
self.error = errmsg.value
self.url = "127.0.0.1"
if YAPI.RegisterHub(self.url, errmsg) != YAPI.SUCCESS:
return self.error
rf: YRangeFinder = YRangeFinder.FirstRangeFinder()
while rf is not None:
self.all_rf.append(rf.get_hardwareId())
if len(self.all_rf) == 0:
self.error = "No Yocto-RangeFinder detected on " + self.url
return self.error
return ""
def check_parameters(self, fr_hwid, fr_travel, rd_hwid], rd_travel):
self.front_travel = int(fr_travel)
self.rear_travel = int(rd_travel)
if fr_hwid == "None" and rd_hwid == "None":
return "You need to select at least one Yocto-RangeFinder device"
self.front = SuspStatus(self.front_travel)
self.rear = SuspStatus(self.rear_travel)
if fr_hwid != "None":
self.front_rf = YRangeFinder.FindRangeFinder(fr_hwid)
self.front_rf.set_rangeFinderMode(
YRangeFinder.RANGEFINDERMODE_HIGH_ACCURACY)
self.front_rf.set_userData(self.front)
self.front_rf.registerValueCallback(valueCallback)
if rd_hwid != "None":
self.rear_rf = YRangeFinder.FindRangeFinder(rd_hwid)
self.rear_rf.set_rangeFinderMode(
YRangeFinder.RANGEFINDERMODE_HIGH_ACCURACY)
self.rear_rf.set_userData(self.rear)
self.rear_rf.registerValueCallback(valueCallback)
return ""
L'interface
Pour l'interface, nous avons deux vues: ConfigurationView et AppView.
La première permet à l’utilisateur de choisir quel Yocto-RangeFinder utiliser et de définir le débattement de la fourche et de l’amortisseur.

Le panneau de configuration
Cette vue utilise un UIManager pour gérer les éléments interactifs, comme les menus déroulants (UIDropdown) et le champ de texte (UIInputText). Quand le bouton Continue est pressé, on appelle la méthode check_parameters que nous avons vue plus tôt et on bascule sur la vue AppView.
La vue AppView affiche le sprite du vélo au centre et quatre champs texte.

L'application
La mise à jour des champs texte qui affichent le SAG se fait dans la fonction on_update. Cette méthode est appelée automatiquement par la librairie Python Arcade à chaque rafraîchissement de l'écran. Il est donc important d'avoir un code efficace.
On appelle YAPI.HandleEvents() qui va appeler le callback que nous avons enregistré avec les dernières valeurs des Yocto-RangeFinder. Le callback va calculer le SAG et mettre à jour l'objet SuspStatus. Ensuite, on met à jour les deux champs texte avec le SAG que l'on vient de calculer.
YAPI.HandleEvents()
self.front_sag_text.text = self.param.front.get_sag_text()
self.rear_sag_text.text = self.param.rear.get_sag_text()
Le code de cette application a été ajouté au repository GitHub du premier article disponible, à l'adresse suivante: https://github.com/yoctopuce-examples/y_suspension_sag
Conclusion
Après avoir réalisé ce projet, que pensons-nous de la librairie Python Arcade ? Parmi les avantages, c'est que c'est assez facile et intuitif. La documentation est très bien faite et, en peu de temps, on arrive à faire ce dont on a envie. L'application fonctionne sur Windows, macOS et Linux. Enfin, en utilisant PyInstaller , il est possible de packager le projet complet en un seul fichier exécutable.
D'un autre côté, ce n'est pas vraiment possible de réaliser une grosse application en utilisant cette technique. D'une part, le code va devenir trop complexe pour être maintenable, et surtout, le look est très particulier et n'est pas modifiable.
Bref, cette librairie est très bien si vous devez réaliser rapidement une petite application pour un projet perso, mais pas beaucoup plus.
