Projet IoT I4 Secura

We present to you our project, Secura, a highly secure smart lock.

IntermediateShowcase (no instructions)Over 2 days44
Projet IoT I4 Secura

Things used in this project

Hardware components

SODAQ ONE V3
SODAQ ONE V3
×1
USB-A to Mini-USB Cable
USB-A to Mini-USB Cable
×1
RGB Backlight LCD - 16x2
Adafruit RGB Backlight LCD - 16x2
×1
Arduino clavier Matricielle
×1
Arduino Serrure électromagnétique
×1

Software apps and online services

Visual Studio Code Extension for Arduino
Microsoft Visual Studio Code Extension for Arduino
Arduino IDE
Arduino IDE
Fusion
Autodesk Fusion
The Things Stack
The Things Industries The Things Stack

Story

Read more

Custom parts and enclosures

Partie gauche

Partie droite

Cache écran

Fermeture boite

Liaison porte

Porte

Partie milieu

Liaison boite

Code

Serveur

Python
from flask import Flask, session, jsonify, render_template, redirect, url_for, request, flash
from flask_socketio import SocketIO, emit
import logging
import requests
from functools import wraps
from appwrite.client import Client
from appwrite.services.account import Account
from appwrite.services.users import Users
from appwrite.services.databases import Databases
from appwrite.id import ID
from appwrite.exception import AppwriteException
from appwrite.query import Query
from functools import wraps
import os
from dotenv import load_dotenv
from appwrite.services.databases import Databases
import json
import threading
import lora
import time
import base64
import binascii
import random
from datetime import datetime
from time import strftime

load_dotenv()

app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*")
app.secret_key = os.getenv("FLASK_SECRET_KEY")

# Configuration de Appwrite
client = Client()
client.set_endpoint(os.getenv("APPWRITE_ENDPOINT"))
client.set_project(os.getenv("APPWRITE_PROJECT_ID"))
client.set_key(os.getenv("APPWRITE_API_KEY"))

databases = Databases(client)
USERS_COLLECTION_ID = os.getenv("USERS_COLLECTION_ID")
ADMINS_COLLECTION_ID = os.getenv("ADMINS_COLLECTION_ID")
DATABASE_ID = os.getenv("DATABASE_ID")

code_secret = os.getenv("CODE_SECRET")
code_secret = int(code_secret)

app_id = os.getenv("TTN_APP_ID").lower()
dev_id = os.getenv("TTN_DEV_ID")
api_key = os.getenv("TTN_API_KEY")
url = f"https://eu1.cloud.thethings.network/api/v3/as/applications/{app_id}/devices/{dev_id}/down/replace"

logging.basicConfig(level=logging.INFO)

uid_badge = None
generated_code = None

def promote_to_admin(user_id, user_email, user_password, isAdmin):
    try:
        databases.create_document(
            database_id=os.getenv("DATABASE_ID"),
            admins_collection_id=os.getenv("ADMINS_COLLECTION_ID"),
            document_id=ID.unique(),
            data={
                'userID': user_id,
                'email': user_email,
                'password': user_password,
                'isAdmin': isAdmin
            }
        )
        print(f"Utilisateur {user_email} promu admin")
    except AppwriteException as e:
        print(f"Erreur: {e.message}")


def add_user(user_email, user_password, badgeUID, nom, prenom):
    try:
        databases.create_document(
            database_id=os.getenv("DATABASE_ID"),
            collection_id=os.getenv("USERS_COLLECTION_USER_ID"),
            document_id=ID.unique(),
            data={
                'nom': nom,
                'prenom': prenom,
                'email': user_email,
                'password': user_password,
                'Badge_UID': badgeUID
            }
        )
        print(f"Utilisateur {user_email} ajout")
    except AppwriteException as e:
        print(f"Erreur: {e.message}")
    
    
def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not session.get('admin_logged_in'):
            return redirect(url_for('login', next=request.url))
        return f(*args, **kwargs)
    return decorated_function

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

        try:
            admins = databases.list_documents(
                DATABASE_ID,
                ADMINS_COLLECTION_ID,
                queries=[Query.equal('email', email)]
            )

            if admins['total'] == 0:
                flash('Identifiants invalides', 'error')
                return redirect(url_for('login'))

            admin = admins['documents'][0]

            if admin['password'] != password:
                flash('Identifiants invalides', 'error')
                return redirect(url_for('login'))
            
            if admin['isAdmin'] == False:
                flash('Identifiants invalides', 'error')
                return redirect(url_for('login'))
            
            
            session['admin_logged_in'] = True
            session['admin_email'] = email
            
            return redirect(url_for('menu'))

        except AppwriteException as e:
            flash(f"Erreur systme: {e.message}", 'error')
            return redirect(url_for('login'))

    return render_template('login.html')

@app.route('/menu', methods=['GET', 'POST'])
@login_required
def menu():
    if request.method == 'POST':
        if 'add' in request.form:
            return redirect(url_for('add'))
        elif 'act' in request.form:
            return redirect(url_for('uid'))
        elif 'dash' in request.form:
            return redirect(url_for('dashboard'))
    return render_template('menu.html', admin_email=session.get('admin_email'))
    
    

@app.route('/add', methods=['GET', 'POST'])
@login_required
def add():
    if request.method == 'POST':
        nom = request.form['nom']
        prenom = request.form['prenom']
        email = request.form['email']
        password = request.form['password']
        confirm_password = request.form['confirm-password']
        BadgeUID = request.form['BadgeUID']

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

        try:
            email_query = Query.equal('email', email)
            badge_query = Query.equal('Badge_UID', BadgeUID)

            email_response = databases.list_documents(
                database_id=os.getenv("DATABASE_ID"),
                collection_id=os.getenv("USERS_COLLECTION_USER_ID"),
                queries=[email_query]
            )
            if email_response['documents']:
                flash('Un utilisateur avec cet email existe dj', 'error')
                return render_template('add.html')

            badge_response = databases.list_documents(
                database_id=os.getenv("DATABASE_ID"),
                collection_id=os.getenv("USERS_COLLECTION_USER_ID"),
                queries=[badge_query]
            )
            if badge_response['documents']:
                flash('Un utilisateur avec ce badge UID existe dj', 'error')
                return render_template('add.html')

        except AppwriteException as e:
            print(f"Erreur lors de la vrification de l'utilisateur: {e.message}")
            flash('Erreur lors de la vrification de l\'utilisateur', 'error')
            return render_template('add.html')

        add_user(email, password, BadgeUID, nom, prenom)

        flash("Ajout russi", 'success')
        return redirect(url_for('menu'))

    return render_template('add.html')
        
        
@app.route('/uid')
@login_required
def uid():
    return render_template('uid.html', uid = uid_badge)


@app.route('/dashboard')
@login_required
def dashboard():
    return render_template('dashboard.html', uid = uid_badge)

@app.route('/logout', methods=['GET', 'POST'])
def logout():
    session.pop('admin_logged_in', None)
    session.pop('admin_email', None)
    return redirect(url_for('login'))

badges_list = []

@app.route('/ttn-webhook', methods=['GET', 'POST'])
def ttn_webhook():
    global badges_list, uid_badge
    try:
        data = request.get_json()
        if not data:
            return jsonify({"error": "Aucune donne reue"}), 400

        frm_payload = data.get("uplink_message", {}).get("frm_payload")
        if not frm_payload:
            return jsonify({"error": "frm_payload manquant"}), 400

        missing_padding = len(frm_payload) % 4
        if missing_padding:
            frm_payload += '=' * (4 - missing_padding)

        decoded_bytes = base64.b64decode(frm_payload)
        hex_payload = decoded_bytes.hex()
        uid_badge = hex_payload[2:10].upper()

        current_datetime = datetime.now()
        formatted_date = current_datetime.strftime("%Y-%m-%d %H:%M:%S")
        date = str(formatted_date)   

        try:
            user_response = databases.list_documents(
                DATABASE_ID,
                USERS_COLLECTION_ID,
                queries=[Query.equal('Badge_UID', uid_badge)]
            )
            if user_response['total'] > 0:
                user = user_response['documents'][0]
                badge_data = {
                    'uid': uid_badge,
                    'email': user.get('email', 'N/A'),
                    'nom': user.get('nom', 'N/A'),
                    'prenom': user.get('prenom', 'N/A'),
                    'porte': user.get('porte', '1'),
                    'date': date
                }
                badges_list.append(badge_data)

                socketio.emit('new_badge', badge_data)
                sendCode()

        except AppwriteException as e:
            print(f"Erreur Appwrite: {e.message}")

        return jsonify({"status": "success"}), 200

    except Exception as e:
        import traceback
        traceback.print_exc()
        return jsonify({"error": str(e)}), 500

@app.route('/get_badges', methods=['GET'])
def get_badges():
    return jsonify(badges_list)
    
def code():
    random_number = random.randint(10000000, 99999999)
    operate = random_number % code_secret
    operate = str(operate)
    liste = list(operate)
    random.shuffle(liste)
    code = liste[:6]
    chaine = ''.join(map(str, code))
    code = int(chaine)
    print("Code gnr :", code)
    code = str(code)
    return code

def sendCode():
    global generated_code
    if generated_code is None:  # Gnrer un code uniquement s'il n'existe pas encore
        generated_code = code()

    print(f"Envoi du code {generated_code}")
    socketio.emit("code", {"number": generated_code})
    print("Code envoy  l'application mobile")

    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json"
    }

    code_bytes = generated_code.encode('utf-8')
    encoded_payload = base64.b64encode(code_bytes).decode('utf-8')

    data = {
        "downlinks": [{
            "f_port": 1,
            "frm_payload": encoded_payload,
            "priority": "NORMAL",
            "confirmed": False
        }],
        "end_device_ids": { 
            "device_id": dev_id,
            "application_ids": {
                "application_id": app_id
            }
        }
    }

    print("Donnes envoyes  TTN :", data)

    response = requests.post(url, headers=headers, json=data)

    if response.status_code == 200:
        print("Code envoy  TTN avec succs")
    else:
        print("Erreur lors de l'envoi du code  TTN")
        print("Code de statut :", response.status_code)
        print("Rponse :", response.text)
    
    generated_code = None  # Rinitialiser le code aprs l'envoi


if __name__ == '__main__':
    socketio.run(app, debug=True, host='0.0.0.0', port=5000)


    
        

         

Arduino

Arduino
#define DEBUG
#include <Sodaq_RN2483.h>
#include <MFRC522.h>
#include <Wire.h>
#include <SPI.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>

int col = 0;
const byte ROW_NUM = 4;
const byte COLUMN_NUM = 4;

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

byte pin_rows[ROW_NUM] = {1, 2, 3, 4};  // Broches des lignes
byte pin_column[COLUMN_NUM] = {5, 6, 7, 8};  // Broches des colonnes

Keypad keypad = Keypad(makeKeymap(keys), pin_rows, pin_column, ROW_NUM, COLUMN_NUM);

#define debugSerial SerialUSB
#define loraSerial Serial2

#define LOCK_PIN 0
#define RST_PIN 9      // Pin Reset pour le MFRC522
#define SS_PIN 10      // Pin Slave Select pour le MFRC522

//#define ABP
#define OTAA

#ifdef ABP
  // ABP Keys - Use your own KEYS!
  const uint8_t devAddr[4] = {0x00, 0x00, 0x00, 0x00};
  const uint8_t nwkSKey[16] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB7, 0x93, 0xEE, 0x79, 0x2F, 0x05, 0xF7};
  const uint8_t appSKey[16] = {0xD4, 0x46, 0xCF, 0x21, 0x32, 0x8B, 0xB9, 0xEB, 0xFF, 0x7D, 0x83, 0x9D, 0xC8, 0x79, 0xE4, 0x10};
#else
  // OTAA Keys - Use your own KEYS!
  const uint8_t devEUI[8] = {0x13, 0x12, 0x20, 0x03, 0x18, 0x03, 0x20, 0x03};
  const uint8_t appEUI[8] = {0x13, 0x12, 0x20, 0x03, 0x18, 0x03, 0x20, 0x03};
  const uint8_t appKey[16] = {0x13, 0x12, 0x20, 0x03, 0x13, 0x12, 0x20, 0x03, 0x18, 0x03, 0x20, 0x03, 0x18, 0x03, 0x20, 0x03};
#endif

MFRC522 mfrc522(SS_PIN, RST_PIN);  // Crer une instance du lecteur RFID
LiquidCrystal_I2C lcd(0x27, 20, 4);  // Adresse LCD

// Payload is 'Microchip ExpLoRa' in HEX
uint8_t payload[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

String ascii = "";  // Stocke le code reu via LoRa
String enteredCode = "";  // Stocke le code entr par l'utilisateur
bool waitingForCode = false;  // Drapeau pour indiquer si nous attendons la saisie du code

void setup() {
  debugSerial.begin(9600);
  loraSerial.begin(LoRaBee.getDefaultBaudRate());

  pinMode(LOCK_PIN, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LOCK_PIN, LOW);
  digitalWrite(LED_BUILTIN, LOW);
  Serial.println("Initialisation terminee. Serrure verrouillee.");

  // Set ADC resolution to 12 bits
  analogReadResolution(12);

  SPI.begin();
  mfrc522.PCD_Init();  // Initialisation du lecteur RFID

  lcd.init();
  lcd.backlight();
  lcd.setCursor(3, 0);
  lcd.print("Bienvenue!");
  lcd.setCursor(1, 1);
  lcd.print("Veuillez Badger");

  delay(10000);

  LoRaBee.setDiag(debugSerial);  // optional

#ifdef ABP
  if (LoRaBee.initABP(loraSerial, devAddr, appSKey, nwkSKey, true)) {
    debugSerial.println("ABP Keys Accepted.");
  } else {
    debugSerial.println("ABP Key Setup Failed!");
  }
#else
  if (LoRaBee.initOTA(loraSerial, devEUI, appEUI, appKey, true)) {
    debugSerial.println("OTAA Keys Accepted.");
  } else {
    debugSerial.println("OTAA Keys Setup Failed!");
  }
#endif

  debugSerial.println("Sleeping for 5 seconds before starting sending out test packets.");
  for (uint8_t i = 5; i > 0; i--) {
    debugSerial.println(i);
    delay(1000);
  }

  if (LoRaBee.send(1, payload, sizeof(payload)) != NoError) {
    debugSerial.println("First payload was sent");
  }
}

void loop() {
  if (!waitingForCode) {
    // Vrifier si un badge RFID est dtect
    if (!mfrc522.PICC_IsNewCardPresent()) {
      delay(100);  // Ajouter une temporisation pour viter de bloquer le programme
      return;  // Si aucun badge n'est dtect, ne rien faire
    }

    // Lire le badge RFID
    if (!mfrc522.PICC_ReadCardSerial()) {
      delay(100);  // Ajouter une temporisation pour viter de bloquer le programme
      return;  // Si l'UID du badge ne peut pas tre lu, ne rien faire
    }

    // Convertir l'UID en une chane de caractres
    byte uidSize = mfrc522.uid.size;

    payload[0] = payload[0] + 1;

    // Crer un payload de la taille exacte de l'UID
    for (byte i = 0; i < 8; i++) {
      payload[i + 1] = mfrc522.uid.uidByte[i];
    }

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Badge detecte!");
    lcd.setCursor(0, 1);
    for (byte i = 1; i < mfrc522.uid.size + 1; i++) {
      lcd.print(payload[i], HEX);  // Afficher l'UID en HEX sur l'cran LCD
      lcd.print(" ");
    }

    debugSerial.print("Envoi du message via LoRa: ");
    for (byte i = 0; i < 9; i++) {
      debugSerial.print(payload[i], HEX);
      debugSerial.print(" ");
    }
    debugSerial.println();

    switch (LoRaBee.send(1, payload, sizeof(payload))) {
      case NoError:
        debugSerial.println("Successful transmission.");
        delay(1000);
        break;
      case NoResponse:
        debugSerial.println("There was no response from the device.");
        break;
      case Timeout:
        debugSerial.println("Connection timed-out. Check your serial connection to the device! Sleeping for 20sec.");
        delay(20000);
        break;
      case PayloadSizeError:
        debugSerial.println("The size of the payload is greater than allowed. Transmission failed!");
        break;
      case InternalError:
        debugSerial.println("Oh No! This shouldn't happen. Something is really wrong! Try restarting the device!\r\nThe program will now halt.");
        while (1) {}
        break;
      case Busy:
        debugSerial.println("The device is busy. Sleeping for 10 extra seconds.");
        delay(10000);
        break;
      case NetworkFatalError:
        debugSerial.println("There is a non-recoverable error with the network connection. You should re-connect.\r\nThe program will now halt.");
        while (1) {}
        break;
      case NotConnected:
        debugSerial.println("The device is not connected to the network. Please connect to the network before attempting to send data.\r\nThe program will now halt.");
        while (1) {}
        break;
      case NoAcknowledgment:
        debugSerial.println("There was no acknowledgment sent back!");
        break;
      default:
        break;
    }

    uint8_t downlink[64];  // Crer un tableau pour stocker les donnes reues
    int downlinkSize = LoRaBee.receive(downlink, sizeof(downlink));

    if (downlinkSize > 0) {
      debugSerial.print("Donnes downlink reues: ");
      for (int i = 0; i < downlinkSize; i++) {
        debugSerial.print(downlink[i], HEX);
        debugSerial.print(" ");
      }
      debugSerial.println();

      ascii = "";
      for (int i = 0; i < downlinkSize; i++) {
        ascii += (char)downlink[i];
      }

      debugSerial.print("Code recu :");
      debugSerial.println(ascii);

      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("Entrez le code:");

      waitingForCode = true;  // Activer le drapeau pour indiquer que nous attendons la saisie du code
    }
  }

  if (waitingForCode) {
    // Gestion du clavier
    char key = keypad.getKey();
    if (key) {
      debugSerial.print("Touche detectee: ");
      debugSerial.println(key);
      if (key == '#') {
        // Valider le code entr
        lcd.clear();
        lcd.setCursor(0, 0);
        if (enteredCode == ascii) {
          lcd.print("Acces autorise");
          debugSerial.println("Code correct");
          /*
          digitalWrite(LOCK_PIN, HIGH);
          digitalWrite(LED_BUILTIN, HIGH);
          Serial.println("Serrure DEVERROUILLEE => LED ON");
          delay(5000);
          digitalWrite(LOCK_PIN, LOW);
          digitalWrite(LED_BUILTIN, LOW);
          Serial.println("Serrure VERROUILLEE => LED OFF");
          */
          delay(5000);
          lcd.clear();
          lcd.setCursor(3, 0);
          lcd.print("Bienvenue!");
          lcd.setCursor(1, 1);
          lcd.print("Veuillez Badger");
          waitingForCode = false;
          enteredCode = "";  // Rinitialiser le code entr
          col = 0;  // Rinitialiser la position du curseur
        } else {
          lcd.print("Acces refuse!");
          debugSerial.println("Code incorrect!");
        }
        delay(2000);  // Afficher le message pendant 2 secondes

      } else {
        // Ajouter la touche au code entr
        enteredCode += key;
        lcd.setCursor(col, 1);
        lcd.print(key);
        col++;
      }
    }
  }

  delay(100);
}

Credits

baptiste “WORX” gogolewski
1 project • 0 followers
Contact
Alexandre Létocart
3 projects • 3 followers
Contact
Nicolas DAILLY
33 projects • 21 followers
Associated Professor at UniLaSalle - Amiens / Head of the Computer Network Department / Teach Computer and Telecommunication Networks
Contact
fcaron
15 projects • 3 followers
Contact
Lecomte_Noa
1 project • 0 followers
Contact

Comments

Please log in or sign up to comment.