Vous utilisez un navigateur obsolète. Il se peut qu'il n'affiche pas correctement ce site ou d'autres. Vous devez le mettre à niveau ou utiliser un navigateur alternatif.
Sans l'aide d'aucun moyen externe, j'ai eu l'approbation d'Edgar, et comme dit sur mon profil, à mes yeux, la réaction d'Edgar, compte pour l'infini. Ainsi, je suis en droit de faire mon topic de DevLog, et combattre la menace anarcho-gaucho qu'est Pingouin à l'aide de ma droite (fascisme) inné.
Beaucoup de projet ont été fait depuis que j'ai commencé la faculté, le premier et le plus gros, le NASpi (que vous pouvez retrouver ici), qui me sert d'unité de NAS et aussi de service VPN (pour me connecter à mon réseau local même en dehors de chez moi). Cependannnnt, c'est vraiment peu puissant :
Le charabia informatique avec des composants que personne ne connait (sauf les plus néophytes), mais en gros l'un a un processeur bien plus puissant, et 4 à 8 fois plus de RAM (et encore, étant donné que la RAM du 5 est de 2 générations supérieures, c'est bien plus que ça). Mais, pour quelques opérations et actions simples ça peut être utile.
Maintenant, quand on doit faire des opérations bien plus compliquées (du transcodage vidéo avec Plex, ou autre), ça demande plus de performances. C'est pour ça que j'ai sauté sur l'occasion quand mon père m'a proposé de récupérer un vieux PC à son boulot, qui était dans un mauvais état niveau configuration. J'ai quand même tenté de le réparer (avec succès), mais le disque dur était selon moi trop endommagé après avoir tourné tout le temps pendant plusieurs paires d'années.
Comme sous-entendu dans ce topic, j'avais une configuration avec :
Processeur
Intel Pentium G4400 (G pour Gold, car il est goldé comme processeur (non)),
2 coeurs 2 threads à 3.30 GHz
3 Mo de "smart cache Intel"
RAM
1x4 Go en DDR4 (c'est vraiment bof, surtout en une seule barrette car aucun dual-channel du coup)
Stockage
500 Go en HDD (qui, comme dit, devait bien être fatigué
J'ai procédé à un ravalement de façade (enfin, pas vraiment de façade, car c'était à l'intérieur...) :
Processeur
i7 6700 (lui il est goldé)
4 coeurs 8 threads à 3.40 GHz, 4.00 GHz en mode turbo
8 Mo de "smart cache Intel"
RAM
2x8 Go en DDR4 (bien mieux, et on peut bénéficier du dual-channel comme ça)
Stockage
256 Go en SSD (SATA), ce qui est bien mieux Et 4 To en HDD, que j'ai ajouté après en achetant un disque dur de vidéo-surveillance sur Leboncoin
Donc avec cette config, j'ai un très bon PC de serveur, qui peut faire du transcodage et bien plus (surtout quand on sait maitriser la distribution Debian en terminal, et Docker notamment, qui permet de créer des "systèmes d'exploitation" dans un espace clos, de sorte à ce qu'il ne puisse pas communiquer en dehors et casser d'autres trucs si jamais il y a un problème).
Prochaine histoire : La création d'un système de backup codé en bash (vous verrez, c'est bien compliqué, mais assez amusant)
C'est pas un peu du plagiat tout ça cher Gno ??? Bon après si le patron a dit oui, c'est que ce n'en est pas car le patron a toujours raison. Et sinon, très beau vlog.
C'est pas un peu du plagiat tout ça cher Gno ??? Bon après si le patron a dit oui, c'est que ce n'en est pas car le patron a toujours raison. Et sinon, très beau vlog.
Ce n'est pas un plagiat parce que : j'ai mis un "V" majuscule et j'ai ajouté un underscore (_) dans le titre. Donc pouèt pouèt camembert c'est moi qu'a raison.
Bon
Aujourd'hui, à l'affiche de cette deuxième édition : un service de backup pour mon téléphone (je vais tâcher de ne pas trop entrer dans les détails).
Au final, pour ce DevLog, je peux tout simplement envoyer le PDF de la documentation que j'ai produite en amont et en même temps que la mise en place de ce projet, c'est vrai que ça serait plus simple et vous auriez eu 10 pages toutes construites et avec une mise en page parfaite, mais le hic c'est qu'il y a mes coordonnées et j'ai la flemme d'anonymiser (alors que ça ne prendrait que 5 minutes à faire).
Donc tout d'abord, pourquoi et comment ? C'est vrai, c'est pas tous les jours qu'on se dit "tiens, je vais faire un système de backup", d'autant plus que des solutions toutes faites existent (Google One, d'autres services Google, et j'en passe énormément), mais le premier problème majoritaire à ces solutions toutes faites c'est que c'est généralement payant. Et même si ça ne l'est pas, l'idée d'avoir mes données stockées sur un cloud d'une grande multinationale et sans aucunes certitudes qu'elles ne fuiteront pas me dérange énormément. Bon, même si dernières ont probablement déjà toutes sortes de données sur moi, et ce n'est pas mes quelques photos qui les intéresseront. Néanmoins, c'est dans une logique de "je vais moins leur en donner", et surtout de bidouille, que j'ai décidé de faire ce projet.
Mais là vous allez me dire "Ô grand Gno, mais pourquoi faire ça ?". Réponse simple : je me suis fait voler mon téléphone. Et à cause de cette erreur, j'ai perdu toutes les photos de mon chat que j'avais prises. Et je n'avais fait aucune backup de ces dernières, et pour la majorité, je ne les ai jamais envoyé au travers d'un quelconque réseau social. Je suis donc un homme en colère. Du moins, j'étais, car à chaque problème il y a une solution, d'autant plus si on sait bidouiller du Linux.
En effet, sur Linux (et aussi Android, qui a un noyau Linux) il y a énormément de choses utiles et pratiques. La première, qui se trouve sur Android, c'est ADB (pour Android Debug Bridge), qui permet de connecter un téléphone à un PC, et de le "contrôler" via ce même PC. C'est un système qui est souvent utilisé quand on fait de la programmation (programmation avec Android Studio, ou avec React), pour notamment voir le résultat sur l'écran du téléphone (et savoir si l'affichage n'est pas fucked up). Ce ADB possède deux modes de connectivité, le premier (le plus courant et le moins dangereux) via USB, et le second (qui peut être tout aussi courant mais surtout plus dangereux) via Wi-Fi, c'est ce dernier qui nous intéressera car c'est contraignant de brancher son téléphone en USB sur le serveur. (il est plus dangereux car impaginons je l'active dans un réseau publique, il sera plus simple de modifier certains paramètres du téléphone, sans qu'on s'en rende compte).
Outils principaux
Vous ai-je dit que Linux était incroyable ? Oui, mais je le redis. Sur Linux, il existe divers "applications" et outils (appelés paquets), notamment un qui se nomme adb, et qui permet d'effectuer une backup très simplement via une commande (et en ayant pensé à connecter son téléphone au PC, via l'adresse IP locale ou simplement via USB) :
Bash:
adb pull <source> <destination>
On peut décider de faire une backup intégrale du téléphone, qui sera enregistrer sous le format d'une image (image qu'on peut appliquer intégralement sur le même modèle de téléphone, pour le "dupliquer"), ou très simplement copier un répertoire précis du téléphone. Nous allons partir sur la seconde solution, qui aura le mérite d'être plus lègère et plus simple, d'autant plus que je ne veux que sauvegarder les photos et autres.
Scripts
Pour ce projet, nous utiliserons le langage Bash, qui est très populaire dans le milieu de l'automatisation sous linux, et qui permet très facilement d'exécuter des commandes qui ont été installées via des paquets. (par ailleurs, la grande majorité des commandes disponibles sous Linux ont été codées en Bash).
Le premier script à faire c'est celui pour trouver le téléphone dans le réseau. En effet, hormis si je configure le téléphone pour avoir une IP statique, l'IP du téléphone sur le réseau local pourrait changer, donc il faut trouver un moyen de récupérer l'IP dynamiquement. Et pour cela, on peut utiliser l'outil nmap (qu'il faut au préalable installer). Et grace à ce dernier, on peut lister et récupérer les informations des appareils connectés au réseau.
Maintenant, on pourrait prendre le nom du téléphone pour tenter de le faire match avec un des noms d'appareil sur le réseau. Idée intéressante et claire, mais un des problème c'est qu'il peut y avoir 2 téléphones de même nom sur le réseau.
Typiquement, dans mon cas, j'ai un Nothing Phone 2(a), avec comme nom A142, donc il suffit qu'il y ait une personne avec le même téléphone que moi (donc avec comme nom A142 aussi) pour qu'il y ait des problèmes de concurrence. La façon la plus simple de régler ce souci, c'est de prendre l'adresse MAC du téléphone, qui reste fixe.
Ainsi, on peut construire un script assez simple (dans la logique, peut-être pas dans la pratique) pour trouver le téléphone :
Bash:
#!/bin/bash
if [ $# -ne 1 ]; then
echo "Erreur: Nombre d'arguments incorrects."
echo "Usage: $0 <device_mac>"
exit 1
fi
# Variables utilitaires
TARGET_PHONE_MAC=$1
NETWORK="192.168.0.0/24"
LOG="/var/log/phone_backup.log"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
# Fonction pour vérifier si l'IP correspond au téléphone recherché
check_phone_mac() {
local ip=$1
echo "[$TIMESTAMP] [INFO] [SCAN_NETWORK] - Verification of $ip..." | tee -a $LOG
PHONE_MAC=$(sudo nmap -sn "$ip" | awk '/MAC Address/ {print $3}')
echo "[$TIMESTAMP] [INFO] [SCAN_NETWORK] - MAC of $ip is $PHONE_MAC" | tee -a $LOG
if [ -n "$PHONE_MAC" ]; then
echo "[$TIMESTAMP] [INFO] [SCAN_NETWORK] - MAC address found : $PHONE_MAC" | tee -a $LOG
if [ "$PHONE_MAC" == "$TARGET_PHONE_MAC" ]; then
echo "[$TIMESTAMP] [INFO] [SCAN_NETWORK] - Device found: $PHONE_MAC ($ip)" | tee -a $LOG
return 0
else
echo "[$TIMESTAMP] [WARNING] [SCAN_NETWORK] - $ip has a different MAC address" | tee -a $LOG
fi
else
echo "[$TIMESTAMP] [ERROR] [SCAN_NETWORK] - Cannot get MAC address" | tee -a $LOG
fi
return 1
}
# Scan du réseau local pour trouver les IP actives
echo "[$TIMESTAMP] [INFO] [SCAN_NETWORK] - Scanning local network..." | tee -a $LOG
IP_LIST=$(nmap -sn $NETWORK | grep "Nmap scan report for" | awk '{print $NF}' | tr -d '()')
if [ -z "$IP_LIST" ]; then
echo "[$TIMESTAMP] [ERROR] [SCAN_NETWORK] - No IP address detected on the network" | tee -a $LOG
exit 1
fi
echo "[$TIMESTAMP] [INFO] [SCAN_NETWORK] - IP addresses detected :" | tee -a $LOG
echo "$IP_LIST" | tee -a $LOG
# Parcourir chaque IP trouvée et vérifier si c'est le bon téléphone
for ip in $IP_LIST; do
check_phone_mac "$ip"
if [ $? -eq 0 ]; then
echo "[$TIMESTAMP] [INFO] [SCAN_NETWORK] - Targetted phone found on IP : $ip" | tee -a $LOG
exit 0
fi
done
echo "[$TIMESTAMP] [ERROR] [SCAN_NETWORK] - Targetted phone ($TARGET_PHONE_MAC) not found" | tee -a $LOG
La seconde commande à coder c'est simplement une récupération de l'IP avec la commande précédente, puis une succession de copie des répertoires du téléphone.
Bash:
#!/bin/bash
# Définition des variables
BACKUP_DIR="$HOME/phone_backup_docker/backups"
LOG="/var/log/phone_backup.log"
TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S")
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Starting backup..." | tee -a $LOG
# Scan du réseau pour détecter l'IP du téléphone
MAC=$(cat ~/phone_backup_docker/phone_mac.conf)
PHONE_IP=$(~/phone_backup_docker/find_device.sh $MAC | sed -n 's/.*Targetted phone found on IP : \([0-9.]\+\).*/\1/p')
if [ -n "$PHONE_IP" ]; then
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Phone detected at $PHONE_IP" | tee -a $LOG
# Connexion ADB
adb connect "$PHONE_IP":5555 > /dev/null 2>&1
sleep 5 # Attendre la connexion
# Vérification de la connexion
adb devices | grep "$PHONE_IP" > /dev/null
if [ $? -eq 0 ]; then
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Connexion success, starting backup..." | tee -a $LOG
# Créer une sauvegarde avec un timestamp unique
BACKUP_NAME="backup_$TIMESTAMP"
mkdir -p "$BACKUP_DIR/$BACKUP_NAME"
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Pulling Download" | tee -a $LOG
adb pull /storage/emulated/0/Download "$BACKUP_DIR/$BACKUP_NAME/Download" | tee -a $LOG 2>&1
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Download pulled" | tee -a $LOG
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Pulling DCIM" | tee -a $LOG
adb pull /storage/emulated/0/DCIM "$BACKUP_DIR/$BACKUP_NAME/DCIM" | tee -a $LOG 2>&1
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - DCIM pulled" | tee -a $LOG
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Pulling Pictures" | tee -a $LOG
adb pull /storage/emulated/0/Pictures "$BACKUP_DIR/$BACKUP_NAME/Pictures" | tee -a $LOG 2>&1
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Pictures pulled" | tee -a $LOG
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Pulling Musics" | tee -a $LOG
adb pull /storage/emulated/0/Musics "$BACKUP_DIR/$BACKUP_NAME/Musics" | tee -a $LOG 2>&1
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Music pulled" | tee -a $LOG
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Pulling Documents" | tee -a $LOG
adb pull /storage/emulated/0/Documents "$BACKUP_DIR/$BACKUP_NAME/Documents" | tee -a $LOG 2>&1
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Documents pulled" | tee -a $LOG
if [ $? -eq 0 ]; then
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Backup success : $BACKUP_NAME" | tee -a $LOG
else
echo "[$TIMESTAMP] [ERROR] [PHONE_BACKUP] - Backup failed" | tee -a $LOG
fi
# Déconnexion propre
adb disconnect "$PHONE_IP":5555
# Supprimer les sauvegardes les plus anciennes, ne garder que les 10 plus récentes
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - Clean old backups..." | tee -a $LOG
ls -1t "$BACKUP_DIR" | tail -n +11 | xargs -I {} rm -rf "$BACKUP_DIR/{}"
else
echo "[$TIMESTAMP] [ERROR] [PHONE_BACKUP] - ADB connexion failed" | tee -a $LOG
fi
else
echo "[$TIMESTAMP] [ERROR] [PHONE_BACKUP] - Phone not found in network" | tee -a $LOG
fi
echo "[$TIMESTAMP] [INFO] [PHONE_BACKUP] - End of backup" | tee -a $LOG
Pour les deux codes, on a géré les différentes erreurs possibles (téléphone pas trouvé, erreurs avec la commande, etc), et nous avons aussi fait des logs appropriés pour pouvoir mieux diagnostiquer les erreurs.
Automatisation et conteneur
Pour faire tourner ce code, nous allons utiliser Docker, qui est une sorte "d'émulateur de système d'exploitation". C'est dit très grossièrement, mais ça permet de dédier un conteneur (une boite fermée) à un service qui tournera non-stop. C'est une façon sécurisée de faire tourner un script, car si jamais il y a un problème (que le script devienne fada et qu'il casse le système d'exploitation en entier), il n'aura détruit que son conteneur et il suffira simplement de le recréer via une commande.
Nous allons aussi utiliser crontab, un outil Linux qui permet de planifier l'exécution des tâches.
Exemple :
Vient ensuite Docker, et sans entrer dans les détails pour configurer le conteneur (car je me suis un peu arraché les cheveux), on va préciser dans un fichier "Dockerfile" (qui sert de fichier de configuration) :
L'image à utiliser (dans notre cas, Debian, qui est assez lourd comparé à Alpine, mais ça permet d'avoir une meilleure compatibilité)
Une commande à effectuer, pour notamment mettre le conteneur à jour, et installer les outils qu'on aura besoin, comme :
rsync (outil pour copier efficacement)
iputils-ping (dépendances pour les outils liés aux scans d'IP)
nmap (l'outil pour scanner le réseau)
bash
adb
Une commande pour copier les scripts du PC dans le conteneur (car sinon, on y a pas accès étant donné que le conteneur est clos)
Une commande pour donner une permission d'exécution aux scripts qui ont été copiés
On précise aussi le répertoire de travail (le chemin où les commandes seront exécutés, c'est très important)
Puis, le script à exécuter (script = crontab (qui exécutera le script avec son intervalle))
J'ai été assez évasif sur certaines choses, car parfois ce n'était pas pertinent, ou trop compliqué. J'ai aussi omis énormément de problèmes que j'ai eu, car finalement pour l'un des plus gros que j'ai actuellement, je n'ai toujours pas réussi à le régler. Mais pour avoir essayé, le script fonctionne et tout tourne comme il faut (plus jamais je ne perdrais les photos de mon chat.) !
Un DevLog plus court, peut-être moins informatique mais je vais relayer ce que j'ai pu faire aujourd'hui.
1. Le PC bricolé
Plus haut dans ce topic, j'ai dit que j'avais acheté un PC en entier car ça coutait tout aussi cher qu'acheter le processeur séparément. J'ai un peu attendu avant de le booter etc, car il me fallait un SSD pour le faire fonctionner, SSD que j'ai récupéré en défonçant un SSD externe pour récupérer la petite carte à l'intérieur.
J'attendais de recevoir la commande Aliexpress que j'avais faite, car il y avait justement un adaptateur en SATA pour cette petite carte, afin que je puisse le connecter au PC, et j'ai reçu tout ça aujourd'hui. En soi, tout fonctionne, c'est ce qu'il me fallait. La prochaine étape c'est l'installation de Debian (j'ai déjà une clé USB bootable avec un Debian dessus, donc c'est 15 minutes d'économisé). J'allume le PC, après un peu de galère, je rentre dans un des nombreux menus UEFI, et j'ai une page ressemblant à un pré-BIOS. Je choisis l'option pour aller dans le BIOS et là : (faites pas attention au bureau.)
Donc en gros : Le vendeur m'a vendu un PC avec un mot de passe qui m'empêche d'accéder au BIOS (donc je ne peux même pas désactiver le secure boot).
La solution la plus simple c'est de contacter le vendeur (ce que j'ai fait), en espérant avoir une réponse positive.
Maintenant, si jamais je n'ai pas de réponse, la dernière chose à faire c'est de l'éteindre, le débrancher, vider les condensateurs et retirer la pile CMOS pendant 30 minutes pour espérer reset la mémoire de la carte mère. Maintenant j'ai pas beaucoup d'espoir pour cette solution car ça a été "patch" pour les PC professionnels (et c'est justement ce type de PC que j'ai).
2. NFC et compagnie
Dans le lot Aliexpress que j'ai commandé, j'ai aussi reçu mes tags NFC. C'est une connerie qui coute 2€ (les 20), mais j'ai toujours voulu en avoir pour rigoler un peu. Pour ceux qui ne savent pas ce que c'est, c'est des puces sans fils (généralement sous la forme d'une carte), qui permettent d'accéder à une page internet ou autre. C'est souvent utilisé (à l'heure d'aujourd'hui) dans les restaurants pour avoir les cartes avec le téléphone.
J'ai premièrement fait un tag qui m'ouvre Waze sur mon téléphone, et vous allez me demander "mais pourquoi Gno ?". En fait je l'ai mis sur le support de téléphone dans ma voiture, comme ça, quand je le mets dessus, ça m'ouvre Waze et j'ai plus qu'à entrer la destination que je veux. (bon l'idée est géniale mais dans la pratique c'est pas parfait).
Pour la deuxième utilisation, je me suis muni de mon meilleur logiciel de modélisation, et j'ai modélisé un petit truc rapidement. Je l'ai imprimé, j'ai tout monté, peint et voici le résultat : (pour ceux qui ne reconnaitrait pas, c'est un badge qui ouvre une page vers mon GitLab.)
Au prochain épisode : probablement la réponse du vendeur (si elle est négative je pète mon crâne, mais au moins ça vous sera peut-être plus divertissant de me voir en train de galérer avec un PC).
Au programme : Aucune réponse de la part du vendeur.
Mais pour vous consoler : de la programmation !
_______________________
J'ai décidément énormément de problèmes dans ma vie, et l'un d'entre eux c'est d'avoir énormément de vinyles. J'aime la musique, c'est vrai, et c'est plaisant d'avoir des disques à mettre sur une platine vinyle, mais quand on commence à en avoir trop, c'est compliqué de choisir efficacement, sans passer 5 minutes à chercher. Et comme vous avez vu en haut, j'ai pas mal bidouillé avec les tags NFC, et en fait je me suis rendu compte que c'était bien plus intéressant et puissant quand on avait un serveur pour héberger tout sorte de site.
La solution à mon problème est simple : coder un site qui me donne aléatoirement un vinyle à écouter. Quel rapport avec le tag NFC ? Et bien je n'ai qu'à le coller sur la platine vinyle (dans un endroit pas trop dérangeant) et faire en sorte qu'il redirige mon téléphone sur la page de choix aléatoire lorsque je le scanne. Je concède, je remets pas mal ma chance sur l'aléatoire, mais dans la street on m'appelle Lucky Guy (c'est complètement faux, et Pingouin peut témoigner).
En tant qu'homme minutieux, j'ai une Google Sheets où je stocke tous les vinyles que j'ai (c'est un peu comme mon inventaire), donc j'ai déjà la base de données qu'il me faut, maintenant il manque plus qu'à savoir comment la récupérer (et comme de par hasard, la librairie Pandas peut le faire, et donc oui, je vais faire tout ça en Python).
Comme dans de nombreux domaines (comme l'IA par exemple), quand on récupère des données, l'étape la plus cruciale est de nettoyer les données. Parfois c'est simple, parfois c'est compliqué, et dans mon cas c'est plutôt simple.
Ci-dessous, vous avez un screenshot de ma Google Sheets : (n'en profitez pas pour espionner mes goûts musicaux, bande de voyeurs.)
On remarque que le filtrage est assez simple, ce qu'on veut garder c'est :
Les valeurs comprises entre les lignes 4 et +∞
Les valeurs comprises entre les colonnes 1 (colonne A) et 7 (colonne G)
Et le vinyle doit être en 33T (grand format)
Pour la partie code, on va considérer qu'on a un fichier avec nos accès à la Google Sheets (je ne vais évidemment pas vous donner mes accès, simplement la façon dont c'est présenté dans ledit fichier) :
Python:
SHEET_NAME = 'nom de la sheets'
SHEET_ID = 'partie de l URL entre "/d/" et "/edit?"'
Python:
import sheets_token
sheet_name = sheets_token.SHEET_NAME
sheet_id = sheets_token.SHEET_ID
def get_sheets(
sheet_name: str,
sheet_id: str,
keepcols: list[int]=[0,1,2,3,4,5,6]
) -> list[dict]:
"""
Méthode pour récupérer la Google Sheets via un lien
Paramètres :
- `sheet_name` : nom de la Google Sheets
- `sheet_id` : id (entre /d/ et /edit?)
- `keepcols` : colonnes à garder
Retourne une liste de dictionnaires (`list[dict]`) sous forme d'un `dataframe` (**Pandas**)
"""
# Lien spécifique pour que pandas récupère la sheet
url = f"https://docs.google.com/spreadsheets/d/{sheet_id}/gviz/tq?tqx=out:csv&sheet={sheet_name}"
# Lecture du Google Sheets
df = pd.read_csv(url, skiprows=[1], usecols=keepcols)
# Nettoyage des noms de colonnes : suppression des espaces et des accents
df.columns = [unidecode(col).strip().replace(' ', '_') for col in df.columns]
data = df.to_dict(orient="records") # data = tableau avec tous les vinyles = vinyls
res = []
for d in data:
if d.get("Format","") == "33T":
res.append(d)
return res
Nous avons une autre fonction, qui permet de récupérer l'image de l'album. Pas la peine de s'y attarder, c'est du simple requêtage d'API.
Python:
def get_album_cover(artist, album):
""" Récupère la jaquette d'un album via MusicBrainz + Cover Art Archive """
mb_url = f"https://musicbrainz.org/ws/2/release-group/?query=album:{album} AND artist:{artist}&fmt=json"
headers = {"User-Agent": "VinylApp/1.0"}
response = requests.get(mb_url, headers=headers)
if response.status_code == 200:
data = response.json()
if "release-groups" in data and data["release-groups"]:
mbid = data["release-groups"][0]["id"]
return f"https://coverartarchive.org/release-group/{mbid}/front"
return "static/default.jpg"
Partie peut-être plus intéressante : le back de l'application web
Pour cette partie, j'ai décidé de faire ça en Flask, qui permet de coder une application web en Python. Il n'y a pas que Flask qui permet cela, il y a aussi FastAPI, et encore bien d'autre, mais j'ai décidé d'utiliser Flask.
Avec Flask, pour définir une route (un chemin, un url) vers une certaine partie de l'application, nous avons juste à mettre une ligne de code avant la définition de la fonction :
Python:
@app.route("/is_that_cool")
def test():
return jsonify("some random shit yk") # jsonify est utilisé pour renvoyer une "page json" à la place de l'URL, vous verrez l'utilité après
Ainsi, dans notre cas, on souhaite récupérer un vinyle aléatoirement lorsque nous accédons à une certaine URL, donc nous avons juste à faire :
Ici, on fait appel à une méthode spécifique de la librairie Flask. On retourne une page dite "template" (donc simplement à comprendre qu'on renvoie une page HTML custom).
L'antépénultième étape (l'avant avant dernière étape) c'est de faire un beau fichier HTML, un beau CSS (la blague, j'utilise juste TailwindCSS avec DaisyUI, j'ai pas le temps pour le CSS) et un JavaScript. Je vous épargne ça, car c'est pas très intéressant (le front-end c'est pas intéressant, mais c'est plus visuel que le back-end).
L'avant dernière étape, c'est d'exporter ça dans un conteneur Docker, pour que ça puisse tourner 24/7.
PUIS, la dernière étape : on configure le tag NFC, puis on le colle sur la platine vinyle
Et quand je scanne le tag, voici ce que j'ai :
Maintenant, les problèmes n'arrivent jamais seuls
Le premier c'était qu'il y avait trop d'appel pour récupérer la Google Sheets, et en soi pourquoi pas, mais c'est pas intéressant de la re-récupérer avec chaque chargement de la page, car la discographie ne changera pas tout le temps (je ne suis pas riche au point d'ajouter un vinyle tous les jours dedans). Donc ce que je fais, c'est que je stocke la variable contenant toute la discographie dans le cache du navigateur (et Flask permet de faire ça très simplement !)
Le second problème, toujours en rapport avec la latence avec les appels d'API. A chaque fois que je charge le lien, je fais un appel d'API (en vérité, il y en a 2) pour récupérer l'image de l'album. Et ça ne peut pas continuer comme ça, car déjà c'est quelque peu des appels inutiles, et il y a moyen de monter sa propre base de données localement, au fur et a mesure.
Pour faire simple :
Je veux une image
Je regarde déjà localement si je ne l'ai pas
Je l'ai -> je renvoie l'URL que j'ai stocké
Je ne l'ai pas -> je stocke l'URL que j'ai récupéré avec l'API dans un fichier
Ce qui donne comme code :
Python:
def get_local_album_cover(
artist: str,
album: str,
date: str
)->str:
"""
Renvoie l'url vers l'image de l'album
Soit :
- On trouve l'url de l'image dans le dossier static
- On récupère l'url de l'image avec l'API, tout en créant le fichier
Paramètres :
- `artist`: nom de l'artiste
- `album`: nom/titre de l'album
- `date`: date de sortie de l'album
Retourne une chaîne de caractères correspondant avec l'image de l'album
"""
cover_path = "static/covers"
if not os.path.exists(cover_path):
os.makedirs(cover_path)
cover_name = f'{artist}_{album}_{date}'
# Cas où le fichier existe localement
if os.path.exists(f'{cover_path}/{cover_name}'):
url = ""
with open(f'{cover_path}/{cover_name}', 'r') as input:
url = input.read()
return url
# Cas où le fichier n'existe pas localement
else:
url = get_album_cover(artist, album)
with open(f'{cover_path}/{cover_name}', 'w') as output:
output.write(url)
return url
_______________________
Et voila, c'est fini !
Au prochain épisode : on espère une réponse de la part du vendeur...
Avec un peu de retard, pour le backup de ton téléphone, un logiciel comme PhotoPrism sur ton VPS aurait pas fait l'affaire (pour backup les photos en tout cas) ?
Je m'y connais pas plus que ça mais ça me semble plus simple que de backup l'entièreté du tel avec adb (et tu peux voir tes images depuis d'autres appareils en bonus).
Avec un peu de retard, pour le backup de ton téléphone, un logiciel comme PhotoPrism sur ton VPS aurait pas fait l'affaire (pour backup les photos en tout cas) ?
Je m'y connais pas plus que ça mais ça me semble plus simple que de backup l'entièreté du tel avec adb (et tu peux voir tes images depuis d'autres appareils en bonus).
Alors je connaissais pas-
Et pour mon truc de backup, je fais pas une backup sur l'entièreté du téléphone justement
Post scriptum: je viens de regarder photoprism, et c'est assez stylé, donc je vais peut-être un peu plus regarder tout ça et peut-être tenter un Docker (ça ne me coûte rien d'essayer, si ce n'est du temps)
Je vois je vois, j'ai juste survolé les derniers messages donc j'ai loupé les détails x)
Je pense m'acheter moi aussi (plutôt racheter vu que le mien est en France) un Raspberry un de ces 4, j'ai bien envie d'un petit serveur avec backup de mes photos, home assistant...
Je vois je vois, j'ai juste survolé les derniers messages donc j'ai loupé les détails x)
Je pense m'acheter moi aussi (plutôt racheter vu que le mien est en France) un Raspberry un de ces 4, j'ai bien envie d'un petit serveur avec backup de mes photos, home assistant...
En vrai, essaie de voir si c'est pas mieux un vieux PC fixe plutôt qu'un Raspberry (rapport consommation/performance, etc). Après au niveau de l'occasion ça doit probablement être abordable un Raspberry Pi, mais je sais que neuf (et surtout les derniers) c'est un des grands problèmes, genre le 5 qui est à 94€ (8 Go de RAM, je suis pas fou hein, après y'a une version avec 16 si tu veux...) (source: https://www.kubii.com/fr/cartes-nan...2-raspberry-pi-5-3272496315938.html#/ram-8_gb)
Toujours sur le même site, le 4B est à 87€, donc à voir (après tout dépend de ce que tu as besoin, de l'encombrement, consommation, etc)
En moyenne les prix sont les mêmes ici pour les Raspberry, mais pour le rapport prix/taille/consommation/performance, je suis pas sûr qu'il y en ait beaucoup qui puissent le battre. Après si je partais là dessus ce serait avec 8gb et un SSD NVMe (vu que c'est supporté par le 5).
Ouais, parce qu'après, prendre du 16Gb sur un Raspi (même le 5), c'est pas le mieux (car il faut que le processeur ne bride pas la RAM) et c'est pas ce qui va le rendre plus puissant. Tandis que mettre un petit NVMe, ça peut permettre d'avoir une meilleure bande passante
En soit j'ai pas prévu de faire quoi que ce soit de graphique (UI, AI...), donc ça devrait suffire. Et un SSD c'est réutilisable donc pas de perte même si je change.
J'avais le 3b+ 1gb donc n'importe quoi sera une bonne upgrade.
Petit DevLog rapide, plus en mode état des lieux :
Toujours pas de réponse du vendeur, mais il a vu.
J'ai voulu essayer pas mal de trucs aujourd'hui, notamment voir pour essayer PhotoPrism (car c'est très très stylé) et réinstaller un serveur VPN tout beau (car celui sur le NASpi est dead)
J'ai bien lu la doc de PhotoPrism et en vrai c'est pas tant compliqué à configurer
Mais je n'ai rien pu faire de plus
J'ai galéré toute la journée à réinstaller le VPN sur le gros PC, alors que l'erreur était conne :
Le port sur le routeur était encore configuré pour le NASpi et pas l'autre PC... (j'ai corrigé ça à l'instant)
MAISSSS : ça fonctionne, j'ai accès à tous sur mon réseau local, et c'est plutôt amusant de pouvoir accéder à OMV, etc hors du réseau
Toujours quelque chose de rapide, car j'ai pas grand chose à dire.
Comme dit dans le DevLog #3.2.4b, le vendeur a vu mon message, mais il ne m'a pas répondu. J'en conclus que c'est un vile malandrin, mais je m'en doutais, car pour répondre un "Bonjour non" à une de mes questions, il faut vraiment être un salaupiot.
J'ai donc retiré la pile CMOS pendant près d'une heure, et contre toute attente, en le rallumant et en tentant d'aller sur le BIOS : Il y toujours le mot de passe d'activé.
A partir de là, je n'ai pas trop su quoi faire. Mais en analysant la configuration, j'ai vu un jumper (mais qu'est-ce qu'un jumper ? c'est un pin qui vient "court-circuiter" deux pins ensemble, c'est une sorte d'interrupteur en gros). En soi, je m'en fiche un peu (mais généralement c'est pas anodin un jumper dans un PC), cependant, il y avait une curieuse indication à côté : (oui y'a un peu de poussière.)
PSWD mais quelle est cette incantation ?? Ca veut juste dire "PASSWORD". Donc je l'ai retiré, j'ai rallumé le PC en ayant branché la clé USB bootable, et ta-daa~
Sur ce, à une prochaine, je vais installer Sunshine du coup.
Qu'est-ce que c'est poussiéreux ici. Je vais passer un coup de balai puis on pourra passer au
DevLog #4
Et au programme de l'administration système et du rétrogaming on va faire de l'IA, donc sortez vos crayons, il y aura un peu de mathématiques.
Introduction
Contexte
J'ai récemment vu des vidéos de quelqu'un sur Instagram faire ce petit projet de réseau de neurones qui reconnaît les chiffres écrits, et je me suis dit que ça pouvait être une bonne idée. Bien que ce soit un projet très simple, il reste très intéressant pour comprendre comment fonctionne un réseau de neurones, et il peut être assez utile dans certains cas car ça reste quelque chose qui est au coeur de nombreuses choses.
Objectif du projet
L'objectif est assez simple : implémenter un réseau de neurones multicouches (MLP) from scratch avec NumPy et sans Tensorflow (sauf pour le dataset d'entraînement, mais nous n'utiliserons pas les fonctions pour nous simplifier la tâche).
Donc les contraintes sont claires : pas de TensorFlow, PyTorch, ni Scikit-learn pour le modèle.
Mise en place
Présentation du dataset
Avant de commencer, nous allons voir à quoi ressemble le dataset que nous allons utiliser pour l'entraînement. C'est une étape primordiale pour ce type de projet, étant donné que la données est au coeur même de l'IA, donc nous devons savoir avec quoi nous travaillons.
Le dataset NMIST de Tensorflow est composé de :
70 000 images de chiffres manuscrits (28x28 pixels)
60 000 pour l'entraînement + 10 000 pour le test
Le dataset est composé d'images, chacune avec une étiquette (label) :
Images : niveaux de gris (valeurs 0–255)
Étiquettes : entiers de 0 à 9
Architecture du réseau
Type de modèle : Perceptron multicouche (MLP)
Couche d'entrée : 784 neurones
Couches cachées : nombre de neurones variable (ex: 128)
Couche de sortie : 10 neurones (1 par chiffre)
Fonctions d'activation
ReLU pour les couches cachées
Rectified Linear Unit
fonction d'activation utilisée dans les réseaux de neurones pour ajouter de la non-linéarité
Défini par la formule :
L'utilisation de ReLU permet un apprentissage plus rapide (pas d'exponentielle, juste une comparaison) et stable
Problème de Dead Neurons : Si un neurone reçoit toujours des valeurs négatives, sa sortie est toujours 0 et il n'apprend plus
Softmax pour la sortie
Interprétation probabiliste, transformation des vecteurs scores en distribution de probabilités
Permet d'utiliser les résultats plus facilement
Défini par la formule :
Pour l'initialisation des poids (le "premier tour"), nous allons simplement la mettre aléatoirement, elle se corrigera ensuite toute seule.
Entraînement du réseau
Pour l'entraîner, nous allons utiliser la méthode du cross-entropy loss, ce qui veut dire qu'on va analyser les réponses des noeuds, puis pénaliser les mauvaises prédictions en ajustant les poids des noeuds.
Pour l'image du réseau de neurones, imaginez 10 personnes qui ont une tâche spécifique : trouver un nombre (la personne 1 cherchera si c'est un 1, la personne 2 si c'est un 2, jusqu'à la personne 10 qui cherchera si c'est un 0). Il y a une autre personne en plus dans ce réseau de neurones, c'est un peu comme le manager, il va regarder les résultats et gronder ceux qui vont se rater et pas ceux qui ont réussi. Le manager ne va pas donner d'indice, c'est le réseau de neurones qui va y aller petit à petit, en tatonnant et en cherchant les correspondances entre les différents résultats (les probabilités sortantes).
Ce qui va permettre de "propager l'erreur" et de modifier les poids des noeuds, c'est le backdrop qui va (sans entrer dans les détails) calculer les dérivées pour chaque couche, et donc propager l'erreur et modifier les poids des noeuds en conséquences, un peu comme une grande machine avec plein de molettes, où on viendrait modifier petit à petit les paramètres en tournant ces molettes pour avoir le résultat qu'on veut.
Pour l'entraînement de notre réseau nous avons différents paramètres sur lesquels nous pouvons jouer :
epoch : c'est le nombre d'itérations où notre réseau va s'entrainer
alpha: c'est le taux d'apprentissage, la taille des pas que le modèle va faire pour ajuster ses poids
Si on met un alpha très petit, les pas seront très petits, et ça sera plus stable, mais l'entrainement sera très long
Si on met un alpha suffisamment grand, les pas seront très grand, donc l'apprentissage très rapide
Maintenant que nous avons un modèle fonctionnel, on va sortir des données et faire des graphes pour observer les différences quand on change les valeurs du alpha.
Nous remarquons qu'en ayant doublé la valeur de l'alpha par 2 nous avons pas doublé les résultats. Cependant, le modèle s'est affiné bien plus vite, voire plus que quand l'alpha était à 0.1.
Pour mieux se rendre compte des différences, nous allons superposer les différentes courbes :
C'est subtil, mais nous pouvons observer que la tendance fait que si nous augmentons encore l'alpha, ça risque de stagner à un moment, et il semblerait que 0.6 soit le meilleur compromis.
De plus, le risque d'avoir un alpha trop élevé c'est de faire tellement des trop grands pas au point qu'on passe le bon résultat sans même le savoir, et donc d'avoir un modèle trop lent.
Un des points d'amélioration, qui est d'ailleurs la norme dans l'apprentissage automatique aujourd'hui, c'est de commencer avec une valeur d'alpha grande au début pour dégrossir le travail très rapidement, puis de passer à une valeur plus petite pour affiner le modèle.
Mise en application
A l'heure où j'écris ça (21/03/2025, 16h33), je n'ai pas encore codé la partie pour écrire un chiffre dans une fenêtre pour le détecter, mais ça va se faire assez rapidement avec une boucle while et TKinter.
Re (21/03/2025, 19h47), j'ai fait le pannel Tkinter, avec quelque chose de basique, et voici le résultat :
Impressionnant n'est-ce pas ? Et bien pas tant que ça, j'ai menti, car il marche de manière approximative. Plusieurs raisons à cela (sur ou sous-entrainement, ce que j'écris n'est pas adapté par rapport au dataset MNIST, trop de "déformations humaines", etc.).
Malheureusement, je ne peux rien faire de plus que de l'entraîner bien plus, et pour cela, j'ai paraléllisé tout le programme. Nous n'utilisons plus NumPy, mais JAX (notamment le module jax.numpy) qui permet de paralléliser tous les calculs de manière assez simplifié. Comme ça, je ne fais plus du monoprocesseur mais du multiprocesseur (avec 8 coeurs), ce qui permet d'être plus efficace.
En plus de ça, je fais du batching (je découpe les charges de travail) pour avoir plus de parties mais moins grosses, ce qui évite de surcharger la RAM (et d'aller au delà des 8Go que j'ai).
Bien sûr, nous nous éloignons des réseaux de neurones, mais qui dit IA dit forcément parallélisation, donc j'aurai tôt ou tard dû passer par là.
Conclusion et suite
J'ai pas grand chose à dire, si ce n'est que j'ai pu apprendre pas mal de chose et surtout des subtilité avec le parallélisme. Le modèle est actuellement en train d'apprendre, donc je vais probablement faire une suite d'ici peu (ce soir avec un peu de chance).
Donc je vais skip les galères au niveau de l'écran chinois non officiel, où j'ai du me démerder pour trouver les bonnes adresses mémoires que l'écran prenait.
Mais après un prototypage :
Maintenant je sais que ça fonctionne (malgré les problèmes que j'avais, qui étaient dus au fait que j'avais la flemme de souder les pins sur le Wemos D1), on peut passer aux vraies soudures.
Malheureusement, je n'ai pas de photo avant montage, mais sachez que j'en ai eu marre de l'odeur de l'étain et des soudures qui lâchaient (malgré le fait que je les faisais plutôt bien pour une fois). Par contre j'ai une photo de la pire des étapes l'emboitement de tout ça. Car entre le potentiomètre, les fils, et la puce, c'était une véritable horreur de tout mettre (j'ai d'ailleurs cassé une soudure, et heureusement que j'ai vu ça avant de tout mettre).
(faites pas gaffe à l'appart en désordre)
J'ai dû remodéliser la façade avant, car ça n'allait pas (maudits écrans chinois à 1€), et après ça j'ai pu tout finaliser : (faites pas gaffe au bureau)
BIEN, VOILA DE FAIT
Second truc, j'ai reçu un vinyle, et quand j'ai voulu l'écouter j'ai été étonné de ne pas reconnaitre la voix de mes chanteuses préférées, mais aussi un son très inquiétant quand je mettais la platine vinyle à "45 tours par minute". Un bruit de "clac" régulier.
J'ai démonté la platine, c'était assez rapide, et en changeant le mode en regardant le mécanisme, j'ai vite compris.
Le pignon où la courroie passe, c'est ça qui fait le changement entre la vitesse 33 et la vitesse 45. Quand on appuie sur le bouton de la platine, ça vient (avec des tringles) faire monter la courroie, la faisant passer par dessus les ailettes du pignon (et donc elle remonte en haut et passe sur un axe plus gros). Sauf que ça passait pas bien en haut, donc y'avait un bruit relatif à l'ailette qui tentait de la pousser en haut, mais la courroie qui ne voulait pas. Donc je l'ai simplement forcé à l'arrêt, et maintenant, même si je change la vitesse, tout se passe comme prévu.
Et le vinyle à un meilleur son, je l'aime beaucoup (surtout c'est un maxi-45, et ça c'est stylé)