Projet VanLife — Local First — Créer une API sur RaspberryPi pour gérer localement des données et les synchroniser
Introduction
Lors de la deuxième conférence React Native Connection qui a eu lieu à Paris le 23 avril 2024, Kræn Hansen de MongoDB a pris la parole sur le sujet “Building apps with local-first persistence & real-time sync”. Son sujet s’appuyait sur l’article “Local-first software” de Ink & Switch, que je vous invite à découvrir.
Depuis les débuts de l’aventure OnTheBeach.dev, j’ai a coeur de créer des applications qui puissent être utilisées si possible sans réseau, et qui sont économiques en terme d’échanges avec les serveurs. Pour utiliser un anglicisme proche de l’idée de cet article, ce sont des applications “offline first”. Le “Local-first” va plus loin, car il fait mention non seulement des données présentes sur l’appareil de consommation de celles-ci (Téléphone, tablette, ou autre..), mais également sur le fait de garder une synchronisation “locale” entre plusieurs appareils qui communiquerai avec la même base, en plus d’une synchronisation “distante”.
Si Kræn Hansen a pris la parole sur le sujet, c’est aussi pour mettre en lumière les solutions de MongoDB. J’aimerai ici développer cette approche avec une solution “maison” créée avec un RaspberryPi pour la partie “locale” et un service Firebase pour la partie distante.
Mise en place d’une API sur le Raspberry Pi
La première étape — comme l’indique le titre de cet article — est de mettre en place une API sur le Raspberry Pi.
I. Installation de FastAPI & Uvicorn
Pour se faire nous allons utiliser FastAPI (Framework Python) et Uvicorn (Server Web). Installons donc Python, FastAPI et Uvicorn sur le Raspberry Pi.
sudo apt-get install python3 python3-pip
pip3 install fastapi uvicorn[standard]
Note : Si une erreur survient, utilisez la commande
pip3 install fastapi uvicorn[standard] --break-system-packages
II. Développement de l’API en Python
Une fois installés, je vais créer un fichier api.py, dans lequel je vais tester mes premières.
sudo nano api.py
Voici le code de mon API de test
from fastapi import FastAPI
app = FastAPI()
@app.get("/read")
def read():
return {"success":True, "msg":"Bienvenue"}
Une fois sauvegardée, je n’ai plus qu’à lancer la commande suivante pour démarrer la serveur Uvicorn.
uvicorn api:app --host 0.0.0.0 --port 8000 --reload
Si je spécifie l’option host 0.0.0.0, c’est dans le but de pouvoir appeler mon API depuis n’importe quelle autre machine connectée à mon réseau. Par exemple via Postman sur mon Mac.
Et voilà, j’ai donc mon Raspberry Pi qui est prêt à communiquer.
III. Mise en place d’une base de données avec TinyDB
Nous allons maintenant nous attaquer aux méthodes CRUD, à la sécurité des échanges et à la base de données.
Commençons par la base de données. Je suis sur un Raspberry Pi, je veux quelques choses de léger pour commencer. Je vais donc tester TinyDB.
Commençons par installer TinyDB
pip install tinydb
TinyDB fonctionne avec des fichiers JSON. À la racine de mon projet, je vais créer un dossier DB, dans lequel je vais stocker la base de données.
Je reviens à mon fichier api.py dans lequel je vais ajouter les accès à la base de données (dans la suite du projet, je séparerai la logique de la base dans un fichier propre à la gestion de la base).
from fastapi import FastAPI, HTTPException, status
from tinydb import TinyDB, Query
from pydantic import BaseModel
import uuid
#Gestion des DB
temperature_db = TinyDB('DB/temperatures.json')
#Gestion de l'API
app = FastAPI()
class Position(BaseModel):
lat: float
long: float
class Temperature(BaseModel):
id: str
value: float
unity: str
ts: int
position: Position
@app.post("/temperature", status_code=status.HTTP_201_CREATED)
def add_temperature(temperature: Temperature):
new_entry = temperature.dict()
new_id = uuid.uuid4()
new_entry["id"] = str(new_id)
temperature_db.insert(new_entry)
return {"success": True}
@app.get("/temperatures")
def get_temperature():
all_temperatures = temperature_db.all()
return {"success":True, "data":all_temperatures}
Je commence par initialiser ma base de données
#Gestion des DB
temperature_db = TinyDB('DB/temperatures.json')
Je vais avoir un fichier JSON par type de données. Je vais commencer par gérer des températures relevées, avec pour chaque température, la date à laquelle elle a été relevée, et la géolocalisation.
class Position(BaseModel):
lat: float
long: float
class Temperature(BaseModel):
id: str
value: float
unity: str
ts: int
position: Position
Je définis donc ensuite mon modèle de données pour chaque entrée. Puis je définis une méthode d’écriture, qui ajoute une ID unique à chaque entrée.
@app.post("/temperature", status_code=status.HTTP_201_CREATED)
def add_temperature(temperature: Temperature):
new_entry = temperature.dict()
new_id = uuid.uuid4()
new_entry["id"] = str(new_id)
temperature_db.insert(new_entry)
return {"success": True}
Et enfin une méthode de lecture de l’ensemble des températures présentes dans ma base.
@app.get("/temperatures")
def get_temperature():
all_temperatures = temperature_db.all()
return {"success":True, "data":all_temperatures}
Une fois le serveur connecté, nous pouvons utiliser ces méthodes.
L’un des avantages de FastAPI est la création automatique d’une documentation accessible automatiquement quand votre serveur tourne. vous y accéder grâce à l’adresse http://adresse_ip:port/docs (dans l’exemple que je développe, j’y accès directement via http://192.168.1.34:8000/docs)