Secu²

IoT project to create a lock requiring two-factor authentication to be unlocked.

IntermediateShowcase (no instructions)74
Secu²

Things used in this project

Hardware components

Arduino MKR WAN 1310
Arduino MKR WAN 1310
×1
Solderless Breadboard Half Size
Solderless Breadboard Half Size
×1
Sunfounder RFID RC522
×1
Arduino 4x4 Keyboard Panel
×1

Software apps and online services

Arduino IDE
Arduino IDE
Visual Studio 2017
Microsoft Visual Studio 2017
Tinkercad
Autodesk Tinkercad
The Things Stack
The Things Industries The Things Stack

Story

Read more

Custom parts and enclosures

3D modeled box

Sketchfab still processing.

Code

Web Server Flask MQTT

Python
import requests
from flask import Flask, request, render_template, redirect, session, url_for, jsonify
from datetime import datetime, timedelta
import threading
import sqlite3
import json
import random
import base64
from werkzeug.security import generate_password_hash, check_password_hash
import time
import paho.mqtt.client as mqtt

app = Flask(__name__)
app.secret_key = "vraiment_pas_secure"

# Configuration TTN
TTN_API_KEY = "*"
TTN_APP_ID = "projetiotserrure"
TTN_DEVICE_ID = "mkr1310-projet-iot"
TTN_MQTT_USERNAME = "projetiotserrure@ttn"
TTN_MQTT_PASSWORD = TTN_API_KEY
TTN_MQTT_SERVER = "eu1.cloud.thethings.network"

# Fonction pour envoyer un downlink vers TTN
def send_downlink(code):
    url = f"https://eu1.cloud.thethings.network/api/v3/as/applications/{TTN_APP_ID}/devices/{TTN_DEVICE_ID}/down/push"

    headers = {
        "Authorization": f"Bearer {TTN_API_KEY}",
        "Content-Type": "application/json",
        "User-Agent": "my-iot-app"
    }

    payload = base64.b64encode(str(code).encode()).decode()

    data = {
        "downlinks": [{
            "frm_payload": payload,
            "f_port": 1,
            "priority": "NORMAL"
        }]
    }

    try:
        response = requests.post(url, headers=headers, json=data)
        response.raise_for_status()
        print(f"Downlink envoy : {code}")
    except requests.RequestException as e:
        print(f"Erreur lors de l'envoi du downlink : {e}")

# Fonction pour se connecter  la base SQLite
def get_db_connection():
    conn = sqlite3.connect('badges.db', check_same_thread=False)
    conn.row_factory = sqlite3.Row
    return conn

# Traitement du message MQTT
processing_lock = threading.Lock()
badge_last_processed = {}

def on_message(client, userdata, msg):
    print("MQTT message reu")
    try:
        data = json.loads(msg.payload.decode())
        uplink = data.get("uplink_message", {})
        payload_raw = uplink.get("frm_payload")

        if not payload_raw:
            print("Message MQTT sans frm_payload, ignor.")
            return

        decoded_payload = base64.b64decode(payload_raw).decode('utf-8')
        print(f"Badge reu : {decoded_payload}")

        now = datetime.now()

        with processing_lock:
            last_time = badge_last_processed.get(decoded_payload)
            if last_time and (now - last_time).total_seconds() < 5:
                print(f"Badge {decoded_payload} dj trait rcemment, ignor.")
                return
            badge_last_processed[decoded_payload] = now

        conn = get_db_connection()
        badge = conn.execute('SELECT * FROM badges WHERE badge_id = ?', (decoded_payload,)).fetchone()

        if badge:
            if badge['droit'] == 1:
                code = random.randint(1000, 9999)
                timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
                conn.execute('UPDATE badges SET code = ?, timestamp = ? WHERE badge_id = ?',
                             (code, timestamp, decoded_payload))
                conn.commit()
                send_downlink(code)
                print(f"Code {code} gnr et envoy  {decoded_payload}")
            else:
                conn.execute('UPDATE badges SET timestamp = ? WHERE badge_id = ?',
                             (now.strftime("%Y-%m-%d %H:%M:%S"), decoded_payload))
                conn.commit()
                print("Badge non autoris")
        else:
            print("Badge inconnu")

    except Exception as e:
        print(f"Erreur MQTT: {e}")
    finally:
        try:
            conn.close()
        except:
            pass

def mqtt_listen():
    while True:
        try:
            client = mqtt.Client()
            client.username_pw_set(TTN_MQTT_USERNAME, TTN_MQTT_PASSWORD)
            client.on_message = on_message

            client.connect(TTN_MQTT_SERVER, 1883, 60)
            topic = f"v3/{TTN_APP_ID}@ttn/devices/{TTN_DEVICE_ID}/up"
            client.subscribe(topic)

            client.loop_forever()
        except Exception as e:
            print(f"Erreur dans le thread MQTT : {e}, reconnexion dans 5 sec")
            time.sleep(5)

def nettoyer_codes_expires():
    conn = get_db_connection()
    cursor = conn.cursor()
    now = datetime.now()

    badges = cursor.execute('SELECT badge_id, timestamp FROM badges WHERE code IS NOT NULL').fetchall()

    for badge in badges:
        if badge['timestamp']:
            try:
                badge_time = datetime.strptime(badge['timestamp'], "%Y-%m-%d %H:%M:%S")
                if now - badge_time > timedelta(minutes=5):
                    cursor.execute('UPDATE badges SET code = NULL WHERE badge_id = ?', (badge['badge_id'],))
                    print(f"Code expir supprim pour le badge {badge['badge_id']}")
            except Exception as e:
                print(f"Erreur de parsing de timestamp : {e}")

    conn.commit()
    conn.close()

# Route principale (affichage des badges)
@app.route('/')
def index():
    if 'user_id' not in session:
        return redirect(url_for('login'))
    # Assurez-vous que vous vrifiez bien que l'utilisateur a des droits admin
    conn = get_db_connection()
    user = conn.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],)).fetchone()
    conn.close()

    if user['is_admin'] == 1:
        return render_template('index.html')  # Page admin
    else:
        return redirect(url_for('dashboard'))  # Page utilisateur
   
@app.route('/auth')
def auth():
    # Page d'authentification avec le choix de se connecter ou crer un compte
    return render_template('auth.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        conn = get_db_connection()
        user = conn.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone()
        conn.close()

        if user and check_password_hash(user['password'], password):
            session['user_id'] = user['id']
            is_admin = user['is_admin']

            print(f"Utilisateur connect : {username}, is_admin = {is_admin}")

            if is_admin == 1:
                return redirect(url_for('index'))
            else:
                return redirect(url_for('dashboard'))

        else:
            return render_template('login.html', error="Identifiants incorrects.")
    return render_template('login.html')

@app.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        confirm_password = request.form['confirm_password']

        if password != confirm_password:
            return render_template('signup.html', error="Les mots de passe ne correspondent pas.")

        # Hash du mot de passe avant de l'enregistrer dans la base de donnes
        hashed_password = generate_password_hash(password)

        # Connexion  la base de donnes
        conn = get_db_connection()

        # Vrifier si l'utilisateur existe dj
        existing_user = conn.execute('SELECT * FROM users WHERE username = ?', (username,)).fetchone()
        if existing_user:
            conn.close()
            return render_template('signup.html', error="Ce nom d'utilisateur est dj pris.")

        # Ajouter l'utilisateur dans la base de donnes
        conn.execute('INSERT INTO users (username, password) VALUES (?, ?)', (username, hashed_password))
        conn.commit()
        conn.close()

        return redirect(url_for('login'))  # Rediriger l'utilisateur vers la page de connexion aprs l'inscription

    return render_template('signup.html')

@app.route('/dashboard')
def dashboard():
    if 'user_id' not in session:
        return redirect(url_for('login'))

    conn = get_db_connection()
    user = conn.execute('SELECT * FROM users WHERE id = ?', (session['user_id'],)).fetchone()
    badge = conn.execute('SELECT * FROM badges WHERE badge_id = ?', (user['badge_id'],)).fetchone()
    conn.close()

    return render_template('dashboard.html', user=user, badge=badge)

@app.route('/logout')
def logout():
    session.pop('user_id', None)
    return redirect(url_for('login'))

@app.route('/badges-json')
def badges_json():
    conn = get_db_connection()
    badges = conn.execute('SELECT * FROM badges').fetchall()
    conn.close()

    badges_info = []
    for badge in badges:
        badges_info.append({
            'badge_id': badge['badge_id'],
            'droit': badge['droit'],
            'code': badge['code'],
            'nom': badge['nom'],
            'timestamp': badge['timestamp']
        })

    return jsonify(badges_info)

def boucle_nettoyage():
    while True:
        try:
            nettoyer_codes_expires()
        except Exception as e:
            print(f"Erreur dans le nettoyage des codes : {e}")
        time.sleep(60)

if __name__ == '__main__':
    threading.Thread(target=boucle_nettoyage, daemon=True).start()
    threading.Thread(target=mqtt_listen, daemon=True).start()
    app.run(debug=False)

Arduino MKR1310

Arduino
#include <MKRWAN.h>
#include <SPI.h>
#include <MFRC522.h>
#include <Keypad.h>

const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

byte rowPins[ROWS] = {5, 4, 3, 2};
byte colPins[COLS] = {A0, A1, A2, A3};

Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

#define SS_PIN 11  // RC522
#define RST_PIN 7
#define RELAY_PIN 1

MFRC522 mfrc522(SS_PIN, RST_PIN);
LoRaModem modem;

bool attenteCode = false;

#include "arduino_secrets.h"
String appEui = SECRET_APP_EUI;
String appKey = SECRET_APP_KEY;

String correctPin = "";
String enteredPin = "";

void setup() {
  Serial.begin(115200);
  while (!Serial);

  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);

  pinMode(SS_PIN, OUTPUT);
  digitalWrite(SS_PIN, HIGH); // Dsactiver le RC522 par dfaut

  if (!modem.begin(EU868)) {
    Serial.println("Erreur init LoRa");
    while (1);
  }

  Serial.print("Version: ");
  Serial.println(modem.version());
  Serial.print("Device EUI: ");
  Serial.println(modem.deviceEUI());

  int connected = modem.joinOTAA(appEui, appKey);
  if (!connected) {
    Serial.println("chec join OTAA");
    while (1);
  }

  modem.minPollInterval(60);

  SPI.begin();
  mfrc522.PCD_Init();
  Serial.println("Scan une carte...");
}

void loop() {
  // --- Activation RC522
  digitalWrite(SS_PIN, LOW);

  if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
    digitalWrite(SS_PIN, HIGH);
    return;
  }

  String badge_id = "";
  for (byte i = 0; i < mfrc522.uid.size; i++) {
    badge_id += String(mfrc522.uid.uidByte[i], HEX);
    if (i < mfrc522.uid.size - 1) badge_id += ":";
  }

  Serial.println("Badge ID: " + badge_id);

  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
  digitalWrite(SS_PIN, HIGH);  // Dsactiver RC522

  // --- Envoi LoRa
  Serial.print("Envoi ID : ");
  Serial.println(badge_id);

  modem.beginPacket();
  modem.print(badge_id);
  int err = modem.endPacket(true);

  if (err > 0) {
    Serial.println("Badge envoy !");
  } else {
    Serial.println("chec envoi badge");
  }

  delay(10000);

  Serial.println("Envoi uplink vide pour attendre le downlink...");
  modem.beginPacket();
  modem.print("");  // Uplink vide
  modem.endPacket(true);

  if (!modem.available()) {
    Serial.println("Pas de downlink reu.");
    return;
  }

  correctPin = "";
  while (modem.available()) {
    correctPin += (char)modem.read();
  }

  Serial.print("Code reu : ");
  Serial.println(correctPin);
  attenteCode = true;

  // --- Attente saisie clavier
  enteredPin = "";
  while (attenteCode) {
    char key = keypad.getKey();
    if (key) {
      Serial.print(key);
      if (key == '#') {
        Serial.println();
        if (enteredPin == correctPin) {
          Serial.println("Accs accord !");
          digitalWrite(RELAY_PIN, HIGH);
          delay(3000);
          digitalWrite(RELAY_PIN, LOW);
        } else {
          Serial.println("Code incorrect");
        }
        enteredPin = "";
        attenteCode = false;
      } else if (key == '*') {
        enteredPin = "";
        Serial.println("\nCode rinitialis");
      } else {
        enteredPin += key;
      }
    }
  }
}

Admin Page

Python
import tkinter as tk
from tkinter import messagebox
import sqlite3

# --- Connexion  la base de donnes ---
def get_db_connection():
    return sqlite3.connect("badges.db")

# --- Application principale ---
class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Gestion des Badges")
        self.geometry("400x300")
        self.frames = {}

        for F in (HomePage, AddBadgePage, LinkUserPage, DeleteUserPage):
            frame = F(self, self)
            self.frames[F] = frame
            frame.place(relwidth=1, relheight=1)

        self.show_frame(HomePage)

    def show_frame(self, frame_class):
        frame = self.frames[frame_class]
        frame.tkraise()

# --- Page d'accueil ---
class HomePage(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)
        tk.Label(self, text="Page d'accueil", font=("Arial", 16)).pack(pady=20)

        tk.Button(self, text="Ajouter un badge", command=lambda: controller.show_frame(AddBadgePage)).pack(pady=5)
        tk.Button(self, text="Lier utilisateur  un badge", command=lambda: controller.show_frame(LinkUserPage)).pack(pady=5)
        tk.Button(self, text="Supprimer un utilisateur", command=lambda: controller.show_frame(DeleteUserPage)).pack(pady=5)

# --- Page : Ajouter un badge ---
class AddBadgePage(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)
        tk.Label(self, text="Ajouter un Badge", font=("Arial", 14)).pack(pady=10)

        tk.Label(self, text="Badge ID:").pack()
        self.badge_id_entry = tk.Entry(self)
        self.badge_id_entry.pack()

        tk.Label(self, text="Droit (1/0):").pack()
        self.droit_entry = tk.Entry(self)
        self.droit_entry.pack()

        tk.Button(self, text="Ajouter", command=self.ajouter_badge).pack(pady=10)
        tk.Button(self, text="Retour", command=lambda: controller.show_frame(HomePage)).pack()

    def ajouter_badge(self):
        badge_id = self.badge_id_entry.get()
        droit = self.droit_entry.get()

        if not badge_id or not droit:
            messagebox.showerror("Erreur", "Tous les champs doivent tre remplis.")
            return

        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute('SELECT * FROM badges WHERE badge_id = ?', (badge_id,))
        if cursor.fetchone():
            messagebox.showerror("Erreur", "Le badge existe dj.")
        else:
            cursor.execute('INSERT INTO badges (badge_id, droit, nom) VALUES (?, ?, ?)', (badge_id, droit, None))
            conn.commit()
            messagebox.showinfo("Succs", "Badge ajout.")
        conn.close()

# --- Page : Lier utilisateur  badge ---
class LinkUserPage(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)
        tk.Label(self, text="Lier un utilisateur  un badge", font=("Arial", 14)).pack(pady=10)

        tk.Label(self, text="Nom d'utilisateur:").pack()
        self.username_entry = tk.Entry(self)
        self.username_entry.pack()

        tk.Label(self, text="Badge ID:").pack()
        self.badge_id_entry = tk.Entry(self)
        self.badge_id_entry.pack()

        tk.Label(self, text="Admin (1/0):").pack()
        self.is_admin_entry = tk.Entry(self)
        self.is_admin_entry.pack()

        tk.Button(self, text="Lier", command=self.lier_utilisateur).pack(pady=10)
        tk.Button(self, text="Retour", command=lambda: controller.show_frame(HomePage)).pack()

    def lier_utilisateur(self):
        username = self.username_entry.get()
        badge_id = self.badge_id_entry.get()
        is_admin = self.is_admin_entry.get()

        if not username or not badge_id or not is_admin:
            messagebox.showerror("Erreur", "Tous les champs doivent tre remplis.")
            return

        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute('SELECT * FROM badges WHERE badge_id = ?', (badge_id,))
        badge = cursor.fetchone()

        if not badge:
            messagebox.showerror("Erreur", "Le badge n'existe pas.")
            conn.close()
            return

        cursor.execute('SELECT * FROM users WHERE username = ?', (username,))
        user = cursor.fetchone()

        if not user:
            cursor.execute('INSERT INTO users (username, badge_id, is_admin) VALUES (?, ?, ?)', (username, badge_id, is_admin))
        else:
            cursor.execute('UPDATE users SET badge_id = ?, is_admin = ? WHERE username = ?', (badge_id, is_admin, username))

        cursor.execute('UPDATE badges SET nom = ? WHERE badge_id = ?', (username, badge_id))

        conn.commit()
        conn.close()
        messagebox.showinfo("Succs", f"Utilisateur '{username}' li au badge {badge_id}.")

# --- Page : Supprimer un utilisateur ---
class DeleteUserPage(tk.Frame):
    def __init__(self, parent, controller):
        super().__init__(parent)
        tk.Label(self, text="Supprimer un Utilisateur", font=("Arial", 14)).pack(pady=10)

        tk.Label(self, text="Nom d'utilisateur:").pack()
        self.username_entry = tk.Entry(self)
        self.username_entry.pack()

        tk.Button(self, text="Supprimer", command=self.supprimer_utilisateur).pack(pady=10)
        tk.Button(self, text="Retour", command=lambda: controller.show_frame(HomePage)).pack()

    def supprimer_utilisateur(self):
        username = self.username_entry.get()

        if not username:
            messagebox.showerror("Erreur", "Nom d'utilisateur requis.")
            return

        conn = get_db_connection()
        cursor = conn.cursor()

        cursor.execute('SELECT badge_id FROM users WHERE username = ?', (username,))
        result = cursor.fetchone()

        if not result:
            messagebox.showerror("Erreur", "Utilisateur non trouv.")
            conn.close()
            return

        badge_id = result[0]

        cursor.execute('DELETE FROM users WHERE username = ?', (username,))
        cursor.execute('DELETE FROM badges WHERE badge_id = ?', (badge_id,))
        conn.commit()
        conn.close()
        messagebox.showinfo("Succs", f"Utilisateur '{username}' et son badge ont t supprims.")

# --- Dmarrage ---
if __name__ == "__main__":
    # Assure-toi que la base a bien les bonnes tables ( adapter si dj cre)
    conn = get_db_connection()
    c = conn.cursor()
    c.execute('''
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE,
            badge_id TEXT,
            is_admin INTEGER
        )
    ''')
    c.execute('''
        CREATE TABLE IF NOT EXISTS badges (
            badge_id TEXT PRIMARY KEY,
            droit INTEGER,
            nom TEXT
        )
    ''')
    conn.commit()
    conn.close()

    app = App()
    app.mainloop()

Web Authentification

HTML
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Page d'authentification</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        table { border-collapse: collapse; width: 80%; }
        th, td { padding: 10px; text-align: center; border: 1px solid #ddd; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h2>Bienvenue sur le site</h2>
    <p><a href="{{ url_for('login') }}">Se connecter</a></p>
    <p><a href="{{ url_for('signup') }}">Crer un compte</a></p>
</body>
</html>

Login Page

HTML
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Connexion</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        table { border-collapse: collapse; width: 80%; }
        th, td { padding: 10px; text-align: center; border: 1px solid #ddd; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h2>Connexion</h2>
    <form method="POST">
        <label>Nom d'utilisateur: <input type="text" name="username"></label><br>
        <label>Mot de passe: <input type="password" name="password"></label><br>
        <button type="submit">Se connecter</button>
    </form>
    {% if error %}<p style="color:red;">{{ error }}</p>{% endif %}
</body>
</html>

Signup Page

HTML
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Crer un Compte</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        table { border-collapse: collapse; width: 80%; }
        th, td { padding: 10px; text-align: center; border: 1px solid #ddd; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h2>Crer un compte</h2>
    <form method="POST">
        <label>Nom d'utilisateur: <input type="text" name="username" required></label><br>
        <label>Mot de passe: <input type="password" name="password" required></label><br>
        <label>Confirmer le mot de passe: <input type="password" name="confirm_password" required></label><br>
        <button type="submit">Crer un compte</button>
    </form>
    {% if error %}<p style="color:red;">{{ error }}</p>{% endif %}
</body>
</html>

Dashboard Page

HTML
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tableau de Bord</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        table { border-collapse: collapse; width: 80%; }
        th, td { padding: 10px; text-align: center; border: 1px solid #ddd; }
        th { background-color: #f2f2f2; }
    </style>
    <script type="text/javascript">
        // Rafrachir la page toutes les 30 secondes
        setTimeout(function(){
            location.reload();
        }, 10000);  // 10 secondes
    </script>
</head>
<body>
    <h1>Tableau de Bord</h1>
    <p>Bienvenue, {{ user['username'] }} !</p>
    <p>Voici les informations de votre badge :</p>
    
    <ul>
        <li>Nom : {{ badge['nom'] }}</li>
        <li>ID Badge : {{ badge['badge_id'] }}</li>
        <li>Droit : {{ badge['droit'] }}</li>
        <li>Code : {{ badge['code'] }}</li>
        <li>Timestamp : {{ badge['timestamp'] }}</li>
    </ul>
</body>
</html>

Admin Web Page

HTML
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Badges</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 40px; }
        table { border-collapse: collapse; width: 80%; }
        th, td { padding: 10px; text-align: center; border: 1px solid #ddd; }
        th { background-color: #f2f2f2; }
    </style>
</head>
<body>
    <h1>Badges et Codes</h1>
    <table>
        <thead>
            <tr>
                <th>ID Badge</th>
                <th>Nom</th>
                <th>Droit</th>
                <th>Code gnr</th>
                <th>Dernire gnration</th>
            </tr>
        </thead>
        <tbody id="badge-table-body">
        </tbody>
    </table>

    <script>
        async function loadBadges() {
            try {
                const res = await fetch("/badges-json");
                const badges = await res.json();

                const tbody = document.getElementById("badge-table-body");
                tbody.innerHTML = "";

                badges.forEach(badge => {
                    const row = document.createElement("tr");

                    row.innerHTML = `
                        <td>${badge.badge_id}</td>
                        <td>${badge.nom || '-'}</td>
                        <td>${badge.droit ? 'Oui' : 'Non'}</td>
                        <td>${badge.code || 'Aucun code gnr'}</td>
                        <td>${badge.timestamp || 'Jamais'}</td>
                    `;
                    tbody.appendChild(row);
                });
            } catch (error) {
                console.error("Erreur lors du chargement des badges :", error);
            }
        }

        loadBadges(); // Initial
        setInterval(loadBadges, 5000); // Rafrachit toutes les 5 secondes
    </script>
</body>
</html>

Credits

Nico Delim
1 project • 0 followers
Third year Unilasalle Amiens IT engineering student, this page will display as much as school projects as possible.
Nicolas DAILLY
41 projects • 26 followers
Associated Professor at UniLaSalle - Amiens / Head of the Computer Network Department / Teach Computer and Telecommunication Networks
fcaron
22 projects • 7 followers
Alexandre Létocart
11 projects • 7 followers
Mathéo Tinchon (Hackster)
1 project • 1 follower

Comments