Merge branch 'master' of gitlab.com:superboum/quentin.dufour.io

This commit is contained in:
Quentin 2019-09-04 18:48:08 +02:00
commit 8253e7143d
23 changed files with 353 additions and 7 deletions

View file

@ -11,14 +11,14 @@ markdown: redcarpet
highlighter: rouge highlighter: rouge
paginate: 20 paginate: 20
domain_name: 'http://quentin.dufour.io' domain_name: 'https://quentin.dufour.io'
# Details for the RSS feed generator # Details for the RSS feed generator
url: 'http://quentin.dufour.io' url: 'https://quentin.dufour.io'
author: 'Quentin Dufour' author: 'Quentin Dufour'
authorTwitter: 'superboum' authorTwitter: 'superboum'
permalink: /blog/:year-:month-:day/:title permalink: /blog/:year-:month-:day/:title/
defaults: defaults:
- -

View file

@ -1,12 +1,15 @@
<nav class="main-nav"> <nav class="main-nav">
{% if page.url != "/index.html" and page.url != "/"%} {% if page.url != "/index.html" and page.url != "/"%}
<a href='{{ site.url }}/{{ page.about }}'> <span class="arrow"></span> Blog </a> <a href='/'> <span class="arrow"></span> Blog </a>
{% endif %}
{% if page.url != "/cours/" %}
<a href='/cours'>Cours </a>
{% endif %} {% endif %}
{% if page.url != "/about/" %} {% if page.url != "/about/" %}
{% if site.aboutPage %} {% if site.aboutPage %}
<a href='{{ site.url }}/about'>À propos </a> <a href='/about'>À propos </a>
{% endif %} {% endif %}
{% endif %} {% endif %}
<a class="cta" href="{{ site.url }}/feed.xml">Flux RSS</a> <a class="cta" href="/feed.xml">Flux RSS</a>
</nav> </nav>

View file

@ -0,0 +1,48 @@
---
layout: post
slug: manette-xbox-reparation
status: published
sitemap: true
title: Réparation d'une manette Xbox One
description: La panne la plus bête du monde
categories:
- bricolage
tags:
---
Depuis maintenant plusieurs mois, j'ai perdu la gachette gauche de ma manette Xbox One, ma seule manette de jeu.
Ce qui est plutôt embêtant, car elle me rend de fiers services quand je joue à Trackmania et plus récemment à Firewatch.
Et pas de chance, bien que la peur n'évite pas le danger, le frein sur la gachette gauche évite les murs sur Trackmania.
[![Panneau Trackmania "La peur n'évite pas le danger"](/assets/images/posts/xbox-controller-tm.jpg)](/assets/images/posts/xbox-controller-tm.jpg)
Pas mieux du côté de Firewatch, car il s'agit du talkie walkie, un élément central du jeu. Sans grand espoir, je me suis donc lancé dans la réparation de cette manette, qui n'en est pas à sa première défaillance. En effet, j'ai dû jeter le cable micro-usb à usb fourni avec car il ne fonctionnait plus.
## Dissection de la manette
iFixit, comme toujours, propose [un guide](https://fr.ifixit.com/Vue+%C3%89clat%C3%A9e/Xbox+One+Wireless+Controller+Teardown/72986) très pratique pour démonter la manette, que j'ai suivi scrupuleusement.
En démontant la manette, je me suis rendu compte qu'il manquait une vis à un endroit. Simple oubli ou début de réponse ?
[![Vis manquante](/assets/images/posts/xbox-controller-td1.jpg)](/assets/images/posts/xbox-controller-td1.jpg)
Une fois le démontage fini, je me retrouve avec les différents circuits de ma manette.
[![Circuit électronique manette xbox](/assets/images/posts/xbox-controller-td2.jpg)](/assets/images/posts/xbox-controller-td2.jpg)
En regardant un peu mieux, je me rends compte qu'une vis est aimantée sur la gachette.
En effet, le bout de la gachette possède un aimant relativement puissant.
Même en agitant la manette dans tous les sens, la vis ne risquait pas de se désaimanter.
Vis qui provient probablement de notre emplacement vide précédent...
[![La vis perdue est aimantée sur une gachette !](/assets/images/posts/xbox-controller-td3.jpg)](/assets/images/posts/xbox-controller-td3.jpg)
Une fois enlevée, un rapide test permet de vérifier que ma gachette gauche fonctionne de nouveau
## Conclusion
En remontant les vis, je me suis rendu compte qu'une des vis tournait plus ou moins dans le vide.
Je pense que le taraudage d'une pièce doit être cassé. La vis avait l'air de bien tenir pour l'instant, je l'ai donc laissée.
Mais si elle venait à se promener de nouveau, un peu de colle pourrait résoudre le problème. Ou simplement l'enlever...
Bref, une réparation plus simple que prévue, c'est suffisament rare pour être signalé ! Essayez de réparer vos objets,
parfois ça vaut le coup !

View file

@ -0,0 +1,48 @@
---
layout: post
slug: alfawise-u30-et-fedora-linux
status: published
sitemap: true
title: Utiliser une Alfawise U30 depuis Fedora
description: Pour des impressions 3D libres
categories:
- bricolage
tags:
---
L'Alfawise U30 n'a été intégrée que récemment dans Cura et n'est disponible que depuis le dépôt.
Qui plus est, la dernière version majeur de Cura, la version 4, est encore en beta et n'est pas non plus disponible dans Fedora stable.
Nous allons donc installer Cura 4 beta depuis Fedora Rawhide (unstable) puis télécharger les fichiers de définition de l'Alfawise U30 depuis le dépôt officiel.
Une fois le code actuel du dépot publié dans une version stable, ce guide sera inutile.
## Installer Cura 4
Nous allons tout d'abord commencer par installer un paquet qui permet d'activer les dépôts instables (mais seulement sur demande) :
```bash
sudo dnf install fedora-repos-rawhide
```
Ensuite installons Cura depuis les dépôts instables.
```bash
sudo dnf install --nogpg --enablerepo=rawhide cura
```
**Il semble y avoir un bug avec les clés PGP qui ne sont pas correctement installés pour le dépôt instable, j'ai donc désactivé la vérification juste pour cette commande.
Cela reste une très mauvaise idée et corriger durablement ce bug en important les clés PGP serait une meilleur solution.**
## Installer les définitions de l'Alfawise U30
Les configurations pour les imprimantes 3D sont contenues dans des fichiers JSON.
Nous allons donc les télécharger au bon endroit, tout simplement :
```bash
cd /usr/share/cura/resources/definitions/
sudo wget https://raw.githubusercontent.com/Ultimaker/Cura/137619567a1d68139444f0fea76a022d63d86a0b/resources/definitions/alfawise_u30.def.json
cd /usr/share/cura/resources/extruders
sudo wget https://raw.githubusercontent.com/Ultimaker/Cura/057c30f86e86635721e8a269d864e1597699c834/resources/extruders/alfawise_u30_extruder_0.def.json
```
Et voilà, ce devrait être bon, bonne impression !

View file

@ -0,0 +1,183 @@
---
layout: post
slug: le-protocole-shoutcast-au-scalpel
status: published
sitemap: true
title: Le protocole Shoutcast au scalpel
description: Découpage de flux HTTP
categories:
- network
tags:
---
Shoutcast est un protocole de diffusion pour webradio créé par Nullsoft, l'éditeur de Winamp entre autre. Il repose sur des technologies bien établies car au final il s'agit principalement de diffuser un fichier en MP3 en continu à travers le protocole HTTP. Il est donc possible d'écouter simplement une radio diffusé via Shoutcast en copiant son lien dans le navigateur. Nous allons prendre pour exemple ce flux :
```
http://streaming.radionti.com:80/nti-320.mp3
```
Alors qu'apporte Shoutcast de plus ? Tout d'abord il standardise des en-têtes HTTP, ce qui est bien pratique car l'on peut écrire un lecteur qui fonctionnera avec plein de flux. Mais regardons de plus prêt ces en-têtes :
```
$ curl -vvv http://streaming.radionti.com:80/nti-320.mp3
* Trying 51.15.166.151...
* TCP_NODELAY set
* Connected to streaming.radionti.com (51.15.166.151) port 80 (#0)
> GET /nti-320.mp3 HTTP/1.1
> Host: streaming.radionti.com
> User-Agent: curl/7.64.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Server: Icecast 2.4.2
< Date: Tue, 19 Dec 2017 21:45:23 GMT
< Content-Type: audio/mpeg
< Cache-Control: no-cache
< Expires: Mon, 26 Jul 1997 05:00:00 GMT
< Pragma: no-cache
< Access-Control-Allow-Origin: *
< icy-br:320
< icy-description:NTI - LA NOUVELLE TENDANCE (FRANCE)
< icy-genre:DANCE & EDM
< icy-name:NTI - LA NOUVELLE TENDANCE (FRANCE)
< icy-pub:0
< icy-url:http://www.radionti.com
```
On reconnait facilement les en-têtes définies par le protocole Shoutcast car elles sont préfixées par `icy-`. On peut retrouver des informations sur la radio, mais pas le titre de la musique qui est en train d'être joué. Et pour comprendre pourquoi, il faut se plonger un peu dans HTTP.
## Le streaming HTTP
On commence rarement par aborder HTTP à travers le streaming. Généralement, on fait une requête HTTP, on attend de recevoir la réponse dans sa totalité, si possible dans une magnifique chaine de caractère, on ferme la connexion HTTP et seulement ensuite on traite l'information.
Oui, mais là on veut diffuser de la musique en continu ! Alors on pourrait envoyer des morceaux de musique de quelques secondes par requete et répéter le processus suivant en boucle. Mais ouvrir et fermer plein de connexion est couteux, peu optimisé et introduirait un décalage peu utile.
Nous allons donc utiliser une seule connexion qui ne se fermera jamais, qui va nous envoyer de la donnée en continue, au fur et à mesure qu'elle arrive. Ce qui veut également dire que l'on devra gérer en même temps la connexion et le player audio, faire un petit peu de chaque.
Mais vu que l'on ouvre la connexion qu'une seule fois, les en-têtes ne seront envoyées qu'une seule fois ! Et une fois que l'on a commencé à transférer des données, impossible d'envoyer de nouvelles en-têtes !
Alors comment fait-on ? Dans les données que le serveur Shoutcast va envoyer, se trouvera un mélange de flux audio et de méta donnée. Ce sera alors au client de séparer les deux et de rédiger le flux audio vers le lecteur audio et les méta données vers l'interface utilisateur.
## Icy Metadata
Par défaut et pour des raisons de compatibilité, seul le flux audio est envoyé. Pour obtenir les méta données en plus, il est nécessaire de le demander explicitement au moment où l'on réalise la requête. En échange, le serveur va nous fournir une nouvelle en-tête `icy-metaint` qui nous informera à quelle fréquence les méta données seront envoyées. Plus exactement, tous les combiens d'octets de musique envoyés se trouveront ces méta données.
Une fois arrivée aux métadonnées, on commence par lire un octet. En le multipliant par 16, on peut en déduire la taille totale des métadonnées à lire. Une fois ces méta données lues, on recommence à compter `icy-metaint` octets, etc.
Tout ça peut paraitre très abstrait, pourtant avec quelques lignes de python et des streams on peut s'en sortir sans trop de mal !
## Python, Streams et découpage de webradios
Nous allons commencer avec ce squelette qui nous est un peu imposé par asyncio:
```python
import asyncio
async def icy():
pass
asyncio.run(icy())
```
Nous n'utiliserons pas de bibliothèque HTTP mais une simple connexion TCP pour bien comprendre comment ça se passe dans les niveaux en dessous. Nous utiliserons également les objets [Streams de Python](https://docs.python.org/3/library/asyncio-stream.html) qui semblent appropriés pour résoudre notre problème.
Nous allons commencer par nous connecter au serveur et lui demander le stream qui nous intéresse :
```python
async def icy():
reader, writer = await asyncio.open_connection('streaming.radionti.com', 80)
writer.write(b"""GET /nti-320.mp3 HTTP/1.0
Host: streaming.radionti.com
User-Agent: Icy-Test
Accept: */*
Icy-Metadata: 1
""")
```
On commence par ouvrire une connexion TCP vers l'URL et le port du serveur.
Ensuite, on utilise l'objet `writer` pour envoyer notre requête HTTP.
Pour rappel, HTTP est un protocole texte. La première ligne permet d'indiquer le verbe HTTP, la page ainsi que la version du protocole. Les lignes suivantes sont des en-tête au format clé valeur, une par ligne, séparées par deux points.
Nous avons justement fait attention à préciser l'entête `Icy-Metadata: 1` pour demander un flux audio mélangé avec des métadonnées (sinon, nous n'aurions eu que le flux audio sans les métadonnées !).
La ligne vide indique la fin de l'envoie des en-têtes.
Puisque nous envoyons une requête GET, nous n'avons pas de données à envoyer.
Le serveur sait alors qu'il peut commencer à générer la réponse.
Nous allons donc pouvoir nous préparer à analyser la réponse que le serveur va nous faire.
Mais permettons-nous d'écrire une petite fonction utilitaire pour extraire les en-têtes renvoyées par le serveur :
```python
async def readHeaders(reader):
status_code = await reader.readline()
assert (status_code, b'HTTP/1.0 200 OK\r\n')
headers = {}
while True:
data = await reader.readline()
if data == b'\r\n':
print("End of metadata part, all key/value headers have been read")
return headers
header_name, header_value = data.split(b':', 1)
headers[header_name] = header_value
```
Tout d'abord, la première ligne est un peu particulière. On vérifie que le protocole correspond, que le code de status est bien 200 (qui veut dire OK, tout s'est bien passé).
Ensuite, nous récuperons les en-têtes envoyées par le serveur sous le même format que celles que nous avons envoyées ! Une ligne vide indique également la fin des en-têtes, et dans notre cas le début du contenu que nous avons demandé !
Armés de cette fonction, complétons notre fonction `icy` pour les récupérer, et surtout récupérer l'en-tête qui nous intéresse, `icy-metaint` qui nous indiquera comment découper notre flux !
```python
async def icy():
# ...
headers = await readHeaders(reader)
metaint = int(headers[b'icy-metaint'])
```
Nous avons donc stocké dans la variable `metaint` le nombre d'octets d'audio à lire dans le flux envoyé par notre serveur Shoutcast.
Maintenant que nous avons toutes les informations dont nous avons besoin, récupérons ce flux et découpons le !
```python
async def icy():
# ...
while True:
audio = await reader.readexactly(metaint)
metadata_size_raw = await reader.readexactly(1)
metadata_size_bytes = \
16 * int.from_bytes(metadata_size_raw, "big")
metadata_content = \
await reader.readexactly(metadata_size)
if metadata_size > 0:
print(metadata_content)
```
Nous y voilà ! On récupère exactement le nombre de bytes indiqués par l'en-tête. Puis nous lisons un octet, qui va nous permettre de calculer la taille des métadonnées, nous allons lire exactement ce nombre d'octets. Les méta données sont du simple texte, donc on les affiche. Ici, on ne fait rien avec l'audio, mais il faudrait écrire le contenu de audio dans notre lecteur. Ce dernier fournirait probablement un Stream dans lequel on pourrait écrire le contenu de la variable. Il suffit ensuite de répéter cette action en boucle.
**Attention ! Les métadonnées seront la plupart du temps vides. En effet, pas besoin de renvoyer le titre de la chanson quand il n'a pas changé. Donc il sera envoyé uniquement au lancement du stream puis à chaque changement de chanson. Le reste du temps, les métadonnées indiqueront que leur taille est de zéro.**
Et voilà la sortie de notre application :
```
$ python3 /tmp/icy.py
End of metadata part, all key/value headers have been read
b"StreamTitle='SOPHIE ELLIS BEXTOR - MURDER ON THE DANCEFLOOR - 2002';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='QUINTINO - CAN'T BRING ME DOWN - 2019';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='DIRTY VEGAS - WHY DID YOU DO IT 2K19';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='FMZ - GET DOWN TO THIS - 2010';\x00\x00\x00\x00"
b"StreamTitle='MOSIMANN + MARUV - MON AMOUR - 2019';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='DUA LIPA - SWAN SONG (REMIX) 2019';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='MILK & SUGAR - LOVE IS IN THE AIR 2K19';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='PAPA ZEUS - ABOUT YOU - 2019';\x00\x00\x00\x00\x00"
b"StreamTitle='DON DIABLO - BRAVE - 2019';\x00\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='MARTIN GARRIX - SUMMER DAYS - 2019';\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='DEORRO - FIVE HOURS - 2014';\x00\x00\x00\x00\x00\x00\x00"
b"StreamTitle='INTERNATIONAL DRINKING PARTY - DESOLEE - 2017';\x00\x00\x00\x00"
```
Le code complet de l'exemple se trouve dans ce fichier [icy.py](/assets/code/icy.py).

37
assets/code/icy.py Normal file
View file

@ -0,0 +1,37 @@
import asyncio
async def readHeaders(reader):
status_code = await reader.readline()
assert status_code, b'HTTP/1.0 200 OK\r\n'
headers = {}
while True:
data = await reader.readline()
if data == b'\r\n':
print("End of metadata part, all key/value headers have been read")
return headers
header_name, header_value = data.split(b':', 1)
headers[header_name] = header_value
async def icy():
reader, writer = await asyncio.open_connection('streaming.radionti.com', 80)
writer.write(b"""GET /nti-320.mp3 HTTP/1.0
Host: streaming.radionti.com
User-Agent: Icy-Test
Accept: */*
Icy-Metadata: 1
""")
headers = await readHeaders(reader)
metaint = int(headers[b'icy-metaint'])
while True:
audio = await reader.readexactly(metaint)
metadata_size = int.from_bytes(await reader.readexactly(1), "big") * 16
metadata_content = await reader.readexactly(metadata_size)
if metadata_size > 0: print(metadata_content)
asyncio.run(icy())

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,016 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

25
cours/index.md Normal file
View file

@ -0,0 +1,25 @@
---
title: Cours
profile: false
---
### Développement logiciel pour le Cloud (TLC)
Slides:
* [Slides 1: Introduction to cloud computing](tlc-course-1.pdf)
* [Slides 2: Microservices](tlc-course-2.pdf)
* [Slides 3: Analytics](tlc-course-3.pdf)
* [Slides 4: Data Management](tlc-course-4.pdf)
* [Slide 4.1 : DHT (Pastry)](tlc-course-4.1.pdf)
* [Slide 4.2 : NoSQL (Dynamo DB)](tlc-course-4.2.pdf)
Travaux dirigés :
* [TD1 : Base de données NoSQL : Google Datastore](td-datastore.pdf)
Lab:
* [Lab 1: Découverte d'un PaaS : Google AppEngine](tlc-tp1.pdf)
* [Lab 2: Découverte d'un orchestrateur de conteneurs : Kubernetes](tlc-tp2.pdf)
* [Lab 3: Découverte d'un IaaS : AWS EC2](tlc-tp3.pdf)

BIN
cours/td-datastore.pdf Normal file

Binary file not shown.

BIN
cours/tlc-course-1.pdf Normal file

Binary file not shown.

BIN
cours/tlc-course-2.pdf Normal file

Binary file not shown.

BIN
cours/tlc-course-3.pdf Normal file

Binary file not shown.

BIN
cours/tlc-course-4.1.pdf Normal file

Binary file not shown.

BIN
cours/tlc-course-4.2.pdf Normal file

Binary file not shown.

BIN
cours/tlc-course-4.pdf Normal file

Binary file not shown.

BIN
cours/tlc-tp1.pdf Normal file

Binary file not shown.

BIN
cours/tlc-tp2.pdf Normal file

Binary file not shown.

BIN
cours/tlc-tp3.pdf Normal file

Binary file not shown.

2
robots.txt Normal file
View file

@ -0,0 +1,2 @@
User-agent: *
Disallow: /cours/