ChenNdGCyril AudinelleNicolas DAILLYfcaron
Published © GPL3+

NASecure

A physically secure NAS equipped with numerous sensors to monitor its status and feeding into a dashboard for visualizing relevant infos

IntermediateFull instructions providedOver 1 day236
NASecure

Things used in this project

Hardware components

Raspberry Pi 3 Model B
Raspberry Pi 3 Model B
×1
Sodaq Explorer
×1
Arduino Leonardo
Arduino Leonardo
×2
DollaTek Fingerprint Sensor
×1
Debo2 Tilt Sensor
×1
GPS & LoRa Shield
×1

Software apps and online services

OpenMediaVault
Pycharm
Arduino IDE
Arduino IDE
Docker
PostgreSQL

Story

Read more

Custom parts and enclosures

Schematics for the Box of the NAS

Schematics of the wodden box for a laser cutting machine. It was created using an online generator (https://www.festi.info/boxes.py/HingeBox?language=en)

Schematics

Schematics of the Hardware

Schematics that represent in an easy way how to connect all the component

Code

DataBase Connection

Python
It's was use to connect to a PostgreSQL database with all fetch i use in the dashboard
from datetime import datetime
import locale

import psycopg2
from psycopg2 import sql, Error


# Function to establish a connection to the PostgreSQL database
def connect_to_db(dbname, user, password, host, port):
    try:
        conn = psycopg2.connect(
            dbname=dbname,
            user=user,
            password=password,
            host=host,
            port=port
        )
        print("Connected to the database.")
        return conn
    except Error as e:
        print(f"Error connecting to the database: {e}")


# Function to fetch data from a specified table
def fetch_data(conn):
    try:
        cursor = conn.cursor()
        query = "SELECT * FROM \"public\".\"SensorData\" ORDER BY \"Time\" OFFSET 0"
        cursor.execute(query)
        rows = cursor.fetchall()
        cursor.close()
        return rows
    except Error as e:
        print(f"Error fetching data: {e}")


# Function to verify credentials against database
def verify_credentials(conn):
    if conn:
        try:
            with conn.cursor() as cursor:
                # Execute SQL query to check username and password
                cursor.execute(
                    "SELECT \"username\", \"password\" FROM \"public\".\"users\" WHERE \"username\" = 'chen'")
                row = cursor.fetchone()
                if row:
                    username, password = row
                    return {username: password}
                else:
                    return None
        except psycopg2.Error as e:
            print("Error verifying credentials:", e)


# ---------------------------------------------------------------------------------------

# Function to fetch the latest temperature from PostgreSQL database
def fetch_temperature(conn):
    try:
        with conn.cursor() as cursor:
            # Execute SQL query to get the latest temperature
            cursor.execute("SELECT \"Temperature\" FROM \"public\".\"SensorData\" ORDER BY \"Time\" DESC")
            rows = cursor.fetchall()
            temperature_values = []
            for row in rows:
                temperature_values.append(row[0])  # Fetch the humidity values
            return temperature_values
    except psycopg2.Error as e:
        print("Error fetching temperature:", e)
        return None


# Function to fetch the latest humidity from PostgreSQL database
def fetch_humidity(conn):
    try:
        with conn.cursor() as cursor:
            # Execute SQL query to get the latest humidity
            cursor.execute("SELECT \"Humidité\" FROM \"public\".\"SensorData\" ORDER BY \"Time\" DESC")
            rows = cursor.fetchall()
            humidity_values = []
            for row in rows:
                humidity_values.append(row[0])  # Fetch the humidity values
            return humidity_values
    except psycopg2.Error as e:
        print("Error fetching humidity:", e)
        return None


# Function to fetch the latest tilt status from PostgreSQL database
def fetch_current_tilt(conn):
    try:
        with conn.cursor() as cursor:
            # Execute SQL query to get the latest tilt status
            cursor.execute("SELECT \"Tilt\" FROM \"public\".\"SensorData\" ORDER BY \"Time\" DESC LIMIT 1")
            row = cursor.fetchone()
            if row:
                return row[0]  # Fetch the tilt status
            else:
                return None
    except psycopg2.Error as e:
        print("Error fetching tilt status:", e)
        return None


def fetch_user_access_stats(conn):
    try:
        with conn.cursor() as cursor:
            # Execute SQL query to fetch user access statistics
            cursor.execute(
                "SELECT \"WhoAcess\", COUNT(*), MAX(\"Time\") AS last_access_time FROM \"public\".\"SensorData\" GROUP BY \"WhoAcess\" ORDER BY MAX(\"Time\") DESC;")
            rows = cursor.fetchall()
            user_stats = []
            for row in rows:
                user_stats.append({
                    "user": row[0],
                    "access_count": row[1],
                    "last_access_time": row[2]
                })
            return user_stats
    except psycopg2.Error as e:
        print("Error fetching user access stats:", e)
        return None


# Function to fetch all temperature data for the current day from PostgreSQL database
def fetch_temperature_for_day(conn):
    try:
        # Get current date
        # current_date = datetime.datetime.now().date()
        current_date = "2024-02-12"
        with conn.cursor() as cursor:
            # Execute SQL query to get all temperature readings for the current day
            cursor.execute(
                "SELECT \"Time\", \"Temperature\" FROM \"public\".\"SensorData\" WHERE date(\"Time\") = %s ORDER BY \"Time\"",
                (current_date,))
            rows = cursor.fetchall()
            timestamps = []
            temperatures = []
            for row in rows:
                timestamps.append(row[0])  # Fetch the timestamp
                temperatures.append(row[1])  # Fetch the temperature
            return timestamps, temperatures
    except psycopg2.Error as e:
        print("Error fetching temperature for the day:", e)
        return None, None


# Function to fetch all humidity data for the current day from PostgreSQL database
def fetch_humidity_for_day(conn):
    try:
        # Get current date
        # current_date = datetime.datetime.now().date()
        current_date = "2024-02-12"

        with conn.cursor() as cursor:
            # Execute SQL query to get all humidity readings for the current day
            cursor.execute(
                "SELECT \"Time\", \"Humidité\" FROM \"public\".\"SensorData\" WHERE date(\"Time\") = %s ORDER BY \"Time\"",
                (current_date,))
            rows = cursor.fetchall()
            timestamps = []
            humidity_values = []
            for row in rows:
                timestamps.append(row[0])  # Fetch the timestamp
                humidity_values.append(row[1])  # Fetch the humidity value
            return timestamps, humidity_values
    except psycopg2.Error as e:
        print("Error fetching humidity for the day:", e)
        return None, None


# Function to insert data into a specified table
def insert_data(conn, data):
    try:
        # Set the locale to French
        locale.setlocale(locale.LC_TIME, "fr_FR.UTF-8")

        current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        cursor = conn.cursor()
        # Use gps bool to determine if I use insert or update to handle the double trams
        query = ("INSERT INTO \"public\".\"SensorData\" (\"Tilt\", \"Longitude\", \"Latitude\", \"WhoAcess\", \"Buzzer\""
                 ", \"Temperature\", \"Humidité\", \"Time\") "
                 "VALUES (%s, %s, %s, %s, %s, 31, 72, %s);")

        cursor.execute(query, (bool(data[4]), data[2], data[3], data[1], bool(data[0]), current_time))
        conn.commit()
        cursor.close()
        print("Data inserted successfully.")
    except Error as e:
        print(f"Error inserting data: {e}")


# Function to delete data from a specified table
def delete_data(conn, table_name, condition):
    try:
        cursor = conn.cursor()
        query = sql.SQL("DELETE FROM {} WHERE {}").format(
            sql.Identifier(table_name), sql.Identifier(condition)
        )
        cursor.execute(query)
        conn.commit()
        cursor.close()
        print("Data deleted successfully.")
    except Error as e:
        print(f"Error deleting data: {e}")


# Function to close the database connection
def close_connection(conn):
    try:
        conn.close()
        print("Connection closed.")
    except Error as e:
        print(f"Error closing connection: {e}")

MQTT connection

Python
It's to subscribe to the TTN MQTT channel to get all sensor information.
import paho.mqtt.client as mqtt
import json

# TTN MQTT broker details
broker_address = "eu1.cloud.thethings.network"
port = 1883
username = "test-sodaq@ttn"
password = "NNSXS.BWMLNSI6GKFJQKKOA7KLOXIL7U45M52TN6SJO4I.GWXSKDNFUHY7VZD5GWYUFYL3R3QFAX7VZW6WAW4LTDCMDWZGRJCQ"
client_id = "0"
topic = "#"


# Callback when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    if rc == 0:
        print("Connected with success")
    else:
        print("Connection error")
    # Subscribe to the topic where uplink messages are published
    client.subscribe(topic)


# Callback when a message is received from the server.
def on_message(client, userdata, msg):
    payload = json.loads(msg.payload.decode('utf-8'))
    decoded_payload = payload['uplink_message']['decoded_payload']
    topic = msg.topic  # Get the topic of the received messagex
    print("Message payload:", decoded_payload)


# Create a MQTT client
client = mqtt.Client(client_id=client_id, transport="tcp")

# Set username and password for TTN MQTT broker
client.username_pw_set(username, password)

# Set callback functions
client.on_connect = on_connect
client.on_message = on_message

# Connect to TTN MQTT broker
client.connect(broker_address, port, 60)

# Loop to maintain connection and process incoming messages
client.loop_forever()

The Dashboard

Python
It's all the dashboard, with all layer, callback and datafetch
import locale
from datetime import datetime
import db
from dash import Dash, dcc, html, Input, Output
import plotly.graph_objs as go
import pandas as pd
import dash_bootstrap_components as dbc
from dash_auth import BasicAuth


# ---------------------------------------------------------------------------------------

# Define function to create a Dash table component
def create_user_access_table(user_stats):
    if user_stats:
        # Convert user stats to a DataFrame
        df = pd.DataFrame(user_stats)

        # Slice the DataFrame to include only the first 8 rows for the example
        df = df.head(6)

        # Create a Dash DataTable component
        table = dbc.Table.from_dataframe(
            df, size="sm", striped=True, borderless=True, hover=True
        )
        return table
    else:
        return html.Div("No data available", className="text-muted")


# ---------------------------------------------------------------------------------------

# Set the locale to French
locale.setlocale(locale.LC_TIME, "fr_FR.UTF-8")


# Define a function to get current date and time
def get_current_datetime():
    now = datetime.now()
    formatted_date = now.strftime("%A %d %B")
    formatted_time = now.strftime("%H:%M")
    return formatted_date, formatted_time


# ---------------------------------------------------------------------------------------


def dashboard_page():
    return dbc.Container([

        dcc.Tabs([
            dcc.Tab(label='Live Data Dashboard', children=[
                dbc.Row([
                    dbc.Col([
                        dcc.Graph(id='temperature-graph'),
                    ], width=3),
                    dbc.Col([
                        dcc.Graph(id='humidity-graph'),
                    ], width=3),
                    dbc.Col([
                        html.Div(id='tilt'),
                    ], width=3),
                    dbc.Col([
                        html.H2("User Access Statistics"),
                        html.Div(id='user-access-table'),
                    ], width=3),
                ]),
            ]),
            dcc.Tab(label='Daily Graphics', children=[
                html.H1("Daily Graphics"),
                # Add your other graphics components here

                dbc.Row([
                    dbc.Col([
                        dcc.Graph(id='temperature-graphic'),
                    ], width=6),
                    dbc.Col([
                        dcc.Graph(id='humidity-graphic'),
                    ], width=6),
                ]),

            ]),
        ]),
    ])


def nas_dashboard():
    return dbc.Container([

        dbc.Row([
            dbc.Col(
                dbc.Card(
                    dbc.CardBody(
                        [
                            html.H4("Utilisation du processeur", className="card-title"),
                            dcc.Graph(id='cpu-usage', style={'height': '400px'}),
                        ]
                    ),
                    style={"width": "24rem"},
                ),

            ),

            dbc.Col(
                dbc.Card(
                    dbc.CardBody([
                        html.H4("Charge du cpu moyen", className="card-title"),
                        dcc.Graph(id='cpu-charge', style={'height': '400px'}),
                    ])

                )

            ),

            dbc.Col(
                dbc.Card(
                    dbc.CardBody(
                        [
                            html.H4("Mémoire", className="card-title"),
                            dcc.Graph(id='ram-usage', style={'height': '400px'}),
                        ]
                    ),
                    style={"width": "24rem"},
                ),
            ),

        ]),

        dbc.Row([
            dbc.Col([
                html.Div(id="system-info")
            ], width=4),



            dbc.Col(
                html.Div(id='storage'),

            ),

            dbc.Col(
                html.Div(id='network'),

            ),

        ]),

    ])


def home_page():
    return dbc.Container([
        html.H1("Mon NAS"),
        dbc.Row([
            dbc.Col([
                dcc.Graph(id='map', style={'width': '100%'}),
            ], width=8),
            dbc.Col([

                dbc.CardGroup([
                    dbc.Card(
                        dbc.CardBody(
                            [
                                html.H4(id="date-content", className="card-title"),
                                html.P(id="time-content", className="card-text"),
                            ]
                        ),
                    ),
                    dbc.Card(
                        html.I(className="fa-regular fa-clock",
                               style={"textAlign": "center", "fontSize": 30, "margin": "auto"}),
                        className="bg-primary",
                        style={"maxWidth": 75},
                    )
                ]),

                html.Iframe(
                    src="https://calendar.google.com/calendar/embed?src=en.french%23holiday%40group.v.calendar.google.com&ctz=Europe%2FParis",
                    style={"width": "100%", "height": "400px", "border": "0"}),

                dbc.CardGroup([
                    dbc.Card(
                        dbc.CardBody(
                            [
                                html.H4("Etat du NAS", className="card-title"),

                            ]
                        ),
                    ),
                    dbc.Card(
                        html.I(className="fa-regular fa-square-check",
                               style={"textAlign": "center", "fontSize": 30, "margin": "auto"}),
                        id="nas_statue",
                        className="bg-success",
                        style={"maxWidth": 75},
                    )
                ], ),

            ], width=4),

        ]),

    ])


# ---------------------------------------------------------------------------------------

# STYLE SECTION

# the style arguments for the sidebar. We use position:fixed and a fixed width
SIDEBAR_STYLE = {
    "position": "fixed",
    "top": 0,
    "left": 0,
    "bottom": 0,
    "width": "16rem",
    "padding": "2rem 1rem",
    "background-color": "#edeef0",
}

# the styles for the main content position it to the right of the sidebar and
# add some padding.
CONTENT_STYLE = {
    "top": 0,
    "right": 0,
    "bottom": 0,
    "margin-left": "15rem",
}

sidebar = html.Div(
    [
        html.H2("NASecure", className="display-4"),
        html.Hr(),
        html.Img(src="assets/logo.png"),
        html.P(
            "", className="lead"
        ),
        dbc.Nav(
            [
                dbc.NavLink("Home", href="/home", active="exact"),
                dbc.NavLink("Dashboard", href="/dash", active="exact"),
                dbc.NavLink("NAS", href="/nas", active="exact"),
            ],
            vertical=True,
            pills=True,
        ),
    ],
    style=SIDEBAR_STYLE,
)

content = html.Div(id="page-content", style=CONTENT_STYLE)

# ---------------------------------------------------------------------------------------

FONT_AWESOME = "https://use.fontawesome.com/releases/v6.5.1/css/all.css"

# Initialize Dash app
app = Dash(__name__, external_stylesheets=["/assets/styles.css", FONT_AWESOME, dbc.themes.BOOTSTRAP])

# Connect to the database
conn = db.connect_to_db("mydb", "chen", "rioc", "localhost", "5432")
if conn is None:
    exit(1)

VALID_USERNAME_PASSWORD_PAIRS = db.verify_credentials(conn)

# Authentication
auth = BasicAuth(app, VALID_USERNAME_PASSWORD_PAIRS)


# Define layout of the app
app.layout = dbc.Container([

    html.Div([dcc.Location(id="url"), sidebar, content]),

    dcc.Interval(
        id='interval-component',
        interval=10 * 1000,  # Update every 10 seconds
        n_intervals=0
    )

])


# ---------------------------------------------------------------------------------------

@app.callback(Output("page-content", "children"), [Input("url", "pathname")])
def render_page_content(pathname):
    if pathname == "/dash":
        return dashboard_page()
    elif pathname == "/nas":
        return nas_dashboard()
    elif pathname == "/home":
        return home_page()
    # If the user tries to reach a different page, return a 404 message
    return html.Div(
        [
            html.H1("404: Not found", className="text-danger"),
            html.Hr(),
            html.P(f"The pathname {pathname} was not recognised..."),
        ],
        className="p-3 bg-light rounded-3",
    )


# Callback to update the temperature graph
@app.callback(Output('temperature-graph', 'figure'),
              [Input('interval-component', 'n_intervals')])
def update_temperature_graph(n):
    # Fetch current temperature
    current_temperature = db.fetch_temperature(conn)
    temperature_value = current_temperature[0]

    # Determine the color based on temperature range
    if temperature_value <= 15:
        bg_color = "lightblue"  # Cold color
    elif 16 <= temperature_value <= 28:
        bg_color = "lime"  # Moderate color
    elif 29 <= temperature_value <= 35:
        bg_color = "orange"  # Warm color
    else:
        bg_color = "red"  # Hot color

    # Create a Plotly graph for temperature

    fig = go.Figure(go.Indicator(
        mode="number+delta",
        value=current_temperature[0],
        number={'suffix': " C°"},
        delta={'reference': current_temperature[1], "valueformat": ".2%", 'relative': True},
        title={'text': "Current Temperature"},
    ))

    # Set background color
    # fig.update_layout(paper_bgcolor=bg_color)

    return fig


# Callback to update the humidity graph
@app.callback(Output('humidity-graph', 'figure'),
              [Input('interval-component', 'n_intervals')])
def update_humidity_graph(n):
    # Fetch current humidity
    current_humidity = db.fetch_humidity(conn)
    humidity_value = current_humidity[0]

    # Determine the color based on humidity range
    if humidity_value <= 30:
        bg_color = "lightblue"  # Light Blue color
    elif 31 <= humidity_value <= 60:
        bg_color = "cyan"  # Cyan color
    elif 61 <= humidity_value <= 80:
        bg_color = "royalblue"  # Lapis color
    else:
        bg_color = "darkblue"  # Dark Blue color

    # Create a Plotly graph for humidity
    fig = go.Figure(go.Indicator(
        mode="number+delta",
        value=current_humidity[0],
        number={'suffix': " %"},
        delta={'reference': current_humidity[1], "valueformat": ".2%", 'relative': True},
        title={'text': "Current Humidity"},
    ))

    # Set background color
    # fig.update_layout(paper_bgcolor=bg_color)

    return fig


# Callback to update the tilt box color
@app.callback(Output('tilt', 'children'),
              [Input('interval-component', 'n_intervals')])
def update_tilt_display(n):
    # Fetch current tilt status
    current_tilt = db.fetch_current_tilt(conn)

    # Determine display based on tilt status
    if not current_tilt:
        # Define the data for the green circle
        x = [0]
        y = [0]

        # Create a Plotly figure with a single green circle
        fig = go.Figure(go.Scatter(
            x=x,
            y=y,
            mode='markers',
            marker=dict(color='green', size=150),
        ))

        # Add text annotation for "Tilt Statues"
        fig.add_annotation(
            x=0,
            y=0,
            text="Tilt Statues",
            showarrow=False,
            font=dict(color='black', size=14)
        )

        # Update layout to ensure the circle is centered
        fig.update_layout(
            xaxis=dict(range=[-1, 1], visible=False),  # Hide x-axis
            yaxis=dict(range=[-1, 1], visible=False),  # Hide y-axis
            plot_bgcolor='rgba(0,0,0,0)',  # Make background transparent
        )

        return dcc.Graph(figure=fig)
    else:
        return html.Div(className="d-flex flex-column align-items-center justify-content-center", children=[
            dbc.Spinner(color="danger", type="grow", size="sm",
                        spinner_style={"width": "200px", "height": "200px"}),
            html.Div("Tilt Statues",
                     style={"position": "relative", "transform": "translate(0%, -400%)"}),
        ])


# Callback to update the user access table
@app.callback(Output('user-access-table', 'children'),
              [Input('interval-component', 'n_intervals')])
def update_user_access_table(n):
    # Fetch user access statistics
    user_stats = db.fetch_user_access_stats(conn)
    # Create table component
    return create_user_access_table(user_stats)


# Callback to update the temperature graphic with data for the day
@app.callback(Output('temperature-graphic', 'figure'),
              [Input('interval-component', 'n_intervals')])
def update_temperature_graphic(n):
    # Fetch all temperature data for the day
    timestamps, temperatures = db.fetch_temperature_for_day(conn)

    if timestamps and temperatures:
        # Create a Plotly graph for temperature
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=timestamps, y=temperatures, mode='lines', name='Temperature'))
        fig.update_layout(title='Temperature Variation Throughout the Day',
                          xaxis_title='Time',
                          yaxis_title='Temperature (°C)')
        return fig
    else:
        # Return an empty graph if no data available
        return go.Figure()


# Callback to update the humidity graphic with data for the day
@app.callback(Output('humidity-graphic', 'figure'),
              [Input('interval-component', 'n_intervals')])
def update_humidity_graphic(n):
    # Fetch all humidity data for the day
    timestamps, humidity_values = db.fetch_humidity_for_day(conn)

    if timestamps and humidity_values:
        # Create a Plotly graph for humidity
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=timestamps, y=humidity_values, mode='lines', name='Humidity'))
        fig.update_layout(title='Humidity Variation Throughout the Day',
                          xaxis_title='Time',
                          yaxis_title='Humidity (%)')
        return fig
    else:
        # Return an empty graph if no data available
        return go.Figure()


@app.callback(Output('system-info', 'children'),
              [Input('interval-component', 'n_intervals')])
def systeminfo(n):
    return dbc.Card(
        dbc.CardBody(
            [
                html.H4("Informations système", className="card-title"),
                html.H6("", className="card-subtitle"),
                html.Tr(html.Strong("Nom de l'hôte")), html.Td("berry"),
                html.Tr(html.Strong("Processeur")), html.Td("ARMv7 Processor rev 4 (v7l)"),
                html.Tr(html.Strong("Noyau")), html.Td("Linux 6.21-v7+"),
                html.Tr(html.Strong("Horloge système")), html.Td("Mon Mar 4 12:16:42 2024"),
                html.Tr(html.Strong("Fonctionnement")), html.Td("deux heures"),
            ]
        ),
        style={"width": "18rem"},
    ),


@app.callback(Output('storage', 'children'),
              [Input('interval-component', 'n_intervals')])
def storage(n):
    return dbc.Card(
        dbc.CardBody([
            html.H4("Système de fichier", className="card-title"),
            html.Div(style={"width": "5px", "height": "100%", "background-color": "green", "position": "absolute",
                            "left": "0", "top": "0"}),  # Adding inline styles for the vertical bar
            html.Tr(html.Strong("dev/sda1")),
            html.Tr(html.Strong("1.80 GB")),
            dbc.Progress(label="75%", value=75, striped=True)
        ])

    )


@app.callback(Output('network', 'children'),
              [Input('interval-component', 'n_intervals')])
def network(n):
    return dbc.Card(
        dbc.CardBody([
            html.H4("Interface réseau", className="card-title"),
            html.Div(style={"width": "5px", "height": "100%", "background-color": "green", "position": "absolute",
                            "left": "0", "top": "0"}),  # Adding inline styles for the vertical bar
            dbc.Row([
                dbc.Col(
                    dbc.Card(
                        dbc.CardBody([
                            html.H6("eth0", className="card-title"),
                            html.P(
                                "10.100.3.6/24",
                                className="card-text",
                            ),
                        ]),
                        id='eth0', color="success")
                ),

                dbc.Tooltip(
                    html.Div([
                        html.Div("MAC: ff-ff-ff-ff-ff"),
                        html.Div("IPv6: fe80::1"),
                        html.Div("Paserelle: 10.100.0.254"),
                    ])
                    , target="eth0",
                ),

                dbc.Col(
                    dbc.Card(
                        dbc.CardBody([
                            html.H6("wlan0", className="card-title"),
                            html.P(
                                "-",
                                className="card-text",
                            ),
                        ]),
                        id='wlan0', color="secondary")
                ),

                dbc.Tooltip(
                    html.Div([
                        html.Div("MAC: -"),
                        html.Div("IPv6: -"),
                        html.Div("Paserelle: -"),
                    ]),
                    target="wlan0",
                ),

                dbc.Col(
                    dbc.Card(
                        dbc.CardBody([
                            html.H6("wlan1", className="card-title"),
                            html.P(
                                "-",
                                className="card-text",
                            ),
                        ]),
                        id='wlan1', color="secondary")
                ),

                dbc.Tooltip(
                    html.Div([
                        html.Div("MAC: -"),
                        html.Div("IPv6: -"),
                        html.Div("Paserelle: -"),
                    ]),
                    target="wlan1",
                ),

            ]),

        ])

    )


@app.callback(Output('ram-usage', 'figure'),
              [Input('interval-component', 'n_intervals')])
def update_ram_usage_graphic(n):
    labels = ["Used", "Free"]
    total_ram = 100
    value = [52, 48]
    fig = go.Figure(data=[go.Pie(labels=labels, values=value, name='ram usage', hole=.8, hoverinfo="label+percent",
                                 hovertemplate='<b>%{label}</b><br>' +
                                               'RAM Usage: %{value:.0f}%<br>' +
                                               'Total RAM: ' + str(total_ram))])
    return fig


@app.callback(Output('cpu-usage', 'figure'),
              [Input('interval-component', 'n_intervals')])
def cpu_usage(n):
    fig = go.Figure(go.Indicator(
        mode="gauge+number+delta",
        value=20,
        domain={'x': [0, 1], 'y': [0, 1]},
        title={'text': "CPU", 'font': {'size': 24}},
        gauge={
            'axis': {'range': [None, 100], 'tickwidth': 1, 'tickcolor': "darkblue"},
            'bar': {'color': "darkblue"},
            'bgcolor': "white",
            'borderwidth': 2,
            'bordercolor': "gray",
            'steps': [
                {'range': [0, 50], 'color': 'cyan'},
                {'range': [50, 100], 'color': 'royalblue'}],
            'threshold': {
                'line': {'color': "red", 'width': 4},
                'thickness': 0.75,
                'value': 95}}))
    return fig


@app.callback(Output('cpu-charge', 'figure'),
              [Input('interval-component', 'n_intervals')])
def cpu_charge(n):
    fig = go.Figure()

    fig.add_trace(go.Indicator(
        mode="number+gauge+delta", value=50,
        domain={'x': [0.25, 1], 'y': [0.08, 0.25]},
        title={'text': "1min"},
        gauge={
            'shape': "bullet",
            'axis': {'range': [None, 100]},
            'steps': [
                {'range': [0, 50], 'color': "gray"},
                {'range': [50, 100], 'color': "lightgray"}],
            'bar': {'color': "black"}}))

    fig.add_trace(go.Indicator(
        mode="number+gauge+delta", value=35,
        domain={'x': [0.25, 1], 'y': [0.4, 0.6]},
        title={'text': "5min"},
        gauge={
            'shape': "bullet",
            'axis': {'range': [None, 100]},
            'steps': [
                {'range': [0, 50], 'color': "gray"},
                {'range': [50, 100], 'color': "lightgray"}],
            'bar': {'color': "black"}}))

    fig.add_trace(go.Indicator(
        mode="number+gauge+delta", value=70,
        domain={'x': [0.25, 1], 'y': [0.7, 0.9]},
        title={'text': "10min"},
        gauge={
            'shape': "bullet",
            'axis': {'range': [None, 100]},
            'steps': [
                {'range': [0, 50], 'color': "gray"},
                {'range': [50, 100], 'color': "lightgray"}],
            'bar': {'color': "black"}}))
    fig.update_layout(height=400, margin={'t': 0, 'b': 0, 'l': 0})

    return fig


@app.callback(Output('map', 'figure'),
              [Input('interval-component', 'n_intervals')])
def update_map(n):
    # Your GPS coordinates
    latitude = 49.900035  # Example latitude
    longitude = 2.290920  # Example longitude

    # Initialize the figure
    fig = go.Figure(go.Scattermapbox(
        lat=[latitude],
        lon=[longitude],
        mode='markers',
        marker=go.scattermapbox.Marker(
            size=14
        ),
        text=['NAS Localisation'],
    ))

    # Update layout for the map
    fig.update_layout(
        title='Localisation du NAS',
        hovermode='closest',
        mapbox=dict(
            style='carto-positron',  # You can change the map style here
            center=go.layout.mapbox.Center(
                lat=latitude,  # Centered around France
                lon=longitude
            ),
            zoom=10  # You can adjust the initial zoom level
        )
    )

    return fig


# Define callback to change className
@app.callback(
    Output("nas_statue", "className"),
    [Input('interval-component', 'n_intervals')]
)
def change_classname(n):
    state = True

    if state:
        return "bg-success"
    else:
        return "bg-danger"


# Define callback to update date and time
@app.callback(
    [Output("date-content", "children"),
     Output("time-content", "children")],
    [Input("interval-component", "n_intervals")]
)
def update_datetime(n):
    formatted_date, formatted_time = get_current_datetime()
    return formatted_date, formatted_time


# ---------------------------------------------------------------------------------------

# Run the app
if __name__ == '__main__':
    app.run_server(debug=True)

SNMP connection

Python
It's all OIDs that was used for the dashboard.
import asyncio
import datetime

from puresnmp import Client, V2C, PyWrapper

# Replace the following values with your own
host = "10.100.3.6"  # The IP address of the Pi
community = "public"  # The SNMP community string

"""
# Define a list of OIDs to query
oids = [
    "1.3.6.1.2.1.1.1.0",  # System description
    "1.3.6.1.2.1.1.3.0",  # System uptime
    "1.3.6.1.2.1.25.1.1.0",  # System clock
    "1.3.6.1.4.1.2021.4.5.0",  # Total memory size
    "1.3.6.1.4.1.2021.4.6.0",  # Used memory size
    "1.3.6.1.4.1.2021.4.11.0",  # Free RAM size
    "1.3.6.1.4.1.2021.10.1.3.1",  # CPU 1 minute Load
    "1.3.6.1.4.1.2021.10.1.3.2",  # CPU 5 minute Load
    "1.3.6.1.4.1.2021.10.1.3.3",  # CPU 10 minute Load
    "1.3.6.1.4.1.2021.11.9.0",  # CPU idle time
    "1.3.6.1.4.1.2021.13.16.2.1.3.1",  # CPU temperature need /1000 to get in C°

    "1.3.6.1.4.1.2021.9.1.3.21",  # Storage Path
    "1.3.6.1.4.1.2021.9.1.6.21",  # Total size of the disk / par 1024 to get in Go
    "1.3.6.1.4.1.2021.9.1.9.21",  # % Storage Used
    "1.3.6.1.2.1.2.2.1.2.2",  # Interface name eth0
    "1.3.6.1.2.1.2.2.1.2.3",  # Interface name wlan 0
    "1.3.6.1.2.1.2.2.1.2.4",  # Interface name wlan 1
    "1.3.6.1.2.1.2.2.1.8.2",  # Interface statues eth0
    "1.3.6.1.2.1.2.2.1.8.3",  # Interface statues wlan 0
    "1.3.6.1.2.1.2.2.1.8.4",  # Interface statues wlan 1
    "1.3.6.1.2.1.2.2.1.6.2",  # MAC address eth0
    "1.3.6.1.2.1.2.2.1.6.3",  # MAC address wlan 0
    "1.3.6.1.2.1.2.2.1.6.4",  # MAC address wlan 1
    "1.3.6.1.2.1.2.2.1.5.2",  # Interface bandwidth in bits per second eth0
    "1.3.6.1.2.1.2.2.1.5.3",  # Interface bandwidth in bits per second wlan0
    "1.3.6.1.2.1.2.2.1.5.4",  # Interface bandwidth in bits per second wlan1
    "1.3.6.1.2.1.4.21.1.7.0.0.0.0",  # Interface wlan1 gateway
    "1.3.6.1.2.1.4.20.1.1.10.100.3.6",  # IPv4 address

]
"""


async def fetch_snmp_data(oid):
    client = PyWrapper(Client(host, V2C(community)))
    output = await client.get(oid)
    return output


async def get_system_info():
    oids = [
        "1.3.6.1.2.1.1.1.0",  # System description
        "1.3.6.1.2.1.1.3.0",  # System uptime
        "1.3.6.1.2.1.25.1.2.0",  # System date
    ]
    results = {}
    for oid in oids:
        results[oid] = await fetch_snmp_data(oid)

    parsed_info = {}

    for oid, value in results.items():
        if oid == "1.3.6.1.2.1.1.1.0":
            description = value.decode("utf-8").split(' ')
            parsed_info["Kernel"] = description[0]+" "+description[2]
            parsed_info["Hostname"] = description[1]
        elif oid == "1.3.6.1.2.1.1.3.0":
            parsed_info["UpTime"] = value.seconds
        elif oid == "1.3.6.1.2.1.25.1.2.0":
            date_hex = value.hex()
            # Extract year, month, day, hour, minute, and second from the hex string
            year = int(date_hex[:4], 16)
            month = int(date_hex[4:6], 16)
            day = int(date_hex[6:8], 16)
            hour = int(date_hex[8:10], 16)
            minute = int(date_hex[10:12], 16)
            second = int(date_hex[12:14], 16)
            # Create a datetime object
            parsed_info["Date"] = datetime.datetime(year, month, day, hour, minute, second).strftime(
                '%Y-%m-%d %H:%M:%S')
    return parsed_info


async def get_memory_info():
    oids = [
        "1.3.6.1.4.1.2021.4.5.0",  # Total memory size
        "1.3.6.1.4.1.2021.4.6.0",  # Used memory size
        "1.3.6.1.4.1.2021.4.11.0",  # Free RAM size
    ]
    results = {}
    for oid in oids:
        results[oid] = await fetch_snmp_data(oid)

    total_memory = results.get('1.3.6.1.4.1.2021.4.5.0', 0)
    used_memory = results.get('1.3.6.1.4.1.2021.4.6.0', 0)

    memory_info = {
        'total_memory': total_memory/1024,
        'used_memory': used_memory/1024,
    }
    
    return memory_info


async def get_cpu_info():
    oids = [
        "1.3.6.1.4.1.2021.10.1.3.1",  # CPU 1 minute Load
        "1.3.6.1.4.1.2021.10.1.3.2",  # CPU 5 minute Load
        "1.3.6.1.4.1.2021.10.1.3.3",  # CPU 10 minute Load
        "1.3.6.1.4.1.2021.11.9.0",  # CPU idle time
        "1.3.6.1.4.1.2021.13.16.2.1.3.1",  # CPU temperature
    ]
    results = {}
    for oid in oids:
        results[oid] = await fetch_snmp_data(oid)

    cpu_load_1min = results.get('1.3.6.1.4.1.2021.10.1.3.1', 0)
    cpu_load_5min = results.get('1.3.6.1.4.1.2021.10.1.3.2', 0)
    cpu_load_10min = results.get('1.3.6.1.4.1.2021.10.1.3.3', 0)
    cpu_idle_time = results.get('1.3.6.1.4.1.2021.11.9.0', 0)
    cpu_temperature = results.get('1.3.6.1.4.1.2021.13.16.2.1.3.1', 0)

    cpu_info = {
        'cpu_load_1min': cpu_load_1min*100,
        'cpu_load_5min': cpu_load_5min*100,
        'cpu_load_10min': cpu_load_10min*100,
        'cpu_idle_time': cpu_idle_time,
        'cpu_temperature': cpu_temperature/1000
    }

    return cpu_info


async def get_storage_info():
    oids = [
        "1.3.6.1.4.1.2021.9.1.3.21",  # Storage Path
        "1.3.6.1.4.1.2021.9.1.6.21",  # Total size of the disk
        "1.3.6.1.4.1.2021.9.1.9.21",  # % Storage Used
    ]
    results = {}
    for oid in oids:
        results[oid] = await fetch_snmp_data(oid)

    storage_path = results.get('1.3.6.1.4.1.2021.9.1.3.21', "")
    total_disk_size = results.get('1.3.6.1.4.1.2021.9.1.6.21', 0)
    storage_used_percentage = results.get('1.3.6.1.4.1.2021.9.1.9.21', 0)

    storage_info = {
        'storage_path': storage_path,
        'total_disk_size': total_disk_size/1024,
        'storage_used_percentage': storage_used_percentage
    }

    return storage_info


async def get_interface_info():
    oids = [
        "1.3.6.1.2.1.2.2.1.2.2",  # Interface name eth0
        "1.3.6.1.2.1.2.2.1.2.3",  # Interface name wlan 0
        "1.3.6.1.2.1.2.2.1.2.4",  # Interface name wlan 1
        "1.3.6.1.2.1.2.2.1.8.2",  # Interface status eth0
        "1.3.6.1.2.1.2.2.1.8.3",  # Interface status wlan 0
        "1.3.6.1.2.1.2.2.1.8.4",  # Interface status wlan 1
        "1.3.6.1.2.1.2.2.1.6.2",  # MAC address eth0
        "1.3.6.1.2.1.2.2.1.6.3",  # MAC address wlan 0
        "1.3.6.1.2.1.2.2.1.6.4",  # MAC address wlan 1
        "1.3.6.1.2.1.2.2.1.5.2",  # Interface bandwidth in bits per second eth0
        "1.3.6.1.2.1.2.2.1.5.3",  # Interface bandwidth in bits per second wlan0
        "1.3.6.1.2.1.2.2.1.5.4",  # Interface bandwidth in bits per second wlan1
    ]
    results = {}
    for oid in oids:
        data = await fetch_snmp_data(oid)
        # Parse the data based on the OID
        if oid.startswith("1.3.6.1.2.1.2.2.1.2."):
            interface_name = oid.split('.')[-1]
            results[f'interface_name_{interface_name}'] = data.decode()
        elif oid.startswith("1.3.6.1.2.1.2.2.1.8."):
            interface_status = "up" if data == 1 else "down"
            interface_name = "eth0" if oid.endswith("2") else "wlan0" if oid.endswith("3") else "wlan1"
            results[f'interface_status_{interface_name}'] = interface_status
        elif oid.startswith("1.3.6.1.2.1.2.2.1.6."):
            interface_name = "eth0" if oid.endswith("2") else "wlan0" if oid.endswith("3") else "wlan1"
            mac_address = ":".join("{:02x}".format(x) for x in data)
            results[f'mac_address_{interface_name}'] = mac_address
        elif oid.startswith("1.3.6.1.2.1.2.2.1.5."):
            interface_name = "eth0" if oid.endswith("2") else "wlan0" if oid.endswith("3") else "wlan1"
            results[f'interface_bandwidth_{interface_name}'] = data
    return results


async def get_network_info():
    oids = [
        "1.3.6.1.2.1.4.21.1.7.0.0.0.0",  # Interface gateway
        "1.3.6.1.2.1.4.20.1.1.10.100.3.6",  # IPv4 address
    ]
    results = {}
    for oid in oids:
        data = await fetch_snmp_data(oid)
        # Parse the data based on the OID
        if oid == "1.3.6.1.2.1.4.21.1.7.0.0.0.0":
            results['interface_gateway'] = data.decode()
        elif oid == "1.3.6.1.2.1.4.20.1.1.10.100.3.6":
            results['ipv4_address'] = data.decode()
    return results


async def main():
    system_info = await get_system_info()
    print("System Info:", system_info)

    memory_info = await get_memory_info()
    print("Memory Info:", memory_info)

    cpu_info = await get_cpu_info()
    print("CPU Info:", cpu_info)

    storage_info = await get_storage_info()
    print("Storage Info:", storage_info)

    interface_info = await get_interface_info()
    print("Interface Info:", interface_info)

    network_info = await get_network_info()
    print("Network Info:", network_info)

asyncio.run(main())

LoRa Sodaq Explorer

Arduino
Code that collect and send all data to TTN through LoRa
#include <Sodaq_RN2483.h>

#if defined(ARDUINO_SODAQ_EXPLORER)
#define debugSerial  SerialUSB
#define LORA_STREAM     Serial2
#define LORA_RESET_PIN  LORA_RESET
#elif defined(ARDUINO_SODAQ_ONE)
#define debugSerial  SerialUSB
#define LORA_STREAM     Serial1
#define LORA_RESET_PIN  LORA_RESET
#else
#error "Please select Sodaq ExpLoRer or SodaqOne board"
#endif

#define FORCE_FULL_JOIN 0
#define LORA_PORT       1
#define USE_OTAA        1
#define USE_ABP         0

#define uplinkCnt 10

#define debugSerial SerialUSB
#define loraSerial  Serial2

//#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]  = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} ;
  const uint8_t appEUI[8]  = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} ;
  const uint8_t appKey[16] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0} ;
#endif


// Pin Used For The Sensor ----------------------------
#define TILT_SENSOR 11
#define FINGERPRINT_SENSOR 10

//End Pin Used For The Sensor -------------------------

void LoRaSendPacket(int Tilt, float latitude, float longitude, int whoAccessed, int hasBuzzerActivated); //define in advance in function that is at the end of the code


// Payload is 'Microchip ExpLoRa' in HEX
const uint8_t testPayload[] = {0x4d, 0x69, 0x63, 0x72, 0x6f, 0x63, 0x68, 0x69, 0x70, 0x20, 0x45, 0x78, 0x70, 0x4c, 0x6f, 0x52, 0x61} ;

typedef union {//Typedef to convert byte to float automaticaly
  float floatingPoint;
  byte binary[4];
} binaryFloat;

typedef union {//Typedef to jump between byte, interger and float
  float floatingPoint;
  int interger;
  byte binary[4];
} binaryIntFloat;


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

  pinMode(TILT_SENSOR, INPUT);

	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
  
  /* Interrupt commented because of difficulty of making it work
  // Start of Interrupt Setup ----------------------------------------------------
  attachInterrupt(digitalPinToInterrupt(TILT_INTERRUPT), tiltInterruptFunction, FALLING);
  attachInterrupt(digitalPinToInterrupt(FINGERPRINT_INTERRUPT), fingerprintInterruptFunction, RISING);
  // End of Interrupt Setup ------------------------------------------------------
  */

 LoRaSendPacket(0, 2.5241, 48.2482, 255, 0);

}

int fiveMinCounter = 290;
int counter = 0;
binaryFloat hi;
binaryFloat Gps[2];

void loop()
{

  if(digitalRead(TILT_SENSOR)){
    //LoRaSendPacket(1, 0, 0, 255, 0); //TODO Uncomment this
    debugSerial.println("Warning Tilt");
  }
  if(Serial.available() >  0){
    int incomingByte = Serial.read();
    debugSerial.println("Received :");
    debugSerial.println(incomingByte);
    //LoRaSendPacket(0, 0, 0, incomingByte, 0);
    if(incomingByte == 1){//If the first data received is a 1 then it is interpreted as a fingerprint data
      while(Serial.available() <=  0);
      int incomingByte = Serial.read();
      debugSerial.println("Received :");
      debugSerial.println(incomingByte);
      LoRaSendPacket(0, 0, 0, incomingByte, 0);
    }
    if(incomingByte == 9){//If the first data received is a 9 then the following data is interpreted as GPS data
      while(counter < 4){//loop 4 time to receive every byte of latitude
        while(Serial.available() <=  0);
        byte incomingLocation = Serial.read();
        //Serial.println(incomingLocation, HEX);
        hi.binary[counter] = incomingLocation;
        counter++;
        if(counter > 3){
          debugSerial.println(hi.floatingPoint , 6);
          Gps[0] = hi;
        }
      }
      counter = 0;
      while(counter < 4){//loop 4 time to receive every byte of longitude
        while(Serial.available() <=  0);
        byte incomingLocation = Serial.read();
        //Serial.println(incomingLocation, HEX);
        hi.binary[counter] = incomingLocation;
        counter++;
        if(counter > 3){
          debugSerial.println(hi.floatingPoint , 6);
          Gps[1] = hi;
          LoRaSendPacket(0, Gps[0].floatingPoint, Gps[1].floatingPoint, 255, 0);
        }
      }
      counter = 0;
    }
  }
  //delay 1 sec then add 1 to counter till its 5 min
  delay(1500);
  fiveMinCounter++;
  //debugSerial.println(fiveMinCounter);
  if(fiveMinCounter > 300){
    LoRaSendPacket(digitalRead(TILT_SENSOR), 0, 0, 255, 0);
    fiveMinCounter = 0 ;
  }
}

/*---------------
1s Byte : Tilt
2nd Byte : Latitude
3nd Byte : Longitude
4nd Byte : WhoAccessed - 255 Base value - nobody accessed
5th Byte : HasBuzzerActivated 
----------------*/


void LoRaSendPacket(int Tilt, float latitude, float longitude, int whoAccessed, int hasBuzzerActivated){
  binaryIntFloat LoRaFormat[5];
  LoRaFormat[0].interger = Tilt;
  LoRaFormat[1].floatingPoint = latitude;
  LoRaFormat[2].floatingPoint = longitude;
  LoRaFormat[3].interger = whoAccessed;
  LoRaFormat[4].floatingPoint = hasBuzzerActivated;
  uint8_t error;
  error = LoRaBee.send(LORA_PORT, (uint8_t*)LoRaFormat, sizeof(LoRaFormat));
  if(error == NoError){
      debugSerial.println("Succesful send");
  }
  else{
      debugSerial.print("Error 404 : ");
      debugSerial.print("#");
      debugSerial.print(NoError);
      debugSerial.print("#");
      debugSerial.print(NoResponse);
      debugSerial.print("#");
      debugSerial.print(Timeout);
      debugSerial.print("#");
      debugSerial.print(PayloadSizeError);
      debugSerial.print("#");
      debugSerial.print(InternalError);
      debugSerial.print("#");
      debugSerial.print(Busy);
      debugSerial.print("#");
      debugSerial.print(NetworkFatalError);
      debugSerial.print("#");
      debugSerial.print(NotConnected);
      debugSerial.print("#");
      debugSerial.print(NoAcknowledgment);
      debugSerial.print("# ->");
      debugSerial.println(error);
  }
}

/*
How to setup Tilt Sensor :
Green Wire  : GND
Yellow Wire : 5V
Grey Wire   : DATA
Also is from left to right
*/

GPS Arduino Leonardo shield

Arduino
The code was used in a Arduino Leonardo with a GPS shield.
#include "TinyGPS.h"
#include <SoftwareSerial.h>
/*
   This sample sketch demonstrates the normal use of a TinyGPS++ (TinyGPSPlus) object.
   It requires the use of SoftwareSerial, and assumes that you have a
   4800-baud serial GPS device hooked up on pins 4(rx) and 3(tx).
*/

//this code was only modified from the sketch to use typdef union and send the gps data through a serial connection (Line 77 for the serial)


static const int RXPin = 11, TXPin = 12;
static const uint32_t GPSBaud = 9600;

typedef union {
  float floatingPoint;
  uint32_t as_int;
} binaryFloatConversion;

typedef union {
  float floatingPoint;
  byte binary[4];
} binaryFloat;


// The TinyGPS++ object
TinyGPSPlus gps;

// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

void setup()
{
  Serial.begin(115200);
  Serial1.begin(GPSBaud);
  ss.begin(9600);
  Serial.println(F("DeviceExample.ino"));
  Serial.println(F("A simple demonstration of TinyGPS++ with an attached GPS module"));
  Serial.print(F("Testing TinyGPS++ library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
  Serial.println(F("by Mikal Hart"));
  Serial.println();
}

void loop()
{
  // This sketch displays information every time a new sentence is correctly encoded.

  while (Serial1.available() > 0)
   
    if (gps.encode(Serial1.read()))
      displayInfo();

  if (millis() > 5000 && gps.charsProcessed() < 10)
  {
    Serial.println(F("No GPS detected: check wiring."));
    while(true);
  }
}

void displayInfo()
{
  binaryFloatConversion arg;
  binaryFloat lng,lat;
   delay(100);
  Serial.print(F("Location: ")); 
  if (gps.location.isValid())
  {
    Serial.print(gps.location.lat(), 6);
    Serial.print(F(","));
    Serial.print(gps.location.lng(), 6);
    // get access to the float as a byte-array:
    arg.floatingPoint = gps.location.lng();
    lng.floatingPoint =  gps.location.lng();
    lat.floatingPoint =  gps.location.lat();
    Serial.print(" Binary : ");
    Serial.print(arg.as_int, HEX);
  // write the data to the serial
    ss.write(lat.binary,4);
    ss.write(lng.binary,4);
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.print(F("  Date/Time: "));
  if (gps.date.isValid())
  {
    Serial.print(gps.date.month());
    Serial.print(F("/"));
    Serial.print(gps.date.day());
    Serial.print(F("/"));
    Serial.print(gps.date.year());
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.print(F(" "));
  if (gps.time.isValid())
  {
    if (gps.time.hour() < 10) Serial.print(F("0"));
    Serial.print(gps.time.hour());
    Serial.print(F(":"));
    if (gps.time.minute() < 10) Serial.print(F("0"));
    Serial.print(gps.time.minute());
    Serial.print(F(":"));
    if (gps.time.second() < 10) Serial.print(F("0"));
    Serial.print(gps.time.second());
    Serial.print(F("."));
    if (gps.time.centisecond() < 10) Serial.print(F("0"));
    Serial.print(gps.time.centisecond());
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.println();
}

FingerPrint Detector

Arduino
Code for the fingerprint detector being used by an Arduino Leonardo
#include <Adafruit_Fingerprint.h>
#include "SomeSerial.h"
#define mySerial Serial1

Adafruit_Fingerprint finger = Adafruit_Fingerprint(&mySerial);

#define lagitude 1
#define lontitude 0


/*____________________
Red : Vcc (3.3V)
Yellow : Tx
White : Rx
Black : GND
_____________________*/
uint8_t id;

typedef union{
  float floatingPoint;
  byte binary[4];
} binaryFloat;


const int led = 16; //GPIO5
SoftwareSerial SerialArduino(10, 11); // RX, TX  
SoftwareSerial ss(8, 9);

void setup()  
{

  Serial.begin(9600);
  SerialArduino.begin(9600);
  while (!Serial);  // For Yun/Leo/Micro/Zero/...
  delay(100);
  Serial.println("\n\nAdafruit finger detect test");

  // set the data rate for the sensor serial port
  finger.begin(57600);
  
  if (finger.verifyPassword()) {
    Serial.println("Found fingerprint sensor!");
  } else {
    Serial.println("Did not find fingerprint sensor :(");
    while (1) { delay(1); }
  }

  finger.getTemplateCount();
  Serial.print("Sensor contains "); Serial.print(finger.templateCount); Serial.println(" templates");
  Serial.println("Waiting for valid finger...");
  ss.begin(9600);
}
binaryFloat hi;
binaryFloat Gps[2];
int counter = 0;
int secondcounter = 0;
void loop()                     // run over and over again
{

  getFingerprintIDez();
  //because of problem making a Software serial function on the Sodaq Explorer, we needed to make use
  //of its only Serial port, so the gps data transit through this Leonardo, making us having to detect
  //and transfer the GPS data to the Sodaq Explorer. If you don't have this problem delete every thing marked

  //Can be deleted if needed ---------------------------
  if(ss.available() >  0){
    byte incomingLocation = ss.read();
    //Serial.println(incomingLocation, HEX);
    hi.binary[counter] = incomingLocation;
    counter++;
    if(counter > 3){
      Serial.println(hi.floatingPoint , 6);

      if(secondcounter == 0){
        Gps[secondcounter] = hi;
        secondcounter++;
      }
      else{
        Gps[secondcounter] = hi;
        secondcounter = 0;
        int nmb = 9;
        SerialArduino.write(nmb);
        Serial.println("Envoye");
        Serial.println(Gps[0].floatingPoint,6);
        Serial.println(Gps[1].floatingPoint,6);
        Serial.println("Fin");
        SerialArduino.write(Gps[0].binary,4);
        SerialArduino.write(Gps[1].binary,4);
      }
      counter =0;
      hi.floatingPoint = 0;
    }
  }
  //Can be deleted if needed ^---------------------------
  delay(50);            //don't ned to run this at full speed.
}

uint8_t getFingerprintID() {
  uint8_t p = finger.getImage();
  switch (p) {
    case FINGERPRINT_OK:
      Serial.println("Image taken");
      break;
    case FINGERPRINT_NOFINGER:
      Serial.println("No finger detected");
      return p;
    case FINGERPRINT_PACKETRECIEVEERR:
      Serial.println("Communication error");
      return p;
    case FINGERPRINT_IMAGEFAIL:
      Serial.println("Imaging error");
      return p;
    default:
      Serial.println("Unknown error");
      return p;
  }

  // OK success!

  p = finger.image2Tz();
  switch (p) {
    case FINGERPRINT_OK:
      Serial.println("Image converted");
      break;
    case FINGERPRINT_IMAGEMESS:
      Serial.println("Image too messy");
      return p;
    case FINGERPRINT_PACKETRECIEVEERR:
      Serial.println("Communication error");
      return p;
    case FINGERPRINT_FEATUREFAIL:
      Serial.println("Could not find fingerprint features");
      return p;
    case FINGERPRINT_INVALIDIMAGE:
      Serial.println("Could not find fingerprint features");
      return p;
    default:
      Serial.println("Unknown error");
      return p;
  }
  
  // OK converted!
  p = finger.fingerFastSearch();
  if (p == FINGERPRINT_OK) {
    Serial.println("Found a print match!");
  } else if (p == FINGERPRINT_PACKETRECIEVEERR) {
    Serial.println("Communication error");
    return p;
  } else if (p == FINGERPRINT_NOTFOUND) {
    Serial.println("Did not find a match");
    return p;
  } else {
    Serial.println("Unknown error");
    return p;
  }   
  
  // found a match!
  Serial.print("Found ID #"); Serial.print(finger.fingerID); 
  Serial.print(" with confidence of "); Serial.println(finger.confidence);
  digitalWrite(9, HIGH);
  delay(100);
  digitalWrite(9, LOW);
  return finger.fingerID;
}

// returns -1 if failed, otherwise returns ID #
int getFingerprintIDez() {
  uint8_t p = finger.getImage();
  if (p != FINGERPRINT_OK)  return -1;

  p = finger.image2Tz();
  if (p != FINGERPRINT_OK)  return -1;

  p = finger.fingerFastSearch();
  if (p != FINGERPRINT_OK)  return -1;
  
  // found a match!
  Serial.print("Found ID #"); Serial.print(finger.fingerID); 
  Serial.print(" with confidence of "); Serial.println(finger.confidence);
  //Send to LoRa Card
  if(finger.confidence > 100){//if the fingerprint detector find a match with a score superior than 100 then send the data to the Sodaq Explorer
    Serial.println("Envoye 1");
    int nmb = 1;
    SerialArduino.write(nmb);
    Serial.println("Envoye fingerprint number");
    SerialArduino.write(finger.fingerID);
  }

  return finger.fingerID; 
}

Credits

ChenNdG

ChenNdG

1 project • 0 followers
Cyril Audinelle

Cyril Audinelle

1 project • 0 followers
Nicolas DAILLY

Nicolas DAILLY

29 projects • 16 followers
Associated Professor at UniLaSalle - Amiens / Head of the Computer Network Department / Teach Computer and Telecommunication Networks
fcaron

fcaron

12 projects • 1 follower
Thanks to OpenMediaVault .

Comments