RASPBERRY PI
OBJETS CONNECTES

Introduction

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/

Commandes en Bash

CommandeDescription
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

Photos avec la caméra

En ligne de commande

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

Quelques options

OptionParamètreDescription
--output, -oNom de fichierFichier de sauvegarde
--nopreview, -n - Pas de visualisation
--timelapse, -tlNombre de millisecondesLaps de temps entre deux images
--timeout, -tNombre de millisecondesDurée d'acquisition,
--width, -wNomre de pixelsLargeur de l'image
--height, -hNomre de pixelsHauteur de l'image
--quality, -qNombre de 1 à 99, par défaut 93Qualité de l'image
--rotation, -rotNombre de degrés Rotation de l'image
--hflip, -hf - Miroir horizontal
--vflip, -vf - Miroir vertical

En Python

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

Solution intermédiaire

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

Contrôle des broches GPIO

En ligne de commande

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

En Python

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

Repérage des broches

Exemple de serveur Web et d'IHM

Démarrage et arrêt du système

Fichier marche.sh

L'acquisition d'images et le serveur Web fonctionnent en continu. Pour garder l'invite de commande, des démarrages en tâches de fond s'imposent.

#!/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 &

Fichier stop.sh

Pour stopper le tout :

#!/bin/bash
killall raspistill
killall serveur.py

Serveur Web

Fichier 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()

Interface Web

Fichier 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>

Rendu

Connexion Wi-Fi

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.

Flux vidéo avec la caméra

Fichier 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()

Fichier 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 &

Fichier stop.sh

Pour les arrêter :

#!/bin/bash
killall camera.py
killall serveur.py

Fichier index.html

Et l'IHM :

<!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>