Projet VanLife — Local First — Créer une API sur RaspberryPi pour gérer localement des données et les synchroniser

Axel de Sainte Marie
4 min readSep 23, 2024

--

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.

Schéma très simplifié du résultat attendu

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.

Premier appel test de mon API

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.

Ajout d’un relevé de température
Récupération de la liste des températures

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)

--

--

Axel de Sainte Marie

Show Runner de projets mobiles. Développeur React Native et passionné par les challenges du monde mobile.