Table des matières

Système d'ouverture du garage et de la porte arrière

Le boîtier rose fixé près du garage contient un Arduino Uno (clone AZ-Delivery), une télécommande 433 MHz de la porte de garage et le lecteur de badges RFID. Il est branché en USB sur le serveur Atom (192.168.42.1), qui le pilote de deux façons :

Architecture : ouverture du garage et de la porte arrière (Archify)

Schéma réalisé avec Archify ; source : docs/boitier-garage/archify/garage.architecture.json dans le dépôt AdminLova.

Électronique

Schéma de câblage du boîtier (Fritzing)

Schéma Fritzing modifiable : boitier_garage_fritzing.zip (contient boitier-garage.fzz, à ouvrir avec Fritzing)

Vue d'ensemble de l'intérieur du boîtier Arduino Uno AZ-Delivery, résistances sur D9–D11 Télécommande 433 MHz HXQ920BT-4 alimentée par l'Arduino

Broche Arduino Fonction Fonctionnement
D9 Bouton de la télécommande du garage Mise à la masse pendant 800 ms = appui (ouvre ou ferme)
D10 Télécommande de la serrure : ouvrir Impulsion 5 V de 2,5 s
D11 Télécommande de la serrure : fermer Impulsion 5 V de 2,5 s
USB Liaison avec l'Atom Série 9600 bauds, puce CH340

La télécommande du garage (PCB vert HXQ920BT-4) n'a plus de pile : elle est alimentée par l'Arduino. Les boutons de la télécommande de la serrure sont court-circuités par D10 / D11.

Anciennement, un servomoteur appuyait physiquement sur le bouton de la télécommande ; il a été remplacé par la commande électrique de D9 (code servo conservé en commentaire ci-dessous).

Firmware Arduino (LOV garage opener v3)

Protocole : un caractère sur le port série (9600 bauds). O = garage, E = ouvrir la porte arrière, L = fermer la porte arrière. Tout autre caractère est ignoré. Au démarrage, l'Arduino affiche LOV garage opener v3.

#include <Servo.h>
 
Servo servo;
 
void setup() {
  pinMode(10, OUTPUT);
  digitalWrite(10, LOW);
  pinMode(11, OUTPUT);
  digitalWrite(11, LOW);
 
  Serial.begin(9600);
  Serial.println("LOV garage opener v3");
 
  /*servo.attach(9);
  servo.write(0);
  delay(400);
  servo.detach();*/
  pinMode(9, INPUT);
  digitalWrite(9, HIGH);
}
 
void loop() {
    int c = 0;
    c = Serial.read();
    if (c == 'O') {
      Serial.println("opening garage...");
      /*servo.attach(9);
      servo.write(30);
      delay(1000);
      servo.write(0);
      delay(400);
      servo.detach();*/
      pinMode(9, OUTPUT);
      digitalWrite(9, LOW);
      delay(800);
      pinMode(9, INPUT);
      digitalWrite(9, HIGH);
      Serial.println("done");
    } else if (c == 'E') {
      //Serial data for shorting the remote controller of backdoor
      Serial.println("opening back door...");
      digitalWrite(10, HIGH);
      delay(2500);
      digitalWrite(10, LOW);
      Serial.println("done");
    } else if (c == 'L') {
      //Serial data for shorting the remote controller of backdoor
      Serial.println("closing back door...");
      digitalWrite(11, HIGH);
      delay(2500);
      digitalWrite(11, LOW);
      Serial.println("done");
    }
}

Vérifié le 10/10/2026 : ce code correspond au firmware présent sur la carte.

Serveur (Atom)

Pré-requis :

apt install python3-flask python3-serial python3-evdev inotify-tools tmux nginx
adduser lov dialout

Fichiers dans /home/lov/garage :

Fichier Rôle
server.py Serveur Flask (127.0.0.1:8001) qui envoie O / E / L à l'Arduino (/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0)
gate-service.sh Surveille rfid.log, cherche le badge dans perm.list et ouvre le garage
rfid.py Lit le lecteur RFID USB et ajoute chaque badge lu à rfid.log
perm.list Badges autorisés, une ligne ID pseudo
rfid.log Journal de tous les badges passés

nginx écoute sur le port 4280 et ne transmet au serveur Flask que les URL secrètes (voir /etc/nginx/sites-enabled/default sur l'Atom — ne pas les recopier sur le wiki) :

Route Déclenchée par Action Contrôle
ouverture locale (GET) gate-service.sh (badge de l'atom) et bouton garage du Pi de la porte garage aucun : réservé au réseau local
garage (POST) commande slash Framateam garage canal garage ou canal porte
entrer (POST) commande slash Framateam ouvre la porte arrière + message « X a ouvert le local » canal porte
sortir (POST) commande slash Framateam ferme la porte arrière + message « X a fermé le local » canal porte

Code du serveur (server.py)

Version en service sur l'atom (/home/lov/garage/server.py). Les chemins d'URL et les identifiants de canaux Framateam sont secrets : ils sont remplacés ci-dessous par <chemin-…> et <id-canal-…>. Les vraies valeurs sont dans le fichier sur l'atom.

from flask import Flask, request, jsonify
app = Flask(__name__)
import serial
 
ARDUINO = '/dev/serial/by-id/usb-1a86_USB_Serial-if00-port0'
GARAGE_CHANNEL = '<id-canal-garage>'
PORTE_CHANNEL = '<id-canal-porte>'
 
def send_command(c):
    try:
        with serial.Serial(port=ARDUINO, baudrate=9600) as f:
            banner = f.readline().decode('ascii')
            if 'LOV' not in banner:
                return "didn't find the garage opener banner"
            f.write(c)
            f.flush()
            resp = f.readline().decode('ascii')
            return "\n".join((banner, resp))
    except serial.serialutil.SerialException as e:
        return str(e)
 
# uncondtionnally open the garage (for local usage)
@app.route('/<chemin-local>', methods=['GET'])
def local_open():
    return send_command(b'O')
 
@app.route('/<chemin-garage>', methods=['POST'])
def open():
    user_name = request.form.get('user_name', 'unknown user')
    channel_id = request.form.get('channel_id', '')
    if channel_id != GARAGE_CHANNEL and channel_id != PORTE_CHANNEL:
        return "this is not the command you are looking for"
    return send_command(b'O')
 
@app.route('/<chemin-entrer>', methods=['POST'])
def enter():
    user_name = request.form.get('user_name', 'unknown user')
    channel_id = request.form.get('channel_id', '')
    if channel_id != PORTE_CHANNEL:
        return "this is not the command you are looking for"
    r = send_command(b'E')
    return jsonify({
        "response_type": "ephemeral",
        "text": r,
        "extra_responses": [{
            "response_type": "in_channel",
            "text": "%s a ouvert le local" % user_name,
            "channel_id": "<id-canal-annonces>"
        }]
    })
 
@app.route('/<chemin-sortir>', methods=['POST'])
def leave():
    user_name = request.form.get('user_name', 'unknown user')
    channel_id = request.form.get('channel_id', '')
    if channel_id != PORTE_CHANNEL:
        return "this is not the command you are looking for"
    r = send_command(b'L')
    return jsonify({
        "response_type": "ephemeral",
        "text": r,
        "extra_responses": [{
            "response_type": "in_channel",
            "text": "%s a fermé le local" % user_name,
            "channel_id": "<id-canal-annonces>"
        }]
    })
 
if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8001)

Fonctionnement :

Code du lecteur de badges (gate-service.sh)

#!/bin/bash
 
cd $(dirname "$0")
LOG=./rfid.log
 
(while true ; do python3 rfid.py ; sleep 1 ; done) &
 
validate_id() {
	awk "/^$1 / { print \$2 }" ./perm.list
}
 
while true; do
	inotifywait -qq -e modify "$LOG"
	date
	l=$(tail -n 1 "$LOG")
	if ! egrep -q '^[0-9A-Z]{8}' <<< "$l" ; then
		echo "invalid read: $l"
		continue
	fi
	c=$(cut -d ' ' -f 2 <<< "$l")
	name=$(validate_id "$c")
	if [ -z "$name" ] ; then
		echo "unauthorized code $l"
		continue
	fi
	echo "identified $name, opening garage..."
	curl http://192.168.42.1:4280/<chemin-local> || true
done

Fonctionnement :

Lancement au démarrage, dans /etc/rc.local :

tmux new -s garage -d 'sudo -u lov -g lov /usr/bin/python3 /home/lov/garage/server.py'
tmux new -s rfid -d /home/lov/garage/gate-service.sh

Pour voir ce qui se passe : sudo tmux attach -t garage ou sudo tmux attach -t rfid (sortir avec Ctrl-B puis D).

Ajouter un badge

  1. Passer le badge sur le lecteur.
  2. Sur l'Atom : tail ~/garage/rfid.log → noter le 2e champ (8 caractères, ex. A1B2C3D4).
  3. Ajouter la ligne <ID> <pseudo> dans ~/garage/perm.list. C'est pris en compte immédiatement.

Dépannage

Documentation technique détaillée (rétro-ingénierie, schéma, sauvegarde du firmware) : dépôt AdminLova, docs/boitier-garage/.