Table des matières

bvdp.inetdoc.net_files_iut_tp_pic_warning.jpeg

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

  1. Programmation du robot en 2 étapes:
    1. 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
    2. 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
  2. Conception 3D des éléments de la cellule avec OpenScad
    1. Cellule, préhenseur, support bouteille, champ de vision de la caméra
    2. Examen TP Openscad 2h pendant la SAE pour note de TP Robotique
  3. 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:

  1. 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
  2. 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
  3. Mesure du temps de cycle pour la position de référence du bouchon
  4. 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)
  5. Conception 3D des éléments de la cellule avec OpenScad
    • Cellule
    • Préhenseur
    • Support bouteille
    • Champ de vision de la caméra
  6. 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)
  7. Explication du role de chaque fichier
    • Programmes VAL3
    • Openscad
    • STL
    • Python
  8. Parties spécifiques que vous avez traitées
  9. 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)

Commande du robot en boucle fermée (BF)

Modélisation 3D avec Openscad

Middleware et communication

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é.

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:

  1. Choisir l'onglet Cellule dans la fenêtre de droite
  2. Cliquer sur la flèche à gauche du nom de la cellule pour faire apparaitre “Controller1 [s9…]”
  3. Cliquer droit sur “Controller1 [s9…]” puis cliquer gauche sur “Nouvelle application”
  4. 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!
  5. Cliquer sur Ok

Il faut maintenant configurer l’outil du robot, pour cela:

  1. Choisir l'onglet Géométrie dans la fenêtre de droite
  2. Cliquer sur la flèche à gauche du nom de la cellule pour faire apparaitre “Controller1 [s9…]” puis faire de même pour votre Projet
  3. Cliquer droit sur “Flange” (qui signifie Bride en français, soit l'emplacement où fixer l’outil) puis cliquer gauche sur “Nouvelle donnée”
  4. Saisir un nom pour l’outil, ou laisser le nom par défaut (tTool) puis cliquer sur Ok
  5. Vérifier que tTool apparait maintenant en cliquant sur la flèche à coté de “flange[0]”

Édition du programme

  1. Cliquer sur l'onglet “Accueil” puis sur “Afficher la vue 3D”
  2. Choisir l'onglet Cellule dans la fenêtre de droite
  3. 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 à:

La vue 3D utilise les mêmes principes que Catia pour naviguer:

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

  1. Importer le modèle dans le projet
  2. Onglet Modeleur→Ajouter un modèle CAO
  3. Chercher le fichier cellule_simplifiee.wrl à l'aide du bouton …
  4. Régler la résolution sur Standard et cocher l'option “Copier et insérer le fichier” et “importer comme un assemblage”
  5. 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:

  1. Si un outils est déjà attaché au handler du robot: Scene→Robot→Controleur→Base→Controleur click droit et Détacher
  2. masquer le robot→ cliquer droit sur l'arborescence de scène sur Controller et décocher “Afficher le robot”
  3. Onglet Modeleur→Ajouter un modèle CAO
  4. choisir Copier et insérer le fichier
  5. l'objet apparait dans l'arbre en tant que Géométrie, cliquer droit dessus et faire “nouvel outils”
  6. NE PAS déplacer l'outils (car il est normalement bien positionné)
  7. choisir dans l'arbre l’outil et son handle
  8. cliquer sur “Édition” sous la barre de menu
  9. cliquer droit sur handle et “Éditer la position”
  10. Régler tout à 0 dans la position absolue
  11. cliquer droit sur handler et “Éditer la position”
  12. Régler tout à 0 dans la position absolue sauf Z à 140.00
  13. Sortir du mode Édition
  14. Cliquer droit sur Tool1 dans l'arborescence, attacher à et choisir le handler du robot
  15. réactiver l'affichage du robot

Ensuite il faut associer l’outil à la variable tTool, pour cela:

  1. Sélectionner le Tool1 dans l'arborescence puis faire CTRL+C
  2. 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:

  1. Onglet Modeleur→Ajouter un modèle CAO
  2. Télécharger le fichier et aller le chercher dans l'arborescence avec le bouton “…”
  3. Choisir Copier et insérer le fichier
  4. L'objet apparait dans l'arbre en tant que Géométrie

Insérer dans l'espace de travail du robot:

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…→

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…→

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):

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:

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:

  1. Onglet “Simulation”, cliquer sur icone “PLAY” triangle bleu (Démarrer la synchro)

Pour lancer le Debug du programme

  1. Dans l'onglet Cellule, ouvrir le Controller1→Projet et cliquer gauche sur “start()” pour choisir le programme
  2. 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:

  1. 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”

  1. Dans la fenêtre “Emulator Controller”, il faut:
  2. Choisir le mode automatique en cliquant sur le bouton rond blanc jusqu'à ce que l'icone “auto” soit en bleu
  3. Mettre le robot sous puissance: cliquer sur le bouton rond avec un trait noir vertical pour le faire devenir vert
  4. cliquer sur l'onglet “Move” (qui devient “Hold”) (SHIFT+F7) pour autoriser le robot à bouger

Pour arrêter un Debug en cours et recommencer:

  1. Onglet “Deboguer” Arrêt du débogueur. (CTRL+D,S abbrégé de Débugguer Stop) .
  2. Onglet Val3, cliquer sur “Démarrer et Debugguer l'application” (CTRL+R,D abbrégé de Run Débugguer )
  3. 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

  1. cliquer sur le contrôleur dans l'arborescence du projet
  2. cliquer sur l'onglet Accueil
  3. cliquer sur l'onglet IO Physiques
  4. clic droit sur “Sockets” puis gauche sur “Editer la carte”
  5. clic gauche sur “+”
  6. choisir “UDP”
  7. régler les paramètres et valider
    1. Nom: siSocketUDP
    2. Port: 10000
    3. Description: Emission etat robot
    4. Timeout: 1
    5. Fin de chaine: 13
    6. IP du serveur: 172.16.6.59
  8. créer une nouvelle donnée de type sio nommée siSocketUDP

Socket TCP

  1. cliquer sur le contrôleur dans l'arborescence du projet
  2. cliquer sur l'onglet Accueil
  3. cliquer sur l'onglet IO Physiques
  4. clic droit sur “Sockets” puis gauche sur “Editer la carte”
  5. clic gauche sur “+”
  6. choisir “TCP client”
  7. régler les paramètres et valider
    1. Nom: siSocketTCP
    2. Port: 30000
    3. Description: requete donnee capteur
    4. Timeout: 1
    5. Fin de chaine: 13
    6. IP du serveur: 172.16.6.59
  8. créer une nouvelle donnée de type sio nommée siSocketTCP

Association des variables Val3 sio aux Sockets

  1. Sauver tous les fichiers du projet en cliquant sur l'icône 2 disquettes en haut à gauche
  2. cliquer sur l'onglet Accueil
  3. cliquer sur l'onglet IO Physiques
  4. cliquer sur le petit “+” à gauche de “Sockets” pour faire apparaître le détail des sockets
  5. 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: )
No matching distribution found for PyQt5-Qt5>=5.15.2 (from PyQt5)
You 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.
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.