Drone et occulus rift : Différence entre versions
(→Choix techniques : Matériel et Logiciel) |
|||
(222 révisions intermédiaires par 4 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
+ | <include nopre noesc src="/home/pedago/pimasc/include/video-DroneControleOcculusRift-iframe.html" /> | ||
+ | __TOC__ | ||
+ | <br style="clear: both;"/> | ||
==Cahier des charges== | ==Cahier des charges== | ||
− | + | ===Contexte=== | |
− | |||
− | |||
Depuis quelques années seulement, les drones se sont popularisés dans le domaine de l'utilisation civile. | Depuis quelques années seulement, les drones se sont popularisés dans le domaine de l'utilisation civile. | ||
Ligne 13 : | Ligne 14 : | ||
<center> | <center> | ||
{| | {| | ||
− | | [[Fichier:Drone Flying Eye.jpg|thumb|upright|Drone pour prise de vues aériennes]] | + | | [[Fichier:Drone Flying Eye.jpg|thumb|upright=1.5|Drone pour prise de vues aériennes]] |
− | | [[Fichier:Onyxstar Fox-C8 XT xender 360.jpg|thumb|upright|Drone civil OnyxStar Fox-C8 XT en vol]] | + | | [[Fichier:Onyxstar Fox-C8 XT xender 360.jpg|thumb|upright=1.5|Drone civil OnyxStar Fox-C8 XT en vol]] |
|} | |} | ||
</center> | </center> | ||
− | + | ===Description du projet=== | |
+ | |||
+ | ====Deux idées distinctes==== | ||
L'objectif de ce projet est de concevoir et implémenter, sur un drone de bonne manufacture, un système embarqué capable de diffuser un flux vidéo en 3D stéréoscopique sur un casque de réalité virtuelle. | L'objectif de ce projet est de concevoir et implémenter, sur un drone de bonne manufacture, un système embarqué capable de diffuser un flux vidéo en 3D stéréoscopique sur un casque de réalité virtuelle. | ||
Ligne 26 : | Ligne 29 : | ||
<center> | <center> | ||
{| | {| | ||
− | | [[Fichier:Idée1.png|thumb|Schéma de la première idée]] | + | | [[Fichier:Idée1.png|thumb|upright=1.75|Schéma de la première idée]] |
− | | [[Fichier:Idée2.png|thumb|Schéma de la deuxième idée]] | + | | [[Fichier:Idée2.png|thumb|upright=1.75|Schéma de la deuxième idée]] |
|} | |} | ||
</center> | </center> | ||
Ligne 38 : | Ligne 41 : | ||
et contrôlée via les mouvements de la tête grâce au casque. Un ordinateur embarqué (type Raspberry) récupérera en liaison sans fil les informations (inclinaison par ex.) du casque pour agir sur les moteurs en plus d'envoyer le flux vidéo des caméras. | et contrôlée via les mouvements de la tête grâce au casque. Un ordinateur embarqué (type Raspberry) récupérera en liaison sans fil les informations (inclinaison par ex.) du casque pour agir sur les moteurs en plus d'envoyer le flux vidéo des caméras. | ||
− | ==== | + | ====L'idée choisie dans les détails==== |
− | + | <center> | |
+ | {| | ||
+ | | [[Fichier:Schema P41.png|thumb|upright=2|Schéma du système complet]] | ||
+ | |} | ||
+ | </center> | ||
− | + | Une RaspberryPi hébergera un serveur capable de recevoir les valeurs envoyés par l'ordinateur et de diffuser les images issus des caméras pour qu'on puisse les visualiser via un navigateur. | |
− | + | Les servomoteurs seront commandés via la RaspberryPi qui traitera les valeurs des positions reçues de l'Oculus par l'intermédiaire de l'ordinateur. | |
− | ====Choix techniques : Matériel et Logiciel | + | De plus, la RaspberryPi sera alimentée via un chargeur de batterie externe. |
+ | |||
+ | ===Contraintes=== | ||
+ | |||
+ | * Notre système embarqué ne devra pas excéder le poids de charge du drone (1kg). | ||
+ | |||
+ | * Il faudra pouvoir alimenter notre système soit via l'alimentation du drone, soit à l'aide d'une source d'énergie supplémentaire. | ||
+ | |||
+ | * La communication entre le drone, la télécommande pour piloter le drone et l'oculus doit se faire depuis une distance raisonnable (au moins 20 mètres). | ||
+ | |||
+ | ===Choix techniques : Matériel et Logiciel=== | ||
*Matériel | *Matériel | ||
Ligne 52 : | Ligne 69 : | ||
**x1 Raspberry Pi 2 pour le traitement d'image lié aux caméras, l’émission et réception du flux vidéo et le contrôle de la ou les caméras à distance. [<span style="color: green">Fournie 27/01/2015</span>] | **x1 Raspberry Pi 2 pour le traitement d'image lié aux caméras, l’émission et réception du flux vidéo et le contrôle de la ou les caméras à distance. [<span style="color: green">Fournie 27/01/2015</span>] | ||
**x1 Dongle Wifi (compatible Raspberry Pi 2, Wi-Pi par exemple) pour la communication sans fil entre l'utilisateur du drone et la structure montée sur le drone.[<span style="color: green">Fourni 27/01/2015</span>] | **x1 Dongle Wifi (compatible Raspberry Pi 2, Wi-Pi par exemple) pour la communication sans fil entre l'utilisateur du drone et la structure montée sur le drone.[<span style="color: green">Fourni 27/01/2015</span>] | ||
− | **x1 Drone de bonne manufacture ( | + | **x1 Drone de bonne manufacture (disponible en E306) |
− | **x1 Oculus Rift ( | + | **x1 Oculus Rift (Fournis par Geoffrey) |
− | ** | + | **x1 Servomoteurs pour faire pivoter selon 2 axes la structure où seront fixés les caméras (360°).[<span style="color: green">1 fourni 27/01/2015</span>] |
+ | **X2 Micro Servomoteur pour piloter "" (180°) (Fournis par Geoffrey) | ||
+ | **X1 breadboard | ||
+ | **X10 fils mâle/mâle | ||
*Logiciel | *Logiciel | ||
**Solidworks ou un autre logiciel de modélisation 3D pour concevoir les différents supports de la structure. | **Solidworks ou un autre logiciel de modélisation 3D pour concevoir les différents supports de la structure. | ||
− | **Tout logiciel | + | **Tout logiciel lié au traitement de texte et à la programmation (Notepad++, Sublime, vim, nano et Xcode) |
*Autres | *Autres | ||
− | **Utilisation de l'imprimante 3D du FabLab pour concevoir les différents supports de la structure. | + | **Utilisation de l'imprimante 3D du FabLab pour concevoir les différents supports. |
+ | **Utilisation de la découpeuse laser pour le système lié aux caméras. | ||
+ | |||
+ | ==Journal de Projet== | ||
+ | |||
+ | ===Semaine 1 (25 janv - 30 janv)=== | ||
+ | |||
+ | *Rassemblement du matériel disponible à l'école (manque Oculus et Drone) | ||
+ | *Recherche sur internet pour une batterie, deux caméras | ||
+ | |||
+ | ===Semaine 2 (1 fev - 6 fev)=== | ||
+ | |||
+ | *Documentation sur la Raspberry (configuration des GPIO) | ||
+ | *Installation et configuration de la Raspberry | ||
+ | ** Connexion automatique sur un réseau local hebergé sur un ordinateur portable (hosted network - Windows) | ||
+ | ** Installation des bibliothèques pour la commande des servomoteurs via Raspberry | ||
+ | *Premiers essais de commande des servos | ||
+ | ** Problème au niveau des librairies | ||
+ | ** Un simple programme ne fonctionne pas | ||
+ | ** Les broches commandées ne correspondent pas et ne changent pas d'état | ||
+ | *Premières idées sur la structure du support caméra pour 3D Stéréoscopique | ||
+ | |||
+ | ===Semaine 3 (8 fev - 13 fev)=== | ||
+ | |||
+ | *Résolution (partielle) du problème de librairie pour contrôler les servomoteurs | ||
+ | **Certaines fonctions pouvant être utiles ne fonctionnent toujours pas alors que d'autres de la même librairie si | ||
+ | **Précision angulaire d'environ 10° avec la fonction de contrôle utilisée (insuffisant) [https://projets-ima.polytech-lille.net:40079/mediawiki/index.php?title=Drone_et_occulus_rift#Sp.C3.A9cifications_de_WiringPi_et_branchement cf. Spécification de WiringPi et branchement] | ||
+ | *Début de conception de support pour fixer la Raspberry et la batterie externe sur le drone | ||
+ | *Réception du drone | ||
+ | |||
+ | ===Semaine 4 (22 fev - 27 fev)=== | ||
+ | |||
+ | *Résolution complète du problème de librairie pour contrôler les servomoteurs | ||
+ | **Nouvelle fonction de la librairie utilisée pour le contrôle des servomoteurs | ||
+ | **Précision angulaire d'environ 0,1° avec la nouvelle fonction de contrôle utilisée mais sur une plage de 120° au lieu de 180° | ||
+ | *Essais et recherches pour contrôler les servomoteurs précisément et sur une plage plus grande | ||
+ | *Problème de Wifi qui se déconnecte tout seul | ||
+ | **On essayera de transformer la Raspberry en Hotspot pour voir si c'est mieux | ||
+ | **Sinon on utilisera une borne wifi (solution mieux adaptée dans ce cas) | ||
+ | *Discussion avec notre tuteur (M. Dequidt) | ||
+ | **Mise au point sur l'avancement du projet | ||
+ | **Une salle va nous être prêtée pour effectuer les tests au moment venu (plafond de 6m pour le drone) | ||
+ | **L'Oculus, prêté par un laboratoire ne va pas tardé à arrivé | ||
+ | **Notre tuteur se propose de passer quelques heures avec nous pour travailler sur la manière de récupérer les valeurs de l'accéléromètre de l'Oculus | ||
+ | |||
+ | ===Semaine 5 (29 fev - 5 mars)=== | ||
+ | *Installation et paramétrage de la Raspberry en tant que point d'accès wifi | ||
+ | **Point d'accès wifi sur la Raspberry impossible à faire fonctionner (problème au niveau du DHCP...) | ||
+ | **Fonctionne sur nos rasberry et nos dongles respectifs, et aussi pour d'autres groupes de projet en suivant la même méthode avec le même matériel, une de nos librairies provoque peut être un conflit | ||
+ | **On verra plus tard pour le wifi, le réseau de l'école "fonctionne très bien" du coup on pourra travailler dessus | ||
+ | *OpenCV | ||
+ | **Documentation | ||
+ | **Compilation et test de fonctionnement sur une machine indépendante | ||
+ | *Diverses méthodes étudiées vis à vis des flux vidéo des deux caméras | ||
+ | **Motion, OpenCV ou libcam | ||
+ | **Streaming sur une page internet à l'aide de motion ? | ||
+ | |||
+ | ===Semaine 6 (7 mars - 12 mars)=== | ||
+ | *Travail sur l'Oculus DK2 dans les locaux de l'IRCICA avec notre tuteur (M. Dequidt) | ||
+ | **Recherche sur le SDK pour comprendre le fonctionnement "logiciel" de l'Oculus | ||
+ | **Code minimaliste et fonctionnel (testé sur OSX) récupérant les valeurs de l'orientation de l'Oculus | ||
+ | *OpenCV est un peu trop lourd pour la Raspberry et peut être inadéquate pour l'utilisation qu'on souhaite en faire | ||
+ | *Nouveaux objectifs | ||
+ | **On va essayer d'ouvrir deux flux vidéos en même temps sur Raspberry | ||
+ | **Tester la compilation du code minimaliste sur Windows et Linux | ||
+ | **Tester le code avec un Oculus DK1 pour juger de la compatibilité du code | ||
+ | **Trouver un moyen pour diffuser les valeurs sur le réseau (certainement de la même manière qu'en Tutorat S&R) | ||
+ | |||
+ | ===Semaine 7 (14 mars - 19 mars)=== | ||
+ | *Trop de problèmes avec Wheezy (surtout au niveau du wifi, erreurs dans le noyau), installation de Jessie | ||
+ | *Réussite d'ouverture de deux flux vidéos en même temps [https://projets-ima.polytech-lille.net:40079/mediawiki/index.php?title=Drone_et_occulus_rift#Lire_le_flux_vid.C3.A9o_sur_le_navigateur_web cf. Lire le flux vidéo sur le navigateur web] | ||
+ | |||
+ | ===Semaine 8 (21 mars - 26 mars)=== | ||
+ | *Finalisation de la conception du support pour fixer la Raspberry et la batterie externe sur le drone | ||
+ | *Code minimaliste | ||
+ | **La compilation du code minimaliste fonctionne sur une machine virtuelle Linux après plusieurs installations de bibliothèques (et beaucoup de chance), il faudra tester avec un Oculus | ||
+ | **Par contre avec Visual Studio 2012 sur Windows c'est trop compliqué et rien fonctionne, perte de temps | ||
+ | |||
+ | ===Semaine 9 (28 mars - 2 avril)=== | ||
+ | *Support terminé et imprimé [https://projets-ima.polytech-lille.net:40079/mediawiki/index.php?title=Drone_et_occulus_rift#Design_d.27une_nacelle cf. Design d'une nacelle] | ||
+ | **Support assez fragile du fait que certaines parties sont petites (2mm d'épaisseur) | ||
+ | **La batterie, la Raspberry et le premier servomoteur logent parfaitement, l'ensemble est plutôt léger, résultat conforme à nos attentes | ||
+ | *Montage sur le drone | ||
+ | **Impossibilité de démonter la caméra et son support présents de base | ||
+ | **Il faut repenser entièrement le support car la fixation ne se fait plus au milieu du drone au niveau des pieds... | ||
+ | *Nouvelles idées support | ||
+ | **Pour équilibrer les charges, un coté support avec la Raspberry, un autre avec la batterie | ||
+ | **Entre les deux, notre support de caméra | ||
+ | **En gros, moins équilibré, un peu plus lourd... | ||
+ | |||
+ | ===Semaine 10 (18 avril - 23 avril)=== | ||
+ | *Développement support caméra (à ne pas confondre avec support Raspberry/Batterie) | ||
+ | **Bricolage / Utilisation de l’imprimante laser | ||
+ | **Premier prototype bricolé assez sympa mais trop fragile (épaisseur du bois très faible) | ||
+ | **Deuxième prototype plus petit mais plus résistant, plans pour le refaire et l'améliorer sur SolidWorks | ||
+ | *Développement support Raspberry | ||
+ | **Support presque finalisé sous SolidWorks | ||
+ | |||
+ | ===Semaine 11 (25 avril - 30 avril)=== | ||
+ | *Développement des systèmes de fixation terminé [https://projets-ima.polytech-lille.net:40079/mediawiki/index.php?title=Drone_et_occulus_rift#Autre_syst.C3.A8me_de_fixation cf. Autre système de fixation] | ||
+ | **Support Raspberry terminé sous SolidWorks | ||
+ | **Support Batterie débuté et terminé sous SolidWorks | ||
+ | *Programme minimaliste de l'Oculus | ||
+ | **Impossibilité de reconnaître l'Oculus sur une VM Linux où le programme était compilé (problème de redirection USB) | ||
+ | **Problème dans la compilation sous OSX | ||
+ | **Problème dans la compilation sous Linux sur les machines à disposition (une librairie impossible à mettre, coup de chance sur la VM et il faudrait repasser encore 8h dessus) | ||
+ | |||
+ | ===Semaine 12 (2 mai - 7 mai)=== | ||
+ | *Supports Raspberry et Batterie | ||
+ | **Supports imprimés correctement | ||
+ | **Supports installés très facilement et fonctionnels | ||
+ | **Support Raspberry mal conçu au niveau des prises USB, manque de place | ||
+ | *Communication entre la Raspberry et l'Oculus | ||
+ | **Utilisation d'un pont virtuel pour communiquer entre un ordinateur et la Raspberry | ||
+ | **On communique via RJ45, le résultat sera le même avec une vraie borne Wifi | ||
+ | **Le programme minimaliste pour les valeurs de l'Oculus fonctionne enfin sous OSX via Xcode | ||
+ | |||
+ | ===Semaine 13 (9 mai - 13 mai)=== | ||
+ | *Traitement des données de l'Oculus | ||
+ | **Modification du code en C++ pour mettre les valeurs dans un fichier (pas le temps de recreer le client en C++, le copier coller ne suffit pas pour intégrer du C en C++...) | ||
+ | **Les valeurs dans le fichier sont lues par un programme client en C qui envoie les données via le pont virtuel | ||
+ | **Les valeurs sont récupérées par la RaspberryPi puis traitées pour gérer la commande des servomoteurs | ||
+ | *Compatibilité du pont virtuel | ||
+ | **Modification d'une librairie pour permettre à un utilisateur sous OSX de compiler le client (uniquement) | ||
+ | *Flux vidéos | ||
+ | **On sait intégrer les deux flux vidéos grâce à Motion sur une page internet pour visualiser les 2 caméras dans l'Oculus | ||
+ | **Mais pas (encore) sur la Raspberry utilisée en projet, Motion change en fonction de la version Linux utilisée | ||
+ | |||
+ | ===Semaine 14 (extra)=== | ||
+ | *Quelques réglages avant de faire la vidéo de présentation | ||
+ | *Le contrôle des caméras pendant la soutenance donnait une impression de "tremblement", c'était Motion qui faisait n'importe quoi en arrière plan | ||
+ | |||
+ | = Avancement du projet = | ||
+ | |||
+ | == Gestion des caméras == | ||
+ | |||
+ | Dans un premier temps, nous avons du réfléchir sur comment récupérer les deux flux des caméras branchées en USB sur notre Raspberry Pi 2. | ||
+ | |||
+ | Nous étions partis sur l'utilisation de la bibliothèque OpenCV, mais étant assez difficile d'utilisation et beaucoup trop puissante pour ce que l'on veut faire, nous nous sommes dirigés vers une autre solution qui est motion. | ||
+ | |||
+ | Motion est un logiciel qui permet de diffuser un flux vidéo via internet par le protocole HTTP. C'est une solution simple pour diffuser le flux de sa webcam en ligne ou pour détecter des mouvements dans le champ d'une caméra par exemple. | ||
+ | |||
+ | === Installation de motion === | ||
+ | |||
+ | <pre>sudo apt-get update | ||
+ | sudo apt-get updrade | ||
+ | sudo apt-get install motion | ||
+ | sudo apt-get install apache2 </pre> | ||
+ | |||
+ | On utilise apache2 pour diffuser notre flux vidéo, depuis notre navigateur web, en local. | ||
+ | |||
+ | === Configuration de motion === | ||
+ | |||
+ | Tout d'abord, on configure motion à partir de deux fichiers : | ||
+ | |||
+ | <pre>nano /etc/motion/motion.conf</pre> | ||
+ | |||
+ | Puis on cherche les lignes suivantes et on change quelques paramètres : | ||
+ | |||
+ | <pre>deamon on (au lieu de off) | ||
+ | width 320 | ||
+ | height 240 | ||
+ | framerate 90</pre> | ||
+ | |||
+ | Dans le champ Live Webcam serveur | ||
+ | |||
+ | <pre>webcam_maxrate 24 | ||
+ | webcam_localhost off | ||
+ | #rajoutez les lignes suivantes en dessous de la ligne précédente | ||
+ | thread /home/pi/webcam/cam1.conf | ||
+ | thread /home/pi/webcam/cam2.conf</pre> | ||
+ | |||
+ | On rajoute ensuite deux "thread" pour donner la configuration des deux caméras. Pour cela : | ||
+ | |||
+ | <pre>cd /home/pi | ||
+ | mkdir webcam | ||
+ | cd webcam | ||
+ | nano cam1.conf</pre> | ||
+ | |||
+ | On vérifie la présence des caméras avec la commande : | ||
+ | |||
+ | <pre> ls /dev/vid* | ||
+ | /dev/video0 /dev/video1</pre> | ||
+ | |||
+ | Puis on met la configuration suivante pour la première caméra (de même pour la deuxième, en changeant de port) : | ||
+ | |||
+ | <pre>videodevice /dev/video0 | ||
+ | webcam_port 8081</pre> | ||
+ | |||
+ | On a donc la Caméra 1 qui est lu sur le port 8081 et la Caméra 2 sur le port 8082. | ||
+ | |||
+ | Si on veut lancer le flux au démarrage de la Rasperry Pi : | ||
+ | |||
+ | <pre>nano /etc/default/motion | ||
+ | start_motion_daemon=yes</pre> | ||
+ | |||
+ | === Lire le flux vidéo sur le navigateur web === | ||
+ | |||
+ | Une fois apache2 installé et motion configuré, on créer une page web minimal permettant d'afficher en même temps nos deux flux vidéo. Pour cela : | ||
+ | |||
+ | <pre>nano /var/www/webcam.html</pre> | ||
+ | |||
+ | Puis on récupère le flux via ce code minimaliste : | ||
+ | |||
+ | <pre><html> | ||
+ | <head> | ||
+ | <title>Raspberry Pi Webcams</title> | ||
+ | <head> | ||
+ | <body> | ||
+ | <h1>Raspberry pi Webcaméras</h1> | ||
+ | <a href="http://raspberrypi:8081/"> | ||
+ | <img src="http://raspberrypi:8081/" alt="Camera1"></a> | ||
+ | <a href="http://raspberrypi:8082/"> | ||
+ | <img src="http://raspberrypi:8082/" alt="Camera2"></a> | ||
+ | </body> | ||
+ | </html></pre> | ||
+ | |||
+ | Pour démarrer et arrêter le service (attention à bien être en root) | ||
+ | |||
+ | <pre>service motion start | ||
+ | service motion stop</pre> | ||
+ | |||
+ | Pour visualiser les flux de notre page web sur notre navigateur : | ||
+ | |||
+ | <pre>http://raspberrypi/webcam.html</pre> | ||
+ | |||
+ | On peut mettre l'adresse de la Raspberry à la place de "raspberrypi" via : | ||
+ | |||
+ | <pre>ifconfig</pre> | ||
+ | |||
+ | |||
+ | |||
+ | Ainsi, on obtient bien le "stream" des deux caméras en USB à partir de notre Raspberry Pi 2 : | ||
+ | |||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Rpi cam.jpg|237px]] | ||
+ | | [[Fichier:1651645165651658.gif]] | ||
+ | | [[Fichier:35628452.gif|290px]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | === Contrôler une caméra directement en C === | ||
+ | |||
+ | Partie non-abordée par manque de temps. Néanmoins quelques exemples sont disponibles ci-dessous : | ||
+ | |||
+ | [[Fichier:FoxCam.zip]] | ||
+ | |||
+ | C'est un ensemble de fichiers qui permet de mettre en place une caméra USB sur une foxboard. (à modifier pour faire fonctionner sous Rpi) | ||
+ | |||
+ | == Gestion des GPIO pour contrôle de servo-moteur == | ||
+ | |||
+ | Dans un second temps, nous avons dû chercher et décider de la manière dont nous allions contrôler les servomoteurs depuis les broches de la Raspberry Pi 2. | ||
+ | |||
+ | |||
+ | Nous sommes restés sur l'utilisation de la bibliothèque softPwm de WiringPi car c'est la première qui a fonctionné bien qu'elle ne soit pas très précise. | ||
+ | |||
+ | En effet, avec la fonction softPwmWrite() de softPwm, nous sommes limités à moins de 20 positions pour commander un servomoteur 180°, ce qui fait une grossière précision angulaire de 10°. | ||
+ | |||
+ | En utilisant des servomoteurs 360°, la PWM désigne la commande en vitesse, donc il est possible d'avoir une meilleure précision angulaire, mais il faudrait un capteur au niveau des servomoteurs pour avoir un retour de position ce qui complique le système. | ||
+ | |||
+ | |||
+ | La fonction softServoWrite() de softServo nous permet d'avoir plus de positions pour arriver à une précision angulaire de 0,1°. | ||
+ | |||
+ | La bibliothèque softServo doit être compilée séparément car elle ne l'est pas lors de l'installation de WiringPi. | ||
+ | |||
+ | Lorsque nous avons réinstallé notre Raspberry vers une version Debian supérieur, nous n'arrivions plus à compiler cette bibliothèque et nous n'avons pas voulu perdre plus de temps dessus, d'où l'utilisation de softPwm. | ||
+ | |||
+ | |||
+ | Une bibliothèque très intéressante (nommée bcm2835), lié directement au microcontrôleur de la Raspberry, aurait pu aussi être utilisée pour "générer en C" les PWM en sortie de GPIO en utilisant des delay et des changement d'états. | ||
+ | |||
+ | === WiringPi === | ||
+ | |||
+ | ==== Installation de WiringPi ==== | ||
+ | |||
+ | Tout d'abord, on a besoin d'installer git pour récupérer WiringPi : | ||
+ | |||
+ | <pre>apt-get install git-core</pre> | ||
+ | |||
+ | Pour obtenir WiringPi via Git : | ||
+ | |||
+ | <pre>git clone git://git.drogon.net/wiringPi</pre> | ||
+ | |||
+ | Au premier clone, lors de l'installation : | ||
+ | |||
+ | <pre>cd wiringPi | ||
+ | git pull origin</pre> | ||
+ | |||
+ | Puis, pour construire avec le script fourni : | ||
+ | |||
+ | <pre>cd wiringPi | ||
+ | ./build</pre> | ||
+ | |||
+ | ==== Spécifications de WiringPi et branchement ==== | ||
+ | |||
+ | Pour la documentation de wiringPi : | ||
+ | |||
+ | -Français[http://mchobby.be/wiki/index.php?title=Pi-WiringPi] | ||
+ | -Anglais[http://wiringpi.com/] | ||
+ | |||
+ | Pour vérifier que le programme est bien installé : | ||
+ | |||
+ | <pre>gpio -v</pre> | ||
+ | |||
+ | Pour lire les entrées et les sorties sur les gpio de la Pi : | ||
+ | |||
+ | <pre>gpio readall</pre> | ||
+ | |||
+ | Si la version de wiringPi est bonne, un "mapping" des GPIO devrait s'afficher sur le terminal. | ||
+ | |||
+ | Il faut maintenant écrire un programme pour contrôler les servomoteurs. | ||
+ | |||
+ | Les servomoteurs peuvent être alimentés en 5V, directement via les GPIO 5V/0V de la Raspberry | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:terminal-gpio.png|vignette|upright=1.91|Mapping des GPIO sur Raspberry Pi 2]] | ||
+ | | [[Fichier:brachement-servo.jpg|vignette|upright=1.4|Servomoteur 180° commandé via GPIO 1]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | ==== Test avec <sofPwm.h> ==== | ||
+ | ===== Servo 180° ===== | ||
+ | |||
+ | Ici on va utiliser la librairie de wiringPi <softPwm.h>, les spécifications de la librairie sont disponibles à cette adresse[http://wiringpi.com/reference/software-pwm-library/]. | ||
+ | |||
+ | Voici un code sommaire permettant de contrôler un servomoteur sur sa plage d'action en position (ici 0 à 180 degrés). La Pwm envoyée au servomoteur détermine la position qu'il doit atteindre : | ||
+ | |||
+ | <pre>#include <wiringPi.h> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <softPwm.h> | ||
+ | |||
+ | #define PIN_0 1 | ||
+ | |||
+ | int main(int argc, char *argv[]){ | ||
+ | int pos =0; | ||
+ | char reponse = ' '; | ||
+ | if(wiringPiSetup() == -1){ | ||
+ | printf("Bug\n"); | ||
+ | exit(1); | ||
+ | }//si l'initialisation de wiringPi échoue, on arrête le programme | ||
+ | |||
+ | pinMode(PIN_0,OUTPUT); //dédfinition de la gpio 1 comme sortie | ||
+ | digitalWrite(PIN_0,LOW);//son etat initial est à létat bas | ||
+ | softPwmCreate(PIN_0,0,500);//creation de la pwm | ||
+ | |||
+ | do{ | ||
+ | |||
+ | printf("Position à 0°:\n"); | ||
+ | softPwmWrite(PIN_0,25); | ||
+ | delay(2000); | ||
+ | printf("Position à 90° :\n"); | ||
+ | softPwmWrite(PIN_0,16); | ||
+ | delay(2000); | ||
+ | printf("Position à 180° : \n"); | ||
+ | softPwmWrite(PIN_0,8); | ||
+ | delay(2000); //les positions sont arbitraires pour ma maquette sinon elle sera liée dans le futur aux accelero de l'oculus | ||
+ | do{ | ||
+ | printf("Voulez-vous continuer (0/N)\n"); | ||
+ | scanf("%c",&reponse); | ||
+ | }while(reponse != 'O' && reponse !='N'); | ||
+ | }while(reponse == 'O'); | ||
+ | |||
+ | printf("Au revoir\n"); | ||
+ | return 0; | ||
+ | }</pre> | ||
+ | |||
+ | Pour compiler le programme, on utilise la ligne ci-dessous : | ||
+ | |||
+ | <pre>gcc -o servo servo.c -lwiringPi</pre> | ||
+ | |||
+ | Et voici un gif illustrant le code ci-dessus : | ||
+ | |||
+ | [[Fichier:Servo180-45625214.gif]] | ||
+ | |||
+ | ===== Servo 360° ===== | ||
+ | |||
+ | De même, on va asservir ici un servomoteur 360°. Le code reste similaire. On ne contrôle plus en position mais la Pwm appliquée détermine le sens/vitesse de rotation : | ||
+ | |||
+ | <pre>#include <wiringPi.h> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <softPwm.h> | ||
+ | |||
+ | #define PIN_1 0 | ||
+ | |||
+ | int main(int argc, char *argv[]){ | ||
+ | int pos =0; | ||
+ | char reponse = ' '; | ||
+ | if(wiringPiSetup() == -1){ | ||
+ | printf("Bug\n"); | ||
+ | exit(1); | ||
+ | }//si l'initialisation de wiringPi échoue, on arrête le programme | ||
+ | |||
+ | pinMode(PIN_1,OUTPUT); //dédfinition de la gpio 1 comme sortie | ||
+ | digitalWrite(PIN_1,LOW);//son etat initial est à létat bas | ||
+ | softPwmCreate(PIN_1,0,500);//creation de la pwm | ||
+ | |||
+ | do{ | ||
+ | |||
+ | printf("Sens anti-horaire:\n"); | ||
+ | softPwmWrite(PIN_10,9); | ||
+ | delay(2000); | ||
+ | printf("Arrêt du servo :\n"); | ||
+ | softPwmWrite(PIN_1,15); | ||
+ | delay(2000); | ||
+ | printf("Sens horaire : \n"); | ||
+ | softPwmWrite(PIN_1,18); | ||
+ | delay(2000); | ||
+ | softPwmWrite(PIN_1,15);//arret du servo apres test | ||
+ | do{ | ||
+ | printf("Voulez-vous continuer (0/N)\n"); | ||
+ | scanf("%c",&reponse); | ||
+ | }while(reponse != 'O' && reponse !='N'); | ||
+ | }while(reponse == 'O'); | ||
+ | |||
+ | printf("Au revoir\n"); | ||
+ | return 0; | ||
+ | }</pre> | ||
+ | |||
+ | On compile comme vu précédemment pour obtenir ce que l'on peut visionner sur le gif montrant le servomoteur en action : | ||
+ | |||
+ | [[Fichier:Servo360-45625214.gif]] | ||
+ | |||
+ | === Contrôler les GPIO directement en C === | ||
+ | |||
+ | Ici nous allons directement tester les bibliothèques du processeur de la Raspberry Pi 2: la Bcm2835. | ||
+ | |||
+ | ==== Installation de la bibliothèque ==== | ||
+ | |||
+ | Liens de la documentation complète de la librairie : [http://www.airspayce.com/mikem/bcm2835/] | ||
+ | |||
+ | Pour commencer, choisir le dossier d'installation de la bibliothèque : | ||
+ | |||
+ | <pre> cd /home/pi/ | ||
+ | wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.XX.tar.gz </pre> | ||
+ | |||
+ | Puis pour dé-zippé le fichier téléchargé : | ||
+ | |||
+ | <pre>tar zxvf bcm2835-1.XX.tar.gz </pre> | ||
+ | |||
+ | On va maintenant construire l'archive : | ||
+ | |||
+ | <pre>cd bcm2835.XX | ||
+ | ./configure | ||
+ | make | ||
+ | sudo make check | ||
+ | sudo make install</pre> | ||
+ | |||
+ | La librairie est maintenant prête à être utilisée. Attention à ne pas oublier : | ||
+ | |||
+ | <pre>#include <bcm2835.h> </pre> | ||
+ | |||
+ | Pour compiler un .c : | ||
+ | |||
+ | <pre>gcc -o fichier fichier.c -lbcm2835</pre> | ||
+ | |||
+ | ou en utilisant un makefile: | ||
+ | |||
+ | <pre>all: output_file_name | ||
+ | |||
+ | output_file_name: main.o | ||
+ | gcc main.o -lbcm2835 -o output_file_name | ||
+ | |||
+ | main.o: main.c | ||
+ | gcc -c main.c | ||
+ | |||
+ | clean: | ||
+ | rm -rf *o output_file_name </pre> | ||
+ | |||
+ | Note: la librairie bcm2835 est issue de la première version de la raspberry pi et de son processeur associé. | ||
+ | |||
+ | Aujourd'hui la rpi 2 fonctionne sous un bcm2836 et même problème pour la rpi 3. | ||
+ | |||
+ | La solution est que la librairie est constamment mise à jour. Les changements majeurs se situent au niveau de l’appellation des broches. | ||
+ | |||
+ | ==== Test avec une diode ==== | ||
+ | |||
+ | Pour tester la librairie, on compile un programme pour faire clignoter une diode : | ||
+ | |||
+ | <pre>#include <bcm2835.h> | ||
+ | |||
+ | // Blinks on RPi Plug P1 pin 11 (which is GPIO 0 pin 17) | ||
+ | #define PIN 17 | ||
+ | int main(int argc, char **argv) | ||
+ | { | ||
+ | // If you call this, it will not actually access the GPIO | ||
+ | // Use for testing | ||
+ | // bcm2835_set_debug(1); | ||
+ | |||
+ | if (!bcm2835_init()) | ||
+ | return 1; | ||
+ | |||
+ | // Set the pin to be an output | ||
+ | bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP); | ||
+ | |||
+ | // Blink | ||
+ | while (1) | ||
+ | { | ||
+ | // Turn it on | ||
+ | bcm2835_gpio_write(PIN, HIGH); | ||
+ | |||
+ | // wait a bit | ||
+ | bcm2835_delay(500); | ||
+ | |||
+ | // turn it off | ||
+ | bcm2835_gpio_write(PIN, LOW); | ||
+ | |||
+ | // wait a bit | ||
+ | bcm2835_delay(500); | ||
+ | } | ||
+ | bcm2835_close(); | ||
+ | return 0; | ||
+ | } </pre> | ||
+ | |||
+ | == Récupération des valeurs des accéléromètres de l'Oculus Rift == | ||
+ | |||
+ | Pour essayer de comprendre comment le SDK de l'Oculus Rift fonctionne, nous avons utilisé une série d'exemples créés et expliqués par Jherico sur son [https://github.com/OculusRiftInAction/OculusRiftInAction gitHub]. | ||
+ | |||
+ | Il montre comment utiliser et programmer les différents composants des Oculus Rift. | ||
+ | |||
+ | Pour la suite, on a besoin d'installer [https://cmake.org/ cmake] pour construire le projet. | ||
+ | |||
+ | On récupère ensuite le projet (sur notre pc) contenant les différents exemples : | ||
+ | |||
+ | <pre>git clone https://github.com/OculusRiftInAction/OculusRiftInAction.git --recursive</pre> | ||
+ | |||
+ | puis après la fin du téléchargement, pour compiler les différents projets, | ||
+ | |||
+ | <pre>cd OculusRiftInAction | ||
+ | mkdir build | ||
+ | cd build | ||
+ | cmake ..</pre> | ||
+ | |||
+ | On peut aussi générer au besoin un Makefile : | ||
+ | |||
+ | <pre>cmake .. -G "Unix Makefiles"</pre> | ||
+ | |||
+ | Ou ce que l'on souhaite (selon l'Os, l'IDE...) en regardant : | ||
+ | |||
+ | <pre>cmake -h</pre> | ||
+ | |||
+ | On lance un programme au hasard pour vérifier son bon fonctionnement : | ||
+ | |||
+ | <pre>./examples/cpp/./Example_2_2_Tracker.cpp</pre> | ||
+ | |||
+ | Pour nos tests, nous avons rencontré beaucoup de problèmes pour compiler les différentes bibliothèques nécessaires pour contrôler les oculus. Une solution que nous avons trouvé est (sous Mac OSX): | ||
+ | |||
+ | <pre>cmake .. -G "Xcode"</pre> | ||
+ | |||
+ | Un projet est construit sous Xcode, puis l'IDE fait les liens entre les différentes bibliothèques. On utilise le fichier C++ suivant : | ||
+ | |||
+ | <pre>#include "Common.h" | ||
+ | #include <sys/types.h> | ||
+ | #include <sys/ipc.h> | ||
+ | #include <sys/wait.h> | ||
+ | #include <sys/msg.h> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <unistd.h> | ||
+ | #include <fcntl.h> | ||
+ | #include <sys/stat.h> | ||
+ | #include <fstream> | ||
+ | #include <iostream> | ||
+ | |||
+ | using namespace std; | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | class Tracker { | ||
+ | protected: | ||
+ | public: | ||
+ | |||
+ | int run() { | ||
+ | |||
+ | key_t ftok(char); | ||
+ | |||
+ | float val_x,val_y,val_z; | ||
+ | |||
+ | |||
+ | ovrHmd hmd = ovrHmd_Create(0); | ||
+ | if (!hmd || !ovrHmd_ConfigureTracking(hmd,ovrTrackingCap_Orientation, 0)) { | ||
+ | SAY_ERR("Unable to detect Rift head tracker"); | ||
+ | return -1; | ||
+ | } | ||
+ | for (int i = 0; i < 10; ++i) { | ||
+ | ovrTrackingState state = ovrHmd_GetTrackingState(hmd, 0); | ||
+ | |||
+ | ovrQuatf orientation = state.HeadPose.ThePose.Orientation; | ||
+ | glm::quat q = glm::make_quat(&orientation.x); | ||
+ | glm::vec3 euler = glm::eulerAngles(q); | ||
+ | |||
+ | SAY("Current orientation - roll %0.2f, pitch %0.2f, yaw %0.2f", | ||
+ | euler.z * RADIANS_TO_DEGREES, | ||
+ | euler.x * RADIANS_TO_DEGREES, | ||
+ | euler.y * RADIANS_TO_DEGREES); | ||
+ | |||
+ | val_x = euler.x * RADIANS_TO_DEGREES; | ||
+ | val_y = euler.y * RADIANS_TO_DEGREES; | ||
+ | val_z = euler.z * RADIANS_TO_DEGREES; | ||
+ | |||
+ | Platform::sleepMillis(1000); | ||
+ | } | ||
+ | ovrHmd_Destroy(hmd); | ||
+ | return 0; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | RUN_OVR_APP(Tracker); | ||
+ | </pre> | ||
+ | |||
+ | Ainsi, on récupère nos valeurs dans un terminal : | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Terminal-xyz.png|thumb|upright=1.75|Valeur terminal Oculus]] | ||
+ | | [[Fichier:Oculus-term.jpg |thumb|upright=1.75|Oculus + term]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | et une petite animation qui récupère aussi les positions des accéléromètres (C++): | ||
+ | |||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Oculus_anim.gif ]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | == Fixation du système embarqué sur le drone == | ||
+ | |||
+ | === Design d'une nacelle === | ||
+ | |||
+ | Au début du projet, nous avions commencé à réaliser une première structure, basée sur un morceau du support de la caméra du drone : | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Plate.PNG|thumb|upright=1.75|Morceau d'attache du support sous SolidWorks]] | ||
+ | | [[Fichier:Boite_rasp.JPG|thumb|upright=1.75|Structure complète sous SolidWorks]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | Cette structure avait pour avantage de se situer sous le drone, embarquant tout l'électronique et le système de caméra. | ||
+ | |||
+ | Son principal avantage était de centrer la masse pour garantir une bonne stabilité du drone en vol. | ||
+ | |||
+ | Nous avions imprimé un premier prototype de cette nacelle et le résultat était plutôt très satisfaisant. | ||
+ | |||
+ | En effet, le premier servomoteur, la Raspberry et la batterie (non disponible lors de la photo) rentraient parfaitement dans la structure. | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Struct1.jpg|thumb|upright=1.75|Photo de la structure terminée]] | ||
+ | | [[Fichier:Struct2.jpg|thumb|upright=1.75|Autre photo de la structure terminée]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | Mais malheureusement, la modification de conception de la nacelle originelle du drone s'avère impossible. | ||
+ | |||
+ | === Autre système de fixation === | ||
+ | |||
+ | Lorsque que nous fûmes habilité à démonter la nacelle d'origine, nous nous sommes rendus compte que deux attaches étaient indémontables et qu'il fallait donc les casser pour retirer la nacelle. | ||
+ | |||
+ | Chose qui n'avait pas été pris en compte dans le cahier des charges car nous n'avions pas eu beaucoup d'informations sur le drone et que nous n'étions pas responsables de son montage à sa réception. | ||
+ | |||
+ | Nous avons donc repensé la structure en optant pour plusieurs fixations : Une pour la batterie, une pour la Raspberry Pi et un système attaché devant le drone pour le contrôle des caméras dans l'espace. | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Fixation_Rasp.JPG|thumb|upright=2.1|Support Raspberry Pi 2 sous SolidWorks]] | ||
+ | | [[Fichier:ASBLG Rasp 2.JPG|thumb|upright=1.75|Support, Raspberry Pi 2 et le couvercle du support sous SolidWorks]] | ||
+ | | [[Fichier:Fixation_Batterie.JPG|thumb|upright=1.4|Support batterie sous SolidWorks]] | ||
+ | | [[Fichier:ASBLG_Power_Bank.JPG|thumb|upright=1.7|Rendu final du support batterie sous SolidWorks]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | === Système à cardan === | ||
+ | |||
+ | Pour pouvoir déplacer les caméra dans un plan se situant devant le drone, on a du imaginer une structure pouvant déplacer les deux caméras à différentes positions. | ||
+ | |||
+ | Nous avons réalisé deux prototypes car le premier était trop fragile, puis effectué un test pour montrer le fonctionnement de la structure : | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Support v1 cam.jpg|thumb|upright=1.75|Premier prototype du système]] | ||
+ | | [[Fichier:Support v2 cam.jpg|thumb|upright=1.75|Deuxième prototype plus résistant]] | ||
+ | | [[Fichier:Sys cam 654651658.gif]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | Il a fallu ensuite, à partir de nos connaissances sur les servomoteurs, réaliser un "plan" des différentes positions que nous voulons atteindre. | ||
+ | |||
+ | En effet, on a recensé 13 positions par servomoteur, ce qui nous donne un total de 168 couples disponible : | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Fonction 3d servo.jpg|vignette|upright=1.25|Brouillon d'un schéma pour les positions possibles]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | A partir de ce tableau, on a créé une fonction qui permet de placer nos servos. Voici son prototype : | ||
+ | |||
+ | <pre> void pos_servo(int tableau1[], int tableau2[2], nombre) </pre> | ||
+ | |||
+ | Elle demande deux tableaux qui sont les coordonnées des positions disponibles de nos servomoteurs (x,y,nombre) dans leur plage de fonctionnement. | ||
+ | |||
+ | Puis elle demande un nombre qui correspond aux valeurs des différents couples de positions disponibles dans une demi-sphere se situant devant le drone. | ||
+ | |||
+ | Pour tester ces positions, on génère un nombre aléatoire que l'on rentre dans notre fonction pour vérifier son bon fonctionnement : | ||
+ | |||
+ | <pre>#include <time.h> | ||
+ | int rand_a_b(int a, int b){ //genère un nb aleatoire entre a inclue et b exclu | ||
+ | return rand()%(b-a) +a; | ||
+ | }</pre> | ||
+ | |||
+ | |||
+ | Il reste ensuite à réaliser une fonction qui traitera les valeurs des accéléromètres de l'oculus rift et les transformera en position pour notre fonction. | ||
+ | |||
+ | === Support batterie === | ||
+ | |||
+ | Une fois nos schémas réalisés et corrigés sur solidworks, nous obtenons notre première boite pour la batterie. | ||
+ | |||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Support-case-1.jpg|thumb|upright=1.75|Première vue du prototype]] | ||
+ | | [[Fichier:Support-case-2.jpg|thumb|upright=1.75|Deuxième vue du prototype]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | === Support Raspberry === | ||
+ | |||
+ | Puis notre support pour la raspberry. | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[ Fichier:Support bat-1.jpg|thumb|upright=1.75|Première vue du prototype]] | ||
+ | | [[ Fichier:Support-bat-2.jpg |thumb|upright=1.75|Deuxième vue du prototype]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | === Vu de l'ensemble === | ||
+ | |||
+ | Enfin, après avoir placé les composants, la raspberry Pi et la batterie dans leur rangement respectif, on obtient ainsi l'ensemble suivant: | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[ Fichier:Vu-mont-1.jpg|thumb|upright=1.75|Première vue de l'ensemble]] | ||
+ | | [[ Fichier:Vu-mont-2.jpg |thumb|upright=1.75|Deuxième vue de l'ensemble]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | Le résultat est très satisfaisant malgré que les supports soient plus spacieux et plus lourd que la nacelle pensée au début. | ||
+ | |||
+ | Les supports sont très résistants et s'accrochent très bien sur les pieds du drone. | ||
+ | |||
+ | On notera un léger manque de place au niveau des deux prises USB situées au milieu ce qui est assez handicapant lorsque nous avons un dongle wifi trop grand. | ||
+ | |||
+ | == Test du système complet == | ||
+ | |||
+ | Nous avons, pour finir, implanté tous les modules développés ci-dessus ensemble. | ||
+ | |||
+ | Pour cela, nous avons modifié le fichier .cpp qui récupère les valeurs des oculus rift, puis, nous avons "piper" ces valeurs pour pouvoir les récupérer et les envoyer sur le réseau. | ||
+ | |||
+ | Nous nous sommes inspirés d'un pont réseau développé en cours de crypto. | ||
+ | |||
+ | <center> | ||
+ | {| | ||
+ | | [[Fichier:Schema pont 1546.png|thumb|upright=5|Schéma du système de communication]] | ||
+ | |} | ||
+ | </center> | ||
+ | |||
+ | |||
+ | === Récupération/envoie valeurs accéléro === | ||
+ | |||
+ | Le fichier ci-dessous est un fichier en C++. Il récupère la valeur des accéléromètres et les enregistre dans un fichier. Il est à noter que les valeurs sont perpétuellement réécrites par dessus. | ||
+ | |||
+ | <pre>#include "Common.h" | ||
+ | #include <sys/types.h> | ||
+ | #include <sys/ipc.h> | ||
+ | #include <sys/wait.h> | ||
+ | #include <sys/msg.h> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <unistd.h> | ||
+ | #include <fcntl.h> | ||
+ | #include <sys/stat.h> | ||
+ | #include <fstream> | ||
+ | #include <iostream> | ||
+ | |||
+ | using namespace std; | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | class Tracker { | ||
+ | protected: | ||
+ | public: | ||
+ | |||
+ | int run() { | ||
+ | |||
+ | key_t ftok(char); | ||
+ | |||
+ | float val_x,val_y,val_z; | ||
+ | float xt,yt,zt; | ||
+ | FILE* fd ; | ||
+ | |||
+ | ovrHmd hmd = ovrHmd_Create(0); | ||
+ | if (!hmd || !ovrHmd_ConfigureTracking(hmd,ovrTrackingCap_Orientation, 0)) { | ||
+ | SAY_ERR("Unable to detect Rift head tracker"); | ||
+ | return -1; | ||
+ | } | ||
+ | for (int i = 0; i < 10; ++i) { | ||
+ | ovrTrackingState state = ovrHmd_GetTrackingState(hmd, 0); | ||
+ | |||
+ | ovrQuatf orientation = state.HeadPose.ThePose.Orientation; | ||
+ | glm::quat q = glm::make_quat(&orientation.x); | ||
+ | glm::vec3 euler = glm::eulerAngles(q); | ||
+ | |||
+ | SAY("Current orientation - roll %0.2f, pitch %0.2f, yaw %0.2f", | ||
+ | euler.z * RADIANS_TO_DEGREES, | ||
+ | euler.x * RADIANS_TO_DEGREES, | ||
+ | euler.y * RADIANS_TO_DEGREES); | ||
+ | |||
+ | val_x = euler.x * RADIANS_TO_DEGREES; | ||
+ | val_y = euler.y * RADIANS_TO_DEGREES; | ||
+ | val_z = euler.z * RADIANS_TO_DEGREES; | ||
+ | |||
+ | ofstream fichier("val.data",ios::out | ios::trunc); | ||
+ | |||
+ | if(fichier) | ||
+ | { | ||
+ | fichier << val_x << " " << val_y << " " << val_z; | ||
+ | fichier.close(); | ||
+ | } | ||
+ | |||
+ | /*fd = fopen("val.data",w+); | ||
+ | fputs("%0.2f %0.2f %0.2f",val_x,val_y,val_z,fd); | ||
+ | fclose(fd); | ||
+ | |||
+ | fd = fopen("val.data",r); | ||
+ | fscanf(fd,"%0.2f %0.2f %0.2f",xt,yt,zt); | ||
+ | printf("RECU LOCAL %0.2f %0.2f %0.2f",xt,yt,zt); | ||
+ | fclose(fd);*/ | ||
+ | |||
+ | Platform::sleepMillis(1000); | ||
+ | } | ||
+ | ovrHmd_Destroy(hmd); | ||
+ | return 0; | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | RUN_OVR_APP(Tracker); | ||
+ | </pre> | ||
+ | |||
+ | === Pont (serveur/client) === | ||
+ | |||
+ | Pour envoyer les données des oculus vers le drone, on a utilisé un serveur hébergé sur la RaspberryPi, et un client sur un ordinateur. Voici les fichiers qui ont été utilisés : | ||
+ | |||
+ | [[Fichier:ZOCULUS-4.zip]] (La version du serveur n'est pas à jour, elle affiche simplement les valeurs reçues. La version avec contrôle des servomoteurs est restée sur la SD qui n'est plus accessible...) | ||
+ | |||
+ | Pour lancer le serveur : | ||
+ | |||
+ | <pre>./virtual_bridge <port></pre> | ||
+ | |||
+ | Pour lancer le client : | ||
+ | |||
+ | <pre>./virtual_client <adress> <port></pre> | ||
+ | |||
+ | Pour le client, voici une partie du code que nous avons développé : | ||
+ | |||
+ | <pre> | ||
+ | // Communication avec le serveur | ||
+ | FILE *dialogue=fdopen(s,"a+"); | ||
+ | FILE* fd = NULL; | ||
+ | if(dialogue==NULL){ perror("gestionClient.fdopen"); exit(EXIT_FAILURE); } | ||
+ | printf("OK\n"); | ||
+ | |||
+ | sigemptyset(&ens1); | ||
+ | sigaddset(&ens1, SIGINT); | ||
+ | sigaddset(&ens1, SIGQUIT); | ||
+ | |||
+ | sigprocmask(SIG_SETMASK, &ens1, NULL); | ||
+ | |||
+ | |||
+ | printf("Initialisation des valeurs de l'Oculus... "); | ||
+ | fd = fopen("val.data","r"); | ||
+ | |||
+ | if(fd == NULL) | ||
+ | { | ||
+ | fclose(dialogue); | ||
+ | shutdown(s,SHUT_RDWR); | ||
+ | perror("Impossible d'ouvrir le fichier val.data pour l'Oculus\n"); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | |||
+ | fscanf(fd,"%f %f %f",&val_x_init, &val_y_init, &val_z_init); | ||
+ | |||
+ | #ifdef DEBUG | ||
+ | fprintf(stdout,"Lecture Fichier (BRUT) : %0.2f %0.2f %0.2f\n",val_x_init, val_y_init, val_z_init); | ||
+ | #endif | ||
+ | |||
+ | fclose(fd); | ||
+ | sleep(1); | ||
+ | |||
+ | printf("OK\n"); | ||
+ | |||
+ | printf("Transmission des valeurs, CTRL_C pour arrêter\n"); | ||
+ | while(run) | ||
+ | { | ||
+ | sigpending(&ens2); | ||
+ | if(sigismember(&ens2,SIGINT) || sigismember(&ens2,SIGQUIT)) | ||
+ | { | ||
+ | val_x = 404.404; | ||
+ | val_y = 404.404; | ||
+ | val_z = 404.404; | ||
+ | |||
+ | write(s,&val_x,sizeof(val_x)); | ||
+ | write(s,&val_y,sizeof(val_y)); | ||
+ | write(s,&val_z,sizeof(val_z)); | ||
+ | #ifdef DEBUG | ||
+ | fprintf(stdout,"(W) %0.3f %0.3f %0.3f\n",val_x, val_y, val_z); | ||
+ | #endif | ||
+ | run = 0; | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | fd = NULL; | ||
+ | fd = fopen("val.data","r"); | ||
+ | |||
+ | if(fd == NULL) | ||
+ | { | ||
+ | fclose(dialogue); | ||
+ | shutdown(s,SHUT_RDWR); | ||
+ | perror("Impossible d'ouvrir le fichier val.data pour l'Oculus\n"); | ||
+ | exit(EXIT_FAILURE); | ||
+ | } | ||
+ | |||
+ | fscanf(fd,"%f %f %f",&val_x, &val_y, &val_z); | ||
+ | #ifdef DEBUG | ||
+ | fprintf(stdout,"Lecture Fichier : %0.2f %0.2f %0.2f\n",val_x, val_y, val_z); | ||
+ | #endif | ||
+ | |||
+ | val_x = val_x_init - val_x; | ||
+ | val_y = val_y_init - val_y; | ||
+ | val_z = val_z_init - val_z; | ||
+ | |||
+ | write(s,&val_x,sizeof(val_x)); | ||
+ | write(s,&val_y,sizeof(val_y)); | ||
+ | write(s,&val_z,sizeof(val_z)); | ||
+ | |||
+ | #ifdef DEBUG | ||
+ | fprintf(stdout,"(W) %0.2f %0.2f %0.2f\n",val_x, val_y, val_z); | ||
+ | #endif | ||
+ | |||
+ | fclose(fd); | ||
+ | sleep(1); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | sigemptyset(&ens1); | ||
+ | sigprocmask(SIG_SETMASK, &ens1, NULL); | ||
+ | |||
+ | fclose(dialogue); | ||
+ | </pre> | ||
+ | |||
+ | On retrouve la "prise origine" au début pour s'assurer d'avoir toujours des valeurs correspondant à une même quantité de mouvement au niveau de l'Oculus. | ||
+ | |||
+ | Puis dans une boucle, on récupère les nouvelles valeurs constamment pour envoyer la différence à la Raspberry et contrôler les servomoteurs en conséquence. | ||
+ | |||
+ | On notera la présence d'une gestion de signaux afin de quitter le programme proprement et signaler à la Raspberry l'arrêt de la communication. | ||
+ | |||
+ | === Démonstration de la liaison oculus/drone === | ||
+ | |||
+ | Maintenant que le transfert des données est opérationnel, nous l'avons implanté sur le système de caméra en rentrant les valeurs seuils des différents axes. | ||
+ | |||
+ | Ici, on a une démonstration de la communication entre le bloc drone et un ordinateur avec les oculus rift: | ||
+ | |||
+ | [[Fichier:5465684861566848.gif]] | ||
+ | |||
+ | On peut imaginer ce que l'on récupère dans les oculus, via un test fait plus tard, montrant le bon fonctionnement du transfert du flux vidéo : | ||
+ | |||
+ | [[Fichier:Ezgif-475218049.gif]] | ||
− | + | Voilà ce qu'on observe alors depuis une lentille du masque de réalité virtuelle : | |
− | + | [[Fichier:15156165151500005456454.gif]] | |
− | == | + | == Rapport de fin de projet == |
− | + | [[Fichier:P41 Rapport de projet PIEKACZ RICHEZ.pdf]] |
Version actuelle datée du 14 juin 2016 à 08:52
Sommaire
- 1 Cahier des charges
- 2 Journal de Projet
- 2.1 Semaine 1 (25 janv - 30 janv)
- 2.2 Semaine 2 (1 fev - 6 fev)
- 2.3 Semaine 3 (8 fev - 13 fev)
- 2.4 Semaine 4 (22 fev - 27 fev)
- 2.5 Semaine 5 (29 fev - 5 mars)
- 2.6 Semaine 6 (7 mars - 12 mars)
- 2.7 Semaine 7 (14 mars - 19 mars)
- 2.8 Semaine 8 (21 mars - 26 mars)
- 2.9 Semaine 9 (28 mars - 2 avril)
- 2.10 Semaine 10 (18 avril - 23 avril)
- 2.11 Semaine 11 (25 avril - 30 avril)
- 2.12 Semaine 12 (2 mai - 7 mai)
- 2.13 Semaine 13 (9 mai - 13 mai)
- 2.14 Semaine 14 (extra)
- 3 Avancement du projet
Cahier des charges
Contexte
Depuis quelques années seulement, les drones se sont popularisés dans le domaine de l'utilisation civile.
Équipés de caméras, ils peuvent être utilisés pour lutter contre l'insécurité, surveiller des manifestations ou pour savoir le nombre de personnes prises au piège d'un immeuble en flammes.
Ils peuvent être aussi utilisés pour la prise de vues aériennes.
Description du projet
Deux idées distinctes
L'objectif de ce projet est de concevoir et implémenter, sur un drone de bonne manufacture, un système embarqué capable de diffuser un flux vidéo en 3D stéréoscopique sur un casque de réalité virtuelle.
De plus, nous avons eu deux idées pour la gestion du drone :
La première idée est de contrôler le drone en position directement via les mouvements de la tête à l'aide du casque, et nous aurions simplement fixé une caméra sur le drone pour pouvoir nous repérer.
La deuxième idée, retenue après discussion avec notre encadrant M. Dequidt, est de contrôler le drone en position via une télécommande par exemple.
Nous aurions cependant une caméra fixée sur le drone capable de tourner suivant deux axes et contrôlée via les mouvements de la tête grâce au casque. Un ordinateur embarqué (type Raspberry) récupérera en liaison sans fil les informations (inclinaison par ex.) du casque pour agir sur les moteurs en plus d'envoyer le flux vidéo des caméras.
L'idée choisie dans les détails
Une RaspberryPi hébergera un serveur capable de recevoir les valeurs envoyés par l'ordinateur et de diffuser les images issus des caméras pour qu'on puisse les visualiser via un navigateur.
Les servomoteurs seront commandés via la RaspberryPi qui traitera les valeurs des positions reçues de l'Oculus par l'intermédiaire de l'ordinateur.
De plus, la RaspberryPi sera alimentée via un chargeur de batterie externe.
Contraintes
- Notre système embarqué ne devra pas excéder le poids de charge du drone (1kg).
- Il faudra pouvoir alimenter notre système soit via l'alimentation du drone, soit à l'aide d'une source d'énergie supplémentaire.
- La communication entre le drone, la télécommande pour piloter le drone et l'oculus doit se faire depuis une distance raisonnable (au moins 20 mètres).
Choix techniques : Matériel et Logiciel
- Matériel
- x1 ou x2 Caméras (résolution 800x600 minimum) pour récupérer des images depuis le drone, la vision 3D sera soit faite grâce à deux caméras, soit grâce à une caméra suivi d'un traitement d'image plus poussé.
- x1 Raspberry Pi 2 pour le traitement d'image lié aux caméras, l’émission et réception du flux vidéo et le contrôle de la ou les caméras à distance. [Fournie 27/01/2015]
- x1 Dongle Wifi (compatible Raspberry Pi 2, Wi-Pi par exemple) pour la communication sans fil entre l'utilisateur du drone et la structure montée sur le drone.[Fourni 27/01/2015]
- x1 Drone de bonne manufacture (disponible en E306)
- x1 Oculus Rift (Fournis par Geoffrey)
- x1 Servomoteurs pour faire pivoter selon 2 axes la structure où seront fixés les caméras (360°).[1 fourni 27/01/2015]
- X2 Micro Servomoteur pour piloter "" (180°) (Fournis par Geoffrey)
- X1 breadboard
- X10 fils mâle/mâle
- Logiciel
- Solidworks ou un autre logiciel de modélisation 3D pour concevoir les différents supports de la structure.
- Tout logiciel lié au traitement de texte et à la programmation (Notepad++, Sublime, vim, nano et Xcode)
- Autres
- Utilisation de l'imprimante 3D du FabLab pour concevoir les différents supports.
- Utilisation de la découpeuse laser pour le système lié aux caméras.
Journal de Projet
Semaine 1 (25 janv - 30 janv)
- Rassemblement du matériel disponible à l'école (manque Oculus et Drone)
- Recherche sur internet pour une batterie, deux caméras
Semaine 2 (1 fev - 6 fev)
- Documentation sur la Raspberry (configuration des GPIO)
- Installation et configuration de la Raspberry
- Connexion automatique sur un réseau local hebergé sur un ordinateur portable (hosted network - Windows)
- Installation des bibliothèques pour la commande des servomoteurs via Raspberry
- Premiers essais de commande des servos
- Problème au niveau des librairies
- Un simple programme ne fonctionne pas
- Les broches commandées ne correspondent pas et ne changent pas d'état
- Premières idées sur la structure du support caméra pour 3D Stéréoscopique
Semaine 3 (8 fev - 13 fev)
- Résolution (partielle) du problème de librairie pour contrôler les servomoteurs
- Certaines fonctions pouvant être utiles ne fonctionnent toujours pas alors que d'autres de la même librairie si
- Précision angulaire d'environ 10° avec la fonction de contrôle utilisée (insuffisant) cf. Spécification de WiringPi et branchement
- Début de conception de support pour fixer la Raspberry et la batterie externe sur le drone
- Réception du drone
Semaine 4 (22 fev - 27 fev)
- Résolution complète du problème de librairie pour contrôler les servomoteurs
- Nouvelle fonction de la librairie utilisée pour le contrôle des servomoteurs
- Précision angulaire d'environ 0,1° avec la nouvelle fonction de contrôle utilisée mais sur une plage de 120° au lieu de 180°
- Essais et recherches pour contrôler les servomoteurs précisément et sur une plage plus grande
- Problème de Wifi qui se déconnecte tout seul
- On essayera de transformer la Raspberry en Hotspot pour voir si c'est mieux
- Sinon on utilisera une borne wifi (solution mieux adaptée dans ce cas)
- Discussion avec notre tuteur (M. Dequidt)
- Mise au point sur l'avancement du projet
- Une salle va nous être prêtée pour effectuer les tests au moment venu (plafond de 6m pour le drone)
- L'Oculus, prêté par un laboratoire ne va pas tardé à arrivé
- Notre tuteur se propose de passer quelques heures avec nous pour travailler sur la manière de récupérer les valeurs de l'accéléromètre de l'Oculus
Semaine 5 (29 fev - 5 mars)
- Installation et paramétrage de la Raspberry en tant que point d'accès wifi
- Point d'accès wifi sur la Raspberry impossible à faire fonctionner (problème au niveau du DHCP...)
- Fonctionne sur nos rasberry et nos dongles respectifs, et aussi pour d'autres groupes de projet en suivant la même méthode avec le même matériel, une de nos librairies provoque peut être un conflit
- On verra plus tard pour le wifi, le réseau de l'école "fonctionne très bien" du coup on pourra travailler dessus
- OpenCV
- Documentation
- Compilation et test de fonctionnement sur une machine indépendante
- Diverses méthodes étudiées vis à vis des flux vidéo des deux caméras
- Motion, OpenCV ou libcam
- Streaming sur une page internet à l'aide de motion ?
Semaine 6 (7 mars - 12 mars)
- Travail sur l'Oculus DK2 dans les locaux de l'IRCICA avec notre tuteur (M. Dequidt)
- Recherche sur le SDK pour comprendre le fonctionnement "logiciel" de l'Oculus
- Code minimaliste et fonctionnel (testé sur OSX) récupérant les valeurs de l'orientation de l'Oculus
- OpenCV est un peu trop lourd pour la Raspberry et peut être inadéquate pour l'utilisation qu'on souhaite en faire
- Nouveaux objectifs
- On va essayer d'ouvrir deux flux vidéos en même temps sur Raspberry
- Tester la compilation du code minimaliste sur Windows et Linux
- Tester le code avec un Oculus DK1 pour juger de la compatibilité du code
- Trouver un moyen pour diffuser les valeurs sur le réseau (certainement de la même manière qu'en Tutorat S&R)
Semaine 7 (14 mars - 19 mars)
- Trop de problèmes avec Wheezy (surtout au niveau du wifi, erreurs dans le noyau), installation de Jessie
- Réussite d'ouverture de deux flux vidéos en même temps cf. Lire le flux vidéo sur le navigateur web
Semaine 8 (21 mars - 26 mars)
- Finalisation de la conception du support pour fixer la Raspberry et la batterie externe sur le drone
- Code minimaliste
- La compilation du code minimaliste fonctionne sur une machine virtuelle Linux après plusieurs installations de bibliothèques (et beaucoup de chance), il faudra tester avec un Oculus
- Par contre avec Visual Studio 2012 sur Windows c'est trop compliqué et rien fonctionne, perte de temps
Semaine 9 (28 mars - 2 avril)
- Support terminé et imprimé cf. Design d'une nacelle
- Support assez fragile du fait que certaines parties sont petites (2mm d'épaisseur)
- La batterie, la Raspberry et le premier servomoteur logent parfaitement, l'ensemble est plutôt léger, résultat conforme à nos attentes
- Montage sur le drone
- Impossibilité de démonter la caméra et son support présents de base
- Il faut repenser entièrement le support car la fixation ne se fait plus au milieu du drone au niveau des pieds...
- Nouvelles idées support
- Pour équilibrer les charges, un coté support avec la Raspberry, un autre avec la batterie
- Entre les deux, notre support de caméra
- En gros, moins équilibré, un peu plus lourd...
Semaine 10 (18 avril - 23 avril)
- Développement support caméra (à ne pas confondre avec support Raspberry/Batterie)
- Bricolage / Utilisation de l’imprimante laser
- Premier prototype bricolé assez sympa mais trop fragile (épaisseur du bois très faible)
- Deuxième prototype plus petit mais plus résistant, plans pour le refaire et l'améliorer sur SolidWorks
- Développement support Raspberry
- Support presque finalisé sous SolidWorks
Semaine 11 (25 avril - 30 avril)
- Développement des systèmes de fixation terminé cf. Autre système de fixation
- Support Raspberry terminé sous SolidWorks
- Support Batterie débuté et terminé sous SolidWorks
- Programme minimaliste de l'Oculus
- Impossibilité de reconnaître l'Oculus sur une VM Linux où le programme était compilé (problème de redirection USB)
- Problème dans la compilation sous OSX
- Problème dans la compilation sous Linux sur les machines à disposition (une librairie impossible à mettre, coup de chance sur la VM et il faudrait repasser encore 8h dessus)
Semaine 12 (2 mai - 7 mai)
- Supports Raspberry et Batterie
- Supports imprimés correctement
- Supports installés très facilement et fonctionnels
- Support Raspberry mal conçu au niveau des prises USB, manque de place
- Communication entre la Raspberry et l'Oculus
- Utilisation d'un pont virtuel pour communiquer entre un ordinateur et la Raspberry
- On communique via RJ45, le résultat sera le même avec une vraie borne Wifi
- Le programme minimaliste pour les valeurs de l'Oculus fonctionne enfin sous OSX via Xcode
Semaine 13 (9 mai - 13 mai)
- Traitement des données de l'Oculus
- Modification du code en C++ pour mettre les valeurs dans un fichier (pas le temps de recreer le client en C++, le copier coller ne suffit pas pour intégrer du C en C++...)
- Les valeurs dans le fichier sont lues par un programme client en C qui envoie les données via le pont virtuel
- Les valeurs sont récupérées par la RaspberryPi puis traitées pour gérer la commande des servomoteurs
- Compatibilité du pont virtuel
- Modification d'une librairie pour permettre à un utilisateur sous OSX de compiler le client (uniquement)
- Flux vidéos
- On sait intégrer les deux flux vidéos grâce à Motion sur une page internet pour visualiser les 2 caméras dans l'Oculus
- Mais pas (encore) sur la Raspberry utilisée en projet, Motion change en fonction de la version Linux utilisée
Semaine 14 (extra)
- Quelques réglages avant de faire la vidéo de présentation
- Le contrôle des caméras pendant la soutenance donnait une impression de "tremblement", c'était Motion qui faisait n'importe quoi en arrière plan
Avancement du projet
Gestion des caméras
Dans un premier temps, nous avons du réfléchir sur comment récupérer les deux flux des caméras branchées en USB sur notre Raspberry Pi 2.
Nous étions partis sur l'utilisation de la bibliothèque OpenCV, mais étant assez difficile d'utilisation et beaucoup trop puissante pour ce que l'on veut faire, nous nous sommes dirigés vers une autre solution qui est motion.
Motion est un logiciel qui permet de diffuser un flux vidéo via internet par le protocole HTTP. C'est une solution simple pour diffuser le flux de sa webcam en ligne ou pour détecter des mouvements dans le champ d'une caméra par exemple.
Installation de motion
sudo apt-get update sudo apt-get updrade sudo apt-get install motion sudo apt-get install apache2
On utilise apache2 pour diffuser notre flux vidéo, depuis notre navigateur web, en local.
Configuration de motion
Tout d'abord, on configure motion à partir de deux fichiers :
nano /etc/motion/motion.conf
Puis on cherche les lignes suivantes et on change quelques paramètres :
deamon on (au lieu de off) width 320 height 240 framerate 90
Dans le champ Live Webcam serveur
webcam_maxrate 24 webcam_localhost off #rajoutez les lignes suivantes en dessous de la ligne précédente thread /home/pi/webcam/cam1.conf thread /home/pi/webcam/cam2.conf
On rajoute ensuite deux "thread" pour donner la configuration des deux caméras. Pour cela :
cd /home/pi mkdir webcam cd webcam nano cam1.conf
On vérifie la présence des caméras avec la commande :
ls /dev/vid* /dev/video0 /dev/video1
Puis on met la configuration suivante pour la première caméra (de même pour la deuxième, en changeant de port) :
videodevice /dev/video0 webcam_port 8081
On a donc la Caméra 1 qui est lu sur le port 8081 et la Caméra 2 sur le port 8082.
Si on veut lancer le flux au démarrage de la Rasperry Pi :
nano /etc/default/motion start_motion_daemon=yes
Une fois apache2 installé et motion configuré, on créer une page web minimal permettant d'afficher en même temps nos deux flux vidéo. Pour cela :
nano /var/www/webcam.html
Puis on récupère le flux via ce code minimaliste :
<html> <head> <title>Raspberry Pi Webcams</title> <head> <body> <h1>Raspberry pi Webcaméras</h1> <a href="http://raspberrypi:8081/"> <img src="http://raspberrypi:8081/" alt="Camera1"></a> <a href="http://raspberrypi:8082/"> <img src="http://raspberrypi:8082/" alt="Camera2"></a> </body> </html>
Pour démarrer et arrêter le service (attention à bien être en root)
service motion start service motion stop
Pour visualiser les flux de notre page web sur notre navigateur :
http://raspberrypi/webcam.html
On peut mettre l'adresse de la Raspberry à la place de "raspberrypi" via :
ifconfig
Ainsi, on obtient bien le "stream" des deux caméras en USB à partir de notre Raspberry Pi 2 :
![]() |
![]() |
![]() |
Contrôler une caméra directement en C
Partie non-abordée par manque de temps. Néanmoins quelques exemples sont disponibles ci-dessous :
C'est un ensemble de fichiers qui permet de mettre en place une caméra USB sur une foxboard. (à modifier pour faire fonctionner sous Rpi)
Gestion des GPIO pour contrôle de servo-moteur
Dans un second temps, nous avons dû chercher et décider de la manière dont nous allions contrôler les servomoteurs depuis les broches de la Raspberry Pi 2.
Nous sommes restés sur l'utilisation de la bibliothèque softPwm de WiringPi car c'est la première qui a fonctionné bien qu'elle ne soit pas très précise.
En effet, avec la fonction softPwmWrite() de softPwm, nous sommes limités à moins de 20 positions pour commander un servomoteur 180°, ce qui fait une grossière précision angulaire de 10°.
En utilisant des servomoteurs 360°, la PWM désigne la commande en vitesse, donc il est possible d'avoir une meilleure précision angulaire, mais il faudrait un capteur au niveau des servomoteurs pour avoir un retour de position ce qui complique le système.
La fonction softServoWrite() de softServo nous permet d'avoir plus de positions pour arriver à une précision angulaire de 0,1°.
La bibliothèque softServo doit être compilée séparément car elle ne l'est pas lors de l'installation de WiringPi.
Lorsque nous avons réinstallé notre Raspberry vers une version Debian supérieur, nous n'arrivions plus à compiler cette bibliothèque et nous n'avons pas voulu perdre plus de temps dessus, d'où l'utilisation de softPwm.
Une bibliothèque très intéressante (nommée bcm2835), lié directement au microcontrôleur de la Raspberry, aurait pu aussi être utilisée pour "générer en C" les PWM en sortie de GPIO en utilisant des delay et des changement d'états.
WiringPi
Installation de WiringPi
Tout d'abord, on a besoin d'installer git pour récupérer WiringPi :
apt-get install git-core
Pour obtenir WiringPi via Git :
git clone git://git.drogon.net/wiringPi
Au premier clone, lors de l'installation :
cd wiringPi git pull origin
Puis, pour construire avec le script fourni :
cd wiringPi ./build
Spécifications de WiringPi et branchement
Pour la documentation de wiringPi :
-Français[1] -Anglais[2]
Pour vérifier que le programme est bien installé :
gpio -v
Pour lire les entrées et les sorties sur les gpio de la Pi :
gpio readall
Si la version de wiringPi est bonne, un "mapping" des GPIO devrait s'afficher sur le terminal.
Il faut maintenant écrire un programme pour contrôler les servomoteurs.
Les servomoteurs peuvent être alimentés en 5V, directement via les GPIO 5V/0V de la Raspberry
Test avec <sofPwm.h>
Servo 180°
Ici on va utiliser la librairie de wiringPi <softPwm.h>, les spécifications de la librairie sont disponibles à cette adresse[3].
Voici un code sommaire permettant de contrôler un servomoteur sur sa plage d'action en position (ici 0 à 180 degrés). La Pwm envoyée au servomoteur détermine la position qu'il doit atteindre :
#include <wiringPi.h> #include <stdio.h> #include <stdlib.h> #include <softPwm.h> #define PIN_0 1 int main(int argc, char *argv[]){ int pos =0; char reponse = ' '; if(wiringPiSetup() == -1){ printf("Bug\n"); exit(1); }//si l'initialisation de wiringPi échoue, on arrête le programme pinMode(PIN_0,OUTPUT); //dédfinition de la gpio 1 comme sortie digitalWrite(PIN_0,LOW);//son etat initial est à létat bas softPwmCreate(PIN_0,0,500);//creation de la pwm do{ printf("Position à 0°:\n"); softPwmWrite(PIN_0,25); delay(2000); printf("Position à 90° :\n"); softPwmWrite(PIN_0,16); delay(2000); printf("Position à 180° : \n"); softPwmWrite(PIN_0,8); delay(2000); //les positions sont arbitraires pour ma maquette sinon elle sera liée dans le futur aux accelero de l'oculus do{ printf("Voulez-vous continuer (0/N)\n"); scanf("%c",&reponse); }while(reponse != 'O' && reponse !='N'); }while(reponse == 'O'); printf("Au revoir\n"); return 0; }
Pour compiler le programme, on utilise la ligne ci-dessous :
gcc -o servo servo.c -lwiringPi
Et voici un gif illustrant le code ci-dessus :
Servo 360°
De même, on va asservir ici un servomoteur 360°. Le code reste similaire. On ne contrôle plus en position mais la Pwm appliquée détermine le sens/vitesse de rotation :
#include <wiringPi.h> #include <stdio.h> #include <stdlib.h> #include <softPwm.h> #define PIN_1 0 int main(int argc, char *argv[]){ int pos =0; char reponse = ' '; if(wiringPiSetup() == -1){ printf("Bug\n"); exit(1); }//si l'initialisation de wiringPi échoue, on arrête le programme pinMode(PIN_1,OUTPUT); //dédfinition de la gpio 1 comme sortie digitalWrite(PIN_1,LOW);//son etat initial est à létat bas softPwmCreate(PIN_1,0,500);//creation de la pwm do{ printf("Sens anti-horaire:\n"); softPwmWrite(PIN_10,9); delay(2000); printf("Arrêt du servo :\n"); softPwmWrite(PIN_1,15); delay(2000); printf("Sens horaire : \n"); softPwmWrite(PIN_1,18); delay(2000); softPwmWrite(PIN_1,15);//arret du servo apres test do{ printf("Voulez-vous continuer (0/N)\n"); scanf("%c",&reponse); }while(reponse != 'O' && reponse !='N'); }while(reponse == 'O'); printf("Au revoir\n"); return 0; }
On compile comme vu précédemment pour obtenir ce que l'on peut visionner sur le gif montrant le servomoteur en action :
Contrôler les GPIO directement en C
Ici nous allons directement tester les bibliothèques du processeur de la Raspberry Pi 2: la Bcm2835.
Installation de la bibliothèque
Liens de la documentation complète de la librairie : [4]
Pour commencer, choisir le dossier d'installation de la bibliothèque :
cd /home/pi/ wget http://www.airspayce.com/mikem/bcm2835/bcm2835-1.XX.tar.gz
Puis pour dé-zippé le fichier téléchargé :
tar zxvf bcm2835-1.XX.tar.gz
On va maintenant construire l'archive :
cd bcm2835.XX ./configure make sudo make check sudo make install
La librairie est maintenant prête à être utilisée. Attention à ne pas oublier :
#include <bcm2835.h>
Pour compiler un .c :
gcc -o fichier fichier.c -lbcm2835
ou en utilisant un makefile:
all: output_file_name output_file_name: main.o gcc main.o -lbcm2835 -o output_file_name main.o: main.c gcc -c main.c clean: rm -rf *o output_file_name
Note: la librairie bcm2835 est issue de la première version de la raspberry pi et de son processeur associé.
Aujourd'hui la rpi 2 fonctionne sous un bcm2836 et même problème pour la rpi 3.
La solution est que la librairie est constamment mise à jour. Les changements majeurs se situent au niveau de l’appellation des broches.
Test avec une diode
Pour tester la librairie, on compile un programme pour faire clignoter une diode :
#include <bcm2835.h> // Blinks on RPi Plug P1 pin 11 (which is GPIO 0 pin 17) #define PIN 17 int main(int argc, char **argv) { // If you call this, it will not actually access the GPIO // Use for testing // bcm2835_set_debug(1); if (!bcm2835_init()) return 1; // Set the pin to be an output bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_OUTP); // Blink while (1) { // Turn it on bcm2835_gpio_write(PIN, HIGH); // wait a bit bcm2835_delay(500); // turn it off bcm2835_gpio_write(PIN, LOW); // wait a bit bcm2835_delay(500); } bcm2835_close(); return 0; }
Récupération des valeurs des accéléromètres de l'Oculus Rift
Pour essayer de comprendre comment le SDK de l'Oculus Rift fonctionne, nous avons utilisé une série d'exemples créés et expliqués par Jherico sur son gitHub.
Il montre comment utiliser et programmer les différents composants des Oculus Rift.
Pour la suite, on a besoin d'installer cmake pour construire le projet.
On récupère ensuite le projet (sur notre pc) contenant les différents exemples :
git clone https://github.com/OculusRiftInAction/OculusRiftInAction.git --recursive
puis après la fin du téléchargement, pour compiler les différents projets,
cd OculusRiftInAction mkdir build cd build cmake ..
On peut aussi générer au besoin un Makefile :
cmake .. -G "Unix Makefiles"
Ou ce que l'on souhaite (selon l'Os, l'IDE...) en regardant :
cmake -h
On lance un programme au hasard pour vérifier son bon fonctionnement :
./examples/cpp/./Example_2_2_Tracker.cpp
Pour nos tests, nous avons rencontré beaucoup de problèmes pour compiler les différentes bibliothèques nécessaires pour contrôler les oculus. Une solution que nous avons trouvé est (sous Mac OSX):
cmake .. -G "Xcode"
Un projet est construit sous Xcode, puis l'IDE fait les liens entre les différentes bibliothèques. On utilise le fichier C++ suivant :
#include "Common.h" #include <sys/types.h> #include <sys/ipc.h> #include <sys/wait.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <fstream> #include <iostream> using namespace std; class Tracker { protected: public: int run() { key_t ftok(char); float val_x,val_y,val_z; ovrHmd hmd = ovrHmd_Create(0); if (!hmd || !ovrHmd_ConfigureTracking(hmd,ovrTrackingCap_Orientation, 0)) { SAY_ERR("Unable to detect Rift head tracker"); return -1; } for (int i = 0; i < 10; ++i) { ovrTrackingState state = ovrHmd_GetTrackingState(hmd, 0); ovrQuatf orientation = state.HeadPose.ThePose.Orientation; glm::quat q = glm::make_quat(&orientation.x); glm::vec3 euler = glm::eulerAngles(q); SAY("Current orientation - roll %0.2f, pitch %0.2f, yaw %0.2f", euler.z * RADIANS_TO_DEGREES, euler.x * RADIANS_TO_DEGREES, euler.y * RADIANS_TO_DEGREES); val_x = euler.x * RADIANS_TO_DEGREES; val_y = euler.y * RADIANS_TO_DEGREES; val_z = euler.z * RADIANS_TO_DEGREES; Platform::sleepMillis(1000); } ovrHmd_Destroy(hmd); return 0; } }; RUN_OVR_APP(Tracker);
Ainsi, on récupère nos valeurs dans un terminal :
et une petite animation qui récupère aussi les positions des accéléromètres (C++):
![]() |
Fixation du système embarqué sur le drone
Design d'une nacelle
Au début du projet, nous avions commencé à réaliser une première structure, basée sur un morceau du support de la caméra du drone :
Cette structure avait pour avantage de se situer sous le drone, embarquant tout l'électronique et le système de caméra.
Son principal avantage était de centrer la masse pour garantir une bonne stabilité du drone en vol.
Nous avions imprimé un premier prototype de cette nacelle et le résultat était plutôt très satisfaisant.
En effet, le premier servomoteur, la Raspberry et la batterie (non disponible lors de la photo) rentraient parfaitement dans la structure.
Mais malheureusement, la modification de conception de la nacelle originelle du drone s'avère impossible.
Autre système de fixation
Lorsque que nous fûmes habilité à démonter la nacelle d'origine, nous nous sommes rendus compte que deux attaches étaient indémontables et qu'il fallait donc les casser pour retirer la nacelle.
Chose qui n'avait pas été pris en compte dans le cahier des charges car nous n'avions pas eu beaucoup d'informations sur le drone et que nous n'étions pas responsables de son montage à sa réception.
Nous avons donc repensé la structure en optant pour plusieurs fixations : Une pour la batterie, une pour la Raspberry Pi et un système attaché devant le drone pour le contrôle des caméras dans l'espace.
Système à cardan
Pour pouvoir déplacer les caméra dans un plan se situant devant le drone, on a du imaginer une structure pouvant déplacer les deux caméras à différentes positions.
Nous avons réalisé deux prototypes car le premier était trop fragile, puis effectué un test pour montrer le fonctionnement de la structure :
![]() |
Il a fallu ensuite, à partir de nos connaissances sur les servomoteurs, réaliser un "plan" des différentes positions que nous voulons atteindre.
En effet, on a recensé 13 positions par servomoteur, ce qui nous donne un total de 168 couples disponible :
A partir de ce tableau, on a créé une fonction qui permet de placer nos servos. Voici son prototype :
void pos_servo(int tableau1[], int tableau2[2], nombre)
Elle demande deux tableaux qui sont les coordonnées des positions disponibles de nos servomoteurs (x,y,nombre) dans leur plage de fonctionnement.
Puis elle demande un nombre qui correspond aux valeurs des différents couples de positions disponibles dans une demi-sphere se situant devant le drone.
Pour tester ces positions, on génère un nombre aléatoire que l'on rentre dans notre fonction pour vérifier son bon fonctionnement :
#include <time.h> int rand_a_b(int a, int b){ //genère un nb aleatoire entre a inclue et b exclu return rand()%(b-a) +a; }
Il reste ensuite à réaliser une fonction qui traitera les valeurs des accéléromètres de l'oculus rift et les transformera en position pour notre fonction.
Support batterie
Une fois nos schémas réalisés et corrigés sur solidworks, nous obtenons notre première boite pour la batterie.
Support Raspberry
Puis notre support pour la raspberry.
Vu de l'ensemble
Enfin, après avoir placé les composants, la raspberry Pi et la batterie dans leur rangement respectif, on obtient ainsi l'ensemble suivant:
Le résultat est très satisfaisant malgré que les supports soient plus spacieux et plus lourd que la nacelle pensée au début.
Les supports sont très résistants et s'accrochent très bien sur les pieds du drone.
On notera un léger manque de place au niveau des deux prises USB situées au milieu ce qui est assez handicapant lorsque nous avons un dongle wifi trop grand.
Test du système complet
Nous avons, pour finir, implanté tous les modules développés ci-dessus ensemble.
Pour cela, nous avons modifié le fichier .cpp qui récupère les valeurs des oculus rift, puis, nous avons "piper" ces valeurs pour pouvoir les récupérer et les envoyer sur le réseau.
Nous nous sommes inspirés d'un pont réseau développé en cours de crypto.
Récupération/envoie valeurs accéléro
Le fichier ci-dessous est un fichier en C++. Il récupère la valeur des accéléromètres et les enregistre dans un fichier. Il est à noter que les valeurs sont perpétuellement réécrites par dessus.
#include "Common.h" #include <sys/types.h> #include <sys/ipc.h> #include <sys/wait.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <fstream> #include <iostream> using namespace std; class Tracker { protected: public: int run() { key_t ftok(char); float val_x,val_y,val_z; float xt,yt,zt; FILE* fd ; ovrHmd hmd = ovrHmd_Create(0); if (!hmd || !ovrHmd_ConfigureTracking(hmd,ovrTrackingCap_Orientation, 0)) { SAY_ERR("Unable to detect Rift head tracker"); return -1; } for (int i = 0; i < 10; ++i) { ovrTrackingState state = ovrHmd_GetTrackingState(hmd, 0); ovrQuatf orientation = state.HeadPose.ThePose.Orientation; glm::quat q = glm::make_quat(&orientation.x); glm::vec3 euler = glm::eulerAngles(q); SAY("Current orientation - roll %0.2f, pitch %0.2f, yaw %0.2f", euler.z * RADIANS_TO_DEGREES, euler.x * RADIANS_TO_DEGREES, euler.y * RADIANS_TO_DEGREES); val_x = euler.x * RADIANS_TO_DEGREES; val_y = euler.y * RADIANS_TO_DEGREES; val_z = euler.z * RADIANS_TO_DEGREES; ofstream fichier("val.data",ios::out | ios::trunc); if(fichier) { fichier << val_x << " " << val_y << " " << val_z; fichier.close(); } /*fd = fopen("val.data",w+); fputs("%0.2f %0.2f %0.2f",val_x,val_y,val_z,fd); fclose(fd); fd = fopen("val.data",r); fscanf(fd,"%0.2f %0.2f %0.2f",xt,yt,zt); printf("RECU LOCAL %0.2f %0.2f %0.2f",xt,yt,zt); fclose(fd);*/ Platform::sleepMillis(1000); } ovrHmd_Destroy(hmd); return 0; } }; RUN_OVR_APP(Tracker);
Pont (serveur/client)
Pour envoyer les données des oculus vers le drone, on a utilisé un serveur hébergé sur la RaspberryPi, et un client sur un ordinateur. Voici les fichiers qui ont été utilisés :
Fichier:ZOCULUS-4.zip (La version du serveur n'est pas à jour, elle affiche simplement les valeurs reçues. La version avec contrôle des servomoteurs est restée sur la SD qui n'est plus accessible...)
Pour lancer le serveur :
./virtual_bridge <port>
Pour lancer le client :
./virtual_client <adress> <port>
Pour le client, voici une partie du code que nous avons développé :
// Communication avec le serveur FILE *dialogue=fdopen(s,"a+"); FILE* fd = NULL; if(dialogue==NULL){ perror("gestionClient.fdopen"); exit(EXIT_FAILURE); } printf("OK\n"); sigemptyset(&ens1); sigaddset(&ens1, SIGINT); sigaddset(&ens1, SIGQUIT); sigprocmask(SIG_SETMASK, &ens1, NULL); printf("Initialisation des valeurs de l'Oculus... "); fd = fopen("val.data","r"); if(fd == NULL) { fclose(dialogue); shutdown(s,SHUT_RDWR); perror("Impossible d'ouvrir le fichier val.data pour l'Oculus\n"); exit(EXIT_FAILURE); } fscanf(fd,"%f %f %f",&val_x_init, &val_y_init, &val_z_init); #ifdef DEBUG fprintf(stdout,"Lecture Fichier (BRUT) : %0.2f %0.2f %0.2f\n",val_x_init, val_y_init, val_z_init); #endif fclose(fd); sleep(1); printf("OK\n"); printf("Transmission des valeurs, CTRL_C pour arrêter\n"); while(run) { sigpending(&ens2); if(sigismember(&ens2,SIGINT) || sigismember(&ens2,SIGQUIT)) { val_x = 404.404; val_y = 404.404; val_z = 404.404; write(s,&val_x,sizeof(val_x)); write(s,&val_y,sizeof(val_y)); write(s,&val_z,sizeof(val_z)); #ifdef DEBUG fprintf(stdout,"(W) %0.3f %0.3f %0.3f\n",val_x, val_y, val_z); #endif run = 0; } else { fd = NULL; fd = fopen("val.data","r"); if(fd == NULL) { fclose(dialogue); shutdown(s,SHUT_RDWR); perror("Impossible d'ouvrir le fichier val.data pour l'Oculus\n"); exit(EXIT_FAILURE); } fscanf(fd,"%f %f %f",&val_x, &val_y, &val_z); #ifdef DEBUG fprintf(stdout,"Lecture Fichier : %0.2f %0.2f %0.2f\n",val_x, val_y, val_z); #endif val_x = val_x_init - val_x; val_y = val_y_init - val_y; val_z = val_z_init - val_z; write(s,&val_x,sizeof(val_x)); write(s,&val_y,sizeof(val_y)); write(s,&val_z,sizeof(val_z)); #ifdef DEBUG fprintf(stdout,"(W) %0.2f %0.2f %0.2f\n",val_x, val_y, val_z); #endif fclose(fd); sleep(1); } } sigemptyset(&ens1); sigprocmask(SIG_SETMASK, &ens1, NULL); fclose(dialogue);
On retrouve la "prise origine" au début pour s'assurer d'avoir toujours des valeurs correspondant à une même quantité de mouvement au niveau de l'Oculus.
Puis dans une boucle, on récupère les nouvelles valeurs constamment pour envoyer la différence à la Raspberry et contrôler les servomoteurs en conséquence.
On notera la présence d'une gestion de signaux afin de quitter le programme proprement et signaler à la Raspberry l'arrêt de la communication.
Démonstration de la liaison oculus/drone
Maintenant que le transfert des données est opérationnel, nous l'avons implanté sur le système de caméra en rentrant les valeurs seuils des différents axes.
Ici, on a une démonstration de la communication entre le bloc drone et un ordinateur avec les oculus rift:
On peut imaginer ce que l'on récupère dans les oculus, via un test fait plus tard, montrant le bon fonctionnement du transfert du flux vidéo :
Voilà ce qu'on observe alors depuis une lentille du masque de réalité virtuelle :