Raspberry Pi

Raspberry Pi OS version = 32-bit Bullseye (debian 11)
Sur Bullseye, le driver de la PiCam a été remplacé par libcamera , qui est moins ouvert et incompatible avec les applications raspicam et le module Python Picamera. Ce module est remplacé par Picamera2 mais sur Bullseye 32-bit.

Installer la camera dans Raspberry Pi OS

Se connecter en tant qu’utilisateur pi au terminal du Raspberry.

Comment vérifier que Raspberry Pi OS a bien détecté la caméra

Taper dans le terminal :

vcgencmd get_camera
  • Si la caméra n’est pas installée, la sortie est : supported=0 detected=0
  • Si la caméra est installée, la sortie est : supported=1 detected=1 et par conséquent, vous pouvez passer à l’étape suivante : Installer le module Python picamera

Comment installer la caméra dans Raspberry Pi OS

D’abord, il faut mettre à jour les dépendances de l’OS :

sudo apt-get update

Puis, pour installer la caméra, il faut lancer l’assistant de configuration :

sudo raspi-config

D’abord, on met à jour, cet assistant de configuration :

Puis on installe le module camera :

Normalement le menu va proposer un reboot, sinon le faire manuellement en tapant sudo reboot

Lorsque le Raspberry Pi aura redémarré, vérifier que la caméra est bien supportée par l’OS :

vcgencmd get_camera

Comment vérifier que le format vidéo H.264 est supporté ?

v4l2-ctl --list-formats

Dans la liste des formats vidéo supportés, H264 devrait être présent :

Diffuser le flux vidéo sur un canal RTSP

Le but est de créer un serveur de flux vidéo RTSP afin de :

  • visualiser sur un ordi du réseau avec VLC
  • ou bien intégrer cette caméra dans un système de surveillance NVR prenant en charge le RTSP.

Se connecter en tant qu’utilisateur pi au terminal du Raspberry.

Installation du serveur RTSP v4l2 v4l2rtspserver

On va installer v4l2rtspserver, un serveur RTSP pour l’API v4l2 (video for Linux 2) acceptant les formats H264, HEVC, JPEG, VP8, VP9. On clone un projet Github puis on installe :

cd /home/pi
sudo apt-get -y install cmake liblog4cpp5-dev libv4l-dev git
git clone https://github.com/mpromonet/v4l2rtspserver.git
cd v4l2rtspserver/
cmake .
make
sudo make install

Pour tester lancer la diffusion du flux vidéo d’une image 1296 x 972 @ 15 fps sur le port 8554 :

v4l2rtspserver -H 972 -W 1296 -F 15 -P 8554 /dev/video0

Regarder le flux vidéo RTSP

Sur un ordi du réseau, ouvrir VLC > ouvrir un flux réseau.

Renseigner l’URL du Raspberry Pi en utilisant son hostname (ou son adresse ip) sur le port 8554.
(rappel : Si besoin, l’adresse IP est accessible avec l’instruction ifconfig)
L’adresse a donc la forme suivante :

rtsp://<raspberry-pi-ip>:8554/unicast

Puis faire Ctrl+C pour arrêter la diffusion et récupérer l’invite de commande.

Faire tourner le serveur en tâche de fond

Modifier le service déjà installé

Pour que le processus tourne en tâche de fond, v4l2 a déjà créé un service. On va éditer le fichier :

sudo nano /lib/systemd/system/v4l2rtspserver.service

Et le modifier pour qu’il ressemble à ceci :

[Unit]
Description=V4L2 RTSP server
After=network.target

[Service]
Type=simple
Restart=always
RestartSec=1
User=pi
ExecStart=/usr/local/bin/v4l2rtspserver -H 972 -W 1296 -F 15 -P 8554 /dev/video0
WorkingDirectory=/usr/local/share/v4l2rtspserver
StartLimitIntervalSec=0

[Install]
WantedBy=multi-user.target

Taper Ctrl+O et ENTRER pour sauvegarder, Ctrl+X pour fermer.

Activer ce service en tâche de fond

Pour lancer la diffusion vidéo en tâche de fond :

systemctl start 4l2rtspserver.service

Pour stopper la diffusion vidéo :

systemctl stop 4l2rtspserver.service

Remarque : Si on souhaite que ce service se lance automatiquement à chaque démarrage alors il faut l’ajouter à la séquence de boot en :

  • rafraîchissant le daemon
  • puis l’ajoutant au boot.

Puis on reboot pour tester.

sudo systemctl daemon-reload
sudo systemctl enable v4l2rtspserver.service
sudo reboot

Après le redémarrage, on peut ouvrir VLC et afficher le flux vidéo sur VLC avec l’URL précédente.

Pour les réglages de la caméra

Par exemple pour faire une rotation de 180° de l’image :

v4l2-ctl --set-ctrl=rotate=180

La commande suivante liste les réglages disponibles, ensuite pour les appliquer il faut utiliser --set-ctrl.

v4l2-ctl --list-ctrls

Pour en savoir plus : https://github.com/mpromonet/v4l2rtspserver/

Intégration dans un NVR

Par exemple, on peut intégrer ce flux dans un système d’enregistrement de caméra de surveillance NVR, comme QVR d’un nas Qnap :

Alternative

Pour un autre serveur RTSP (en gardant désactivé la Legacy Camera dans raspi-config) :
MediaMTX : https://github.com/bluenviron/mediamtx

Pi Camera & serveur web

Sur Bullseye, il faut utiliser le module picamera2 et désactiver la caméra dans Raspiconfig.

Lien vers le manuel de picamera2 : https://datasheets.raspberrypi.com/camera/picamera2-manual.pdf

Se connecter en tant qu’utilisateur pi au terminal du Raspberry.

Installer le module Python picamera2

Comment vérifier si le module picamera2 est déjà présent

Normalement, le module est déjà présent. Pour s’en assurer, taper dans le terminal Bash (l’option -c signifie command, en effet on n’exécute qu’une ligne de commande) :

python3 -c "import picamera2"
  • Si rien ne s’affiche alors le module est déjà installé : vous pouvez passer à l’étape suivante : Créer un serveur web Python pour le streaming
  • Si vous obtenez un message d’erreur alors cela signifie que le module n’est pas présent :
    ModuleNotFoundError: No module named 'picamera'

Comment installer le module Python picamera2

On installe pip3 :

sudo apt-get update && sudo apt-get install python3 python3-pip

Puis on installe le module picamera par l’intermédiaire de pip :

pip install --user picamera

Depuis Python 3.11, pip se heurte à error: externally-managed-environment. Une solution consiste à désactiver ce fichier (en le renommant) :

sudo mv /usr/lib/python3.11/EXTERNALLY-MANAGED /usr/lib/python3.11/EXTERNALLY-MANAGED.old

Créer un serveur web Python pour le streaming

Le programme Python

Créer un fichier (ne surtout pas le nommer picamera.py car c’est le nom du module importé) :

nano /home/pi/camera.py

L’éditeur nano s’ouvre, y copier/coller le programme suivant :

# On ajoute au path l'emplacement des modules python
# ce sera utile pour lancer ce programme dès le boot
import sys
sys.path.append('/home/pi/.local/lib/python3.7/site-packages')

import io
import picamera
import logging
import socketserver
from threading import Condition
from http import server

PAGE="""\
<html>
<head>
<title>picamera MJPEG streaming demo</title>
</head>
<body>
<h1>PiCamera MJPEG Streaming Demo</h1>
<img src="stream.mjpg" width="640" height="480" />
</body>
</html>
"""

class StreamingOutput(object):
    def __init__(self):
        self.frame = None
        self.buffer = io.BytesIO()
        self.condition = Condition()

    def write(self, buf):
        if buf.startswith(b'\xff\xd8'):
            # New frame, copy the existing buffer's content and notify all
            # clients it's available
            self.buffer.truncate()
            with self.condition:
                self.frame = self.buffer.getvalue()
                self.condition.notify_all()
            self.buffer.seek(0)
        return self.buffer.write(buf)

class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/':
            self.send_response(301)
            self.send_header('Location', '/index.html')
            self.end_headers()
        elif self.path == '/index.html':
            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':
            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:
            self.send_error(404)
            self.end_headers()

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True

### Pgm ###
with picamera.PiCamera(resolution='640x480', framerate=24) as camera:
    output = StreamingOutput()

    # Si besoin d'une rotation (en degrés) :
    # camera.rotation = 90

    # Pour insérer un texte :
    # camera.annotate_text_size = 50    # réglage facultatif
    # camera.annotate_text = "Hello world!"

    # Pour changer l'apparence :
    # camera.brightness = 70  # dans 0-100 (par défaut = 50)
    # camera.contrast   = 70  # dans 0-100 (par défaut = 50)

    # Pour ajouter un filtre de la liste camera.IMAGE_EFFECTS :
    #   camera.IMAGE_EFFECTS = ['none', 'negative', 'solarize', 'sketch', 
    #       'denoise', 'emboss', 'oilpaint', 'hatch', 'gpen', 'pastel', 
    #       'watercolor', 'film', 'blur', 'saturation', 'colorswap',
    #       'washedout', 'posterise', 'colorpoint', 'colorbalance',
    #       'cartoon', 'deinterlace1', 'deinterlace2']
    # camera.image_effect = 'colorswap'

    # Pour régler l'exposition de la liste camera.IMAGE_EFFECTS (par défaut : auto)
    #   camera.IMAGE_EFFECTS = ['off', 'auto', 'night', 'nightpreview', 'backlight',
    #        'spotlight', 'sports', 'snow', 'beach', 'verylong', 'fixedfps', 'antishake',
    #        'fireworks']
    # camera.exposure_mode = 'beach

    # Pour régler la balance des blancs dans la liste camera.AWB_MODES (par défaut : auto) :
    #    camera.AWB_MODES = ['off', 'auto', 'sunlight', 'cloudy', 'shade', 'tungsten',
    #         'fluorescent', 'incandescent', 'flash', 'horizon']
    camera.awb_mode = 'tungsten'

    camera.start_recording(output, format='mjpeg')
    try:
        address = ('', 8000)
        server = StreamingServer(address, StreamingHandler)
        server.serve_forever()
    finally:
        camera.stop_recording()

Taper Ctrl+O et ENTRER pour sauvegarder, Ctrl+X pour fermer.

Pour obtenir l’emplacement des modules python, il suffit de taper dans le terminal pip3 install --user picamera
Puis de copier le chemin après « Requirement already satisfied: »

Récupérer l’adresse IP du Raspberry Pi

Dans le terminal, taper :

ifconfig

Pour se connecter par hostname :

Démarrer le serveur de web streaming

Pour démarrer le serveur, exécuter le programme précédent :

python3 camera.py

Sur un autre ordinateur (appelé client), ouvrir un navigateur et se connecter au serveur sur son port 8000. Utiliser son adresse IP ou son nom d’hôte (hostname) http://adresse_IP_du_RPi:8000

http://192.168.0.234:8000

ou

http://raspberrypi0:8000

Exécuter ce script à chaque démarrage

Créer un service

On va ajouter le programme aux fichiers de systemd.
En effet, systemd est un processus qui commande les programmes au moment du boot.

On commence par créer un nouveau fichier Unit

sudo nano /lib/systemd/system/camera.service

L’éditeur nano s’ouvre, y copier/coller le fichier unit suivant :

[Unit]
Description=Mon service de camera streaming
After=multi-user.target

[Service]
Type=idle
ExecStart=/usr/bin/python3 /home/pi/camera.py

[Install]
WantedBy=multi-user.target

Taper Ctrl+O et ENTRER pour sauvegarder, Ctrl+X pour fermer.

Ce fichier commence par définir un nouveau « service » qui sera lancé une fois que l’environnement multi-utilisateur sera disponible After=multi-user.target

Puis ExecStart donne la ligne de commande à exécuter, donc python3 camera.py
Mais, il faut tout noter en chemin absolu (car c’est le démarrage de linux), cela est valable pour python3 et pour notre script camera.py

Type=idle signifie que systemd attendra que tous les travaux en cours soient finis avant de lancer le service.

sudo chmod 644 /lib/systemd/system/camera.service

Vérifions avec un affichage long :

Rappel sur la base octale :
Le poids de chaque droit read/write/execute est :
r=100=4     w=010=2    x=001=1.

Donc pour 644 :

  • U = 6 = 110 = r + w
  • G = 4 = 100 = r
  • O = 4 = 100 = r

Ajouter ce service à la séquence de boot

Maintenant que le service a été créé, on doit :

  • rafraîchir le daemon
  • puis ajouter le service à la séquence de boot.

Puis on reboot pour tester.

sudo systemctl daemon-reload
sudo systemctl enable camera.service
sudo reboot

Après le redémarrage, on peut ouvrir le navigateur et tester l’IP du Raspberry Pi (ou son hostname) sur le port 8000.

http://raspberrypi0:8000

Installer une camera USB

Il existe plusieurs flux possibles :

  • HLS / DASH : c’est un flux H264 avec un bitrate variable, mais il y a un délai de 3s.
    HLS est de Apple, DASH est de Chrome.
  • MJPEG : pas de délai mais gourmande en bande passante, car les images JPEG sont volumineuses.
  • H264 : peu de latence et une bande passante raisonnable.

Pour vérifier que la caméra est bien reconnue :

lsusb

Pour connaître les formats vidéo supportées par la caméra :

v4l2-ctl --list-formats

Notre caméra MJPEG

Elle ne possède que le MJPEG, le flux est très lent et très saccadé.
La PiCamera est de bien meilleure qualité.

Installer motion

Installation de motion et ses dépendances (c’est très très long) :

sudo apt-get update
sudo apt-get install -y libwebp-dev ffmpeg libmariadb3 libpq5 libmicrohttpd12
sudo apt-get install -y motion

Modifier la configuration

Editer le fichier /etc/default/motion

sudo nano /etc/default/motion

Mettre start_motion_daemon=yes au lieu de start_motion_daemon=no


Editer /etc/motion/motion.conf

sudo nano /etc/motion/motion.conf

Mettre ces valeurs :

  • daemon on au lieu de daemon off
  • width 640 au lieu de width 320
  • height 480 au lieu de height 240
  • stream_localhost off au lieu de stream_localhost on (cette ligne est à la fin du fichier)
  • Notez le n° du stream_port normalement c’est 8081.

Reboot le Raspberry :

sudo reboot

Activer le service

Démarrer le streaming :

sudo systemctl start motion

Puis on peut activer le service au boot :

sudo systemctl enable motion

Installer un dongle Wifi

Voir https://www.electronicshub.org/setup-wifi-raspberry-pi-2-using-usb-dongle/

Description

Modèle : tp-link TL-WN725N Version 3.0

  • Vitesse : 150 Mbps
  • Wifi N (2.4 GHz) donc 802.11n

Reconnaissance par l’OS

Brancher le dongle puis démarrer le raspberry. Se connecter en tant que pi.

Pour vérifier que le dongle wifi est bien reconnu par l’OS, on va utiliser la commande dmesg (abréviation de « display message ») car elle affiche les messages de la mémoire tampon du noyau.
Comme ces messages représentent beaucoup beaucoup de lignes, alors on va ajouter un pipe | et l’option more pour avoir un affichage page par page.

En appuyant sur la touche ESPACE, on passe d’une page à la suivante.

Sur la dernière page, vous devriez voir le dongle USB : le wifi 802.11n et le fabriquant Realtek.

dmesg | more

Ajouter l’interface wifi

Pour ajouter cette nouvelle interface, on édite le fichier des interfaces :

sudo nano /etc/network/interfaces

Le fichier est possiblement comme ceci :

Puis ajouter ces lignes (si elles ne sont pas déjà présentes) :
Rappel : Pour coller dans Linux, il faut faire un clic droit.

auto lo
iface lo inet loopback
iface eth0 inet manual
auto wlan0
allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf

Taper Ctrl+O et ENTRER pour sauvegarder, Ctrl+X pour fermer.

Le code du wifi

Puis il faut ajouter les codes de connexion dans le fichier cité ci-dessus :

sudo nano /etc/wpa_supplicant/wpa_supplicant.conf

Et ajouter les lignes suivantes en renseignant correctement :

  • le SSID : le nom du réseau wifi
  • le mot de passe de connexion
country=FR

network={
        ssid="nom du réseau wifi"
        scan_ssid=1
        psk="mot de passe du réseau wifi"
#        proto=RSN
        key_mgmt=WPA-PSK
#        pairwise=CCMP TKIP
#        group=CCMP TKIP
#        id_str="nom du réseau wifi"
}

Plusieurs réseaux wifi

On peut également insérer plusieurs réseaux wifi et ajouter une priorité (s’ils sont présents simultanément).

country=FR

network={
        ssid="réseau1"
        psk="mot de passe 1"
#        proto=RSN
        key_mgmt=WPA-PSK
#        pairwise=CCMP TKIP
#        group=CCMP TKIP
      priority=5
}

network={
        ssid="réseau2"
        psk="mot de passe 2"
#        proto=RSN
        key_mgmt=WPA-PSK
#        pairwise=CCMP TKIP
#        group=CCMP TKIP
      priority=2
}

Eteindre et essayer

  • On éteint le Raspberry Pi : sudo poweroff
  • On débranche le câble Ethernet
  • >> Brancher un écran HDMI pour lire l’adresse IP <<
  • On rallume le Raspberry Pi

Liaison série Microbit & Raspberry

Liaison série

Une liaison série nécessite l’utilisation de 2 fils (3 avec la masse) entre les 2 équipements.

  • TX : borne de transmission du signal.
  • RX : borne de réception du signal.

La liaison série va se faire au travers du câble USB.

Pré-requis sur le Raspberry Pi

Sur le Raspberry Pi, on installe python3 et pip3 .

sudo apt-get update && sudo apt-get install -y python3 python3-pip

Puis on télécharge/installe le module pyserial avec pip3.
Ce module pyserial permet de gérer une liaison série, mais dans le programme on écrira import serial .

pip3 install --user pyserial

Microbit => Raspberry

Microbit utilise la fonction print pour envoyer un signal sur la liaison série.
Le baudrate de cette liaison est 115200.

Sur Microbit : le programme émetteur

Avec l’éditeur Mu, on stocke ce programme dans la Microbit.
En réalité Mu utilise aussi une liaison série pour communiquer avec la microbit.

# Pgm Microbit : l'émetteur de la liaison série
from microbit import *

while True:
    print("1")        # envoi sur la liaison série (port microUSB)
    display.show("1") # affichage led, juste pour vérifier
    sleep(1)          # pause de 1 seconde
    print("2")
    display.show("2")
    sleep(1)

Sur Raspberry Pi : le programme récepteur

Puis on connecte la Microbit à un port usb du Raspberry : l’alimentation usb lance donc immédiatement le programme « émetteur » de la Microbit.
Ainsi, une liaison série se crée avec Raspberry OS.
Pour trouver le n° de port série, on liste :

ls -l /dev/ttyA*

Ou bien ainsi :

ls -l /dev/serial/by-id

Puis on écrit le programme python nano recepteur.py
Pour ajuster le timeout, des explications sont fournies plus bas.

Pour trouver le port série en liaison avec la microbit : voir la fonction python plus bas.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

### Pgm Raspberry Pi : le récepteur de la liaison série ###

# On ajoute au path l'emplacement des modules python
# ce sera utile pour lancer ce programme dès le boot
import sys
sys.path.append('/home/pi/.local/lib/python3.7/site-packages')
sys.path.append('/home/pi/.local/bin')

import serial, time

# facultatif :
print(f"Receveur de liaison série python, version : {serial.VERSION}")

# initialisation de la liaison série
PORT = "/dev/ttyACM0"  # remplacer par le n° port ou utiliser une fct de recherche

# On ouvre une liaison série sur le port "/dev/ttyACM0"
# 3 valeurs de timeout : None, 0, un flottant
s = serial.Serial(PORT, baudrate=115200, timeout=1)
time.sleep(0.1)  # attendre 100ms que la connexion soit prête

s.reset_input_buffer()   # vide le buffer des entrées (input buffer)
s.reset_output_buffer() # et le buffer des sorties (output buffer)

while True:
    try:
        # On lit une ligne encodée, enlève le '\n' puis décode en str. 
        data = s.readline().rstrip().decode('UTF-8')
        print(data)   # affichage dans REPL
    except:
        pass

s.close()

Enfin on lance le programme avec la ligne de commande bash :

sudo python3 recepteur.py

Ctrl+Z permet d’arrêter le programme Python sur le Raspberry Pi.

Pensez aussi à vider régulièrement les buffers (par exemple dans la boucle while).

Note sur read() et readline() :

  • s.read() lit 1 byte, donc 1 octet car, par défaut dans serial, 1 byte = 8 bits.
    Le type de la valeur retournée est bytes , il s’agit d’une chaine de caractère encodée. L’encodage peut être UTF-8, UTF-16, windows-1255 … Donc en décodant le bytes, on récupère un str.
  • s.read(5) lit 5 bytes (5 est une un exemple d’entier), donc 5 octets. Mais si un timeout a été mis alors la lecture peut se terminer prématurément et retourner moins de 5 octets. Type de la valeur retournée : bytes.
  • s.readline() lit au maximum une ligne, c’est-à-dire une chaîne se terminant par le marqueur de fin de ligne binaire b'\n'. Le marqueur b'\n' est inclus à la fin de la valeur retournée.
    Il est conseillé de mettre un timeout pour ne pas bloquer le programme en cas d’absence de b'\n'.
  • s.readlines() tente de lire « toutes » les lignes, mais comme la liaison est ouverte alors c’est confus. Pour clarifier, on met un timeout et s.readlines() retourne la liste de lignes reçues, mais le b'\n' n’est pas inclus.

Note sur le timeout  :

Le timeout ne concerne que la lecture, donc read, readline, readlines :

  • timeout = None est bloquant (valeur par défaut). L’instruction attend … attend … jusqu’à recevoir le nombre attendu d’octets, par exemple 5 octets pour s.read(5)
  • timeout = 0 est non bloquant. Il retourne immédiatement 0 ou plusieurs octets (dans la limite du nombre d’octets attendus, par exemple 5 octets pour s.read(5)).
  • timeout = 2.5  met une durée maximum de 2.5 secondes (2.5 est juste un exemple de flottant). Si le nombre d’octets a été reçu avant la fin du timeout alors le message est retourné, sinon il attend la fin du timeout et retourne les octets reçus jusque-là.

Note sur la durée de transmission :

Chaque caractère transmis occupe 10 bits (par défaut on compte 8 bits de data + 1 bit de début + 1 bit de fin) à une vitesse de 115 200 bits par seconde, donc 100 caractères prennent environ 9 ms.

Documentation sur la classe Serial de pySerial : lien pdf

Trouver le port série en liaison avec la microbit :

import os

def port_microbit():
    if os.path.isdir('/dev/serial/by-id'):
        # liste_équipmt  est une liste de str (le nom du fichier crée par l'OS)
        liste_équipmt = os.listdir('/dev/serial/by-id')
        print(f"La liste des équipmts série : {liste_équipmt}")

        # on ne garde que les équipmts microbit
        liste_microbit = [ équipmt for équipmt in liste_équipmt \
                        if 'usb-ARM_BBC_micro:bit' in équipmt]
        print(f"La liste des microbits : {liste_microbit}")

        # on suppose qu'on n'a branché qu'une microbit, donc la gagnante est [0]
        # pour récupérer le nom raccourci du port, on suit les symlink
        symlink = os.path.join('/dev/serial/by-id', liste_microbit[0])
        from pathlib import Path
        return str(Path(symlink).resolve())
    else:
        print('le répertoire n''a pas été trouvé')
        return None

print(port_microbit())   # "/dev/ttyACM0"

Raspberry => Microbit

Sur Raspberry Pi : le programme émetteur

Sur le Raspberry, faire nano envoyeur.py et copier/coller ceci :

#!/usr/bin/env python
# -*- coding: utf-8 -*-

### Pgm Raspberry Pi : l'envoyeur de la liaison série ###

# On ajoute au path l'emplacement des modules python
# ce sera utile pour lancer ce programme dès le boot
import sys
sys.path.append('/home/pi/.local/lib/python3.7/site-packages')
sys.path.append('/home/pi/.local/bin')

import serial, time

# facultatif :
print(f"Receveur de liaison série python, version : {serial.VERSION}")

# initialisation de la liaison série
PORT = "/dev/ttyACM0"  # remplacer par le n° port ou utiliser une fct de recherche

# On ouvre une liaison série sur le port "/dev/ttyACM0"
# 3 valeurs de timeout : None, 0, un flottant
s = serial.Serial(PORT, baudrate=115200, timeout=1)
time.sleep(0.1)     # attendre 100ms que la connexion soit prête

# 
s.reset_input_buffer()   # vide le buffer des entrées (input buffer)
s.reset_output_buffer() # et le buffer des sorties (output buffer)

while True:
    s.write("Hello World !".encode('UTF-8'))
    print("message envoyé")
    time.sleep(1)    # attendre 1 seconde (module time)

Pensez aussi à vider régulièrement les buffers (par exemple dans la boucle while).

Sur Microbit : le programme récepteur

Avec l’éditeur Mu, on stocke ce programme dans la Microbit (en réalité Mu utilise aussi une liaison série pour communiquer avec la microbit).

# Pgm Microbit : Récepteur de liaison série
from microbit import *

# Le module uart est à l'intérieur du module microbit
uart.init(baudrate=115200)  

while True:
    if uart.any(): # si de la data attend dans le buffer
        data = str(uart.read(), 'UTF-8')
        display.scroll(data)
    sleep(1000)  # 1 seconde (module microbit)

Note sur le timeout de Microbit

Le timeout de Microbit est non modifiable par Python, le timeout d’un caractère est calculé en milliseconde selon la formule :
microbit_uart_timeout_char = 13000 / baudrate + 1
Pour un baudrate de 115200, on obtient 1.2 ms .
Taille du buffer de réception de la Microbit = 64 octets.

Note sur read et readline

Les fonctions read et readline de Microbit sont toutes NON BLOQUANTES :

  • read() lit autant d’octets que possible … dans la limite du timeout et du buffer (64 octets).
    La valeur retournée est de type bytes, on peut la convertir en str :
    txt = str(uart.read(), 'UTF-8')
    Si le read_buffer est resté vide jusqu’au timeout alors elle renvoie None.
  • read(6) lit 6 octets. Mais si le timeout a été atteint alors elle ne renvoie que les octets reçus jusque là.
  • readline() lit une séquence jusqu’à trouver le marqueur de fin de ligne. Elle retourne les bytes reçus y compris le marqueur de fin de ligne.
    Si le timeout a été atteint alors elle renvoie None.

Lorsque le buffer est plein, les nouvelles données arrivantes sont ignorées, donc perdues.

Microbit <=> Raspberry

La liaison bidirectionnelle est tout à fait possible : il suffit d’assembler pour chaque équipement les 2 bouts de programme : envoyeur + récepteur.

Le programme suivant envoie un entier c d’un équipement à l’autre, puis dès que l’un d’eux reçoit un entier , il incrémente cet entier puis le renvoie à l’autre équipement.

C’est la microbit qui démarre ce ping-pong en envoyant 1 au Raspberry Pi.

Conséquence : La microbit ne reçoit que des entiers pairs et envoie des entiers impairs.
Et inversement pour le Raspberry Pi.

Sur Microbit : programme bidirectionnel

Au tout début, la microbit envoie l’entier c = 1
Puis elle reçoit en retour, une chaîne data qui contient un nouvel entier.
La microbit incrémente l’entier et le renvoie au Raspberry Pi.

# Pgm Microbit : liaison série bidirectionnelle
from microbit import *

uart.init(baudrate=115200)

# L'entier de départ : c'est la microbit qui commence le ping pong
c = 1

while True:
    # Tant qu'on ne reçoit pas de réponse, on répète l'envoi
    while not uart.any():
        print(c)            # envoi l'entier c vers RPi
        sleep(1000)     # 1 seconde (selon le module microbit)

    # On lit la réponse reçue
    data = str(uart.read(), 'UTF-8')
    display.scroll(data) # on l'affiche sur les leds
    c = int(data) + 1    # on incrémente l'entier reçu puis on fait une boucle d'envoi

Sur Raspberry Pi : programme bidirectionnel

Le Raspberry Pi reçoit une chaîne data qui contient un entier.
Le Raspberry Pi incrémente cet entier et le renvoie à la microbit.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

### Pgm Raspberry Pi : liaison série bidirectionnelle ###

# On ajoute au path l'emplacement des modules python
# ce sera utile pour lancer ce programme dès le boot
import sys
sys.path.append('/home/pi/.local/lib/python3.7/site-packages')
sys.path.append('/home/pi/.local/bin')

import os, serial, time

def port_microbit():
    if os.path.isdir('/dev/serial/by-id'):
        # liste_équipmt  est une liste de str (le nom du fichier crée par l'OS)
        liste_équipmt = os.listdir('/dev/serial/by-id')
        # on ne garde que les équipmts microbit
        liste_microbit = [ équipmt for équipmt in liste_équipmt \
                        if 'usb-ARM_BBC_micro:bit' in équipmt]

        # on suppose qu'on n'a branché qu'une microbit, donc la gagnante est [0]
        # pour récupérer le nom raccourci du port, on suit les symlink
        symlink = os.path.join('/dev/serial/by-id', liste_microbit[0])
        from pathlib import Path
        return str(Path(symlink).resolve())
    else:
        print('le répertoire n''a pas été trouvé')
        return None


# initialisation de la liaison série en mode BLOQUANT
port_BBC = port_microbit()
s = serial.Serial(port_BBC, baudrate=115200, timeout=None)
time.sleep(0.1)  # attendre 100ms que la connexion soit prête

while True:
    try:
        data = s.readline().rstrip().decode('UTF-8') # on lit juste 1 ligne
        s.reset_input_buffer()   # vide le buffer des entrées
        print(data)                   # affichage dans REPL
        c = int(data) + 1          # on incrémente l'entier reçu

        s.reset_output_buffer()  # on vide le buffer des sorties
        s.write(str(c).encode('UTF-8'))
        print(f"message envoyé : {c}")
        time.sleep(1)            # attendre 1 seconde (module time)
    except:
        pass

s.close()

Les trames d’une liaison série

Une trame UART de 10 bits (1 bit début + 8 bits data + 1 bit stop)

Une trame UART est constituée des bits suivants :

  • un bit de début toujours à 0 : servant à la synchronisation du récepteur
  • les données : leur taille est comprise entre 5 et 9 bits.
    Les bits sont envoyés « à l’envers » : du LSB au MSB.
  • un bit de parité : paire ou impaire (optionnel)
  • un bit de stop, toujours à 1. La durée du bit de stop varie entre 1, 1.5 et 2.

Le niveau logique de repos est le 1.

Installer un écran LCD 3.5″

Il y a 3 type de connexion d’écran :

  • DSI : le plus cher, mais il laisse disponible les 2 ports : HDMI & GPIO.
  • HDMI : plus abordable mais il faut ajouter un port usb pour l’alimenter.
  • GPIO : le moins cher mais il monopolise le port GPIO. C’est celui présenté ci-dessous.

Caractéristiques

L’écran LCD est incompatible avec la Pi Camera
Les instructions du constructeur datent de 2016 sur Raspbian Wheezy.
Problème référencé en 2022 : https://github.com/goodtft/LCD-show/issues/313

Nom : 3.5inch RPi LCD (A)

  • Compatible avec Raspberry Pi A, B, A+, B+, 2B, 3B, 3B+, 4B
  • Resolution : 320×480
  • Ecran LCD de type TFT
  • Ecran tactile de type résistif
  • Controller tactile : XPT2046
  • Interface : SPI

Wiki du constructeur : https://www.waveshare.com/wiki/3.5inch_RPi_LCD_(A)
Fiche produit du constructeur : https://www.waveshare.com/product/3.5inch-rpi-lcd-a.htm

Connecter l’écran

Connecter l’écran sur les 26 broches du GPIO (qui lui, possède 40 broches), comme ci-dessous :

Ensuite, on télécharge et installe les drivers dans Raspberry Pi OS.

Télécharger les pilotes (drivers)

Les pilotes sont disponibles dans un dépot Github : https://github.com/waveshare/LCD-show
Il s’agit d’un dossier nommé LCD-show à télécharger (cloner) avec le logiciel git.

Installons le logiciel git :

sudo apt-get install git

Et valider l’installation en tapant : Y

Une fois git installé, on va créer par précaution un répertoire temporaire dans notre home ~ pour télécharger/cloner les pilotes LCD :

cd ~
mkdir temp
cd temp
git clone https://github.com/waveshare/LCD-show.git

Vérifions le téléchargement avec un « affichage long » pour avoir tous les détails :

cd LCD-show
ls -l

Vérifier que le fichier LCD35-show est bien exécutable.
Si besoin, on le rend exécutable en tapant : chmod +x LCD35-show

Puis on lance l’installation depuis le répertoire courant, qui est donc le répertoire .

Attention, la commande change selon le type d’OS :

  • Sur Raspberry Pi OS lite (c’est notre version), on exécute : sudo ./LCD35-show lite
  • Sur Raspberry Pi OS, on exécute : sudo ./LCD35-show

Et, valider l’installation en tapant : Y

Remarque : Normalement une exécution de fichier se fait depuis un répertoire, donc en tapant dossier/fichier
Cependant, si on se situe déjà dans le répertoire alors c’est le répertoire courant qu’on spécifie ./fichier


A la fin de l’installation, le Raspberry redémarre, donc la connexion SSH se coupe.
… Attendre 4 min …
Puis se reconnecter.


Supprimer le répertoire temporaire :
On peut dorénavant supprimer le répertoire temp :

  • il faut d’abord se déplacer à l’extérieur du répertoire avec cd
  • puis on fait une suppression récursive rm -r de ce répertoire et de tous les répertoires qu’il contient.
cd ~
rm -r ~/temp

En cas de problème : tester ces pilotes https://github.com/goodtft/LCD-show
et garder exactement les mêmes lignes de commande.

Installer Raspberry Pi OS

On va installer l’OS sur une carte micro-SD de capacité 2 Go ou plus.

A partir d’un ordi Windows

Télécharger une image de Raspberry Pi OS

Une version lite est suffisante :

Puis il faut décompresser l’archive ZIP téléchargée, on obtient alors un fichier *.img

Télécharger Rufus

Pour copier/installer le fichier image sur une carte micro-SD, il faut installer un logiciel de type « disk imager ».
Par exemple : Rufus (Windows) : https://rufus.ie/fr/

Copier l’image sur la carte micro-SD

Insérer la micro-SD dans l’adaptateur … puis dans l’ordinateur.

Ouvrir Rufus :

  • Périphérique : sélectionner la carte SD.
  • Type de démarrage : laisser par défaut (image disque ou ISO)
  • SELECTION : Choisir le fichier précédemment téléchargé puis décompressé *.img

A la fin du flash, la carte contient alors 2 partitions :

  • une partition Linux
  • une partition boot (de type Fat32) qui pourra être lue par Linux et Windows.

Ajouter un accès SSH

Par défaut, l’accès SSH est désactivé.
Pour l’activer, il faut simplement ajouter un nouveau fichier vide nommé ssh
/!\ Il s’agit d’un fichier sans extension.

Ejecter la micro-SD puis la ré-insérer dans l’ordinateur.

Windows va proposer de formater la partition Linux (plusieurs fois) : il faut refuser le formatage.

Dans l’explorateur, ouvrir la micro-SD (c’est la partition boot) et ajouter un nouveau fichier texte.

Vérifier que l’affichage des extensions de fichiers est activé : dans l’explorateur de fichiers > onglet Affichage.

Renommer le fichier « Nouveau document texte.txt » en « ssh »

A partir d’une distribution Linux (en ligne de commande)

1ère étape : On se connecte en tant que root.
On met à jour la distribution de l’OS :

apt-get update && apt-get -y dist-upgrade

2e étape : On insère la carte SD dans l’ordi linux (grâce à un adaptateur USB-microSD).
On va récupérer le nom du device (qui sera sûrement /dev/sda) et ses partitions (ex : /dev/sda1 et /dev/sda2) :

fdisk -l

3ème étape : On fait vérifier l’intégrité de chaque partition et accepte les réparations (fix) le cas échéant :

fsck /dev/sda1
# idem avec /dev/sda2

4ème étape : Pour préparer la carte SD au flash, on va la repartitionner et reformater.
Il faut d’abord s’assurer que la carte ne soit pas « montée » (c’est-à-dire un disque en lecture dans l’OS).
On affiche la liste des disques montés :

df -h

Si jamais la carte SD est montée alors il faut démonter toutes les partitions : umount /dev/sda1 et umount /dev/sda2

5ème étape : On va supprimer toutes les partitions avec l’utilitaire fdisk

fdisk /dev/sda

Taper d (pour delete).

S’il y a plusieurs partitions, alors fdisk va demander le n° de la partition à supprimer.
Recommencer pour supprimer chaque partition, en tapant d.

A la fin, taper w (pour écrire et sauvegarder).

6ème étape : On va recréer une partition de type ms-dos et la formater avec l’outils parted.

parted /dev/sda

Puis pour les commandes, taper :

  • mklabel msdos (pour créer une table) puis Yes pour valider
  • print pour voir la taille du disque, ici c’est 8053 MB
  • mkpart primary ext4 1MB 8053MB pour créer une partition de 8 Go, il faut adapter selon la taille affichée avec print
  • quit

8ème étape : On télécharge une image de Raspberry Pi OS.
Prenons une 32bit-BULLSEYE (debian 11) en lite (sans desktop).

cd /home/pi
wget "https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2022-09-26/2022-09-22-raspios-bullseye-armhf-lite.img.xz"

Vérifions avec la commande ls :

9ème étape : On décompresse l’image téléchargée *.xz (c’est très long !!) puis on flashe la carte CD avec cette image (c’est très long aussi).

xz --decompress *.xz                                                                                           
dd bs=1M if=2022-09-22-raspios-bullseye-armhf-lite.img of=/dev/sda status=progress conv=fsync

Note : bs (block size) = 1M est une vitesse lente car la vitesse normale est plutôt 4M.

10ème étape : Pour placer les fichiers ssh.txt et userconf.txt, on a besoin de monter les 2 partitions de la carte SD (qui ont été créées par le flashage).
On va créer 2 points de montage en /mnt/boot et /mnt/raspbian (ou 2 autres répertoires vides).

mkdir /mnt/boot
mkdir /mnt/raspbian
mount /dev/sda1 /mnt/boot
mount /dev/sda2 /mnt/raspbian

On crée un fichier vide ssh.txt dans la partition boot de la carte SD :

touch /mnt/boot/ssh.txt

On ajoute une marge à gauche et en bas (positive) pour éviter les débordement hors de l’écran, à la fin du fichier config.txt :

cat >> /mnt/boot/config.txt <<'EOF'
overscan_left=50
overscan_bottom=50
EOF

On chiffre le mot de passe « nsirennes » :

echo 'nsirennes' | openssl passwd -6 -stdin

Puis on affecte ce mot de passe à l’utilisateur pi dans un fichier userconf.txt sous la forme pi:mot_de_passe_hashé :

cat > /mnt/boot/userconf.txt <<'PWDEOF'
pi:$6$kbPS1eG8Vnvkw4q1$s/e6kzL2S0k9xaVvIYt27HtOnL8lENC8QxiR.h/82tFJoX/0O18lW4VJIq9wLw9oCiXRMbXonUYVvR.3V7UxI1
PWDEOF

Note : à chaque fois qu’on lance un hashage, on obtient un mot de passe chiffré différent car le programme utilise une petite dose d’aléatoire (appelé « sel » ou « salt »).

11ème étape : on démonte les partitions :

cd ~
umount /mnt/boot
umount /mnt/raspbian
rm -rf /mnt/boot
rm -rf /mnt/raspbian

Configurer l’OS

  • Ejecter la carte micro-SD de l’ordinateur et l’insérer dans le Raspberry Pi
  • Relier le Raspberry Pi avec un câble Ethernet
  • Alimenter le Raspberry Pi avec un câble micro-usb.

Attendre 3 minutes (le temps du démarrage).

Ouvrir Putty pour démarrer une connexion SSH (port 22) :
Hostname : raspberrypi Port : 22

Le login de connexion est :
Login : pi Mot de passe : raspberry

Puis pour autoriser un accès root, on change son mot de passe :

sudo passwd root

et choisir un mot de passe : ******

Puis se connecter en tant que superutilisateur root :

su root

On change le mot de passe de l’utilisateur pi (pour éviter les message d’alerte de mot de passe inchangé) :

passwd pi

et choisir un mot de passe : ******

Puis copier/coller ceci afin permettre :

  • la connexion SSH en tant que root
  • changer le clavier en AZERTY
  • ajouter la coloration syntaxique dans Bash.

Rappel : Dans un terminal linux, il faut faire clic droit pour coller.

mkdir -p /root/dos
touch /root/hello.c
touch /root/hello.js
sed -i "s/#PermitRootLogin .*/PermitRootLogin yes/1" /etc/ssh/sshd_config
systemctl restart ssh

rm -f /etc/localtime
echo "Europe/Paris" >/etc/timezone
dpkg-reconfigure -f noninteractive tzdata
cat >/etc/default/keyboard <<'KBEOF'
XKBMODEL="pc105"
XKBLAYOUT="fr"
XKBVARIANT=""
XKBOPTIONS=""

KBEOF
dpkg-reconfigure -f noninteractive keyboard-configuration

cat >> ~/.bashrc <<"EOF"
alias ls='ls --color=auto'
force_color_prompt=yes
if [ -n "$force_color_prompt" ]; then
    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
        # We have color support; assume it's compliant with Ecma-48
        # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
        # a case would tend to support setf rather than setaf.)
        color_prompt=yes
    else
        color_prompt=
    fi
fi
if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w \$\[\033[00m\] '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

EOF

Changer le hostname

Si on veut mettre plusieurs Raspberry Pi sur le réseau alors il faut leur donner des HostName différents.

Voici comment renommer le hostname en raspberrypi0 :

sed -i "s/\(^127\.0\.1\.1.*raspberrypi.*$\)/127\.0\.1\.1     raspberrypi0/1" /etc/hosts
sed -i "s/raspberrypi/raspberrypi0/1" /etc/hostname

Mettre à jour le Firmware du Raspberry

sudo apt-get install rpi-update
sudo rpi-update

En cas de problème de partition (carte SD)

Si Rufus refuse la carte SD alors :

  • touche Windows + R et taper diskpart
  • taper : list volume
  • si la carte SD est le volume 3 alors taper : select volume 3
  • pour supprimer taper : delete volume

Puis on fait un nettoyage :

  • taper : list disk
  • si la carte SD est le disque 1 alors taper : select disk 1
  • taper : clean
  • pour quitter, taper : exit

En cas de problème : carte SD & explorateur

Dans la barre de recherche Windows : taper partition de disque

Puis il faut lui assigner une lettre de disque, par exemple : D

Pour tester la réelle capacité de la SDcard

Beaucoup de carte SD sont « fake » :

  • Elles ont une capacité moindre.
    Mais leur firmware mentionne le contraire à l’hôte : par conséquent, les données débordantes sont silencieusement ignorées.
  • Elles ont une vitesse lente de lecture/écriture

Sur Windows, le logiciel H2testw teste ces 2 défaillances :

Sur Linux, c’est F3.

Sur Mac, c’est F3X ou F3XSwift.