Table des matières
Fichier Zip (1 par binôme) devant contenir le rapport au format pdf ET word ainsi que tous les fichiers sources du projet (100Mo maximum ) à déposer sur moodle jusqu'au 8 mai 2024 à 23h59 sur: https://moodle.iut-tlse3.fr/mod/assign/view.php?id=248570
Lien vers la page d'explication de la communication via socket: reseaulprob
Lien vers le cours de robotique BUT2 AII: https://bvdp.inetdoc.net/files/iut/robotiqueBUT2/cours_ROBOTIQUE_BVDP_2024.pdf
Présentation de la SAE Robotique et Intégration
SAE Tache robotique complexe: Saisie d'un bouchon et vissage sur une bouteille par un robot Staubli
- Programmation du robot en 2 étapes:
- Vissage en boucle ouverte: mouvement de pick & place puis hélicoïdal
- Commande complexe pour piloter l'articulation du poignet sur un débattement de plus d'un tour.
- Optimisation de temps de cycle en jouant sur les paramètres de la trajectoire
- Vissage en boucle fermée: Intégration de capteurs dans un contexte de Middleware simple:
- Un capteur de couple pour visser jusqu'au serrage de consigne
- Un capteur de vision pour localiser le bouchon à saisir
- Les capteurs réels sont tout d'abord simulés pour le développement en Programmation Hors Ligne
- Étalonnage géométrique du capteur de vision
- Communication à base de sockets UDP et TCP
- Conception 3D des éléments de la cellule avec OpenScad
- Cellule, préhenseur, support bouteille, champ de vision de la caméra
- Examen TP Openscad 2h pendant la SAE pour note de TP Robotique
- Développement de petites applications en Python pour enregistrer et afficher les trajectoires cartésiennes et articulaires du robot (qui se comporte alors comme un capteur)
Consignes pour l'évaluation du projet
Durant le projet, vous travaillerez en binôme, et serez normalement évalués conjointement, à moins que l'enseignant juge que le travail n'a pas été réparti équitablement. Vous devrez produire un rapport avec le code commenté (notamment par des messages affichés sur l'interface utilisateur) et des captures d'écran (Pour faire des captures d'écran avec Windows 10: SHIFT+WINDOWS+S ). Vous fournirez également une archive zip du projet contenant les informations de suivi de version GIT, qui permettront de suivre l'évolution de votre projet et l'aide apporté par l'enseignant. Vous veillerez à ne pas casser le matériel et dans le cas contraire, cela pourra être pris en compte dans la notation.
Canevas pour le rapport
Vous remettrez votre rapport (par binôme) au format de votre choix (word ou autre) ET en plus au format PDF via moodle dans le dépôt suivant: https://moodle.iut-tlse3.fr/mod/assign/view.php?id=248558
Vous indiquerez les éventuelles conditions particulières de réalisation de votre projet: (absences justifiées ou non, travail en monôme…) pour que j'en tienne compte dans la notation.
Dans la partie technique vous respecterez l'organisation suivante:
- Vissage en boucle ouverte: mouvement de pick & place puis hélicoïdal. Variables pour régler:
- position articulaire de repos
- position bouchon
- position goulot
- débattement angulaire
- pas de vis
- Respect des consignes pour les différents troncons
- Maintien de l'orientation du poignet pendant les mouvements verticaux
- Mouvement articulaire sur J6 pendant les movej entre les points d'approche
- trajectoire fluide
- Mesure du temps de cycle pour la position de référence du bouchon
- Vissage en boucle fermée: Intégration de capteurs dans un contexte de Middleware simple:
- Uutilisation de la donnée fournie par le capteur de couple (simulé/réel)
- Utilisation de la donnée fournie par le capteur de vision (simulé/réel)
- Conception 3D des éléments de la cellule avec OpenScad
- Cellule
- Préhenseur
- Support bouteille
- Champ de vision de la caméra
- Développement de petites applications en Python pour:
- Enregistrer et afficher les trajectoires cartésiennes et articulaires du robot (qui se comporte alors comme un capteur)
- Explication du role de chaque fichier
- Programmes VAL3
- Openscad
- STL
- Python
- Parties spécifiques que vous avez traitées
- Expliquer ce qui vous a plu/déplu dans la SAE, ce qui était trop facile/difficile, ce que vous auriez aimé pouvoir faire
Partie Staubli
SRS 2019, Problème des licences : 14 disponibles, puis 13 de plus
Informations sur la structure d'un programme VAL3 et sur les types utilisés
L'enseignant vous guidera au cours du projet dans la lecture de la documentation du langage VAL3 utilisé par SRS pour programmer le robot. Ouvrez cette documentation dans une autre fenêtre de navigateur pour la consulter pendant tout le TP, elle est disponible à l'adresse: https://bvdp.inetdoc.net/files/iut/staubli/documentations/Val3.PDF
Commande du robot en boucle ouverte (BO)
- Intégration des éléments dans la cellule du simulateur
- fichiers CAO fournis puis à remplacer par les vôtres au fur et à mesure
- Positions de saisie et de dépose du bouchon définies dans le repère World (pour tout le monde), pas besoin de frames
- Mouvement de pick & place
- Mouvement vissage (paramétrique: débattement angulaire et pas de vis)
- Tracé des trajectoires de vissage
- Pré-dévissage, à faire pendant le déplacement vers la verticale de la bouteille
- Gestion de débattement angulaire +-270° sur j6
- Tester avec une rotation de 400° pour le vissage
- Limitation de la commande en cartésien pour piloter le poignet: Modèle Géométrique Inverse + commande en articulaire
- Poses du goulot et du bouchon: degré de liberté en rotation autour de l'axe Z
- Utilisation de la gravité pour rattraper le jeu: lâcher le bouchon pour mettre en contact les pas de vis
- Mesure et optimisation des temps de cycle
- Apprentissage des points et essai sur le robot réel EN PRÉSENCE D'UN ENSEIGNANT UNIQUEMENT!
Commande du robot en boucle fermée (BF)
- Intégration de fonctions de communication via sockets réseau
- Condition d'arrêt pour le vissage: Capteur de couple
- Parallélisme de tâches
- La trajectoire complète de vissage est positionnée dans la file d'ordre, mais l’exécution est interrompue lorsque le couple de serrage est atteint
- Position de saisie du bouchon fournie par un capteur de vision
- Tests si les positions sont atteignables pour ne pas faire planter le programme
- Position de saisie
- Position d'approche
- Toutes les positions possibles dans la zone de saisie
Modélisation 3D avec Openscad
- Reproduire le robot et tous les éléments hors de SRS
- Importer les fichiers stl des parties du robot sans les articuler dans un premier temps: https://bvdp.inetdoc.net/files/iut/saerobotique/openscad_staubli/stl_staubli_tx260.zip
- Exporter vers SRS les éléments que vous aurez modélisés via le format stl
- Prise de cotes sur la scène réelle
- Cellule
- Champ de vision camera/éclairage
- Outil + préhenseur
- Bouchon
- Support bouteille
- Référencement des modèles par rapport au robot
- Compilation par script .BAT →génération des différents fichiers stl, exemple: ~/Bureau/impression3D/boitier_dralam
- Récupération des paramètres de DH via fonction VAL3 (attention aux indices)
- Positionner les repères DH sur les stl des morceaux de robot à importer
- Adapter le programme dh.scad vu en TD d'openscad: https://bvdp.inetdoc.net/wiki/doku.php?id=openscad
- Génération d'un simulateur de la cellule
- Pose du robot pilotée par variables j1 à j6
- Animation hors ligne par parcours d'un tableau de valeurs articulaires
- Animation en ligne par logiciel externe qui pilote la pose en écrivant dans un fichier .scad à inclure
Middleware et communication
- Architecture globale:
- Schéma général et IP/Numéros de ports/types de sockets/Numéros de slots
- Serveur Middleware maison fournit un service de stockage et de consultation des données capteurs
- Protocole très simple basé sur des chaines ASCII: format de requêtes et de réponses
- Principe de base: Un gros tableau de données de type flottant, enregistrables et consultable via leurs indices
- Numéro de projet assigné N par binôme de 1 à …
- Slots de données capteurs allant de 100xN à 100xN+99
- Vous pouvez simuler ce que vous voulez sans perturber les autres
- Slots de 1 à 99 réservés pour les capteurs réels
- Communication via socket UDP et TCP
- Données capteurs envoyées en UDP, sans garantie d'acheminement
- Consultation des données capteurs via TCP, plus robuste, et avec acquittement
- Utilisation de Wireshark pour analyser les échanges sur le réseau
- Applications Python émetteurs UDP pour simuler des capteurs
- Simple en ligne de commande
- Avec interface graphique QT5: slider et boutons
- Exemple de simulation du capteur de couple et du système de vision
- Communication en VAL3
- Variables chaines de caractères spéciales (type sio) associées à des ressources de communication
- Robot en tant que:
- Capteur: il informe sur sa position articulaire/cartésienne, son état (action en cours), la position d'objets manipulés etc…
- Consommateur: il récupère des informations utiles telles que le couple mesuré, la position du bouchon, un ordre de démarrage…
- Démo Middleware
- Capteur réels et simulés
- Viewer 2D et 3D
Outil de gestion de version GIT
Durant le projet, nous utiliserons un outils pour suivre les changements effectués sur les fichiers au fur et à mesure du développement. Ceci nous permettra éventuellement de revenir en arrière en cas de problème et vous montrera le code nécessaire pour chaque nouvelle fonctionnalité.
- Versioning:
- Principalement pour des fichiers de texte
- Commentaires temporels
- Reversion (réparer après casser)
- Horodatage et auteur
- Utilisation simple en local, avec un seul utilisateur et sur une seule branche
- Visualisation des différences entre version
- Structure des dossiers du projet: Tout dans un même dossier avec des sous dossiers
- P:\s3aii\s3aii“X”\“nom_prenom”\saerobotique2
- staubli
- python
- openscad
- .git créé par l'outils git, à ne pas effacer!
- En fin de projet, zipper tout le dossier, il contiendra .git et donc l'historique des versions
- Installation Git sur les postes
- Utilisation en ligne de commande depuis le dossier du projet
- Commandes de base:
- git add seulement des fichiers à suivre
- git commit -m“commentaire”
- fichier gitignore certains fichiers qui sont des résultats, des log
- git status
- Démonstration gitk
Configuration de GIT
Pour lister les paramètres de configurations de l'outils, copier-coller dans une console Windows:
git config --list --show-origin
Pour configurer votre identité, copier-coller dans une console Windows en adaptant:
git config --global user.name "Bertrand Vandeportaele" git config --global user.email secret@meninblack.jupiter
Consigne d'utilisation
Vous devrez d'abord faire ce qui est indiqué dans la partie “Récupération du projet SRS de départ et configuration” avant de faire ce qui est indiqué ici, mais les informations concernant GIT sont regroupées pour plus de lisibilité.
GIT sera utilisé depuis la console Windows (que vous garderez ouverte) en utilisant des copier-coller de commandes présentées ci dessous, que vous adapterez en fonction des besoins.
Vous pourrez, si vous le souhaitez, consulter les documentations: https://git-scm.com/book/fr/v2/D%C3%A9marrage-rapide-Param%C3%A9trage-%C3%A0-la-premi%C3%A8re-utilisation-de-Git https://www.atlassian.com/git/tutorials/gitk https://git-scm.com/download/gui/windows https://www.atlassian.com/fr/git/tutorials/undoing-changes
La commande git doit être lancée depuis un dossier contenant un dépôt (un ensemble de fichiers permettant le suivi). Dans notre cas ce dossier sera celui dans lequel vous aurez décompressé le projet SRS, pour aller dans ce dossier saisir à chaque début de séance (vous pourrez utiliser la touche Tabulation pour auto-compléter les noms de dossiers):
P: cd P:\s3aii\s3aii"X"\"nom_prenom"\saerobotique2
La première fois, il faut initialiser le dépôt en copiant-collant:
git init
et créer une version initiale stockant l'état des fichiers tels que vous les aurez récupéré. Habituellement on ne fait pas l'ajout de tous les fichiers mais comme ici VAL3 est un langage interprété, on peut suivre tous les fichiers. Cette commande sera à refaire si vous ajoutez de nouveaux fichiers au projet:
git add *
Ensuite, il faut enregistrer cette version en faisant un “commit”, associé à un commentaire qui vous permettra d'indiquer ce qu'il fait (voyez cela comme un commentaire temporel). La première fois, vous saisirez:
git commit -m"ajout de tous les fichiers en version initiale"
Les fois suivantes, vous saisirez:
git commit -a -m"Whaou! mon programme arrive à faire un truc super: l exercice 2!!!"
Vous utiliserez l'outils gitk (que vous garderez ouvert pendant toute la séance, en pressant “F5” pour rafraîchir l'affichage) pour visualiser les différentes version en copiant-collant:
gitk
En cas de problème, et après accord du professeur, pour ramener vos fichiers à l'état du dernier commit en annulant toutes les modifications locales non commitées, fermer SRS puis copier coller:
git reset --hard
Récupération du projet SRS de départ et configuration
Télécharger et décompresser le fichier: https://bvdp.inetdoc.net/files/iut/staubli/projets_SRS/Cellule_depart_staubli_vide.zip
Vous devez dézipper le dossier CELL_IUT0 afin qu'il soit dans le dossier P:\s3aii\s3aii“X”\“nom_prenom”\saerobotique2\staubli
Il est temps d'initialiser le dépot git tel que montré plus haut.
Ensuite, lancer le programme SRS en cliquant sur: Menu Démarrer→Tous les programmes→Staubli Robotic Suite 2019→Staubli Robotic Suite 2019.10.1
et ouvrir le projet.
Dans la vue 3D vous devez voir la cellule. Vous pouvez cliquer à gauche sur Cameras→Camera pour sélectionner la vue perspective telle que visible ci dessous:
Le contrôleur du robot est configuré dans le projet fourni, il faut maintenant créer une nouvelle application, pour cela:
- Choisir l'onglet Cellule dans la fenêtre de droite
- Cliquer sur la flèche à gauche du nom de la cellule pour faire apparaitre “Controller1 [s9…]”
- Cliquer droit sur “Controller1 [s9…]” puis cliquer gauche sur “Nouvelle application”
- Saisir un nom: “Projet” suivi sans espace d'un numéro sur 2 chiffres (complété à gauche avec des 0 si nécessaire) qui vous sera attribué en début de séance et que vous conserverez durant toutes les séances. Par exemple, le groupe numéro 3 devra utiliser “Projet03” et le groupe 16 devra utiliser “Projet16” . Il est important que vous ayez chacun un nom de projet différent pour pouvoir identifier votre programme une fois chargé sur le contrôleur du robot!
- Cliquer sur Ok
Il faut maintenant configurer l’outil du robot, pour cela:
- Choisir l'onglet Géométrie dans la fenêtre de droite
- Cliquer sur la flèche à gauche du nom de la cellule pour faire apparaitre “Controller1 [s9…]” puis faire de même pour votre Projet
- Cliquer droit sur “Flange” (qui signifie Bride en français, soit l'emplacement où fixer l’outil) puis cliquer gauche sur “Nouvelle donnée”
- Saisir un nom pour l’outil, ou laisser le nom par défaut (tTool) puis cliquer sur Ok
- Vérifier que tTool apparait maintenant en cliquant sur la flèche à coté de “flange[0]”
Édition du programme
- Cliquer sur l'onglet “Accueil” puis sur “Afficher la vue 3D”
- Choisir l'onglet Cellule dans la fenêtre de droite
- Cliquer sur la flèche à gauche du nom de la cellule pour faire apparaitre “Controller1 [s9…]” puis “Projet” puis double cliquer sur “start()” pour ouvrir la fenêtre d'édition du programme.
Afin d'organiser la disposition des sous fenêtres pour permettre de voir simultanément le programme et le simulateur 3D du robot, cliquer gauche longuement sur l'onglet Projet-start puis, tout en maintenant le clic déplacer la flèche vers le carré de droite qui vient d’apparaitre, relâcher alors le clic gauche. Vous devriez obtenir un affichage ressemblant à:
Navigation dans la vue 3D
La vue 3D utilise les mêmes principes que Catia pour naviguer:
- molette vers le haut pour dézoomer et vers le bas pour zoomer (ils sont tordus les mécanos…)
- clic molette maintenu pour déplacer la caméra
- clic molette + clic droit maintenus pour orienter la caméra
En fonction de la position de départ du mouvement de souris, la rotation obtenue n'est pas la même. Pour afficher le disque délimitant les zones de départ, il faut cliquer sur l’icône rotation en bas à droite à coté de zoom.
Il peut être nécessaire de désactiver l'affichage du plancher car il fait buguer l'application. Pour cela, cliquer droit sur la vue 3D, “réglages affichage” et décocher “afficher plancher”.
Le programme propose par défaut un certain nombres de vues (de face, de coté, etc…). Il peut être intéressant d'ajouter des caméras fixes dans l'environnement pour observer les mouvements du robot depuis des points de vues particuliers. Pour cela, configurer la vue à l'aide de la souris puis dans l'arbre à gauche de la vue 3D, cliquer droit sur la vue 3D et sélectionner Caméras→ajouter une camera pour la vue courante. Il suffira de double cliquer sur la caméra créée dans l'arbre pour revenir sur cette vue.
Dans certains cas l'affichage de trièdres est trop petit pour être visible. Pour changer leur taille, aller dans l'onglet Simulation→réglage affichage→taille des trièdres (en pixel).
Lorsqu'il est nécessaire de définir un repère à l'aide de clics de souris, l'ordre de clics des points définit l'orientation du 3° axe par la règle de la main droite (aussi appelée du tire-bouchon).
Modification de la configuration du robot
Pour modifier la configuration du robot, double cliquer sur Scène→Robot→Controller dans l'arbre, ce qui fait apparaître des disques permettant de contrôler chaque articulation. Cliquer gauche maintenu sur un des disque pour faire tourner l'articulation. Il est également possible de modifier la configuration du robot en cartésien en jouant sur le trièdre placé au niveau de la bride du robot. Cliquer droit sur le robot pour faire apparaître le menu et clic gauche sur “déplacement manuel” pour faire apparaître la fenêtre de configuration du robot qui permet un réglage fin et la visualisation des valeurs extrémales atteintes par chaque articulation et le pilotage manuel en cartésien:
L'affichage de l'espace de travail (righty ou lefty) est obtenu en sélectionnant Scène→Robot→Controller dans l'arbre, puis clic droit sur le robot et afficher l'espace de travail… Il s'agit du volume atteignable par le milieu de l'axe 5 du robot, ce sont juste 2 sphères calculées statiquement à partir du modèle du robot, mais elle permette de se faire un idée pour positionner les différents éléments dans la cellule.
Configuration de la cellule
Import du modèle 3D de la cellule
Récupérer le modèle: https://bvdp.inetdoc.net/files/iut/staubli/stl/cellule_simplifiee.wrl
- Importer le modèle dans le projet
- Onglet Modeleur→Ajouter un modèle CAO
- Chercher le fichier cellule_simplifiee.wrl à l'aide du bouton …
- Régler la résolution sur Standard et cocher l'option “Copier et insérer le fichier” et “importer comme un assemblage”
- Dans la vue 3D, dans Scène→Géométrie→cellule_simplifiee_1.wrl, clic droit puis “éditer la position”
Éditer la position absolue suivante:
63.3 -68.2 550.45 0 0 -90
Import du modèle de pince
Récupérer le modèle STL de la pince Schunk et du préhenseur de bouchon: https://bvdp.inetdoc.net/files/iut/staubli/stl/adaptateur_pince_bouchon_iut2021_open.stl
Le modèle téléchargé est déjà référencé correctement pour faciliter le positionnement du Handler (point saisisseur) et du Handle (base: point de saisie). La bride du robot (Flange) est un Handler en Val3, et nous allons connecter ce Handler au Handle de l'outils pince. Il est important de respecter scrupuleusement les étapes suivantes sinon l’outil ne sera pas correctement ajouté au robot!
L'image suivante montre le repère du handler du robot c'est à dire sa bride (Flange en anglais):
Pour ajouter la pince au robot:
- Si un outils est déjà attaché au handler du robot: Scene→Robot→Controleur→Base→Controleur click droit et Détacher
- masquer le robot→ cliquer droit sur l'arborescence de scène sur Controller et décocher “Afficher le robot”
- Onglet Modeleur→Ajouter un modèle CAO
- choisir Copier et insérer le fichier
- l'objet apparait dans l'arbre en tant que Géométrie, cliquer droit dessus et faire “nouvel outils”
- NE PAS déplacer l'outils (car il est normalement bien positionné)
- choisir dans l'arbre l’outil et son handle
- cliquer sur “Édition” sous la barre de menu
- cliquer droit sur handle et “Éditer la position”
- Régler tout à 0 dans la position absolue
- cliquer droit sur handler et “Éditer la position”
- Régler tout à 0 dans la position absolue sauf Z à 140.00
- Sortir du mode Édition
- Cliquer droit sur Tool1 dans l'arborescence, attacher à et choisir le handler du robot
- réactiver l'affichage du robot
Ensuite il faut associer l’outil à la variable tTool, pour cela:
- Sélectionner le Tool1 dans l'arborescence puis faire CTRL+C
- pour utiliser un outils de la géométrie graphique vers une variable val3, il faut clic gauche sur le handler de l’outil, CTRL+C et coller dans la ligne de la variable tTool[0] dans l'onglet données. Vous devriez obtenir un vecteur dont toutes les composantes sont à 0 à l’exception du champ Z à 140.00.
Ensuite, cliquer droit sur le handler de l’outil puis définir comme TCP (Tool Center Point= Centre De l’Outil) pour pouvoir déplacer l’outil.
Vous devriez voir la pince solidaire de la bride du robot, comme sur la figure suivante, dans laquelle le repère du haut est le handler du robot connecté au handle de la pince (sur la bride), alors que le repère du bas est le handler de la pince, situé au centre des mors:
Ajout des éléments dans la cellule
Pour ajouter un objet dans l'espace de travail du robot:
- Onglet Modeleur→Ajouter un modèle CAO
- Télécharger le fichier et aller le chercher dans l'arborescence avec le bouton “…”
- Choisir Copier et insérer le fichier
- L'objet apparait dans l'arbre en tant que Géométrie
Insérer dans l'espace de travail du robot:
- le support de bouteille: https://bvdp.inetdoc.net/files/iut/staubli/stl/support_bouteille_cristaline_lr.stl
Vous devez positionner les éléments pour obtenir une configuration proche de celle du robot réel (vous adapterez les positions plus tard en relevant avec le pendant la configuration cartésienne du robot lorsqu'il est en position de saisie et de dépose du bouchon). Vous devez obtenir un espace de travail tel que celui visible sur la figure suivante:
Configuration de l'outils
Dans le données, sélectionner la variable tTool de type tool, et double cliquer pour l'éditer. Vérifiez que vous avez bien comme changement de repère {0,0,140,0,0,0}, et que le gripper est bien réglé à FastIO\fOut1 . Saisir 1 pour les 2 champs suivants qui permettent de régler les durées de délais en seconde(s) pour que la pince s'ouvre et se ferme après que la commande lui soit envoyée.
Activation de la détection de collision
Pour activer la détection de collisions dans le simulateur, il faut griser le bouton Simulation→Collision + choisir les évènements qui déclenchent les collisions + activer la gestion des collisions dans chaque objet (Propriétés F4).
Ajout d'un sous programme d'affichage bien pratique
Dans la fenêtre cellule→Controller1→Projet…→
- clic droit et “Ajouter nouveau programme”
- Nom: logMsgBlock
- Sauver tout (icone à plusieurs disques)
- Télécharger le fichier suivant dans le dossier CELL_IUT0\Controller1\usr\usrapp\ en écrasant le fichier qui vient d'être créé par SRS
- logMsgBlock.pgx
<?xml version="1.0" encoding="utf-8"?> <Programs xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.staubli.com/robotics/VAL3/Program/2"> <Program name="logMsgBlock" access="public"> <Parameters xmlns="http://www.staubli.com/robotics/VAL3/Param/1"> <Parameter name="x_sString" type="string" xsi:type="element" /> <Parameter name="x_bBool" type="bool" xsi:type="element" /> </Parameters> <Code><![CDATA[begin //attend que le robot arrive à la position souhaitée si demandé if x_bBool==true waitEndMove() endIf //assure l'affichage du message while logMsg(x_sString)==false delay(0) endWhile end]]></Code> </Program> </Programs>
Dans la fenêtre cellule→Controller1→Projet…→
- cliquer droit sur Projet…
- cliquer gauche sur “recharger l'application”
- vous devriez voir apparaître le programme ainsi que ses paramètres
Démarche de développement et de simulation
Structure du programme
Vous veillerez à bien structurer votre programme en utilisant des sous programmes lorsque cela est judicieux. Au démarrage de votre programme, le fichier “start.pgx” et exécuté. Vous veillerez à informer l'utilisateur du pendant de l'état de votre programme à l'aide de d'appel de fonction d'affichage, afin d'être capable d'identifier facilement la partie du programme en cours d’exécution etc…
Nommage des variables
Vous veillerez à respecter les règles de nommages des variables en fonction de leurs types (voici ce que nous manipulerons dans le projet):
- variable bool (booléen) commençant par b
- variable frame (repère) commençant par f
- variable jointRx (configuration en articulaire) commençant par j
- variable pointRx (configuration en cartésien) commençant par p
- variable mdesc (descripteur de mouvement) commençant par m
- variable num (valeur numérique) commençant par n
- variable string (chaine de caractères) commençant par s
- variable tool (outils) commençant par t
- variable trsf (transformation) commençant par tr
- variable sio (ressource de communication) commençant par si
Ceci permettra notamment d'ajouter automatiquement une variable à partir du code en faisant clic droit sur la variable puis “ajouter donnée”. Le type de la variable est alors inféré d'après la convention de nommage.
Les noms des variables locales sont de plus préfixés par l_ à ajouter en tant que nouvelle variable locale (pas donnée) et les nom des paramètres sont préfixés par x_. Nous Utiliserons les variables locales pour les variables qui n'ont pas à être réglées depuis le pendant (par exemple les variables numériques de compteurs). Dans le projet, les variables locales n'apparaissent pas dans les “Données” mais dans “Cellule”→Controller1→Projet→start()→Variables locales.
Gestion du temps
Présentation au tableau de la gestion du temps par le contrôleur du robot.
Programmation des mouvements de saisie et de transport du bouchon
Nous allons mettre en place un mouvement semblable à celui présenté page 144 de la documentation pour saisir le bouchon et l'amener en face du goulot de la bouteille. Les fonctions de mouvement utilisables sont:
- movej(): mouvement en point à point, la trajectoire entre les points optimise la vitesse de déplacement en interpolant sur le coordonnées articulaires
- movel(): mouvement linéaire dans l'espace de travail
- movec(): mouvement circulaire dans l'espace de travail
Selon le descripteur de mouvement passé en paramètre à la fonction move, un lissage est possible, et celui-ci peut se faire en interpolant sur les coordonnées articulaires ou cartésiennes.
Simulation
La simulation peut se faire en mode “exécution continue” ou en mode “Debug”.
Mode "Debug"
Dans le mode “Debug”, il est possible de positionner des points d'arrêt dans le programme, d’exécuter des instructions en pas à pas, ou fonction par fonction, et d'espionner la valeurs de variables.
Presser la touche F9 pour activer ou désactiver un point d'arrêt sur une ligne d'un programme. Un point rouge apparaît à gauche de la ligne pour indiquer la présence du point d'arrêt.
Pour synchroniser l'affichage 3D avec le simulateur:
- Onglet “Simulation”, cliquer sur icone “PLAY” triangle bleu (Démarrer la synchro)
Pour lancer le Debug du programme
- Dans l'onglet Cellule, ouvrir le Controller1→Projet et cliquer gauche sur “start()” pour choisir le programme
- Onglet “Val3”, cliquer sur “Démarrer et Debugguer l'application” (CTRL+R,D). On doit alors observer une flêche jaune sur la ligne du premier point d'arrêt visité
Pour éxecuter en pas à pas:
- Onglet “Déboguer”, “Pas principal” (raccourci F10)
Observer dans la fenêtre “Emulator Controller” les affichages utilisateurs (au besoin cliquer sur l'onglet “User”)
Amener la souris sur le “?” de la fenêtre de l'émulateur pour voir les raccourcis clavier.
Arrivé à l’exécution de l'instruction WaitEndMove(), le simulateur semble bloqué, ceci est dû au fait que le robot est par défaut en mode “Hold”
- Dans la fenêtre “Emulator Controller”, il faut:
- Choisir le mode automatique en cliquant sur le bouton rond blanc jusqu'à ce que l'icone “auto” soit en bleu
- Mettre le robot sous puissance: cliquer sur le bouton rond avec un trait noir vertical pour le faire devenir vert
- cliquer sur l'onglet “Move” (qui devient “Hold”) (SHIFT+F7) pour autoriser le robot à bouger
Pour arrêter un Debug en cours et recommencer:
- Onglet “Deboguer” Arrêt du débogueur. (CTRL+D,S abbrégé de Débugguer Stop) .
- Onglet Val3, cliquer sur “Démarrer et Debugguer l'application” (CTRL+R,D abbrégé de Run Débugguer )
- Fenêtre “Emulator Controller”, cliquer sur “Move”
Mode "Exécution continue"
L’exécution en mode “exécution continue” est accessible depuis le bouton “Démarrer l'application” dans l'onglet “VAL3” ( raccourci (CTRL+R,X)).
A part cela, la synchronisation avec l'affichage 3D et l'émulateur de contrôleur se passent comme pour le mode “Debug”. Les points d'arrêt éventuellement présents dans le programme sont ignorés.
Mode automatique déporté
Il est également possible d'utiliser le mode automatique déporté (remote) pour simuler l'application. Dans ce mode, le bouton de mise en marche de la puissance du robot du pendant n'est pas utilisable. Pour débugger, dans le pendant virtuel, changer le mode en automatique déporté à l'aide du bouton blanc. Une tentative d'exécution de votre programme provoquera l'affichage d'une erreur “Demande de mise sous puissance du bras refusée: la baie est en mode déporté ou invalide”. Pour débloquer cette situation, il suffit d'appeler la fonction VAL3 enablePower() au début de votre programme. Grâce à cela vous n'aurez plus à agir sur le bouton “Move/Hold” et mise sous puissance du pendant virtuel.
Réglage manuel de la configuration du robot
Pour pouvoir configurer la pose du robot en cliquant sur la vue 3D il faut cliquer sur Onglet Simulation, icone “STOP” afin d’arrêter la synchronisation entre de simulateur et la vue 3D. Dans le cas contraire, c'est le programme qui pilote le robot.
Affichage d'informations pour l'utilisateur du robot
Vous veillerez à afficher avec call logMsgBlock(sString “…”,bBool block) ce que fait le robot après chaque commande move?() pour pouvoir le visualiser sur le pendant. Nous pourrions utiliser le mode debug pas à pas sur le pendant mais ce n'est pas pratique pour le test sur le robot réel. De plus ces commandes d'affichage permettent de documenter le code.
Les affichages sont faits dans l'onglet Journal/Info du pendant (ne sélectionner que Info et désélectionner Critique,Erreur et Attention). Pour afficher une valeur numérique, vous pourrez la convertir en chaîne et la concaténer à une autre chaîne en faisant par exemple:
call logMsgBlock("prise numéro:"+toString(".3",nprise),true)
Communication via Sockets
Intégration des capteurs
La communication via Socket proposée par SRS ne fait pas de distinction entre les numéros de ports local et distant. Il n'est donc pas possible de faire communiquer directement l'émulateur de robot tournant sur votre PC de développement avec une application émulant un capteur sur le même PC. Nous utiliserons donc une autre machine (dotée d'une adresse IP permettant de l'identifier) appelée “serveur de capteurs” qui sera en charge de centraliser les données issues des différents capteurs réels ou simulés. SRS pourra communiquer avec ce serveur car il dispose d'une adresse IP différente de celle de votre PC. De plus la lecture de données via socket depuis SRS fonctionne de manière bloquante avec timeout, donc le fait d'avoir le pc serveur qui puisse répondre à des requêtes même si le capteur n'a pas fourni de valeur permet par exemple d'indiquer qu'il n'y a pas de donnée disponible rapidement sans que le robot soit bloqué en attente du capteur.
Python 3.9.7 est installé sur les PC Windows. Vous devrez télécharger/modifier un programme python émulant un capteur à l'aide d'une interface graphique.
Dans ce projet, l'échange d'information entre le contrôleur du robot et son environnement (notamment des capteurs) est réalisé par réseau ethernet et wifi à l'aide des Sockets. Vous devez dans un premier temps configurer deux sockets dans votre projet SRS pour pouvoir dialoguer (et nous utiliserons ici deux types de sockets, qui seront illustrés en cours de réseau)
Création et configuration des sockets dans le projet
Socket UDP
- cliquer sur le contrôleur dans l'arborescence du projet
- cliquer sur l'onglet Accueil
- cliquer sur l'onglet IO Physiques
- clic droit sur “Sockets” puis gauche sur “Editer la carte”
- clic gauche sur “+”
- choisir “UDP”
- régler les paramètres et valider
- Nom: siSocketUDP
- Port: 10000
- Description: Emission etat robot
- Timeout: 1
- Fin de chaine: 13
- IP du serveur: 172.16.6.59
- créer une nouvelle donnée de type sio nommée siSocketUDP
Socket TCP
- cliquer sur le contrôleur dans l'arborescence du projet
- cliquer sur l'onglet Accueil
- cliquer sur l'onglet IO Physiques
- clic droit sur “Sockets” puis gauche sur “Editer la carte”
- clic gauche sur “+”
- choisir “TCP client”
- régler les paramètres et valider
- Nom: siSocketTCP
- Port: 30000
- Description: requete donnee capteur
- Timeout: 1
- Fin de chaine: 13
- IP du serveur: 172.16.6.59
- créer une nouvelle donnée de type sio nommée siSocketTCP
Association des variables Val3 sio aux Sockets
- Sauver tous les fichiers du projet en cliquant sur l'icône 2 disquettes en haut à gauche
- cliquer sur l'onglet Accueil
- cliquer sur l'onglet IO Physiques
- cliquer sur le petit “+” à gauche de “Sockets” pour faire apparaître le détail des sockets
- glisser/déposer chacune des variables sio pour l'associer au Socket correspondant (en déposant la variable dans la colonne VAL3 et en attendant que l'icone “Link with” apparaisse avant de relâcher le bouton (Si cela ne marche toujours pas après 10 secondes, changer de marque de robot ou de métier.)
Vous devriez obtenir un affichage identique à:
Programme python de simulation d'un capteur
Nous allons maintenant ajouter un capteur simulé (via un programme) afin que vous puissiez l'utiliser depuis votre PC et accéder à ces données depuis SRS.
Pour cela, enregistrer dans le dossier R:\s3aii\s3aii“X”\“nom_prenom”\saerobotique2\python le code du programme suivant (en faisant clic droit sur le nom puis enregistrer sous)
- simu_sensor_2_joystick_qt5.py
#!/usr/bin/python3 # -*- coding: utf-8 -* #Bertrand Vandeportaele 2022 import os import sys debug=True global numeroPremiereDonneeCapteur #TODO: donner une valeur à cette variable en fonction de votre numéro d'étudiant numeroPremiereDonneeCapteur=156 global destIP global portOut global etatBouton etatBouton=0 global slider1Value slider1Value=0 global slider2Value slider2Value=0 global led0Button ######################################## #https://stackoverflow.com/questions/1051254/check-if-python-package-is-installed #install automatically watchdog if not already installed import subprocess import sys reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) installed_packages = [r.decode().split('==')[0] for r in reqs.split()] if 'PyQt5' in installed_packages: if debug: print('PyQt5 pip package already installed') else: if debug: print('PyQt5 pip package missing, lets install it') import pip pip.main(['install','PyQt5']) ######################################## from PyQt5.QtGui import * from PyQt5.QtNetwork import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5 import * import re #pour découpage chaine comme sur https://stackoverflow.com/questions/2175080/sscanf-in-python ################################################################################ def close(): print('close') exit(0) ################################################################################ def commutTimer(): global timer print('commutTimer') if timer.isActive(): timer.stop() commutTimerButton.setText('start stream sensor') else: timer.start() commutTimerButton.setText('stop stream sensor') ################################################################################ def slider1ValueChanged( value ): #label.setText( str( value ) global numeroPremiereDonneeCapteur global labelSlider1Value global slider1Value slider1Value=value labelSlider1Value.setText( str( numeroPremiereDonneeCapteur+5)+" : {:6d}".format(slider1Value) ) #print('changed') ################################################################################ def slider2ValueChanged( value ): #label.setText( str( value ) global numeroPremiereDonneeCapteur global labelSlider2Value global slider2Value slider2Value=value labelSlider2Value.setText( str( numeroPremiereDonneeCapteur+6)+" : {:6d}".format(slider2Value) ) #print('changed') ################################################################################ def bouton0(): global numeroPremiereDonneeCapteur global labelButtonValue global led0Button global etatBouton etatBouton=(etatBouton+1)%2 if etatBouton==0: led0Button.setText('Activer la sortie TOR') else: led0Button.setText('Desactiver la sortie TOR') labelButtonValue.setText( str( numeroPremiereDonneeCapteur+2)+" : {:6d}".format(etatBouton) ) print('etatBouton: '+ str(etatBouton) +" \n"); ################################################################################ def sendUDP(i): global udpSocket global destIP global portOut DestIP = QHostAddress(destIP); global slider1Value global slider2Value global etatBouton global numeroPremiereDonneeCapteur # chaine=str(slider1Value)+ " " + str(slider2Value) #ajout @MAC bidon # chaine="00:00:00:00:00:00 "+str(slider1Value)+ " " + str(slider2Value) #ajout champs vides pour être compatible avec le joystick wifi #@MAC: BC:DD:C2:FE:6F:F0 num: 0 0 -> 529.0 , 1 -> 534.0 , 2 -> 0.0 , 3 -> 0.0 , 4 -> 0.0 , 5 -> -73.0 , 6 -> 63.0 , chaine="00:00:00:00:00:00 "+str(numeroPremiereDonneeCapteur)+" 0 0 "+str(etatBouton)+" 0 0 "+str(slider1Value)+ " " + str(slider2Value) #chaines bidons pour générer erreur de parsing sur le serveur # chaine="00:00:00:00:00:00 0 0 0 "+"687f"+" 0 0 "+str(slider1Value)+ " " + str(slider2Value) chaine=chaine+chr(13)+chr(10) print("la chaine envoyée vers ip: "+str(destIP)+":"+str(portOut) +" est: "+chaine) udpSocket.writeDatagram(chaine.encode('utf-8'),DestIP, portOut); ################################################################################ def timerUpdate(): sendUDP(0) ################################################################################ global led0Button #destIP='192.168.1.50' #pc simulateur #destIP='192.168.0.112' #pc simulateur #destIP='192.168.1.49' #pc serveur rapid connecté en filaire à réseau AIP #destIP='127.0.0.1' #pc serveur sur la boucle locale #destIP='192.168.3.5' #pc serveur rapid connecté en wifi à réseau AIP #destIP='192.168.3.4' #pc serveur rapid connecté en ethernet à réseau AIP #destIP='127.0.0.1' #ip locale #destIP='10.6.11.62' #un pc de la salle u2 IUT destIP='172.16.6.59' #rapid connecté sur gommette bleu portOut=10000 posx=100 posy=100 sizex=500 sizey=150 ################################################################################ app = QApplication(sys.argv) w=QDialog() statusLabel =QLabel('En attente de datagrammes UDP depuis le PIC32') commutTimerButton=QPushButton('stop stream sensor') quitButton = QPushButton('&Quit') led0Button = QPushButton('Activer la sortie TOR') udpSocket =QUdpSocket() udpSocket.bind(portOut, QUdpSocket.ShareAddress) quitButton.clicked.connect(close) commutTimerButton.clicked.connect(commutTimer) led0Button.clicked.connect(bouton0) #led1Button.clicked.connect(bouton1) #led2Button.clicked.connect(bouton2) #app.connect(quitButton,QtCore.SIGNAL('clicked()'), close) #app.connect(commutTimerButton, QtCore.SIGNAL('clicked()'), commutTimer) #app.connect(led0Button,QtCore.SIGNAL('clicked()'), bouton0) buttonLayout =QHBoxLayout() buttonLayout.addStretch(1) buttonLayout.addWidget(quitButton) buttonLayout.addWidget(commutTimerButton) buttonLayout.addStretch(1) global labelButtonValue labelButtonValue=QLabel( ) labelButtonValue.setGeometry(250, 50, 50, 35) #labelButtonValue.setText( "x: "+str(0 ) ) labelButtonValue.setText( str( numeroPremiereDonneeCapteur+2)+" : {:6d}".format(0) ) button1Layout =QHBoxLayout() button1Layout.addStretch(1) button1Layout.addWidget(labelButtonValue) button1Layout.addStretch(1) button1Layout.addWidget(led0Button) button1Layout.addStretch(1) ####################################################"" #http://koor.fr/Python/CodeSamplesQt/PyQtSlider.wp slider1 = QSlider(Qt.Horizontal); amplitude=1800; slider1.setMinimum(int(-amplitude/2)) slider1.setMaximum(int(amplitude/2)) slider1.setGeometry( 10, 10, 600, 40 ) slider1.valueChanged.connect(slider1ValueChanged ) slider1.setValue( 0 ) global labelSlider1Value labelSlider1Value=QLabel( ) labelSlider1Value.setGeometry(250, 50, 50, 35) #labelSlider1Value.setText( "x: "+str(0 ) ) labelSlider1Value.setText( str( numeroPremiereDonneeCapteur+5)+" : {:6d}".format(0) ) slider1Layout =QHBoxLayout() slider1Layout.addWidget(labelSlider1Value) slider1Layout.addWidget(slider1) #labelSlider1Value=QLabel( ) #labelSlider1Value.setGeometry(250, 50, 50, 35) #labelSlider1Value.setText( str( slider1.getValue() ) ) sliderLayout =QHBoxLayout() sliderLayout.addStretch(1) sliderLayout.addWidget(slider1) sliderLayout.addStretch(1) #sliderLayout.addWidget(labelSlider1Value) #sliderLayout.addStretch(1) slider2 = QSlider(Qt.Horizontal); slider2.setMinimum(int(-amplitude/2)) slider2.setMaximum(int(amplitude/2)) slider2.setGeometry( 10, 10, 600, 40 ) slider2.valueChanged.connect(slider2ValueChanged ) slider2.setValue( 0 ) global labelSlider2Value labelSlider2Value=QLabel( ) labelSlider2Value.setGeometry(250, 50, 50, 35) #labelSlider2Value.setText( "Y: "+str(0 ) ) labelSlider2Value.setText( str( numeroPremiereDonneeCapteur+6)+" : {:6d}".format(0) ) slider2Layout =QHBoxLayout() slider2Layout.addWidget(labelSlider2Value) slider2Layout.addWidget(slider2) sliderLayout.addWidget(slider2) sliderLayout.addStretch(1) mainLayout = QVBoxLayout() mainLayout.addLayout(buttonLayout) mainLayout.addLayout(button1Layout) mainLayout.addLayout(slider1Layout) mainLayout.addLayout(slider2Layout) mainLayout.addWidget(statusLabel) w.setLayout(mainLayout) chaine='@BVDP2022: Port Em '+str(portOut) +' vers IP: '+str(destIP) print(chaine) w.setWindowTitle(chaine) #timer adapted from example 1 of https://www.programcreek.com/python/example/52106/PyQt4.QtCore.QTimer timer = QtCore.QTimer() #app.connect(timer, QtCore.SIGNAL('timeout()'), timerUpdate) timer.timeout.connect(timerUpdate) timer.setInterval(100) timer.start() w.setGeometry(posx,posy,sizex,sizey) #donne le focus au bouton 1 led0Button.setDefault(True) w.show() app.exec_()
Sauver ce fichier sur le disque R:\ dans le dossier R:\s3aii\s3aii“X”\“nom_prenom”\saerobotique2\python\ Exécuter une première fois ce programme pour télécharger les librairies en copiant collant dans une console:
R: cd R:\s3aii\s3aii"X"\"nom_prenom"\saerobotique2\python python simu_sensor_2_joystick_qt5.py
Editer ce fichier avec idle pour l'adapter selon les consignes données en séance en modifiant la variable numeroPremiereDonneeCapteur au début du programme pour qu'elle vaille 100x le numéro du projet qui a été attribué à votre binome, en tapant dans la commande windows:
C:\"Program Files"\Python39\Lib\idlelib\idle simu_sensor_2_joystick_qt5.py
Après édition, sauver le fichier et quitter idle puis lancer ce programme en faisant Touche Windows+R puis taper “cmd” et entrée, puis saisir en adaptant les lignes suivantes:
R: cd R:\s3aii\s3aii"X"\"nom_prenom"\python py simu_sensor_2_joystick_qt5.py
Si besoin, à la première utilisation, exécuter deux fois la dernière commande puis ignorer les messages de pare feu!
Agir sur les curseurs et bouton de l'application et observé dans la fenêtre de texte les chaines de caractères émises via Socket.
Le robot en tant que capteur
Nous allons maintenant faire en sorte que le robot se comporte comme un capteur qui informe de son état (articulaire et cartésien). Pour cela, il va envoyer via socket UDP des chaines de caractères comme le fait l'application python. Ceci va être réalisé par une tâche périodique en VAL3 qui envoie en UDP la pose courante du robot T,R, et joint à intervalle régulier dans un programme qui s’exécute en parallèle du programme start.
Présentation au tableau de la gestion de l'émission de l'état du robot via socket UDP.
La variable socket est normalement déjà configurée, mais il est possible la configurer au début du programme à l'aide de:
- configsocketUDP.pgx
//configuration du socket UDP clearBuffer(siSocketUDP) if(sioCtrl(siSocketUDP, "target", "172.16.6.59")!=0) call logMsgBlock("probleme sioCtrl(siSocketUDP, target, 172.16.6.59)", true) endIf //"endOfString" num (Pour ligne série, client et serveur UDP) Code ASCII pour le caractère de fin de chaîne à utiliser avec les opérateurs '=' (dans la plage [0, 255]) if(sioCtrl(siSocketUDP, "endOfString", 13)!=0) call logMsgBlock("probleme sioCtrl(siSocketUDP, endOfString, 13)",true) endIf //port 10000 utilisé par socket UDP if(sioCtrl(siSocketUDP, "port", 10000)!=0) call logMsgBlock("probleme sioCtrl(siSocketUDP, port, 10000)",true) endIf //0 pour configurer les lectures sur socket bloquante, //une autre valeur en seconde sinon if(sioCtrl(siSocketUDP, "timeout",1)!=0 ) call logMsgBlock("probleme sioCtrl(siSocketUDP, timeout,1)",true) endIf
La programmation de la tache périodique doit être faite une fois dans le programme start avec:
- ajouttacheperiodique.pgx
//programmation de la tache périodique //emissionEtat est un programme créé, et exécuté à une périodicité de 0.1s taskCreateSync "emissionEtat",.1,bOverRun,emissionEtat() call logMsgBlock("Tache periodique emission Etat lancee",true)
Ajouter la variable bOverRun en tant que nouvelle donnée.
Ensuite, ajouter un programme dans le projet: Cellule→Projet→clic droit→Ajouter→Nouveau programme. Saisir le nom “emissionEtat”
puis y copier le code suivant:
- emissionEtat.pgx
begin //il faut une boucle car taskCreateSync ne réappelle pas le programme, juste il lui donne la main périodiquement while true l_pHereEtat=here(tTool,world) l_jHereEtat=herej() //déclarer une variable string et utiliser la fonction tostring pour formater //adresse mac puis numéro de la première donnée capteur émise l_sEtat="DC:4F:22:00:00:00 50" l_sEtat=l_sEtat+" "+toString(".6",l_pHereEtat[0].trsf.x) l_sEtat=l_sEtat+" "+toString(".6",l_pHereEtat[0].trsf.y) l_sEtat=l_sEtat+" "+toString(".6",l_pHereEtat[0].trsf.z) l_sEtat=l_sEtat+" "+toString(".6",l_pHereEtat[0].trsf.rx) l_sEtat=l_sEtat+" "+toString(".6",l_pHereEtat[0].trsf.ry) l_sEtat=l_sEtat+" "+toString(".6",l_pHereEtat[0].trsf.rz) //l_sEtat=l_sEtat+" "+toString(".6",l_pHereEtat[0].config.shoulder) //shoulder n'est pas un numero, si je veux l'envoyer il faut faire une correspondante avec numéros siSocketUDP=l_sEtat //envoi en 2 temps de la config du robot, car sinon la chaine est tronquée car trop longue (>128 octets) //envoyer les paramètres suivants avec un offset par rapport aux précédents //adresse mac puis numéro de la première donnée capteur émise l_sEtat="DC:4F:22:00:00:00 56" l_sEtat=l_sEtat+" "+toString(".6",l_jHereEtat[0].j1) l_sEtat=l_sEtat+" "+toString(".6",l_jHereEtat[0].j2) l_sEtat=l_sEtat+" "+toString(".6",l_jHereEtat[0].j3) l_sEtat=l_sEtat+" "+toString(".6",l_jHereEtat[0].j4) l_sEtat=l_sEtat+" "+toString(".6",l_jHereEtat[0].j5) l_sEtat=l_sEtat+" "+toString(".6",l_jHereEtat[0].j6) siSocketUDP=l_sEtat //en cas de doute décommenter la ligne suivante pour afficher des messages indiquant l'exécution de la tache periodique //popUpMsg("emissionEtat") //Il faut un delay de la periodicité de la tâche sinon les données s'accumulent dans la variable l_sEtat et le controleur plante delay(0.1) endWhile end
Remplacer les valeurs 50 et 56 de ce programme par les valeurs de numeroProjet*100+10 et numeroProjet*100+16, par exemple 910 et 916 pour le projet numéro 9.
Ajouter les variables nécessaires dans votre projet en tant que variables LOCALES pour que ce nouveau programme puisse fonctionner.
Tester le programme et vérifier avec l'enseignant que l'état de votre robot est bien envoyé au serveur.
Programme python qui simule le robot en tant que capteur
- simu_sensor_2_staubli.py
#!/usr/bin/python3 # -*- coding: utf-8 -* #Bertrand Vandeportaele 2019 import os import sys debug=True ######################################## #https://stackoverflow.com/questions/1051254/check-if-python-package-is-installed #install automatically watchdog if not already installed import subprocess import sys reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) installed_packages = [r.decode().split('==')[0] for r in reqs.split()] if 'PyQt5' in installed_packages: if debug: print('PyQt5 pip package already installed') else: if debug: print('PyQt5 pip package missing, lets install it') import pip pip.main(['install','PyQt5']) ''' Could not find a version that satisfies the requirement PyQt5-Qt5>=5.15.2 (from PyQt5) (from versions: )[0m [31mNo matching distribution found for PyQt5-Qt5>=5.15.2 (from PyQt5)[0m [33mYou are using pip version 9.0.1, however version 21.3.1 is available. You should consider upgrading via the 'pip install --upgrade pip' command.[0m Traceback (most recent call last): File "/home/bvandepo/Bureau/pythonb/staubli/simu_sensor_2_qt5.py", line 32, in <module> from PyQt5.QtGui import * ModuleNotFoundError: No module named 'PyQt5' ''' ######################################## #from PyQt5 import QtCore, QtGui, QtWidgets ''' from PyQt5.QtGui import * from PyQt5.QtNetwork import * from PyQt5.QtCore import * from PyQt5 import * from PyQt5.QtWidgets import * ''' from PyQt5.QtGui import * from PyQt5.QtNetwork import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5 import * import re #pour découpage chaine comme sur https://stackoverflow.com/questions/2175080/sscanf-in-python global numeroPremiereDonneeCapteur #TODO: donner une valeur à cette variable en fonction de votre numéro d'étudiant numeroPremiereDonneeCapteur=56 global etatBouton etatBouton=0 global slider1Value slider1Value=0 global slider2Value slider2Value=0 global slider3Value slider3Value=0 global slider4Value slider4Value=0 global slider5Value slider5Value=0 global slider6Value slider6Value=0 global led0Button ################################################################################ def close(): print('close') exit(0) ################################################################################ def commutTimer(): global timer print('commutTimer') if timer.isActive(): timer.stop() commutTimerButton.setText('start stream sensor') else: timer.start() commutTimerButton.setText('stop stream sensor') ################################################################################ def slider1ValueChanged( value ): #label.setText( str( value ) global numeroPremiereDonneeCapteur global labelSlider1Value global slider1Value slider1Value=value #labelSlider1Value.setText( "J1: "+str(slider1Value) ) #https://mkaz.blog/code/python-string-format-cookbook/ labelSlider1Value.setText( str( numeroPremiereDonneeCapteur+0)+ " -> J1: {:6d}".format(slider1Value) ) print('changed') ################################################################################ def slider2ValueChanged( value ): #label.setText( str( value ) global numeroPremiereDonneeCapteur global labelSlider2Value global slider2Value slider2Value=value labelSlider2Value.setText( str( numeroPremiereDonneeCapteur+1)+ " -> J2: {:6d}".format(slider2Value) ) print('changed') ################################################################################ def slider3ValueChanged( value ): #label.setText( str( value ) global numeroPremiereDonneeCapteur global labelSlider3Value global slider3Value slider3Value=value labelSlider3Value.setText( str( numeroPremiereDonneeCapteur+2)+ " -> J3: {:6d}".format(slider3Value) ) print('changed') ################################################################################ def slider4ValueChanged( value ): #label.setText( str( value ) global numeroPremiereDonneeCapteur global labelSlider4Value global slider4Value slider4Value=value labelSlider4Value.setText( str( numeroPremiereDonneeCapteur+3)+ " -> J4: {:6d}".format(slider4Value) ) print('changed') ################################################################################ def slider5ValueChanged( value ): #label.setText( str( value ) global numeroPremiereDonneeCapteur global labelSlider5Value global slider5Value slider5Value=value labelSlider5Value.setText( str( numeroPremiereDonneeCapteur+4)+ " -> J5: {:6d}".format(slider5Value) ) print('changed') ################################################################################ def slider6ValueChanged( value ): #label.setText( str( value ) global numeroPremiereDonneeCapteur global labelSlider6Value global slider6Value slider6Value=value labelSlider6Value.setText( str( numeroPremiereDonneeCapteur+5)+ " -> J6: {:6d}".format(slider6Value) ) print('changed') ################################################################################ def bouton0(): global numeroPremiereDonneeCapteur global led0Button global etatBouton global labelButtonValue etatBouton=(etatBouton+1)%2 if etatBouton==0: led0Button.setText('Activer la sortie TOR') else: led0Button.setText('Desactiver la sortie TOR') labelButtonValue.setText( str( numeroPremiereDonneeCapteur+6)+" : {:6d}".format(etatBouton) ) print('etatBouton: '+ str(etatBouton) +" \n"); # led0Button.setText('Appuyer sur le bouton') # else: # led0Button.setText('Relacher le bouton') ################################################################################ def boutonReset(): print('reset: \n'); global slider1 global slider2 global slider3 global slider4 global slider5 global slider6 slider1.setValue(0); slider2.setValue(0); slider3.setValue(0); slider4.setValue(0); slider5.setValue(0); slider6.setValue(0); ################################################################################ def sendUDP(i): global numeroPremiereDonneeCapteur global udpSocket global destIP global portOut DestIP = QHostAddress(destIP); global slider1Value global slider2Value global slider3Value global slider4Value global slider5Value global slider6Value global etatBouton # chaine=str(slider1Value)+ " " + str(slider2Value) #ajout @MAC bidon # chaine="00:00:00:00:00:00 "+str(slider1Value)+ " " + str(slider2Value) #ajout champs vides pour être compatible avec le joystick wifi #@MAC: BC:DD:C2:FE:6F:F0 num: 0 0 -> 529.0 , 1 -> 534.0 , 2 -> 0.0 , 3 -> 0.0 , 4 -> 0.0 , 5 -> -73.0 , 6 -> 63.0 , chaine="00:00:00:00:00:00 "+str(numeroPremiereDonneeCapteur)+ " " +str(slider1Value)+ " " + str(slider2Value) + " " + str(slider3Value) + " " + str(slider4Value) + " " + str(slider5Value) + " " + str(slider6Value)+" " +str(etatBouton) #chaines bidons pour générer erreur de parsing sur le serveur # chaine="00:00:00:00:00:00 0 0 0 "+"687f"+" 0 0 "+str(slider1Value)+ " " + str(slider2Value) chaine=chaine+chr(13)+chr(10) print("la chaine envoyée est: "+chaine) udpSocket.writeDatagram(chaine.encode('utf-8'),DestIP, portOut); ################################################################################ def timerUpdate(): sendUDP(0) ################################################################################ global led0Button #destIP='192.168.1.50' #pc simulateur #destIP='192.168.0.112' #pc simulateur #destIP='192.168.1.49' #pc serveur rapid connecté en filaire à réseau AIP #destIP='127.0.0.1' #pc serveur sur la boucle locale destIP='172.16.6.59' #pc serveur mac oldi connecté en filaire à vlan BLEU IUT portOut=10000 posx=10 posy=10 sizex=500 sizey=150 ################################################################################ #par défaut: numeroPremiereDonneeCapteur= 100 #option -n pour choisir numeroPremiereDonneeCapteur if len(sys.argv)==3: if sys.argv[1]=='-n': numeroPremiereDonneeCapteur= int(sys.argv[2]) print("réglage de numeroPremiereDonneeCapteur à: ",str(numeroPremiereDonneeCapteur)); app = QApplication(sys.argv) w=QWidget(None) #QDialog() statusLabel =QLabel('En attente de datagrammes UDP depuis le PIC32') commutTimerButton=QPushButton('stop stream sensor') quitButton = QPushButton('&Quit') led0Button = QPushButton('Activer la sortie TOR') resetButton = QPushButton('reset axes') udpSocket =QUdpSocket() udpSocket.bind(portOut, QUdpSocket.ShareAddress) quitButton.clicked.connect(close) commutTimerButton.clicked.connect(commutTimer) led0Button.clicked.connect(bouton0) resetButton.clicked.connect(boutonReset) ''' app.connect(quitButton,QtCore.SIGNAL('clicked()'), close) app.connect(commutTimerButton, QtCore.SIGNAL('clicked()'), commutTimer) app.connect(led0Button,QtCore.SIGNAL('clicked()'), bouton0) ''' buttonLayout =QHBoxLayout() buttonLayout.addStretch(1) buttonLayout.addWidget(quitButton) buttonLayout.addWidget(commutTimerButton) buttonLayout.addWidget(resetButton) buttonLayout.addStretch(1) #button1Layout =QHBoxLayout() #button1Layout.addStretch(1) #button1Layout.addWidget(led0Button) #button1Layout.addStretch(1) global labelButtonValue labelButtonValue=QLabel( ) labelButtonValue.setGeometry(250, 50, 50, 35) #labelButtonValue.setText( "x: "+str(0 ) ) labelButtonValue.setText( str( numeroPremiereDonneeCapteur+6)+" : {:6d}".format(0) ) button1Layout =QHBoxLayout() button1Layout.addStretch(1) button1Layout.addWidget(labelButtonValue) button1Layout.addStretch(1) button1Layout.addWidget(led0Button) button1Layout.addStretch(1) #http://koor.fr/Python/CodeSamplesQt/PyQtSlider.wp ################################### global slider1 slider1 = QSlider(Qt.Horizontal); slider1.setMinimum(-160) slider1.setMaximum(160) slider1.setGeometry( 10, 10, 600, 40 ) slider1.valueChanged.connect(slider1ValueChanged ) global labelSlider1Value labelSlider1Value=QLabel( ) labelSlider1Value.setGeometry(250, 50, 50, 35) labelSlider1Value.setText( str( numeroPremiereDonneeCapteur+0)+ " -> J1: "+str(0 ) ) slider1Layout =QHBoxLayout() slider1Layout.addWidget(labelSlider1Value) slider1Layout.addWidget(slider1) slider1.setValue( 0 ) ################################### global slider2 slider2 = QSlider(Qt.Horizontal); #slider2.setMinimum(-127.5) #slider2.setMaximum(127.5) #nouvelle version supporte uniquement les entiers slider2.setMinimum(-128) slider2.setMaximum(128) slider2.setGeometry( 10, 10, 600, 40 ) slider2.valueChanged.connect(slider2ValueChanged ) global labelSlider2Value labelSlider2Value=QLabel( ) labelSlider2Value.setGeometry(250, 50, 50, 35) labelSlider2Value.setText( str( numeroPremiereDonneeCapteur+1)+ " -> J2: "+str(0 ) ) slider2Layout =QHBoxLayout() slider2Layout.addWidget(labelSlider2Value) slider2Layout.addWidget(slider2) slider2.setValue( 0 ) ################################### global slider3 slider3 = QSlider(Qt.Horizontal); #slider3.setMinimum(-134.5) #slider3.setMaximum(134.5) #nouvelle version supporte uniquement les entiers slider3.setMinimum(-135) slider3.setMaximum(135) slider3.setGeometry( 10, 10, 600, 40 ) slider3.valueChanged.connect(slider3ValueChanged ) global labelSlider3Value labelSlider3Value=QLabel( ) labelSlider3Value.setGeometry(250, 50, 50, 35) labelSlider3Value.setText( str( numeroPremiereDonneeCapteur+2)+ " -> J3: "+str(0 ) ) slider3Layout =QHBoxLayout() slider3Layout.addWidget(labelSlider3Value) slider3Layout.addWidget(slider3) slider3.setValue( 0 ) ################################### global slider4 slider4 = QSlider(Qt.Horizontal); slider4.setMinimum(-270) slider4.setMaximum(270) slider4.setGeometry( 10, 10, 600, 40 ) slider4.valueChanged.connect(slider4ValueChanged ) global labelSlider4Value labelSlider4Value=QLabel( ) labelSlider4Value.setGeometry(250, 50, 50, 35) labelSlider4Value.setText( str( numeroPremiereDonneeCapteur+3)+ " -> J4: "+str(0 ) ) slider4Layout =QHBoxLayout() slider4Layout.addWidget(labelSlider4Value) slider4Layout.addWidget(slider4) slider4.setValue( 0 ) ################################### global slider5 slider5 = QSlider(Qt.Horizontal); #slider5.setMinimum(-109.5) #slider5.setMaximum(+120.5) #nouvelle version supporte uniquement les entiers slider5.setMinimum(-110) slider5.setMaximum(+121) slider5.setGeometry( 10, 10, 600, 40 ) slider5.valueChanged.connect(slider5ValueChanged ) global labelSlider5Value labelSlider5Value=QLabel( ) labelSlider5Value.setGeometry(250, 50, 50, 35) labelSlider5Value.setText( str( numeroPremiereDonneeCapteur+4)+ " -> J5: "+str(0 ) ) slider5Layout =QHBoxLayout() slider5Layout.addWidget(labelSlider5Value) slider5Layout.addWidget(slider5) slider5.setValue( 0 ) ################################### global slider6 slider6 = QSlider(Qt.Horizontal); slider6.setMinimum(-270) slider6.setMaximum(270) slider6.setGeometry( 10, 10, 600, 40 ) slider6.valueChanged.connect(slider6ValueChanged ) global labelSlider6Value labelSlider6Value=QLabel( ) labelSlider6Value.setGeometry(250, 50, 50, 35) labelSlider6Value.setText( str( numeroPremiereDonneeCapteur+5)+ " -> J6: "+str(0 ) ) slider6Layout =QHBoxLayout() slider6Layout.addWidget(labelSlider6Value) slider6Layout.addWidget(slider6) slider6.setValue( 0 ) ################################### sliderLayout =QVBoxLayout() sliderLayout.addStretch(1) sliderLayout.addLayout(slider1Layout) sliderLayout.addStretch(1) sliderLayout.addLayout(slider2Layout) sliderLayout.addStretch(1) sliderLayout.addLayout(slider3Layout) sliderLayout.addStretch(1) sliderLayout.addLayout(slider4Layout) sliderLayout.addStretch(1) sliderLayout.addLayout(slider5Layout) sliderLayout.addStretch(1) sliderLayout.addLayout(slider6Layout) sliderLayout.addStretch(1) mainLayout = QVBoxLayout() mainLayout.addLayout(buttonLayout) mainLayout.addLayout(sliderLayout) mainLayout.addLayout(button1Layout) mainLayout.addWidget(statusLabel) w.setLayout(mainLayout) chaine='@BVDP2021: Port Em '+str(portOut) +' vers IP: '+str(destIP) w.setWindowTitle(chaine) #timer adapted from example 1 of https://www.programcreek.com/python/example/52106/PyQt4.QtCore.QTimer timer = QtCore.QTimer() #app.connect(timer, QtCore.SIGNAL('timeout()'), timerUpdate) timer.timeout.connect(timerUpdate) timer.setInterval(100) timer.start() w.setGeometry(posx,posy,sizex,sizey) #donne le focus au bouton 1 led0Button.setDefault(True) w.show() app.exec_()
Récupération des données capteurs depuis le serveur via Socket TCP
La variable socket est normalement déjà configurée, mais il est possible la configurer au début du programme à l'aide de:
- config_socket.val3
clearBuffer(siSocketTCP) if(sioCtrl(siSocketTCP, "target", "172.16.6.59")!=0) call logMsgBlock("probleme sioCtrl(siSocketTCP, target, 172.16.6.59)",true) endIf //"endOfString" num (Pour ligne série, client et serveur TCP) Code ASCII pour le caractère de fin de chaîne à utiliser avec les opérateurs '=' (dans la plage [0, 255]) if(sioCtrl(siSocketTCP, "endOfString", 13)!=0) call logMsgBlock("probleme sioCtrl(siSocketTCP, endOfString, 13)",true) endIf //port 30000 utilisé par socket TCP if(sioCtrl(siSocketTCP, "port", 30000)!=0) call logMsgBlock("probleme sioCtrl(siSocketTCP, port, 30000)",true) endIf //0 pour configurer les lectures sur socket bloquante, //une autre valeur en seconde sinon if(sioCtrl(siSocketTCP, "timeout",1)!=0 ) call logMsgBlock("probleme sioCtrl(siSocketTCP, timeout,1)",true) endIf
Tester la boucle du programme suivant en la copiant collant avant la boucle de votre programme. Il vous faut remplacer les numéros des champs dans la chaîne par les numéros de champs de données émises par votre programme Python:
- test.val3
begin while(true) //exemple d'envoi d'une requete pour obtenir les donnees capteurs 10 11 et 12 siSocketTCP = "r 10 11 12" //receptionne la reponse et la met dans la donnée sMsg, arrêt du programme si la reponse n'est pas recue avant le timeout l_sMsg = siSocketTCP //affiche sur l'ecran la chaine recue call logMsgBlock(l_sMsg,true) //il vous reste a convertir les donnees depuis la chaine endWhile end
Si vous ne l'avez pas déjà fait, créer la variable siSocketTCP (attention à bien choisir le type sio, car le type automatiquement choisi risque d'être string) et l'associer la variable à la ressource sio, pour cela aller dans Accueil→IO Physique→Socket→siSocketTCP puis glisser déposer la variable siSocketTCP[0] dans la colonne VAL3 du lien physique siSocketTCP.
Créer la variable locale l_sMsg (de type string).
Lancer le programme VAL3 alors que le programme python est lancé et vous devriez voir les valeurs des données capteurs s'afficher sur l'écran du pendant virtuel.
Il vous reste maintenant à fusionner votre programme et cette boucle qui récupère les données capteurs pour que votre programme les utilise correctement.