Le but est de piloter par le Wi-Fi, via une IHM sous la forme d'une page web, un robot équipé :
Le serveur HTTP sera programmé en Python.
La carte SD du Raspberry Pi contient le système d'exploitation Raspberry Pi OS (64-bit) avec bureau,
téléchargeable à l'adresse
https://www.raspberrypi.com/sofware/operating-systems/
| Commande | Description |
|---|---|
| ping raspberrypi.local | Teste l'accès au Raspberry Pi et indique son adresse IP |
| ssh eric@raspberrypi.local | Se connecte au Raspberry Pi à distance |
| exit | Quitte ssh |
| ifconfig | Affiche l'adresse IP de la machine locale |
| ls | Affiche le contenu du dossier |
| cat index.html | Affiche le contenu du fichier index.html |
| rm index.html | Supprime le fichier index.html |
| nano index.html | Edite le fichier index.html |
| chmod +x serveur.py | Rend le programme serveur.py exécutable |
| ./serveur.py | Démarre le programme serveur.py |
| sudo raspi-config | Configure Raspberry Pi OS |
| sudo reboot | Redémarre le Raspberry Pi |
| sudo halt | Eteint le Raspberry Pi |
Capture une image enregistrée sous le nom im.jpg
:
rpicam-still -o im.jpg
Capture des images enregistrées sous le nom unique im.jpg
,
toutes les secondes, indéfiniment :
rpicam-still -o im.jpg --timelapse 1000 -t 0 --width 600 --height 400
Capture des images enregistrées sous les noms capture_%04d.jpg
,
toutes les secondes, pendant 30 s :
rpicam-still -o capture_%04d.jpg --timelapse 1000 -t 30000 --width 600 --height 400
Capture une image avec une exposition de 100 s :
rpicam-still -o im.jpg --shutter 100000000 --gain 1 --awbgains 1,1 --immediate
| Option | Paramètre | Description |
|---|---|---|
| --output, -o | Nom de fichier | Fichier de sauvegarde |
| --nopreview, -n | - | Pas de visualisation |
| --timelapse, -tl | Nombre de millisecondes | Laps de temps entre deux images |
| --timeout, -t | Nombre de millisecondes | Durée d'acquisition, |
| --width, -w | Nomre de pixels | Largeur de l'image |
| --height, -h | Nomre de pixels | Hauteur de l'image |
| --quality, -q | Nombre de 1 à 99, par défaut 93 | Qualité de l'image |
| --rotation, -rot | Nombre de degrés | Rotation de l'image |
| --hflip, -hf | - | Miroir horizontal |
| --vflip, -vf | - | Miroir vertical |
Capture une image enregistrée sous le nom im.jpg
:
#!/usr/bin/python3
from picamera2 import Picamera2
import time
picam2 = Picamera2()
picam2.start()
time.sleep(2)
picam2.capture_file("im.jpg")
Capture des images enregistrées sous le nom unique im.jpg
,
toutes les secondes, indéfiniment :
#!/usr/bin/python3
from picamera2 import Picamera2
import time
picam2 = Picamera2()
picam2.start()
while 1:
time.sleep(1)
picam2.capture_file("testcam.jpg")
Un programme en Python peut passer des commandes en Bash.
#!/usr/bin/python3
import os
os.system('rpicam-still -o im.jpg --timelapse 1000 -t 0 --width 600 --height 400')
Crée un accès à une broche GPIO :
echo 18 > /sys/class/gpio/export
Configure la broche 18 en sortie :
echo out > /sys/class/gpio/gpio18/direction
Met la broche 18 au niveau haut :
echo 1 > /sys/class/gpio/gpio18/value
Met la broche 18 au niveau bas :
echo 0 > /sys/class/gpio/gpio18/value
Supprime l'accès à la broche 18 :
echo 18 > /sys/class/gpio/unexport
Allume deux DEL pendant deux secondes puis les éteint :
#!/usr/bin/python3
import RPi.GPIO as GPIO
import time
# Initialisation
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
led1 = 23
led2 = 24
GPIO.setup(led1,GPIO.OUT)
GPIO.setup(led2,GPIO.OUT)
# Allumer les LED
print("LED allumées")
GPIO.output(led1,1)
GPIO.output(led2,1)
# Attendre 2s
time.sleep(2)
# Eteindre les LED
print("LED éteinte")
GPIO.output(led1,0)
GPIO.output(led2,0)
Pour le cas où la bibliothèque RPi.GPIO manquerait :
sudo apt install python3-rpi.gpio
marche.sh
#!/bin/bash raspistill -o im.jpg -n --tl 1000 -t 0 -w 600 -h 400 & ./serveur.py &
La sortie standard étant encombrée de messages, des redirections s'imposent également.
#!/bin/bash raspistill -o im.jpg -n --tl 1000 -t 0 -w 600 -h 400 1>/dev/null 2>/dev/null & ./serveur.py 1>/dev/null 2>/dev/null &
stop.sh
Pour stopper le tout :
#!/bin/bash killall raspistill killall serveur.py
serveur.py
Dans cette solution, les broches du GPOI sont commandées via la bibliothèque en Python.
#!/usr/bin/python3
import RPi.GPIO as GPIO
import http.server
# Configuration des GPIO pour contrôler les moteurs
GPIO.setmode(GPIO.BCM)
GPIO.setup(23, GPIO.OUT)
GPIO.setup(24, GPIO.OUT)
# Serveur Web sur le port 5555
class rep(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/G1':
GPIO.output(23, GPIO.HIGH)
elif self.path == '/D1':
GPIO.output(24, GPIO.HIGH)
elif self.path == '/G0':
GPIO.output(23, GPIO.LOW)
elif self.path == '/D0':
GPIO.output(24, GPIO.LOW)
else:
super().do_GET()
adr = ("", 5555)
http.server.HTTPServer(adr, rep).serve_forever()
index.html
Particularités de cette solution :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: #152;
text-align: center;
}
svg {
width: 100px;
height: 100px;
background-color: #cfc;
border-radius: 28px;
margin: 15px 20px 0px 20px;
}
</style>
<script>
ordre=function (val) {
var xhr=new XMLHttpRequest();
xhr.open("GET", val, true);
xhr.send(null);
};
var nb=0;
recharge=function() {
nb++;
document.images[0].src="im.jpg?nb="+nb;
};
window.onload=function() { setInterval(recharge,1000); };
</script>
</head>
<body>
<img src="im.jpg">
<br>
<svg onclick="ordre('G1');"><path d="M 25 75 L 50 20 L 75 75"/></svg>
<svg onclick="ordre('D1');"><path d="M 25 75 L 50 20 L 75 75"/></svg>
<br>
<svg onclick="ordre('G0');"><path d="M 29 29 H 71 V 71 H 29"/></svg>
<svg onclick="ordre('D0');"><path d="M 29 29 H 71 V 71 H 29"/></svg>
</body>
</html>
Plusieurs solutions sont envisageables :
Cette dernière solution se met en œuvre simplement en cliquant sur l'icône de gestion des réseaux, en haut à droite de l'écran, et en se laissant guider.
camera.py
Afficher un flux vidéo dans une page Web est un peu délicat. La solution ci-dessous provient de la documentation sur la bibliothèque picamera2, à la page 57, en suivant le lien mjpeg_server.py.
#!/usr/bin/python3
import io
import logging
import socketserver
from http import server
from threading import Condition
from picamera2 import Picamera2
from picamera2.encoders import JpegEncoder
from picamera2.outputs import FileOutput
# HTML page for the MJPEG streaming demo
PAGE = """\
<!DOCTYPE html >
<html >
<img src="stream.mjpg" width="640" height="480" >
</html >
"""
# Class to handle streaming output
class StreamingOutput(io.BufferedIOBase):
def __init__(self):
self.frame = None
self.condition = Condition()
def write(self, buf):
with self.condition:
self.frame = buf
self.condition.notify_all()
# Class to handle HTTP requests
class StreamingHandler(server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path == '/':
# Redirect root path to index.html
self.send_response(301)
self.send_header('Location', '/index.html')
self.end_headers()
elif self.path == '/index.html':
# Serve the HTML page
content = PAGE.encode('utf-8')
self.send_response(200)
self.send_header('Content-Type', 'text/html')
self.send_header('Content-Length', len(content))
self.end_headers()
self.wfile.write(content)
elif self.path == '/stream.mjpg':
# Set up MJPEG streaming
self.send_response(200)
self.send_header('Age', 0)
self.send_header('Cache-Control', 'no-cache, private')
self.send_header('Pragma', 'no-cache')
self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
self.end_headers()
try:
while True:
with output.condition:
output.condition.wait()
frame = output.frame
self.wfile.write(b'--FRAME\r\n')
self.send_header('Content-Type', 'image/jpeg')
self.send_header('Content-Length', len(frame))
self.end_headers()
self.wfile.write(frame)
self.wfile.write(b'\r\n')
except Exception as e:
logging.warning(
'Removed streaming client %s: %s',
self.client_address, str(e))
else:
# Handle 404 Not Found
self.send_error(404)
self.end_headers()
# Class to handle streaming server
class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
allow_reuse_address = True
daemon_threads = True
# Create Picamera2 instance and configure it
picam2 = Picamera2()
picam2.configure(picam2.create_video_configuration(main={"size": (640, 480)}))
output = StreamingOutput()
picam2.start_recording(JpegEncoder(), FileOutput(output))
try:
# Set up and start the streaming server
address = ('', 8000)
server = StreamingServer(address, StreamingHandler)
server.serve_forever()
finally:
# Stop recording when the script is interrupted
picam2.stop_recording()
marche.sh
Le fichier serveur.py
reste le même.
Les trois autres fichiers doivent être modifiés.
Pour démarrer les deux services :
#!/bin/bash ./camera.py 1>/dev/null 2>/dev/null & ./serveur.py 1>/dev/null 2>/dev/null &
stop.sh
Pour les arrêter :
#!/bin/bash killall camera.py killall serveur.py
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: #152;
text-align: center;
}
svg {
width: 100px;
height: 100px;
background-color: #cfc;
border-radius: 28px;
margin: 15px 20px 0px 20px;
}
</style>
<script>
ordre=function (val) {
var xhr=new XMLHttpRequest();
xhr.open("GET", val, true);
xhr.send(null);
};
</script>
</head>
<body>
<iframe src="http://raspberrypi.local:8000" width="640" height="480"></iframe>
<br>
<svg onclick="ordre('G1');"><path d="M 25 75 L 50 20 L 75 75"/></svg>
<svg onclick="ordre('D1');"><path d="M 25 75 L 50 20 L 75 75"/></svg>
<br>
<svg onclick="ordre('G0');"><path d="M 29 29 H 71 V 71 H 29"/></svg>
<svg onclick="ordre('D0');"><path d="M 29 29 H 71 V 71 H 29"/></svg>
</body>
</html>